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

在使用者離線時代為工作

狀態:delegate(委派)已實作;真實裝置上的 device lifecycle acceptance 驗收尚待完成(pending)。預設的授權只允許一個操作:files.moveFromStaging。

delegate 讓你的套件在使用者關閉瀏覽器後,仍能繼續替該 DSM 使用者工作。管理員只需核准一次儲存範圍,每位使用者再在範圍內選一個目的地資料夾。之後你的背景工作就能用使用者自己的權限,把完成的檔案移進該資料夾,直到使用者或管理員撤銷為止。套件不會以 root 執行這些寫入,也不會覆蓋既有檔案。

建立含此能力的新專案,或把它加進既有的 native-go 專案:

Terminal window
synopkgland init -t native-go --with delegate my-app
Terminal window
synopkgland add delegate

兩者都會把下列區塊寫入 dsm-package.yaml。宣告 delegate 也會一併啟用 jobs。

capabilities:
delegate:
scope: install-selected
operations: [files.moveFromStaging]
destinations: [save]
lifetime: until-revoked
欄位允許的值
scopeinstall-selected:安裝後由管理員選 home 或某個共用資料夾。home:每位使用者自己的家目錄。shared:<name>:單一共用資料夾,例如 shared:photos。名稱不可含 /、\、: 或控制字元。
lifetimeuntil-revoked
operations[files.moveFromStaging]
destinations1 到 64 個不重複的鍵。每個鍵須符合 ^[a-z][a-z0-9_-]{0,63}$。鍵是你的程式碼用的名稱,不是路徑。

install-selected 必須搭配 lifetime: until-revoked。只要你設定了 destinations 或 lifetime,或列出 files.moveFromStaging,表中四個欄位就都必須符合這些值。

add delegate 不會修改 backend/internal/api/permissions.json,但 init --with delegate 會。如果在 add delegate 之後仍要讓一般使用者看到 App,請在該檔設定 "app.status": "authenticated"。所有方法的預設值是 administrators。

delegate 需要 native-go 專案。binary 與 compose 專案不接受。armv7 的建置尚未在 NAS 上驗證。

設定頁使用 BackgroundSaveSettings,每個目的地 key 只有一列「存放資料夾」,顯示已選資料夾,並提供「選擇資料夾」與「撤銷授權」。App 透過 delegate API 自行管理工作狀態及取消操作。

誰會發生什麼
管理員,使用 install-selected選擇家目錄範圍或共用資料夾根目錄,輸入 DSM 密碼。一次送出就會透過 executor root task 核准範圍、讀回確認,並將該根目錄授權為這位管理員自己的存檔目的地。
一般使用者透過限定範圍的 NAS picker,選擇核准範圍內的資料夾。確認即時 DSM 工作階段與寫入權限後,建立該使用者的授權;不需要管理員密碼,也沒有第二個同意對話框。
使用者,使用固定的 home 或 shared:<name> 範圍在 manifest 指定的範圍內選擇目的地。
管理員,存檔已透過 API 停用選「啟用背景存檔」,之後使用者重新選擇目的地。

開啟設定頁會保留既有目的地。管理員下一次選擇範圍時,會以所選根目錄作為自己的目的地。更換已確認的範圍會撤銷所有舊授權並取消待存檔工作,已存檔檔案保留。「撤銷授權」只影響目前登入使用者的授權。

plain UI 在 DSM 視窗自己的面板核准,管理員在面板內輸入密碼。那個面板核准時不帶範圍,所以只在 home 或 shared:<name>(範圍已固定在 manifest 中)時提供。使用 install-selected 時,核准必須帶上管理員選的範圍,所以請使用 Vue UI。

--ui plain 的頁面沒有目的地選擇器,也無法選範圍,所以 init --ui plain --with delegate 會以 CAPABILITY_COMBINATION_INVALID 失敗,check 也會拒絕 plain UI 搭配 scope: install-selected。請使用 Vue UI,或用下一節的方法自行建立控制項。

install-selected 專案第一次 synopkgland deploy 必須提供安裝精靈的範圍步驟:--param delegate_share=<共用資料夾> 或 --param delegate_home=true。未提供時,deploy 會在建置前以 DEPLOY_PARAM_REQUIRED 失敗。升級沿用已確認的範圍,兩者都不需要。

synopkgland status 會顯示管理員核准是 ready、needs-admin-approval 還是 blocked。使用 identity: standalone 時請用 https:// 主機;status 透過需要 HTTPS 的短期 App 工作階段讀取 executor。

