@synopackageland/cli 0.1.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/README.md +13 -0
- package/dist/attestation/app-package.d.ts +86 -0
- package/dist/attestation/app-package.js +199 -0
- package/dist/attestation/install-on-host.d.ts +14 -0
- package/dist/attestation/install-on-host.js +52 -0
- package/dist/attestation/keyring.d.ts +21 -0
- package/dist/attestation/keyring.js +168 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +14 -0
- package/dist/build-spk/dsm-arch.d.ts +4 -0
- package/dist/build-spk/dsm-arch.js +32 -0
- package/dist/build-spk/dsm-icons.d.ts +2 -0
- package/dist/build-spk/dsm-icons.js +67 -0
- package/dist/build-spk/generator.d.ts +43 -0
- package/dist/build-spk/generator.js +653 -0
- package/dist/build-spk/native-payload.d.ts +22 -0
- package/dist/build-spk/native-payload.js +654 -0
- package/dist/build-spk/reproducible-archive.d.ts +8 -0
- package/dist/build-spk/reproducible-archive.js +34 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +345 -0
- package/dist/compose/analyze.d.ts +25 -0
- package/dist/compose/analyze.js +346 -0
- package/dist/compose/env.d.ts +7 -0
- package/dist/compose/env.js +36 -0
- package/dist/compose/privilege.d.ts +14 -0
- package/dist/compose/privilege.js +102 -0
- package/dist/compose/runner.d.ts +32 -0
- package/dist/compose/runner.js +84 -0
- package/dist/contract/load.d.ts +25 -0
- package/dist/contract/load.js +87 -0
- package/dist/contract/schema-validate.d.ts +3 -0
- package/dist/contract/schema-validate.js +50 -0
- package/dist/contract/validate.d.ts +3 -0
- package/dist/contract/validate.js +80 -0
- package/dist/contract/web-launch.d.ts +19 -0
- package/dist/contract/web-launch.js +94 -0
- package/dist/deploy/catalog-publish.d.ts +3 -0
- package/dist/deploy/catalog-publish.js +7 -0
- package/dist/deploy/deploy.d.ts +22 -0
- package/dist/deploy/deploy.js +136 -0
- package/dist/deploy/identity-shared.d.ts +3 -0
- package/dist/deploy/identity-shared.js +14 -0
- package/dist/deploy/identity.d.ts +7 -0
- package/dist/deploy/identity.js +20 -0
- package/dist/deploy/package-source.d.ts +7 -0
- package/dist/deploy/package-source.js +17 -0
- package/dist/deploy/run-as-values.d.ts +18 -0
- package/dist/deploy/run-as-values.js +48 -0
- package/dist/deploy/share-values.d.ts +20 -0
- package/dist/deploy/share-values.js +60 -0
- package/dist/deploy/spk-info.d.ts +12 -0
- package/dist/deploy/spk-info.js +41 -0
- package/dist/deploy/spk-version.d.ts +4 -0
- package/dist/deploy/spk-version.js +64 -0
- package/dist/dev.d.ts +13 -0
- package/dist/dev.js +31 -0
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +22 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +18 -0
- package/dist/host/broker-paths.d.ts +1 -0
- package/dist/host/broker-paths.js +1 -0
- package/dist/host/catalog-install.d.ts +59 -0
- package/dist/host/catalog-install.js +326 -0
- package/dist/host/credentials.d.ts +2 -0
- package/dist/host/credentials.js +37 -0
- package/dist/host/digests.d.ts +5 -0
- package/dist/host/digests.js +55 -0
- package/dist/host/dsm-client.d.ts +6 -0
- package/dist/host/dsm-client.js +290 -0
- package/dist/host/dsm-http.d.ts +1 -0
- package/dist/host/dsm-http.js +87 -0
- package/dist/host/fake-nas.d.ts +16 -0
- package/dist/host/fake-nas.js +116 -0
- package/dist/host/open-url.d.ts +5 -0
- package/dist/host/open-url.js +13 -0
- package/dist/host/share-lookup.d.ts +3 -0
- package/dist/host/share-lookup.js +144 -0
- package/dist/host/types.d.ts +67 -0
- package/dist/host/types.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/info.d.ts +17 -0
- package/dist/info.js +34 -0
- package/dist/init.d.ts +4 -0
- package/dist/init.js +31 -0
- package/dist/inspect.d.ts +13 -0
- package/dist/inspect.js +102 -0
- package/dist/load-env.d.ts +1 -0
- package/dist/load-env.js +31 -0
- package/dist/log.d.ts +13 -0
- package/dist/log.js +20 -0
- package/dist/mock-broker/bootstrap.d.ts +2 -0
- package/dist/mock-broker/bootstrap.js +8 -0
- package/dist/mock-broker/files.d.ts +60 -0
- package/dist/mock-broker/files.js +408 -0
- package/dist/mock-broker/oidc.d.ts +31 -0
- package/dist/mock-broker/oidc.js +161 -0
- package/dist/mock-broker/path.d.ts +9 -0
- package/dist/mock-broker/path.js +50 -0
- package/dist/mock-broker/server.d.ts +19 -0
- package/dist/mock-broker/server.js +350 -0
- package/dist/publish/build-ledger.d.ts +22 -0
- package/dist/publish/build-ledger.js +26 -0
- package/dist/publish/community-path.d.ts +6 -0
- package/dist/publish/community-path.js +41 -0
- package/dist/publish/curated-release.d.ts +78 -0
- package/dist/publish/curated-release.js +693 -0
- package/dist/publish/file-build-ledger.d.ts +18 -0
- package/dist/publish/file-build-ledger.js +268 -0
- package/dist/publish/github-pr.d.ts +25 -0
- package/dist/publish/github-pr.js +85 -0
- package/dist/publish/github-repo.d.ts +8 -0
- package/dist/publish/github-repo.js +21 -0
- package/dist/publish/publish.d.ts +23 -0
- package/dist/publish/publish.js +128 -0
- package/dist/publish/sync-definition.d.ts +4 -0
- package/dist/publish/sync-definition.js +48 -0
- package/dist/skill.d.ts +10 -0
- package/dist/skill.js +50 -0
- package/dist/templates/compose-import/compose.yaml +11 -0
- package/dist/templates/compose-import/synology-app.yaml +15 -0
- package/dist/templates/hello-files/com.synology.hello.files.dev.spk +0 -0
- package/dist/templates/hello-files/compose.yaml +8 -0
- package/dist/templates/hello-files/synology-app.yaml +13 -0
- package/dist/templates/hello-web/compose.yaml +7 -0
- package/dist/templates/hello-web/html/app.js +23 -0
- package/dist/templates/hello-web/html/index.html +15 -0
- package/dist/templates/hello-web/html/sdk.js +68 -0
- package/dist/templates/hello-web/synology-app.yaml +13 -0
- package/dist/templates/hello-web-page/com.synology.hello.web.page.dev.spk +0 -0
- package/dist/templates/hello-web-page/compose.yaml +7 -0
- package/dist/templates/hello-web-page/html/app.js +23 -0
- package/dist/templates/hello-web-page/html/index.html +15 -0
- package/dist/templates/hello-web-page/html/sdk.js +68 -0
- package/dist/templates/hello-web-page/synology-app.yaml +13 -0
- package/dist/test-command.d.ts +13 -0
- package/dist/test-command.js +26 -0
- package/dist/test-host.d.ts +13 -0
- package/dist/test-host.js +24 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.js +1 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +103 -0
- package/package.json +38 -0
- package/schema/synology-app.schema.json +137 -0
- package/skills/SKILL.md +51 -0
- package/skills/reference/create.md +34 -0
- package/skills/reference/deploy.md +41 -0
- package/skills/reference/diagnose.md +42 -0
- package/skills/reference/files.md +37 -0
- package/skills/reference/identity.md +31 -0
- package/skills/reference/wrap.md +35 -0
- package/templates/compose-import/compose.yaml +11 -0
- package/templates/compose-import/synology-app.yaml +15 -0
- package/templates/hello-files/com.synology.hello.files.dev.spk +0 -0
- package/templates/hello-files/compose.yaml +8 -0
- package/templates/hello-files/synology-app.yaml +13 -0
- package/templates/hello-web/compose.yaml +7 -0
- package/templates/hello-web/html/app.js +23 -0
- package/templates/hello-web/html/index.html +15 -0
- package/templates/hello-web/html/sdk.js +68 -0
- package/templates/hello-web/synology-app.yaml +13 -0
- package/templates/hello-web-page/com.synology.hello.web.page.dev.spk +0 -0
- package/templates/hello-web-page/compose.yaml +7 -0
- package/templates/hello-web-page/html/app.js +23 -0
- package/templates/hello-web-page/html/index.html +15 -0
- package/templates/hello-web-page/html/sdk.js +68 -0
- package/templates/hello-web-page/synology-app.yaml +13 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://synology.dev/schemas/synology-app/1",
|
|
4
|
+
"title": "Synology App Contract",
|
|
5
|
+
"description": "App Contract schema version 1 for synology-app.yaml",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schema",
|
|
9
|
+
"package",
|
|
10
|
+
"version",
|
|
11
|
+
"displayname",
|
|
12
|
+
"description",
|
|
13
|
+
"maintainer",
|
|
14
|
+
"os_min_ver"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schema": {
|
|
18
|
+
"type": "integer",
|
|
19
|
+
"const": 1,
|
|
20
|
+
"description": "App Contract schema version; v1 only accepts integer 1"
|
|
21
|
+
},
|
|
22
|
+
"package": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"minLength": 1
|
|
25
|
+
},
|
|
26
|
+
"version": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"minLength": 1
|
|
29
|
+
},
|
|
30
|
+
"displayname": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"minLength": 1
|
|
33
|
+
},
|
|
34
|
+
"description": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1
|
|
37
|
+
},
|
|
38
|
+
"maintainer": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"minLength": 1
|
|
41
|
+
},
|
|
42
|
+
"os_min_ver": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"minLength": 1
|
|
45
|
+
},
|
|
46
|
+
"adminprotocol": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
"adminport": {
|
|
50
|
+
"type": "integer"
|
|
51
|
+
},
|
|
52
|
+
"adminurl": {
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
"install": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"additionalProperties": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"type": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"enum": ["string", "boolean", "secret", "shared_folder", "dsm_user"]
|
|
63
|
+
},
|
|
64
|
+
"default": {},
|
|
65
|
+
"generate": {
|
|
66
|
+
"type": "boolean"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"required": ["type"]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"integration": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"web": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"launch": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enum": ["dsm-window", "new-page"],
|
|
81
|
+
"description": "Web App Launch Mode. dsm-window opens a DSM Application Window at the Trusted App Entry; new-page uses Package Center Open. Omit for dsm-window. Headless apps omit this field."
|
|
82
|
+
},
|
|
83
|
+
"cookies": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "string"
|
|
87
|
+
},
|
|
88
|
+
"description": "Optional App cookie name allowlist forwarded by the generated Web Station ingress. Omit to forward no browser cookies."
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"additionalProperties": true
|
|
92
|
+
},
|
|
93
|
+
"identity": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"additionalProperties": false,
|
|
96
|
+
"required": ["mode"],
|
|
97
|
+
"properties": {
|
|
98
|
+
"mode": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"enum": ["broker-managed"],
|
|
101
|
+
"description": "v1 identity mode. SDK Apps omit oidc_compat; wrap-existing may add it."
|
|
102
|
+
},
|
|
103
|
+
"oidc_compat": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"additionalProperties": false,
|
|
106
|
+
"required": ["redirect_path"],
|
|
107
|
+
"properties": {
|
|
108
|
+
"redirect_path": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"pattern": "^/(?!/)(?!.*\\.\\.)[^\\s?#:]*$",
|
|
111
|
+
"description": "App-owned OIDC callback path. No scheme, host, query, or '..'."
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"additionalProperties": true
|
|
119
|
+
},
|
|
120
|
+
"permissions": {
|
|
121
|
+
"type": "object"
|
|
122
|
+
},
|
|
123
|
+
"privilege": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"additionalProperties": false,
|
|
126
|
+
"properties": {
|
|
127
|
+
"privileged": { "type": "boolean" },
|
|
128
|
+
"host_network": { "type": "boolean" },
|
|
129
|
+
"host_pid": { "type": "boolean" },
|
|
130
|
+
"host_ipc": { "type": "boolean" },
|
|
131
|
+
"devices": { "type": "boolean" },
|
|
132
|
+
"cap_add": { "type": "boolean" }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"additionalProperties": true
|
|
137
|
+
}
|
package/skills/SKILL.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: synopkgland
|
|
3
|
+
description: Agent Authoring for Syno Package Land — create, validate, and Inner Loop deploy a Personal App to the author's NAS with synopkgland CLI. Load when the user wants to build a DSM app from a brief, scaffold from templates, validate App Contract + Compose, deploy with Local Attestation, or diagnose analyzer/deploy/readiness failures. Not for Curated publish, SSH to NAS, or inventing platform fields.
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Syno Package Land Skill Pack(與 `@synopackageland/cli` 同版本、同 repo,不另發 npm)。
|
|
8
|
+
|
|
9
|
+
## 載入方式
|
|
10
|
+
|
|
11
|
+
1. 執行 `synopkgland skill path`,得到 Skill Pack 根目錄。
|
|
12
|
+
2. **永遠先讀**本檔 `SKILL.md`。
|
|
13
|
+
3. 依任務載入 `reference/*.md`(見下表)。不要一次讀完全部。
|
|
14
|
+
|
|
15
|
+
| 階段 | 檔案 | 何時 |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| 建立 | `reference/create.md` | 從零或一句話需求 |
|
|
18
|
+
| 封裝 | `reference/wrap.md` | 已有 `compose.yaml` 或要補契約 |
|
|
19
|
+
| 身分 | `reference/identity.md` | 需要 DSM 登入/Open |
|
|
20
|
+
| 檔案 | `reference/files.md` | 需要 NAS 檔案 |
|
|
21
|
+
| 部署 | `reference/deploy.md` | 要 `deploy` 到 NAS |
|
|
22
|
+
| 排查 | `reference/diagnose.md` | `validate`/`deploy`/Open 失敗 |
|
|
23
|
+
|
|
24
|
+
也可用 `synopkgland skill list`、`synopkgland skill show <id>` 在終端機檢視。
|
|
25
|
+
|
|
26
|
+
## 權威邊界(必守)
|
|
27
|
+
|
|
28
|
+
- **權威**:`synology-app.yaml` schema(`packages/app-cli/schema/synology-app.schema.json`)、`synopkgland validate`/analyzer 錯誤碼、repo 測試。Skill 只是操作介面。
|
|
29
|
+
- **禁止發明欄位**:不得新增 schema 沒有的 top-level 或 `install` 型別;不得自創第二套 Compose/SPK DSL。不確定時先 `synopkgland inspect` 或讀模板,再 `validate`。
|
|
30
|
+
- **禁止洩漏 secret**:不得把 DSM 密碼、signing key、API token 寫進 Git、`compose.yaml` environment、`.env` commit、或對話紀錄。NAS 憑證用 synoagentcli profile 或本機環境變數,由 CLI 讀取。
|
|
31
|
+
- **不要讀 ADR** 當預設步驟;需要決策時 skill 才指向規格。
|
|
32
|
+
- **不要宣稱** 15 分鐘黃金路徑已通過、公開 SDK 已 freeze、或 `inner-loop` 全綠。
|
|
33
|
+
|
|
34
|
+
## 典型流程
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
一句話需求
|
|
38
|
+
→ reference/create.md(選模板、最少問題)
|
|
39
|
+
→ synopkgland init -t hello-web|hello-web-page|hello-files|compose-import
|
|
40
|
+
→ synopkgland dev(本機 mock)
|
|
41
|
+
→ synopkgland validate
|
|
42
|
+
→ synopkgland test
|
|
43
|
+
→ reference/deploy.md → synopkgland deploy --host <nas>
|
|
44
|
+
→ synopkgland info --host <nas>;DSM Open
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
失敗時載入 `reference/diagnose.md`;依錯誤碼修契約或 Compose,**不要猜**。
|
|
48
|
+
|
|
49
|
+
## CLI 專案根
|
|
50
|
+
|
|
51
|
+
`synopkgland` 從 cwd 向上找 `synology-app.yaml`,或 `--project <dir>`。v1 不做 monorepo workspace 協定。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 建立(Create)
|
|
2
|
+
|
|
3
|
+
從一句話或空白目錄產出可跑專案。**最少問題**:只問 schema/模板無法推斷的意圖。
|
|
4
|
+
|
|
5
|
+
## 選模板(`synopkgland init -t`)
|
|
6
|
+
|
|
7
|
+
| 模板 | 何時 | 預設整合 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `hello-web` | 第一個「DSM 帳號打開」的 Web App(DSM 視窗) | 身分;`launch: dsm-window`;**不**開廣域 `files.*` |
|
|
10
|
+
| `hello-web-page` | Web App 以 Package Center Open 開新分頁 | 身分;`launch: new-page` |
|
|
11
|
+
| `hello-files` | 讀寫管理員選的 Shared Folder | 身分 + 必填 `install` shared_folder |
|
|
12
|
+
| `compose-import` | 已有 `compose.yaml` | 保留映像/upstream 的 container target;只把 host source 換成 `${PARAM}`;Share 安裝時選 |
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
synopkgland init -t hello-web [目錄]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
產物:`synology-app.yaml`、`compose.yaml`、README。需要程式碼時在同一專案加最小 Web App;v1 官方範例用 TypeScript + `@synopackageland/dev-sdk`。
|
|
19
|
+
|
|
20
|
+
包現成 Compose 時,先用 `inspect` 看原本的 volume mapping。容器路徑跟 Compose/映像的 upstream target 走,不要為了套平台範例改成 `/data`;只把 host source 換成 `${PARAM}`。原有的 upstream named volume 要保留在頂層 `volumes:`。需要既有 Share 時宣告 `install.<PARAM>.type: shared_folder`,管理員在安裝時選這台 NAS 的 Share。
|
|
21
|
+
|
|
22
|
+
## 只問這些(缺才問)
|
|
23
|
+
|
|
24
|
+
1. **Package Identity**(reverse-DNS,例如 `com.example.myapp`)— 將來上架同一條身分。
|
|
25
|
+
2. **顯示名稱**(`displayname`)。
|
|
26
|
+
3. **要不要碰 NAS 檔案** — 若要,預設走 Shared Folder(`hello-files`),不要默默加 `files.read`/`files.write`/`files.delete`(見 `files.md`)。
|
|
27
|
+
4. **已有 Compose 嗎** — 有則 `compose-import` + `inspect`。
|
|
28
|
+
|
|
29
|
+
不要問:SPK INFO、wizard、DDNS、port forwarding、Community PR。
|
|
30
|
+
|
|
31
|
+
## 下一步
|
|
32
|
+
|
|
33
|
+
- 本機:`synopkgland dev` → `validate` → `test`
|
|
34
|
+
- 要上 NAS:讀 `deploy.md`
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 部署(Deploy)
|
|
2
|
+
|
|
3
|
+
Inner Loop:thin SPK、NAS Administrator DSM 授權、Local Attestation、經 Package Center 安裝。**不要 SSH**,不要把 Compose 貼進 Container Manager 手工裝。
|
|
4
|
+
|
|
5
|
+
## 前置
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
synopkgland validate
|
|
9
|
+
synopkgland test
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
NAS 需已安裝 **SynoPkgLandService**(Broker)與 Container Manager。CLI 憑證:`synoagentcli` profile 或 `DSM_URL`/`DSM_PASSWORD`(**勿寫進 Git 或 chat**)。
|
|
13
|
+
|
|
14
|
+
## 正式 vs 開發 Instance(UF-2/D3/D4)
|
|
15
|
+
|
|
16
|
+
| 情況 | 做法 |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| 第一次裝這個 `package` | `synopkgland deploy --host <nas>` |
|
|
19
|
+
| 已裝**正式** Instance,要邊改邊玩、不影響使用者 | **`synopkgland deploy --host <nas> --dev`**(`{package}.dev`) |
|
|
20
|
+
| NAS 上該 `package` 已是 **Curated**(App Package Attestation) | 預設 **`deploy` 會拒絕**(`CURATED_DEPLOY_REFUSED`)→ 建議 `--dev`;**不要**預設「取代商店版」 |
|
|
21
|
+
| 必須用本機版覆蓋 Curated 正式版 | 僅在 NAS Administrator **明示危險確認**後:`--replace-curated`(證明降回 Local) |
|
|
22
|
+
|
|
23
|
+
**規則**:若正式 Instance 已存在,skill **必須建議 `.dev`**,不得默默對同一 Package Identity 執行 `deploy`。
|
|
24
|
+
|
|
25
|
+
## 命令
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
synopkgland deploy --host <nas> # 正式 package
|
|
29
|
+
synopkgland deploy --host <nas> --dev # 開發 package(.dev)
|
|
30
|
+
synopkgland deploy --host <nas> --replace-curated # 危險:Curated 降級,需人確認
|
|
31
|
+
|
|
32
|
+
synopkgland info --host <nas> [--dev]
|
|
33
|
+
synopkgland log --host <nas> [--dev]
|
|
34
|
+
synopkgland test --host <nas> [--dev]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
成功後:DSM 按 Open;`info` 的 Open URL。已登入 DSM 不應再要平台密碼。
|
|
38
|
+
|
|
39
|
+
## 未宣稱通過
|
|
40
|
+
|
|
41
|
+
D3/D4/D12 實機與 15 分鐘黃金路徑仍可能失敗;失敗走 `diagnose.md`,不要宣稱產品已全綠。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 排查(Diagnose)
|
|
2
|
+
|
|
3
|
+
依 **穩定錯誤碼** 行動;權威是 `synopkgland validate` 輸出、`deploy`/Broker 回傳、`info`/`log` 的 readiness。不要猜 Compose。
|
|
4
|
+
|
|
5
|
+
## validate/analyzer(修契約或 Compose)
|
|
6
|
+
|
|
7
|
+
| 錯誤碼 | 下一步 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `CONTRACT_SCHEMA_INVALID`/`CONTRACT_MISSING_FIELD` | 對照 schema 與模板;補必填欄位,刪除非法鍵 |
|
|
10
|
+
| `CONTRACT_SCHEMA_UNSUPPORTED` | `schema` 必須為整數 `1` |
|
|
11
|
+
| `CONTRACT_INVALID_STRING`/`CONTRACT_ADMINPORT_INVALID` | 修正字串或 `adminport` 型別 |
|
|
12
|
+
| `INSTALL_STRING_*`/`INSTALL_PARAMETER_UNREFERENCED` | 修正 install 預設值;在 compose 加 `${參數名}` |
|
|
13
|
+
| `COMPOSE_MISSING` | 建立 `compose.yaml` |
|
|
14
|
+
| `COMPOSE_PRIVILEGED_FORBIDDEN` 等安全碼 | 移除 privileged、host 網路、socket 掛載等 |
|
|
15
|
+
| `COMPOSE_SERVICE_NOT_FOUND` | 修正 `adminport` 對應的 service |
|
|
16
|
+
| `CONTRACT_ADMINPORT_UNMAPPED` | 在 compose 發布與 `adminport` 相同的 host port |
|
|
17
|
+
|
|
18
|
+
流程:改檔 → `synopkgland validate` 直到通過。
|
|
19
|
+
|
|
20
|
+
## deploy
|
|
21
|
+
|
|
22
|
+
| 碼/訊息 | 下一步 |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `VALIDATION_FAILED` | 先 `validate` |
|
|
25
|
+
| `CURATED_DEPLOY_REFUSED` | 用 `--dev` 並行開發,或經管理員確認後 `--replace-curated` |
|
|
26
|
+
| `INNER_LOOP_API_MISSING` | 升級 NAS 上 SynoPkgLandService(>=0.1.0-0016);確認 CGI 非 HTML 404 |
|
|
27
|
+
| `ATTESTATION_FAILED` | 讀 Broker 訊息;常見 digest/身分衝突,必要時 `info --host` 看 attestation |
|
|
28
|
+
| `BUILD_SPK_FAILED` | 修契約/compose 後重試 |
|
|
29
|
+
| `CATALOG_PUBLISH_FAILED`/`CATALOG_INSTALL_FAILED` | Package Source/網路;`info` 看是否已裝;勿手動拼 SPK 上架 |
|
|
30
|
+
|
|
31
|
+
## info/log/test --host
|
|
32
|
+
|
|
33
|
+
| 觀察 | 下一步 |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `NOT_INSTALLED`/log 說先 deploy | `deploy`(或 `--dev`) |
|
|
36
|
+
| Readiness 非 RUNNING | `log --host` 看容器;Container Manager 深連結(勿假設使用者懂 Project) |
|
|
37
|
+
| `test --host` Open smoke 失敗 | `info` 的 `openUrl`、registration port;identity 任務見 broker 日誌 |
|
|
38
|
+
| Attestation 為 Curated 但要改本機 | 不要預設覆蓋;`--dev` 或顯式 `--replace-curated` |
|
|
39
|
+
|
|
40
|
+
## 支援 bundle
|
|
41
|
+
|
|
42
|
+
`synopkgland log --host` 結尾 diagnostics 已遮罩 secret;可複製給人類或 agent,**不要再貼 DSM 密碼**。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 檔案(Files)
|
|
2
|
+
|
|
3
|
+
## 預設(私人 App)
|
|
4
|
+
|
|
5
|
+
Personal App **預設較窄**:
|
|
6
|
+
|
|
7
|
+
1. Compose 管理的 App Data(`${SYNOPKG_PKGVAR}` 等平台路徑)。
|
|
8
|
+
2. 管理員在安裝時用 Package Center 原生 Share 選擇器,從**這台 NAS**挑 **Shared Folder Mount**(`install.<NAME>.type: shared_folder`)。有 Share 時還要選一個 DSM 使用者當 **Container Run-as**(`install.<NAME>.type: dsm_user`)。容器路徑跟 Compose/映像的 upstream target 走;包現成映像時尤其不要改成平台範例的 `/data`,也不要把 upstream 的 `user: "0:0"` 留著去寫 Share。
|
|
9
|
+
|
|
10
|
+
### 包現成 Compose
|
|
11
|
+
|
|
12
|
+
- volume mapping 右側的 container target 與 `ro`/`rw` 是映像/upstream 的契約,保留原值;只把左側 host source 換成 `${NAME}`,並在 App Contract 宣告對應的 `shared_folder` 槽。
|
|
13
|
+
- 原有 named volume 的 mount 與頂層 `volumes:` 宣告都保留。需要兩個既有 Share 就宣告兩個 `shared_folder` 槽;管理員在安裝時各選一個這台 NAS 的 Share。
|
|
14
|
+
|
|
15
|
+
需要列出/讀寫這台 NAS 上登入者能看到的各個 Share 時,宣告 `files.*` 並走 File Gateway,不要掃 `/volume*`。
|
|
16
|
+
|
|
17
|
+
`hello-web` **不**宣告廣域檔案 scope。`hello-files` 只宣告 Shared Folder。
|
|
18
|
+
|
|
19
|
+
## 廣域 `files.read`/`files.write`/`files.delete`
|
|
20
|
+
|
|
21
|
+
僅在使用者**明確要求**且理解風險時才加入;必須:
|
|
22
|
+
|
|
23
|
+
1. 在 App Contract 明示(`integration`/權限模型依模板與 `validate`)。
|
|
24
|
+
2. 用**人話**向 NAS Administrator 說明範圍(可讀寫哪些邏輯路徑、與 Shared Folder 的差異)。
|
|
25
|
+
3. 取得同意後才 `deploy`;**不得**由模板或 skill 默默打開。
|
|
26
|
+
|
|
27
|
+
安裝 wizard 會顯示同意文案;agent 在對話中也要用同樣語氣確認一次。
|
|
28
|
+
|
|
29
|
+
## 開發
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
synopkgland dev --files-root <本機目錄> # 可選:對應假檔案樹
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 下一步
|
|
36
|
+
|
|
37
|
+
契約定案後 → `validate` → `deploy.md`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 身分(Identity)
|
|
2
|
+
|
|
3
|
+
Personal App 用 **broker-managed** 身分:已登入 DSM 的使用者 Open 時不再輸入另一組平台密碼。
|
|
4
|
+
|
|
5
|
+
## 開發
|
|
6
|
+
|
|
7
|
+
- `synopkgland dev` 啟動 mock Broker;`requireLogin` 等 API 與 NAS 同契約,但 User Context **不能**在 NAS 上使用。
|
|
8
|
+
- 範例模板:`hello-web` 已含最小 Browser/Server SDK 整合。
|
|
9
|
+
|
|
10
|
+
## 部署後驗證
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
synopkgland info --host <nas> [--dev]
|
|
14
|
+
synopkgland test --host <nas> [--dev]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`info` 會顯示 attestation 種類、Open URL、readiness。
|
|
18
|
+
|
|
19
|
+
## Package Identity 與 Instance
|
|
20
|
+
|
|
21
|
+
- **正式**:`synology-app.yaml` 的 `package`(例如 `com.example.hello-web`)。
|
|
22
|
+
- **開發用**:`{package}.dev`(`synopkgland deploy --dev`);displayname 加「(開發中)」;不得進 Community Repository。
|
|
23
|
+
|
|
24
|
+
若 NAS 上**已有正式 Instance**(Local 或 Curated),邊改邊玩應走 `.dev`,見 `deploy.md`。
|
|
25
|
+
|
|
26
|
+
## 禁止
|
|
27
|
+
|
|
28
|
+
- 不要把 DSM 密碼、session cookie、signing key 寫進 repo 或 chat。
|
|
29
|
+
- 不要為了繞過登入而在 Compose 硬編碼長期 token。
|
|
30
|
+
- 不要把 App 註冊成 Synology SSO Server 的 application;wrap 的 OIDC 只接 Broker facade(`oidc_compat`),見規格 identity §2.3。
|
|
31
|
+
- 不要以為 OIDC 登入成功=DSM 檔案 ACL。
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 封裝(Wrap)
|
|
2
|
+
|
|
3
|
+
已有 Compose 或要補齊 App Contract 時使用。權威仍是 schema + `validate`。
|
|
4
|
+
|
|
5
|
+
## 命令
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
synopkgland inspect # Compose 意圖、Web entry 提示
|
|
9
|
+
synopkgland validate # 契約 + Compose 安全與交叉引用
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 契約允許什麼(v1 schema 1)
|
|
13
|
+
|
|
14
|
+
必填:`schema`、`package`、`version`、`displayname`、`description`、`maintainer`、`os_min_ver`。
|
|
15
|
+
|
|
16
|
+
常見可選:`adminprotocol`、`adminport`、`adminurl`、`integration.web.launch`(`dsm-window` 預設|`new-page`)、`install`(參數型別僅 `string`|`boolean`|`secret`|`shared_folder`|`dsm_user`)、`integration`、`permissions`。有 `shared_folder` 時必須有恰好一個 `dsm_user`,Compose 用 `${NAME_UID}:${NAME_GID}`,不要寫死 `0:0`。wrap 若只能 OIDC:`integration.identity.oidc_compat.redirect_path` 填 App 自己的 callback path,Compose 引用 `${SYNOPKGLAND_OIDC_ISSUER}`/`${SYNOPKGLAND_OIDC_CLIENT_ID}`,secret 讀檔 `SYNOPKGLAND_OIDC_CLIENT_SECRET`。**不要**在契約寫 issuer/domain/env 改名表。
|
|
17
|
+
|
|
18
|
+
**禁止發明** schema 沒有的 top-level 鍵或 `install` 型別。`integration` 內容以模板與 `validate` 為準,不要自創 Broker DSL。
|
|
19
|
+
|
|
20
|
+
## Compose 規則(analyzer 會擋)
|
|
21
|
+
|
|
22
|
+
- 禁止:`privileged`、`network_mode: host`、裝置節點、`cap_add`、掛載 Docker socket。
|
|
23
|
+
- `adminport` 必須對應到某 service 的 published TCP port。
|
|
24
|
+
- `install` 參數須在 `compose.yaml` 以 `${NAME}` 引用。
|
|
25
|
+
|
|
26
|
+
## 包現成 Compose 的掛載
|
|
27
|
+
|
|
28
|
+
容器路徑跟映像/upstream 的 container target 走,原樣保留;只把 host source 換成 `${NAME}` 這類 install parameter。原有的 upstream named volume mount 與頂層 `volumes:` 宣告也保留,不要為了套模板改成 `/data`。需要既有 Share 時,管理員在安裝時選這台 NAS 的 Share。
|
|
29
|
+
|
|
30
|
+
修復方式:看 `validate` 輸出的 `[錯誤碼]` 與「建議」行,對照 `diagnose.md`。**不要**為了過驗證而亂改 runtime 語意。
|
|
31
|
+
|
|
32
|
+
## 下一步
|
|
33
|
+
|
|
34
|
+
- 要身分/檔案整合 → `identity.md`/`files.md`
|
|
35
|
+
- 要上 NAS → `deploy.md`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
schema: 1
|
|
2
|
+
package: com.synology.compose.import
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
displayname: Compose Import
|
|
5
|
+
description: Start from an existing Compose file and select two Shared Folders at install time.
|
|
6
|
+
maintainer: Syno Package Land
|
|
7
|
+
os_min_ver: "7.2-69057"
|
|
8
|
+
|
|
9
|
+
install:
|
|
10
|
+
MEDIA_SHARE:
|
|
11
|
+
type: shared_folder
|
|
12
|
+
BACKUP_SHARE:
|
|
13
|
+
type: shared_folder
|
|
14
|
+
RUN_AS:
|
|
15
|
+
type: dsm_user
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
schema: 1
|
|
2
|
+
package: com.synology.hello.files
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
displayname: Hello Files
|
|
5
|
+
description: A minimal headless app with a Shared Folder mount.
|
|
6
|
+
maintainer: Syno Package Land
|
|
7
|
+
os_min_ver: "7.2-69057"
|
|
8
|
+
|
|
9
|
+
install:
|
|
10
|
+
MEDIA_SHARE:
|
|
11
|
+
type: shared_folder
|
|
12
|
+
RUN_AS:
|
|
13
|
+
type: dsm_user
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createSynologyClient } from "./sdk.js";
|
|
2
|
+
|
|
3
|
+
const status = document.getElementById("status");
|
|
4
|
+
const userEl = document.getElementById("user");
|
|
5
|
+
|
|
6
|
+
const client = createSynologyClient();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
await client.auth.requireLogin();
|
|
10
|
+
const user = await client.auth.getCurrentUser();
|
|
11
|
+
if (status) {
|
|
12
|
+
status.textContent = "Signed in with DSM";
|
|
13
|
+
}
|
|
14
|
+
if (userEl) {
|
|
15
|
+
userEl.textContent = user.username;
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
if (status) {
|
|
21
|
+
status.textContent = code === "UNAUTHENTICATED" ? "DSM login required" : message;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Hello Web</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Hello Web</h1>
|
|
10
|
+
<p id="status">Opening with your DSM session…</p>
|
|
11
|
+
<p id="user"></p>
|
|
12
|
+
<script src="./bootstrap.js"></script>
|
|
13
|
+
<script type="module" src="./app.js"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function createSynologyClient(options = {}) {
|
|
2
|
+
const bootstrap =
|
|
3
|
+
options.bootstrap ??
|
|
4
|
+
globalThis.__SYNOLOGY_BOOTSTRAP__ ??
|
|
5
|
+
(options.brokerBaseUrl
|
|
6
|
+
? {
|
|
7
|
+
brokerBaseUrl: options.brokerBaseUrl,
|
|
8
|
+
jwksUrl: `${String(options.brokerBaseUrl).replace(/\/$/, "")}/.well-known/jwks.json`,
|
|
9
|
+
packageId: "com.synology.hello.web",
|
|
10
|
+
}
|
|
11
|
+
: undefined);
|
|
12
|
+
if (!bootstrap?.brokerBaseUrl) {
|
|
13
|
+
throw new Error("缺少 Platform SDK bootstrap。");
|
|
14
|
+
}
|
|
15
|
+
const apiBase = `${String(bootstrap.brokerBaseUrl).replace(/\/$/, "")}/api/v1`;
|
|
16
|
+
let accessToken = null;
|
|
17
|
+
|
|
18
|
+
async function parseJson(response) {
|
|
19
|
+
const text = await response.text();
|
|
20
|
+
const body = text ? JSON.parse(text) : {};
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
const error = body.error ?? {};
|
|
23
|
+
const err = new Error(error.message ?? `Broker request failed with status ${response.status}`);
|
|
24
|
+
err.code = error.code;
|
|
25
|
+
err.details = error.details;
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
return body;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
auth: {
|
|
33
|
+
async requireLogin() {
|
|
34
|
+
const result = await parseJson(
|
|
35
|
+
await fetch(`${apiBase}/auth/login`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "content-type": "application/json" },
|
|
38
|
+
credentials: "include",
|
|
39
|
+
body: JSON.stringify({ packageId: bootstrap.packageId }),
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
accessToken = result.accessToken;
|
|
43
|
+
return result.user;
|
|
44
|
+
},
|
|
45
|
+
async getSession() {
|
|
46
|
+
if (!accessToken) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const result = await parseJson(
|
|
50
|
+
await fetch(`${apiBase}/session`, {
|
|
51
|
+
headers: { authorization: `Bearer ${accessToken}` },
|
|
52
|
+
credentials: "include",
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
return result.user;
|
|
56
|
+
},
|
|
57
|
+
async getCurrentUser() {
|
|
58
|
+
const session = await this.getSession();
|
|
59
|
+
if (!session) {
|
|
60
|
+
const err = new Error("尚未建立有效身份。");
|
|
61
|
+
err.code = "UNAUTHENTICATED";
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
return session;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
schema: 1
|
|
2
|
+
package: com.synology.hello.web
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
displayname: Hello Web
|
|
5
|
+
description: A minimal Syno Package Land web app template.
|
|
6
|
+
maintainer: Syno Package Land
|
|
7
|
+
os_min_ver: "7.2-69057"
|
|
8
|
+
adminprotocol: http
|
|
9
|
+
adminport: 8080
|
|
10
|
+
adminurl: /
|
|
11
|
+
integration:
|
|
12
|
+
web:
|
|
13
|
+
launch: dsm-window
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createSynologyClient } from "./sdk.js";
|
|
2
|
+
|
|
3
|
+
const status = document.getElementById("status");
|
|
4
|
+
const userEl = document.getElementById("user");
|
|
5
|
+
|
|
6
|
+
const client = createSynologyClient();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
await client.auth.requireLogin();
|
|
10
|
+
const user = await client.auth.getCurrentUser();
|
|
11
|
+
if (status) {
|
|
12
|
+
status.textContent = "Signed in with DSM";
|
|
13
|
+
}
|
|
14
|
+
if (userEl) {
|
|
15
|
+
userEl.textContent = user.username;
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
if (status) {
|
|
21
|
+
status.textContent = code === "UNAUTHENTICATED" ? "DSM login required" : message;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Hello Web Page</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Hello Web Page</h1>
|
|
10
|
+
<p id="status">Opening with your DSM session…</p>
|
|
11
|
+
<p id="user"></p>
|
|
12
|
+
<script src="./bootstrap.js"></script>
|
|
13
|
+
<script type="module" src="./app.js"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|