@synopackageland/cli 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/attestation/app-package.js +8 -1
  2. package/dist/attestation/platform-service.d.ts +70 -0
  3. package/dist/attestation/platform-service.js +250 -0
  4. package/dist/bin.js +0 -0
  5. package/dist/build-spk/generator.js +202 -10
  6. package/dist/build-spk/resource-worker.d.ts +14 -0
  7. package/dist/build-spk/resource-worker.js +140 -0
  8. package/dist/cli.js +69 -10
  9. package/dist/compose/analyze.d.ts +41 -1
  10. package/dist/compose/analyze.js +458 -18
  11. package/dist/contract/load.d.ts +1 -0
  12. package/dist/contract/load.js +1 -1
  13. package/dist/contract/posix-all-shares.d.ts +16 -0
  14. package/dist/contract/posix-all-shares.js +63 -0
  15. package/dist/contract/schema-validate.js +26 -1
  16. package/dist/contract/validate.js +57 -0
  17. package/dist/contract/web-launch.d.ts +6 -0
  18. package/dist/contract/web-launch.js +25 -0
  19. package/dist/deploy/deploy.d.ts +1 -0
  20. package/dist/deploy/deploy.js +37 -2
  21. package/dist/deploy/homes-values.d.ts +19 -0
  22. package/dist/deploy/homes-values.js +29 -0
  23. package/dist/deploy/param-values.d.ts +21 -0
  24. package/dist/deploy/param-values.js +51 -0
  25. package/dist/deploy/spk-version.d.ts +5 -0
  26. package/dist/deploy/spk-version.js +50 -18
  27. package/dist/doctor.d.ts +22 -0
  28. package/dist/doctor.js +594 -0
  29. package/dist/error-collector.d.ts +2 -0
  30. package/dist/error-collector.js +46 -0
  31. package/dist/error-registry.d.ts +7 -0
  32. package/dist/error-registry.js +827 -0
  33. package/dist/host/broker-upgrade-preflight.d.ts +61 -0
  34. package/dist/host/broker-upgrade-preflight.js +156 -0
  35. package/dist/host/catalog-install.d.ts +12 -0
  36. package/dist/host/catalog-install.js +83 -17
  37. package/dist/host/credentials.js +35 -5
  38. package/dist/host/dsm-client.d.ts +12 -0
  39. package/dist/host/dsm-client.js +56 -1
  40. package/dist/host/dsm-compound.d.ts +37 -0
  41. package/dist/host/dsm-compound.js +120 -0
  42. package/dist/host/fake-nas.d.ts +1 -0
  43. package/dist/host/fake-nas.js +3 -0
  44. package/dist/host/types.d.ts +4 -0
  45. package/dist/publish/curated-release.js +3 -3
  46. package/dist/publish/platform-service-release.d.ts +60 -0
  47. package/dist/publish/platform-service-release.js +333 -0
  48. package/dist/publish/preflight.js +14 -1
  49. package/dist/release-bin.js +5 -1
  50. package/dist/templates/hello-files/compose.yaml +2 -0
  51. package/dist/templates/hello-web/compose.yaml +14 -3
  52. package/dist/templates/hello-web/synology-app.yaml +1 -1
  53. package/dist/templates/hello-web-page/compose.yaml +14 -3
  54. package/dist/templates/hello-web-page/synology-app.yaml +1 -1
  55. package/dist/test-host.d.ts +2 -0
  56. package/dist/test-host.js +15 -2
  57. package/dist/types.d.ts +1 -1
  58. package/dist/validate.js +14 -3
  59. package/package.json +2 -2
  60. package/schema/doctor.schema.json +38 -0
  61. package/schema/synology-app.schema.json +1 -1
  62. package/skills/reference/create.md +19 -0
  63. package/skills/reference/diagnose.md +35 -2
  64. package/skills/reference/files.md +10 -0
  65. package/templates/hello-files/compose.yaml +2 -0
  66. package/templates/hello-web/compose.yaml +14 -3
  67. package/templates/hello-web/synology-app.yaml +1 -1
  68. package/templates/hello-web-page/compose.yaml +14 -3
  69. package/templates/hello-web-page/synology-app.yaml +1 -1
@@ -1,7 +1,18 @@
1
1
  services:
2
2
  web:
3
- image: nginx:alpine
3
+ image: busybox:1.36
4
+ # 兩個平台限制,改動前先看 docs/impl/STATUS.md 的 2026-08-24 段:
5
+ # 1) command/entrypoint 陣列不得有純數字元素(例如 "18333")。DSM 的
6
+ # docker-project worker 會在 YAML round-trip 時掉引號、把它變成整數,
7
+ # postinst acquire 直接失敗(error 276 → 套件 broken/150,且 worker_msg
8
+ # 為空,開發者看不到原因)。所以 port 要包在單一字串裡。
9
+ # `synopkgland validate` 已會擋這個寫法。
10
+ # 2) 預設不用 8080:那個 port 在真實 NAS 上很常被既有容器占用,而 port
11
+ # 衝突同樣以那個看不出原因的 broken/150 呈現。
12
+ # 官方 nginx image 也不能用:resource worker 注入任意 package UID/GID,
13
+ # nginx entrypoint 啟動階段需要 root,會在該身分下直接退出。
14
+ command: ["sh","-c","httpd -f -p 18333 -h /usr/share/nginx/html"]
4
15
  ports:
5
- - "8080:80"
16
+ - "18333:18333"
6
17
  volumes:
7
- - "${SYNOPKG_PKGVAR}/html:/usr/share/nginx/html:ro"
18
+ - "./html:/usr/share/nginx/html:ro"
@@ -6,7 +6,7 @@ description: A Syno Package Land web app template that opens in a new browser pa
6
6
  maintainer: Syno Package Land
7
7
  os_min_ver: "7.2-69057"
8
8
  adminprotocol: http
9
- adminport: 8080
9
+ adminport: 18333
10
10
  adminurl: /
11
11
  integration:
12
12
  web:
@@ -8,6 +8,8 @@ export interface TestProjectOnHostOptions {
8
8
  }
9
9
  export interface TestProjectOnHostResult {
10
10
  ok: boolean;
11
+ code?: string;
11
12
  message?: string;
13
+ suggestion?: string;
12
14
  }
13
15
  export declare function testProjectOnHost(options: TestProjectOnHostOptions): Promise<TestProjectOnHostResult>;
package/dist/test-host.js CHANGED
@@ -3,14 +3,22 @@ import { resolveDeployIdentity } from "./deploy/identity.js";
3
3
  export async function testProjectOnHost(options) {
4
4
  const validation = validateProject(options.project);
5
5
  if (!validation.ok) {
6
- return { ok: false, message: validation.errors.map((e) => e.code).join(", ") };
6
+ const firstCode = validation.errors[0]?.code ?? "VALIDATION_FAILED";
7
+ return {
8
+ ok: false,
9
+ code: firstCode,
10
+ message: validation.errors.map((e) => e.code).join(", "),
11
+ suggestion: "請修正合約與 Compose 錯誤。",
12
+ };
7
13
  }
8
14
  const identity = resolveDeployIdentity(options.project, { dev: options.dev });
9
15
  const installed = await options.host.isPackageInstalled(identity.packageId);
10
16
  if (!installed) {
11
17
  return {
12
18
  ok: false,
19
+ code: "NOT_INSTALLED",
13
20
  message: `尚未部署到 ${options.host.dsmOrigin}。下一步:synopkgland deploy --host <nas>${options.dev ? " --dev" : ""}`,
21
+ suggestion: `請執行 synopkgland deploy --host <nas>${options.dev ? " --dev" : ""} 先行部署。`,
14
22
  };
15
23
  }
16
24
  const smoke = await options.host.smokeRequireLogin({
@@ -18,7 +26,12 @@ export async function testProjectOnHost(options) {
18
26
  cookieHeader: options.cookieHeader ?? "id=fake; did=fake",
19
27
  });
20
28
  if (!smoke.ok) {
21
- return { ok: false, message: "Open smoke 失敗:requireLogin 未取得 DSM 使用者。" };
29
+ return {
30
+ ok: false,
31
+ code: "TEST_HOST_FAILED",
32
+ message: "Open smoke 失敗:requireLogin 未取得 DSM 使用者。",
33
+ suggestion: "請確認應用程式前端已正確初始化 SDK 並登入 DSM。",
34
+ };
22
35
  }
23
36
  return { ok: true };
24
37
  }
package/dist/types.d.ts CHANGED
@@ -33,7 +33,7 @@ export interface AppContract {
33
33
  [key: string]: unknown;
34
34
  }
35
35
  export interface InstallParameter {
36
- type?: string;
36
+ type?: "string" | "boolean" | "secret" | "shared_folder" | "dsm_user" | "homes" | string;
37
37
  default?: unknown;
38
38
  generate?: boolean;
39
39
  }
package/dist/validate.js CHANGED
@@ -2,11 +2,12 @@ import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { collectInstallParameters, loadContract } from "./contract/load.js";
4
4
  import { validateContract } from "./contract/validate.js";
5
- import { collectComposeInterpolations, collectVolumeSourceInterpolations, loadCompose, validateAdminPortMapping, validateComposeHostBinds, validateComposeRunAs, validateComposeSecurity, } from "./compose/analyze.js";
5
+ import { collectComposeInterpolations, collectVolumeSourceInterpolations, loadCompose, validateAdminPortMapping, validateComposeHostBinds, validateComposeNumericCommandArgs, validateComposeRunAs, validateComposeSecurity, } from "./compose/analyze.js";
6
6
  import { loadNative } from "./native/load.js";
7
7
  import { validateNative } from "./native/validate.js";
8
8
  import { COMPOSE_FILENAME, CONTRACT_FILENAME, NATIVE_FILENAME, } from "./discovery.js";
9
9
  import { declaredPrivilegeFlags } from "./compose/privilege.js";
10
+ import { resourceWorkerEligibility } from "./build-spk/resource-worker.js";
10
11
  export function validateProject(project, options = {}) {
11
12
  const profile = options.profile ?? "personal";
12
13
  const errors = [];
@@ -61,11 +62,16 @@ export function validateProject(project, options = {}) {
61
62
  profile,
62
63
  declaredPrivilege: declaredPrivilegeFlags(parsed.contract),
63
64
  }));