backend/internal/app/delegate.go 以 Go 套件 github.com/synopackageland/syno-pkg-land-sdk/delegate 建立 delegate.Service(下文的 svc)並註冊其 route。SDK 有一個可運作的產生端 delegate/example.go(方法 delegate.example.export)。BackgroundSavePanel 可透過 opt-in 的 sampleSave producer 提供此範例。工作受理回覆不代表檔案已存妥。請沿用這個流程:

  1. 在使用者工作階段仍有效時,查詢目的地:svc.Destination(ctx, caller, request, "save")。把 Destination.ID 存進你自己的工作紀錄。
  2. 把檔案產生在服務的暫存目錄 svc.StagingDir,檔案權限用 0600。
  3. 之後不需要工作階段,再移動檔案:svc.MoveFromStaging(ctx, delegate.MoveParams{DelegationID: id, JobID: jobID, Staging: name, Name: name, Size: size})。此函式拒絕以 root 執行。
selected, err := svc.Destination(ctx, caller, request, "save")
// ... 把完成的檔案以 <name> 寫入 svc.StagingDir ...
_, err = svc.MoveFromStaging(ctx, delegate.MoveParams{
DelegationID: selected.Destination.ID,
JobID: jobID,
Staging: name,
Name: name,
Size: size,
})

重複使用同一個 JobID 會回傳原本的操作,而不是開始新的。每個新檔案請用新的 JobID。

Name 可以包含選定目的地底下的資料夾,例如 Album/CD1/01.flac。移動時以使用者自己的 DSM 權限建立缺少的資料夾,因此擁有者與繼承的 ACL 和使用者在 File Station 自己建立的資料夾一致。已存在的資料夾會沿用,所以重試或同一張專輯的下一個檔案不會建立重複的資料夾。資料夾只在檔案發布時才建立:被取消或撤銷的移動不會留下資料夾。

規則值
分隔符號/。開頭與結尾不可有 /,不可有空白段、. 或 ..。
資料夾名稱1 到 255 位元組。不可含控制字元,也不可含 \ : * ? " < > |。不可是 #recycle、#snapshot 或 @eaDir(不分大小寫)。
檔名與不含資料夾的名稱規則相同,既有呼叫的行為不變。
資料夾層數最多 16 層,所以一次移動最多建立 16 個資料夾。
整個名稱最多 1024 位元組。
Unicode照送來的位元組儲存,不做正規化。若要與 Windows 輸入的名稱在 File Station 中一致,請送 NFC。
符號連結與掛載點名稱中的資料夾若是符號連結、檔案或另一個掛載的磁碟區,會以 destination-not-directory 拒絕。

用 Conflict 選擇檔名已被使用時的處理方式:

Conflict效果
fail 或空白預設。移動以 destination-exists 失敗。
overwrite一次原子地取代既有檔案:新檔先以暫存名稱寫入,再改名蓋過舊檔。移動失敗時舊檔保持完整。只會取代使用者可寫入的一般檔案;資料夾、符號連結或裝置檔會以 destination-exists 失敗。
rename發布為 name (1).ext、name (2).ext,依此類推,最多到 name (99).ext。receipt.path 會回報實際使用的名稱。

保留種子目錄結構的 Gopeed 式下載:

for _, f := range task.Files { // f.Path is relative, e.g. "Album/CD1/01.flac"
_, err = svc.MoveFromStaging(ctx, delegate.MoveParams{
DelegationID: selected.Destination.ID,
JobID: task.ID + "-" + strconv.Itoa(f.Index),
Staging: f.StagingName,
Name: task.Name + "/" + f.Path,
Size: f.Size,
Conflict: "rename",
})
}

你的 UI 用 ctx.call 呼叫這些後端方法。每個方法都需要已登入且 DSM 工作階段有效的使用者,否則回傳 session_required。

方法誰能呼叫結果
delegate.status使用者state(ready、needs-admin-approval、blocked、needs-scope-confirmation 或 disabled)、disabled、lifetime、destinations、scope_mode;install-selected 時另有 preselection、selection、mismatch
delegate.destination使用者{key, selection, destination, status}。status 為 active、missing、revoked(此 key 的授權已撤銷,且沒有其他授權)、reselection-required、needs-scope-confirmation 或 disabled。
delegate.select_destination使用者參數 {key, path}。檢查使用者可寫入該處後建立授權。使用者無法寫入的資料夾回傳 PERMISSION_DENIED。
delegate.move_from_staging使用者參數 {delegation_id, job_id, staging, name, size, conflict?}。name 可以包含資料夾。回傳 {job_id, state}。呼叫者必須擁有該授權。
delegate.operations.list使用者參數 {}。回傳該使用者的存檔。executor 已受理但尚未開始的存檔為 queued;uncertain 表示 executor 無法確定是否已受理。
delegate.operations.get、delegate.operations.cancel使用者參數 {delegation_id, job_id}。回傳 {job_id, state, receipt?, code?, reason?}。
delegate.disable、delegate.enable管理員參數 {}。兩者都會撤銷此 App 的所有授權。
delegate.audit使用者管理員看得到此 App 的所有事件;其他使用者只看得到自己的。最多 256 筆。
delegate.example.export使用者參數 {key, job_id}。執行範例產生端。

