@synopackageland/cli 0.14.1 → 0.15.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 +10 -0
- package/README.md +26 -16
- package/dist/build-spk/generator.d.ts +3 -2
- package/dist/build-spk/generator.js +6 -846
- package/dist/build-spk/resource-worker.d.ts +2 -14
- package/dist/build-spk/resource-worker.js +3 -166
- package/dist/cli.js +208 -56
- package/dist/compose/analyze.d.ts +3 -61
- package/dist/compose/analyze.js +35 -776
- package/dist/compose/env.d.ts +5 -3
- package/dist/compose/env.js +17 -25
- package/dist/compose/runner.js +23 -6
- package/dist/contract/web-launch.d.ts +1 -12
- package/dist/contract/web-launch.js +10 -74
- package/dist/ctl/cli.d.ts +36 -0
- package/dist/ctl/cli.js +218 -0
- package/dist/ctl/descriptor.d.ts +81 -0
- package/dist/ctl/descriptor.js +93 -0
- package/dist/ctl/dsm-auth.d.ts +10 -0
- package/dist/ctl/dsm-auth.js +30 -0
- package/dist/ctl/errors.d.ts +17 -0
- package/dist/ctl/errors.js +32 -0
- package/dist/ctl/flows.d.ts +11 -0
- package/dist/ctl/flows.js +52 -0
- package/dist/ctl/input.d.ts +20 -0
- package/dist/ctl/input.js +128 -0
- package/dist/ctl/json.d.ts +2 -0
- package/dist/ctl/json.js +38 -0
- package/dist/ctl/redaction.d.ts +15 -0
- package/dist/ctl/redaction.js +198 -0
- package/dist/ctl/service.d.ts +38 -0
- package/dist/ctl/service.js +156 -0
- package/dist/ctl/store.d.ts +51 -0
- package/dist/ctl/store.js +93 -0
- package/dist/ctl/transfers.d.ts +14 -0
- package/dist/ctl/transfers.js +74 -0
- package/dist/ctl/transport.d.ts +11 -0
- package/dist/ctl/transport.js +79 -0
- package/dist/deploy/deploy.d.ts +4 -3
- package/dist/deploy/deploy.js +5 -210
- package/dist/deploy/plan.js +26 -7
- package/dist/dev.js +5 -4
- package/dist/error-registry.js +1020 -834
- package/dist/info.js +120 -28
- package/dist/init.js +188 -64
- package/dist/inspect.js +21 -6
- package/dist/runtime/executor-digests.json +1 -12
- package/dist/skill.js +51 -9
- package/dist/standalone/api-description-inspect.d.ts +4 -0
- package/dist/standalone/api-description-inspect.js +12 -0
- package/dist/standalone/api-description.d.ts +7 -0
- package/dist/standalone/api-description.js +73 -0
- package/dist/standalone/build.d.ts +3 -0
- package/dist/standalone/build.js +140 -33
- package/dist/standalone/compose-errors.d.ts +3 -0
- package/dist/standalone/compose-errors.js +243 -0
- package/dist/standalone/compose.d.ts +97 -10
- package/dist/standalone/compose.js +306 -75
- package/dist/standalone/daemon.js +111 -29
- package/dist/standalone/dsm-window.js +39 -8
- package/dist/standalone/identity-gateway.d.ts +9 -2
- package/dist/standalone/identity-gateway.js +282 -66
- package/dist/standalone/inspect-spk.d.ts +12 -0
- package/dist/standalone/inspect-spk.js +666 -136
- package/dist/standalone/lifecycle.js +124 -42
- package/dist/standalone/manifest.d.ts +8 -1
- package/dist/standalone/manifest.js +303 -86
- package/dist/standalone/resource.js +6 -2
- package/dist/standalone/spk.d.ts +4 -1
- package/dist/standalone/spk.js +138 -39
- package/dist/template-paths.d.ts +0 -2
- package/dist/template-paths.js +0 -5
- package/dist/test-command.d.ts +1 -1
- package/dist/test-command.js +1 -1
- package/dist/validate.d.ts +3 -3
- package/dist/validate.js +32 -140
- package/package.json +5 -3
- package/schema/synopkgland-api-v1.schema.json +423 -0
- package/skills/SKILL.md +3 -1
- package/skills/reference/compose.md +349 -0
- package/skills/reference/create.md +8 -17
- package/skills/reference/ctl.md +41 -0
- package/skills/reference/deploy.md +1 -1
- package/skills/reference/files.md +4 -0
- package/skills/reference/identity-gateway.md +8 -144
- package/skills/reference/wrap.md +10 -39
- package/assets/go/daemon/fixture_linux_test.go +0 -15
- package/assets/go/daemon/fixture_other_test.go +0 -7
- package/assets/go/daemon/go.mod +0 -5
- package/assets/go/daemon/main.go +0 -636
- package/assets/go/daemon/main_test.go +0 -315
- package/assets/go/daemon/network.go +0 -107
- package/assets/go/daemon/network_test.go +0 -86
- package/assets/go/daemon/process_linux.go +0 -55
- package/assets/go/daemon/process_linux_test.go +0 -161
- package/assets/go/daemon/process_other.go +0 -14
- package/assets/go/daemon/testdata/child.go +0 -44
- package/assets/go/gateway/auth.go +0 -101
- package/assets/go/gateway/auth_test.go +0 -216
- package/assets/go/gateway/cgi.go +0 -133
- package/assets/go/gateway/cgi_test.go +0 -156
- package/assets/go/gateway/csrf.go +0 -73
- package/assets/go/gateway/csrf_test.go +0 -155
- package/assets/go/gateway/directory.go +0 -108
- package/assets/go/gateway/directory_test.go +0 -202
- package/assets/go/gateway/gateway.go +0 -412
- package/assets/go/gateway/gateway_test.go +0 -676
- package/assets/go/gateway/go.mod +0 -5
- package/assets/go/gateway/groups.go +0 -66
- package/assets/go/gateway/main.go +0 -45
- package/dist/build-spk/gateway.d.ts +0 -31
- package/dist/build-spk/gateway.js +0 -60
- package/dist/compose/privilege.d.ts +0 -14
- package/dist/compose/privilege.js +0 -102
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Upstream Compose packages
|
|
2
|
+
|
|
3
|
+
The CLI converts schema-2 declarations to an SPK. The SDK owns Compose planning,
|
|
4
|
+
identity gateway and lifecycle control. There is no separate wrapper container
|
|
5
|
+
or SSO Server. Upstream files stay byte-for-byte unchanged, including comments.
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
schema: 2
|
|
9
|
+
package: MediaApp
|
|
10
|
+
version: 0.1.0
|
|
11
|
+
build: 1
|
|
12
|
+
displayname: Media App
|
|
13
|
+
description: Upstream application
|
|
14
|
+
maintainer: Example
|
|
15
|
+
os_min_ver: "7.2.1-69057"
|
|
16
|
+
arches: [x86_64, aarch64]
|
|
17
|
+
capabilities: {}
|
|
18
|
+
compose:
|
|
19
|
+
files: [compose.yaml, site.yaml]
|
|
20
|
+
env_files: [.env]
|
|
21
|
+
profiles: []
|
|
22
|
+
web: {service: web, port: 8080, health: {path: /health, status: 200}}
|
|
23
|
+
network: {egress: isolated}
|
|
24
|
+
# Private package binds are rejected by DSM build; keep compatible upstream named volumes.
|
|
25
|
+
identity_gateway:
|
|
26
|
+
sdk: 0.15.0
|
|
27
|
+
mode: none
|
|
28
|
+
access: dsm-users
|
|
29
|
+
listen: {http: 28000}
|
|
30
|
+
ui: {launch: new-page}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
No backend, dummy CGI, UI directory or helper payload is required. Omit `web`
|
|
34
|
+
and `identity_gateway` for headless projects. Optional `compose.helpers` names
|
|
35
|
+
architecture-specific native payloads for token/account adapters. All lifecycle
|
|
36
|
+
scripts run as the package account; containers are managed by Container Manager.
|
|
37
|
+
|
|
38
|
+
## Planning and artifacts
|
|
39
|
+
|
|
40
|
+
The pinned compose-go reference loader merges ordered files, env files and
|
|
41
|
+
profiles. Process environment never supplies interpolation values. Local include,
|
|
42
|
+
extends, env_file and label_file dependencies are bounded, confined and copied
|
|
43
|
+
without symlinks. Remote/interpolated dependencies are rejected. CLI import uses
|
|
44
|
+
the SDK inventory, not a second parser for dependency semantics.
|
|
45
|
+
|
|
46
|
+
`compose/upstream/` preserves input bytes. `generated.override.yaml` is a real
|
|
47
|
+
reference-loader replacement override: lists such as ports and mounts use
|
|
48
|
+
`!override` so original wildcard publications cannot survive list merging.
|
|
49
|
+
`docker-compose.yml` is the final tag-free worker document. `compose.lock.json`
|
|
50
|
+
records input/effective hashes, image/platform inventory, fixed identities,
|
|
51
|
+
mounts, networking and explicit acceptance blockers. `images.json` contains the
|
|
52
|
+
registry manifests/config bytes needed for offline image-volume verification.
|
|
53
|
+
Inspection replays the trusted SDK planner over the packaged inputs and compares
|
|
54
|
+
actual effective, override, lock, launcher, gateway, resources and lifecycle.
|
|
55
|
+
|
|
56
|
+
Build resolves anonymous registry metadata and pins the Linux platform manifest.
|
|
57
|
+
It verifies pinned upstream index/manifest digests and image config hashes, and
|
|
58
|
+
turns image-declared anonymous VOLUMEs into stable package-namespaced volumes.
|
|
59
|
+
Private registry credentials are not inherited. `SYNOPKGLAND_COMPOSE_REGISTRY_MIRRORS`
|
|
60
|
+
is an explicit build-host JSON map of registry names to HTTPS origins; it changes
|
|
61
|
+
metadata transport only, never the repository or runtime reference. Mirrors are
|
|
62
|
+
a build operator trust decision. `check`/`inspect` remain offline.
|
|
63
|
+
|
|
64
|
+
## Support boundary
|
|
65
|
+
|
|
66
|
+
| Feature | Current behavior |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| Files in one directory, profiles, anchors, depends_on, healthcheck | Reference-loader semantics retained; DSM worker compatibility needs device acceptance |
|
|
69
|
+
| Named local volumes | Stable package namespace, retained across recreation; inspect inventory |
|
|
70
|
+
| Private bind data | DSM build rejects COMPOSE_PRIVATE_BIND_TIMING_UNSUPPORTED: clean-install preinst precedes pkgvar creation, while worker acquire precedes postinst; no root initialization or automatic volume conversion |
|
|
71
|
+
| Image VOLUME | Build inventories verified image config and assigns a stable named volume |
|
|
72
|
+
| Existing external volume | Rejected without trusted admin ownership/driver readback |
|
|
73
|
+
| Existing Shared Folder | Explicit opt-in intent only; blocked settings page until official protected ACL/project control exists |
|
|
74
|
+
| Bridge | Default no-publish dedicated internal bridge with a package-derived fixed IP; availability requires App health, TCP and the current lifecycle acquire record; no Docker identity attestation |
|
|
75
|
+
| Host | Explicit verified OIDC web consumer with address_env forced to 127.0.0.1; no container-root; internet access is visible |
|
|
76
|
+
| Egress | isolated internal networks, or explicit internet outbound bridge; fine allowlist declarations fail |
|
|
77
|
+
| Public ports | All upstream publishes removed; the host gateway dials the declared web service on its fixed bridge IP; auxiliary ingress is unsupported |
|
|
78
|
+
| Root | Fixed package UID/GID by default; per-service container-root requires reason, bridge and no host binds; only CHOWN/FOWNER/SETUID/SETGID opt-ins |
|
|
79
|
+
| Build, hooks, privileged, host PID/IPC, devices, sockets | Rejected; no host root workaround |
|
|
80
|
+
| Secret/config file resources, plugins, replicas/deploy | Rejected until their bounded delivery/runtime contract exists |
|
|
81
|
+
| Subpath hosting | Unsupported; use the app's dedicated origin at / |
|
|
82
|
+
|
|
83
|
+
Container-root is distinct from host elevation. Named-volume ownership and an
|
|
84
|
+
image's arbitrary-UID compatibility are not proved by a valid plan. Test the
|
|
85
|
+
actual immutable image, initialization, health, UID/capabilities and restart.
|
|
86
|
+
Unknown compatibility is never a certification result.
|
|
87
|
+
|
|
88
|
+
## Shared Folder authority
|
|
89
|
+
|
|
90
|
+
Containers use a fixed package identity. **They do not inherit the signed-in
|
|
91
|
+
user's File Station ACL.** App library/share settings control which app users
|
|
92
|
+
see mounted data. Read-only prevents source changes; read/write can permit file
|
|
93
|
+
modification and deletion. DSM identity/account bridge and file authority are
|
|
94
|
+
separate features. Per-DSM-user filesystem access requires a File Station session
|
|
95
|
+
or Broker Adapter; it is outside this change and cannot be obtained through a mount.
|
|
96
|
+
|
|
97
|
+
Declare `capabilities: {composeMounts: true}` and, for example:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
compose:
|
|
101
|
+
shares:
|
|
102
|
+
media:
|
|
103
|
+
select: admin-after-install
|
|
104
|
+
permission: ro
|
|
105
|
+
mounts: [{service: web, target: /media}]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Selection is after installation by an administrator in the App, not a claim
|
|
109
|
+
that the installation wizard can enumerate existing Shares. `install-draft`
|
|
110
|
+
is only a draft intent. Until authorization, only SDK settings start and no
|
|
111
|
+
upstream docker-project is acquired. The independent authority purpose is
|
|
112
|
+
`compose-mount`; a user destination grant from Download Hub/delegate never
|
|
113
|
+
permits a package-wide mount.
|
|
114
|
+
|
|
115
|
+
The current implementation does **not** issue a mount grant, invoke ACL tools,
|
|
116
|
+
install a protected executor for Compose, or activate/revoke an upstream project.
|
|
117
|
+
Confirmation returns `COMPOSE_PROJECT_CONTROL_UNAVAILABLE`. It must remain
|
|
118
|
+
unsupported until an official ACL plus fixed project-control adapter can use
|
|
119
|
+
protected policy/digest/generation authority, stop/recreate/read back and revoke
|
|
120
|
+
without reviving stale grants. No app runs as host root to bypass this boundary.
|
|
121
|
+
|
|
122
|
+
## Gateway transport
|
|
123
|
+
|
|
124
|
+
`none` keeps native app login; `header`, `token` and embedded `oidc` use supported
|
|
125
|
+
upstream interfaces plus the DSM account bridge. Access defaults to dsm-users;
|
|
126
|
+
admins/groups revalidate NSS membership per request. Public access is explicit
|
|
127
|
+
and only valid with none. Reserved DSM id/did and this gateway's cookies are
|
|
128
|
+
filtered in both directions; other Cookie order/duplicates and Set-Cookie
|
|
129
|
+
attributes are preserved. Claimed identity and DSM token headers are stripped.
|
|
130
|
+
|
|
131
|
+
Upstream POST does not require SynoToken. Browser mutations and WebSocket upgrades
|
|
132
|
+
require exact scheme/host/port Origin, or Referer when Origin is absent, plus
|
|
133
|
+
SameSite gateway cookies. Missing/null/foreign/duplicate origin fails. Explicit
|
|
134
|
+
app-authenticated passthrough can serve native clients without browser headers;
|
|
135
|
+
when browser evidence is present it is still checked. No identity is injected
|
|
136
|
+
into passthrough. SynoToken remains required at DSM's own authorization CGI.
|
|
137
|
+
|
|
138
|
+
WebSocket frames, SSE flush, streamed uploads, Range/HEAD downloads and redirects
|
|
139
|
+
have transport tests. Uploads default to 16 GiB; max_upload_bytes can reduce it.
|
|
140
|
+
Known oversize bodies are rejected before proxying; streamed oversize errors
|
|
141
|
+
return 413. Dial, TLS and response-header deadlines are separate; there is no
|
|
142
|
+
whole-response streaming timeout. Exact upstream absolute redirects change
|
|
143
|
+
origin while retaining encoded path, query and fragment; unrelated IdP URLs and
|
|
144
|
+
relative redirects keep their semantics. No HTML/JS body regex rewriting.
|
|
145
|
+
|
|
146
|
+
## Device acceptance remains required
|
|
147
|
+
|
|
148
|
+
A Linux gate is not DSM proof. Confirm final SPK digest and DSM/Container Manager
|
|
149
|
+
versions, worker interpolation/model semantics, storage ownership timing,
|
|
150
|
+
declared availability readiness, LAN isolation/egress, protected grant lifecycle, encrypted Share
|
|
151
|
+
ACLs, two-user visibility and clean install/upgrade/reboot/stop/uninstall before
|
|
152
|
+
calling the package supported on a NAS. No NAS is accessed by the build gate.
|
|
153
|
+
|
|
154
|
+
The unified Compose contract requires SDK, CLI and templates 0.15.0. Use the published, matching versions together; the legacy packaging route and bridge publish mode are removed.
|
|
155
|
+
|
|
156
|
+
Primary override files, and each included override set, must share a directory. Cross-directory override bases are rejected before loader I/O. Included implicit .env files are confined, inventoried and preserved; symlinks are rejected.
|
|
157
|
+
|
|
158
|
+
## DSM bridge availability readiness
|
|
159
|
+
|
|
160
|
+
Declare `compose.web.health: {path: /health, status: 200}` using the App's real
|
|
161
|
+
unauthenticated health endpoint (an origin-relative path and expected 2xx status).
|
|
162
|
+
The SDK follows no redirects, uses no proxy/cookies/DSM credentials, and bounds
|
|
163
|
+
TCP/HTTP timeouts. All three conditions are required before gateway admission:
|
|
164
|
+
|
|
165
|
+
1. The declared App health endpoint returns the expected status.
|
|
166
|
+
2. The planner-derived fixed bridge IP and container port accept a TCP connection.
|
|
167
|
+
3. A private, durably written `pkgvar/daemon/compose-acquire.json` records an active
|
|
168
|
+
lifecycle acquire generation, the current installed SPK version and plan digest.
|
|
169
|
+
|
|
170
|
+
DSM's startup worker returns before the generated start hook records acquire.
|
|
171
|
+
Stop/upgrade/uninstall durably release it before stopping the gateway; a new
|
|
172
|
+
start records a fresh generation. Missing, released, foreign or stale state
|
|
173
|
+
fails closed. This is the package lifecycle's record, not DSM runtime readback.
|
|
174
|
+
|
|
175
|
+
**Limitation:** these checks cannot prove that the Docker image, project or
|
|
176
|
+
generation actually running is the declared one. The threat is another local
|
|
177
|
+
process occupying the same port and answering health. This is accepted because
|
|
178
|
+
that process must already have code execution on the NAS. Do not use readiness
|
|
179
|
+
as an identity or isolation attestation. Restore full authoritative verification
|
|
180
|
+
when an official package-account readback channel becomes available. Never grant
|
|
181
|
+
Docker socket access, borrow an admin session or use root for readiness.
|
|
182
|
+
|
|
183
|
+
`inspect` reports `compose.readiness.profile` and this limitation. The old
|
|
184
|
+
bridge-unverified admission blocker is replaced by `COMPOSE_READINESS_INVALID`,
|
|
185
|
+
`COMPOSE_ACQUIRE_GENERATION_INVALID`, `COMPOSE_TCP_NOT_READY` and
|
|
186
|
+
`COMPOSE_HEALTH_NOT_READY`, according to the actual failing condition.
|
|
187
|
+
|
|
188
|
+
Compose SPKs intentionally omit `install_dep_packages=ContainerManager`: DSM can
|
|
189
|
+
report that dependency not ready even while its systemd unit and Docker daemon
|
|
190
|
+
are active. The official `docker-project` worker checks actual availability;
|
|
191
|
+
this does not authorize starting, modifying or repairing Container Manager.
|
|
192
|
+
|
|
193
|
+
For disposable environments, optional `compose.resource_prefix: t-0661-` prefixes
|
|
194
|
+
all generated project/network/volume names while retaining the package ID as the
|
|
195
|
+
namespace suffix. Keep it stable across upgrades: changing it selects new
|
|
196
|
+
resources and does not migrate or delete retained data.
|
|
197
|
+
|
|
198
|
+
Bridge web ingress uses a dedicated `sdk_ingress` internal bridge. The web service
|
|
199
|
+
receives a fixed IPv4 address, and the package-account gateway connects directly
|
|
200
|
+
to its declared container port. Internal bridges were reachable by package-account
|
|
201
|
+
hooks in the sa3200d and vt-nas feasibility probes. No HAProxy helper, published
|
|
202
|
+
container port, Docker socket or host root is required. Explicit internet egress
|
|
203
|
+
still adds the existing outbound bridge; other App services retain their internal networks.
|
|
204
|
+
|
|
205
|
+
The subnet is a deterministic /29 candidate derived from package ID and
|
|
206
|
+
`compose.network.ingress_candidate` (integer 0-255, default 0). It comes from
|
|
207
|
+
private 10.128.0.0/9; this pool is not globally reserved. Before acquisition, the
|
|
208
|
+
package-account preparer reads kernel IPv4 routes and all interface prefixes, and rejects overlap with
|
|
209
|
+
`COMPOSE_SUBNET_CONFLICT`. Choose another candidate and rebuild; no silent
|
|
210
|
+
renumbering occurs on upgrade. Choose a different candidate for a clean
|
|
211
|
+
installation; automatic migration of an installed network is not provided. The exact previous package allocation can be
|
|
212
|
+
reused through a private `pkgvar/compose-ingress.json` allocation record written
|
|
213
|
+
in postinst. Preinst reads DSM7's physical `<SYNOPKG_PKGDEST_VOL>/@appdata/<id>`
|
|
214
|
+
when `SYNOPKG_PKGVAR` is absent: DSM removes both the old payload and
|
|
215
|
+
`/var/packages/<id>/var` before replacement;
|
|
216
|
+
it does not attest Docker ownership. Missing or invalid history never exempts a
|
|
217
|
+
conflicting route. Container Manager's atomic IPAM check remains the final arbiter for
|
|
218
|
+
concurrent acquisition and networks without a visible host route; the preparer
|
|
219
|
+
does not claim a complete Docker network inventory or network ownership attestation.
|
|
220
|
+
Unavailable kernel inventory fails with `COMPOSE_SUBNET_INVENTORY_UNAVAILABLE`.
|
|
221
|
+
|
|
222
|
+
`inspect` inventories the address, subnet, service, port and candidate. The gateway
|
|
223
|
+
accepts only its package-derived bridge address; arbitrary private-network URLs,
|
|
224
|
+
DNS targets and user-supplied credentials/query strings are not ingress endpoints.
|
|
225
|
+
The readiness profile is `health-fixed-ip-lifecycle` and retains the existing
|
|
226
|
+
availability-only limitation. Host-network OIDC remains a separate explicit opt-in.
|
|
227
|
+
|
|
228
|
+
Bridge publish mode has been removed. Existing `web.host_port` declarations are
|
|
229
|
+
accepted for compatibility and ignored by bridge transport; new scaffolds omit it.
|
|
230
|
+
Legacy `.sc` residue is not purged by this topology. Migrating an already published
|
|
231
|
+
installation requires a separately authorized cleanup or an official worker fix;
|
|
232
|
+
a no-publish rebuild cannot claim to remove historical service registrations.
|
|
233
|
+
|
|
234
|
+
On vt-nas DSM 7.4/Docker 24.0.2, internal networks blocked tested external IPv4
|
|
235
|
+
and external DNS, but allowed NAS host services through the bridge gateway and
|
|
236
|
+
NAS LAN address. Therefore `isolated` is not a host-service firewall. IPv6 had
|
|
237
|
+
no usable default route on this fixture, which is not proof of an IPv6 firewall.
|
|
238
|
+
The official worker added NET_BIND_SERVICE to the capability bounding set even
|
|
239
|
+
with cap_drop ALL; effective/permitted/ambient capabilities were zero for the
|
|
240
|
+
nonroot fixture. Do not claim an empty bounding set or complete NAS isolation.
|
|
241
|
+
|
|
242
|
+
DSM cookie names `id`, `did`, `_SSID`, `_CrPoSt`, `stay_login` and the complete
|
|
243
|
+
`synopkgland_sso_` namespace are reserved in both request Cookie and response
|
|
244
|
+
Set-Cookie. Other App sessions/native cookies retain their normal semantics.
|
|
245
|
+
Cookies are shared across ports on the same hostname, so filtering only this
|
|
246
|
+
package's SDK cookie would leak another App's SDK session to an upstream.
|
|
247
|
+
|
|
248
|
+
Historical publish-topology vt-nas validation was release-blocked: the Container Manager
|
|
249
|
+
24.0.2-1706 docker-project worker can return profile error 1202 on update,
|
|
250
|
+
which Package Center reports as postinst-worker failure 277. The owned test
|
|
251
|
+
project and a real Vaultwarden package reproduced this; switching Compose
|
|
252
|
+
publish syntax did not resolve it. No root App, firewall edit or Container
|
|
253
|
+
Manager modification is used as a workaround. Validate acquisition, upgrade
|
|
254
|
+
and uninstall on a working official worker before claiming DSM support.
|
|
255
|
+
|
|
256
|
+
### Diagnose official worker failures without changing the NAS
|
|
257
|
+
|
|
258
|
+
Package Center's postinst-worker error is a wrapper. Read only the failed
|
|
259
|
+
package's worker messages and the matching timestamp/PID in DSM logs, removing
|
|
260
|
+
session, token, cookie and password fields before displaying or saving output.
|
|
261
|
+
Distinguish these observed failure chains:
|
|
262
|
+
|
|
263
|
+
| Worker result | Matching lower-level evidence | Interpretation |
|
|
264
|
+
| --- | --- | --- |
|
|
265
|
+
| Profile 1202, Package Center 277 | `service_conf_section_set.c:92 Port conflict with existing ports` followed by `profile.cpp:118 Fail to create port section` | Official service-port registration rejected a conflict; malformed Compose syntax is not established by these codes |
|
|
266
|
+
| Compose-up 2201, Package Center 276 | Registry request fails with `x509: certificate signed by unknown authority` | The Docker daemon could not fetch an image; gateway authentication and readiness have not been tested yet |
|
|
267
|
+
|
|
268
|
+
For the first chain, `servicetool --conf-port-conflict-check --tcp PORT` and
|
|
269
|
+
`servicetool --runtime-port-conflict-check --tcp PORT` are read-only checks.
|
|
270
|
+
Read the matching lower-level error even for wrapper 276: a follow-up failed
|
|
271
|
+
reinstall returned 276 with profile 1202, rather than a registry failure.
|
|
272
|
+
Record the reported service/process owner. A clean readback after uninstall
|
|
273
|
+
does not identify the conflict that existed during the failed operation.
|
|
274
|
+
Do not remove service profiles, disable firewall checks, bypass the official
|
|
275
|
+
worker, change ports automatically or restart Container Manager to turn the
|
|
276
|
+
failure into a pass.
|
|
277
|
+
|
|
278
|
+
Container Manager's status script also requires `synosystemctl` on PATH. A
|
|
279
|
+
status failure from a restricted SSH environment alone does not prove daemon
|
|
280
|
+
failure; correlate the script's actual error, service-unit state and Docker
|
|
281
|
+
server response. Keep administrative diagnostics separate from the App, which
|
|
282
|
+
continues to run as its package account without Docker socket access.
|
|
283
|
+
|
|
284
|
+
An additional DSM 7.3.2 / Container Manager 24.0.2-1606 fixture passed Header
|
|
285
|
+
and Vaultwarden install/start/upgrade. This does not certify the failing DSM
|
|
286
|
+
7.4 / 24.0.2-1706 environment. Its explicit test configuration used immutable
|
|
287
|
+
mirror image references and declared internet egress for the web service; it
|
|
288
|
+
does not extend the isolated-ingress or original-registry support evidence.
|
|
289
|
+
The same Header SPK also passed install/start/upgrade on a second DSM 7.4 /
|
|
290
|
+
24.0.2-1706 device. The original failure therefore cannot be classified as
|
|
291
|
+
affecting every device with that Container Manager version.
|
|
292
|
+
|
|
293
|
+
Official uninstall on both additional fixtures removed the packages, projects,
|
|
294
|
+
containers and networks but left their `docker_<container>.sc` service-port
|
|
295
|
+
profiles. Read-only configuration checks still reported those ports as reserved
|
|
296
|
+
after the containers were gone. This is a separate cleanup acceptance failure;
|
|
297
|
+
successful upgrade/reinstall does not clear it. The retained profiles are a
|
|
298
|
+
possible source of later port conflicts, not proof of the original conflict's
|
|
299
|
+
owner. Do not add a root App hook or delete DSM service configuration as a
|
|
300
|
+
workaround; record the owned profiles and resolve the official worker cleanup
|
|
301
|
+
contract before claiming complete uninstall support.
|
|
302
|
+
|
|
303
|
+
### Official port-profile cleanup boundary
|
|
304
|
+
|
|
305
|
+
Follow-up tests isolated the uninstall defect from SDK-generated ingress. Minimal
|
|
306
|
+
packages containing only the official docker-project resource, package-account
|
|
307
|
+
hooks and a non-root Node container reproduced retained port reservations with
|
|
308
|
+
all three forms: long loopback publish, short loopback publish and wildcard
|
|
309
|
+
publish. Distinct project names and distinct ports allowed two Apps to run
|
|
310
|
+
concurrently, but did not make their uninstall clean. A no-publish control did
|
|
311
|
+
not create a port-profile `.sc`. Changing syntax or exposing the port to the LAN
|
|
312
|
+
is therefore not a validated cleanup fix.
|
|
313
|
+
|
|
314
|
+
Read-only inspection of Container Manager 24.0.2-1606 and 1706 found that
|
|
315
|
+
Project.Clean calls Compose.Down before RemoveProfileForAllContainers; that
|
|
316
|
+
cleanup method obtains IDs through Compose.Ps `-qa`. Once Down has removed the
|
|
317
|
+
containers, that lookup can produce no profiles to unregister. The observed
|
|
318
|
+
files were created during install acquisition, before the package was started.
|
|
319
|
+
This supports an official worker cleanup defect; it does not establish the
|
|
320
|
+
exact internal conflict owner or race during a separate failed upgrade.
|
|
321
|
+
|
|
322
|
+
The documented [port-config worker](https://help.synology.com/developer-guide/resource_acquisition/port_config.html)
|
|
323
|
+
was also tested. Even when its protocol-file basename and section matched the
|
|
324
|
+
CM file, DSM registered it as `<package>.sc`. Container Manager separately
|
|
325
|
+
registered `docker_<container>.sc`; releasing port-config removed only the
|
|
326
|
+
package registration. Declaring that resource did not solve the CM residue.
|
|
327
|
+
The [docker-project contract](https://help.synology.com/developer-guide/resource_acquisition/docker-project.html)
|
|
328
|
+
documents project deletion on PREUNINST and build flags, without a separate
|
|
329
|
+
package-account profile-release option.
|
|
330
|
+
|
|
331
|
+
The original isolated Header fixture on vt-nas installed and started from a
|
|
332
|
+
clean configuration, then its upgrade still returned port conflict → 1202 →
|
|
333
|
+
277. Historical `.sc` residue alone is therefore insufficient to explain or
|
|
334
|
+
resolve that environment's upgrade failure. Preserve the timestamp/PID chain;
|
|
335
|
+
do not report a clean post-uninstall check as proof that upgrade was fixed.
|
|
336
|
+
|
|
337
|
+
The publish topology did not pass the complete lifecycle. The no-publish
|
|
338
|
+
implementation above requires its own final-SPK install/upgrade/reinstall/uninstall
|
|
339
|
+
and two-App acceptance before release. An administrator may separately authorize exact
|
|
340
|
+
owned-profile cleanup through official servicetool, with pre-removal hashes and
|
|
341
|
+
file/config readback; this is fixture recovery, never an App hook or a supported
|
|
342
|
+
rootless package cleanup mechanism.
|
|
343
|
+
|
|
344
|
+
A native package-account listener remains another rootless option. The new
|
|
345
|
+
no-publish bridge topology above implements the separately authorized ingress design. Existing host-network OIDC support requires a
|
|
346
|
+
verified loopback address setting, cannot remap ports or enforce isolated
|
|
347
|
+
egress, and is not a general replacement for Header/native-cookie bridge Apps.
|
|
348
|
+
Do not silently switch networking, add Docker socket access, run an App hook as
|
|
349
|
+
root or delete DSM configuration to claim compatibility.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 建立(Create)
|
|
2
2
|
|
|
3
|
-
先選擇 `native-go`(Go CGI + DSM 視窗)、`native-python`/`native-node`/`native-rust`(同一個 App,backend 換成 Python、Node.js(TypeScript)或 Rust)、`binary`(預建服務程式)或 `compose`(上游容器)。`compose`
|
|
3
|
+
先選擇 `native-go`(Go CGI + DSM 視窗)、`native-python`/`native-node`/`native-rust`(同一個 App,backend 換成 Python、Node.js(TypeScript)或 Rust)、`binary`(預建服務程式)或 `compose`(上游容器)。`compose` 產生 schema-2 的上游 Compose 範例與 DSM 差異宣告,`--from <compose.yaml>` 匯入現有 Compose。
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
synopkgland init -t native-go --with files,jobs,login --package MyApp my-app
|
|
@@ -17,7 +17,8 @@ synopkgland init -t compose --from ./compose.yaml upstream-app
|
|
|
17
17
|
|
|
18
18
|
## 容器存取控制
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Compose 只使用 schema-2 `dsm-package.yaml`,上游原樣保留,SDK planner 產生 override 與 worker 單檔。請先讀 [compose.md](compose.md)。預設 none 保留原生登入,另有 identity gateway 的 header/token/OIDC;不架 SSO Server。
|
|
21
|
+
容器固定使用套件身分,不繼承登入者 File Station ACL。Shared Folder 是安裝後 admin 授權,現階段官方 ACL/project control 未驗證,維持 blocked,不 acquire 上游專案。
|
|
21
22
|
|
|
22
23
|
## 建置與檢查
|
|
23
24
|
|
|
@@ -39,8 +40,8 @@ rootOps 與 delegate 已共用管理員 PasswordConfirm 核准的 protected exec
|
|
|
39
40
|
backend 寫 `runtime: python|node|rust`(`runtime: go` 等同 `kind: go`)。CGI 是 SDK 的 Go runtime(`bridge/cmd/runtime`),它驗證 DSM session、entry 與 `permissions.json` 後,以套件帳號(不是 root)每個請求啟動一次 App。App 放在 ui 以外的目錄,打包成 `target/app/`,build 寫入 `bridge.json`(spec/bridge/v1)並把它與 `permissions.json` 的 sha256 編進 runtime。
|
|
40
41
|
|
|
41
42
|
```yaml
|
|
42
|
-
backend: {runtime: python, dir: app, entry: main.py, sdk: 0.
|
|
43
|
-
backend: {runtime: node, dir: app, entry: main.mjs, sdk: 0.
|
|
43
|
+
backend: {runtime: python, dir: app, entry: main.py, sdk: 0.15.0}
|
|
44
|
+
backend: {runtime: node, dir: app, entry: main.mjs, sdk: 0.15.0, node: {package: Node.js_v20}}
|
|
44
45
|
backend: {runtime: rust, dir: app, entry: hello, methods: [hello.greet]}
|
|
45
46
|
```
|
|
46
47
|
|
|
@@ -232,16 +233,6 @@ Application Portal 是獨立 opt-in:不想要別名就省略 `portal`,不生
|
|
|
232
233
|
`defaultAlias`、Portal 面板/capability 或 Web Station alias;沒有 Portal
|
|
233
234
|
不是 inspect/info/doctor 問題。明確啟用 `portal` 時才將其 alias 作為 `defaultAlias`。
|
|
234
235
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
Web service 主選單使用
|
|
239
|
-
`type: "url"` 與明確的 `url` 指向既有 DSM gateway。明確 `access: public` 才保留 Web Station
|
|
240
|
-
alias;這是服務入口,不是 SDK Application Portal。DSM 會將 standalone URL 改寫成 launchApp 頁面,
|
|
241
|
-
需要 AppInstance/AppWindow JS class;schema 1 web service 沒有該 class,因此使用 url。需要 DSM 視窗時明寫 `dsm-window`。
|
|
242
|
-
|
|
243
|
-
Schema 2 的 `dsm-package.yaml` + `identity_gateway` + `compose` 同樣視為
|
|
244
|
-
web service:省略 `ui.launch` 時預設 `new-page`,使用 standalone launcher,
|
|
245
|
-
不帶 Application Portal。兩條 Compose 路線共用 `contract/app-launch.ts`
|
|
246
|
-
解析開啟方式與選擇 launcher 型別;明確的 `ui.launch` 可覆寫預設。
|
|
247
|
-
只有沒有 web service 的 SDK 原生 App 省略時才預設 `dsm-window`。
|
|
236
|
+
Compose 在 `dsm-package.yaml` 使用 `ui.launch`,預設 new-page;省略時也不宣告 Application Portal。
|
|
237
|
+
|
|
238
|
+
Compose classless gateway 用 `type: url`,有 AppWindow wrapper 的原生 App 才使用 `standalone`;都沿用 CLI 共用 app-launch 解析。Compose 的差異請見 [compose.md](compose.md)。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Control registered NAS Apps with ctl
|
|
2
|
+
|
|
3
|
+
Use this for operating installed Apps, rather than building/deploying projects.
|
|
4
|
+
|
|
5
|
+
1. `synopkgland ctl host list --json` checks registered machines and sessions.
|
|
6
|
+
2. Register with `ctl host add nas --url https://nas.example:5001 --username alice`, or renew with `ctl host login nas`. Credentials are hidden interactive input; `--password-stdin` is available. Do not put passwords/OTP in argv. Sessions and remembered-device tokens use OS secure storage, never a plaintext fallback.
|
|
7
|
+
3. Run `synopkgland ctl list --host nas --json` for all registered Apps, commands, schemas, effects, permissions and executable examples. If description access needs approval, ask the user to approve the whole candidate batch; do not self-approve. An explicit `--approve-installed` records permission to send the DSM session to the displayed inventory's fixed description endpoints. Use `--app <package>` to narrow the batch. No description endpoint is anonymous.
|
|
8
|
+
4. Use the discovered alias, CLI path and typed flags with an explicit `--host nas`; inspect `--help` and `--dry-run` before execution. For example, `synopkgland ctl notes list --status pending --host nas --dry-run`. Use the real command/schema from discovery, not this illustrative name.
|
|
9
|
+
5. Reads need no write confirmation. For authorized write/destructive commands, add `--yes`; without it the CLI returns a plan and confirmation error. A --yes flag does not replace the user's authorization or the App's backend checks.
|
|
10
|
+
|
|
11
|
+
There is no shared current context: with multiple hosts, always specify --host. `--host a,b` and --all-hosts return per-host results, with exit 8 on partial failure. Do not retry a write merely because one host failed; query its state first. --refresh rereads descriptions; unchanged package versions use the host/account/package/version/hash cache. Never assume cached metadata grants resource permission.
|
|
12
|
+
|
|
13
|
+
Use `ctl host show nas` to inspect approvals/pins, `ctl host revoke nas PackageID` to remove an App approval, `ctl host logout nas` to retain registration, and `ctl host remove nas --yes` to delete sessions, device token, approvals and cache. A failed NAS logout is reported independently from successful local removal.
|
|
14
|
+
|
|
15
|
+
Stdout defaults to JSON without a TTY. Success is {ok:true,data}; failure is {ok:false,error:{code,message,hint,allowed?}}. Multi-host output uses {ok,results:[{host,ok,data|error}]}. Follow the concrete hint and complete allowed list; do not guess another method or bypass ctl with raw HTTP. Exit codes: 2 authentication, 3 parameters/names, 4 permissions/trust, 5 App/contract, 6 network, 7 confirmation, 8 partial failure.
|
|
16
|
+
|
|
17
|
+
## Designing commands for AI
|
|
18
|
+
|
|
19
|
+
Declare explicit `ctl.commands` in dsm-package.yaml; build/inspect compare references with actual route registrations and permissions. Internal bindings are not CLI commands. Each command needs a stable cli path, description, parameter JSON Schema (descriptions/enums/defaults/examples), effect and examples. The build derives permissions; don't duplicate authorization. Missing schema means --input file/- only.
|
|
20
|
+
|
|
21
|
+
Prefer a high-level backend method when sequencing is App-specific. Download Hub's immediate acceptance/background preparation, owner-bound search resultId, destination grants and BT file selection must not become a raw gopeed.request proxy. Offer downloads add/list/pause/resume and search tasks with stable responses. search.stream uses NDJSON; use the fixed ndjson flow, not the JSON method adapter. Torrent import needs a dedicated high-level backend contract, not a CLI-specific sequence.
|
|
22
|
+
|
|
23
|
+
Reclaim scan start/poll/result can use the fixed job flow once backend job identifiers/states have a stable documented shape. Existing job.result can be an explicit read command. Never automatically run cleanup.plan then cleanup.apply: the plan identity, freshness and permanent-deletion confirmation belong in the App backend; mark an eventual cleanup command destructive.
|
|
24
|
+
|
|
25
|
+
Connect status/ports listing can be invoke commands; connecting is accepted, not completed. Use job polling or a high-level method for completion and retain provider/exposure confirmation. Do not hide browser authorization in a supposedly successful CLI call.
|
|
26
|
+
|
|
27
|
+
Only fixed generic flows are supported: invoke, bounded job start/poll/result (--wait/--no-wait), bounded offset pagination, bounded NDJSON collection, and SDK upload. SDK upload currently streams a local file once through nas.files.upload to File Station multipart; there is no generic chunk/start/finish protocol to invent. Metadata cannot specify scripts, arbitrary URLs or expressions; every step is bound to the same package's allowlisted methods and normal server permissions.
|
|
28
|
+
|
|
29
|
+
For AI consumers: execute commands that describe a complete task. Do not manually concatenate hidden API methods, invent job polling, or treat accepted as completed. Let the fixed command adapter handle the declared flow and surface its timeout/partial state.
|
|
30
|
+
|
|
31
|
+
## Sensitive parameters
|
|
32
|
+
|
|
33
|
+
App authors must mark passkeys, tokens, private URLs, torrent/base64 and upload content with standard JSON Schema `writeOnly: true`, including nested objects, array items and local references. Mark sensitive response fields in resultSchema too. Do not embed real secrets in schema defaults/enums/examples or command examples.
|
|
34
|
+
|
|
35
|
+
CLI plans, dry-run, missing-yes errors, JSON/multi-host output and known input values repeated by the App use the fixed string `<redacted>`. Schema validation errors never echo instance values or paths. This changes presentation only: authorized requests still send the original values. The CLI does not log or cache business parameters. Sensitive description examples/defaults are masked in list/help and disk caches; a cache altered by redaction requires a fresh authenticated description/hash check before execution. Fill placeholders locally; never paste secrets into AI messages or logs.
|
|
36
|
+
|
|
37
|
+
## Command naming recommendations
|
|
38
|
+
|
|
39
|
+
Prefer `<resource> <verb>`. Common verbs are `list`, `get`, `export` (often reads), `create`, `update`, `import`, `run` (often writes), and `delete` (often destructive). Pick one synonym within an App; avoid mixing show/get/status for the same task. These are recommendations only: Apps may use domain verbs such as `pause` and `resume`.
|
|
40
|
+
|
|
41
|
+
Every command must explicitly declare its required `effect`. CLI/build never infer or overwrite effect from the verb. The existing check that command effect must not be less restrictive than its bindings remains. There is no verb allowlist or new verb rejection rule.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
3. Review `synopkgland deploy --dry-run --arch x86_64`, which does not connect to a NAS.
|
|
6
6
|
4. Only when installation is authorized, run `synopkgland deploy --host <profile>`.
|
|
7
7
|
|
|
8
|
-
Deployment uploads directly to DSM. It does not publish artifacts.
|
|
8
|
+
Deployment uploads directly to DSM. It does not publish artifacts. Compose uses schema-2 differences and fixed package identity. Existing Shared Folder intent requires admin confirmation after install; official protected ACL/project control is currently blocked. No install answer or user destination grant activates a package mount. See compose.md and its device acceptance blockers.
|
|
9
9
|
|
|
10
10
|
For native-go root-ops, approve fixed operations in DSM after installation. Updates that change the task text require fresh approval. Uninstall removes the executor tasks, the App's Application Privileges rules and a stale portal alias itself. Never delete installed packages or data as part of a code cleanup.
|
|
11
11
|
|
|
@@ -122,3 +122,7 @@ sweep forward the cancel instead of admitting it.
|
|
|
122
122
|
forward. This is not proof of other DSM releases or full writer/reboot behavior.
|
|
123
123
|
|
|
124
124
|
The pinned scope-selection task also binds the protected generation observed before confirmation. The executor compares it under the publish lock and refuses stale tasks, so a retained task cannot replay an older selection after another confirmation.
|
|
125
|
+
|
|
126
|
+
## Container mounts
|
|
127
|
+
|
|
128
|
+
Containers use a fixed package identity and never inherit the signed-in user's File Station ACL. Mount grants are distinct from user destination grants. Per-user file access requires a session/Broker Adapter, outside this change. Read [compose.md](compose.md); official ACL/project control remains blocked.
|