2026-04-29 01:30:21 +00:00
|
|
|
import { dirname, fromFileUrl, join, resolve } from '@std/path';
|
|
|
|
|
|
|
|
|
|
import { Onebox } from '../../../onebox/ts/classes/onebox.ts';
|
2026-05-24 06:00:23 +00:00
|
|
|
import { disableManagedDcRouterForScenario } from '../onebox-test-helpers.ts';
|
2026-04-29 01:30:21 +00:00
|
|
|
|
|
|
|
|
const scenarioName = 'onebox-basic-lifecycle';
|
|
|
|
|
const smokeId = `onebox-basic-${Date.now().toString(36)}`;
|
|
|
|
|
const testingDir = resolve(dirname(fromFileUrl(import.meta.url)), '../..');
|
|
|
|
|
const buildDir = join(testingDir, '.nogit', scenarioName, smokeId);
|
|
|
|
|
const serviceName = `app-${Date.now().toString(36)}`;
|
|
|
|
|
const dockerServiceName = `onebox-${serviceName}`;
|
|
|
|
|
const routeDomain = `${serviceName}.test`;
|
2026-05-24 06:00:23 +00:00
|
|
|
const volumeName = `onebox-${serviceName}-data`;
|
2026-04-29 01:30:21 +00:00
|
|
|
|
|
|
|
|
const delayFor = async (millisecondsArg: number) => {
|
|
|
|
|
await new Promise((resolveArg) => setTimeout(resolveArg, millisecondsArg));
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-08 16:24:45 +00:00
|
|
|
const outputCommand = async (
|
|
|
|
|
commandArg: string,
|
|
|
|
|
argsArg: string[],
|
|
|
|
|
optionsArg: Pick<Deno.CommandOptions, 'stdout' | 'stderr'>,
|
|
|
|
|
timeoutMsArg = 30000,
|
|
|
|
|
) => {
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMsArg);
|
|
|
|
|
try {
|
|
|
|
|
const command = new Deno.Command(commandArg, {
|
|
|
|
|
args: argsArg,
|
|
|
|
|
signal: controller.signal,
|
|
|
|
|
...optionsArg,
|
|
|
|
|
});
|
|
|
|
|
return await command.output();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if ((error as Error).name === 'AbortError') {
|
|
|
|
|
throw new Error(`${commandArg} ${argsArg.join(' ')} timed out after ${timeoutMsArg}ms`);
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-29 01:30:21 +00:00
|
|
|
const run = async (commandArg: string, argsArg: string[]) => {
|
2026-05-08 16:24:45 +00:00
|
|
|
const output = await outputCommand(commandArg, argsArg, {
|
2026-04-29 01:30:21 +00:00
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'piped',
|
|
|
|
|
});
|
|
|
|
|
const stdout = new TextDecoder().decode(output.stdout).trim();
|
|
|
|
|
const stderr = new TextDecoder().decode(output.stderr).trim();
|
|
|
|
|
if (stdout) {
|
|
|
|
|
console.log(stdout);
|
|
|
|
|
}
|
|
|
|
|
if (stderr) {
|
|
|
|
|
console.log(stderr);
|
|
|
|
|
}
|
|
|
|
|
if (!output.success) {
|
|
|
|
|
throw new Error(`${commandArg} ${argsArg.join(' ')} exited with ${output.code}`);
|
|
|
|
|
}
|
|
|
|
|
return { stdout, stderr };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const waitFor = async (checkFunctionArg: () => boolean | Promise<boolean>, messageArg: string) => {
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
while (Date.now() - startTime < 120000) {
|
|
|
|
|
if (await checkFunctionArg()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await delayFor(500);
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`Timed out waiting for ${messageArg}`);
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-24 06:00:23 +00:00
|
|
|
const getFreeTcpPort = () => {
|
|
|
|
|
const listener = Deno.listen({ hostname: '127.0.0.1', port: 0 });
|
|
|
|
|
const port = (listener.addr as Deno.NetAddr).port;
|
|
|
|
|
listener.close();
|
|
|
|
|
return port;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-29 01:30:21 +00:00
|
|
|
const dockerServiceExists = async (serviceNameArg: string) => {
|
2026-05-08 16:24:45 +00:00
|
|
|
const output = await outputCommand('docker', ['service', 'inspect', serviceNameArg], {
|
2026-04-29 01:30:21 +00:00
|
|
|
stdout: 'null',
|
|
|
|
|
stderr: 'null',
|
2026-05-08 16:24:45 +00:00
|
|
|
}, 15000);
|
|
|
|
|
return output.success;
|
2026-04-29 01:30:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeDockerService = async (serviceNameArg: string) => {
|
|
|
|
|
if (await dockerServiceExists(serviceNameArg)) {
|
|
|
|
|
await run('docker', ['service', 'rm', serviceNameArg]).catch(() => null);
|
|
|
|
|
await delayFor(2000);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-24 06:00:23 +00:00
|
|
|
const removeDockerVolume = async (volumeNameArg: string) => {
|
|
|
|
|
let lastErrorText = '';
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
while (Date.now() - startTime < 30000) {
|
|
|
|
|
const output = await outputCommand(
|
|
|
|
|
'docker',
|
|
|
|
|
['volume', 'rm', '-f', volumeNameArg],
|
|
|
|
|
{
|
|
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'piped',
|
|
|
|
|
},
|
|
|
|
|
15000,
|
|
|
|
|
);
|
|
|
|
|
if (output.success) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
lastErrorText = new TextDecoder().decode(output.stderr).trim();
|
|
|
|
|
await delayFor(1000);
|
|
|
|
|
}
|
|
|
|
|
console.log(
|
|
|
|
|
`[${scenarioName}] Failed to remove Docker volume ${volumeNameArg}: ${lastErrorText}`,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-29 01:30:21 +00:00
|
|
|
const assertNoPreexistingOneboxIngress = async () => {
|
|
|
|
|
if (await dockerServiceExists('onebox-smartproxy')) {
|
2026-05-24 06:00:23 +00:00
|
|
|
throw new Error(
|
|
|
|
|
'onebox-smartproxy already exists; refusing to overwrite a running Onebox ingress service',
|
|
|
|
|
);
|
2026-04-29 01:30:21 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const waitForDockerServiceRunning = async (serviceNameArg: string) => {
|
|
|
|
|
await waitFor(async () => {
|
2026-05-08 16:24:45 +00:00
|
|
|
const output = await outputCommand('docker', [
|
|
|
|
|
'service',
|
|
|
|
|
'ps',
|
|
|
|
|
serviceNameArg,
|
|
|
|
|
'--format',
|
|
|
|
|
'{{.CurrentState}}',
|
|
|
|
|
], {
|
2026-04-29 01:30:21 +00:00
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'null',
|
2026-05-08 16:24:45 +00:00
|
|
|
}, 15000);
|
2026-04-29 01:30:21 +00:00
|
|
|
if (!output.success) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return new TextDecoder().decode(output.stdout).includes('Running');
|
|
|
|
|
}, `${serviceNameArg} running task`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const waitForDockerServiceRemoved = async (serviceNameArg: string) => {
|
2026-05-24 06:00:23 +00:00
|
|
|
await waitFor(
|
|
|
|
|
async () => !(await dockerServiceExists(serviceNameArg)),
|
|
|
|
|
`${serviceNameArg} removal`,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const inspectDockerServiceJson = async <T>(serviceNameArg: string, formatArg: string) => {
|
|
|
|
|
const output = await outputCommand('docker', [
|
|
|
|
|
'service',
|
|
|
|
|
'inspect',
|
|
|
|
|
serviceNameArg,
|
|
|
|
|
'--format',
|
|
|
|
|
formatArg,
|
|
|
|
|
], {
|
|
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'piped',
|
|
|
|
|
}, 15000);
|
|
|
|
|
if (!output.success) {
|
|
|
|
|
throw new Error(`docker service inspect ${serviceNameArg} exited with ${output.code}`);
|
|
|
|
|
}
|
|
|
|
|
return JSON.parse(new TextDecoder().decode(output.stdout).trim()) as T;
|
2026-04-29 01:30:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const requestRoute = async (protocolArg: 'http' | 'https', portArg: number) => {
|
|
|
|
|
const curlArgs = [
|
|
|
|
|
'-sS',
|
|
|
|
|
'--noproxy',
|
|
|
|
|
'*',
|
|
|
|
|
'--max-time',
|
|
|
|
|
'10',
|
|
|
|
|
'--resolve',
|
|
|
|
|
`${routeDomain}:${portArg}:127.0.0.1`,
|
|
|
|
|
'-o',
|
|
|
|
|
'-',
|
|
|
|
|
'-w',
|
|
|
|
|
'\n%{http_code}',
|
|
|
|
|
`${protocolArg}://${routeDomain}:${portArg}/`,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (protocolArg === 'https') {
|
|
|
|
|
curlArgs.unshift('-k');
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 16:24:45 +00:00
|
|
|
const output = await outputCommand('curl', curlArgs, {
|
2026-04-29 01:30:21 +00:00
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'piped',
|
2026-05-08 16:24:45 +00:00
|
|
|
}, 15000);
|
2026-04-29 01:30:21 +00:00
|
|
|
const stdout = new TextDecoder().decode(output.stdout).trim();
|
|
|
|
|
const stderr = new TextDecoder().decode(output.stderr).trim();
|
|
|
|
|
if (!output.success) {
|
|
|
|
|
throw new Error(`curl failed: ${stderr || stdout}`);
|
|
|
|
|
}
|
|
|
|
|
const lines = stdout.split('\n');
|
|
|
|
|
const statusCode = lines[lines.length - 1];
|
|
|
|
|
const body = lines.slice(0, -1).join('\n');
|
|
|
|
|
return { statusCode, body, stderr };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const waitForRoute = async (protocolArg: 'http' | 'https', portArg: number) => {
|
|
|
|
|
let lastResponse: Awaited<ReturnType<typeof requestRoute>> | undefined;
|
|
|
|
|
let lastError: Error | undefined;
|
|
|
|
|
try {
|
|
|
|
|
await waitFor(async () => {
|
|
|
|
|
try {
|
|
|
|
|
lastResponse = await requestRoute(protocolArg, portArg);
|
|
|
|
|
lastError = undefined;
|
|
|
|
|
return lastResponse.statusCode === '200' && /Caddy|serve/i.test(lastResponse.body);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
lastError = error as Error;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}, `${protocolArg.toUpperCase()} route through Onebox ingress`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(`[${scenarioName}] Last route response: ${JSON.stringify(lastResponse)}`);
|
|
|
|
|
if (lastError) {
|
|
|
|
|
console.log(`[${scenarioName}] Last route error: ${lastError.message}`);
|
|
|
|
|
}
|
|
|
|
|
await run('docker', ['service', 'ps', 'onebox-smartproxy']).catch(() => null);
|
2026-05-24 06:00:23 +00:00
|
|
|
await run('docker', ['service', 'logs', '--raw', '--tail', '120', 'onebox-smartproxy']).catch(
|
|
|
|
|
() => null,
|
|
|
|
|
);
|
2026-04-29 01:30:21 +00:00
|
|
|
await run('docker', ['service', 'ps', dockerServiceName]).catch(() => null);
|
2026-05-24 06:00:23 +00:00
|
|
|
await run('docker', ['service', 'logs', '--raw', '--tail', '120', dockerServiceName]).catch(
|
|
|
|
|
() => null,
|
|
|
|
|
);
|
2026-04-29 01:30:21 +00:00
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createSelfSignedCertificate = async () => {
|
|
|
|
|
const keyPath = join(buildDir, 'route.key');
|
|
|
|
|
const certPath = join(buildDir, 'route.crt');
|
|
|
|
|
await run('openssl', [
|
|
|
|
|
'req',
|
|
|
|
|
'-x509',
|
|
|
|
|
'-newkey',
|
|
|
|
|
'rsa:2048',
|
|
|
|
|
'-nodes',
|
|
|
|
|
'-keyout',
|
|
|
|
|
keyPath,
|
|
|
|
|
'-out',
|
|
|
|
|
certPath,
|
|
|
|
|
'-subj',
|
|
|
|
|
`/CN=${routeDomain}`,
|
|
|
|
|
'-addext',
|
|
|
|
|
`subjectAltName=DNS:${routeDomain}`,
|
|
|
|
|
'-days',
|
|
|
|
|
'1',
|
|
|
|
|
]);
|
|
|
|
|
return {
|
|
|
|
|
privateKey: await Deno.readTextFile(keyPath),
|
|
|
|
|
publicKey: await Deno.readTextFile(certPath),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ensureDockerReady = async () => {
|
|
|
|
|
await run('docker', ['version']);
|
|
|
|
|
const { stdout } = await run('docker', ['info', '--format', '{{.Swarm.LocalNodeState}}']);
|
|
|
|
|
if (stdout.trim() !== 'active') {
|
2026-05-24 06:00:23 +00:00
|
|
|
throw new Error(
|
|
|
|
|
'Docker Swarm must be active. In Vagrant this is handled by scripts/provision-vm.sh.',
|
|
|
|
|
);
|
2026-04-29 01:30:21 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const assertOneboxCoreReady = async (oneboxArg: Onebox) => {
|
|
|
|
|
const status = await oneboxArg.getSystemStatus();
|
2026-05-24 06:00:23 +00:00
|
|
|
const reverseProxy = status.reverseProxy as {
|
|
|
|
|
backend: string;
|
|
|
|
|
http: { running: boolean };
|
|
|
|
|
https: { running: boolean };
|
|
|
|
|
};
|
2026-04-29 01:30:21 +00:00
|
|
|
if (reverseProxy.backend !== 'smartproxy-docker') {
|
|
|
|
|
throw new Error(`Unexpected reverse proxy backend: ${JSON.stringify(reverseProxy)}`);
|
|
|
|
|
}
|
|
|
|
|
if (!reverseProxy.http.running || !reverseProxy.https.running) {
|
|
|
|
|
throw new Error(`Reverse proxy not fully running: ${JSON.stringify(reverseProxy)}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const registryStatus = oneboxArg.registry.getStatus();
|
|
|
|
|
if (!registryStatus.running || registryStatus.port !== 4000) {
|
|
|
|
|
throw new Error(`Registry not running: ${JSON.stringify(registryStatus)}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const platformServices = status.platformServices as Array<{ type: string; status: string }>;
|
2026-05-24 06:00:23 +00:00
|
|
|
if (
|
|
|
|
|
!platformServices.some((serviceArg) =>
|
|
|
|
|
serviceArg.type === 'smartproxy' && serviceArg.status === 'running'
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`SmartProxy platform service not reported as running: ${JSON.stringify(platformServices)}`,
|
|
|
|
|
);
|
2026-04-29 01:30:21 +00:00
|
|
|
}
|
|
|
|
|
if (platformServices.some((serviceArg) => serviceArg.type === 'caddy')) {
|
2026-05-24 06:00:23 +00:00
|
|
|
throw new Error(
|
|
|
|
|
`Unexpected legacy caddy platform service: ${JSON.stringify(platformServices)}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const assertDockerStorageAndPorts = async (publishedPortArg: number) => {
|
|
|
|
|
const mounts = await inspectDockerServiceJson<Array<Record<string, unknown>>>(
|
|
|
|
|
dockerServiceName,
|
|
|
|
|
'{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}',
|
|
|
|
|
);
|
|
|
|
|
if (!mounts.some((mountArg) => mountArg.Source === volumeName && mountArg.Target === '/data')) {
|
|
|
|
|
throw new Error(`Onebox volume mount missing from Docker service: ${JSON.stringify(mounts)}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ports = await inspectDockerServiceJson<Array<Record<string, unknown>>>(
|
|
|
|
|
dockerServiceName,
|
|
|
|
|
'{{json .Spec.EndpointSpec.Ports}}',
|
|
|
|
|
);
|
|
|
|
|
if (
|
|
|
|
|
!ports.some((portArg) =>
|
|
|
|
|
portArg.TargetPort === 80 && portArg.PublishedPort === publishedPortArg &&
|
|
|
|
|
portArg.Protocol === 'tcp'
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error(`Onebox published port missing from Docker service: ${JSON.stringify(ports)}`);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const assertServicePersistence = (oneboxArg: Onebox, publishedPortArg: number) => {
|
|
|
|
|
const service = oneboxArg.services.getService(serviceName);
|
|
|
|
|
if (!service) {
|
|
|
|
|
throw new Error(`Service was not persisted: ${serviceName}`);
|
|
|
|
|
}
|
|
|
|
|
if (!service.volumes?.some((volumeArg) => volumeArg.mountPath === '/data')) {
|
|
|
|
|
throw new Error(`Service volume was not persisted: ${JSON.stringify(service)}`);
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
!service.publishedPorts?.some((portArg) =>
|
|
|
|
|
portArg.targetPort === 80 && portArg.publishedPort === publishedPortArg
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error(`Service published port was not persisted: ${JSON.stringify(service)}`);
|
2026-04-29 01:30:21 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const main = async () => {
|
|
|
|
|
let onebox: Onebox | undefined;
|
|
|
|
|
let deployedService = false;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await ensureDockerReady();
|
|
|
|
|
await assertNoPreexistingOneboxIngress();
|
|
|
|
|
await Deno.mkdir(buildDir, { recursive: true });
|
|
|
|
|
Deno.chdir(buildDir);
|
|
|
|
|
Deno.env.set('ONEBOX_DEV', 'true');
|
|
|
|
|
|
|
|
|
|
console.log(`[${scenarioName}] Starting Onebox from ${buildDir}`);
|
|
|
|
|
onebox = new Onebox();
|
2026-05-24 06:00:23 +00:00
|
|
|
disableManagedDcRouterForScenario(onebox);
|
2026-04-29 01:30:21 +00:00
|
|
|
await onebox.init();
|
|
|
|
|
|
|
|
|
|
await waitForDockerServiceRunning('onebox-smartproxy');
|
|
|
|
|
await assertOneboxCoreReady(onebox);
|
|
|
|
|
|
|
|
|
|
console.log(`[${scenarioName}] Deploying workload ${serviceName}`);
|
2026-05-24 06:00:23 +00:00
|
|
|
const publishedPort = getFreeTcpPort();
|
2026-04-29 01:30:21 +00:00
|
|
|
const service = await onebox.services.deployService({
|
|
|
|
|
name: serviceName,
|
|
|
|
|
image: 'caddy:2-alpine',
|
|
|
|
|
port: 80,
|
|
|
|
|
domain: routeDomain,
|
|
|
|
|
autoDNS: false,
|
|
|
|
|
envVars: {},
|
2026-05-24 06:00:23 +00:00
|
|
|
volumes: [{ mountPath: '/data', backup: true }],
|
|
|
|
|
publishedPorts: [{ targetPort: 80, publishedPort, protocol: 'tcp' }],
|
2026-04-29 01:30:21 +00:00
|
|
|
});
|
|
|
|
|
deployedService = true;
|
|
|
|
|
|
|
|
|
|
if (service.status !== 'running' || service.domain !== routeDomain) {
|
|
|
|
|
throw new Error(`Unexpected deployed service state: ${JSON.stringify(service)}`);
|
|
|
|
|
}
|
|
|
|
|
if (!onebox.services.listServices().some((serviceArg) => serviceArg.name === serviceName)) {
|
|
|
|
|
throw new Error('Deployed service not present in Onebox service list');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await waitForDockerServiceRunning(dockerServiceName);
|
2026-05-24 06:00:23 +00:00
|
|
|
assertServicePersistence(onebox, publishedPort);
|
|
|
|
|
await assertDockerStorageAndPorts(publishedPort);
|
2026-04-29 01:30:21 +00:00
|
|
|
await waitForRoute('http', 8080);
|
|
|
|
|
|
|
|
|
|
const certificate = await createSelfSignedCertificate();
|
2026-05-24 06:00:23 +00:00
|
|
|
await onebox.reverseProxy.addCertificate(
|
|
|
|
|
routeDomain,
|
|
|
|
|
certificate.publicKey,
|
|
|
|
|
certificate.privateKey,
|
|
|
|
|
);
|
2026-04-29 01:30:21 +00:00
|
|
|
await waitForRoute('https', 8443);
|
|
|
|
|
|
|
|
|
|
console.log(`[${scenarioName}] Removing workload ${serviceName}`);
|
|
|
|
|
await onebox.services.removeService(serviceName);
|
|
|
|
|
deployedService = false;
|
|
|
|
|
await waitForDockerServiceRemoved(dockerServiceName);
|
|
|
|
|
if (onebox.services.getService(serviceName)) {
|
|
|
|
|
throw new Error('Removed service still present in Onebox database');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[${scenarioName}] Scenario passed`);
|
|
|
|
|
} finally {
|
|
|
|
|
if (onebox && deployedService) {
|
|
|
|
|
await onebox.services.removeService(serviceName).catch((error) => {
|
2026-05-24 06:00:23 +00:00
|
|
|
console.log(
|
|
|
|
|
`[${scenarioName}] Failed to remove Onebox service: ${(error as Error).message}`,
|
|
|
|
|
);
|
2026-04-29 01:30:21 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (onebox) {
|
|
|
|
|
await onebox.shutdown().catch((error) => {
|
|
|
|
|
console.log(`[${scenarioName}] Failed to shut down Onebox: ${(error as Error).message}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
await removeDockerService(dockerServiceName);
|
|
|
|
|
await removeDockerService('onebox-smartproxy');
|
2026-05-24 06:00:23 +00:00
|
|
|
await removeDockerVolume(volumeName);
|
2026-04-29 01:30:21 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-08 16:24:45 +00:00
|
|
|
try {
|
|
|
|
|
await main();
|
|
|
|
|
Deno.exit(0);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
Deno.exit(1);
|
|
|
|
|
}
|