存檔的結果是 committed(附 receipt,即 {path, size})、canceled、interrupted 或 uncertain。請不要自動重試 uncertain 或 interrupted 的存檔,要先檢查目的地。取消時可能回報的原因有 operation-canceled-by-user、grant-revoked、scope-changed、integration-disabled 或 destination-replaced。由 executor 自行停止存檔時,原因是 grant-revoked、validation-denied、integration-disabled 或 storage-error,且即使使用者已失去寫入權限,executor 也會移除未完成的檔案。

只有當 delegate 使用 lifetime: until-revoked 時,daemon 才能與 jobs 並存。daemon 的欄位請見Manifest 參考。

如果你沒有設定 destinations、lifetime 與 files.moveFromStaging,delegate 會以有時限的模式運作。此時 operations 可以是下列檔案操作中任意不重複的子集,scope 是 home 或 shared:<name>。delegate.create 接受 1 到 3600 的 ttl_seconds,以及這一份授權的操作。產生的 worker 只處理下載與 SHA-256 工作,下載限制為 64 MiB 與 60 秒。預設授權不允許這些檔案操作:呼叫它們會回傳 POLICY_DENIED。

工作透過 job.Files() 呼叫這些操作。每個路徑都必須在授權的資料夾內。executor 會在呼叫前檢查每個路徑,並再次檢查 File Station 回報的路徑。除了 files.write 與 files.read,executor 都以該使用者身分透過 synowebapi 呼叫 File Station,因此 DSM 套用的是該使用者自己的權限。只有固定允許清單中的 File Station 方法能以這種方式呼叫。

操作Go 方法作用使用者需要的 DSM 權限
files.list、files.infoList、Info列出資料夾,或回傳單一項目。每個項目都包含 size。讀取
files.createFolderCreateFolder、CreateFolderAll建立資料夾。CreateFolderAll 也會建立缺少的上層資料夾,如同 mkdir -p。上層資料夾的寫入
files.checkPermissionCheckPermission確認使用者能寫入資料夾。它會寫入再移除一個每次隨機命名的探測檔 .synopkgland-permission-probe-<32 hex>。無
files.writeWrite把 staging 中最多 64 MiB 的內容存成新檔案。寫入
files.readRead回傳最多 64 MiB 的一般檔案。executor 會視需要建立 staging 資料夾,再以使用者身分把檔案複製進去,且不跟隨符號連結。讀取
files.renameRename原地重新命名檔案或資料夾。修改
files.copyCopy開始把檔案或資料夾複製到另一個資料夾。來源的讀取、目的地的寫入
files.moveMove開始把檔案或資料夾移到另一個資料夾。來源的刪除、目的地的寫入
files.deleteDelete開始刪除檔案或資料夾。刪除

規則:

  • 每個用到的操作都要宣告。寫入類操作不會隱含 files.delete,因此只存檔的 App 無法刪除檔案。核准的 policy 沒列出的操作回傳 POLICY_DENIED;授權沒列出的操作回傳 DELEGATION_DENIED。
  • 授權的資料夾本身不能被重新命名、複製、移動或刪除(SCOPE_DENIED)。資料夾外或經過符號連結的路徑也是 SCOPE_DENIED;資料夾內不存在的路徑是 not-found。
  • Copy 與 Move 遇到已被使用的名稱會拒絕;delegate.ConflictOverwrite 會取代它。它們沒有 rename 政策。
  • 刪除資料夾需要 Recursive: true,否則回傳 recursive-required。刪除的項目會進入共用資料夾的資源回收筒。如果該共用資料夾沒有資源回收筒,Delete 會回傳 recycle-bin-disabled,除非你設定 Permanent: true。資源回收筒開啟時,即使設定 Permanent: true,DSM 仍會把項目留在回收筒。

Copy、Move 與 Delete 會啟動 File Station 背景任務,並回傳帶有 TaskID 的 TaskState。持續呼叫 TaskStatus,直到 State 為 finished、failed(附 Error)或 canceled。Progress 是 0 到 1 的比例。任務結束後,TaskStatus 會持續回傳相同的最終狀態。CancelTask 會停止任務。只有啟動任務的工作能追蹤它。撤銷授權會停止其執行中的任務。

