@synopackageland/cli 0.17.4 → 0.17.6
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 +19 -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 +122 -35
- 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
|
@@ -2,6 +2,7 @@ import type { ComposeApp } from "./compose.js";
|
|
|
2
2
|
import type { Capabilities } from "./manifest.js";
|
|
3
3
|
import { type SharedFolder } from "./shared-folder.js";
|
|
4
4
|
import { type PortalPackage } from "../portal/package.js";
|
|
5
|
+
import { type NativeGatewayPlan } from './native-gateway.js';
|
|
5
6
|
/**
|
|
6
7
|
* `conf/resource` for a native package: only DSM's official `data-share`,
|
|
7
8
|
* `systemd-user-unit` and `web-config` workers, in a fixed key order.
|
|
@@ -9,6 +10,8 @@ import { type PortalPackage } from "../portal/package.js";
|
|
|
9
10
|
*/
|
|
10
11
|
export declare function renderStandaloneResource(m: {
|
|
11
12
|
package: string;
|
|
13
|
+
os_min_ver?: string;
|
|
14
|
+
identityGateway?: NativeGatewayPlan;
|
|
12
15
|
capabilities: Capabilities;
|
|
13
16
|
compose?: ComposeApp;
|
|
14
17
|
sharedFolder?: SharedFolder;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { renderDockerProjectResource } from "../build-spk/resource-worker.js";
|
|
2
2
|
import { sharedFolderResource } from "./shared-folder.js";
|
|
3
3
|
import { portalResource } from "../portal/package.js";
|
|
4
|
+
import { nativeGatewayResource } from './native-gateway.js';
|
|
4
5
|
/**
|
|
5
6
|
* `conf/resource` for a native package: only DSM's official `data-share`,
|
|
6
7
|
* `systemd-user-unit` and `web-config` workers, in a fixed key order.
|
|
@@ -8,13 +9,14 @@ import { portalResource } from "../portal/package.js";
|
|
|
8
9
|
*/
|
|
9
10
|
export function renderStandaloneResource(m, portal) {
|
|
10
11
|
const dataShare = m.sharedFolder && sharedFolderResource(m.package, m.sharedFolder);
|
|
12
|
+
const webConfig = { ...(portal ? portalResource()['web-config'] : {}), ...nativeGatewayResource(m) };
|
|
11
13
|
const resource = {
|
|
12
14
|
...(m.compose && !m.compose.plan.mounts_blocked
|
|
13
15
|
? renderDockerProjectResource(m.package, "compose", false, m.compose.plan.project_name)
|
|
14
16
|
: {}),
|
|
15
17
|
...(dataShare ? { "data-share": dataShare } : {}),
|
|
16
18
|
...(m.capabilities.jobs ? { "systemd-user-unit": {} } : {}),
|
|
17
|
-
...(
|
|
19
|
+
...(Object.keys(webConfig).length ? { 'web-config': webConfig } : {}),
|
|
18
20
|
};
|
|
19
21
|
return Object.keys(resource).length > 0
|
|
20
22
|
? JSON.stringify(resource)
|
package/dist/standalone/spk.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nativeGatewaySupported, nativeGatewaySdk, renderNativeGatewayConfig, NATIVE_GATEWAY_CONFIG, legacyGatewayNotice, gatewayEntryPath } from './native-gateway.js';
|
|
1
2
|
import { portalInfoFields } from "../portal/info.js";
|
|
2
3
|
import { sdkSource } from "../runtime/sdk-source.js";
|
|
3
4
|
import { resolvedSdkLaunch } from "../contract/app-launch.js";
|
|
@@ -116,6 +117,10 @@ export async function assembleStandaloneSpk(options) {
|
|
|
116
117
|
chmodSync(target, EXEC_MODE);
|
|
117
118
|
}
|
|
118
119
|
writeFile(join(staging, "package", GATEWAY_CONFIG_PATH), identityGatewayConfig(manifest));
|
|
120
|
+
if (nativeGatewaySupported(manifest))
|
|
121
|
+
writeFile(join(staging, 'package', NATIVE_GATEWAY_CONFIG), renderNativeGatewayConfig(manifest));
|
|
122
|
+
if (!nativeGatewaySdk(manifest.identityGateway.sdk))
|
|
123
|
+
writeFile(join(staging, 'package/ui/sso/open.html'), legacyGatewayNotice());
|
|
119
124
|
}
|
|
120
125
|
if (manifest.capabilities.jobs) {
|
|
121
126
|
if (!options.backgroundWorker)
|
|
@@ -208,8 +213,11 @@ export async function assembleStandaloneSpk(options) {
|
|
|
208
213
|
.digest("hex");
|
|
209
214
|
writeFile(join(staging, "sdk-package.json"), JSON.stringify({
|
|
210
215
|
schema: 2,
|
|
216
|
+
...(manifest.lifecycleTimeouts ? { lifecycleTimeouts: manifest.lifecycleTimeouts } : {}),
|
|
217
|
+
...("timeoutSeconds" in manifest.backend && manifest.backend.timeoutSeconds ? { operationSeconds: manifest.backend.timeoutSeconds } : {}),
|
|
211
218
|
...(options.apiDescription ? { ctl: { hash: options.apiDescription.sha256, registry: options.apiRegistry } } : {}),
|
|
212
219
|
backend: manifest.backend.kind,
|
|
220
|
+
...(nativeGatewaySupported(manifest) ? { native_gateway: { sdk: manifest.identityGateway.sdk, listen: manifest.identityGateway.listen } } : {}),
|
|
213
221
|
launch: resolvedSdkLaunch(manifest),
|
|
214
222
|
capabilities: Object.keys(manifest.capabilities).sort(),
|
|
215
223
|
...(manifest.compose
|
|
@@ -236,7 +244,7 @@ export async function assembleStandaloneSpk(options) {
|
|
|
236
244
|
writeFile(join(staging, "conf", "resource"), resource);
|
|
237
245
|
if (manifest.capabilities.jobs) {
|
|
238
246
|
mkdirSync(join(staging, BACKGROUND_UNIT_DIR), { recursive: true });
|
|
239
|
-
writeFile(join(staging, BACKGROUND_UNIT_DIR, backgroundUnitName(manifest.package)), renderBackgroundUnit(manifest.package, isBridgeBackend(manifest.backend) ? BRIDGE_WORKER_ARGS : []));
|
|
247
|
+
writeFile(join(staging, BACKGROUND_UNIT_DIR, backgroundUnitName(manifest.package)), renderBackgroundUnit(manifest.package, isBridgeBackend(manifest.backend) ? BRIDGE_WORKER_ARGS : [], manifest.daemon?.timeouts.stop ?? manifest.lifecycleTimeouts?.stop ?? 30));
|
|
240
248
|
}
|
|
241
249
|
mkdirSync(join(staging, "scripts"));
|
|
242
250
|
for (const name of LIFECYCLE_SCRIPTS) {
|
|
@@ -265,6 +273,7 @@ export async function assembleStandaloneSpk(options) {
|
|
|
265
273
|
? renderSharedFolderUpgradeWizardFiles(manifest.package, folder)
|
|
266
274
|
: {}),
|
|
267
275
|
...renderUninstallWizardFiles(manifest.package, "native", {
|
|
276
|
+
composeData: manifest.compose?.plan.app_data,
|
|
268
277
|
executor: Boolean(options.executor),
|
|
269
278
|
portal: Boolean(portal),
|
|
270
279
|
portalEntry: Boolean(manifest.portal && portalByExecutor(manifest.os_min_ver)),
|
|
@@ -324,7 +333,7 @@ export function renderInfo(manifest, arch, checksum, executorDigest) {
|
|
|
324
333
|
if (manifest.identityGateway)
|
|
325
334
|
fields.push([
|
|
326
335
|
"adminurl",
|
|
327
|
-
|
|
336
|
+
gatewayEntryPath(manifest),
|
|
328
337
|
]);
|
|
329
338
|
}
|
|
330
339
|
const backend = manifest.backend;
|
|
@@ -341,7 +350,9 @@ export function renderInfo(manifest, arch, checksum, executorDigest) {
|
|
|
341
350
|
if (text?.description)
|
|
342
351
|
fields.push([`description_${DSM_LANGS[locale]}`, text.description]);
|
|
343
352
|
}
|
|
344
|
-
|
|
353
|
+
if (nativeGatewaySupported(manifest))
|
|
354
|
+
fields.push(['startstop_restart_services', 'nginx.service']);
|
|
355
|
+
return portalInfoFields(fields, Boolean(manifest.portal) || nativeGatewaySupported(manifest)).map(([key, value]) => `${key}=${quote(value)}\n`).join("");
|
|
345
356
|
}
|
|
346
357
|
function copyUiTree(source, target) {
|
|
347
358
|
for (const entry of ["app.js", "app.css"]) {
|
|
@@ -22,6 +22,7 @@ export function renderUninstallHook(manifest) {
|
|
|
22
22
|
if (!purge)
|
|
23
23
|
return "";
|
|
24
24
|
const pkg = manifest.package;
|
|
25
|
+
const reserve = Math.min(5, Math.max(1, Math.floor(purge.timeout / 4)));
|
|
25
26
|
const env = [
|
|
26
27
|
"PATH=/usr/bin:/bin", 'HOME="$hook_var"', 'TMPDIR="$hook_var/tmp"',
|
|
27
28
|
...Object.entries(purge.env).map(([key, value]) => `${key}=${word(value)}`),
|
|
@@ -47,7 +48,11 @@ ${manifest.uninstall?.state ? wizardFlag("delete_state", ["pkgwizard_delete_stat
|
|
|
47
48
|
# DSM's bash logs a failed kill to syslog even with 2>/dev/null, so a
|
|
48
49
|
# finished hook is looked up in /proc wherever there is one.
|
|
49
50
|
hook_running() {
|
|
50
|
-
if [ -d /proc/self ]; then
|
|
51
|
+
if [ -d /proc/self ]; then
|
|
52
|
+
[ -r "/proc/$hook_pid/stat" ] || return 1
|
|
53
|
+
case "$(sed 's/.*) //' "/proc/$hook_pid/stat" 2>/dev/null)" in Z*|X*|'') return 1 ;; esac
|
|
54
|
+
return 0
|
|
55
|
+
else kill -0 "$hook_pid" 2>/dev/null; fi
|
|
51
56
|
}
|
|
52
57
|
# A process in /proc whose group is the hook's ("pid (comm) state ppid pgrp ...").
|
|
53
58
|
hook_group_running() {
|
|
@@ -62,19 +67,27 @@ ${manifest.uninstall?.state ? wizardFlag("delete_state", ["pkgwizard_delete_stat
|
|
|
62
67
|
kill -"$1" "$hook_pid"
|
|
63
68
|
fi
|
|
64
69
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
hook_now() { if [ -r /proc/uptime ]; then awk '{print int($1)}' /proc/uptime; else date +%s; fi; }
|
|
71
|
+
hook_until=$(( $(hook_now) + ${purge.timeout} ))
|
|
72
|
+
hook_work_until=$((hook_until - ${reserve}))
|
|
73
|
+
while hook_running && [ "$(hook_now)" -lt "$hook_work_until" ]; do
|
|
74
|
+
sleep 0.1
|
|
69
75
|
done
|
|
70
76
|
hook_failure=
|
|
71
77
|
if hook_running; then
|
|
72
78
|
hook_signal TERM
|
|
73
|
-
|
|
79
|
+
hook_grace_until=$((hook_until - 1))
|
|
80
|
+
while hook_group_running && [ "$(hook_now)" -lt "$hook_grace_until" ]; do sleep 0.1; done
|
|
74
81
|
hook_signal KILL
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
while hook_running && [ "$(hook_now)" -lt "$hook_until" ]; do sleep 0.1; done
|
|
83
|
+
if hook_running || hook_group_running; then
|
|
84
|
+
echo cleanup-pending >"$hook_var/uninstall-hook.pending"
|
|
85
|
+
else
|
|
86
|
+
wait "$hook_pid" 2>/dev/null
|
|
87
|
+
fi
|
|
88
|
+
hook_failure="timeout after ${purge.timeout} s; cleanup may be pending"
|
|
77
89
|
else
|
|
90
|
+
rm -f "$hook_var/uninstall-hook.pending"
|
|
78
91
|
wait "$hook_pid"
|
|
79
92
|
hook_status=$?
|
|
80
93
|
[ "$hook_status" -eq 0 ] || hook_failure="exit $hook_status"
|
|
@@ -19,7 +19,7 @@ export interface UninstallPurge {
|
|
|
19
19
|
exec: string;
|
|
20
20
|
args: string[];
|
|
21
21
|
env: Record<string, string>;
|
|
22
|
-
/** Seconds, 2..
|
|
22
|
+
/** Seconds, 2..3600. */
|
|
23
23
|
timeout: number;
|
|
24
24
|
/** A root/<name>.sh the executor runs at uninstall, only while its approval is live. */
|
|
25
25
|
root?: string;
|
|
@@ -16,7 +16,7 @@ export function readUninstall(value, context, fail) {
|
|
|
16
16
|
if (value === undefined)
|
|
17
17
|
return undefined;
|
|
18
18
|
const invalid = () => {
|
|
19
|
-
fail({ code: "UNINSTALL_INVALID", message: "uninstall accepts only purge {exec, args, env, timeout, root} and state {items: [{key}]}; exec must be a fixed executable inside the package (not in executor/ or root/), and timeout is an integer from 2 to
|
|
19
|
+
fail({ code: "UNINSTALL_INVALID", message: "uninstall accepts only purge {exec, args, env, timeout, root} and state {items: [{key}]}; exec must be a fixed executable inside the package (not in executor/ or root/), and timeout is an integer from 2 to 3600 seconds." });
|
|
20
20
|
return undefined;
|
|
21
21
|
};
|
|
22
22
|
if (!record(value) || Object.keys(value).some(k => !FIELDS.has(k)))
|
|
@@ -54,7 +54,7 @@ function readPurge(value, context, fail) {
|
|
|
54
54
|
|| Object.entries(env).some(([k, v]) => !envKey(k) || k.startsWith("SYNOPKGLAND_") || !token(v)))
|
|
55
55
|
return "invalid";
|
|
56
56
|
const timeout = value.timeout ?? 60;
|
|
57
|
-
if (!Number.isInteger(timeout) || timeout < 2 || timeout >
|
|
57
|
+
if (!Number.isInteger(timeout) || timeout < 2 || timeout > 3600)
|
|
58
58
|
return "invalid";
|
|
59
59
|
const root = value.root;
|
|
60
60
|
if (root !== undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.6",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@napi-rs/keyring": "^2.1.1",
|
|
30
|
-
"@synopackageland/templates": "0.17.
|
|
30
|
+
"@synopackageland/templates": "0.17.8",
|
|
31
31
|
"ajv": "^8.17.1",
|
|
32
32
|
"commander": "^13.1.0",
|
|
33
33
|
"json-canonicalize": "^3.0.1",
|
|
@@ -39,6 +39,12 @@ scripts run as the package account; containers are managed by Container Manager.
|
|
|
39
39
|
|
|
40
40
|
Set `compose.startup_timeout_seconds: 600` for a slow cold start or migration.
|
|
41
41
|
Omitting it uses 120 seconds; only integers from 2 through 900 are accepted.
|
|
42
|
+
Lock waiting, status, readiness, stability and handoff share that deadline; there
|
|
43
|
+
is no extra fixed handoff allowance. The cap is a policy bound, not a measured
|
|
44
|
+
DSM lifecycle limit. `daemon: {timeouts: {stop: 120, status: 15, uninstall: 300}}`
|
|
45
|
+
configures the other lifecycle phases without declaring a daemon payload.
|
|
46
|
+
A transient health failure closes new ingress while the running App gets one
|
|
47
|
+
operation-budget recovery window; authority or generation failures stop at once.
|
|
42
48
|
Use a matching SDK that implements the field and rebuild the App; older planners
|
|
43
49
|
are rejected for explicit budgets. The SDK plan drives generated daemon start
|
|
44
50
|
and supplies every container with `SYNOPKGLAND_STARTUP_BUDGET_SECONDS`, overriding
|
|
@@ -386,3 +392,28 @@ Do not silently switch networking, add Docker socket access, run an App hook as
|
|
|
386
392
|
root or delete DSM configuration to claim compatibility.
|
|
387
393
|
|
|
388
394
|
Compose web health is required before check/build; init has no guessed endpoint or status. Select both `--health-path` and `--health-status`, or edit `compose.web.health`. Readiness does not follow redirects. Static check is not an HTTP or arbitrary-UID/storage preflight. For official-image upstream packaging and DSM login, read [upstream-fork.md](upstream-fork.md).
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
## Named volumes and uninstall
|
|
398
|
+
|
|
399
|
+
The SDK planner and lock inventory all current project-owned named volumes as
|
|
400
|
+
App data, with exact names. Compose alone never selects an executor or root
|
|
401
|
+
permissions. The package account does not delete named volumes or access Docker.
|
|
402
|
+
|
|
403
|
+
Before confirmation, the uninstall wizard states that container data volumes
|
|
404
|
+
are kept even when delete-data is selected, and lists the current exact names.
|
|
405
|
+
Only exact `SYNOPKG_PKG_STATUS=UNINSTALL` with an affirmative
|
|
406
|
+
`pkgwizard_remove_additional` (or supported mirror/legacy alias) clears the
|
|
407
|
+
package data/config/home contents the package account can clear. Upgrade, stop,
|
|
408
|
+
unknown status and keep preserve data. Unsafe paths and remnants are reported.
|
|
409
|
+
|
|
410
|
+
On delete-data uninstall with a nonempty volume inventory, generated postuninst
|
|
411
|
+
appends nine-language native text through `SYNOPKG_TEMP_LOGFILE`, preserving
|
|
412
|
+
existing text. It says plainly the listed volumes were NOT deleted and ends
|
|
413
|
+
with `COMPOSE_DATA_RETAINED`. The list is the current planner/lock inventory,
|
|
414
|
+
not a runtime existence check; volumes retired by older versions may also remain.
|
|
415
|
+
No full-data-deletion success is claimed and no unverified deletion path is given.
|
|
416
|
+
Administrator cleanup procedures, executor-tier Apps and storage redesign are
|
|
417
|
+
out of scope. Final DSM native UI acceptance remains required.
|
|
418
|
+
|
|
419
|
+
See the SDK's [Compose App data contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/main/docs/compose-app-data.md).
|
|
@@ -113,7 +113,7 @@ root:
|
|
|
113
113
|
migrate: {} # 沒有 run、job:只作為 start 步驟
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
start、stop 步驟與 `uninstall.purge.root` 都以 root、無 session 執行,只在管理員核准有效時跑;這些程式不能宣告 run 或 job(session 要用時以另一個名稱設定,可指向同一份內容)。start 步驟在新版本第一次啟動、executor 開 socket
|
|
116
|
+
start、stop 步驟與 `uninstall.purge.root` 都以 root、無 session 執行,只在管理員核准有效時跑;這些程式不能宣告 run 或 job(session 要用時以另一個名稱設定,可指向同一份內容)。start 步驟在新版本第一次啟動、executor 開 socket 並回讀核准任務之後執行,預設最多 60 秒;若宣告 lifecycle budget,則從核准的 start 剩餘預算扣除收尾餘裕;執行期間 executor 只回應 `status`(`state: "starting"`),其他請求回可重試的 `EXECUTOR_STARTING`,DSM 視窗的核准會等它結束,狀態讀取則立即回 `executor_starting`、由 SDK 面板自行重試,失敗只寫進 executor log,不擋啟動;同一版本不重跑,程式要能重複執行。結果由 executor `status` 的 `start_step` 讀取(`pending`、`ok`、`failed`,含 exit 與 output)。DSM 的 start 只寫入要求就返回,所以 `dsm-scripts/postinst`、`postupgrade`、`start` 看不到它的結果;root 的清理或遷移放 `root.start`,非特權的收尾放 `dsm-scripts/`。宣告 `root.start` 會改變 policy digest,需要管理員重新核准;沒有宣告的 App policy 不變。需要 SDK 0.10.3 以上。
|
|
117
117
|
|
|
118
118
|
schema 是 executor 的封閉子集(每個 object 都要 `additionalProperties: false`,key `[a-z0-9_]`,pattern 為 RE2)。這些設定需要支援它們的 SDK executor(`EXECUTOR_ROOT_PROGRAMS_UNSUPPORTED`)。preupgrade 呼叫 `executor/lifecycle stop --upgrade`;舊 SDK 的 helper 不認得此旗標時改用一般 stop。
|
|
119
119
|
|
|
@@ -108,3 +108,28 @@ A silent upgrade has no wizard values; use a default.
|
|
|
108
108
|
change they made.
|
|
109
109
|
- An upgrade runs the old package's preuninst and postuninst; check
|
|
110
110
|
`SYNOPKG_PKG_STATUS` before deleting anything.
|
|
111
|
+
|
|
112
|
+
## Work budgets
|
|
113
|
+
|
|
114
|
+
Use existing `daemon.timeouts` for start/stop/status/uninstall (defaults
|
|
115
|
+
30/30/5/60 seconds). Start/stop/uninstall accept 2–3600 seconds and status accepts
|
|
116
|
+
1–3600. A `daemon` containing only `timeouts` declares budgets without an
|
|
117
|
+
executable, including background and Compose Apps; Compose start still uses
|
|
118
|
+
`compose.startup_timeout_seconds` (120, 2–900). Background user-manager recovery
|
|
119
|
+
uses the start allowance and its generated package user unit uses the stop
|
|
120
|
+
allowance with `KillMode=control-group`.
|
|
121
|
+
|
|
122
|
+
`backend.timeout_seconds` owns request/operation work. Binary and Compose Apps
|
|
123
|
+
without a CGI request setting can enlarge the shared operation allowance through
|
|
124
|
+
`daemon.timeouts.status` above its 60-second default. Generated gateway and
|
|
125
|
+
approved executor configurations inherit it; nested calls use remaining time and
|
|
126
|
+
leave response reserve. Budget changes in the protected policy require renewed
|
|
127
|
+
approval and do not change token/session/grant lifetimes.
|
|
128
|
+
|
|
129
|
+
`uninstall.purge.timeout` accepts 2–3600 seconds and includes termination reserve.
|
|
130
|
+
A timeout is logged and may leave `uninstall-hook.pending`; the uninstall may
|
|
131
|
+
continue. The executor helper's JSON state distinguishes `accepted`,
|
|
132
|
+
`cleanup-complete` and `cleanup-pending`. An exit of zero acknowledges the retained
|
|
133
|
+
request and does not prove cleanup finished. Unconfirmed cleanup retains protected
|
|
134
|
+
state for a later retry. DSM's outer install/uninstall deadline is not established
|
|
135
|
+
by these settings.
|