@synopackageland/cli 0.17.4 → 0.17.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/README.md +27 -1
- package/dist/build-spk/compose-retained-data.d.ts +11 -0
- package/dist/build-spk/compose-retained-data.js +34 -0
- package/dist/build-spk/delete-app-data.d.ts +1 -1
- package/dist/build-spk/delete-app-data.js +1 -1
- package/dist/build-spk/uninstall-wizard.d.ts +2 -0
- package/dist/build-spk/uninstall-wizard.js +2 -1
- package/dist/build-spk/wizard-messages.d.ts +22 -0
- package/dist/build-spk/wizard-messages.js +22 -0
- package/dist/ctl/cli.js +10 -6
- package/dist/ctl/flows.js +1 -1
- package/dist/ctl/service.d.ts +4 -1
- package/dist/ctl/service.js +20 -6
- package/dist/ctl/transfers.d.ts +1 -1
- package/dist/ctl/transfers.js +5 -2
- package/dist/ctl/transport.d.ts +2 -1
- package/dist/ctl/transport.js +7 -3
- package/dist/runtime/compose-data.d.ts +10 -0
- package/dist/runtime/compose-data.js +20 -0
- package/dist/runtime/executor-digests.json +11 -0
- package/dist/runtime/executor-policy.d.ts +25 -0
- package/dist/runtime/executor-policy.js +10 -1
- package/dist/runtime/runtime-budgets.d.ts +24 -0
- package/dist/runtime/runtime-budgets.js +13 -0
- package/dist/standalone/background.d.ts +3 -3
- package/dist/standalone/background.js +12 -11
- package/dist/standalone/compose.d.ts +3 -1
- package/dist/standalone/compose.js +8 -1
- package/dist/standalone/daemon.d.ts +4 -0
- package/dist/standalone/daemon.js +36 -23
- package/dist/standalone/dsm-window.js +2 -1
- package/dist/standalone/identity-gateway.js +9 -1
- package/dist/standalone/inspect-spk.js +87 -15
- package/dist/standalone/lifecycle.js +16 -9
- package/dist/standalone/manifest.d.ts +1 -0
- package/dist/standalone/manifest.js +8 -5
- package/dist/standalone/native-gateway.d.ts +17 -0
- package/dist/standalone/native-gateway.js +115 -0
- package/dist/standalone/resource.d.ts +3 -0
- package/dist/standalone/resource.js +3 -1
- package/dist/standalone/spk.js +14 -3
- package/dist/standalone/uninstall-hook.js +21 -8
- package/dist/standalone/uninstall.d.ts +1 -1
- package/dist/standalone/uninstall.js +2 -2
- package/package.json +2 -2
- package/skills/reference/compose.md +31 -0
- package/skills/reference/create.md +1 -1
- package/skills/reference/lifecycle.md +25 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CGI's declared request budget owns operation work. Binary and Compose Apps
|
|
3
|
+
* have no compiled CGI deadline to configure, so their existing status allowance
|
|
4
|
+
* can enlarge the shared operation budget above its 60-second default.
|
|
5
|
+
*/
|
|
6
|
+
export function operationBudgetSeconds(source) {
|
|
7
|
+
return source.backend?.timeoutSeconds ?? Math.max(60, source.lifecycleTimeouts?.status ?? source.daemon?.timeouts?.status ?? 5);
|
|
8
|
+
}
|
|
9
|
+
/** First released SDK that decodes operation/lifecycle budget config fields. */
|
|
10
|
+
export function runtimeBudgetSdkSupported(sdk) {
|
|
11
|
+
const [major, minor, patch] = sdk.split('.').map(Number);
|
|
12
|
+
return major > 0 || minor > 17 || minor === 17 && patch >= 5;
|
|
13
|
+
}
|
|
@@ -18,7 +18,7 @@ export declare function readBackground(value: unknown, fail: (error: StandaloneE
|
|
|
18
18
|
export declare function renderBackgroundResource(): string;
|
|
19
19
|
/** The worker's arguments: none for a Go App's own worker, `--worker` for the bridge runtime (spec/bridge/v1). */
|
|
20
20
|
export declare const BRIDGE_WORKER_ARGS: readonly ["--worker"];
|
|
21
|
-
export declare function renderBackgroundUnit(pkg: string, args?: readonly string[]): string;
|
|
21
|
+
export declare function renderBackgroundUnit(pkg: string, args?: readonly string[], stopSeconds?: number): string;
|
|
22
22
|
/** Shell function that stops the worker in lifecycle hooks; call it as `stop_background_worker`. */
|
|
23
23
|
export declare function renderBackgroundWorkerStop(pkg: string): string;
|
|
24
24
|
/**
|
|
@@ -32,8 +32,8 @@ export declare function renderBackgroundWorkerStop(pkg: string): string;
|
|
|
32
32
|
* exits. Stop only such a manager, as the package user; the acquire that
|
|
33
33
|
* follows then starts a fresh one.
|
|
34
34
|
*/
|
|
35
|
-
export declare function renderUserManagerRecovery(): string;
|
|
35
|
+
export declare function renderUserManagerRecovery(startSeconds?: number): string;
|
|
36
36
|
/** Shell function that starts the worker and names the failed step; call it as `start_background_worker`. */
|
|
37
37
|
export declare function renderBackgroundWorkerStart(pkg: string): string;
|
|
38
38
|
/** Package Center's start/stop/status map straight onto the user unit. */
|
|
39
|
-
export declare function renderBackgroundControl(pkg: string): string;
|
|
39
|
+
export declare function renderBackgroundControl(pkg: string, startSeconds?: number): string;
|
|
@@ -24,7 +24,7 @@ export function renderBackgroundResource() {
|
|
|
24
24
|
}
|
|
25
25
|
/** The worker's arguments: none for a Go App's own worker, `--worker` for the bridge runtime (spec/bridge/v1). */
|
|
26
26
|
export const BRIDGE_WORKER_ARGS = ["--worker"];
|
|
27
|
-
export function renderBackgroundUnit(pkg, args = []) {
|
|
27
|
+
export function renderBackgroundUnit(pkg, args = [], stopSeconds = 30) {
|
|
28
28
|
return `[Unit]
|
|
29
29
|
Description=${pkg} background worker
|
|
30
30
|
|
|
@@ -33,7 +33,7 @@ Type=simple
|
|
|
33
33
|
ExecStart=${[`/var/packages/${pkg}/target/${BACKGROUND_WORKER_PAYLOAD}`, ...args].join(" ")}
|
|
34
34
|
UMask=0077
|
|
35
35
|
KillMode=control-group
|
|
36
|
-
TimeoutStopSec
|
|
36
|
+
TimeoutStopSec=${stopSeconds}
|
|
37
37
|
Restart=no
|
|
38
38
|
`;
|
|
39
39
|
}
|
|
@@ -66,13 +66,15 @@ export function renderBackgroundWorkerStop(pkg) {
|
|
|
66
66
|
* exits. Stop only such a manager, as the package user; the acquire that
|
|
67
67
|
* follows then starts a fresh one.
|
|
68
68
|
*/
|
|
69
|
-
export function renderUserManagerRecovery() {
|
|
69
|
+
export function renderUserManagerRecovery(startSeconds = 30) {
|
|
70
70
|
return `recover_user_manager() {
|
|
71
71
|
uid="$(id -u)" || return 1
|
|
72
72
|
[ "$uid" != 0 ] || return 0
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
manager_now() { awk '{print int($1)}' /proc/uptime; }
|
|
74
|
+
manager_until=$(( $(manager_now) + ${startSeconds} ))
|
|
75
|
+
while [ "$(/usr/bin/loginctl show-user "$uid" -p State 2>/dev/null)" = State=closing ]; do
|
|
76
|
+
[ "$(manager_now)" -lt "$manager_until" ] || { echo "package user manager still closing" >&2; return 1; }
|
|
77
|
+
sleep 0.1
|
|
76
78
|
done
|
|
77
79
|
[ -S "/run/user/$uid/systemd/private" ] && return 0
|
|
78
80
|
for exe in /proc/[0-9]*/exe; do
|
|
@@ -81,12 +83,11 @@ export function renderUserManagerRecovery() {
|
|
|
81
83
|
case " $(tr '\\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null)" in *" --user "*) ;; *) continue ;; esac
|
|
82
84
|
echo "package user manager $pid has no socket in /run/user/$uid; stopping it so DSM starts a fresh one" >&2
|
|
83
85
|
kill -TERM "$pid" || return 1
|
|
84
|
-
i=0
|
|
85
86
|
# DSM's bash logs a failed kill to syslog even with 2>/dev/null, so the
|
|
86
87
|
# exit is looked up in /proc.
|
|
87
88
|
while [ -d "/proc/$pid" ]; do
|
|
88
|
-
[ "$
|
|
89
|
-
sleep 0.1
|
|
89
|
+
[ "$(manager_now)" -lt "$manager_until" ] || { echo "package user manager $pid did not exit" >&2; return 1; }
|
|
90
|
+
sleep 0.1
|
|
90
91
|
done
|
|
91
92
|
done
|
|
92
93
|
}
|
|
@@ -105,10 +106,10 @@ export function renderBackgroundWorkerStart(pkg) {
|
|
|
105
106
|
`;
|
|
106
107
|
}
|
|
107
108
|
/** Package Center's start/stop/status map straight onto the user unit. */
|
|
108
|
-
export function renderBackgroundControl(pkg) {
|
|
109
|
+
export function renderBackgroundControl(pkg, startSeconds = 30) {
|
|
109
110
|
const unit = backgroundUnitName(pkg);
|
|
110
111
|
return `#!/bin/sh
|
|
111
|
-
${renderUserManagerRecovery()}${renderBackgroundWorkerStart(pkg)}case "$1" in
|
|
112
|
+
${renderUserManagerRecovery(startSeconds)}${renderBackgroundWorkerStart(pkg)}case "$1" in
|
|
112
113
|
prestart) recover_user_manager ;;
|
|
113
114
|
prestop) exit 0 ;;
|
|
114
115
|
start) start_background_worker ;;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { type ComposeAppData } from "../runtime/compose-data.js";
|
|
1
2
|
import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
|
|
2
3
|
import { type Daemon } from "./daemon.js";
|
|
3
4
|
import type { StandaloneError, StandaloneManifest } from "./manifest.js";
|
|
4
5
|
export interface ComposePlan {
|
|
6
|
+
app_data?: ComposeAppData;
|
|
5
7
|
startup_timeout_seconds?: number;
|
|
6
8
|
ingress?: {
|
|
7
9
|
address: string;
|
|
@@ -79,7 +81,7 @@ export declare function readComposeApp(value: unknown, root: string, arches: Nat
|
|
|
79
81
|
architecture?: NativeArchFamily;
|
|
80
82
|
imageProofs?: unknown;
|
|
81
83
|
}): ComposeApp | undefined;
|
|
82
|
-
export declare function composeDaemon(m: Pick<StandaloneManifest, "compose">): Daemon;
|
|
84
|
+
export declare function composeDaemon(m: Pick<StandaloneManifest, "compose" | "lifecycleTimeouts">): Daemon;
|
|
83
85
|
export declare function composeNetwork(m: Pick<StandaloneManifest, "compose" | "identityGateway" | "spkVersion">): {
|
|
84
86
|
public_ports: number[];
|
|
85
87
|
readiness?: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validComposeAppData } from "../runtime/compose-data.js";
|
|
1
2
|
import { composeDiagnosisMessages, renderComposeDiagnosis } from "../build-spk/compose-diagnosis.js";
|
|
2
3
|
import { COMPOSE_ERRORS } from "./compose-errors.js";
|
|
3
4
|
import { spawnSync } from "node:child_process";
|
|
@@ -117,6 +118,12 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
|
|
|
117
118
|
plan.startup_timeout_seconds !== value.startup_timeout_seconds)
|
|
118
119
|
throw new Error("[SDK_COMPOSE_UNSUPPORTED] This SDK does not implement the declared startup budget; use the matching SDK/CLI release.");
|
|
119
120
|
const document = parse(plan.effective);
|
|
121
|
+
const volumes = Object.entries(document?.volumes ?? {});
|
|
122
|
+
if ((!validComposeAppData(plan.app_data, context.package ?? "ComposeApp")
|
|
123
|
+
|| JSON.stringify(plan.app_data) !== JSON.stringify(plan.lock?.app_data)
|
|
124
|
+
|| plan.app_data.project !== plan.project_name || plan.app_data.volumes.length !== volumes.length
|
|
125
|
+
|| volumes.some(([key, value]) => !plan.app_data.volumes.some(v => v.key === key && v.name === value.name))))
|
|
126
|
+
throw new Error("[SDK_COMPOSE_UNSUPPORTED] This SDK does not provide the complete Compose App data contract; update the SDK and rebuild.");
|
|
120
127
|
const web = value.web;
|
|
121
128
|
return {
|
|
122
129
|
declaration: { ...value },
|
|
@@ -150,7 +157,7 @@ export function composeDaemon(m) {
|
|
|
150
157
|
args: ["run"],
|
|
151
158
|
targets: m.compose.targets,
|
|
152
159
|
env: {},
|
|
153
|
-
timeouts: {
|
|
160
|
+
timeouts: { stop: 30, status: 5, uninstall: 60, ...m.lifecycleTimeouts, start: m.compose.plan.startup_timeout_seconds ?? m.lifecycleTimeouts?.start ?? 120 },
|
|
154
161
|
};
|
|
155
162
|
}
|
|
156
163
|
export function composeNetwork(m) {
|
|
@@ -30,3 +30,7 @@ export declare function buildDaemonControl(manifest: StandaloneManifest, arch: N
|
|
|
30
30
|
export declare function copyDaemonPayload(manifest: StandaloneManifest, arch: NativeArchFamily, target: string, controller: string): void;
|
|
31
31
|
/** Copies a bounded, link-free package-account payload tree. */
|
|
32
32
|
export declare function copyPayloadTree(root: string, source: string, target: string): void;
|
|
33
|
+
/** Existing lifecycle namespace; a timeout-only declaration runs no daemon. */
|
|
34
|
+
export declare function readLifecycleTimeouts(value: unknown, fail: (e: StandaloneError) => void): Daemon['timeouts'] | undefined;
|
|
35
|
+
/** Legacy SDKs keep their supported config and timeout bounds. */
|
|
36
|
+
export declare function runtimeBudgetSource(dir: string, manifest?: StandaloneManifest): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { operationBudgetSeconds, runtimeBudgetSdkSupported } from "../runtime/runtime-budgets.js";
|
|
1
2
|
import { goChildEnv } from "../runtime/go-env.js";
|
|
2
|
-
import { chmodSync, copyFileSync, lstatSync, mkdirSync, readdirSync, realpathSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { chmodSync, existsSync, copyFileSync, lstatSync, mkdirSync, readdirSync, realpathSync, writeFileSync, } from "node:fs";
|
|
3
4
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
5
|
import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem, } from "../build-spk/go-toolchain.js";
|
|
5
6
|
import { sdkSource, appSdkVersion } from "../runtime/sdk-source.js";
|
|
@@ -79,23 +80,9 @@ export function readDaemon(value, root, arches, fail) {
|
|
|
79
80
|
Object.keys(env).length > 64 ||
|
|
80
81
|
Object.entries(env).some(([k, v]) => !envKey(k) || !token(v)))
|
|
81
82
|
return invalid();
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
84
|
-
|
|
85
|
-
return invalid();
|
|
86
|
-
// Defaults hold under IO load on a busy NAS; DSM's package unit allows 3600 s.
|
|
87
|
-
const timeouts = {
|
|
88
|
-
start: limits.start ?? 30,
|
|
89
|
-
stop: limits.stop ?? 30,
|
|
90
|
-
status: limits.status ?? 5,
|
|
91
|
-
uninstall: limits.uninstall ?? 60,
|
|
92
|
-
};
|
|
93
|
-
for (const [key, v] of Object.entries(timeouts)) {
|
|
94
|
-
if (!Number.isInteger(v) ||
|
|
95
|
-
v < (key === "status" ? 1 : 2) ||
|
|
96
|
-
v > (key === "status" ? 10 : 120))
|
|
97
|
-
return invalid();
|
|
98
|
-
}
|
|
83
|
+
const timeouts = readLifecycleTimeouts(value.timeouts, fail);
|
|
84
|
+
if (!timeouts)
|
|
85
|
+
return undefined;
|
|
99
86
|
return {
|
|
100
87
|
exec: value.exec,
|
|
101
88
|
targets,
|
|
@@ -107,6 +94,11 @@ export function readDaemon(value, root, arches, fail) {
|
|
|
107
94
|
timeouts: timeouts,
|
|
108
95
|
};
|
|
109
96
|
}
|
|
97
|
+
function daemonSdkVersion(manifest) {
|
|
98
|
+
return manifest.identityGateway?.sdk ??
|
|
99
|
+
(manifest.backend.kind === "go" ? appSdkVersion(manifest.backend) : undefined) ??
|
|
100
|
+
templateSdkVersion();
|
|
101
|
+
}
|
|
110
102
|
export function daemonConfig(manifest) {
|
|
111
103
|
const { exec, args, uninstall_args, env, timeouts } = manifest.compose
|
|
112
104
|
? composeDaemon(manifest)
|
|
@@ -120,6 +112,8 @@ export function daemonConfig(manifest) {
|
|
|
120
112
|
...(uninstall_args ? { uninstall_args } : {}),
|
|
121
113
|
env,
|
|
122
114
|
timeouts,
|
|
115
|
+
...(runtimeBudgetSdkSupported(daemonSdkVersion(manifest))
|
|
116
|
+
? { operation_seconds: operationBudgetSeconds(manifest) } : {}),
|
|
123
117
|
...(manifest.compose ? { network: composeNetwork(manifest) } : {}),
|
|
124
118
|
}) + "\n");
|
|
125
119
|
}
|
|
@@ -137,11 +131,7 @@ export async function buildDaemonControl(manifest, arch, work, run) {
|
|
|
137
131
|
out,
|
|
138
132
|
"./daemonctl",
|
|
139
133
|
], {
|
|
140
|
-
cwd: sdkSource(manifest.
|
|
141
|
-
(manifest.backend.kind === "go"
|
|
142
|
-
? appSdkVersion(manifest.backend)
|
|
143
|
-
: undefined) ??
|
|
144
|
-
templateSdkVersion()).goDir,
|
|
134
|
+
cwd: runtimeBudgetSource(sdkSource(daemonSdkVersion(manifest)).goDir, manifest),
|
|
145
135
|
env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(arch) }),
|
|
146
136
|
});
|
|
147
137
|
if (result.missing || result.status !== 0)
|
|
@@ -212,3 +202,26 @@ export function copyPayloadTree(root, source, target) {
|
|
|
212
202
|
invalid(`Cannot read the daemon payload: ${String(error)}`);
|
|
213
203
|
}
|
|
214
204
|
}
|
|
205
|
+
/** Existing lifecycle namespace; a timeout-only declaration runs no daemon. */
|
|
206
|
+
export function readLifecycleTimeouts(value, fail) {
|
|
207
|
+
const v = value ?? {};
|
|
208
|
+
const bad = () => { fail({ code: 'STANDALONE_DAEMON_INVALID', message: 'daemon.timeouts accepts start/stop/uninstall 2–3600s and status 1–3600s.' }); return undefined; };
|
|
209
|
+
if (!record(v) || Object.keys(v).some(k => !['start', 'stop', 'status', 'uninstall'].includes(k)))
|
|
210
|
+
return bad();
|
|
211
|
+
const limits = { start: v.start ?? 30, stop: v.stop ?? 30, status: v.status ?? 5, uninstall: v.uninstall ?? 60 };
|
|
212
|
+
for (const [key, n] of Object.entries(limits))
|
|
213
|
+
if (!Number.isInteger(n) || Number(n) < (key === 'status' ? 1 : 2) || Number(n) > 3600)
|
|
214
|
+
return bad();
|
|
215
|
+
return limits;
|
|
216
|
+
}
|
|
217
|
+
/** Legacy SDKs keep their supported config and timeout bounds. */
|
|
218
|
+
export function runtimeBudgetSource(dir, manifest) {
|
|
219
|
+
if (!existsSync(join(dir, 'internal/runtimebudget/budget.go'))) {
|
|
220
|
+
const timeouts = manifest?.daemon?.timeouts ?? manifest?.lifecycleTimeouts;
|
|
221
|
+
if (!manifest || operationBudgetSeconds(manifest) > 60 || timeouts &&
|
|
222
|
+
(timeouts.start > 120 || timeouts.stop > 120 || timeouts.status > 10 || timeouts.uninstall > 120)) {
|
|
223
|
+
throw new StandaloneBuildError('STANDALONE_DAEMON_BUILD_FAILED', 'The requested timeouts need an SDK with runtime budget support; use SDK 0.17.5 or later before rebuilding.');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return dir;
|
|
227
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { gatewayEntryPath } from './native-gateway.js';
|
|
1
2
|
import { resolvedSdkLaunch, dsmLauncherType } from "../contract/app-launch.js";
|
|
2
3
|
import { REOPEN_EVENTS_RUNTIME } from "./reopen-events.js";
|
|
3
4
|
import { LAUNCH_PARAMS_RUNTIME } from "./launch-params.js";
|
|
@@ -120,7 +121,7 @@ export function renderUiConfig(manifest, icons = packageIcons(manifest.icon), lo
|
|
|
120
121
|
...titleFields,
|
|
121
122
|
icon,
|
|
122
123
|
...launcherAccess(manifest),
|
|
123
|
-
url:
|
|
124
|
+
url: gatewayEntryPath(manifest),
|
|
124
125
|
},
|
|
125
126
|
},
|
|
126
127
|
}, null, 2) + "\n");
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { nativeGatewaySupported } from './native-gateway.js';
|
|
2
|
+
import { operationBudgetSeconds, runtimeBudgetSdkSupported } from "../runtime/runtime-budgets.js";
|
|
1
3
|
import { existsSync, lstatSync } from "node:fs";
|
|
2
4
|
import { join } from "node:path";
|
|
3
5
|
import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem, } from "../build-spk/go-toolchain.js";
|
|
@@ -5,7 +7,7 @@ import { goChildEnv } from "../runtime/go-env.js";
|
|
|
5
7
|
import { sdkSource } from "../runtime/sdk-source.js";
|
|
6
8
|
import { checkCgiBinary } from "./binary.js";
|
|
7
9
|
import { isWebAccessMode, DSM_GROUP_NAME, } from "../contract/web-launch.js";
|
|
8
|
-
import { relativeExec, token } from "./daemon.js";
|
|
10
|
+
import { relativeExec, runtimeBudgetSource, token } from "./daemon.js";
|
|
9
11
|
import { StandaloneBuildError } from "./spk.js";
|
|
10
12
|
/** The SDK's identity gateway: the daemon wrapper and the DSM-origin authorize CGI. */
|
|
11
13
|
export const GATEWAY_MAIN = "./idgateway/cmd/gateway";
|
|
@@ -309,6 +311,11 @@ export function identityGatewayConfig(m) {
|
|
|
309
311
|
const g = m.identityGateway;
|
|
310
312
|
return (JSON.stringify({
|
|
311
313
|
package: m.package,
|
|
314
|
+
...(nativeGatewaySupported(m) ? { native_ingress: true } : {}),
|
|
315
|
+
...(runtimeBudgetSdkSupported(g.sdk) ? {
|
|
316
|
+
operation_seconds: operationBudgetSeconds(m),
|
|
317
|
+
stop_seconds: m.daemon?.timeouts.stop ?? m.lifecycleTimeouts?.stop ?? 30,
|
|
318
|
+
} : {}),
|
|
312
319
|
mode: g.mode,
|
|
313
320
|
access: g.access ?? "dsm-users",
|
|
314
321
|
...(g.groups ? { groups: g.groups } : {}),
|
|
@@ -385,6 +392,7 @@ export async function buildIdentityGateway(m, arch, work, run) {
|
|
|
385
392
|
catch (error) {
|
|
386
393
|
throw new StandaloneBuildError("SDK_DOWNLOAD_FAILED", error.message.replace(/^\[[A-Z_]+\] /, ""));
|
|
387
394
|
}
|
|
395
|
+
runtimeBudgetSource(sdk.goDir, m);
|
|
388
396
|
const out = join(work, `identity-gateway-${arch}`);
|
|
389
397
|
const ldflags = `-s -w -buildid= -X main.configPath=${packageDest(m)}/${GATEWAY_CONFIG_PATH}`;
|
|
390
398
|
const result = await run("go", [
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { nativePortal } from "./portal.js";
|
|
2
|
+
import { nativeGatewaySupported, renderNativeGatewayConfig, renderNativeGatewayPrepare, NATIVE_GATEWAY_CONFIG, gatewayEntryPath } from './native-gateway.js';
|
|
1
3
|
import { inspectPortalInfo } from "../portal/info.js";
|
|
2
4
|
import { isDeepStrictEqual } from "node:util";
|
|
3
5
|
import { inspectApiDescription } from "./api-description-inspect.js";
|
|
@@ -18,7 +20,7 @@ import { familyOfArchToken, NATIVE_ARCH_FAMILIES, } from "../build-spk/dsm-arch.
|
|
|
18
20
|
import { CONTAINER_MIN_DSM, isDsmAtLeast, PORTAL_EXECUTOR_MIN_DSM, PORTAL_MIN_DSM, PORTAL_MIN_DSM_REASON, } from "../build-spk/dsm-version.js";
|
|
19
21
|
import { ELF_MACHINE } from "./binary.js";
|
|
20
22
|
import { CGI_PAYLOAD_PATH, entryLauncherAccess } from "./dsm-window.js";
|
|
21
|
-
import { daemonConfig, readDaemon } from "./daemon.js";
|
|
23
|
+
import { daemonConfig, readDaemon, readLifecycleTimeouts } from "./daemon.js";
|
|
22
24
|
import { ROOT_SCRIPT_HINTS } from "./root-ops.js";
|
|
23
25
|
import { DELETE_STATE_KEY } from "../build-spk/uninstall-wizard.js";
|
|
24
26
|
import { readUninstallRecord, stateItemTexts } from "./uninstall.js";
|
|
@@ -110,6 +112,10 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
110
112
|
(raw.entry !== undefined &&
|
|
111
113
|
!["admin-only", "app-privilege"].includes(raw.entry)))
|
|
112
114
|
throw new Error();
|
|
115
|
+
if (raw.lifecycleTimeouts !== undefined && (!readLifecycleTimeouts(raw.lifecycleTimeouts, () => { }) || Object.keys(raw.lifecycleTimeouts).length !== 4))
|
|
116
|
+
throw new Error();
|
|
117
|
+
if (raw.operationSeconds !== undefined && (!Number.isInteger(raw.operationSeconds) || raw.operationSeconds < 1 || raw.operationSeconds > 3600))
|
|
118
|
+
throw new Error();
|
|
113
119
|
metadata = raw;
|
|
114
120
|
}
|
|
115
121
|
catch {
|
|
@@ -145,6 +151,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
145
151
|
(!info.os_min_ver || !isDsmAtLeast(info.os_min_ver, CONTAINER_MIN_DSM)))
|
|
146
152
|
findings.push("compose identity_gateway requires DSM >= 7.2.1");
|
|
147
153
|
await checkArch(dir, info, kind, paths.has(CGI_PAYLOAD_PATH), findings);
|
|
154
|
+
const nativeGateway = await recoverNativeGateway(dir, metadata.native_gateway, info, findings);
|
|
148
155
|
const background = compiledKind && paths.has(BACKGROUND_WORKER_PAYLOAD);
|
|
149
156
|
// Before any fixed-lifecycle comparison: those see the SDK steps alone.
|
|
150
157
|
const appScripts = standalone || kind === "container"
|
|
@@ -163,7 +170,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
163
170
|
!isDsmAtLeast(info.os_min_ver, PORTAL_MIN_DSM)) {
|
|
164
171
|
findings.push(`INFO os_min_ver "${info.os_min_ver}" is below the DSM ${PORTAL_MIN_DSM} a portal needs; ${PORTAL_MIN_DSM_REASON}`);
|
|
165
172
|
}
|
|
166
|
-
checkConf(dir, findings, kind, background, sharedFolder, info.package, portal, combined?.compose);
|
|
173
|
+
checkConf(dir, findings, kind, background, sharedFolder, info.package, portal, combined?.compose, nativeGateway, info.os_min_ver);
|
|
167
174
|
checkScripts(dir, findings);
|
|
168
175
|
checkUninstall(dir, findings);
|
|
169
176
|
if (kind === "container")
|
|
@@ -191,10 +198,10 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
191
198
|
if (standalone && portal)
|
|
192
199
|
findings.push(...inspectPortalInfo(info, hasExecutor));
|
|
193
200
|
const daemon = standalone
|
|
194
|
-
? await checkDaemon(dir, info, paths, payload, findings, hasExecutor, background, combined)
|
|
201
|
+
? await checkDaemon(dir, info, paths, payload, findings, hasExecutor, background, combined, nativeGateway)
|
|
195
202
|
: undefined;
|
|
196
203
|
if (background)
|
|
197
|
-
checkBackground(dir, info, payload, findings, hasExecutor, daemon, kind !== "native-go");
|
|
204
|
+
checkBackground(dir, info, payload, findings, hasExecutor, daemon, kind !== "native-go", metadata.lifecycleTimeouts);
|
|
198
205
|
let executor;
|
|
199
206
|
if ((metadata.capabilities ?? []).some((name) => name === "rootOps" || name === "delegate" || name === "webapi") ||
|
|
200
207
|
paths.has("executor/executor")) {
|
|
@@ -224,6 +231,7 @@ export async function inspectSpk(spkPath, expectedKind) {
|
|
|
224
231
|
if (![...paths].some((path) => path.startsWith("daemon/"))) {
|
|
225
232
|
const lifecycleManifest = {
|
|
226
233
|
package: info.package,
|
|
234
|
+
lifecycleTimeouts: metadata.lifecycleTimeouts,
|
|
227
235
|
capabilities: {
|
|
228
236
|
rootOps: true,
|
|
229
237
|
...(background ? { jobs: { worker: "sha256-v1" } } : {}),
|
|
@@ -622,7 +630,8 @@ async function inspectUnifiedCompose(dir, payload, metadata, findings) {
|
|
|
622
630
|
os_min_ver: info.os_min_ver,
|
|
623
631
|
build: Number(info.version?.split("-").pop()),
|
|
624
632
|
arches: [arch],
|
|
625
|
-
backend: { kind: "binary", files: {} },
|
|
633
|
+
backend: { kind: "binary", files: {}, ...(metadata.operationSeconds ? { timeoutSeconds: metadata.operationSeconds } : {}) },
|
|
634
|
+
lifecycleTimeouts: metadata.lifecycleTimeouts,
|
|
626
635
|
ui: { dir: root, launch: metadata.launch },
|
|
627
636
|
capabilities: {
|
|
628
637
|
...(metadata.capabilities?.includes("composeMounts")
|
|
@@ -635,7 +644,7 @@ async function inspectUnifiedCompose(dir, payload, metadata, findings) {
|
|
|
635
644
|
};
|
|
636
645
|
if (Boolean(c.web) !== Boolean(g))
|
|
637
646
|
throw new Error("Web ingress must have a complete identity gateway");
|
|
638
|
-
if (gateway && gateway
|
|
647
|
+
if (gateway && !matchesGatewayConfig(gateway, manifest))
|
|
639
648
|
throw new Error("gateway configuration differs from the declared package contract");
|
|
640
649
|
if (readOptional(join(dir, "conf/privilege")) !== renderNativePrivilege())
|
|
641
650
|
throw new Error("Compose hooks must run as the package account");
|
|
@@ -648,8 +657,15 @@ async function inspectUnifiedCompose(dir, payload, metadata, findings) {
|
|
|
648
657
|
throw new Error("LAUNCHER_TYPE_MISMATCH classless Compose must use the shared url launcher");
|
|
649
658
|
if (g &&
|
|
650
659
|
(cfg.package !== info.package ||
|
|
651
|
-
info.adminurl !==
|
|
660
|
+
info.adminurl !== gatewayEntryPath(manifest)))
|
|
652
661
|
throw new Error("gateway entry mismatch");
|
|
662
|
+
for (const [name, text] of Object.entries(renderUninstallWizardFiles(info.package, "native", {
|
|
663
|
+
composeData: c.plan.app_data,
|
|
664
|
+
portal: Boolean(nativePortal(manifest)),
|
|
665
|
+
}))) {
|
|
666
|
+
if (readOptional(join(dir, "WIZARD_UIFILES", name)) !== text)
|
|
667
|
+
throw new Error(`unexpected WIZARD_UIFILES/${name}; retained-volume guidance must match the plan`);
|
|
668
|
+
}
|
|
653
669
|
const appScripts = unwrapAppScripts(join(dir, "scripts"), findings);
|
|
654
670
|
manifest.appScripts = undefined;
|
|
655
671
|
for (const hook of [
|
|
@@ -771,7 +787,7 @@ async function recoverCompose(dir, value, findings) {
|
|
|
771
787
|
return undefined;
|
|
772
788
|
}
|
|
773
789
|
}
|
|
774
|
-
async function checkDaemon(dir, info, paths, payload, findings, executor, background, combined) {
|
|
790
|
+
async function checkDaemon(dir, info, paths, payload, findings, executor, background, combined, nativeGateway) {
|
|
775
791
|
if (![...paths].some((path) => path.startsWith("daemon/")))
|
|
776
792
|
return;
|
|
777
793
|
if (info.precheckstartstop !== "yes")
|
|
@@ -796,6 +812,7 @@ async function checkDaemon(dir, info, paths, payload, findings, executor, backgr
|
|
|
796
812
|
"args",
|
|
797
813
|
"env",
|
|
798
814
|
"timeouts",
|
|
815
|
+
"operation_seconds",
|
|
799
816
|
"uninstall_args",
|
|
800
817
|
...(combined ? ["network"] : []),
|
|
801
818
|
].includes(key)))
|
|
@@ -810,6 +827,8 @@ async function checkDaemon(dir, info, paths, payload, findings, executor, backgr
|
|
|
810
827
|
}, dir, ["x86_64"], () => { });
|
|
811
828
|
if (!daemon)
|
|
812
829
|
throw new Error("invalid config");
|
|
830
|
+
if (config.operation_seconds !== undefined && (!Number.isInteger(config.operation_seconds) || config.operation_seconds < 1 || config.operation_seconds > 3600))
|
|
831
|
+
throw new Error('invalid operation budget');
|
|
813
832
|
if (combined &&
|
|
814
833
|
(daemon.exec !== "_sso/gateway" ||
|
|
815
834
|
JSON.stringify(daemon.args) !== '["run"]' ||
|
|
@@ -820,6 +839,9 @@ async function checkDaemon(dir, info, paths, payload, findings, executor, backgr
|
|
|
820
839
|
throw new Error("missing executable");
|
|
821
840
|
const manifest = {
|
|
822
841
|
package: info.package,
|
|
842
|
+
backend: { kind: 'go', dir: dir, main: './cmd/app', timeoutSeconds: config.operation_seconds ?? 60 },
|
|
843
|
+
root: dir,
|
|
844
|
+
...(combined ? { lifecycleTimeouts: daemon.timeouts } : {}),
|
|
823
845
|
daemon,
|
|
824
846
|
...combined,
|
|
825
847
|
capabilities: {
|
|
@@ -841,11 +863,14 @@ async function checkDaemon(dir, info, paths, payload, findings, executor, backgr
|
|
|
841
863
|
},
|
|
842
864
|
}
|
|
843
865
|
: manifest);
|
|
844
|
-
|
|
866
|
+
const expectedConfig = JSON.parse(canonical);
|
|
867
|
+
if (config.operation_seconds === undefined)
|
|
868
|
+
delete expectedConfig.operation_seconds;
|
|
869
|
+
if (readFileSync(join(unpacked, "daemon/config.json"), "utf8") !== JSON.stringify(expectedConfig) + '\n')
|
|
845
870
|
throw new Error("non-canonical daemon config");
|
|
846
871
|
for (const hook of ["start-stop-status", "preupgrade", "preuninst"]) {
|
|
847
872
|
if (readFileSync(join(dir, "scripts", hook), "utf8") !==
|
|
848
|
-
renderStandaloneScript(hook, manifest))
|
|
873
|
+
renderStandaloneScript(hook, nativeGateway ? { ...manifest, os_min_ver: info.os_min_ver, identityGateway: nativeGateway } : manifest))
|
|
849
874
|
findings.push(`daemon scripts/${hook} does not match the fixed lifecycle`);
|
|
850
875
|
}
|
|
851
876
|
return daemon;
|
|
@@ -917,12 +942,50 @@ async function checkContainerExposure(dir, packageTgz, findings) {
|
|
|
917
942
|
function readOptional(path) {
|
|
918
943
|
return existsSync(path) ? readFileSync(path, "utf8") : undefined;
|
|
919
944
|
}
|
|
920
|
-
function
|
|
945
|
+
async function recoverNativeGateway(dir, value, info, findings) {
|
|
946
|
+
if (value === undefined)
|
|
947
|
+
return undefined;
|
|
948
|
+
try {
|
|
949
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
950
|
+
throw new Error('invalid native gateway record');
|
|
951
|
+
const plan = value;
|
|
952
|
+
if (typeof plan.sdk !== 'string' || !plan.listen || typeof plan.listen !== 'object' || Array.isArray(plan.listen)
|
|
953
|
+
|| !Object.keys(plan.listen).length || Object.keys(plan).some(k => !['sdk', 'listen'].includes(k))
|
|
954
|
+
|| Object.entries(plan.listen).some(([k, v]) => !['http', 'https'].includes(k) || !Number.isInteger(v) || v < 1 || v > 65535))
|
|
955
|
+
throw new Error('invalid native gateway ports');
|
|
956
|
+
const target = { package: info.package, os_min_ver: info.os_min_ver, identityGateway: plan };
|
|
957
|
+
if (!nativeGatewaySupported(target))
|
|
958
|
+
throw new Error('unsupported native gateway SDK/DSM floor');
|
|
959
|
+
const root = join(dir, 'native-gateway-inspection');
|
|
960
|
+
mkdirSync(root);
|
|
961
|
+
await tar.x({ file: join(dir, 'package.tgz'), cwd: root, filter: path => ['sso/gateway.json', NATIVE_GATEWAY_CONFIG].includes(path.replace(/^\.\//, '')) });
|
|
962
|
+
const config = JSON.parse(readFileSync(join(root, 'sso/gateway.json'), 'utf8'));
|
|
963
|
+
if (config.native_ingress !== true || config.package !== info.package || !isDeepStrictEqual(config.listen, plan.listen)
|
|
964
|
+
|| config.state_dir !== `/var/packages/${info.package}/var/sso`)
|
|
965
|
+
throw new Error('native gateway config does not match its package');
|
|
966
|
+
if (readOptional(join(root, NATIVE_GATEWAY_CONFIG)) !== renderNativeGatewayConfig(target))
|
|
967
|
+
throw new Error('native gateway ingress differs from its fixed proxy');
|
|
968
|
+
for (const key of ['instuninst_restart_services', 'startstop_restart_services'])
|
|
969
|
+
if (!(info[key] ?? '').split(/\s+/).includes('nginx.service'))
|
|
970
|
+
throw new Error(`native gateway requires INFO ${key}`);
|
|
971
|
+
for (const hook of ['postinst', 'postupgrade'])
|
|
972
|
+
if (!readFileSync(join(dir, 'scripts', hook), 'utf8').includes(renderNativeGatewayPrepare(target)))
|
|
973
|
+
throw new Error('native gateway credential preparation is missing');
|
|
974
|
+
return plan;
|
|
975
|
+
}
|
|
976
|
+
catch (e) {
|
|
977
|
+
findings.push(`NATIVE_GATEWAY_INVALID ${e.message}`);
|
|
978
|
+
return undefined;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function checkConf(dir, findings, kind, background, sharedFolder, pkg, portal, compose, nativeGateway, osMinVer) {
|
|
921
982
|
const confDir = join(dir, "conf");
|
|
922
983
|
const entries = existsSync(confDir) ? readdirSync(confDir) : [];
|
|
923
984
|
const nativeResource = kind !== "container"
|
|
924
985
|
? renderStandaloneResource({
|
|
925
986
|
package: pkg ?? "",
|
|
987
|
+
os_min_ver: osMinVer,
|
|
988
|
+
...(nativeGateway ? { identityGateway: nativeGateway } : {}),
|
|
926
989
|
...(compose ? { compose } : {}),
|
|
927
990
|
capabilities: background
|
|
928
991
|
? { jobs: { worker: "sha256-v1" } }
|
|
@@ -979,7 +1042,7 @@ function checkConf(dir, findings, kind, background, sharedFolder, pkg, portal, c
|
|
|
979
1042
|
}
|
|
980
1043
|
}
|
|
981
1044
|
/** The opt-in package user unit must be exactly what the generator writes. */
|
|
982
|
-
function checkBackground(dir, info, payload, findings, executor, daemon, bridge = false) {
|
|
1045
|
+
function checkBackground(dir, info, payload, findings, executor, daemon, bridge = false, lifecycleTimeouts) {
|
|
983
1046
|
const pkg = info.package;
|
|
984
1047
|
if (!pkg)
|
|
985
1048
|
return;
|
|
@@ -997,12 +1060,13 @@ function checkBackground(dir, info, payload, findings, executor, daemon, bridge
|
|
|
997
1060
|
if (units.length !== 1 ||
|
|
998
1061
|
units[0] !== unit ||
|
|
999
1062
|
read(`${BACKGROUND_UNIT_DIR}/${unit}`) !==
|
|
1000
|
-
renderBackgroundUnit(pkg, bridge ? BRIDGE_WORKER_ARGS : [])) {
|
|
1063
|
+
renderBackgroundUnit(pkg, bridge ? BRIDGE_WORKER_ARGS : [], daemon?.timeouts.stop ?? lifecycleTimeouts?.stop ?? 30)) {
|
|
1001
1064
|
findings.push(`${BACKGROUND_UNIT_DIR} must contain only the fixed ${unit} (runs as the package account, no User=, no root)`);
|
|
1002
1065
|
}
|
|
1003
1066
|
if (read("scripts/start-stop-status") !==
|
|
1004
1067
|
renderStandaloneScript("start-stop-status", {
|
|
1005
1068
|
package: pkg,
|
|
1069
|
+
lifecycleTimeouts,
|
|
1006
1070
|
...(daemon ? { daemon } : {}),
|
|
1007
1071
|
capabilities: {
|
|
1008
1072
|
jobs: { worker: "sha256-v1" },
|
|
@@ -1149,7 +1213,7 @@ function checkScripts(dir, findings) {
|
|
|
1149
1213
|
}
|
|
1150
1214
|
const DATA_KEYS = ["pkgwizard_remove_additional", "delete_app_data"];
|
|
1151
1215
|
const DELETE_COMMAND = /\brm\s+-[a-zA-Z]*[rR]|\bfind\b[^\n]*(-delete|-exec\s+rm)/;
|
|
1152
|
-
const UPGRADE_GUARD = /SYNOPKG_PKG_STATUS[^\n]
|
|
1216
|
+
const UPGRADE_GUARD = /SYNOPKG_PKG_STATUS[^\n]*(?:!=\s*"?UPGRADE"?|=\s*"?UNINSTALL"?)/;
|
|
1153
1217
|
function uninstallBoxes(file, keys) {
|
|
1154
1218
|
try {
|
|
1155
1219
|
const steps = JSON.parse(readFileSync(file, "utf8"));
|
|
@@ -1206,7 +1270,7 @@ function checkUninstall(dir, findings) {
|
|
|
1206
1270
|
const deletes = DELETE_COMMAND.test(body);
|
|
1207
1271
|
if (deletes) {
|
|
1208
1272
|
if (!UPGRADE_GUARD.test(body))
|
|
1209
|
-
findings.push('UNINSTALL_NO_UPGRADE_GUARD scripts/postuninst deletes data without
|
|
1273
|
+
findings.push('UNINSTALL_NO_UPGRADE_GUARD scripts/postuninst deletes data without an UNINSTALL-only or != UPGRADE guard, so an upgrade deletes it by mistake');
|
|
1210
1274
|
if (!/is_package_area\(\)\s*\{/.test(body) ||
|
|
1211
1275
|
!/if ! is_package_area "\$real"/.test(body))
|
|
1212
1276
|
findings.push("UNINSTALL_UNSAFE_DELETE scripts/postuninst does not verify the deleted paths are under /volumeN/@appdata|@apphome|@appconf|@appstore/<package>; an empty or redirected variable deletes the wrong data");
|
|
@@ -1265,3 +1329,11 @@ export async function listPayload(packageTgz, findings) {
|
|
|
1265
1329
|
});
|
|
1266
1330
|
return payload;
|
|
1267
1331
|
}
|
|
1332
|
+
function matchesGatewayConfig(actual, manifest) {
|
|
1333
|
+
const saved = JSON.parse(actual), expected = JSON.parse(identityGatewayConfig(manifest));
|
|
1334
|
+
// Older artifacts omit both work-budget members; do not reinterpret their bytes.
|
|
1335
|
+
for (const key of ['operation_seconds', 'stop_seconds'])
|
|
1336
|
+
if (saved[key] === undefined)
|
|
1337
|
+
delete expected[key];
|
|
1338
|
+
return actual === JSON.stringify(expected) + '\n';
|
|
1339
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { renderComposeRetainedData } from "../build-spk/compose-retained-data.js";
|
|
2
|
+
import { renderNativeGatewayPrepare } from './native-gateway.js';
|
|
1
3
|
import { renderComposeDiagnosis } from "../build-spk/compose-diagnosis.js";
|
|
2
4
|
import { composeDaemon, renderComposeInstall } from "./compose.js";
|
|
3
5
|
import { renderBackgroundControl, renderBackgroundWorkerStart, renderBackgroundWorkerStop, renderUserManagerRecovery, backgroundUnitName, } from "./background.js";
|
|
@@ -11,6 +13,14 @@ import { composeAppScript } from "./app-scripts.js";
|
|
|
11
13
|
const UNINSTALL_ONLY = '[ "${SYNOPKG_PKG_STATUS:-}" = UPGRADE ] || synopkgland_uninstall_hook\n';
|
|
12
14
|
/** Package-account lifecycle hooks for CGI, daemon and background packages. */
|
|
13
15
|
export function renderStandaloneScript(name, manifest) {
|
|
16
|
+
const script = renderStandaloneScriptInternal(name, manifest);
|
|
17
|
+
const prepared = ['postinst', 'postupgrade'].includes(name) ? renderNativeGatewayPrepare(manifest) : '';
|
|
18
|
+
const sdk = prepared ? script.replace('#!/bin/sh\n', '#!/bin/sh\n' + prepared) : script;
|
|
19
|
+
return manifest.appScripts?.some((app) => app === name)
|
|
20
|
+
? composeAppScript(name, sdk)
|
|
21
|
+
: sdk;
|
|
22
|
+
}
|
|
23
|
+
function renderStandaloneScriptInternal(name, manifest) {
|
|
14
24
|
if (manifest.compose) {
|
|
15
25
|
const native = {
|
|
16
26
|
...manifest,
|
|
@@ -19,7 +29,7 @@ export function renderStandaloneScript(name, manifest) {
|
|
|
19
29
|
};
|
|
20
30
|
const body = name === "preinst"
|
|
21
31
|
? renderComposeInstall(manifest.package)
|
|
22
|
-
:
|
|
32
|
+
: renderStandaloneScriptInternal(name, { ...native, appScripts: undefined });
|
|
23
33
|
const control = `"/var/packages/${manifest.package}/target/daemon/control"`;
|
|
24
34
|
const paths = `"\${SYNOPKG_PKGDEST:-/var/packages/${manifest.package}/target}" "\${SYNOPKG_PKGVAR:-/var/packages/${manifest.package}/var}"`;
|
|
25
35
|
const preflight = name === "start-stop-status"
|
|
@@ -37,6 +47,7 @@ export function renderStandaloneScript(name, manifest) {
|
|
|
37
47
|
? `if [ "$1" = prestart ]; then\n ${renderComposeDiagnosis(project, "prestart")}fi\n`
|
|
38
48
|
: "";
|
|
39
49
|
const ledger = diagnosis + preflight + remember +
|
|
50
|
+
(name === "postuninst" ? renderComposeRetainedData(manifest.compose.plan.app_data) : "") +
|
|
40
51
|
(manifest.identityGateway && !manifest.compose.plan.mounts_blocked
|
|
41
52
|
? name === "start-stop-status"
|
|
42
53
|
? `if [ "$1" = restart ]; then\n "$0" stop || exit $?\n exec "$0" start\nfi\nif [ "$1" = start ]; then\n ${control} acquire ${paths} || exit $?\nelif [ "$1" = stop ]; then\n ${control} release ${paths} || exit $?\nfi\n`
|
|
@@ -47,18 +58,14 @@ export function renderStandaloneScript(name, manifest) {
|
|
|
47
58
|
const withLedger = ledger
|
|
48
59
|
? body.replace("#!/bin/sh\n", "#!/bin/sh\n" + ledger)
|
|
49
60
|
: body;
|
|
50
|
-
return
|
|
51
|
-
? composeAppScript(name, withLedger)
|
|
52
|
-
: withLedger;
|
|
61
|
+
return withLedger;
|
|
53
62
|
}
|
|
54
63
|
const portal = nativePortal(manifest);
|
|
55
64
|
const script = renderCapabilityScript(name, manifest);
|
|
56
65
|
const sdk = portal
|
|
57
66
|
? script.replace("#!/bin/sh\n", () => `#!/bin/sh\n${renderPortalLifecycle(name, portal)}`)
|
|
58
67
|
: script;
|
|
59
|
-
return
|
|
60
|
-
? composeAppScript(name, sdk)
|
|
61
|
-
: sdk;
|
|
68
|
+
return sdk;
|
|
62
69
|
}
|
|
63
70
|
function renderCapabilityScript(name, manifest) {
|
|
64
71
|
if (name === "preupgrade" &&
|
|
@@ -159,7 +166,7 @@ function renderStandaloneScriptBody(name, manifest) {
|
|
|
159
166
|
if (manifest.daemon)
|
|
160
167
|
return renderDaemonScript(name, manifest);
|
|
161
168
|
if (manifest.capabilities.jobs && name === "start-stop-status")
|
|
162
|
-
return renderBackgroundControl(manifest.package);
|
|
169
|
+
return renderBackgroundControl(manifest.package, manifest.lifecycleTimeouts?.start ?? 30);
|
|
163
170
|
if (name === "start-stop-status") {
|
|
164
171
|
return `#!/bin/sh
|
|
165
172
|
case "$1" in
|
|
@@ -191,7 +198,7 @@ function renderDelegatedDaemonScript(name, manifest) {
|
|
|
191
198
|
return `#!/bin/sh\n${workerStop}${renderUninstallHook(manifest)}if [ "\${SYNOPKG_PKG_STATUS:-}" = UPGRADE ]; then\n${stop("stop", "stop")}fi\n${purge}`;
|
|
192
199
|
}
|
|
193
200
|
return `#!/bin/sh
|
|
194
|
-
${workerStop}${renderUserManagerRecovery()}${renderBackgroundWorkerStart(pkg)}case "$1" in
|
|
201
|
+
${workerStop}${renderUserManagerRecovery(manifest.daemon?.timeouts?.start ?? manifest.lifecycleTimeouts?.start ?? 30)}${renderBackgroundWorkerStart(pkg)}case "$1" in
|
|
195
202
|
prestart) recover_user_manager ;;
|
|
196
203
|
prestop) exit 0 ;;
|
|
197
204
|
start)
|
|
@@ -86,6 +86,7 @@ export interface StandaloneManifest {
|
|
|
86
86
|
tasks: ExecutorTask[];
|
|
87
87
|
};
|
|
88
88
|
daemon?: Daemon;
|
|
89
|
+
lifecycleTimeouts?: Daemon["timeouts"];
|
|
89
90
|
/** `identity_gateway`: signs DSM users in to the daemon's own web UI. */
|
|
90
91
|
identityGateway?: IdentityGateway;
|
|
91
92
|
compose?: ComposeApp;
|