files := job.Files()
task, err := files.Move(ctx, "/home/Downloads/album", "/home/Music", "")
for err == nil && task.State == "running" {
time.Sleep(time.Second)
task, err = files.TaskStatus(ctx, task.TaskID)
}

File Station 會逐項回報拒絕原因,而不只是整個任務的結果。executor 會讀取這些原因並回傳該項目的錯誤碼:沒有權限是 PERMISSION_DENIED、名稱已被使用是 destination-exists、路徑不存在是 not-found、磁碟區拒絕過長的名稱是 name-too-long、磁碟區已滿是 NO_SPACE,在檔案底下 CreateFolder 是 destination-not-directory。這些錯誤碼不可重試。刪除後項目仍在時,會以 PERMISSION_DENIED 或 DSM_FAILED 失敗。

executor 執行期間最多追蹤 64 個任務。套件重新啟動後,TaskStatus 會回傳 task-not-found。DSM 已不認得該任務時,State 為 unknown;此時用 Info 確認結果。DSM 能否把任務狀態回報給之後另一個 synowebapi 程序,尚未在 NAS 上驗證。

限制值
可移動的檔案大小16 GiB
排隊中的存檔16
同時複製數1
單次複製嘗試24 小時
目的地鍵1 到 64 個
目的地已有同名檔案只有 conflict: overwrite 會取代
name 中的資料夾16 層,總長 1024 位元組
files.read 讀取的檔案64 MiB
追蹤中的 File Station 任務64
conflict: rename 的替代名稱name (1) 到 name (99)
後端僅 native-go
架構x86_64、aarch64 與 armv7(armv7 尚未在 NAS 上驗證)
delegate.audit 回傳的事件256 筆
DSM 7.1 上的核准使用較舊的排程 API;見Root 操作的 DSM 7.1

生命週期的影響:

事件影響
停止套件存檔暫停。核准、授權與已確認的範圍都會保留。
升級只有在已核准的策略仍然相符時才會恢復存檔(符合 INFO 錨點的新 executor 不需核准),否則 App 停在 needs-admin-approval。進行中的複製會以 interrupted 結束。
解除安裝移除核准、授權、已確認的範圍、root 任務與這個 App 的應用程式權限規則,並停止 portal 網址。重新安裝需要重新核准。請見資料與移除。
變更 scope、destinations、operations、lifetime 或 entry 欄位管理員必須重新核准。
症狀或錯誤碼原因解法
needs-admin-approval管理員尚未核准,或已核准的策略已改變。管理員開啟設定並重新核准。
needs-scope-confirmationscope: install-selected,但尚未確認範圍。管理員在設定中選擇存放資料夾的範圍,並輸入 DSM 密碼確認。
disabled管理員停用了背景存檔。管理員選「啟用背景存檔」。使用者重新選擇目的地。
revoked這個目的地的授權已被撤銷。使用者重新選擇目的地。
reselection-required範圍已更換,或授權與已確認的範圍不符。使用者重新選擇目的地。
invalid-destination該鍵不在 destinations 內,或使用者對它沒有有效授權。修正鍵,或讓使用者選擇目的地。
destination-exists資料夾內已有同名檔案。以新的 JobID 搭配新的檔名,或設定 conflict。
invalid-path、path-too-deepname 中的資料夾違反上述規則,或超過 16 層。修正名稱,並使用新的 JobID。
destination-not-directoryname 中的某個資料夾其實是檔案、符號連結或另一個磁碟區。換一個資料夾名稱。
name-too-long磁碟區認為名稱過長而拒絕,例如加密共用資料夾的 143 位元組上限。改用較短的名稱。
PERMISSION_DENIED使用者無法寫入目的地資料夾。修正資料夾權限,之後使用者再選一次目的地。
NO_SPACE儲存空間已滿。釋出空間後再開始新的工作。
queue-full已有 16 個存檔在排隊。等待存檔完成。
recursive-required對資料夾呼叫 Delete 但沒有設定 Recursive。設定 Recursive: true。
recycle-bin-disabled共用資料夾沒有資源回收筒。在 DSM 開啟其資源回收筒,或設定 Permanent: true。
not-found、not-a-file路徑不存在,或 Read 遇到資料夾或特殊檔案。檢查路徑。
task-not-found、too-many-tasks任務屬於其他工作或因重新啟動而遺失,或已追蹤 64 個任務。用 Info 確認結果,或等待任務完成。
EXECUTOR_PAUSED套件已停止,或升級後尚未核准。啟動套件,或重新核准。
session_required沒有有效的 DSM 工作階段。使用者重新登入 DSM。
admin_required非管理員呼叫了僅限管理員的方法。以管理員身分呼叫。

其他錯誤碼請見錯誤碼。