@synopackageland/cli 0.7.0 → 0.7.1

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +234 -1
  2. package/dist/add.d.ts +4 -1
  3. package/dist/add.js +14 -9
  4. package/dist/build-spk/dsm-platforms.json +4 -1
  5. package/dist/build-spk/gateway.d.ts +1 -1
  6. package/dist/build-spk/gateway.js +8 -4
  7. package/dist/build-spk/go-toolchain.d.ts +20 -0
  8. package/dist/build-spk/go-toolchain.js +44 -0
  9. package/dist/cli.js +13 -7
  10. package/dist/deploy/native-install-values.d.ts +17 -0
  11. package/dist/deploy/native-install-values.js +32 -0
  12. package/dist/deploy/plan.d.ts +6 -2
  13. package/dist/deploy/plan.js +57 -6
  14. package/dist/error-registry.js +12 -6
  15. package/dist/host/dsm-client.js +53 -17
  16. package/dist/host/dsm-http.js +24 -2
  17. package/dist/host/types.d.ts +1 -0
  18. package/dist/info.js +28 -9
  19. package/dist/init.js +1 -1
  20. package/dist/runtime/dsm-version.d.ts +6 -0
  21. package/dist/runtime/dsm-version.js +8 -0
  22. package/dist/runtime/executor-build.d.ts +1 -1
  23. package/dist/runtime/executor-build.js +8 -5
  24. package/dist/standalone/daemon.d.ts +2 -1
  25. package/dist/standalone/daemon.js +10 -6
  26. package/dist/standalone/dsm-window.js +6 -34
  27. package/dist/standalone/inspect-spk.js +5 -1
  28. package/dist/standalone/native-authorization-window.d.ts +1 -1
  29. package/dist/standalone/native-authorization-window.js +7 -1
  30. package/dist/standalone/native-root-window.d.ts +4 -2
  31. package/dist/standalone/native-root-window.js +33 -18
  32. package/dist/standalone/root-window.d.ts +4 -1
  33. package/dist/standalone/root-window.js +143 -33
  34. package/dist/templates/_daemon/go.mod +1 -1
  35. package/dist/templates/_daemon/main.go +6 -3
  36. package/dist/templates/_daemon/main_test.go +21 -1
  37. package/dist/templates/_daemon/testdata/child.go +3 -0
  38. package/dist/templates/_executor/README.md +4 -1
  39. package/dist/templates/_executor/anchor.go +10 -0
  40. package/dist/templates/_executor/anchor_test.go +40 -5
  41. package/dist/templates/_executor/digest_review_test.go +2 -2
  42. package/dist/templates/_executor/dsm.go +40 -3
  43. package/dist/templates/_executor/go.mod +2 -0
  44. package/dist/templates/_executor/main.go +11 -5
  45. package/dist/templates/_executor/managed.go +3 -3
  46. package/dist/templates/_executor/managed_test.go +3 -3
  47. package/dist/templates/_executor/path_test.go +18 -0
  48. package/dist/templates/_executor/pid.go +37 -0
  49. package/dist/templates/_executor/pid_test.go +113 -0
  50. package/dist/templates/_executor/platform_other.go +8 -0
  51. package/dist/templates/_executor/purge_log.go +18 -0
  52. package/dist/templates/_executor/tasks.go +45 -7
  53. package/dist/templates/_executor/tasks_dsm71_test.go +114 -0
  54. package/dist/templates/_executor/tasks_test.go +58 -1
  55. package/dist/templates/_executor/upgrade_watch_test.go +13 -5
  56. package/dist/templates/_gateway/go.mod +2 -0
  57. package/dist/templates/native-go/backend/go.mod +1 -1
  58. package/dist/templates/native-go/backend/internal/filestation/client.go +39 -3
  59. package/dist/templates/native-go/backend/internal/filestation/download_dsm71_test.go +82 -0
  60. package/dist/templates/native-go/backend/internal/identity/README.md +5 -1
  61. package/dist/templates/native-go/backend/internal/identity/dsm.go +24 -5
  62. package/dist/templates/native-go/backend/internal/identity/http.go +1 -1
  63. package/dist/templates/native-go/backend/internal/identity/identity_test.go +48 -1
  64. package/dist/templates/native-go/backend/internal/identity/service.go +1 -1
  65. package/dist/templates/native-go-delegate/frontend/test/delegate.test.ts +15 -0
  66. package/dist/templates/native-go-root-ops/backend/internal/app/executor.go +30 -2
  67. package/dist/templates/native-go-root-ops/backend/internal/app/executor_test.go +64 -0
  68. package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +3 -2
  69. package/dist/templates/native-go-vue/frontend/src/delegate/executor-settings.test.ts +23 -0
  70. package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +5 -1
  71. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +1 -0
  72. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +1 -0
  73. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +1 -0
  74. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +1 -0
  75. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +1 -0
  76. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +1 -0
  77. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +1 -0
  78. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +1 -0
  79. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +1 -0
  80. package/package.json +1 -1
  81. package/templates/_daemon/go.mod +1 -1
  82. package/templates/_daemon/main.go +6 -3
  83. package/templates/_daemon/main_test.go +21 -1
  84. package/templates/_daemon/testdata/child.go +3 -0
  85. package/templates/_executor/README.md +4 -1
  86. package/templates/_executor/anchor.go +10 -0
  87. package/templates/_executor/anchor_test.go +40 -5
  88. package/templates/_executor/digest_review_test.go +2 -2
  89. package/templates/_executor/dsm.go +40 -3
  90. package/templates/_executor/go.mod +2 -0
  91. package/templates/_executor/main.go +11 -5
  92. package/templates/_executor/managed.go +3 -3
  93. package/templates/_executor/managed_test.go +3 -3
  94. package/templates/_executor/path_test.go +18 -0
  95. package/templates/_executor/pid.go +37 -0
  96. package/templates/_executor/pid_test.go +113 -0
  97. package/templates/_executor/platform_other.go +8 -0
  98. package/templates/_executor/purge_log.go +18 -0
  99. package/templates/_executor/tasks.go +45 -7
  100. package/templates/_executor/tasks_dsm71_test.go +114 -0
  101. package/templates/_executor/tasks_test.go +58 -1
  102. package/templates/_executor/upgrade_watch_test.go +13 -5
  103. package/templates/_gateway/go.mod +2 -0
  104. package/templates/native-go/backend/go.mod +1 -1
  105. package/templates/native-go/backend/internal/filestation/client.go +39 -3
  106. package/templates/native-go/backend/internal/filestation/download_dsm71_test.go +82 -0
  107. package/templates/native-go/backend/internal/identity/README.md +5 -1
  108. package/templates/native-go/backend/internal/identity/dsm.go +24 -5
  109. package/templates/native-go/backend/internal/identity/http.go +1 -1
  110. package/templates/native-go/backend/internal/identity/identity_test.go +48 -1
  111. package/templates/native-go/backend/internal/identity/service.go +1 -1
  112. package/templates/native-go-delegate/frontend/test/delegate.test.ts +15 -0
  113. package/templates/native-go-root-ops/backend/internal/app/executor.go +30 -2
  114. package/templates/native-go-root-ops/backend/internal/app/executor_test.go +64 -0
  115. package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +3 -2
  116. package/templates/native-go-vue/frontend/src/delegate/executor-settings.test.ts +23 -0
  117. package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +5 -1
  118. package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +1 -0
  119. package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +1 -0
  120. package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +1 -0
  121. package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +1 -0
  122. package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +1 -0
  123. package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +1 -0
  124. package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +1 -0
  125. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +1 -0
  126. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +1 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,239 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.7.1 (2026-10-10)
