@synopackageland/cli 0.17.2 → 0.17.4

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 CHANGED
@@ -1,17 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.17.4 (2026-10-19)
6
+
7
+ - Accept `compose.startup_timeout_seconds` (default 120, integer 2–900), use the SDK plan's value for daemon start, and deliver the same budget to container bootstrap adapters through the SDK-generated environment. Reject a planner that cannot honor an explicitly declared budget. Keep ingress closed until readiness succeeds.
8
+
9
+ - Show actionable image-pull diagnosis in nine locales and direct users to Package Center Repair when a package starts with stale container assets.
10
+ - Pin templates 0.17.6 and SDK/App 0.17.4; keep UI at 0.12.3.
11
+
12
+ ## 0.17.3 (2026-10-19)
13
+
14
+ - Pin templates 0.17.5, SDK/App 0.17.3 and UI 0.12.3 for generated Apps.
15
+ - Record SDK 0.17.3 executor and lifecycle digests; all six payloads are unchanged from 0.17.1.
16
+ - Keep the “User password” wording in all nine locales and package the generated native SDK version identifiers.
17
+
3
18
  ## 0.17.2 (2026-10-19)
4
19
 
5
20
  - Pin templates with matching frontend and native SDK versions.
6
21
  - Record the verified SDK 0.17.1 executor and lifecycle payload digests.
7
22
  - Rename password labels and approval instructions to “User password” across all nine locales.
8
- - 對齊前後端 SDK 版本,並記錄 SDK 0.17.1 的 executor 與 lifecycle digest。
9
23
 
10
- ## 0.17.1 (unreleased)
24
+ ## 0.17.1 (2026-10-19)
11
25
 
12
26
  - Remove environment-specific release notes and source comments from distributed content, including generated DSM window scripts.
13
27
  - Prepare npm releases from a clean package directory without local archive references.
14
- - 清理發佈內容中的環境識別敘述;以乾淨套件目錄準備 npm 發佈。
15
28
 
16
29
  ## 0.17.0 (2026-10-19)
17
30
 
package/README.md CHANGED
@@ -259,6 +259,16 @@ Compose uses schema-2 `dsm-package.yaml` and `ui.launch`; the default is new-pag
259
259
  Classless gateways use type url via the shared contract/app-launch resolver;
260
260
  AppWindow wrappers use standalone. Headless projects have no launcher.
261
261
 
262
+ Rebuilt container Apps include informational **Registry check** diagnostics in
263
+ preinst, postinst, postupgrade and prestart. Messages use the existing nine-language
264
+ catalog and end with the native recovery step: fix the cause, then use Package
265
+ Center **Repair**. Repair re-runs the install worker and downloads missing images;
266
+ it is not an SDK Retry action. Checks use only immutable image references from the
267
+ packaged lock, never the Docker socket or registry credentials. They do not block
268
+ cached/offline startup or establish daemon disk space, image existence or pull
269
+ success. Native wrapper codes alone never identify a cause. SDK and CLI changes
270
+ must ship together and Apps must be rebuilt to receive these hooks and catalogs.
271
+
262
272
  See [Compose](skills/reference/compose.md) for multiple upstream files,
263
273
  generated override/worker artifacts, image proofs and the support matrix.
264
274
  Containers use a fixed package identity and do not inherit a signed-in user's
@@ -22,7 +22,7 @@ Application Portal. Headless Apps omit compose.web and have no launcher.
22
22
  Compose ctl is unsupported.
23
23
 
24
24
  Use supported upstream config files, environment, argv or reverse proxy for
25
- login/settings. For integrated upstream Apps, select supported trusted-header authentication or disable upstream login behind the DSM admin gate; or use the existing SDK 0.17.0 token adapter: OpenList uses mode: token, access: dsm-admins and entry: admin-only. Its unmodified admin token CLI supplies a package-private 0600 credential; the gateway injects Authorization server-side after DSM admission, without delivering it to the browser. All DSM admins share one upstream admin; token reset requires package restart; WebDAV keeps its own credentials. Verify each App adapter and DSM admission separately.
25
+ login/settings. For integrated upstream Apps, select supported trusted-header authentication or disable upstream login behind the DSM admin gate; or use the existing SDK 0.17.3 token adapter: OpenList uses mode: token, access: dsm-admins and entry: admin-only. Its unmodified admin token CLI supplies a package-private 0600 credential; the gateway injects Authorization server-side after DSM admission, without delivering it to the browser. All DSM admins share one upstream admin; token reset requires package restart; WebDAV keeps its own credentials. Verify each App adapter and DSM admission separately.
26
26
  Containers use the package account and a dedicated bridge; they do not inherit
27
27
  an interactive user's NAS ACL. Do not use root/chown during any App lifecycle
28
28
  phase. Preserve licenses and dependency/source provenance. Read the CLI skill
