跳到內容
Syno Package Land
語言: 繁體中文

容器 App

容器 App 是打包成 DSM 套件的 Compose 專案。你保留原本的 compose.yaml,再加上一個小小的 synology-app.yaml。synopkgland build 會產生 SPK,透過套件中心安裝後,由 Container Manager 執行你的容器。如果 app 有 Web 介面,DSM 使用者會從 DSM 桌面開啟,並先經過 gateway 檢查 DSM 工作階段。

先從 headless 範例開始。它沒有 Web 連接埠,只有一個共用資料夾和一個 DSM 使用者:

Terminal window
synopkgland init -t compose my-app

要打包現有的 app,請匯入它的 Compose 檔。init 會把它複製成 compose.yaml,並產生一份讓你接著編輯的 synology-app.yaml:

Terminal window
synopkgland init -t compose --from ./compose.yaml my-app

package 與 displayname 取自 --package、--name 或目錄名稱。install 只保留匯入檔案有引用其變數的模板項目。接著設定 description 和 maintainer;如果 app 有 Web 介面,加上 adminport;app 需要的其他共用資料夾或設定,都要再加一個 install 項目。請參考欄位參考。

每個 image 都要用 digest 固定。images pin 以匿名 registry HTTP 解析每個 tag,所以私有 registry 會失敗:

Terminal window
synopkgland images pin

下面這組檔案是帶有安裝設定的 Web app,可通過 check 與 build:

compose.yaml
services:
web:
image: traefik/whoami:v1.10.3@sha256:43a68d10b9dfcfc3ffbfe4dd42100dc9aeaf29b3a5636c856337a5940f1b4f1c
command: ["--port=8080"]
environment:
GREETING: "${GREETING}"
ports:
- "127.0.0.1:18080:8080"
volumes:
- "${SYNOPKG_PKGVAR}/data:/data"
synology-app.yaml
schema: 1
package: hello.whoami
version: 0.1.0
displayname: Hello Whoami
description: A small web service behind the DSM gateway.
maintainer: Example Developer
os_min_ver: "7.2.1-69057"
adminport: 18080
adminurl: /
install:
GREETING:
type: string
default: hello
integration:
web:
launch: dsm-window
access: dsm-users

建置與測試:

Terminal window
synopkgland check
synopkgland build
指令作用
check驗證 synology-app.yaml 與 compose.yaml,並列出偵測到的服務、發佈的連接埠和資料夾。
build產生 SPK。Web app 在未指定 --arch 時會建置 <package>-x86_64.spk 與 <package>-aarch64.spk;headless app 只產生一個 <package>.spk。-o 需要搭配單一 --arch。
dev驗證後執行 docker compose up -d,等服務健康(沒有 healthcheck 時則等到執行中),按 Ctrl+C 時停止服務。
test驗證後,在 .synopkgland/ 下準備 ${SYNOPKG_PKGVAR} 與 secrets 資料夾,執行 up 並等待就緒。容器會繼續執行。
deploy建置 SPK 並安裝到 NAS。--share、--run-as 與 --param 請見 deploy --help。

dev 與 test 需要你的電腦安裝 Docker。

把 adminport 設成容器發佈的主機連接埠,套件就會有一個 gateway:DSM 開啟 /webman/3rdparty/<package>/cgi/gateway.cgi/<adminurl>,gateway 檢查 DSM 工作階段與你的存取規則,再把請求轉給容器。

請像範例一樣只在 loopback 發佈 Web 連接埠(127.0.0.1:18080:8080)。gateway 是唯一預期的入口。如果 adminport 的主機連接埠監聽在其他位址,check 會以 COMPOSE_GATEWAY_PORT_EXPOSED 失敗。其他在所有介面發佈的連接埠會列在 check 報告中,而 inspect 會對它們失敗。NAS 上的其他程序仍可連到 loopback 連接埠,所以不要把 X-Syno-User 用在本機程序不該做的事情上。

經過 gateway 後,你的 app 會看到這些變化:

  • gateway 設定 X-Syno-User(DSM 使用者名稱)與 X-Syno-Admin(true 或 false)。
  • 請求中的 Cookie、Remote-*、Forwarded、X-Syno-*、X-Forwarded-*、X-Remote-*、X-Auth* 與 X-Real-* 會被丟棄,所以 integration.web.cookies 在 gateway 後面沒有作用。
  • 回應中的 Set-Cookie 會被移除,Cache-Control 強制為 private,並加上 Vary: Cookie。
  • Location 標頭會改寫到 gateway 路徑底下。
  • WebSocket 與 Server-Sent Events 可直接通過。WebSocket 請求中,你的 app 看到的是瀏覽器的 Host。
設定值結果
integration.web.launchdsm-window(預設)、new-pagedsm-window 會加入 DSM 主選單與桌面項目,點擊後在新的瀏覽器分頁開啟 app。new-page 不加項目,由套件中心的開啟在新分頁開啟 app。
integration.web.accessdsm-users(預設)任何已登入的 DSM 使用者。
dsm-adminsadministrators 群組成員。
dsm-groupsintegration.web.groups 中任一群組的成員。
public所有人,不需登入。套件使用 Web Station 而不是 gateway。

群組會逐字比對。本機群組直接寫名稱,例如 family;目錄群組寫成 'DOMAIN\group' 或 group@domain。反斜線的寫法在 YAML 中要加引號。

integration:
web:
access: dsm-groups
groups: [family, 'EXAMPLE\Domain Users']

Compose App 不能宣告 integration.web.portal:它沒有 executor,而解除安裝後要讓 DSM 網頁伺服器停止提供登入入口網站別名,只有 root 做得到。check 會以 CONTRACT_PORTAL_REQUIRES_EXECUTOR 失敗。需要短網址時,由管理員在 DSM 設定反向代理。

gateway 把 GET、HEAD 與 OPTIONS 以外的所有方法視為會變更狀態。這些請求需要 X-SYNO-TOKEN 標頭,也必須證明來自 DSM 的來源:Sec-Fetch-Site 若有送出,必須是 same-origin;Origin 若有送出,必須等於 Host。兩個標頭至少要有一個。WebSocket 握手也適用相同的同源規則。

如果你希望 app 自己回的 403、404、500、502、503 或 504 連同內容送到瀏覽器,請在請求上送出 X-Synopkgland-Keep-Error-Body: 1。否則 DSM 的 Web 伺服器會把這些狀態換成它自己的錯誤頁。設定這個標頭後,gateway 會依下表替換狀態,並把真正的狀態放在 X-Synopkgland-Status。

每個 gateway 錯誤都有純文字內容(code、換行、訊息),以及 X-Synopkgland-Error: <code> 與 Cache-Control: no-store 標頭。DSM 會取代狀態為 403、404、500、502、503 與 504 的回應,所以 gateway 送出替代狀態,並把原本的狀態放在 X-Synopkgland-Status。請依 X-Synopkgland-Error 判斷。

X-Synopkgland-Error送出的狀態原本的狀態原因
session_required401401沒有工作階段,或 DSM 拒絕它。
forbidden401403已登入,但存取規則拒絕該使用者。
token_required401403會變更狀態的請求沒有可用的 X-SYNO-TOKEN。
cross_site_blocked401403會變更狀態的請求或 WebSocket 握手不是同源。
not_found410404請求不在 gateway 路徑底下。
bad_path400400容器內的路徑不被允許。
redirect308308補上結尾的斜線。
body_too_large413413請求內容超過 64 MiB。
upgrade_unsupported501501升級成 WebSocket 以外的協定。
upstream_unavailable521503容器已停止或拒絕連線。會送出 Retry-After: 5。
upstream_timeout52450460 秒內沒有回應標頭,或 3 秒內無法連線。
bad_gateway520502容器回了無效的回應。
auth_unavailable520500DSM 無法驗證工作階段或群組成員資格。

DSM 主選單與桌面會在新的瀏覽器分頁開啟 dsm-window app,不是 DSM 視窗。DSM 對所有網址項目都這樣開啟,所以 app 沒有 DSM 視窗,也沒有視窗標題。當 DSM 的Improve protection against cross-site request forgery attacks 設定開啟時,這個分頁不會帶 token,gateway 會回應 session_required。請關閉該設定,或改用 native-go 模板建置 app,它每次呼叫都會帶 token。請見原生 App。

每個 install.<NAME> 項目都會在安裝精靈新增一個欄位。在 compose.yaml 中以 ${NAME} 引用它的值。名稱必須符合 ^[A-Za-z_][A-Za-z0-9_]*$。沒有任何 Compose 檔引用的 string、boolean、shared_folder 或 posix_all_shares 設定,會讓 check 以 INSTALL_PARAMETER_UNREFERENCED 失敗。

type精靈欄位說明
string文字欄位,不可空白default 最多 256 個字元,不可有控制字元,不可有 ${。
boolean核取方塊deploy --param NAME=true 或 false。
shared_folder共用資料夾選擇器當作 bind 來源使用,見下方。
dsm_user使用者選擇器最多一個。套件以該使用者執行每個服務。
posix_all_shares無掛載所有儲存空間。必須是完整的 bind 來源,不能有後綴。deploy 會從管理員的共用資料夾清單自動填入。
secret、homescheck 會警告,deploy 會拒絕。

bind 共用資料夾時,來源以 ${NAME} 開頭,後面可接子路徑。請在共用資料夾旁宣告 dsm_user,讓容器以 DSM 使用者而不是 root 執行。沒有宣告時,check 會警告(DSM_USER_REQUIRED),而 Community 上架會被拒絕:

install:
MEDIA:
type: shared_folder
RUN_AS:
type: dsm_user
services:
web:
volumes:
- "${MEDIA}/photos:/photos:ro"

建置時會替每個服務加上 user: "${RUN_AS_UID}:${RUN_AS_GID}" 與該使用者的群組。宣告了 dsm_user 後,不要自己設定 user:。使用 deploy --share MEDIA=<共用資料夾名稱> 與 --run-as RUN_AS=<DSM 使用者>,請給共用資料夾名稱與使用者名稱,不要給路徑或 id。

允許的 bind 來源有 ${SYNOPKG_PKGVAR}(app 的資料夾)、${SYNO_APP_SECRETS_DIR},以及類型為 shared_folder、homes 與 posix_all_shares 的安裝設定。專案內的路徑,例如 ./config,也允許。具名 volume 必須宣告在最上層的 volumes:。tmpfs 允許。${NAME} 後面的子路徑是 / 加上字母、數字、.、_ 與 -。

check 會拒絕 compose.yaml 中的這些項目。synology-app.yaml 的 privilege 區塊無法解除其中任何一項。

Compose 功能錯誤碼
build:COMPOSE_BUILD_FORBIDDEN
privileged: trueCOMPOSE_PRIVILEGED_FORBIDDEN
network_mode: hostCOMPOSE_HOST_NETWORK_FORBIDDEN
pid: hostCOMPOSE_HOST_PID_FORBIDDEN
ipc: hostCOMPOSE_HOST_IPC_FORBIDDEN
devices:(非空)COMPOSE_DEVICES_FORBIDDEN
cap_add:(非空)COMPOSE_CAP_ADD_FORBIDDEN
掛載 docker.sock 或 Container Manager socketCOMPOSE_SOCKET_MOUNT_FORBIDDEN
主機路徑,例如 /volume1/data、~/data 或 ../dataCOMPOSE_HOST_BIND_FORBIDDEN
type: bind 使用不被允許的來源COMPOSE_BIND_SOURCE_FORBIDDEN
${X} bind 來源未在 install 宣告COMPOSE_BIND_SOURCE_UNDECLARED
${X} 後面的子路徑無效COMPOSE_BIND_SUFFIX_INVALID
具名 volume 未宣告在 volumes:COMPOSE_NAMED_VOLUME_UNDECLARED
已宣告 dsm_user 又設定 user:COMPOSE_USER_CONFLICTS_DSM_USER
有共用資料夾時使用 user: 0、0:0 或 rootCOMPOSE_USER_ROOT_FORBIDDEN
數字的 user:,例如 1000:1000COMPOSE_USER_NUMERIC_FORBIDDEN
command 或 entrypoint 清單中有純數字項目COMPOSE_NUMERIC_COMMAND_ARG_FORBIDDEN
最上層的 secrets: 或 configs:,或未宣告的 ${NAME}COMPOSE_DYNAMIC_ROUTE_UNSUPPORTED
depends_on 指向不存在的服務COMPOSE_SERVICE_NOT_FOUND
synology-app.yaml 中的 capabilities:CAPABILITY_UNSUPPORTED

COMPOSE_USER_ROOT_FORBIDDEN 在你為自己建置時是警告,在 Community 上架時是錯誤。請見錯誤碼。

項目值
最低 DSM 版本7.2.1-69057(init --min-dsm 與 build --min-dsm 會強制檢查;check 不會)
架構x86_64、aarch64
建置機器Web app 需要 Go 1.21 或更新版本(gateway 會固定以 Go 1.26.9 編譯進 SPK)
package 長度最多 32 個字元;超過 28 個字元會得到 PACKAGE_IDENTITY_TOO_LONG,因為 deploy --dev 會加上 .dev
gateway 請求內容64 MiB
gateway 等待回應標頭60 秒
gateway 等待連線3 秒
未用 digest 固定的 image會回報為風險;建置時會設定 force_pull: true
症狀或錯誤碼原因解法
CONTRACT_ADMINPORT_UNMAPPEDadminport 不是已發佈的 TCP 主機連接埠。在 ports: 發佈它,或修正數字。
COMPOSE_GATEWAY_PORT_EXPOSEDadminport 的主機連接埠不在 loopback。寫成 127.0.0.1:<port>:<container port>。
IMAGE_PIN_REQUIREDimages pin --check 發現未固定或過期的 image。執行 synopkgland images pin。
GATEWAY_GO_MISSING建置 Web app 需要 Go 1.21 或更新版本。安裝 Go 後重新建置。
CAPABILITY_UNSUPPORTED容器 App 設定了 capabilities。移除它。
CONTRACT_PORTAL_REQUIRES_EXECUTOR設定了 integration.web.portal。Compose App 沒有 executor。移除它。可由管理員在 DSM 設定反向代理。
COMPOSE_ENV_MISSINGtest 無法準備本機資料夾。在專案資料夾中、Docker 執行時再試一次。
PORT_ALREADY_IN_USEdeploy 發現發佈的連接埠已被 NAS 佔用。改用其他主機連接埠。
瀏覽器顯示 401 與 session_required沒有 DSM 工作階段,或上述的 CSRF 設定。重新登入;請見「從 DSM 開啟 app」。
瀏覽器顯示 521容器已停止。在套件中心與 Container Manager 檢查套件。