4
+
5
+ Rebuild packages to pick up template, hook and executor changes. The executor
6
+ digest changed. A package built with CLI 0.7.0 needs one administrator approval
7
+ after upgrading to a 0.7.1 build: the 0.7.0 executor opened the package
8
+ `target` link without following it, so it cannot install the anchored executor
9
+ itself. Executor-only upgrades between 0.7.1 or later builds need no approval.
10
+
11
+ ### Executor upgrade and daemon timeouts
12
+
13
+ - During an INFO-anchored executor-only upgrade the executor log said
14
+ `executor paused: package pins require administrator approval` about two
15
+ seconds before the new executor was ready. That line now appears only when
16
+ the pins differ; an anchored handover logs only `executor upgrade:
17
+ INFO-anchored executor installed under the approved policy`. The executor
18
+ digest changes.
19
+
20
+ - `executor_status_mismatch` in the app window and the app's settings names
21
+ its cause: `the approved executor is not answering (<reason>)`, `the running
22
+ executor reports other digests than this package pins`, or `the executor
23
+ status request failed` with the request's own code, instead of `scheduler
24
+ readback or operation failed` for all three. The Data and removal
25
+ troubleshooting table explains each.
26
+
27
+ - After approval or control start the window asked `executor.status` every
28
+ 500 ms for up to 10 s. The wait now doubles from 500 ms to 2 s within the
29
+ same 10 s budget: at most 7 requests instead of 20.
30
+
31
+ - For live upgrade tests, the repository's `scripts/test/executor-bump.py`
32
+ makes an executor-only build+1 SPK and now rewrites the window script's
33
+ embedded pins with the INFO anchor and `executor/setup.json`. The earlier
34
+ out-of-tree copy left the window pins behind, so the panel reported
35
+ `executor_status_mismatch` after every such upgrade while CGI and CLI
36
+ status were ready.
37
+
38
+ - On kernel 3.10 (DSM 7.1, for example DS416 armv7) an executor-only
39
+ upgrade, and approving again while the executor runs, no longer fail
40
+ with "pid executable mismatch". That kernel names a running executor
41
+ replaced by a rename after the rename source (`runtime/.executor
42
+ (deleted)`), and root control accepted only `runtime/executor`. Root
43
+ control now identifies the monitor by inode: the current
44
+ `runtime/executor`, or an unlinked root-owned inode on the runtime's
45
+ device whose last name was `executor` or `.executor*` in the runtime.
46
+ The fix runs in the new executor, which the installed one starts: an
47
+ upgrade from a package built with the target-link fix below needs no
48
+ approval; from 0.7.0 it still needs the one approval described there.
49
+ The executor digest changes.
50
+
51
+ - When the approved tasks are ready but the executor does not answer
52
+ (`executor_status_mismatch`), the app window offers Approve background
53
+ access again. With the tasks approved, it restarts the executor without
54
+ a password.
55
+
56
+ - `executor.status` (root-ops) names approval, not "Start the package", as
57
+ the next step when the package's last lifecycle request was start.
58
+ Regenerate or copy `backend/internal/app/executor.go`.
59
+
60
+ - An executor-only upgrade (same policy, new executor matching the INFO
61
+ `synopkgland_executor_sha256` anchor) no longer fails with "executor
62
+ upgrade refused: INFO-anchored executor could not be installed". The
63
+ installer opened `/var/packages/<pkg>/target/executor` without following
64
+ the DSM `target` link, which the executor refuses, so every such upgrade
65
+ stayed paused. The fix runs in the executor that is already installed:
66
+ an upgrade from a package built with an earlier SDK still needs one
67
+ administrator approval. The executor digest changes.
68
+
69
+ - `executor.status` (root-ops and delegate projects) no longer answers
70
+ `internal` while the executor is paused (package stopped, or pins waiting
71
+ for approval). It answers `{ready: false, state: "paused", reason:
72
+ "executor_not_listening", next}`, and `synopkgland status` shows the
73
+ reason and next step. Regenerate or copy
74
+ `backend/internal/app/executor.go` into existing projects.
75
+
76
+ - `daemon.timeouts` defaults are now `{start: 30, stop: 30, status: 5,
77
+ uninstall: 60}` (were `start: 5, stop: 10, status: 2`). On vt-nas under
78
+ IO load a boot start took more than 5 s and a Connect revoke more than
79
+ 9 s. The new `timeouts.uninstall` (2–120) bounds `uninstall_args`
80
+ separately; it used to get `stop - 1`, so an app had to raise the SIGKILL
81
+ wait to give its revoke time. DSM's package unit allows 3600 s for start
82
+ and stop. `inspect` still accepts SPKs built before `timeouts.uninstall`.
83
+ The manifest reference explains how to pick values from a loaded NAS.
84
+
85
+ ### Approval, identity and uninstall fixes
86
+
87
+ - `identity: standalone`: "Continue with DSM" and every live revalidation
88
+ failed with `session_required` after a DSM web login, because DSM web
89
+ sessions enable SynoToken and DSM answers the `id` cookie alone with 119.
90
+ The backend now forwards the request's single `X-SYNO-TOKEN` header, which
91
+ the DSM window's `ctx.call` already sends, when it checks the DSM session.
92
+ Sessions without SynoToken still verify with the cookie alone. The token is
93
+ never put in a URL, logged or stored. This also unblocks root-ops approval
94
+ in an `identity: standalone` package, which stopped at `executor.control`.
95
+ Existing projects need the updated `internal/identity` files.
96
+ - When approval created and read back both tasks but the executor then did
97
+ not start or report healthy, the window left the tasks behind, and with no
98
+ running executor there was no task inventory for uninstall to delete them.
99
+ The window now withdraws the tasks that approval created; tasks from an
100
+ earlier approval stay.
101
+ - After a wrong password or a withdrawn approval, the window panel showed
102
+ only "Check status". It and the app's settings now keep the approve action.
103
+ - Uninstall no longer logs `task cleanup unconfirmed` for every successful
104
+ delete: EventScheduler `delete` v1 and TaskScheduler `delete` v2 answer
105
+ `{"success":true}` without data, which now counts as confirmed. The task
106
+ definitions are unchanged, so no new approval is needed; the executor
107
+ digest changes.
108
+ - The native root window (`@synopackageland/cli/authorization-sdk`) gave up
109
+ after about 30 s with `root_timeout`, while a loaded NAS ran the bootstrap
110
+ task for up to 4.5 minutes. It now keeps waiting while the task is queued or
111
+ running, up to 10 minutes, reports `{op, state, elapsedMs}` through
112
+ `options.progress`, and the authorization flow accepts the backend's ready
113
+ grant after `root_timeout` or `root_task_failed`.
114
+ - A DSM certificate that Node does not trust failed with only
115
+ `fetch failed`. The CLI now reports `DSM_TLS_UNTRUSTED` with the origin and
116
+ certificate error and points to `NODE_EXTRA_CA_CERTS`; certificate checks
117
+ stay on.
118
+ - Docs: on DSM 7.1 the purge timing lines are only in the journal
119
+ (`journalctl -t synopkgland-executor`); `init -t native-go` does not include
120
+ `armv7` in `arches`, so add it for an armv7 NAS.
121
+
122
+ ### Deploy and status fixes
123
+
124
+ - `status` no longer reports `blocked` for every `identity: standalone`
125
+ project. It opens a short-lived App session from the administrator's DSM
126
+ session, reads `executor.status`, then logs out. That needs an `https://`
127
+ host; over `http://` the capability stays `blocked` and the reason says so.
128
+ - `deploy` accepts the install wizard values of an `install-selected`
129
+ delegate: `--param delegate_share=<Shared Folder>` or
130
+ `--param delegate_home=true`. A first install without one now fails with
131
+ `DEPLOY_PARAM_REQUIRED` and a usage line before anything is built, instead
132
+ of failing inside DSM. Invalid values fail with `DEPLOY_PARAM_TYPE_INVALID`.
133
+ - `deploy` and `status` work on a native layout (existing native product).
134
+ `deploy` builds the NAS architecture only; an `aarch64` NAS uses a declared
135
+ `armv8`.
136
+ - The deploy note no longer says every upgrade needs approval: an
137
+ executor-only upgrade stays ready; a policy digest change needs approval.
138
+ - Uninstall logs per-phase purge timing with the package name to the system
139
+ log (`synopkgland-executor`): `task cleanup timing` per task and
140
+ `purge timing` for the task and file phases. The executor digest changes.
141
+ - `add root-ops` and `add portal` print a notice on stderr when they raise
142
+ `os_min_ver`.
143
+ - `inspect` on an executor SPK describes the root boot and control tasks
144
+ instead of saying the package needs no root privileges.
145
+
146
+ ### DSM 7.1: root-ops and delegate approval work
147
+
148
+ - DSM 7.1.1 has no `SYNO.Core.TaskScheduler.Root` or
149
+ `SYNO.Core.EventScheduler.Root`, TaskScheduler stops at v3 and
150
+ PasswordConfirm at v1, so approval failed with "Background file access is
151
+ unavailable" and `status`/`deploy` showed `rootOps: unsupported — DSM 103`
152
+ or `DSM 104`. DSM 7.1.1 answers a version above the API's maximum with 104
153
+ (PasswordConfirm v2, TaskScheduler `get` v4) and a method missing at an
154
+ existing version with 103 (TaskScheduler `list` v3). When DSM answers a
155
+ scheduler read or the password check with 103 or 104, the app now uses
156
+ PasswordConfirm v1, TaskScheduler `list` v2/`get` v3 and creates
157
+ the tasks with EventScheduler `create` v1 and TaskScheduler `create` v3.
158
+ The password is still confirmed first and both tasks are still read back
159
+ exactly before anything runs. Other errors (such as 105, 119, 102 or a
160
+ wrong password, 3121) never fall back, and a create is never retried. DSM
161
+ 7.1 does not bind task creation to the password check; see the root-ops
162
+ guide.
163
+ - `status` and `deploy` read the control task the same way.
164
+ - Uninstall on DSM 7.1 now removes the boot and control tasks. The executor
165
+ looked the control task up with TaskScheduler `get` v4, which DSM 7.1.1
166
+ refuses with 104, so it wrote no task inventory and left both tasks behind.
167
+ - If approval stops after it created a task (for example, the boot task was
168
+ created and the control task was not), the app deletes the tasks it created
169
+ in that attempt instead of leaving an enabled root boot task that nothing
170
+ owns. Tasks from an earlier approval stay.
171
+ - A failed approval shows its reason and DSM error code in the app's settings
172
+ instead of only "Background file access is unavailable", and a refused
173
+ password reads as a password failure, not a scheduler failure.
174
+ - After the app's settings approve, the window's status line no longer shows
175
+ "needs-admin-approval: Task is missing" until a reload; the window panel and
176
+ the settings now follow each other's results.
177
+
178
+ ### DSM 7.1: jobs import and file downloads
179
+
180
+ - DSM 7.1.1 sends `SYNO.FileStation.Download` v2 replies without
181
+ `Content-Disposition`, so `bg.jobs.submit` and downloads failed with
182
+ `unavailable`. With no such header, a body of at most 4 KiB that decodes
183
+ as a DSM reply is still refused as that reply; any other body is the file.
184
+
185
+ ### Docs: build your own armv7 Go code with Go 1.26.9
186
+
187
+ - On armv7 DSM 7.1.1 (kernel 3.10), a program built with Go 1.26.0 crashed
188
+ at start (`futexwakeup returned -22`, SIGSEGV) and Go 1.26.9 ran. Other
189
+ 1.26 releases were not tested.
190
+
191
+ ### init: next steps install frontend dependencies
192
+
193
+ - `init -t native-go` with a Vue frontend now lists `(cd frontend && npm ci)`
194
+ before `synopkgland build`; without it the build fails with
195
+ `STANDALONE_UI_BUILD_FAILED`. `build` still does not install dependencies.
196
+
197
+ ### Executor, daemon and gateway build with exactly Go 1.26.9
198
+
199
+ - The CLI builds the executor, lifecycle helper, daemon controller and gateway
200
+ with `GOTOOLCHAIN=go1.26.9`, ignoring your `GOTOOLCHAIN`, `GOFLAGS`,
201
+ `GOEXPERIMENT`, `GOWORK` and `GOAMD64`/`GOARM64`, so every builder produces
202
+ the same executor digest. Go 1.21 or later downloads go1.26.9 when needed;
203
+ offline without it the build fails and names go1.26.9. A binary whose Go
204
+ build info names another release is refused.
205
+ - Template go.mod files now say `toolchain go1.26.9`. The executor digest
206
+ changes; it is anchored in INFO, so an upgrade with an unchanged policy needs
207
+ no new approval.
208
+
209
+ ### INFO arch: four more DSM platforms
210
+
211
+ - x86_64 SPKs now also list `broadwellntb`, `coffeelake` and `skylaked`, and
212
+ aarch64 SPKs list `rtd1619` (DSM Developer Guide Appendix A family members).
213
+ INFO `arch` now matches the platforms the package source maps to each family.
214
+
215
+ ### 繁體中文摘要
216
+
217
+ - 升級後重新建置套件;executor digest 改變。**從 0.7.0 建的套件升級需管理員再核准一次**(0.7.0 的
218
+ executor 開啟套件 `target` symlink 時未跟隨連結,無法自行安裝錨定的 executor);之後 0.7.1 起的
219
+ executor-only 升級不需核准。
220
+ - executor:kernel 3.10(DSM 7.1,如 DS416 armv7)上 executor-only 升級與執行中再核准不再出現
221
+ `pid executable mismatch`;INFO 錨定升級不再誤報 `executor upgrade refused`,也不再先印
222
+ 「paused: 需管理員核准」;`executor.status` 暫停時回 `paused`/`executor_not_listening` 與下一步。
223
+ - 面板:executor 不回應時可再按一次「Approve background access」免密碼重啟;
224
+ `executor_status_mismatch` 會寫出原因(不回應/digest 不同/status 請求失敗);
225
+ 啟動後的 `executor.status` 等待改為 500 ms→2 s 退避,10 s 預算不變。
226
+ - `daemon.timeouts` 預設改為 `{start: 30, stop: 30, status: 5, uninstall: 60}`,新增 `timeouts.uninstall`。
227
+ - `identity: standalone`:DSM 網頁 session(SynoToken)下「Continue with DSM」與 root-ops 核准修正;
228
+ 核准建立 task 後 executor 未啟動時自動撤回本次建立的 task;卸載不再誤記 `task cleanup unconfirmed`。
229
+ - 原生 root window 等待上限改為 10 分鐘並回報進度;`DSM_TLS_UNTRUSTED` 指向 `NODE_EXTRA_CA_CERTS`。
230
+ - `status`/`deploy`:standalone 專案的 status 改用短期 App session;`deploy` 接受
231
+ install-selected delegate 的精靈參數;支援 native layout;卸載記錄 purge timing。
232
+ - DSM 7.1:無 Root API 時改走 PasswordConfirm v1/TaskScheduler v2–v3/EventScheduler create v1,
233
+ 卸載會移除 boot/control task;`SYNO.FileStation.Download` v2 無 `Content-Disposition` 的下載修正。
234
+ - 建置:executor、lifecycle、daemon、gateway 一律以 Go 1.26.9 建置(armv7 kernel 3.10 上 1.26.0 會當機);
235
+ INFO `arch` 新增 `broadwellntb`、`coffeelake`、`skylaked`、`rtd1619`。
236
+ - 測試工具:`scripts/test/executor-bump.py` 收進 repo,會一併改寫視窗腳本內嵌的 executor pin。
4
237
 
5
238
  ## 0.7.0 (2026-10-10)
6
239
 
package/dist/add.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- export declare function addCapabilities(root: string, requested: string[]): {
1
+ type AddResult = {
2
2
  added: string[];
3
3
  unchanged: string[];
4
+ notes?: string[];
4
5
  };
6
+ export declare function addCapabilities(root: string, requested: string[]): AddResult;
7
+ export {};
package/dist/add.js CHANGED
@@ -46,11 +46,17 @@ function safePath(root, file) {
46
46
  }
47
47
  return true;
48
48
  }
49
- /** Raise a lower os_min_ver to the floor a capability needs; a higher one stays. */
50
- function raiseOsMinVer(doc, current, floor) {
49
+ /** Raise a lower os_min_ver to the floor a capability needs; a higher one stays. Returns the notice. */
50
+ function raiseOsMinVer(doc, current, floor, capability) {
51
51
  const osMinVer = doc.get('os_min_ver', true);
52
- if (isScalar(osMinVer) && !isDsmAtLeast(current, floor))
53
- osMinVer.value = floor;
52
+ if (!isScalar(osMinVer) || isDsmAtLeast(current, floor))
53
+ return [];
54
+ osMinVer.value = floor;
55
+ return [`os_min_ver raised from ${current} to ${floor}: ${capability} needs DSM ${floor} or later.`];
56
+ }
57
+ function withNotes(result, notes) {
58
+ const all = [...(result.notes ?? []), ...notes];
59
+ return all.length ? { ...result, notes: all } : result;
54
60
  }
55
61
  /**
56
62
  * `portal: {}` in dsm-package.yaml; the Go and binary starters already carry the UI and backend side.
@@ -67,9 +73,9 @@ function addPortal(root, result) {
67
73
  const manifestPath = join(root, 'dsm-package.yaml');
68
74
  const doc = parseDocument(readFileSync(manifestPath, 'utf8'));
69
75
  doc.set('portal', doc.createNode({}, { flow: true }));
70
- raiseOsMinVer(doc, loaded.manifest.os_min_ver, PORTAL_MIN_DSM);
76
+ const notes = raiseOsMinVer(doc, loaded.manifest.os_min_ver, PORTAL_MIN_DSM, 'portal');
71
77
  writeFileSync(manifestPath, String(doc));
72
- return { ...result, added: [...result.added, 'portal'] };
78
+ return withNotes({ ...result, added: [...result.added, 'portal'] }, notes);
73
79
  }
74
80
  export function addCapabilities(root, requested) {
75
81
  const wanted = [...new Set(requested.flatMap(value => value.split(',')).map(value => value.trim()))];
@@ -129,8 +135,7 @@ export function addCapabilities(root, requested) {
129
135
  for (const [key, value] of Object.entries(nativeCapabilitySettings(added)))
130
136
  doc.setIn(['capabilities', key], value);
131
137
  // rootOps needs DSM 7.1-42661 or later.
132
- if (added.includes('root-ops'))
133
- raiseOsMinVer(doc, m.os_min_ver, DEFAULT_MIN_DSM);
138
+ const notes = added.includes('root-ops') ? raiseOsMinVer(doc, m.os_min_ver, DEFAULT_MIN_DSM, 'root-ops') : [];
134
139
  for (const file of destinations) {
135
140
  const path = join(root, file);
136
141
  mkdirSync(dirname(path), { recursive: true });
@@ -139,7 +144,7 @@ export function addCapabilities(root, requested) {
139
144
  for (const file of deletions)
140
145
  rmSync(join(root, file), { force: true });
141
146
  writeFileSync(manifestPath, String(doc));
142
- return { added, unchanged };
147
+ return withNotes({ added, unchanged }, notes);
143
148
  }
144
149
  finally {
145
150
  rmSync(work, { recursive: true, force: true });
@@ -7,9 +7,11 @@
7
7
  "broadwell",
8
8
  "broadwellnk",
9
9
  "broadwellnkv2",
10
+ "broadwellntb",
10
11
  "broadwellntbap",
11
12
  "bromolow",
12
13
  "cedarview",
14
+ "coffeelake",
13
15
  "denverton",
14
16
  "epyc7002",
15
17
  "epyc7003",
@@ -23,13 +25,14 @@
23
25
  "purley",
24
26
  "r1000",
25
27
  "r1000nk",
28
+ "skylaked",
26
29
  "v1000",
27
30
  "v1000nk"
28
31
  ],
29
32
  "platformOnly": []
30
33
  },
31
34
  "aarch64": {
32
- "members": ["armada37xx", "rtd1296", "rtd1619b"],
35
+ "members": ["armada37xx", "rtd1296", "rtd1619", "rtd1619b"],
33
36
  "platformOnly": []
34
37
  },
35
38
  "armv7": {
@@ -1,6 +1,6 @@
1
1
  import { type CommandRunner } from "../standalone/build.js";
2
2
  import type { AppContract } from "../types.js";
3
- import { type NativeArchFamily } from "./dsm-arch.js";
3
+ import type { NativeArchFamily } from "./dsm-arch.js";
4
4
  export { gatewayEntryPath } from "../contract/web-launch.js";
5
5
  /** Where the gateway CGI sits in the package payload; DSM serves `ui/` under /webman/3rdparty/<package>/. */
6
6
  export declare const GATEWAY_CGI_PAYLOAD = "ui/cgi/gateway.cgi";
@@ -4,7 +4,7 @@ import { checkCgiBinary } from "../standalone/binary.js";
4
4
  import { runCommand } from "../standalone/build.js";
5
5
  import { resolvedWebAccess } from "../contract/web-launch.js";
6
6
  import { templatesRoot } from "../template-paths.js";
7
- import { goBuildEnv } from "./dsm-arch.js";
7
+ import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem } from "./go-toolchain.js";
8
8
  export { gatewayEntryPath } from "../contract/web-launch.js";