@@ -0,0 +1,4 @@
1
+ /** Packaged data for compose/control; one source of native text in all locales. */
2
+ export declare function composeDiagnosisMessages(): Record<string, Record<string, string>>;
3
+ /** Diagnostics are informational and cannot mask or replace a native result. */
4
+ export declare function renderComposeDiagnosis(project: string, phase: "preinst" | "postinst" | "postupgrade" | "prestart"): string;
@@ -0,0 +1,12 @@
1
+ import { LOCALES } from "../standalone/i18n.js";
2
+ import { WIZARD_MESSAGES } from "./wizard-i18n.js";
3
+ /** Packaged data for compose/control; one source of native text in all locales. */
4
+ export function composeDiagnosisMessages() {
5
+ return Object.fromEntries(LOCALES.map((locale) => [locale, Object.fromEntries(Object.entries(WIZARD_MESSAGES)
6
+ .filter(([key]) => key.startsWith("compose.diagnosis."))
7
+ .map(([key, texts]) => [key.slice("compose.diagnosis.".length), texts[locale]]))]));
8
+ }
9
+ /** Diagnostics are informational and cannot mask or replace a native result. */
10
+ export function renderComposeDiagnosis(project, phase) {
11
+ return `"${project}/control" diagnose --project "${project}" --phase ${phase} || :\n`;
12
+ }
@@ -1,4 +1,136 @@
1
1
  export declare const WIZARD_MESSAGES: {
2
+ "compose.diagnosis.registry_check": {
3
+ en: string;
4
+ "zh-TW": string;
5
+ "zh-CN": string;
6
+ ja: string;
7
+ ko: string;
8
+ de: string;
9
+ fr: string;
10
+ es: string;
11
+ it: string;
12
+ };
13
+ "compose.diagnosis.container_manager": {
14
+ en: string;
15
+ "zh-TW": string;
16
+ "zh-CN": string;
17
+ ja: string;
18
+ ko: string;
19
+ de: string;
20
+ fr: string;
21
+ es: string;
22
+ it: string;
23
+ };
24
+ "compose.diagnosis.tls_trust": {
25
+ en: string;
26
+ "zh-TW": string;
27
+ "zh-CN": string;
28
+ ja: string;
29
+ ko: string;
30
+ de: string;
31
+ fr: string;
32
+ es: string;
33
+ it: string;
34
+ };
35
+ "compose.diagnosis.unreachable": {
36
+ en: string;
37
+ "zh-TW": string;
38
+ "zh-CN": string;
39
+ ja: string;
40
+ ko: string;
41
+ de: string;
42
+ fr: string;
43
+ es: string;
44
+ it: string;
45
+ };
46
+ "compose.diagnosis.dns": {
47
+ en: string;
48
+ "zh-TW": string;
49
+ "zh-CN": string;
50
+ ja: string;
51
+ ko: string;
52
+ de: string;
53
+ fr: string;
54
+ es: string;
55
+ it: string;
56
+ };
57
+ "compose.diagnosis.rate_limit": {
58
+ en: string;
59
+ "zh-TW": string;
60
+ "zh-CN": string;
61
+ ja: string;
62
+ ko: string;
63
+ de: string;
64
+ fr: string;
65
+ es: string;
66
+ it: string;
67
+ };
68
+ "compose.diagnosis.disk_full": {
69
+ en: string;
70
+ "zh-TW": string;
71
+ "zh-CN": string;
72
+ ja: string;
73
+ ko: string;
74
+ de: string;
75
+ fr: string;
76
+ es: string;
77
+ it: string;
78
+ };
79
+ "compose.diagnosis.not_found": {
80
+ en: string;
81
+ "zh-TW": string;
82
+ "zh-CN": string;
83
+ ja: string;
84
+ ko: string;
85
+ de: string;
86
+ fr: string;
87
+ es: string;
88
+ it: string;
89
+ };
90
+ "compose.diagnosis.denied": {
91
+ en: string;
92
+ "zh-TW": string;
93
+ "zh-CN": string;
94
+ ja: string;
95
+ ko: string;
96
+ de: string;
97
+ fr: string;
98
+ es: string;
99
+ it: string;
100
+ };
101
+ "compose.diagnosis.unknown": {
102
+ en: string;
103
+ "zh-TW": string;
104
+ "zh-CN": string;
105
+ ja: string;
106
+ ko: string;
107
+ de: string;
108
+ fr: string;
109
+ es: string;
110
+ it: string;
111
+ };
112
+ "compose.diagnosis.repair": {
113
+ en: string;
114
+ "zh-TW": string;
115
+ "zh-CN": string;
116
+ ja: string;
117
+ ko: string;
118
+ de: string;
119
+ fr: string;
120
+ es: string;
121
+ it: string;
122
+ };
123
+ "compose.diagnosis.error_code": {
124
+ en: string;
125
+ "zh-TW": string;
126
+ "zh-CN": string;
127
+ ja: string;
128
+ ko: string;
129
+ de: string;
130
+ fr: string;
131
+ es: string;
132
+ it: string;
133
+ };
2
134
  "delegate.preselection": {
3
135
  en: string;
4
136
  "zh-TW": string;
@@ -1,4 +1,136 @@
1
1
  export const WIZARD_MESSAGES = {
2
+ "compose.diagnosis.registry_check": {
3
+ "en": "Registry check",
4
+ "zh-TW": "登錄站檢查",
5
+ "zh-CN": "镜像仓库检查",
6
+ "ja": "レジストリ確認",
7
+ "ko": "레지스트리 확인",
8
+ "de": "Registry-Prüfung",
9
+ "fr": "Vérification du registre",
10
+ "es": "Comprobación del registro",
11
+ "it": "Verifica del registro"
12
+ },
13
+ "compose.diagnosis.container_manager": {
14
+ "en": "Container Manager",
15
+ "zh-TW": "Container Manager 錯誤",
16
+ "zh-CN": "Container Manager 错误",
17
+ "ja": "Container Manager のエラー",
18
+ "ko": "Container Manager 오류",
19
+ "de": "Container Manager-Fehler",
20
+ "fr": "Erreur de Container Manager",
21
+ "es": "Error de Container Manager",
22
+ "it": "Errore di Container Manager"
23
+ },
24
+ "compose.diagnosis.tls_trust": {
25
+ "en": "The registry certificate could not be verified. Check the NAS date and time. Ask your network administrator to configure trusted certificates for Container Manager. Alternatively, ask them to let this registry bypass network certificate checks.",
26
+ "zh-TW": "無法驗證登錄站憑證。請檢查 NAS 日期與時間。若公司網路會攔查加密連線,請網管協助。可為 Container Manager 設定信任憑證,或讓登錄站略過攔查。",
27
+ "zh-CN": "无法验证镜像仓库证书。请检查 NAS 日期和时间。如果公司网络会检查加密连接,请联系网络管理员。可为 Container Manager 配置信任证书,或让镜像仓库绕过此类检查。",
28
+ "ja": "レジストリの証明書を検証できません。NAS の日付と時刻を確認してください。Container Manager で信頼する証明書の設定をネットワーク管理者に依頼してください。または、このレジストリをネットワークの証明書検査から除外してもらってください。",
29
+ "ko": "레지스트리 인증서를 확인할 수 없습니다. NAS 날짜와 시간을 확인하십시오. 네트워크 관리자에게 Container Manager의 신뢰할 수 있는 인증서 설정을 요청하십시오. 또는 이 레지스트리를 네트워크 인증서 검사에서 제외하도록 요청하십시오.",
30
+ "de": "Das Zertifikat der Registry konnte nicht geprüft werden. Prüfen Sie Datum und Uhrzeit des NAS. Bitten Sie Ihre Netzwerkadministration, vertrauenswürdige Zertifikate für Container Manager einzurichten. Alternativ kann sie diese Registry von der Zertifikatsprüfung im Netzwerk ausnehmen.",
31
+ "fr": "Le certificat du registre ne peut pas être vérifié. Vérifiez la date et l’heure du NAS. Demandez à votre administrateur réseau de configurer les certificats de confiance pour Container Manager. Il peut aussi exclure ce registre du contrôle des certificats sur le réseau.",
32
+ "es": "No se pudo verificar el certificado del registro. Compruebe la fecha y la hora del NAS. Solicite a su administrador de red que configure certificados de confianza para Container Manager. Como alternativa, puede excluir este registro de la comprobación de certificados de la red.",
33
+ "it": "Impossibile verificare il certificato del registro. Controllare la data e l’ora del NAS. Chiedere all’amministratore di rete di configurare i certificati attendibili per Container Manager. In alternativa, chiedere di escludere questo registro dai controlli dei certificati di rete."
34
+ },
35
+ "compose.diagnosis.unreachable": {
36
+ "en": "Cannot reach the registry. Check the NAS network, firewall and Container Manager proxy settings.",
37
+ "zh-TW": "無法連線至登錄站。檢查 NAS 網路、防火牆及 Container Manager 的代理伺服器設定。",
38
+ "zh-CN": "无法连接镜像仓库。检查 NAS 网络、防火墙及 Container Manager 的代理服务器设置。",
39
+ "ja": "レジストリに接続できません。NAS のネットワーク、ファイアウォール、Container Manager のプロキシ設定を確認してください。",
40
+ "ko": "레지스트리에 연결할 수 없습니다. NAS 네트워크, 방화벽 및 Container Manager 프록시 설정을 확인하십시오.",
41
+ "de": "Die Registry ist nicht erreichbar. Prüfen Sie Netzwerk, Firewall und die Proxy-Einstellungen von Container Manager.",
42
+ "fr": "Le registre est inaccessible. Vérifiez le réseau du NAS, le pare-feu et les paramètres de proxy de Container Manager.",
43
+ "es": "No se puede acceder al registro. Compruebe la red del NAS, el cortafuegos y la configuración de proxy de Container Manager.",
44
+ "it": "Impossibile raggiungere il registro. Controllare la rete del NAS, il firewall e le impostazioni proxy di Container Manager."
45
+ },
46
+ "compose.diagnosis.dns": {
47
+ "en": "The registry name could not be resolved. Check the NAS DNS settings.",
48
+ "zh-TW": "無法解析登錄站名稱。檢查 NAS 的 DNS 設定。",
49
+ "zh-CN": "无法解析镜像仓库名称。检查 NAS 的 DNS 设置。",
50
+ "ja": "レジストリ名を解決できません。NAS の DNS 設定を確認してください。",
51
+ "ko": "레지스트리 이름을 확인할 수 없습니다. NAS DNS 설정을 확인하십시오.",
52
+ "de": "Der Name der Registry konnte nicht aufgelöst werden. Prüfen Sie die DNS-Einstellungen des NAS.",
53
+ "fr": "Le nom du registre ne peut pas être résolu. Vérifiez les paramètres DNS du NAS.",
54
+ "es": "No se pudo resolver el nombre del registro. Compruebe la configuración DNS del NAS.",
55
+ "it": "Impossibile risolvere il nome del registro. Controllare le impostazioni DNS del NAS."
56
+ },
57
+ "compose.diagnosis.rate_limit": {
58
+ "en": "The registry download limit was reached. Wait before trying again. You can also ask an administrator to check registry sign-in.",
59
+ "zh-TW": "已達登錄站下載上限。請稍後再試,也可請管理員檢查登錄站登入設定。",
60
+ "zh-CN": "已达到镜像仓库下载上限。请稍后再试,也可请管理员检查镜像仓库登录设置。",
61
+ "ja": "レジストリのダウンロード制限に達しました。しばらく待つか、管理者にレジストリのサインイン設定の確認を依頼してください。",
62
+ "ko": "레지스트리 다운로드 한도에 도달했습니다. 기다리거나 관리자에게 레지스트리 로그인 설정 확인을 요청하십시오.",
63
+ "de": "Das Downloadlimit der Registry wurde erreicht. Warten Sie oder bitten Sie einen Administrator, die Anmeldung bei der Registry zu prüfen.",
64
+ "fr": "La limite de téléchargement du registre est atteinte. Patientez ou demandez à un administrateur de vérifier la connexion au registre.",
65
+ "es": "Se alcanzó el límite de descargas del registro. Espere o solicite a un administrador que compruebe el inicio de sesión en el registro.",
66
+ "it": "È stato raggiunto il limite di download del registro. Attendere o chiedere a un amministratore di controllare l’accesso al registro."
67
+ },
68
+ "compose.diagnosis.disk_full": {
69
+ "en": "There is not enough storage for the image. Free space on Container Manager's storage volume.",
70
+ "zh-TW": "儲存空間不足,無法下載映像。請釋出 Container Manager 儲存磁碟區的空間。",
71
+ "zh-CN": "存储空间不足,无法下载映像。请释放 Container Manager 存储卷的空间。",
72
+ "ja": "イメージ用の空き容量が不足しています。Container Manager のストレージボリュームの空き容量を増やしてください。",
73
+ "ko": "이미지를 위한 저장 공간이 부족합니다. Container Manager 저장소 볼륨의 공간을 확보하십시오.",
74
+ "de": "Für das Image ist nicht genug Speicherplatz verfügbar. Geben Sie Speicherplatz auf dem Speichervolume von Container Manager frei.",
75
+ "fr": "L’espace de stockage est insuffisant pour l’image. Libérez de l’espace sur le volume de stockage de Container Manager.",
76
+ "es": "No hay espacio de almacenamiento suficiente para la imagen. Libere espacio en el volumen de almacenamiento de Container Manager.",
77
+ "it": "Lo spazio di archiviazione per l’immagine non è sufficiente. Liberare spazio sul volume di archiviazione di Container Manager."
78
+ },
79
+ "compose.diagnosis.not_found": {
80
+ "en": "The required image version is unavailable. Check for an App update or contact its publisher.",
81
+ "zh-TW": "所需映像版本無法取得。檢查 App 更新或聯絡發佈者。",
82
+ "zh-CN": "无法获取所需映像版本。检查应用更新或联系发布者。",
83
+ "ja": "必要なイメージバージョンを取得できません。アプリの更新を確認するか、公開元に問い合わせてください。",
84
+ "ko": "필요한 이미지 버전을 사용할 수 없습니다. 앱 업데이트를 확인하거나 게시자에게 문의하십시오.",
85
+ "de": "Die erforderliche Image-Version ist nicht verfügbar. Prüfen Sie auf App-Updates oder wenden Sie sich an den Herausgeber.",
86
+ "fr": "La version d’image requise est indisponible. Recherchez une mise à jour de l’application ou contactez son éditeur.",
87
+ "es": "La versión de imagen requerida no está disponible. Busque una actualización de la aplicación o contacte con su editor.",
88
+ "it": "La versione dell’immagine richiesta non è disponibile. Verificare gli aggiornamenti dell’app o contattare l’editore."
89
+ },
90
+ "compose.diagnosis.denied": {
91
+ "en": "The registry denied access. Ask an administrator to check registry sign-in and image permissions.",
92
+ "zh-TW": "登錄站拒絕存取。請管理員檢查登錄站登入設定及映像權限。",
93
+ "zh-CN": "镜像仓库拒绝访问。请管理员检查镜像仓库登录设置及映像权限。",
94
+ "ja": "レジストリにアクセスを拒否されました。管理者にレジストリのサインイン設定とイメージの権限の確認を依頼してください。",
95
+ "ko": "레지스트리가 접근을 거부했습니다. 관리자에게 레지스트리 로그인 설정과 이미지 권한 확인을 요청하십시오.",
96
+ "de": "Die Registry hat den Zugriff verweigert. Bitten Sie einen Administrator, die Anmeldung und die Image-Berechtigungen zu prüfen.",
97
+ "fr": "Le registre a refusé l’accès. Demandez à un administrateur de vérifier la connexion au registre et les autorisations de l’image.",
98
+ "es": "El registro denegó el acceso. Solicite a un administrador que compruebe el inicio de sesión y los permisos de la imagen.",
99
+ "it": "Il registro ha negato l’accesso. Chiedere a un amministratore di controllare l’accesso al registro e le autorizzazioni dell’immagine."
100
+ },
101
+ "compose.diagnosis.unknown": {
102
+ "en": "The check could not determine the cause. Open Container Manager for details or contact the App publisher.",
103
+ "zh-TW": "檢查無法判斷原因。開啟 Container Manager 查看詳細資訊,或聯絡 App 發佈者。",
104
+ "zh-CN": "检查无法确定原因。打开 Container Manager 查看详细信息,或联系应用发布者。",
105
+ "ja": "確認では原因を特定できませんでした。Container Manager で詳細を確認するか、アプリの公開元に問い合わせてください。",
106
+ "ko": "확인으로 원인을 파악할 수 없습니다. Container Manager에서 세부 정보를 확인하거나 앱 게시자에게 문의하십시오.",
107
+ "de": "Die Prüfung konnte die Ursache nicht ermitteln. Öffnen Sie Container Manager für Details oder wenden Sie sich an den App-Herausgeber.",
108
+ "fr": "La vérification ne permet pas de déterminer la cause. Ouvrez Container Manager pour plus de détails ou contactez l’éditeur de l’application.",
109
+ "es": "La comprobación no pudo determinar la causa. Abra Container Manager para ver los detalles o contacte con el editor de la aplicación.",
110
+ "it": "La verifica non ha potuto determinare la causa. Aprire Container Manager per i dettagli o contattare l’editore dell’app."
111
+ },
112
+ "compose.diagnosis.repair": {
113
+ "en": "After fixing the cause, use Package Center \"Repair\". The system will reinstall the package and download missing images.",
114
+ "zh-TW": "修正後,請在套件中心按「修復」,系統會重新安裝並下載缺少的映像。",
115
+ "zh-CN": "修正后,请在套件中心点击“修复”,系统会重新安装并下载缺少的映像。",
116
+ "ja": "原因を解消してから、パッケージセンターで「修復」を実行してください。システムがパッケージを再インストールし、不足しているイメージをダウンロードします。",
117
+ "ko": "원인을 해결한 후 패키지 센터에서 \"복구\"를 선택하십시오. 시스템이 패키지를 다시 설치하고 누락된 이미지를 다운로드합니다.",
118
+ "de": "Beheben Sie die Ursache und wählen Sie im Paket-Zentrum „Reparieren“. Das System installiert das Paket erneut und lädt fehlende Images herunter.",
119
+ "fr": "Corrigez la cause, puis sélectionnez « Réparer » dans le Centre de paquets. Le système réinstallera le paquet et téléchargera les images manquantes.",
120
+ "es": "Corrija la causa y seleccione \"Reparar\" en el Centro de paquetes. El sistema reinstalará el paquete y descargará las imágenes que faltan.",
121
+ "it": "Correggere la causa e selezionare «Ripara» nel Centro pacchetti. Il sistema reinstallerà il pacchetto e scaricherà le immagini mancanti."
122
+ },
123
+ "compose.diagnosis.error_code": {
124
+ "en": "(Error code: {code})",
125
+ "zh-TW": "(錯誤碼:{code})",
126
+ "zh-CN": "(错误代码:{code})",
127
+ "ja": "(エラーコード:{code})",
128
+ "ko": "(오류 코드: {code})",
129
+ "de": "(Fehlercode: {code})",
130
+ "fr": "(Code d’erreur : {code})",
131
+ "es": "(Código de error: {code})",
132
+ "it": "(Codice errore: {code})"
133
+ },
2
134
  "delegate.preselection": {
3
135
  en: "Pre-select the shared folder for background saves. Confirm this selection with your administrator password when first enabling background access in the app. This does not grant the package account access to the share.",
4
136
  "zh-TW": "預選背景存檔使用的共用資料夾。首次在 App 啟用背景存取時,以管理員密碼確認這個選項。這不會授予套件帳號共用資料夾權限。",
@@ -58,7 +58,7 @@ Commit the scaffold, then run bash synology/upgrade.sh <upstream-tag> <build-num
58
58
 
59
59
  ${official ? 'Default: use the upstream official image. upgrade.sh updates its release tag and resolves an OCI index/manifest digest through synopkgland images pin, writing only synology/compose.image.yaml. Container Manager downloads that pinned official image at install. Configure upstream-recipe.json repository/service and imageTag: tag (exact upstream tag) or version (strip v only when upstream image naming requires it). Review multi-architecture support and upstream source/license correspondence. The SELECT_UPSTREAM_TAG placeholder must be replaced by a successful upgrade before check/build.' : 'Native App: implement build-source.sh as the native payload hook; it deliberately fails until implemented. Stage official release binaries pinned by SHA-256 (the OpenList route), or compile the merged source with a source build hook. Verify the release digest before staging architecture-specific daemon.targets; keep the SDK management shell separate.'}
60
60
 
61
- Use DSM administrator identity without an additional App password, an OIDC/SSO server or upstream source changes. Prefer supported trusted-header/proxy auth (FreshRSS), otherwise disable upstream login/enable guest behind the administrator-only gateway after a bounded first bootstrap (Uptime Kuma disableAuth). The third shipped pattern is the existing SDK 0.17.0 token adapter: mode: token, access: dsm-admins, entry: admin-only. OpenList obtains its native API token with the unmodified admin token CLI while stopped, stores it package-private 0600 under a 0700 directory, and the gateway injects Authorization server-side after DSM admission; packaging never delivers it to the browser. All DSM admins share one upstream admin; token reset requires package restart; WebDAV keeps its own native credentials. The upstream admin token/settings APIs remain unchanged. Verify actual DSM admission/revocation separately. Use supported config, environment, argv or proxy adapters. Every App lifecycle phase runs as the package UID. For upstream fixed-UID data directories, currently provide a synology/ startup adapter or writable path without root/chown; replace it with the framework ownership mechanism when available. Preserve licenses and the full source/component inventory.
61
+ Use DSM administrator identity without an additional App password, an OIDC/SSO server or upstream source changes. Prefer supported trusted-header/proxy auth (FreshRSS), otherwise disable upstream login/enable guest behind the administrator-only gateway after a bounded first bootstrap (Uptime Kuma disableAuth). The third shipped pattern is the existing SDK 0.17.3 token adapter: mode: token, access: dsm-admins, entry: admin-only. OpenList obtains its native API token with the unmodified admin token CLI while stopped, stores it package-private 0600 under a 0700 directory, and the gateway injects Authorization server-side after DSM admission; packaging never delivers it to the browser. All DSM admins share one upstream admin; token reset requires package restart; WebDAV keeps its own native credentials. The upstream admin token/settings APIs remain unchanged. Verify actual DSM admission/revocation separately. Use supported config, environment, argv or proxy adapters. Every App lifecycle phase runs as the package UID. For upstream fixed-UID data directories, currently provide a synology/ startup adapter or writable path without root/chown; replace it with the framework ownership mechanism when available. Preserve licenses and the full source/component inventory.
62
62
  `;
63
63
  writeFileSync(join(packaging, 'AGENTS.md'), readFileSync(join(packaging, 'AGENTS.md'), 'utf8') + guidance);
64
64
  }
@@ -20,5 +20,27 @@
20
20
  "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
21
21
  },
22
22
  "goToolchain": "go1.26.9"
23
+ },
24
+ "0.17.3": {
25
+ "x86_64": "68f372684997258adc61312f2c353e1a286a6b5aaa556f716b4e6ba6f33893d0",
26
+ "aarch64": "f6950301e201a4ebc14577a4554fa1b671f90d0f354d1e8809f4d1dafb57c723",
27
+ "armv7": "e77c9b6ae22ecfa85d9834af3350192e445987265a7ca6c02ca9b01d88a1b4f7",
28
+ "lifecycle": {
29
+ "x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
30
+ "aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
31
+ "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
32
+ },
33
+ "goToolchain": "go1.26.9"
34
+ },
35
+ "0.17.4": {
36
+ "x86_64": "68f372684997258adc61312f2c353e1a286a6b5aaa556f716b4e6ba6f33893d0",
37
+ "aarch64": "f6950301e201a4ebc14577a4554fa1b671f90d0f354d1e8809f4d1dafb57c723",
38
+ "armv7": "e77c9b6ae22ecfa85d9834af3350192e445987265a7ca6c02ca9b01d88a1b4f7",
39
+ "lifecycle": {
40
+ "x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
41
+ "aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
42
+ "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
43
+ },
44
+ "goToolchain": "go1.26.9"
23
45
  }
24
46
  }
@@ -1,5 +1,15 @@
1
1
  /** SDK planner errors accepted at this versioned tool boundary. */
2
2
  export const COMPOSE_ERRORS = {
3
+ COMPOSE_STARTUP_INVALID: {
4
+ code: "COMPOSE_STARTUP_INVALID",
5
+ description: "The container startup budget is invalid.",
6
+ suggestion: "Set compose.startup_timeout_seconds to an integer from 2 to 900 seconds; the default is 120. Read the same SYNOPKGLAND_STARTUP_BUDGET_SECONDS in the App bootstrap adapter.",
7
+ },
8
+ COMPOSE_STARTUP_TIMEOUT: {
9
+ code: "COMPOSE_STARTUP_TIMEOUT",
10
+ description: "Container readiness exhausted its declared startup budget.",
11
+ suggestion: "Inspect the safe phase code in daemon/daemon.log and upstream migration/bootstrap health. Declare a sufficient bounded startup_timeout_seconds and rebuild; keep ingress closed until ready.",
12
+ },
3
13
  COMPOSE_STORAGE_INIT_INVALID: {
4
14
  code: "COMPOSE_STORAGE_INIT_INVALID",
5
15
  description: "The container storage initialization declaration is invalid.",
@@ -2,6 +2,7 @@ import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
2
2
  import { type Daemon } from "./daemon.js";
3
3
  import type { StandaloneError, StandaloneManifest } from "./manifest.js";
4
4
  export interface ComposePlan {
5
+ startup_timeout_seconds?: number;
5
6
  ingress?: {
6
7
  address: string;
7
8
  subnet: string;
@@ -68,7 +69,7 @@ export interface ComposeApp {
68
69
  document: Record<string, any>;
69
70
  plan: ComposePlan;
70
71
  }
71
- export declare const COMPOSE_FIELDS: readonly ["resource_prefix", "files", "env_files", "profiles", "web", "data", "shares", "network", "services", "helpers", "sdk"];
72
+ export declare const COMPOSE_FIELDS: readonly ["startup_timeout_seconds", "resource_prefix", "files", "env_files", "profiles", "web", "data", "shares", "network", "services", "helpers", "sdk"];
72
73
  export declare function readComposeApp(value: unknown, root: string, arches: NativeArchFamily[], fail: (e: StandaloneError) => void, context?: {
73
74
  package?: string;
74
75
  sdk?: string;
@@ -1,3 +1,4 @@
1
+ import { composeDiagnosisMessages, renderComposeDiagnosis } from "../build-spk/compose-diagnosis.js";
1
2
  import { COMPOSE_ERRORS } from "./compose-errors.js";
2
3
  import { spawnSync } from "node:child_process";
3
4
  import { createHash } from "node:crypto";
@@ -10,6 +11,7 @@ import { goChildEnv } from "../runtime/go-env.js";
10
11
  import { sdkSource } from "../runtime/sdk-source.js";
11
12
  import { readDaemon } from "./daemon.js";
12
13
  export const COMPOSE_FIELDS = [
14
+ "startup_timeout_seconds",
13
15
  "resource_prefix",
14
16
  "files",
15
17
  "env_files",
@@ -69,6 +71,10 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
69
71
  if (value.sdk !== undefined &&
70
72
  (typeof value.sdk !== "string" || !/^\d+\.\d+\.\d+$/.test(value.sdk)))
71
73
  return invalid("compose.sdk must name the matching SDK release.", "SDK_COMPOSE_UNSUPPORTED");
74
+ if (value.startup_timeout_seconds !== undefined &&
75
+ (!Number.isInteger(value.startup_timeout_seconds) ||
76
+ value.startup_timeout_seconds < 2 || value.startup_timeout_seconds > 900))
77
+ return invalid("compose.startup_timeout_seconds must be an integer from 2 to 900 seconds (default 120).", "COMPOSE_STARTUP_INVALID");
72
78
  const sdk = typeof value.sdk === "string" ? value.sdk : (context.sdk ?? templateSdkVersion());
73
79
  let targets = {};
74
80
  if (value.helpers !== undefined) {
@@ -107,6 +113,9 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
107
113
  }
108
114
  if (result.status !== 0 || plan.error)
109
115
  throw new Error(plan.error ?? "[SDK_COMPOSE_FAILED] SDK planner failed.");
116
+ if (value.startup_timeout_seconds !== undefined &&
117
+ plan.startup_timeout_seconds !== value.startup_timeout_seconds)
118
+ throw new Error("[SDK_COMPOSE_UNSUPPORTED] This SDK does not implement the declared startup budget; use the matching SDK/CLI release.");
110
119
  const document = parse(plan.effective);
111
120
  const web = value.web;
112
121
  return {
@@ -141,7 +150,7 @@ export function composeDaemon(m) {
141
150
  args: ["run"],
142
151
  targets: m.compose.targets,
143
152
  env: {},
144
- timeouts: { start: 120, stop: 30, status: 5, uninstall: 60 },
153
+ timeouts: { start: m.compose.plan.startup_timeout_seconds ?? 120, stop: 30, status: 5, uninstall: 60 },
145
154
  };
146
155
  }
147
156
  export function composeNetwork(m) {
@@ -187,6 +196,7 @@ export function writeComposePayload(c, root, target, gateway) {
187
196
  "compose.lock.json": JSON.stringify(c.plan.lock, null, 2) + "\n",
188
197
  "request.json": JSON.stringify({ declaration: c.declaration, sdk: c.sdk, gateway }, null, 2) + "\n",
189
198
  "images.json": JSON.stringify(c.plan.image_proofs ?? {}, null, 2) + "\n",
199
+ "diagnosis-messages.json": JSON.stringify(composeDiagnosisMessages(), null, 2) + "\n",
190
200
  }))
191
201
  writeFileSync(join(target, name), content, { mode: 0o600 });
192
202
  }
@@ -195,7 +205,7 @@ export function renderComposeInstall(pkg) {
195
205
  return `#!/bin/sh
196
206
  set -eu
197
207
  "\${SYNOPKG_PKGINST_TEMP_DIR:?}/compose/control" preflight || exit $?
198
- exec "\${SYNOPKG_PKGINST_TEMP_DIR:?}/compose/control" prepare --project "\${SYNOPKG_PKGINST_TEMP_DIR:?}/compose" --package '${pkg}'
208
+ ${renderComposeDiagnosis("\${SYNOPKG_PKGINST_TEMP_DIR:?}/compose", "preinst")}exec "\${SYNOPKG_PKGINST_TEMP_DIR:?}/compose/control" prepare --project "\${SYNOPKG_PKGINST_TEMP_DIR:?}/compose" --package '${pkg}'
199
209
  `;
200
210
  }
201
211
  /** The same SDK entry point supplies the package-account install materializer. */
@@ -1,3 +1,4 @@
1
+ import { renderComposeDiagnosis } from "../build-spk/compose-diagnosis.js";
1
2
  import { composeDaemon, renderComposeInstall } from "./compose.js";
2
3
  import { renderBackgroundControl, renderBackgroundWorkerStart, renderBackgroundWorkerStop, renderUserManagerRecovery, backgroundUnitName, } from "./background.js";
3
4
  import { renderPackagePostuninst } from "../build-spk/native-lifecycle.js";
@@ -29,7 +30,13 @@ export function renderStandaloneScript(name, manifest) {
29
30
  !manifest.compose.plan.mounts_blocked
30
31
  ? `"/var/packages/${manifest.package}/target/compose/control" remember-ingress --project "/var/packages/${manifest.package}/target/compose" --package '${manifest.package}' || exit $?\n`
31
32
  : "";
32
- const ledger = preflight + remember +
33
+ const project = `\${SYNOPKG_PKGDEST:-/var/packages/${manifest.package}/target}/compose`;
34
+ const diagnosis = ["postinst", "postupgrade"].includes(name)
35
+ ? renderComposeDiagnosis(project, name)
36
+ : name === "start-stop-status"
37
+ ? `if [ "$1" = prestart ]; then\n ${renderComposeDiagnosis(project, "prestart")}fi\n`
38
+ : "";
39
+ const ledger = diagnosis + preflight + remember +
33
40
  (manifest.identityGateway && !manifest.compose.plan.mounts_blocked
34
41
  ? name === "start-stop-status"
35
42
  ? `if [ "$1" = restart ]; then\n "$0" stop || exit $?\n exec "$0" start\nfi\nif [ "$1" = start ]; then\n ${control} acquire ${paths} || exit $?\nelif [ "$1" = stop ]; then\n ${control} release ${paths} || exit $?\nfi\n`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@napi-rs/keyring": "^2.1.1",
30
- "@synopackageland/templates": "0.17.3",
30
+ "@synopackageland/templates": "0.17.6",
31
31
  "ajv": "^8.17.1",
32
32
  "commander": "^13.1.0",
33
33
  "json-canonicalize": "^3.0.1",
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
- For an existing open-source App, first read [reference/upstream-fork.md](reference/upstream-fork.md): upstream fork/history + untouched upstream files + all packaging in `synology/`, official container images pinned by tag/index digest, native official release binaries pinned by SHA-256 or a source build hook, and a single tag-merge upgrade command. Use `init --upstream-fork`; never rewrite the App as a component. Integrated upstream Apps require direct DSM administrator entry without a separate App password or an OIDC/SSO server: supported trusted headers first, disabling native login second; the shipped OpenList pattern uses the existing SDK 0.17.0 token adapter (`mode: token`, `access: dsm-admins`, `entry: admin-only`) with server-side Authorization injection. All DSM admins share one upstream admin; token reset requires package restart; WebDAV retains its native credentials.
10
+ For an existing open-source App, first read [reference/upstream-fork.md](reference/upstream-fork.md): upstream fork/history + untouched upstream files + all packaging in `synology/`, official container images pinned by tag/index digest, native official release binaries pinned by SHA-256 or a source build hook, and a single tag-merge upgrade command. Use `init --upstream-fork`; never rewrite the App as a component. Integrated upstream Apps require direct DSM administrator entry without a separate App password or an OIDC/SSO server: supported trusted headers first, disabling native login second; the shipped OpenList pattern uses the existing SDK 0.17.3 token adapter (`mode: token`, `access: dsm-admins`, `entry: admin-only`) with server-side Authorization injection. All DSM admins share one upstream admin; token reset requires package restart; WebDAV retains its native credentials.
11
11
 
12
12
  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. For all Compose work, read [reference/compose.md](reference/compose.md): schema-2 upstream files, generated override, SDK planner, fixed package identity and current DSM blockers. Containers do not inherit a signed-in user's File Station ACL. Existing Shared Folder mounts are blocked until official protected ACL/project control is verified.
13
13
 
@@ -23,7 +23,7 @@ compose:
23
23
  network: {egress: isolated}
24
24
  # Private package binds are rejected by DSM build; keep compatible upstream named volumes.
25
25
  identity_gateway:
26
- sdk: 0.17.0
26
+ sdk: 0.17.3
27
27
  mode: none
28
28
  access: dsm-users
29
29
  listen: {http: 28000}
@@ -35,6 +35,28 @@ and `identity_gateway` for headless projects. Optional `compose.helpers` names
35
35
  architecture-specific native payloads for token/account adapters. All lifecycle
36
36
  scripts run as the package account; containers are managed by Container Manager.
37
37
 
38
+ ## Startup budget
39
+
40
+ Set `compose.startup_timeout_seconds: 600` for a slow cold start or migration.
41
+ Omitting it uses 120 seconds; only integers from 2 through 900 are accepted.
42
+ Use a matching SDK that implements the field and rebuild the App; older planners
43
+ are rejected for explicit budgets. The SDK plan drives generated daemon start
44
+ and supplies every container with `SYNOPKGLAND_STARTUP_BUDGET_SECONDS`, overriding
45
+ upstream and service-policy values for that SDK-owned key.
46
+
47
+ Custom bootstrap adapters must read that environment value once and use one
48
+ deadline across all bootstrap phases; an existing hardcoded adapter timer does
49
+ not change automatically. Keep the health endpoint unready until setup/migration
50
+ and any serving restart finish. Do not log credentials, raw API errors or bodies.
51
+
52
+ The gateway starts after readiness, so ingress remains closed while waiting.
53
+ The supervisor reserves five extra seconds for handoff/probe completion/process
54
+ stability. Timeout returns `COMPOSE_STARTUP_TIMEOUT` plus a safe last `phase` code
55
+ in the start result and `daemon/daemon.log`. Stop/uninstall limits are independent.
56
+ The budget begins after the native Container Manager worker returns; it does not
57
+ extend image pulls, native worker acquisition or Package Center deadlines. Verify
58
+ the installed DSM unit and full fresh-install lifecycle on the target NAS.
59
+
38
60
  ## Planning and artifacts
39
61
 
40
62
  The pinned compose-go reference loader merges ordered files, env files and
@@ -151,7 +173,7 @@ declared availability readiness, LAN isolation/egress, protected grant lifecycle
151
173
  ACLs, two-user visibility and clean install/upgrade/reboot/stop/uninstall before
152
174
  calling the package supported on a NAS. No NAS is accessed by the build gate.
153
175
 
154
- The unified Compose contract uses SDK 0.17.0 and templates 0.17.1 with CLI 0.17.0. Use the published, matching versions together; the legacy packaging route and bridge publish mode are removed.
176
+ The unified Compose contract uses SDK 0.17.3 and templates 0.17.5 with CLI 0.17.3. Use the published, matching versions together; the legacy packaging route and bridge publish mode are removed.
155
177
 
156
178
  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
179
 
@@ -40,8 +40,8 @@ rootOps 與 delegate 已共用管理員 PasswordConfirm 核准的 protected exec
40
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。
41
41
 
42
42
  ```yaml
43
- backend: {runtime: python, dir: app, entry: main.py, sdk: 0.17.0}
44
- backend: {runtime: node, dir: app, entry: main.mjs, sdk: 0.17.0, node: {package: Node.js_v20}}
43
+ backend: {runtime: python, dir: app, entry: main.py, sdk: 0.17.3}
44
+ backend: {runtime: node, dir: app, entry: main.mjs, sdk: 0.17.3, node: {package: Node.js_v20}}
45
45
  backend: {runtime: rust, dir: app, entry: hello, methods: [hello.greet]}
46
46
  ```
47
47
 
@@ -104,7 +104,7 @@ upstream mechanisms in order:
104
104
  If manual App login is still necessary, label bootstrap incomplete. Verify
105
105
  subsequent DSM entry, restart and upgrade all retain password-free access,
106
106
  while anonymous/nonadmin requests and direct upstream ports remain blocked.
107
- 3. **Existing SDK token adapter**: SDK **0.17.0** supports `mode: token`, with
107
+ 3. **Existing SDK token adapter**: SDK **0.17.3** supports `mode: token`, with
108
108
  `access: dsm-admins` and `entry: admin-only`. OpenList uses its unmodified
109
109
  upstream `admin token` CLI while the server is stopped to obtain the native
110
110
  administrator API token. The package stores it in a package-private **0600**
@@ -142,7 +142,7 @@ is undecided. Never copy credentials into generated projects.
142
142
 
143
143
  | Prototype | Reusable lesson and remaining boundary |
144
144
  | --- | --- |
145
- | OpenList | Official release binary pinned by SHA-256, `/ping` and SDK 0.17.0 server-side token adapter; shared upstream admin, restart after token reset, native WebDAV credentials. Share selection does not grant package ACL/register Local storage. |
145
+ | OpenList | Official release binary pinned by SHA-256, `/ping` and SDK 0.17.3 server-side token adapter; shared upstream admin, restart after token reset, native WebDAV credentials. Share selection does not grant package ACL/register Local storage. |
146
146
  | FreshRSS | Official image, package-UID Apache/config adapter in `synology/`, explicit health; supported proxy authentication with fixed admin mapping and header clearing. |
147
147
  | Uptime Kuma | Official image, writable-path adapter, nonredirecting health and `disableAuth` after first bootstrap; WebSocket expiry/revocation need separate tests. |
148
148