mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-06 23:47:17 +00:00
109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
import { execFileSync } from 'node:child_process';
|
|
|
|
export function shellQuote(value) {
|
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
}
|
|
|
|
export function runRemote(cloudHost, command) {
|
|
return execFileSync('ssh', [cloudHost, command], {
|
|
encoding: 'utf8',
|
|
stdio: 'pipe',
|
|
maxBuffer: 32 * 1024 * 1024,
|
|
});
|
|
}
|
|
|
|
export function resolveHostedTenantRootDataDir(tenantId) {
|
|
return `/data/tenants/${tenantId}`;
|
|
}
|
|
|
|
export function resolveHostedTenantOrgDataDir(tenantId, orgId) {
|
|
const scopedOrgId = String(orgId || tenantId).trim();
|
|
if (scopedOrgId === '' || scopedOrgId === 'default') {
|
|
return resolveHostedTenantRootDataDir(tenantId);
|
|
}
|
|
return `/data/tenants/${tenantId}/orgs/${scopedOrgId}`;
|
|
}
|
|
|
|
export function hostedTenantContainerName(tenantId) {
|
|
const normalizedTenantId = String(tenantId || '').trim();
|
|
if (!normalizedTenantId) {
|
|
throw new Error('tenantId is required');
|
|
}
|
|
return `pulse-${normalizedTenantId}`;
|
|
}
|
|
|
|
function remoteFailureOutput(error) {
|
|
const remoteOutput = ['stderr', 'stdout']
|
|
.map((field) => error?.[field])
|
|
.filter((value) => value !== undefined && value !== null && String(value).trim() !== '')
|
|
.map((value) => String(value).trim())
|
|
.join('\n');
|
|
if (remoteOutput) {
|
|
return remoteOutput;
|
|
}
|
|
return String(error?.message || '').trim();
|
|
}
|
|
|
|
export function hostedTenantRuntimeExistsScript(tenantId) {
|
|
const containerName = hostedTenantContainerName(tenantId);
|
|
return `
|
|
set -eu
|
|
container=${shellQuote(containerName)}
|
|
if ! docker inspect "$container" >/dev/null 2>&1; then
|
|
echo "hosted tenant runtime container $container does not exist" >&2
|
|
echo "create or restore an active hosted tenant before running hosted mobile proof seeding" >&2
|
|
exit 72
|
|
fi
|
|
docker inspect --format '{{.Name}} {{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container"
|
|
`;
|
|
}
|
|
|
|
export function assertHostedTenantRuntimeExists(cloudHost, tenantId, runner = runRemote) {
|
|
const containerName = hostedTenantContainerName(tenantId);
|
|
try {
|
|
return runner(cloudHost, `sh -lc ${shellQuote(hostedTenantRuntimeExistsScript(tenantId))}`);
|
|
} catch (error) {
|
|
const detail = remoteFailureOutput(error);
|
|
throw new Error([
|
|
`hosted tenant runtime ${containerName} is unavailable on ${cloudHost}`,
|
|
'Hosted mobile proof seeding requires an active hosted tenant container before it mutates approval or token data.',
|
|
detail,
|
|
].filter(Boolean).join('\n'));
|
|
}
|
|
}
|
|
|
|
export function restartHostedTenantRuntime(cloudHost, tenantId, runner = runRemote) {
|
|
const containerName = hostedTenantContainerName(tenantId);
|
|
const script = `
|
|
set -eu
|
|
container=${shellQuote(containerName)}
|
|
if ! docker inspect "$container" >/dev/null 2>&1; then
|
|
echo "hosted tenant runtime container $container does not exist" >&2
|
|
echo "create or restore an active hosted tenant before restarting hosted mobile proof state" >&2
|
|
exit 72
|
|
fi
|
|
docker restart "$container" >/dev/null
|
|
deadline=$(( $(date +%s) + 60 ))
|
|
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
state="$(docker inspect --format '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container" 2>/dev/null || true)"
|
|
case "$state" in
|
|
"running healthy"|"running none")
|
|
exit 0
|
|
;;
|
|
esac
|
|
sleep 1
|
|
done
|
|
echo "timed out waiting for $container to become ready" >&2
|
|
docker inspect --format '{{json .State}}' "$container" >&2 || true
|
|
exit 1
|
|
`;
|
|
try {
|
|
runner(cloudHost, `sh -lc ${shellQuote(script)}`);
|
|
} catch (error) {
|
|
const detail = remoteFailureOutput(error);
|
|
throw new Error([
|
|
`failed to restart hosted tenant runtime ${containerName} on ${cloudHost}`,
|
|
detail,
|
|
].filter(Boolean).join('\n'));
|
|
}
|
|
}
|