9
9
  /** Where the gateway CGI sits in the package payload; DSM serves `ui/` under /webman/3rdparty/<package>/. */
10
10
  export const GATEWAY_CGI_PAYLOAD = "ui/cgi/gateway.cgi";
@@ -29,18 +29,22 @@ export function buildGatewayBinary(input) {
29
29
  const ldflags = ["-s", "-w", "-buildid=", `-X main.packageID=${input.packageId}`, `-X 'main.configJSON=${JSON.stringify(input.config)}'`].join(" ");
30
30
  const result = (input.run ?? runCommand)("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, "."], {
31
31
  cwd: join(templatesRoot(), "_gateway"),
32
- env: { ...process.env, ...goBuildEnv(input.arch) },
32
+ env: { ...process.env, ...pinnedGoBuildEnv(input.arch) },
33
33
  });
34
34
  if (result.missing) {
35
- return { error: "GATEWAY_GO_MISSING", detail: "找不到 go 指令。DSM 驗證閘道需要 Go 1.24 以上版本(https://go.dev/dl/)才能建置。" };
35
+ return { error: "GATEWAY_GO_MISSING", detail: `找不到 go 指令。DSM 驗證閘道需要 Go 1.21 以上版本(https://go.dev/dl/)才能建置。${PINNED_GO_HINT}` };
36
36
  }
37
37
  if (result.status !== 0) {
38
- return { error: "GATEWAY_BUILD_FAILED", detail: `go build(${input.arch})失敗。\n${result.stderr}` };
38
+ return { error: "GATEWAY_BUILD_FAILED", detail: `go build(${input.arch})失敗。${PINNED_GO_HINT}\n${result.stderr}` };
39
39
  }
40
40
  const problem = checkCgiBinary(input.work, out, input.arch);
41
41
  if (problem) {
42
42
  return { error: "GATEWAY_BUILD_FAILED", detail: problem.message };
43
43
  }
44
+ const toolchainProblem = pinnedGoProblem(out);
45
+ if (toolchainProblem) {
46
+ return { error: "GATEWAY_BUILD_FAILED", detail: toolchainProblem };
47
+ }
44
48
  return { binary: out };
45
49
  }
46
50
  /** Puts the CGI (mode 0755) and its readable configuration into the package payload. */
@@ -0,0 +1,20 @@
1
+ import { type NativeArchFamily } from "./dsm-arch.js";
2
+ /**
3
+ * The Go release that compiles every binary the CLI ships from its own
4
+ * templates, so their digests do not depend on the builder's Go. Each
5
+ * template go.mod names it in its `toolchain` line.
6
+ */
7
+ export declare const PINNED_GO_TOOLCHAIN = "go1.26.9";
8
+ /**
9
+ * The `go build` environment for a binary the CLI ships from its own templates
10
+ * (executor, lifecycle helper, daemon controller, gateway). GOTOOLCHAIN names
11
+ * the exact release, overriding `local` or `auto`: Go 1.21+ runs it from PATH
12
+ * or the module cache, or downloads it, and fails when it cannot.
13
+ */
14
+ export declare function pinnedGoBuildEnv(family: NativeArchFamily): Record<string, string>;
15
+ /** The Go release recorded in a binary's build info (Go 1.18+ layout), or undefined. */
16
+ export declare function goBinaryVersion(file: string): string | undefined;
17
+ /** Why a CLI-owned binary cannot ship: it was not built by the pinned Go release. */
18
+ export declare function pinnedGoProblem(file: string): string | undefined;
19
+ /** Appended to a failed CLI-owned `go build`: what the build needs. */
20
+ export declare const PINNED_GO_HINT = "These binaries need go1.26.9: Go 1.21 or later downloads it automatically; offline, install go1.26.9 on PATH or allow GOPROXY to serve golang.org/toolchain.";
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { goBuildEnv } from "./dsm-arch.js";
3
+ /**
4
+ * The Go release that compiles every binary the CLI ships from its own
5
+ * templates, so their digests do not depend on the builder's Go. Each
6
+ * template go.mod names it in its `toolchain` line.
7
+ */
8
+ export const PINNED_GO_TOOLCHAIN = "go1.26.9";
9
+ /**
10
+ * The `go build` environment for a binary the CLI ships from its own templates
11
+ * (executor, lifecycle helper, daemon controller, gateway). GOTOOLCHAIN names
12
+ * the exact release, overriding `local` or `auto`: Go 1.21+ runs it from PATH
13
+ * or the module cache, or downloads it, and fails when it cannot.
14
+ */
15
+ export function pinnedGoBuildEnv(family) {
16
+ return { ...goBuildEnv(family), GOTOOLCHAIN: PINNED_GO_TOOLCHAIN, GOFLAGS: "", GOEXPERIMENT: "", GOWORK: "off", GOAMD64: "v1", GOARM64: "v8.0" };
17
+ }
18
+ const BUILD_INFO_MAGIC = Buffer.from("\xff Go buildinf:", "latin1");
19
+ const BUILD_INFO_INLINE_STRINGS = 0x2;
20
+ /** The Go release recorded in a binary's build info (Go 1.18+ layout), or undefined. */
21
+ export function goBinaryVersion(file) {
22
+ const bytes = readFileSync(file);
23
+ const start = bytes.indexOf(BUILD_INFO_MAGIC);
24
+ if (start < 0 || !(bytes[start + 15] & BUILD_INFO_INLINE_STRINGS))
25
+ return undefined;
26
+ let length = 0, shift = 0, at = start + 32;
27
+ for (;;) {
28
+ const byte = bytes[at++];
29
+ if (byte === undefined || shift > 28)
30
+ return undefined;
31
+ length |= (byte & 0x7f) << shift;
32
+ shift += 7;
33
+ if (byte < 0x80)
34
+ break;
35
+ }
36
+ return bytes.subarray(at, at + length).toString("latin1") || undefined;
37
+ }
38
+ /** Why a CLI-owned binary cannot ship: it was not built by the pinned Go release. */
39
+ export function pinnedGoProblem(file) {
40
+ const version = goBinaryVersion(file);
41
+ return version === PINNED_GO_TOOLCHAIN ? undefined : `${file} was built by ${version ?? "an unknown Go release"}, not ${PINNED_GO_TOOLCHAIN}.`;
42
+ }
43
+ /** Appended to a failed CLI-owned `go build`: what the build needs. */
44
+ export const PINNED_GO_HINT = `These binaries need ${PINNED_GO_TOOLCHAIN}: Go 1.21 or later downloads it automatically; offline, install ${PINNED_GO_TOOLCHAIN} on PATH or allow GOPROXY to serve golang.org/toolchain.`;
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Command, CommanderError } from "commander";
2
- import { readFileSync, renameSync } from "node:fs";
2
+ import { existsSync, readFileSync, renameSync } from "node:fs";
3
3
  import { resolve, dirname, join } from "node:path";