65
+ if (resourceWorkerEligibility(parsed.contract, parsedCompose.compose).eligible) {
66
+ errors.push(...validateComposeNumericCommandArgs(parsedCompose.compose, composeFile));
67
+ }
64
68
  const installParams = collectInstallParameters(parsed.contract);
65
69
  const sharedFolderParams = new Set(installParams.filter(({ param }) => param.type === "shared_folder").map(({ name }) => name));
70
+ const homesParams = new Set(installParams.filter(({ param }) => param.type === "homes").map(({ name }) => name));
66
71
  const installParamNames = new Set(installParams.map(({ name }) => name));
67
72
  errors.push(...validateComposeHostBinds(parsedCompose.compose, composeFile, {
68
73
  sharedFolderParams,
74
+ homesParams,
69
75
  installParamNames,
70
76
  }));
71
77
  if (typeof parsed.contract.adminport === "number") {
@@ -96,8 +102,13 @@ export function validateProject(project, options = {}) {
96
102
  ? { ...item, severity: (profile === "listing" ? "error" : "warning") }
97
103
  : item));
98
104
  for (const { name, param } of installParams) {
99
- const referenced = param.type === "shared_folder" ? volumeSourceVars.has(name) : interpolations.has(name);
100
- if ((param.type === "string" || param.type === "boolean" || param.type === "shared_folder") &&
105
+ const referenced = param.type === "shared_folder" || param.type === "homes"
106
+ ? volumeSourceVars.has(name)
107
+ : interpolations.has(name);
108
+ if ((param.type === "string" ||
109
+ param.type === "boolean" ||
110
+ param.type === "shared_folder" ||
111
+ param.type === "homes") &&
101
112
  !referenced) {
102
113
  errors.push({
103
114
  code: "INSTALL_PARAMETER_UNREFERENCED",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@synopackageland/dev-sdk": "^0.2.0",
23
- "@synopackageland/package-source": "^0.2.0",
23
+ "@synopackageland/package-source": "^0.2.1",
24
24
  "ajv": "^8.17.1",
25
25
  "commander": "^13.1.0",
26
26
  "jose": "^6.0.10",
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "SynoPkgLandDoctorReport",
4
+ "type": "object",
5
+ "required": ["ok", "checks"],
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "ok": {
9
+ "type": "boolean"
10
+ },
11
+ "checks": {
12
+ "type": "array",
13
+ "items": {
14
+ "type": "object",
15
+ "required": ["id", "status", "message"],
16
+ "additionalProperties": false,
17
+ "properties": {
18
+ "id": {
19
+ "type": "string"
20
+ },
21
+ "status": {
22
+ "type": "string",
23
+ "enum": ["pass", "fail", "skip"]
24
+ },
25
+ "code": {
26
+ "type": "string"
27
+ },
28
+ "message": {
29
+ "type": "string"
30
+ },
31
+ "suggestion": {
32
+ "type": "string"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
@@ -59,7 +59,7 @@
59
59
  "properties": {
60
60
  "type": {
61
61
  "type": "string",
62
- "enum": ["string", "boolean", "secret", "shared_folder", "dsm_user"]
62
+ "enum": ["string", "boolean", "secret", "shared_folder", "dsm_user", "homes"]
63
63
  },
64
64
  "default": {},
65
65
  "generate": {
@@ -17,6 +17,25 @@ synopkgland init -t hello-web [目錄]
17
17
 
18
18
  產物:`synology-app.yaml`、`compose.yaml`、README。需要程式碼時在同一專案加最小 Web App;v1 官方範例用 TypeScript + `@synopackageland/dev-sdk`。
19
19
 
20
+ ## Published port 分配
21
+
22
+ `hello-web` 與 `hello-web-page` 模板的預設 `adminport` 是 **18333**。這個預設值在 2026-08-24 由 8080 調整而來,因為 8080 在真實 NAS 上常被既有容器使用;它只降低碰撞機率,不能保證該 port 一定可用。
23
+
24
+ `adminport` 是 NAS 上的 published host port,必須和 `compose.yaml` 的 host-side published port 對應。若同一台 NAS 上有多個測試或正式 instance,每個 package/instance 都要分配獨立的 published port;**同一台 NAS 上不要重用同一個 port**。可挑選尚未被既有服務使用的高位 port,例如 19xxx 範圍(`19160` 只是示例),並記錄每個 instance 的分配。
25
+
26
+ 例如把模板 instance 改用 19160 時,`synology-app.yaml` 與 Compose 的 host-side port 要一起改,容器內 target 仍依原本 Compose/映像設定:
27
+
28
+ ```yaml
29
+ # synology-app.yaml
30
+ adminport: 19160
31
+
32
+ # compose.yaml
33
+ ports:
34
+ - "19160:18333"
35
+ ```
36
+
37
+ 不要只改其中一處:`adminport` 沒有對應的 published port 會在 `validate` 被擋下;host port 已被同一台 NAS 的其他服務占用,則可能直到 deploy 才以 DSM 的 `broken` 呈現。
38
+
20
39
  包現成 Compose 時,先用 `inspect` 看原本的 volume mapping。容器路徑跟 Compose/映像的 upstream target 走,不要為了套平台範例改成 `/data`;只把 host source 換成 `${PARAM}`。原有的 upstream named volume 要保留在頂層 `volumes:`。需要既有 Share 時宣告 `install.<PARAM>.type: shared_folder`,管理員在安裝時選這台 NAS 的 Share。
21
40
 
22
41
  ## 只問這些(缺才問)
@@ -1,6 +1,38 @@
1
1
  # 排查(Diagnose)
2
2
 
3
- 依 **穩定錯誤碼** 行動;權威是 `synopkgland validate` 輸出、`deploy`/Broker 回傳、`info`/`log` 的 readiness。不要猜 Compose。
3
+ 遇到問題時,**先跑 `synopkgland doctor`,依穩定錯誤碼行動**。
4
+
5
+ ```bash
6
+ # 診斷本機專案(契約、Compose/Native 設定、版本漂移)
7
+ synopkgland doctor
8
+
9
+ # 診斷目標 NAS 主機(連線、Broker 狀態、Container Manager、Attestation 信任根)
10
+ synopkgland doctor --host <nas>
11
+
12
+ # 診斷特定 App 套件(Project Binding 狀態、上次安裝/就緒失敗原因、入口探測)
13
+ synopkgland doctor --host <nas> --package <id>
14
+ ```
15
+
16
+ 權威是 `synopkgland doctor` 診斷檢查、`synopkgland validate` 輸出、`deploy`/Broker 回傳與 `info`/`log` 的 readiness。所有 fail 項均帶有穩定錯誤碼與具體的下一步建議。不要猜 Compose。
17
+
18
+ ## deploy 後 `broken`/statusCode 150
19
+
20
+ 若 deploy 回報 `CATALOG_INSTALL_FAILED`,而 DSM 顯示 `[CATALOG_INSTALL_FAILED] ... (Package Center status broken). Check synopkg postinst / webservice acquire.`,先跑:
21
+
22
+ ```bash
23
+ synopkgland doctor --host <nas> --package <id>
24
+ ```
25
+
26
+ 這類失敗可能只有 `broken_by=install_corruption`/`statusCode=150`,DSM 的錯誤訊息通常看不出真正原因。先依 `doctor` 的建議排查以下兩項:
27
+
28
+ | 常見原因 | 修法 |
29
+ | --- | --- |
30
+ | 同一台 NAS 上已有其他服務或 instance,published host port 已被占用 | 為每個 package/instance 分配不重複的 host port,例如改用 19160;同步把 `synology-app.yaml` 的 `adminport` 改成 19160,並把 `compose.yaml` 的 host-side mapping 改成例如 `"19160:18333"`。容器內 target 依原本 Compose/映像設定保留。 |
31
+ | Compose `command`/`entrypoint` 陣列含純數字字串元素,例如 `"18333"` | 若走 resource-worker 路徑,`validate` 會報 `COMPOSE_NUMERIC_COMMAND_ARG_FORBIDDEN`。把整個命令包成單一字串,例如 `command: ["sh","-c","httpd -f -p 18333 -h /srv"]`,或把數字和 flag 併寫成 `-p18333`。 |
32
+
33
+ 第一項是 published port 碰撞;第二項是 DSM 的 resource-worker 在 YAML round-trip 時掉引號,將純數字元素變成整數,導致 postinst acquire 失敗,可能同樣呈現 `broken`/150,且 `worker_msg` 為空。`COMPOSE_NUMERIC_COMMAND_ARG_FORBIDDEN` **只適用於 Compose 走 resource-worker 路徑**;純數字元素在標準 Compose 合法,Broker fallback 不受此限制,這是 DSM worker 的缺陷而不是 Compose 規格不允許。
34
+
35
+ 修正後依序執行 `synopkgland validate`,再用 `synopkgland deploy --host <nas>` 重試;若仍失敗,保留 `doctor`、`info` 與 `log` 的診斷輸出供後續判斷。
4
36
 
5
37
  ## validate/analyzer(修契約或 Compose)
6
38
 
@@ -40,7 +72,8 @@
40
72
  | `INNER_LOOP_API_MISSING` | 升級 NAS 上 SynoPkgLandService(>=0.1.0-0016);確認 CGI 非 HTML 404 |
41
73
  | `ATTESTATION_FAILED` | 讀 Broker 訊息;常見 digest/身分衝突,必要時 `info --host` 看 attestation |
42
74
  | `BUILD_SPK_FAILED` | 修契約/compose 後重試 |
43
- | `CATALOG_PUBLISH_FAILED`/`CATALOG_INSTALL_FAILED` | Package Source/網路;`info` 看是否已裝;勿手動拼 SPK 上架 |
75
+ | `CATALOG_PUBLISH_FAILED` | Package Source/網路;`info` 看是否已裝;勿手動拼 SPK 上架 |
76
+ | `CATALOG_INSTALL_FAILED`/DSM `broken`/150 | 先跑 `doctor --host <nas> --package <id>`;檢查 published port 碰撞與 `COMPOSE_NUMERIC_COMMAND_ARG_FORBIDDEN` |
44
77
 
45
78
  ## info/log/test --host
46
79
 
@@ -7,6 +7,16 @@ Personal App **預設較窄**:
7
7
  1. Compose 管理的 App Data(`${SYNOPKG_PKGVAR}` 等平台路徑)。
8
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
9
 
10
+ ### App Data、App Secret 位置與備份提醒
11
+
12
+ 在 Synology NAS 上,資料與生命週期分為三類:
13
+
14
+ - **App Data**:位於 SPK 原生 package `var`(`/var/packages/<package>/var` → `/volume?/@appdata/<package>`),保存 App 自身資料庫與設定檔。Normal uninstall 預設保留,只有在 Package Center 卸載時明確勾選「Delete App Data」才會刪除。
15
+ - **App Secret**:位於 package `home` 的受限子目錄 `/var/packages/<package>/home/secrets`(權限 0700/0600),由 Compose secret mount 掛載。Normal uninstall 預設保留,重裝同一個 Package Identity 時優先重用同名 secret。
16
+ - **Shared Folder Mount**:由 NAS Administrator 在安裝時挑選的 DSM 共用資料夾(`/volume?/<ShareName>`)。寫入權限由 Container Run-as 使用者決定;平台 lifecycle 絕不刪除或修改其內容。
17
+
18
+ > **Named Volume 警告**:若第三方 Compose 使用 Docker named volume(如 `volumes: [ "my-data:/data" ]`),其生命週期完全由 Container Manager 管理,**不在** Syno Package Land 平台的保留、備份或清理承諾範圍內。需要持久化時請改用 package `var` 或 Shared Folder Mount。
19
+
10
20
  ### 包現成 Compose
11
21
 
12
22
  - volume mapping 右側的 container target 與 `ro`/`rw` 是映像/upstream 的契約,保留原值;只把左側 host source 換成 `${NAME}`,並在 App Contract 宣告對應的 `shared_folder` 槽。
@@ -4,5 +4,7 @@ services:
4
4
  command: ["sleep", "infinity"]
5
5
  user: "${RUN_AS_UID}:${RUN_AS_GID}"
6
6
  volumes:
7
+ # Dynamic Shared Folder plus writable /state is Broker-managed and is
8
+ # intentionally outside the zero-config docker-project resource-worker path.
7
9
  - "${MEDIA_SHARE}/data:/data:ro"
8
10
  - "${SYNOPKG_PKGVAR}/state:/state"
@@ -1,7 +1,18 @@
1
1
  services:
2
2
  web:
3
- image: nginx:alpine
3
+ image: busybox:1.36
4
+ # 兩個平台限制,改動前先看 docs/impl/STATUS.md 的 2026-08-24 段:
5
+ # 1) command/entrypoint 陣列不得有純數字元素(例如 "18333")。DSM 的
6
+ # docker-project worker 會在 YAML round-trip 時掉引號、把它變成整數,
7
+ # postinst acquire 直接失敗(error 276 → 套件 broken/150,且 worker_msg
8
+ # 為空,開發者看不到原因)。所以 port 要包在單一字串裡。
9
+ # `synopkgland validate` 已會擋這個寫法。
10
+ # 2) 預設不用 8080:那個 port 在真實 NAS 上很常被既有容器占用,而 port
11
+ # 衝突同樣以那個看不出原因的 broken/150 呈現。
12
+ # 官方 nginx image 也不能用:resource worker 注入任意 package UID/GID,
13
+ # nginx entrypoint 啟動階段需要 root,會在該身分下直接退出。
14
+ command: ["sh","-c","httpd -f -p 18333 -h /usr/share/nginx/html"]
4
15
  ports:
5
- - "8080:80"
16
+ - "18333:18333"
6
17
  volumes:
7
- - "${SYNOPKG_PKGVAR}/html:/usr/share/nginx/html:ro"
18
+ - "./html:/usr/share/nginx/html:ro"
@@ -6,7 +6,7 @@ description: A minimal Syno Package Land web app template.
6
6
  maintainer: Syno Package Land
7
7
  os_min_ver: "7.2-69057"
8
8
  adminprotocol: http
9
- adminport: 8080
9
+ adminport: 18333
10
10
  adminurl: /
11
11
  integration:
12
12
  web:
@@ -1,7 +1,18 @@
1
1
  services:
2
2
  web:
3
- image: nginx:alpine
3
+ image: busybox:1.36
4
+ # 兩個平台限制,改動前先看 docs/impl/STATUS.md 的 2026-08-24 段:
5
+ # 1) command/entrypoint 陣列不得有純數字元素(例如 "18333")。DSM 的
6
+ # docker-project worker 會在 YAML round-trip 時掉引號、把它變成整數,
7
+ # postinst acquire 直接失敗(error 276 → 套件 broken/150,且 worker_msg
8
+ # 為空,開發者看不到原因)。所以 port 要包在單一字串裡。
9
+ # `synopkgland validate` 已會擋這個寫法。
10
+ # 2) 預設不用 8080:那個 port 在真實 NAS 上很常被既有容器占用,而 port
11
+ # 衝突同樣以那個看不出原因的 broken/150 呈現。
12
+ # 官方 nginx image 也不能用:resource worker 注入任意 package UID/GID,
13
+ # nginx entrypoint 啟動階段需要 root,會在該身分下直接退出。
14
+ command: ["sh","-c","httpd -f -p 18333 -h /usr/share/nginx/html"]
4
15
  ports:
5
- - "8080:80"
16
+ - "18333:18333"
6
17
  volumes:
7
- - "${SYNOPKG_PKGVAR}/html:/usr/share/nginx/html:ro"
18
+ - "./html:/usr/share/nginx/html:ro"
@@ -6,7 +6,7 @@ description: A Syno Package Land web app template that opens in a new browser pa
6
6
  maintainer: Syno Package Land
7
7
  os_min_ver: "7.2-69057"
8
8
  adminprotocol: http
9
- adminport: 8080
9
+ adminport: 18333
10
10
  adminurl: /
11
11
  integration:
12
12
  web: