Refactor local memory and role-fixed runtime routing
This commit is contained in:
209
setup/legacy-service-guard.ts
Normal file
209
setup/legacy-service-guard.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import type { ServiceManager } from './platform.js';
|
||||
import { getLegacyServiceDefs } from './service-defs.js';
|
||||
import {
|
||||
checkLaunchdServiceArtifact,
|
||||
checkNohupServiceArtifact,
|
||||
checkSystemdServiceInScope,
|
||||
type ServiceCheck,
|
||||
type SystemdScope,
|
||||
} from './verify-services.js';
|
||||
|
||||
type LegacySource = 'launchd' | 'systemd-system' | 'systemd-user' | 'nohup';
|
||||
|
||||
export interface LegacyServiceIssue extends ServiceCheck {
|
||||
sources: LegacySource[];
|
||||
}
|
||||
|
||||
function summarizeLegacyStatus(statuses: ServiceCheck['status'][]): ServiceCheck['status'] {
|
||||
if (statuses.includes('running')) {
|
||||
return 'running';
|
||||
}
|
||||
if (statuses.includes('stopped')) {
|
||||
return 'stopped';
|
||||
}
|
||||
return 'not_found';
|
||||
}
|
||||
|
||||
export function detectLegacyServiceIssues(
|
||||
projectRoot: string,
|
||||
serviceManager: ServiceManager,
|
||||
homeDir = os.homedir(),
|
||||
): LegacyServiceIssue[] {
|
||||
return getLegacyServiceDefs(projectRoot)
|
||||
.map((def) => {
|
||||
if (serviceManager === 'launchd') {
|
||||
const status = checkLaunchdServiceArtifact(
|
||||
def.launchdLabel,
|
||||
path.join(homeDir, 'Library', 'LaunchAgents', `${def.launchdLabel}.plist`),
|
||||
);
|
||||
if (status === 'not_found') return null;
|
||||
return {
|
||||
name: def.name,
|
||||
status,
|
||||
sources: ['launchd'] as LegacySource[],
|
||||
};
|
||||
}
|
||||
|
||||
if (serviceManager === 'systemd') {
|
||||
const scopedChecks: Array<{ source: LegacySource; status: ServiceCheck['status'] }> = [
|
||||
{
|
||||
source: 'systemd-system',
|
||||
status: checkSystemdServiceInScope(def.name, 'system'),
|
||||
},
|
||||
{
|
||||
source: 'systemd-user',
|
||||
status: checkSystemdServiceInScope(def.name, 'user'),
|
||||
},
|
||||
{
|
||||
source: 'nohup',
|
||||
status: checkNohupServiceArtifact(projectRoot, def.name),
|
||||
},
|
||||
];
|
||||
const detected = scopedChecks.filter(
|
||||
(current) => current.status !== 'not_found',
|
||||
);
|
||||
if (detected.length === 0) return null;
|
||||
return {
|
||||
name: def.name,
|
||||
status: summarizeLegacyStatus(detected.map((current) => current.status)),
|
||||
sources: detected.map((current) => current.source),
|
||||
};
|
||||
}
|
||||
|
||||
const status = checkNohupServiceArtifact(projectRoot, def.name);
|
||||
if (status === 'not_found') return null;
|
||||
return {
|
||||
name: def.name,
|
||||
status,
|
||||
sources: ['nohup'] as LegacySource[],
|
||||
};
|
||||
})
|
||||
.filter((service): service is LegacyServiceIssue => Boolean(service));
|
||||
}
|
||||
|
||||
function formatSystemdCleanupForScope(
|
||||
serviceNames: string[],
|
||||
homeDir: string,
|
||||
scope: SystemdScope,
|
||||
): string[] {
|
||||
if (serviceNames.length === 0) return [];
|
||||
const systemctlPrefix = scope === 'system' ? 'systemctl' : 'systemctl --user';
|
||||
const unitDir =
|
||||
scope === 'system'
|
||||
? '/etc/systemd/system'
|
||||
: path.join(homeDir, '.config', 'systemd', 'user');
|
||||
const unitPaths = serviceNames
|
||||
.map((serviceName) =>
|
||||
JSON.stringify(path.join(unitDir, `${serviceName}.service`)),
|
||||
)
|
||||
.join(' ');
|
||||
|
||||
return [
|
||||
`${systemctlPrefix} disable --now ${serviceNames.join(' ')}`,
|
||||
`rm -f ${unitPaths}`,
|
||||
`${systemctlPrefix} daemon-reload`,
|
||||
];
|
||||
}
|
||||
|
||||
function formatLaunchdCleanup(serviceNames: string[], homeDir: string): string {
|
||||
if (serviceNames.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const plistPaths = serviceNames.map((serviceName) => {
|
||||
const label = serviceName === 'ejclaw-codex'
|
||||
? 'com.ejclaw-codex'
|
||||
: 'com.ejclaw-review';
|
||||
return path.join(homeDir, 'Library', 'LaunchAgents', `${label}.plist`);
|
||||
});
|
||||
|
||||
return [
|
||||
...plistPaths.map(
|
||||
(plistPath) =>
|
||||
`launchctl unload ${JSON.stringify(plistPath)} 2>/dev/null || true`,
|
||||
),
|
||||
`rm -f ${plistPaths.map((plistPath) => JSON.stringify(plistPath)).join(' ')}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function formatNohupCleanup(
|
||||
projectRoot: string,
|
||||
serviceNames: string[],
|
||||
): string {
|
||||
if (serviceNames.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const pidPaths = serviceNames
|
||||
.map((serviceName) => path.join(projectRoot, `${serviceName}.pid`))
|
||||
.map((currentPath) => JSON.stringify(currentPath));
|
||||
const wrapperPaths = serviceNames
|
||||
.map((serviceName) => path.join(projectRoot, `start-${serviceName}.sh`))
|
||||
.map((currentPath) => JSON.stringify(currentPath));
|
||||
|
||||
return [
|
||||
...pidPaths.map((pidPath) => `pkill -F ${pidPath} 2>/dev/null || true`),
|
||||
`rm -f ${[...pidPaths, ...wrapperPaths].join(' ')}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function formatLegacyServiceFailureMessage(args: {
|
||||
projectRoot: string;
|
||||
serviceManager: ServiceManager;
|
||||
services: LegacyServiceIssue[];
|
||||
homeDir?: string;
|
||||
}): string {
|
||||
const homeDir = args.homeDir ?? os.homedir();
|
||||
const details = args.services
|
||||
.map(
|
||||
(service) =>
|
||||
`${service.name}=${service.status} [${service.sources.join(',')}]`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
let cleanupLines: string[] = [];
|
||||
if (args.serviceManager === 'launchd') {
|
||||
cleanupLines = formatLaunchdCleanup(
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('launchd'))
|
||||
.map((service) => service.name),
|
||||
homeDir,
|
||||
).split('\n');
|
||||
} else if (args.serviceManager === 'systemd') {
|
||||
cleanupLines = [
|
||||
...formatSystemdCleanupForScope(
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('systemd-system'))
|
||||
.map((service) => service.name),
|
||||
homeDir,
|
||||
'system',
|
||||
),
|
||||
...formatSystemdCleanupForScope(
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('systemd-user'))
|
||||
.map((service) => service.name),
|
||||
homeDir,
|
||||
'user',
|
||||
),
|
||||
...formatNohupCleanup(
|
||||
args.projectRoot,
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('nohup'))
|
||||
.map((service) => service.name),
|
||||
).split('\n'),
|
||||
].filter(Boolean);
|
||||
} else {
|
||||
cleanupLines = formatNohupCleanup(
|
||||
args.projectRoot,
|
||||
args.services.map((service) => service.name),
|
||||
).split('\n');
|
||||
}
|
||||
|
||||
return [
|
||||
`Legacy EJClaw multi-service install detected: ${details}`,
|
||||
'This setup is reinstall-only. Remove the legacy services before continuing.',
|
||||
'Suggested cleanup:',
|
||||
...cleanupLines,
|
||||
].join('\n');
|
||||
}
|
||||
Reference in New Issue
Block a user