4
4
  import { findProjectRoot } from "./discovery.js";
5
5
  import { loadContract } from "./contract/load.js";
@@ -62,7 +62,11 @@ function formatSpkInspection(spk, report) {
62
62
  "payload:",
63
63
  ...(report.uninstallCleanup ?? []).map(item => `Uninstall cleanup (if needed):${item.target} — ${item.condition} ${item.action}`),
64
64
  ...report.payload.map((e) => ` ${e.mode} ${e.path}`),
65
- report.findings.length ? "問題:" : "檢查:沒有 root 權限需求或異常。",
65
+ report.findings.length
66
+ ? "問題:"
67
+ : report.executor
68
+ ? "檢查:沒有異常。管理員核准後會建立 root executor 排程(boot 與 control task),以 root 執行此 SPK 固定的 executor 與 policy。"
69
+ : "檢查:沒有 root 權限需求或異常。",
66
70
  ...report.findings.map((f) => ` - ${f}`),
67
71
  ].join("\n");
68
72
  }
@@ -234,13 +238,15 @@ export async function runCli(argv) {
234
238
  });
235
239
  stdout(`已建立 ${options.template} 專案於 ${targetDir}`);
236
240
  if (options.template === "native-go")
237
- stdout(`下一步:\n cd ${dir}\n (cd backend && go test ./...)\n synopkgland build\n說明見 README.md。`);
241
+ stdout(`下一步:\n cd ${dir}\n (cd backend && go test ./...)\n${existsSync(join(targetDir, "frontend", "package.json")) ? " (cd frontend && npm ci)\n" : ""} synopkgland build\n說明見 README.md。`);
238
242
  });
239
243
  program
240
244
  .command("add <caps...>")
241
245
  .description(`加入能力(${CAPABILITY_NAMES.join("、")})或 portal 與 SDK-owned overlay;不覆寫既有應用檔案`)
242
246
  .action((caps, _options, command) => {
243
247
  const result = addCapabilities(rootOf(command), caps);
248
+ for (const note of result.notes ?? [])
249
+ console.error(`[注意] ${note}`);
244
250
  stdout(`已新增:${result.added.join(", ") || "無"};已存在:${result.unchanged.join(", ") || "無"}`);
245
251
  });
246
252
  program
@@ -281,7 +287,7 @@ export async function runCli(argv) {
281
287
  .option("--dev", "Development Package Identity")
282
288
  .option("--share <slot=name>", "Compose 共用資料夾", (value, previous) => collectKv(value, previous, "--share"))
283
289
  .option("--run-as <slot=name>", "Compose DSM 使用者", (value, previous) => collectKv(value, previous, "--run-as"))
284
- .option("--param <NAME=value>", "Compose 安裝參數", (value, previous) => collectKv(value, previous, "--param"))
290
+ .option("--param <NAME=value>", "安裝參數(Compose)或安裝精靈值(native-go/binary)", (value, previous) => collectKv(value, previous, "--param"))
285
291
  .option("--dry-run", "只輸出本機部署計畫,不連接 NAS")
286
292
  .action(async (options, command) => {
287
293
  if (options.arch !== undefined &&
@@ -292,15 +298,15 @@ export async function runCli(argv) {
292
298
  dev: options.dev,
293
299
  });
294
300
  if (plan.kind !== "container" &&
295
- (options.share || options.runAs || options.param))
296
- throw failure("CLI_OPTION_INVALID", "native-go/binary 不接受 Compose 安裝參數。");
301
+ (options.share || options.runAs))
302
+ throw failure("CLI_OPTION_INVALID", "native-go/binary 不接受 --share/--run-as;安裝精靈值請用 --param。");
297
303
  if (options.dryRun) {
298
304
  emit("deploy", plan, JSON.stringify(plan, null, 2));
299
305
  return;
300
306
  }
301
307
  if (plan.kind !== "container") {
302
308
  const host = hostOf(options.host);
303
- const output = await deployStandalone(plan, host);
309
+ const output = await deployStandalone(plan, host, { params: options.param });
304
310
  const status = await standaloneInfo({ root: plan.root, host });
305
311
  emit("deploy", { ...plan, output, status }, `已部署 ${plan.packageId}: ${output}\n${formatProjectInfo(status)}${plan.notes.length ? "\n" + plan.notes.join("\n") : ""}`);
306
312
  return;
@@ -0,0 +1,17 @@
1
+ import type { StandaloneManifest } from "../standalone/manifest.js";
2
+ export type NativeInstallValuesResult = {
3
+ ok: true;
4
+ extraValues: Record<string, string>;
5
+ } | {
6
+ ok: false;
7
+ code: string;
8
+ message: string;
9
+ };
10
+ /**
11
+ * Install wizard values for a native-go/binary deploy. Only an install-selected
12
+ * delegate asks the wizard for anything; preinst refuses an empty selection on
13
+ * the first install, so refuse it here before building or uploading.
14
+ */
15
+ export declare function resolveNativeInstallValues(manifest: StandaloneManifest, provided: Record<string, string>, state: {
16
+ installed: boolean;
17
+ }): NativeInstallValuesResult;
@@ -0,0 +1,32 @@
1
+ const USAGE = "synopkgland deploy --param delegate_share=<shared folder>,或 --param delegate_home=true";
2
+ /** Mirrors the preinst draft check in build-spk/delegate-scope.ts. */
3
+ function validShareName(name) {
4
+ return (name !== "" && name !== "." && !name.includes("..") &&
5
+ !/[/\\:"%\x00-\x1f\x7f]/.test(name) &&
6
+ !/^homes?$/i.test(name) && name.trim() === name && name.length <= 256);
7
+ }
8
+ /**
9
+ * Install wizard values for a native-go/binary deploy. Only an install-selected
10
+ * delegate asks the wizard for anything; preinst refuses an empty selection on
11
+ * the first install, so refuse it here before building or uploading.
12
+ */
13
+ export function resolveNativeInstallValues(manifest, provided, state) {
14
+ const fail = (code, message) => ({ ok: false, code, message });
15
+ const selected = manifest.capabilities.delegate?.scope === "install-selected";
16
+ const names = Object.keys(provided);
17
+ if (!selected && names.length)
18
+ return fail("DEPLOY_PARAM_UNDECLARED", `${manifest.package} has no install wizard values; remove --param ${names[0]}.`);
19
+ const unknown = names.find((name) => name !== "delegate_share" && name !== "delegate_home");
20
+ if (unknown)
21
+ return fail("DEPLOY_PARAM_UNDECLARED", `--param ${unknown} is not an install wizard value. Use: ${USAGE}.`);
22
+ const { delegate_share: share, delegate_home: home } = provided;
23
+ if (home !== undefined && home !== "true" && home !== "false")
24
+ return fail("DEPLOY_PARAM_TYPE_INVALID", "--param delegate_home must be true or false.");
25
+ if (share !== undefined && !validShareName(share))
26
+ return fail("DEPLOY_PARAM_TYPE_INVALID", `--param delegate_share must be one Shared Folder name, not a path, home or homes.`);
27
+ if (share !== undefined && home === "true")
28
+ return fail("DEPLOY_PARAM_TYPE_INVALID", "Choose either --param delegate_share or --param delegate_home=true, not both.");
29
+ if (selected && !state.installed && share === undefined && home !== "true")
30
+ return fail("DEPLOY_PARAM_REQUIRED", `The first install of ${manifest.package} must select the delegate scope: ${USAGE}.`);
31
+ return { ok: true, extraValues: { ...provided } };
32
+ }
@@ -1,8 +1,9 @@
1
+ import { type CommandRunner } from "../standalone/build.js";
1
2
  import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
2
3
  import type { NasHost } from "../host/types.js";
3
4
  export interface DeploymentPlan {
4
5
  root: string;
5
- kind: "native-go" | "binary" | "container";
6
+ kind: "native-go" | "binary" | "native-layout" | "container";
6
7
  packageId: string;
7
8
  version: string;
8
9
  arch?: NativeArchFamily;
@@ -15,4 +16,7 @@ export declare function planDeployment(root: string, options?: {
15
16
  arch?: NativeArchFamily;
16
17
  dev?: boolean;
17
18
  }): DeploymentPlan;
18
- export declare function deployStandalone(plan: DeploymentPlan, host: NasHost): Promise<string>;
19
+ export declare function deployStandalone(plan: DeploymentPlan, host: NasHost, options?: {
20
+ params?: Record<string, string>;
21
+ run?: CommandRunner;
22
+ }): Promise<string>;