@synopackageland/cli 0.12.1 → 0.13.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 +17 -0
- package/assets/go/daemon/main.go +25 -0
- package/assets/go/daemon/network.go +107 -0
- package/assets/go/daemon/network_test.go +86 -0
- package/dist/error-registry.js +15 -0
- package/dist/runtime/executor-digests.json +1 -12
- package/dist/skill.js +1 -0
- package/dist/standalone/build.js +5 -1
- package/dist/standalone/compose.d.ts +22 -0
- package/dist/standalone/compose.js +90 -0
- package/dist/standalone/daemon.d.ts +2 -0
- package/dist/standalone/daemon.js +22 -14
- package/dist/standalone/dsm-window.d.ts +3 -0
- package/dist/standalone/dsm-window.js +30 -1
- package/dist/standalone/identity-gateway.d.ts +73 -0
- package/dist/standalone/identity-gateway.js +215 -0
- package/dist/standalone/inspect-spk.js +40 -10
- package/dist/standalone/launch-params.d.ts +11 -0
- package/dist/standalone/launch-params.js +33 -0
- package/dist/standalone/lifecycle.js +6 -0
- package/dist/standalone/manifest.d.ts +5 -0
- package/dist/standalone/manifest.js +12 -2
- package/dist/standalone/reopen-events.d.ts +2 -2
- package/dist/standalone/reopen-events.js +3 -3
- package/dist/standalone/resource.d.ts +2 -0
- package/dist/standalone/resource.js +2 -0
- package/dist/standalone/spk.d.ts +2 -0
- package/dist/standalone/spk.js +20 -3
- package/dist/validate.js +1 -1
- package/package.json +2 -2
- package/skills/SKILL.md +1 -1
- package/skills/reference/create.md +2 -2
- package/skills/reference/identity-gateway.md +174 -0
- package/skills/reference/wrap.md +4 -0
package/dist/standalone/spk.js
CHANGED
|
@@ -22,7 +22,9 @@ import { copyStandaloneNotices } from "../build-spk/notices.js";
|
|
|
22
22
|
import { copyAppTree } from "./bridge-app.js";
|
|
23
23
|
import { isBridgeBackend } from "./bridge-backend.js";
|
|
24
24
|
import { hostScriptName, renderHostAdapterScript } from "./host-adapter.js";
|
|
25
|
+
import { composeSource } from "./compose.js";
|
|
25
26
|
import { copyDaemonPayload } from "./daemon.js";
|
|
27
|
+
import { GATEWAY_CGI_PATH, GATEWAY_CONFIG_PATH, GATEWAY_EXEC, identityGatewayConfig } from "./identity-gateway.js";
|
|
26
28
|
import { BACKGROUND_UNIT_DIR, BACKGROUND_WORKER_PAYLOAD, backgroundUnitName, renderBackgroundUnit, BRIDGE_WORKER_ARGS } from "./background.js";
|
|
27
29
|
import { renderStandaloneResource } from "./resource.js";
|
|
28
30
|
import { nativePortal } from "./portal.js";
|
|
@@ -79,11 +81,22 @@ export async function assembleStandaloneSpk(options) {
|
|
|
79
81
|
mkdirSync(dirname(cgiTarget), { recursive: true });
|
|
80
82
|
copyFileSync(options.cgiPath, cgiTarget);
|
|
81
83
|
chmodSync(cgiTarget, EXEC_MODE);
|
|
82
|
-
if (manifest.daemon) {
|
|
84
|
+
if (manifest.daemon || manifest.compose) {
|
|
83
85
|
if (!options.daemonControl)
|
|
84
86
|
throw new StandaloneBuildError("STANDALONE_DAEMON_ARTIFACT_REQUIRED", "daemon controller artifact required.");
|
|
85
87
|
copyDaemonPayload(manifest, options.arch, join(staging, "package", "daemon"), options.daemonControl);
|
|
86
88
|
}
|
|
89
|
+
if (manifest.identityGateway) {
|
|
90
|
+
if (!options.identityGateway)
|
|
91
|
+
throw new StandaloneBuildError("STANDALONE_IDENTITY_GATEWAY_ARTIFACT_REQUIRED", "identity gateway artifact required.");
|
|
92
|
+
for (const path of [GATEWAY_CGI_PATH, `daemon/payload/${GATEWAY_EXEC}`]) {
|
|
93
|
+
const target = join(staging, "package", path);
|
|
94
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
95
|
+
copyFileSync(options.identityGateway, target);
|
|
96
|
+
chmodSync(target, EXEC_MODE);
|
|
97
|
+
}
|
|
98
|
+
writeFile(join(staging, "package", GATEWAY_CONFIG_PATH), identityGatewayConfig(manifest));
|
|
99
|
+
}
|
|
87
100
|
if (manifest.capabilities.jobs) {
|
|
88
101
|
if (!options.backgroundWorker)
|
|
89
102
|
throw new StandaloneBuildError("STANDALONE_BACKGROUND_ARTIFACT_REQUIRED", "background worker artifact required.");
|
|
@@ -127,11 +140,13 @@ export async function assembleStandaloneSpk(options) {
|
|
|
127
140
|
const hook = manifest.uninstall && uninstallExecProblem(join(staging, "package"), manifest.uninstall.purge);
|
|
128
141
|
if (hook)
|
|
129
142
|
throw new StandaloneBuildError(hook.code, hook.message);
|
|
143
|
+
if (manifest.compose)
|
|
144
|
+
writeFile(join(staging, "package/compose/docker-compose.yml"), composeSource(manifest.compose));
|
|
130
145
|
const packageTgz = join(staging, "package.tgz");
|
|
131
146
|
await createReproducibleTar(join(staging, "package"), packageTgz, { gzip: true });
|
|
132
147
|
rmSync(join(staging, "package"), { recursive: true, force: true });
|
|
133
148
|
const checksum = createHash("md5").update(readFileSync(packageTgz)).digest("hex");
|
|
134
|
-
writeFile(join(staging, "sdk-package.json"), JSON.stringify({ schema: 2, backend: manifest.backend.kind, capabilities: Object.keys(manifest.capabilities).sort(), ...(manifest.entry ? { entry: manifest.entry } : {}), ...(manifest.uninstall ? { uninstall: uninstallRecord(manifest.uninstall, manifest.i18n?.catalogs) } : {}) }));
|
|
149
|
+
writeFile(join(staging, "sdk-package.json"), JSON.stringify({ schema: 2, backend: manifest.backend.kind, capabilities: Object.keys(manifest.capabilities).sort(), ...(manifest.compose ? { compose: { services: manifest.compose.services } } : {}), ...(manifest.entry ? { entry: manifest.entry } : {}), ...(manifest.uninstall ? { uninstall: uninstallRecord(manifest.uninstall, manifest.i18n?.catalogs) } : {}) }));
|
|
135
150
|
writeFile(join(staging, "INFO"), renderInfo(manifest, options.arch, checksum, options.executor?.executorDigest));
|
|
136
151
|
mkdirSync(join(staging, "conf"));
|
|
137
152
|
writeFile(join(staging, "conf", "privilege"), renderNativePrivilege());
|
|
@@ -203,12 +218,14 @@ function renderInfo(manifest, arch, checksum, executorDigest) {
|
|
|
203
218
|
// Package Center writes INFO as root; root tasks accept only this executor.
|
|
204
219
|
if (executorDigest)
|
|
205
220
|
fields.push([EXECUTOR_ANCHOR, executorDigest]);
|
|
221
|
+
if (manifest.compose)
|
|
222
|
+
fields.push(["install_dep_packages", "ContainerManager"]);
|
|
206
223
|
const backend = manifest.backend;
|
|
207
224
|
if (isBridgeBackend(backend) && backend.node)
|
|
208
225
|
fields.push(["install_dep_packages", backend.node.package]);
|
|
209
226
|
fields.push(...metadataInfoFields(manifest.metadata));
|
|
210
227
|
// Background packages need prestart: it runs before DSM's systemd-user-unit acquire.
|
|
211
|
-
if (manifest.daemon || manifest.capabilities.jobs)
|
|
228
|
+
if (manifest.daemon || manifest.compose || manifest.capabilities.jobs)
|
|
212
229
|
fields.push(["precheckstartstop", "yes"]);
|
|
213
230
|
for (const locale of LOCALES) {
|
|
214
231
|
const text = manifest.i18n?.info[locale];
|
package/dist/validate.js
CHANGED
|
@@ -21,7 +21,7 @@ export function validateProject(project, options = {}) {
|
|
|
21
21
|
const standalonePath = join(project.root, DSM_PACKAGE_FILENAME);
|
|
22
22
|
const hasCompose = existsSync(project.composePath);
|
|
23
23
|
const hasStandalone = existsSync(standalonePath);
|
|
24
|
-
if (hasCompose && hasStandalone) {
|
|
24
|
+
if (hasCompose && hasStandalone && isNativeLayout(project.root)) {
|
|
25
25
|
return { ok: false, errors: [{
|
|
26
26
|
code: "PROJECT_RUNTIME_EXCLUSIVE",
|
|
27
27
|
span: { file: DSM_PACKAGE_FILENAME, line: 1, column: 1 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"prepublishOnly": "npm run build"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@synopackageland/templates": "0.
|
|
28
|
+
"@synopackageland/templates": "0.13.0",
|
|
29
29
|
"ajv": "^8.17.1",
|
|
30
30
|
"commander": "^13.1.0",
|
|
31
31
|
"tar": "^7.4.3",
|
package/skills/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Initialize, extend, check, build, inspect, and deploy Synology appl
|
|
|
7
7
|
|
|
8
8
|
Use this guide to initialize, build and inspect own applications or upstream OSS as independent packages. 依已實作能力區分 DSM session 檔案、私有 jobs 與 App 登入;delegate 已實作並隨 CLI 提供固定操作的離線委託(預設 `files.moveFromStaging`);共用 delegate/rootOps executor 的完整裝置生命週期驗收仍待完成,不得宣稱已通過;支援邊界依 delegate guide/support matrix,禁止發明新的身分平台。
|
|
9
9
|
|
|
10
|
-
Start with `synopkgland init -t native-go`, `synopkgland init -t binary`, or `synopkgland init -t compose`. Read reference/create.md or reference/wrap.md, then reference/deploy.md. Native CGI session checks belong to the package.
|
|
10
|
+
Start with `synopkgland init -t native-go`, `synopkgland init -t binary`, or `synopkgland init -t compose`. Read reference/create.md or reference/wrap.md, then reference/deploy.md. Native CGI session checks belong to the package. For wrapping upstream OSS with DSM accounts, read [reference/identity-gateway.md](reference/identity-gateway.md) first. It covers native and host-network Compose upstreams, mode selection, isolation, account deletion and verification. Fixed package credentials govern container data access.
|
|
11
11
|
|
|
12
12
|
Never install, uninstall, publish or modify NAS data without task authorization. Prefer check, inspect and deploy --dry-run for reviewable artifacts.
|
|
13
13
|
|
|
@@ -39,8 +39,8 @@ rootOps 與 delegate 已共用管理員 PasswordConfirm 核准的 protected exec
|
|
|
39
39
|
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
40
|
|
|
41
41
|
```yaml
|
|
42
|
-
backend: {runtime: python, dir: app, entry: main.py, sdk:
|
|
43
|
-
backend: {runtime: node, dir: app, entry: main.mjs, sdk:
|
|
42
|
+
backend: {runtime: python, dir: app, entry: main.py, sdk: 0.13.0}
|
|
43
|
+
backend: {runtime: node, dir: app, entry: main.mjs, sdk: 0.13.0, node: {package: Node.js_v20}}
|
|
44
44
|
backend: {runtime: rust, dir: app, entry: hello, methods: [hello.greet]}
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Wrap an OSS app with DSM accounts
|
|
2
|
+
|
|
3
|
+
Use this checklist before selecting an image or writing glue. Discover the
|
|
4
|
+
installed CLI (`--help`, `skill show`, `check --json`) and SDK contracts. Use the
|
|
5
|
+
app's supported API/configuration; do not patch upstream or write its database.
|
|
6
|
+
Record the tested app revision, image digest, DSM, architecture and limitations.
|
|
7
|
+
Ask the owner when evidence cannot establish a decision that affects their data
|
|
8
|
+
or expected behavior. An unknown answer is not a pass.
|
|
9
|
+
|
|
10
|
+
## Decide before building
|
|
11
|
+
|
|
12
|
+
| Question to answer with upstream source/docs and a probe | Decision |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| OIDC, trusted reverse-proxy headers, or only its own login? | Prefer `mode: oidc`; use `header` only with upstream trusted-header support. `token` runs a bounded native argv adapter that returns headers; establish how it obtains a per-user app credential. Do not invent an admin impersonation API. |
|
|
15
|
+
| Does OIDC JIT-create an account? Is registration interactive? Which username claim is used? | Enable supported auto-registration and silent entry. The gateway emits `sub` as DSM UID; verify the app stores/matches that stable subject, rather than only name/email. Test existing-account collisions and rename behavior. Never silently link unrelated accounts by email. |
|
|
16
|
+
| Does each sign-in apply a role/group claim, including demotion? | Configure `roles` and the app's claim mapping. If unsupported, accept and document the limitation by default. OpenList's JIT accounts can all be ordinary users; do not patch it or describe a manually linked bootstrap admin as automatic mapping. Ask only if the owner requires a different product behavior. |
|
|
17
|
+
| Is data private to each app user? | Test two ordinary users in both directions, including search, shares, downloads and APIs. OpenList needs distinct `base_path` and permissions; JIT alone does not create private storage. Jellyfin library visibility is not proof of object-level isolation. Paperless owner-NULL objects can become visible to everyone: deleting a user before their documents can expose them. Ask whether shared data is intended; do not promise isolation the app lacks. |
|
|
18
|
+
| What starts login, and which base/callback paths does the app use? | Verify redirects, trailing slash, query and HTTP/HTTPS behavior. `redirects` accepts local paths without a query. OIDC callbacks require exact allowlists; `rewrite_json` handles supported JSON login redirects. Test the actual desktop/bare-root entry separately from a direct SSO URL. |
|
|
19
|
+
| What really controls the listen address and port? | Verify the exact upstream environment/config key and actual kernel listener. OpenList uses `ADDR`, not `ADDRESS`. Set `127.0.0.1`; turn off every auxiliary public listener (FTP/SFTP/S3/debug/metrics as applicable). |
|
|
20
|
+
| Can every container run at an arbitrary non-root UID/GID? | Require package UID/GID materialization, `cap_drop: [ALL]`, `no-new-privileges:true`, and `network_mode: host` for every service. Bridge cannot reach the native loopback issuer. Host networking shares the NAS network namespace; it is not network isolation. An image that needs root or extra privileges is unsupported by this policy. |
|
|
21
|
+
| Where is data kept, and when does it exist? | Bind DSM's persistent `${SYNOPKG_PKGVAR}` root. DSM creates and owns it before resource acquisition; preinst only writes incoming `compose/.env`, never mkdir/chown `@appdata`. Do not mount upgrade-replaced `target/compose/data`. Test an actual package upgrade. Current support has no external share mounts, named volumes, extra Compose files or container secrets/config resources. |
|
|
22
|
+
| What deletes the app account, sessions and owned data? | Use the supported CLI/REST interface through native `account.on_delete.exec`, then explicitly scoped `remove`. Keep secrets in package-owned 0600 files and directories in 0700. OpenList REST user deletion does not remove physical `base_path` files. Shared files need an explicit retention decision. Make deletion idempotent; inspect pending failures and verify readback. |
|
|
23
|
+
| What happens when DSM disables the user or logs out? | New DSM sign-ins must fail. Existing app sessions, API tokens, download links or gateway sessions may remain until expiry; document the measured gap. Do not claim immediate revocation or add a root account watcher. |
|
|
24
|
+
| Must native/mobile clients work? | Verify their actual flow: app passwords/API tokens, Jellyfin Quick Connect, or OIDC in the system browser. Use exact `native_redirects` custom schemes where supported. Passthrough grants no DSM session protection; document app authentication and revocation for each allowed prefix. Browser success is not mobile success. |
|
|
25
|
+
| HTTP, HTTPS or both? | Test both requested schemes. HTTP can transmit the DSM `id` cookie without `Secure`; recommend HTTPS without claiming HTTP is unsupported. Local self-signed TLS and mobile trust are separate issues. Never disable NAS security settings to obtain a pass. |
|
|
26
|
+
| Does the NAS meet deployment/resource requirements? | `docker-project` needs DSM >= 7.2.1 and installed/running Container Manager. Keep package code unprivileged; Container Manager owns containers. Measure app RSS/cgroup usage separately from gateway and shared infrastructure. Check image architecture, CPU features, helper tools and Docker-only dependencies. Do not infer a native build exists. |
|
|
27
|
+
|
|
28
|
+
Account deletion uses a zero-root `/etc` inotify watcher and startup comparison
|
|
29
|
+
of previously seen local UIDs. It does not install DSM plugins, hooks, poll DSM
|
|
30
|
+
or use `account.snapshot`. Failed local delete actions remain pending and retry
|
|
31
|
+
with backoff. Domain/LDAP disappearances require explicit `gateway account
|
|
32
|
+
status` / `gateway account purge`; never infer deletion from an NSS outage.
|
|
33
|
+
|
|
34
|
+
## OpenList container example
|
|
35
|
+
|
|
36
|
+
Start with a schema-2 native-go or binary project for the native CGI and package
|
|
37
|
+
UI. Add `compose` to **dsm-package.yaml**; do not combine the legacy
|
|
38
|
+
`synology-app.yaml` Compose contract with it. Omit `daemon`. Set `os_min_ver` to
|
|
39
|
+
at least `7.2.1-69057`, `capabilities: {}`, and supply one helper payload directory
|
|
40
|
+
per architecture. The examples below pin released SDK 0.13.0, which contains the gateway/account APIs.
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
compose:
|
|
44
|
+
file: compose.yaml
|
|
45
|
+
targets: {x86_64: helpers}
|
|
46
|
+
services:
|
|
47
|
+
openlist: {address_env: ADDR, port: 25244}
|
|
48
|
+
identity_gateway:
|
|
49
|
+
sdk: 0.13.0
|
|
50
|
+
mode: oidc
|
|
51
|
+
upstream: 25244
|
|
52
|
+
listen: {http: 25240, https: 25241}
|
|
53
|
+
oidc:
|
|
54
|
+
issuer_port: 25249
|
|
55
|
+
clients:
|
|
56
|
+
- id: openlist
|
|
57
|
+
redirect_paths: [/api/auth/sso_callback, /api/auth/sso_get_token]
|
|
58
|
+
account:
|
|
59
|
+
on_delete:
|
|
60
|
+
- exec: [bin/openlist-glue, delete-user, "{uid}"]
|
|
61
|
+
- remove: users/{uid}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`compose.yaml` (replace `IMAGE_DIGEST` with the verified official image digest):
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
services:
|
|
68
|
+
openlist:
|
|
69
|
+
image: openlistteam/openlist@sha256:IMAGE_DIGEST
|
|
70
|
+
network_mode: host
|
|
71
|
+
user: "${SYNOPKGLAND_UID}:${SYNOPKGLAND_GID}"
|
|
72
|
+
cap_drop: [ALL]
|
|
73
|
+
security_opt: [no-new-privileges:true]
|
|
74
|
+
restart: "no"
|
|
75
|
+
environment:
|
|
76
|
+
ADDR: 127.0.0.1
|
|
77
|
+
HTTP_PORT: "25244"
|
|
78
|
+
HTTPS_PORT: "-1"
|
|
79
|
+
LOG_ENABLE: "false"
|
|
80
|
+
volumes: ["${SYNOPKG_PKGVAR}:/opt/openlist/data"]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Verify the current OpenList settings API, then have package-account app glue
|
|
84
|
+
configure `sso_login_enabled=true`, `sso_login_platform=OIDC`,
|
|
85
|
+
`sso_client_id=openlist`, `sso_client_secret` from
|
|
86
|
+
`var/sso/client-openlist.secret`, `sso_oidc_username_key=sub`,
|
|
87
|
+
`sso_endpoint_name=http://127.0.0.1:25249`, `sso_auto_register=true` and the
|
|
88
|
+
required compatibility setting. Keep bootstrap/admin credentials in a private
|
|
89
|
+
file, never argv/logs. The helper `helpers/bin/openlist-glue` is a project-owned
|
|
90
|
+
native executable: `delete-user UID` lists users, selects the exact ordinary
|
|
91
|
+
account by SSO subject, deletes through REST, and reads back absence. It returns
|
|
92
|
+
success if already absent and never deletes the bootstrap admin.
|
|
93
|
+
|
|
94
|
+
Configure a tested upstream JIT path policy before promising private files.
|
|
95
|
+
`remove: users/{uid}` deletes only that directory **under package var**; it is
|
|
96
|
+
not automatic cleanup of an arbitrary OpenList storage provider. The app's
|
|
97
|
+
SSO entry can be `/api/auth/sso?method=sso_get_token`; that query URL is not a
|
|
98
|
+
valid `identity_gateway.redirects` value. Test and document the entry you ship.
|
|
99
|
+
|
|
100
|
+
Startup order is DSM resource acquisition -> Container Manager starts the
|
|
101
|
+
project -> the native controller waits up to its bounded start deadline for
|
|
102
|
+
**all declared service ports**, owned by the package UID, at 127.0.0.1 ->
|
|
103
|
+
`gateway run` without a child. The controller checks IPv4/IPv6 TCP and UDP
|
|
104
|
+
bindings every second and closes the gateway on missing/unsafe upstreams.
|
|
105
|
+
Public TCP listener exceptions are only the declared gateway ports. Package
|
|
106
|
+
stop/preupgrade/preuninst stop the gateway before DSM releases the project;
|
|
107
|
+
no package code calls Docker or uses its socket. Startup/monitor failure closes
|
|
108
|
+
gateway ingress and fails package health; resource rollback/release belongs to
|
|
109
|
+
DSM/Container Manager. The guard cannot stop a misconfigured container or act
|
|
110
|
+
as a firewall: correct bindings must be established before deployment.
|
|
111
|
+
|
|
112
|
+
## Audiobookshelf native example
|
|
113
|
+
|
|
114
|
+
Use the existing native `daemon` route. A project-owned `absglue run` launches
|
|
115
|
+
the unmodified ABS binary, binds it to loopback with the supported host/base
|
|
116
|
+
path settings, configures OIDC through its API, and supervises it. The same
|
|
117
|
+
helper implements idempotent deletion. The example pins released SDK 0.13.0.
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
daemon:
|
|
121
|
+
exec: bin/absglue
|
|
122
|
+
targets: {x86_64: payload}
|
|
123
|
+
args: [run]
|
|
124
|
+
identity_gateway:
|
|
125
|
+
sdk: 0.13.0
|
|
126
|
+
mode: oidc
|
|
127
|
+
upstream: 13378
|
|
128
|
+
listen: {http: 13380, https: 13443}
|
|
129
|
+
redirects: {"/": /audiobookshelf/}
|
|
130
|
+
oidc:
|
|
131
|
+
issuer_port: 13390
|
|
132
|
+
clients:
|
|
133
|
+
- id: abs
|
|
134
|
+
redirect_paths: [/audiobookshelf/auth/openid/callback]
|
|
135
|
+
pkce: true
|
|
136
|
+
roles: {claim: groups, admin: admin, user: user}
|
|
137
|
+
account:
|
|
138
|
+
on_delete:
|
|
139
|
+
- exec: [bin/absglue, delete-user, "{name}"]
|
|
140
|
+
- remove: users/{uid}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Verify ABS's current `openidAutoLaunch`, auto-registration, username/sub
|
|
144
|
+
matching and `groups` mapping through its API. Test promotion and demotion at
|
|
145
|
+
sign-in. `delete-user` must handle sessions, feeds/shares and retention in the
|
|
146
|
+
correct order when those features are used; deleting a library does not imply
|
|
147
|
+
deleting audio files. A minimal fixture directory is not proof of full purge.
|
|
148
|
+
|
|
149
|
+
## Before calling it done
|
|
150
|
+
|
|
151
|
+
- Run `check --json`, `build --json`, and `inspect <final-spk> --json` with the
|
|
152
|
+
installed CLI. Run the affected CLI/SDK gates, not only a patched PoC.
|
|
153
|
+
- Use fresh ordinary/admin DSM browser sessions over each requested scheme;
|
|
154
|
+
prove zero-click sign-in, record the exact entry URL, stable subject and app
|
|
155
|
+
identity. Test signed-out/fake-identity requests. Do not log tokens, cookies,
|
|
156
|
+
assertions, URL queries or `/proc/*/cmdline`.
|
|
157
|
+
- Read back admin/user roles and a demotion after another sign-in. Mark the
|
|
158
|
+
accepted OpenList role limitation explicitly; never manufacture a pass.
|
|
159
|
+
- Prove two ordinary users cannot read each other's intended private objects,
|
|
160
|
+
including NULL-owner/shared-link behavior. Record what was actually tested.
|
|
161
|
+
- Create only task-owned test users/data. Delete a DSM user and observe the
|
|
162
|
+
automatic native REST/CLI delete **and** owned file removal; read gateway
|
|
163
|
+
pending status and app absence. Restart with a pending deletion to exercise
|
|
164
|
+
readiness/retry. Preserve shared data unless its removal is authorized.
|
|
165
|
+
- Disable a test user; show new SSO is blocked over both schemes. Probe the old
|
|
166
|
+
app token separately and report its actual lifetime/revocation behavior.
|
|
167
|
+
- Inspect actual upstream/issuer listeners, UID and `CapEff: 0`, including CGI
|
|
168
|
+
and every container. From a second LAN host, prove upstream/issuer cannot be
|
|
169
|
+
reached, with the gateway as a positive reachability control.
|
|
170
|
+
- Write package-owned data, upgrade the SPK, and read back the same data,
|
|
171
|
+
ownership and sign-in/deletion state. Test stop/start and failure health.
|
|
172
|
+
- Clean up only task-owned users, package/project/containers/images, private
|
|
173
|
+
fixtures and test resources. Read back absence. Keep DSM-retained package
|
|
174
|
+
identities unless their deletion was authorized, and report their names.
|
package/skills/reference/wrap.md
CHANGED
|
@@ -14,6 +14,10 @@ synopkgland check # 契約、Compose 意圖與 Web entry 提示
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
For DSM-account SSO around OSS, first read [identity-gateway.md](identity-gateway.md).
|
|
18
|
+
It defines the separate schema-2 `compose` + `identity_gateway` host-network policy;
|
|
19
|
+
the legacy Compose route below retains its restrictions.
|
|
20
|
+
|
|
17
21
|
## Compose 規則(analyzer 會擋)
|
|
18
22
|
|
|
19
23
|
- 禁止:`privileged`、`network_mode: host`、裝置節點、`cap_add`、掛載 Docker socket。
|