@synopackageland/cli 0.35.0 → 0.37.0
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 +54 -0
- package/README.md +15 -3
- package/assets/go/route-audit/main.go +181 -8
- package/dist/build-spk/install-wizard.d.ts +2 -0
- package/dist/build-spk/install-wizard.js +4 -0
- package/dist/build-spk/wizard-messages.d.ts +22 -0
- package/dist/build-spk/wizard-messages.js +22 -0
- package/dist/cli.js +33 -0
- package/dist/error-registry.js +15 -0
- package/dist/skill.js +5 -0
- package/dist/standalone/api-permissions.d.ts +4 -1
- package/dist/standalone/api-permissions.js +10 -4
- package/dist/standalone/build.js +5 -0
- package/dist/standalone/hyper-backup.d.ts +59 -0
- package/dist/standalone/hyper-backup.js +180 -0
- package/dist/standalone/inspect-spk.d.ts +5 -0
- package/dist/standalone/inspect-spk.js +28 -10
- package/dist/standalone/manifest.d.ts +9 -0
- package/dist/standalone/manifest.js +32 -1
- package/dist/standalone/shared-folder.d.ts +6 -1
- package/dist/standalone/shared-folder.js +13 -0
- package/dist/standalone/spk.d.ts +2 -0
- package/dist/standalone/spk.js +20 -6
- package/package.json +2 -2
- package/skills/SKILL.md +34 -2
- package/skills/reference/backup.md +70 -0
package/dist/standalone/spk.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { settingsInstallSteps, settingsConfig } from './install-settings.js';
|
|
2
|
+
import { backupConfig, backupHookProblem, backupRecord, renderBackupScripts, BACKUP_PAYLOAD_DIR, BACKUP_SCRIPTS_DIR, BACKUP_SCRIPT_MODES } from './hyper-backup.js';
|
|
2
3
|
import { nativeGatewaySupported, nativeGatewaySdk, renderNativeGatewayConfig, NATIVE_GATEWAY_CONFIG, legacyGatewayNotice, gatewayEntryPath } from './native-gateway.js';
|
|
3
4
|
import { portalInfoFields } from "../portal/info.js";
|
|
4
5
|
import { sdkSource } from "../runtime/sdk-source.js";
|
|
@@ -13,7 +14,7 @@ import { infoArchValue } from "../build-spk/dsm-arch.js";
|
|
|
13
14
|
import { packageIcons, writeDsmWindowIcons, writePackageCenterIcons, } from "../build-spk/dsm-icons.js";
|
|
14
15
|
import { renderNativePrivilege } from "../build-spk/native-lifecycle.js";
|
|
15
16
|
import { renderUninstallWizardFiles } from "../build-spk/uninstall-wizard.js";
|
|
16
|
-
import { renderInstallWizardFiles, renderNativeInstallWizard, } from "../build-spk/install-wizard.js";
|
|
17
|
+
import { renderInstallWizardFiles, integrationWizardSteps, renderNativeInstallWizard, } from "../build-spk/install-wizard.js";
|
|
17
18
|
import { writeWizardUiFiles, } from "../build-spk/wizard-i18n.js";
|
|
18
19
|
import { renderDelegateScopeSteps } from "../build-spk/delegate-scope.js";
|
|
19
20
|
import { createReproducibleTar } from "../build-spk/reproducible-archive.js";
|
|
@@ -40,7 +41,7 @@ import { portalByExecutor } from "../build-spk/dsm-version.js";
|
|
|
40
41
|
import { PORTAL_JSON_RELPATH, PORTAL_MUSTACHE_RELPATH, renderPortalJson, renderPortalMustache, renderPortalWizardStep, } from "../portal/package.js";
|
|
41
42
|
import { executorPlan, } from "../runtime/executor-build.js";
|
|
42
43
|
import { EXECUTOR_ANCHOR } from "../runtime/executor-inspect.js";
|
|
43
|
-
import {
|
|
44
|
+
import { renderUpgradeWizardFiles, sharedFolderInstallSteps, } from "./shared-folder.js";
|
|
44
45
|
import { stateItemTexts, uninstallExecProblem, uninstallRecord, } from "./uninstall.js";
|
|
45
46
|
const FILE_MODE = 0o644;
|
|
46
47
|
const EXEC_MODE = 0o755;
|
|
@@ -225,6 +226,14 @@ export async function assembleStandaloneSpk(options) {
|
|
|
225
226
|
copyExecutable(options.installSettingsControl, join(target, 'control'));
|
|
226
227
|
writeFile(join(target, 'settings.json'), JSON.stringify(settingsConfig(manifest), null, 2) + '\n');
|
|
227
228
|
}
|
|
229
|
+
const backup = options.appBackupControl ? manifest.hyperBackup : undefined;
|
|
230
|
+
if (backup) {
|
|
231
|
+
const hook = backupHookProblem(join(staging, "package"), backup);
|
|
232
|
+
if (hook)
|
|
233
|
+
throw new StandaloneBuildError("BACKUP_HOOK_MISSING", hook);
|
|
234
|
+
copyExecutable(options.appBackupControl, join(staging, "package", BACKUP_PAYLOAD_DIR, "control"));
|
|
235
|
+
writeFile(join(staging, "package", BACKUP_PAYLOAD_DIR, "config.json"), backupConfig(manifest.package, backup));
|
|
236
|
+
}
|
|
228
237
|
const packageTgz = join(staging, "package.tgz");
|
|
229
238
|
await createReproducibleTar(join(staging, "package"), packageTgz, {
|
|
230
239
|
gzip: true,
|
|
@@ -237,6 +246,8 @@ export async function assembleStandaloneSpk(options) {
|
|
|
237
246
|
schema: 2,
|
|
238
247
|
...(moduleVersion ? { ui_module: moduleVersion } : {}),
|
|
239
248
|
...(manifest.installSettings ? { installSettings: manifest.installSettings } : {}),
|
|
249
|
+
...(backup ? { hyperBackup: backupRecord(backup) } : {}),
|
|
250
|
+
...(manifest.integrations ? { integrations: manifest.integrations } : {}),
|
|
240
251
|
...(manifest.lifecycleTimeouts ? { lifecycleTimeouts: manifest.lifecycleTimeouts } : {}),
|
|
241
252
|
...("timeoutSeconds" in manifest.backend && manifest.backend.timeoutSeconds ? { operationSeconds: manifest.backend.timeoutSeconds } : {}),
|
|
242
253
|
...(options.apiDescription ? { ctl: { hash: options.apiDescription.sha256, registry: options.apiRegistry } } : {}),
|
|
@@ -282,6 +293,11 @@ export async function assembleStandaloneSpk(options) {
|
|
|
282
293
|
for (const name of LIFECYCLE_SCRIPTS) {
|
|
283
294
|
writeFile(join(staging, "scripts", name), renderStandaloneScript(name, manifest), EXEC_MODE);
|
|
284
295
|
}
|
|
296
|
+
if (backup) {
|
|
297
|
+
mkdirSync(join(staging, BACKUP_SCRIPTS_DIR));
|
|
298
|
+
for (const [name, text] of Object.entries(renderBackupScripts(backup)))
|
|
299
|
+
writeFile(join(staging, BACKUP_SCRIPTS_DIR, name), text, BACKUP_SCRIPT_MODES[name]);
|
|
300
|
+
}
|
|
285
301
|
for (const name of manifest.appScripts ?? [])
|
|
286
302
|
copyExecutable(join(manifest.root, APP_SCRIPTS_DIR, name), join(staging, APP_SCRIPTS_SPK_DIR, name));
|
|
287
303
|
const folder = manifest.sharedFolder;
|
|
@@ -295,13 +311,11 @@ export async function assembleStandaloneSpk(options) {
|
|
|
295
311
|
: folder
|
|
296
312
|
? sharedFolderInstallSteps(manifest.package, folder)
|
|
297
313
|
: renderNativeInstallWizard;
|
|
298
|
-
const install = renderInstallWizardFiles(manifest.package, (t) => [...steps(t), ...settingsInstallSteps(manifest.installSettings)(t), ...(portal ? [renderPortalWizardStep(t, portal.alias)] : [])]);
|
|
314
|
+
const install = renderInstallWizardFiles(manifest.package, (t) => [...steps(t), ...settingsInstallSteps(manifest.installSettings)(t), ...(portal ? [renderPortalWizardStep(t, portal.alias)] : []), ...integrationWizardSteps(t, manifest.integrations)]);
|
|
299
315
|
writeWizardUiFiles(staging, {
|
|
300
316
|
...install.static,
|
|
301
317
|
...install.dynamic,
|
|
302
|
-
...(folder
|
|
303
|
-
? renderSharedFolderUpgradeWizardFiles(manifest.package, folder)
|
|
304
|
-
: {}),
|
|
318
|
+
...renderUpgradeWizardFiles(manifest.package, folder, manifest.integrations),
|
|
305
319
|
...renderUninstallWizardFiles(manifest.package, "native", {
|
|
306
320
|
composeData: manifest.compose?.plan.app_data,
|
|
307
321
|
executor: Boolean(options.executor),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
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.
|
|
30
|
+
"@synopackageland/templates": "~0.33.0",
|
|
31
31
|
"ajv": "^8.17.1",
|
|
32
32
|
"commander": "^13.1.0",
|
|
33
33
|
"json-canonicalize": "^3.0.1",
|
package/skills/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Initialize, extend, check, build, inspect, and deploy Synology appl
|
|
|
5
5
|
|
|
6
6
|
# Independent Synology SPKs
|
|
7
7
|
|
|
8
|
-
CLI 0.
|
|
8
|
+
CLI 0.36.0 installs templates through `~0.32.0`. A templates patch needs only a
|
|
9
9
|
templates release; npm install/update selects a patch in that minor line. The CLI
|
|
10
10
|
uses the locally installed templates and reads their SDK pin for defaults; it
|
|
11
11
|
never downloads templates at runtime. Preserve the lockfile and use `npm ci` for
|
|
@@ -24,7 +24,7 @@ Use this guide to initialize, build and inspect own applications or upstream OSS
|
|
|
24
24
|
|
|
25
25
|
For an existing open-source App, first read [reference/upstream-fork.md](reference/upstream-fork.md): upstream fork/history + untouched upstream files + all packaging in `synology/`, official container images pinned by tag/index digest, native official release binaries pinned by SHA-256 or a source build hook, and a single tag-merge upgrade command. Use `init --upstream-fork`; never rewrite the App as a component. Integrated upstream Apps require direct DSM administrator entry without a separate App password or an OIDC/SSO server: supported trusted headers first, disabling native login second; the shipped OpenList pattern uses the existing SDK 0.17.3 token adapter (`mode: token`, `access: dsm-admins`, `entry: admin-only`) with server-side Authorization injection. All DSM admins share one upstream admin; token reset requires package restart; WebDAV retains its native credentials.
|
|
26
26
|
|
|
27
|
-
Use SDK 0.
|
|
27
|
+
Use SDK/App 0.35.0 and CLI 0.37.0 for App entry enforcement. Declare every App method
|
|
28
28
|
in the permissions table; dynamic names and unregistered HTTP dispatchers are rejected.
|
|
29
29
|
Use fixed SDK routes and `identity_gateway.upstream_auth_paths` for client API
|
|
30
30
|
paths that rely on upstream authentication. Read
|
|
@@ -41,6 +41,36 @@ cannot upgrade a connection; use an existing upgrade-capable deployment
|
|
|
41
41
|
transport. The audit follows each configured Go binary's Linux import graph,
|
|
42
42
|
including declared workers; unshipped dev commands are excluded.
|
|
43
43
|
|
|
44
|
+
Use `ipc.StartUnix(ctx, path, handler)` for multiple private data-plane sockets.
|
|
45
|
+
Each direct `.sock` child of the package `var/run` gets an independent handle
|
|
46
|
+
with Close, Shutdown and Wait. Stop/revoke closes active streams and hijacked
|
|
47
|
+
connections, including WebSocket; keep target approval and pin checks in the
|
|
48
|
+
handler. A private socket may serve the upstream root path without CGI.
|
|
49
|
+
For official supervised children use `ipc.ReserveLoopback(ctx, 0)` to hold a
|
|
50
|
+
127.0.0.1 TCP port, read Port/Address and Release immediately before starting
|
|
51
|
+
the child. `ipc.ProbeLoopback(ctx, port)` checks a specific port without
|
|
52
|
+
retaining it. Check child readiness and retry bind conflicts: release-to-bind
|
|
53
|
+
is inherently racy. Never replace these with custom listeners or fixed ports.
|
|
54
|
+
|
|
55
|
+
An optional root helper is a separately approved executor payload.
|
|
56
|
+
`ipc.ServePeerUnix(ctx, path, ipc.PeerPolicy{UID: uid, GID: gid}, handler)`
|
|
57
|
+
supports a bounded raw JSON control protocol on a root-owned 0660 socket under
|
|
58
|
+
`/var/run/synopkgland/<package>/`. The SDK authenticates exactly the selected
|
|
59
|
+
UID with SO_PEERCRED; the handler validates bounded frames and current
|
|
60
|
+
authorization. `ipc.DialPeerUnix` verifies root ownership, protected ancestors,
|
|
61
|
+
mode and server UID zero. The server requires root AND the helper's explicit
|
|
62
|
+
`ipc.executorTier=executor` linker binding. Bind packageID in custom builds.
|
|
63
|
+
Generated CGI/workers bind tier `none`; never launch or install them as root.
|
|
64
|
+
The transport does not provide installation, approval or privilege elevation.
|
|
65
|
+
|
|
66
|
+
Audit every custom-hook Go payload with
|
|
67
|
+
`synopkgland audit-entry ./cmd/helper --dir backend --arch amd64,arm64,arm --json`
|
|
68
|
+
before packaging. Use `--permissions` when it registers App methods.
|
|
69
|
+
Each entry is checked against its actual target import graph, including every
|
|
70
|
+
file of shared packages. Unused server functions have no exemption. Use real
|
|
71
|
+
client/server packages or supported SDK boundaries; no App allowlists, hidden
|
|
72
|
+
directories, aliases or ABI workarounds.
|
|
73
|
+
|
|
44
74
|
**If you package someone else's open-source project, set `maintainer` to the
|
|
45
75
|
original project/owner (prefer the project's own team/org name, else the owner's
|
|
46
76
|
display name, else login), `metadata.maintainer_url` to the upstream repository,
|
|
@@ -61,6 +91,8 @@ Read reference/portal.md before declaring or changing a DSM Application Portal a
|
|
|
61
91
|
|
|
62
92
|
Read [reference/lifecycle.md](reference/lifecycle.md) before editing `dsm-scripts/` (preinst, postinst, preupgrade, postupgrade, preuninst, postuninst, start-stop-status): the SDK runs them inside its generated scripts as the package account; never add root work or repeat SDK steps there.
|
|
63
93
|
|
|
94
|
+
Native Apps appear in Hyper Backup as applications by default. Read [reference/backup.md](reference/backup.md) before changing `backup` in dsm-package.yaml (opt out with `hyper_backup: false`, online mode, data version, hook). Hyper Backup runs the generated `scripts/backup/` as root; it is a fixed template that only switches to the package account, so never add or edit files there.
|
|
95
|
+
|
|
64
96
|
Read [reference/ui-vue.md](reference/ui-vue.md) when editing Vue components in the native-go Vue template; use the installed SDK contracts, keep DSM embedding boundaries, and verify the actual app.
|
|
65
97
|
|
|
66
98
|
Buttons follow DSM's policy ([reference/ui-vue.md](reference/ui-vue.md#buttons), the UI package's `docs/button-policy.md`): only the bar that closes a dialog, wizard or form draws capsules, with Cancel/Reset first and one primary (or one danger to confirm a deletion) last; toolbar, page, section and row actions are grey tool buttons, Delete included; links go to `Link`. Choose the variant, never the shape.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Hyper Backup application backup
|
|
2
|
+
|
|
3
|
+
Native Apps (Go, Python, Node, Rust and binary backends) appear as applications
|
|
4
|
+
in Hyper Backup's backup wizard by default. An administrator selects the App in
|
|
5
|
+
a backup task and restores it from that task later. Compose Apps keep their
|
|
6
|
+
data in container volumes, which this does not cover, so it is off for them
|
|
7
|
+
and `backup` is refused for a Compose App.
|
|
8
|
+
|
|
9
|
+
## Declaration
|
|
10
|
+
|
|
11
|
+
Nothing to declare for the default: offline backup of the whole data folder,
|
|
12
|
+
data version `1.0`.
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
backup:
|
|
16
|
+
online: false # default; true keeps the App running during a backup
|
|
17
|
+
data_version: "1.0" # "major.minor", see below
|
|
18
|
+
hook: bin/backup-hook # optional, relative to the package target
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Opt out with exactly:
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
backup:
|
|
25
|
+
hyper_backup: false
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **Offline** (default): Hyper Backup stops the App, the SDK archives the data
|
|
29
|
+
folder, and Hyper Backup starts the App again.
|
|
30
|
+
- **Online**: the App keeps running; SQLite databases are copied through
|
|
31
|
+
SQLite's backup API, their journals are left out, other files are copied as
|
|
32
|
+
they are. Use it only when other files stay consistent while the App runs,
|
|
33
|
+
or add a hook.
|
|
34
|
+
- **Hook**: a packaged executable run as the package account as `<hook> freeze`
|
|
35
|
+
before the export and `<hook> thaw` after it. It writes only to standard
|
|
36
|
+
error. The build fails with `BACKUP_HOOK_MISSING` when the file is not an
|
|
37
|
+
executable in the package.
|
|
38
|
+
- **Data version**: a restore accepts the same package with the same major
|
|
39
|
+
version and an equal or lower minor version; otherwise it refuses and keeps
|
|
40
|
+
the current data. Raise the minor version when the App migrates older data
|
|
41
|
+
itself, the major version when it cannot.
|
|
42
|
+
|
|
43
|
+
Restore always runs with the App stopped and replaces the whole data folder
|
|
44
|
+
(the package's `var`). It needs the App installed first.
|
|
45
|
+
|
|
46
|
+
## What runs as root
|
|
47
|
+
|
|
48
|
+
Hyper Backup runs a package's backup scripts as root. This is a DSM-owned hook,
|
|
49
|
+
not App runtime: the App still runs as its package account, and the SDK
|
|
50
|
+
privilege tiers for App code are unchanged. The CLI writes the root part from a
|
|
51
|
+
fixed template. It switches to the owner of the App's data folder (refusing
|
|
52
|
+
root), runs the SDK backup control as that account, and moves one archive
|
|
53
|
+
between that account and Hyper Backup's temporary folder without reading it.
|
|
54
|
+
The package ships no script that Hyper Backup would run while only listing
|
|
55
|
+
applications.
|
|
56
|
+
|
|
57
|
+
Never add files to `scripts/backup/` or edit them. `check` validates the
|
|
58
|
+
`backup` declaration and reports it as `hyperBackup` in `--json`; `inspect`
|
|
59
|
+
verifies the exact bytes of the scripts, the SDK control and its config, and
|
|
60
|
+
refuses any other file there, such as `can_export` or `info.dynamic`.
|
|
61
|
+
|
|
62
|
+
## Errors
|
|
63
|
+
|
|
64
|
+
| Code | Meaning |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `BACKUP_INVALID` | Bad `backup` declaration, or packaged backup files differ from what build writes |
|
|
67
|
+
| `BACKUP_COMPOSE_UNSUPPORTED` | `backup` on a Compose App |
|
|
68
|
+
| `BACKUP_HOOK_MISSING` | `backup.hook` is not an executable in the package |
|
|
69
|
+
| `SDK_APP_BACKUP_UNSUPPORTED` | The SDK has no backup control; a default build skips backup with this warning, an explicit `backup` fails |
|
|
70
|
+
| `SDK_APP_BACKUP_BUILD_FAILED` | The SDK backup control did not build; when Go is not installed (a prebuilt backend), a default build skips backup with this warning, an explicit `backup` fails |
|