@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.
@@ -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 { renderSharedFolderUpgradeWizardFiles, sharedFolderInstallSteps, } from "./shared-folder.js";
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.35.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.32.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.35.0 installs templates through `~0.32.0`. A templates patch needs only a
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.33.0 and CLI 0.35.0 for App entry enforcement. Declare every App method
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 |