@synopackageland/cli 0.6.1 → 0.7.1

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 (640) hide show
  1. package/CHANGELOG.md +846 -0
  2. package/README.md +81 -89
  3. package/dist/add.d.ts +4 -1
  4. package/dist/add.js +45 -17
  5. package/dist/build-spk/delegate-scope.d.ts +6 -0
  6. package/dist/build-spk/delegate-scope.js +34 -0
  7. package/dist/build-spk/dsm-arch.d.ts +7 -1
  8. package/dist/build-spk/dsm-arch.js +24 -28
  9. package/dist/build-spk/dsm-platforms.json +42 -0
  10. package/dist/build-spk/dsm-version.d.ts +11 -0
  11. package/dist/build-spk/dsm-version.js +17 -0
  12. package/dist/build-spk/gateway.js +8 -4
  13. package/dist/build-spk/generator.js +30 -5
  14. package/dist/build-spk/go-toolchain.d.ts +20 -0
  15. package/dist/build-spk/go-toolchain.js +44 -0
  16. package/dist/build-spk/notices.js +4 -1
  17. package/dist/build-spk/resource-worker.js +3 -0
  18. package/dist/build-spk/wizard-messages.d.ts +66 -0
  19. package/dist/build-spk/wizard-messages.js +75 -9
  20. package/dist/cli.js +28 -14
  21. package/dist/contract/schema-validate.js +8 -0
  22. package/dist/contract/validate.js +30 -0
  23. package/dist/deploy/deploy.d.ts +1 -1
  24. package/dist/deploy/deploy.js +8 -0
  25. package/dist/deploy/native-install-values.d.ts +17 -0
  26. package/dist/deploy/native-install-values.js +32 -0
  27. package/dist/deploy/plan.d.ts +6 -2
  28. package/dist/deploy/plan.js +58 -7
  29. package/dist/deploy/spk-version.js +1 -1
  30. package/dist/error-registry.js +54 -20
  31. package/dist/host/dsm-client.d.ts +8 -4
  32. package/dist/host/dsm-client.js +63 -21
  33. package/dist/host/dsm-http.js +24 -2
  34. package/dist/host/types.d.ts +3 -1
  35. package/dist/info.js +45 -10
  36. package/dist/init.d.ts +3 -0
  37. package/dist/init.js +50 -19
  38. package/dist/package-metadata.d.ts +21 -0
  39. package/dist/package-metadata.js +101 -0
  40. package/dist/portal/alias.d.ts +25 -0
  41. package/dist/portal/alias.js +48 -0
  42. package/dist/portal/package.d.ts +33 -0
  43. package/dist/portal/package.js +116 -0
  44. package/dist/runtime/delegate-contract.d.ts +79 -0
  45. package/dist/runtime/delegate-contract.js +27 -0
  46. package/dist/runtime/dsm-version.d.ts +6 -0
  47. package/dist/runtime/dsm-version.js +8 -0
  48. package/dist/runtime/executor-build.js +10 -6
  49. package/dist/runtime/executor-inspect.d.ts +3 -0
  50. package/dist/runtime/executor-inspect.js +11 -5
  51. package/dist/runtime/executor-policy.d.ts +12 -0
  52. package/dist/runtime/executor-policy.js +43 -3
  53. package/dist/runtime/executor-scope-task.d.ts +4 -0
  54. package/dist/runtime/executor-scope-task.js +21 -0
  55. package/dist/runtime/executor-tasks.d.ts +2 -1
  56. package/dist/runtime/executor-tasks.js +4 -4
  57. package/dist/skill.js +1 -0
  58. package/dist/standalone/api-permissions.d.ts +10 -0
  59. package/dist/standalone/api-permissions.js +19 -2
  60. package/dist/standalone/background.d.ts +16 -0
  61. package/dist/standalone/background.js +69 -2
  62. package/dist/standalone/binary.d.ts +1 -1
  63. package/dist/standalone/binary.js +22 -8
  64. package/dist/standalone/build.d.ts +1 -1
  65. package/dist/standalone/build.js +38 -8
  66. package/dist/standalone/daemon.d.ts +1 -0
  67. package/dist/standalone/daemon.js +10 -5
  68. package/dist/standalone/dsm-window.d.ts +3 -1
  69. package/dist/standalone/dsm-window.js +78 -49
  70. package/dist/standalone/inspect-spk.d.ts +8 -0
  71. package/dist/standalone/inspect-spk.js +110 -27
  72. package/dist/standalone/lifecycle.js +86 -8
  73. package/dist/standalone/manifest.d.ts +13 -0
  74. package/dist/standalone/manifest.js +57 -8
  75. package/dist/standalone/native-authorization-window.d.ts +1 -1
  76. package/dist/standalone/native-authorization-window.js +9 -1
  77. package/dist/standalone/native-layout.d.ts +3 -0
  78. package/dist/standalone/native-layout.js +37 -5
  79. package/dist/standalone/native-root-window.d.ts +4 -2
  80. package/dist/standalone/native-root-window.js +33 -18
  81. package/dist/standalone/portal-window.d.ts +21 -0
  82. package/dist/standalone/portal-window.js +155 -0
  83. package/dist/standalone/portal.d.ts +4 -0
  84. package/dist/standalone/portal.js +4 -0
  85. package/dist/standalone/resource.d.ts +5 -3
  86. package/dist/standalone/resource.js +6 -3
  87. package/dist/standalone/root-window.d.ts +4 -1
  88. package/dist/standalone/root-window.js +193 -36
  89. package/dist/standalone/spk.d.ts +1 -1
  90. package/dist/standalone/spk.js +25 -7
  91. package/dist/templates/_daemon/go.mod +1 -1
  92. package/dist/templates/_daemon/main.go +6 -3
  93. package/dist/templates/_daemon/main_test.go +82 -9
  94. package/dist/templates/_daemon/testdata/child.go +3 -0
  95. package/dist/templates/_executor/README.md +130 -16
  96. package/dist/templates/_executor/anchor.go +156 -0
  97. package/dist/templates/_executor/anchor_test.go +161 -0
  98. package/dist/templates/_executor/apppriv_review_test.go +1 -1
  99. package/dist/templates/_executor/cancel_contract_test.go +236 -0
  100. package/dist/templates/_executor/copy.go +64 -0
  101. package/dist/templates/_executor/copy_test.go +88 -0
  102. package/dist/templates/_executor/digest_review_test.go +2 -2
  103. package/dist/templates/_executor/dsm.go +63 -13
  104. package/dist/templates/_executor/dsm_test.go +95 -1
  105. package/dist/templates/_executor/executor.go +260 -49
  106. package/dist/templates/_executor/executor_test.go +36 -6
  107. package/dist/templates/_executor/go.mod +2 -0
  108. package/dist/templates/_executor/identity.go +27 -1
  109. package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
  110. package/dist/templates/_executor/identity_linux.go +82 -0
  111. package/dist/templates/_executor/identity_security_test.go +43 -0
  112. package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  113. package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  114. package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
  115. package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  116. package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
  117. package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
  118. package/dist/templates/_executor/lifecycle_test.go +11 -0
  119. package/dist/templates/_executor/link_linux.go +28 -0
  120. package/dist/templates/_executor/long_running_test.go +530 -0
  121. package/dist/templates/_executor/main.go +83 -25
  122. package/dist/templates/_executor/managed.go +3 -3
  123. package/dist/templates/_executor/managed_test.go +3 -3
  124. package/dist/templates/_executor/move_linux.go +490 -0
  125. package/dist/templates/_executor/move_linux_test.go +460 -0
  126. package/dist/templates/_executor/move_validation.go +46 -0
  127. package/dist/templates/_executor/move_validation_test.go +187 -0
  128. package/dist/templates/_executor/operations.go +926 -0
  129. package/dist/templates/_executor/path_test.go +18 -0
  130. package/dist/templates/_executor/pid.go +37 -0
  131. package/dist/templates/_executor/pid_test.go +113 -0
  132. package/dist/templates/_executor/platform_linux.go +24 -173
  133. package/dist/templates/_executor/platform_linux_test.go +9 -65
  134. package/dist/templates/_executor/platform_other.go +38 -4
  135. package/dist/templates/_executor/process.go +3 -0
  136. package/dist/templates/_executor/purge_log.go +18 -0
  137. package/dist/templates/_executor/records.go +242 -0
  138. package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
  139. package/dist/templates/_executor/scope_client.go +37 -0
  140. package/dist/templates/_executor/scope_contract.go +12 -0
  141. package/dist/templates/_executor/selected_scope.go +146 -0
  142. package/dist/templates/_executor/selected_scope_test.go +186 -0
  143. package/dist/templates/_executor/status_disabled_test.go +33 -0
  144. package/dist/templates/_executor/store.go +4 -3
  145. package/dist/templates/_executor/supervise.go +109 -0
  146. package/dist/templates/_executor/supervise_test.go +130 -0
  147. package/dist/templates/_executor/task_race_review_test.go +1 -1
  148. package/dist/templates/_executor/tasks.go +52 -15
  149. package/dist/templates/_executor/tasks_dsm71_test.go +114 -0
  150. package/dist/templates/_executor/tasks_review_test.go +1 -1
  151. package/dist/templates/_executor/tasks_test.go +62 -5
  152. package/dist/templates/_executor/upgrade_watch_test.go +13 -5
  153. package/dist/templates/_executor/watch.go +8 -0
  154. package/dist/templates/_executor/watch_test.go +23 -2
  155. package/dist/templates/_executor/write_review_linux_test.go +4 -12
  156. package/dist/templates/_gateway/go.mod +2 -0
  157. package/dist/templates/_shared/AGENTS.md +129 -0
  158. package/dist/templates/binary/dsm-package.yaml +24 -0
  159. package/dist/templates/binary/i18n/messages/de.json +4 -0
  160. package/dist/templates/binary/i18n/messages/en.json +4 -0
  161. package/dist/templates/binary/i18n/messages/es.json +4 -0
  162. package/dist/templates/binary/i18n/messages/fr.json +4 -0
  163. package/dist/templates/binary/i18n/messages/it.json +4 -0
  164. package/dist/templates/binary/i18n/messages/ja.json +4 -0
  165. package/dist/templates/binary/i18n/messages/ko.json +4 -0
  166. package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
  167. package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
  168. package/dist/templates/binary/ui/app.js +19 -4
  169. package/dist/templates/native-authorization/AuthorizationGate.vue +5 -3
  170. package/dist/templates/native-authorization/i18n/de.json +1 -0
  171. package/dist/templates/native-authorization/i18n/en.json +1 -0
  172. package/dist/templates/native-authorization/i18n/es.json +2 -1
  173. package/dist/templates/native-authorization/i18n/fr.json +1 -0
  174. package/dist/templates/native-authorization/i18n/it.json +11 -10
  175. package/dist/templates/native-authorization/i18n/ja.json +1 -0
  176. package/dist/templates/native-authorization/i18n/ko.json +10 -9
  177. package/dist/templates/native-authorization/i18n/zh-CN.json +6 -5
  178. package/dist/templates/native-authorization/i18n/zh-TW.json +6 -5
  179. package/dist/templates/native-go/README.md +43 -10
  180. package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
  181. package/dist/templates/native-go/backend/go.mod +1 -1
  182. package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
  183. package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  184. package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
  185. package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  186. package/dist/templates/native-go/backend/internal/api/server.go +18 -5
  187. package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  188. package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  189. package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
  190. package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
  191. package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
  192. package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
  193. package/dist/templates/native-go/backend/internal/background/store.go +16 -14
  194. package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
  195. package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  196. package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  197. package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  198. package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  199. package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  200. package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  201. package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
  202. package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  203. package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  204. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  205. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  206. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  207. package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  208. package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  209. package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  210. package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  211. package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  212. package/dist/templates/native-go/backend/internal/filestation/client.go +39 -3
  213. package/dist/templates/native-go/backend/internal/filestation/download_dsm71_test.go +82 -0
  214. package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  215. package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
  216. package/dist/templates/native-go/backend/internal/identity/README.md +5 -1
  217. package/dist/templates/native-go/backend/internal/identity/dsm.go +24 -5
  218. package/dist/templates/native-go/backend/internal/identity/http.go +1 -1
  219. package/dist/templates/native-go/backend/internal/identity/identity_test.go +48 -1
  220. package/dist/templates/native-go/backend/internal/identity/service.go +1 -1
  221. package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
  222. package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  223. package/dist/templates/native-go/dsm-package.yaml +20 -1
  224. package/dist/templates/native-go/i18n/glossary.json +2 -1
  225. package/dist/templates/native-go/i18n/messages/de.json +1 -0
  226. package/dist/templates/native-go/i18n/messages/en.json +1 -0
  227. package/dist/templates/native-go/i18n/messages/es.json +1 -0
  228. package/dist/templates/native-go/i18n/messages/fr.json +1 -0
  229. package/dist/templates/native-go/i18n/messages/it.json +1 -0
  230. package/dist/templates/native-go/i18n/messages/ja.json +1 -0
  231. package/dist/templates/native-go/i18n/messages/ko.json +1 -0
  232. package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
  233. package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
  234. package/dist/templates/native-go/ui/app.css +10 -0
  235. package/dist/templates/native-go/ui/app.js +11 -1
  236. package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
  237. package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  238. package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  239. package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  240. package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  241. package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  242. package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  243. package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  244. package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  245. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  246. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  247. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  248. package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  249. package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  250. package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  251. package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  252. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  253. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  254. package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  255. package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  256. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  257. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  258. package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  259. package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  260. package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  261. package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  262. package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  263. package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  264. package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  265. package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  266. package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  267. package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  268. package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  269. package/dist/templates/native-go-delegate/frontend/test/delegate.test.ts +15 -0
  270. package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  271. package/dist/templates/native-go-delegate/test.sh +1 -0
  272. package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  273. package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  274. package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  275. package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  276. package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  277. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  278. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  279. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  280. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  281. package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  282. package/dist/templates/native-go-root-ops/backend/internal/app/executor.go +30 -2
  283. package/dist/templates/native-go-root-ops/backend/internal/app/executor_test.go +64 -0
  284. package/dist/templates/native-go-vue/README.md +4 -0
  285. package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
  286. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  287. package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
  288. package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
  289. package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +54 -9
  290. package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  291. package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  292. package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  293. package/dist/templates/native-go-vue/frontend/src/delegate/executor-settings.test.ts +23 -0
  294. package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +8 -2
  295. package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  296. package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  297. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +47 -0
  298. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +13 -2
  299. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +47 -0
  300. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +47 -0
  301. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +47 -0
  302. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +47 -0
  303. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +47 -0
  304. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  305. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  306. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  307. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  308. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  309. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  310. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  311. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  312. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  313. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +47 -0
  314. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +16 -5
  315. package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  316. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  317. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  318. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  319. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  320. package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  321. package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  322. package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  323. package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  324. package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  325. package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  326. package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  327. package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  328. package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  329. package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  330. package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  331. package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  332. package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  333. package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  334. package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  335. package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  336. package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  337. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  338. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  339. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  340. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  341. package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  342. package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  343. package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  344. package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  345. package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  346. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  347. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  348. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  349. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  350. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  351. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  352. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  353. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  354. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  355. package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  356. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  357. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  358. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  359. package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  360. package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
  361. package/package.json +5 -1
  362. package/schema/package-metadata.schema.json +101 -0
  363. package/schema/synology-app.schema.json +112 -0
  364. package/skills/SKILL.md +106 -4
  365. package/skills/reference/create.md +101 -2
  366. package/skills/reference/deploy.md +2 -2
  367. package/skills/reference/files.md +113 -2
  368. package/skills/reference/identity.md +70 -1
  369. package/skills/reference/portal.md +17 -0
  370. package/skills/reference/ui-vue.md +12 -0
  371. package/templates/_daemon/go.mod +1 -1
  372. package/templates/_daemon/main.go +6 -3
  373. package/templates/_daemon/main_test.go +82 -9
  374. package/templates/_daemon/testdata/child.go +3 -0
  375. package/templates/_executor/README.md +130 -16
  376. package/templates/_executor/anchor.go +156 -0
  377. package/templates/_executor/anchor_test.go +161 -0
  378. package/templates/_executor/apppriv_review_test.go +1 -1
  379. package/templates/_executor/cancel_contract_test.go +236 -0
  380. package/templates/_executor/copy.go +64 -0
  381. package/templates/_executor/copy_test.go +88 -0
  382. package/templates/_executor/digest_review_test.go +2 -2
  383. package/templates/_executor/dsm.go +63 -13
  384. package/templates/_executor/dsm_test.go +95 -1
  385. package/templates/_executor/executor.go +260 -49
  386. package/templates/_executor/executor_test.go +36 -6
  387. package/templates/_executor/go.mod +2 -0
  388. package/templates/_executor/identity.go +27 -1
  389. package/templates/_executor/identity_kernel_linux_test.go +60 -0
  390. package/templates/_executor/identity_linux.go +82 -0
  391. package/templates/_executor/identity_security_test.go +43 -0
  392. package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  393. package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  394. package/templates/_executor/internal/taskscript/boot.sh +9 -9
  395. package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  396. package/templates/_executor/internal/taskscript/control.sh +15 -10
  397. package/templates/_executor/internal/taskscript/tasks.go +5 -3
  398. package/templates/_executor/lifecycle_test.go +11 -0
  399. package/templates/_executor/link_linux.go +28 -0
  400. package/templates/_executor/long_running_test.go +530 -0
  401. package/templates/_executor/main.go +83 -25
  402. package/templates/_executor/managed.go +3 -3
  403. package/templates/_executor/managed_test.go +3 -3
  404. package/templates/_executor/move_linux.go +490 -0
  405. package/templates/_executor/move_linux_test.go +460 -0
  406. package/templates/_executor/move_validation.go +46 -0
  407. package/templates/_executor/move_validation_test.go +187 -0
  408. package/templates/_executor/operations.go +926 -0
  409. package/templates/_executor/path_test.go +18 -0
  410. package/templates/_executor/pid.go +37 -0
  411. package/templates/_executor/pid_test.go +113 -0
  412. package/templates/_executor/platform_linux.go +24 -173
  413. package/templates/_executor/platform_linux_test.go +9 -65
  414. package/templates/_executor/platform_other.go +38 -4
  415. package/templates/_executor/process.go +3 -0
  416. package/templates/_executor/purge_log.go +18 -0
  417. package/templates/_executor/records.go +242 -0
  418. package/templates/_executor/scope_cancel_contract_test.go +91 -0
  419. package/templates/_executor/scope_client.go +37 -0
  420. package/templates/_executor/scope_contract.go +12 -0
  421. package/templates/_executor/selected_scope.go +146 -0
  422. package/templates/_executor/selected_scope_test.go +186 -0
  423. package/templates/_executor/status_disabled_test.go +33 -0
  424. package/templates/_executor/store.go +4 -3
  425. package/templates/_executor/supervise.go +109 -0
  426. package/templates/_executor/supervise_test.go +130 -0
  427. package/templates/_executor/task_race_review_test.go +1 -1
  428. package/templates/_executor/tasks.go +52 -15
  429. package/templates/_executor/tasks_dsm71_test.go +114 -0
  430. package/templates/_executor/tasks_review_test.go +1 -1
  431. package/templates/_executor/tasks_test.go +62 -5
  432. package/templates/_executor/upgrade_watch_test.go +13 -5
  433. package/templates/_executor/watch.go +8 -0
  434. package/templates/_executor/watch_test.go +23 -2
  435. package/templates/_executor/write_review_linux_test.go +4 -12
  436. package/templates/_gateway/go.mod +2 -0
  437. package/templates/_shared/AGENTS.md +129 -0
  438. package/templates/binary/dsm-package.yaml +24 -0
  439. package/templates/binary/i18n/messages/de.json +4 -0
  440. package/templates/binary/i18n/messages/en.json +4 -0
  441. package/templates/binary/i18n/messages/es.json +4 -0
  442. package/templates/binary/i18n/messages/fr.json +4 -0
  443. package/templates/binary/i18n/messages/it.json +4 -0
  444. package/templates/binary/i18n/messages/ja.json +4 -0
  445. package/templates/binary/i18n/messages/ko.json +4 -0
  446. package/templates/binary/i18n/messages/zh-CN.json +4 -0
  447. package/templates/binary/i18n/messages/zh-TW.json +4 -0
  448. package/templates/binary/ui/app.js +19 -4
  449. package/templates/native-authorization/AuthorizationGate.vue +5 -3
  450. package/templates/native-authorization/i18n/de.json +1 -0
  451. package/templates/native-authorization/i18n/en.json +1 -0
  452. package/templates/native-authorization/i18n/es.json +2 -1
  453. package/templates/native-authorization/i18n/fr.json +1 -0
  454. package/templates/native-authorization/i18n/it.json +11 -10
  455. package/templates/native-authorization/i18n/ja.json +1 -0
  456. package/templates/native-authorization/i18n/ko.json +10 -9
  457. package/templates/native-authorization/i18n/zh-CN.json +6 -5
  458. package/templates/native-authorization/i18n/zh-TW.json +6 -5
  459. package/templates/native-go/README.md +43 -10
  460. package/templates/native-go/backend/cmd/app/main.go +27 -7
  461. package/templates/native-go/backend/go.mod +1 -1
  462. package/templates/native-go/backend/internal/api/entry_test.go +47 -0
  463. package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  464. package/templates/native-go/backend/internal/api/registry.go +11 -0
  465. package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  466. package/templates/native-go/backend/internal/api/server.go +18 -5
  467. package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  468. package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  469. package/templates/native-go/backend/internal/app/portal.go +30 -0
  470. package/templates/native-go/backend/internal/app/portal_test.go +55 -0
  471. package/templates/native-go/backend/internal/app/routes.go +8 -2
  472. package/templates/native-go/backend/internal/background/runner.go +1 -1
  473. package/templates/native-go/backend/internal/background/store.go +16 -14
  474. package/templates/native-go/backend/internal/dsm/admin.go +8 -2
  475. package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  476. package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  477. package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  478. package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  479. package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  480. package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  481. package/templates/native-go/backend/internal/dsm/entry.go +177 -0
  482. package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  483. package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  484. package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  485. package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  486. package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  487. package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  488. package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  489. package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  490. package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  491. package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  492. package/templates/native-go/backend/internal/filestation/client.go +39 -3
  493. package/templates/native-go/backend/internal/filestation/download_dsm71_test.go +82 -0
  494. package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  495. package/templates/native-go/backend/internal/filestation/service.go +1 -1
  496. package/templates/native-go/backend/internal/identity/README.md +5 -1
  497. package/templates/native-go/backend/internal/identity/dsm.go +24 -5
  498. package/templates/native-go/backend/internal/identity/http.go +1 -1
  499. package/templates/native-go/backend/internal/identity/identity_test.go +48 -1
  500. package/templates/native-go/backend/internal/identity/service.go +1 -1
  501. package/templates/native-go/backend/internal/portal/portal.go +138 -0
  502. package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  503. package/templates/native-go/dsm-package.yaml +20 -1
  504. package/templates/native-go/i18n/glossary.json +2 -1
  505. package/templates/native-go/i18n/messages/de.json +1 -0
  506. package/templates/native-go/i18n/messages/en.json +1 -0
  507. package/templates/native-go/i18n/messages/es.json +1 -0
  508. package/templates/native-go/i18n/messages/fr.json +1 -0
  509. package/templates/native-go/i18n/messages/it.json +1 -0
  510. package/templates/native-go/i18n/messages/ja.json +1 -0
  511. package/templates/native-go/i18n/messages/ko.json +1 -0
  512. package/templates/native-go/i18n/messages/zh-CN.json +1 -0
  513. package/templates/native-go/i18n/messages/zh-TW.json +1 -0
  514. package/templates/native-go/ui/app.css +10 -0
  515. package/templates/native-go/ui/app.js +11 -1
  516. package/templates/native-go-delegate/DELEGATE.md +126 -5
  517. package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  518. package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  519. package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  520. package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  521. package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  522. package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  523. package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  524. package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  525. package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  526. package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  527. package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  528. package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  529. package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  530. package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  531. package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  532. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  533. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  534. package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  535. package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  536. package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  537. package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  538. package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  539. package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  540. package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  541. package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  542. package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  543. package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  544. package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  545. package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  546. package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  547. package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  548. package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  549. package/templates/native-go-delegate/frontend/test/delegate.test.ts +15 -0
  550. package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  551. package/templates/native-go-delegate/test.sh +1 -0
  552. package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  553. package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  554. package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  555. package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  556. package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  557. package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  558. package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  559. package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  560. package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  561. package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  562. package/templates/native-go-root-ops/backend/internal/app/executor.go +30 -2
  563. package/templates/native-go-root-ops/backend/internal/app/executor_test.go +64 -0
  564. package/templates/native-go-vue/README.md +4 -0
  565. package/templates/native-go-vue/frontend/package-lock.json +4 -4
  566. package/templates/native-go-vue/frontend/package.json +1 -1
  567. package/templates/native-go-vue/frontend/src/App.vue +18 -13
  568. package/templates/native-go-vue/frontend/src/context.ts +31 -0
  569. package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +54 -9
  570. package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  571. package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  572. package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  573. package/templates/native-go-vue/frontend/src/delegate/executor-settings.test.ts +23 -0
  574. package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +8 -2
  575. package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  576. package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  577. package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +47 -0
  578. package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +13 -2
  579. package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +47 -0
  580. package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +47 -0
  581. package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +47 -0
  582. package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +47 -0
  583. package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +47 -0
  584. package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  585. package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  586. package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  587. package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  588. package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  589. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  590. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  591. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  592. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  593. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +47 -0
  594. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +16 -5
  595. package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  596. package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  597. package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  598. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  599. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  600. package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  601. package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  602. package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  603. package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  604. package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  605. package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  606. package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  607. package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  608. package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  609. package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  610. package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  611. package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  612. package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  613. package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  614. package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  615. package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  616. package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  617. package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  618. package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  619. package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  620. package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  621. package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  622. package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  623. package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  624. package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  625. package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  626. package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  627. package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  628. package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  629. package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  630. package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  631. package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  632. package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  633. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  634. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  635. package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  636. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  637. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  638. package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  639. package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  640. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
@@ -0,0 +1,77 @@
1
+ package filestation
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "testing"
9
+ )
10
+
11
+ func TestCheckPermissionRequiresVerifiedSessionAndExplicitWriteResult(t *testing.T) {
12
+ for _, body := range []string{`{"blSkip":false}`, `{}`, `{"blSkip":true}`, `{"blSkip":null}`, `{"blSkip":false,"errors":[{"code":407}]}`} {
13
+ t.Run(body, func(t *testing.T) {
14
+ checks := 0
15
+ client := fakeClient(t, func(w http.ResponseWriter, r *http.Request) {
16
+ r.ParseForm()
17
+ switch r.Form.Get("api") {
18
+ case "SYNO.Core.NormalUser":
19
+ fmt.Fprint(w, `{"success":true,"data":{"username":"alice"}}`)
20
+ case "SYNO.FileStation.CheckPermission":
21
+ checks++
22
+ if r.Form.Get("version") != "3" || r.Form.Get("method") != "write" || r.Form.Get("path") != "/home" || r.Form.Get("filename") != ".synopkgland-permission-probe" || r.Form.Get("overwrite") != "false" || r.Form.Get("size") != "0" || r.Header.Get("Cookie") != "id=test-session" || r.Header.Get("X-SYNO-TOKEN") != "test-token" {
23
+ t.Error("incorrect permission check transport")
24
+ }
25
+ fmt.Fprintf(w, `{"success":true,"data":%s}`, body)
26
+ default:
27
+ t.Error("unexpected API")
28
+ }
29
+ })
30
+ service := NewService(client)
31
+ err := service.CheckPermission(context.Background(), callerFor(t, "alice"), sessionRequest(), "/home")
32
+ if (err == nil) != (body == `{"blSkip":false}`) || checks != 1 {
33
+ t.Fatalf("checks=%d err=%v", checks, err)
34
+ }
35
+ checks = 0
36
+ if err := service.CheckPermission(context.Background(), callerFor(t, "bob"), sessionRequest(), "/home"); err == nil || checks != 0 {
37
+ t.Fatal("mismatched subject checked write permission")
38
+ }
39
+ if err := service.CheckPermission(context.Background(), callerFor(t, "alice"), sessionRequest(), "/home/../etc"); err == nil || checks != 0 {
40
+ t.Fatal("invalid path checked")
41
+ }
42
+ })
43
+ }
44
+ }
45
+
46
+ func TestCheckPermissionValidatesCredentialPairBeforeDSM(t *testing.T) {
47
+ calls := 0
48
+ client := fakeClient(t, func(w http.ResponseWriter, r *http.Request) {
49
+ calls++
50
+ r.ParseForm()
51
+ if r.Form.Get("api") == "SYNO.Core.NormalUser" {
52
+ fmt.Fprint(w, `{"success":true,"data":{"username":"alice"}}`)
53
+ } else {
54
+ fmt.Fprint(w, `{"success":true,"data":{"blSkip":false}}`)
55
+ }
56
+ })
57
+ service := NewService(client)
58
+ for index, r := range []*http.Request{nil, httptest.NewRequest("POST", "/", nil), sessionRequest(), sessionRequest()} {
59
+ if r != nil && r.Header.Get("X-SYNO-TOKEN") != "" {
60
+ if index == 2 {
61
+ r.Header.Del("X-SYNO-TOKEN")
62
+ } else {
63
+ r.Header.Set("SynoToken", "different")
64
+ }
65
+ }
66
+ before := calls
67
+ if err := service.CheckPermission(context.Background(), callerFor(t, "alice"), r, "/home"); err == nil || calls != before {
68
+ t.Fatal("invalid session reached DSM", err, calls)
69
+ }
70
+ }
71
+ r := sessionRequest()
72
+ r.Header.Del("X-SYNO-TOKEN")
73
+ r.Header.Set("SynoToken", "test-token")
74
+ if err := service.CheckPermission(context.Background(), callerFor(t, "alice"), r, "/home"); err != nil {
75
+ t.Fatal("canonical alias refused", err)
76
+ }
77
+ }
@@ -21,6 +21,9 @@ const MaxBytes = 512 * 1024
21
21
  const maxMetadata = 1024 * 1024
22
22
  const PageSize = 100
23
23
 
24
+ // downloadPeek bounds how much of a DSM 7.1 download is inspected for an error envelope.
25
+ const downloadPeek = 4096
26
+
24
27
  // Client has no cookie jar or credentials. The destination is startup-owned,
25
28
  // literal loopback only; proxies, redirects and automatic retries are disabled.
26
29
  type Client struct {
@@ -269,15 +272,48 @@ func (c *Client) DownloadStream(ctx context.Context, r *http.Request, p string)
269
272
  if e != nil {
270
273
  return Download{}, e
271
274
  }
272
- disposition, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
273
- if disposition != "attachment" {
275
+ body := resp.Body
276
+ raw := resp.Header.Values("Content-Disposition")
277
+ if len(raw) == 0 {
278
+ // DSM 7.1 sends no Content-Disposition at all. Only a short body that is
279
+ // a complete DSM envelope is an error; everything else is the file.
280
+ if body, e = unlessEnvelope(resp); e != nil {
281
+ return Download{}, e
282
+ }
283
+ } else if disposition, _, _ := mime.ParseMediaType(raw[0]); len(raw) != 1 || disposition != "attachment" {
274
284
  e = decode(resp, nil)
275
285
  if e == nil {
276
286
  e = fail("unavailable")
277
287
  }
278
288
  return Download{}, e
279
289
  }
280
- return Download{Body: resp.Body, ContentType: resp.Header.Get("Content-Type"), ContentDisposition: resp.Header.Get("Content-Disposition"), ContentLength: resp.ContentLength}, nil
290
+ return Download{Body: body, ContentType: resp.Header.Get("Content-Type"), ContentDisposition: resp.Header.Get("Content-Disposition"), ContentLength: resp.ContentLength}, nil
291
+ }
292
+
293
+ // unlessEnvelope returns the whole body as a stream unless it fits in the peek
294
+ // and decodes as a DSM envelope, which is refused and never becomes content.
295
+ func unlessEnvelope(resp *http.Response) (io.ReadCloser, error) {
296
+ head, e := io.ReadAll(io.LimitReader(resp.Body, downloadPeek+1))
297
+ if e != nil {
298
+ resp.Body.Close()
299
+ return nil, fail("read_failed")
300
+ }
301
+ if len(head) <= downloadPeek {
302
+ var envelope struct {
303
+ Success *bool `json:"success"`
304
+ }
305
+ if json.Unmarshal(head, &envelope) == nil && envelope.Success != nil {
306
+ resp.Body = io.NopCloser(bytes.NewReader(head))
307
+ if e = decode(resp, nil); e == nil {
308
+ e = fail("unavailable")
309
+ }
310
+ return nil, e
311
+ }
312
+ }
313
+ return struct {
314
+ io.Reader
315
+ io.Closer
316
+ }{io.MultiReader(bytes.NewReader(head), resp.Body), resp.Body}, nil
281
317
  }
282
318
 
283
319
  // Read is the bounded package-private job import, using the same stream.
@@ -0,0 +1,82 @@
1
+ package filestation
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "io"
7
+ "net/http"
8
+ "strings"
9
+ "testing"
10
+ )
11
+
12
+ // DSM 7.1.1-42962 (vt-62, t-0231) answers Download v2 mode=download with the
13
+ // file bytes and no Content-Disposition header. Its Content-Type was not
14
+ // recorded, so every plausible type is covered.
15
+ func dsm71Download(contentType, body string) http.HandlerFunc {
16
+ return func(w http.ResponseWriter, r *http.Request) {
17
+ if contentType != "" {
18
+ w.Header().Set("Content-Type", contentType)
19
+ }
20
+ fmt.Fprint(w, body)
21
+ }
22
+ }
23
+
24
+ func TestDSM71DownloadWithoutDispositionReturnsFileBytes(t *testing.T) {
25
+ for _, tt := range []struct{ contentType, body string }{
26
+ {"text/plain", "t0231 fixture a.txt"},
27
+ {"application/octet-stream", "\x00\x01binary"},
28
+ {"", "no type"},
29
+ {"application/json", `{"name":"a user file"}`},
30
+ {"application/json", `[{"success":false}]`},
31
+ {"text/plain", strings.Repeat("y", downloadPeek+7)},
32
+ } {
33
+ t.Run(tt.contentType, func(t *testing.T) {
34
+ c := fakeClient(t, dsm71Download(tt.contentType, tt.body))
35
+ b, err := c.Read(context.Background(), sessionRequest(), "/home/a")
36
+ if err != nil || string(b) != tt.body {
37
+ t.Fatalf("%v %q", err, b)
38
+ }
39
+ })
40
+ }
41
+ }
42
+
43
+ func TestDSM71DownloadStreamsBeyondThePeek(t *testing.T) {
44
+ body := strings.Repeat("z", MaxBytes*3)
45
+ c := fakeClient(t, dsm71Download("application/octet-stream", body))
46
+ d, err := c.DownloadStream(context.Background(), sessionRequest(), "/home/a")
47
+ if err != nil {
48
+ t.Fatal(err)
49
+ }
50
+ defer d.Body.Close()
51
+ b, err := io.ReadAll(d.Body)
52
+ if err != nil || string(b) != body || d.ContentType != "application/octet-stream" {
53
+ t.Fatal(err, len(b))
54
+ }
55
+ }
56
+
57
+ func TestDSM71DownloadEnvelopeWithoutDispositionIsNeverContent(t *testing.T) {
58
+ for _, tt := range []struct{ contentType, body, want string }{
59
+ {"application/json; charset=utf-8", `{"success":false,"error":{"code":408}}`, "denied"},
60
+ {"text/plain", `{"success":false,"error":{"code":119}}`, "session_required"},
61
+ {"", ` {"error":{"code":407},"success":false}` + "\n", "denied"},
62
+ {"application/json", `{"success":true,"data":{}}`, "unavailable"},
63
+ } {
64
+ t.Run(tt.want, func(t *testing.T) {
65
+ c := fakeClient(t, dsm71Download(tt.contentType, tt.body))
66
+ b, err := c.Read(context.Background(), sessionRequest(), "/home/a")
67
+ if err == nil || len(b) != 0 || !strings.Contains(err.Error(), tt.want) {
68
+ t.Fatalf("%v %q", err, b)
69
+ }
70
+ })
71
+ }
72
+ }
73
+
74
+ func TestNonAttachmentDispositionStaysRefused(t *testing.T) {
75
+ c := fakeClient(t, func(w http.ResponseWriter, r *http.Request) {
76
+ w.Header().Set("Content-Disposition", `inline; filename="a"`)
77
+ fmt.Fprint(w, "abc")
78
+ })
79
+ if b, err := c.Read(context.Background(), sessionRequest(), "/home/a"); err == nil || len(b) != 0 {
80
+ t.Fatal("inline disposition admitted")
81
+ }
82
+ }
@@ -0,0 +1,39 @@
1
+ package filestation
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "sort"
7
+ "testing"
8
+ )
9
+
10
+ // Every files entry in the SDK policy must name a route this package serves.
11
+ func TestSDKPolicyListsExactlyTheFileRoutes(t *testing.T) {
12
+ raw, err := os.ReadFile("../api/sdk-permissions.json")
13
+ if err != nil {
14
+ t.Fatal(err)
15
+ }
16
+ var policy struct {
17
+ Files map[string]string `json:"files"`
18
+ }
19
+ if err := json.Unmarshal(raw, &policy); err != nil {
20
+ t.Fatal(err)
21
+ }
22
+ var declared, routed []string
23
+ for method := range policy.Files {
24
+ declared = append(declared, method)
25
+ }
26
+ for _, route := range (&Service{}).Routes() {
27
+ routed = append(routed, route.Method())
28
+ }
29
+ sort.Strings(declared)
30
+ sort.Strings(routed)
31
+ if len(declared) != len(routed) {
32
+ t.Fatalf("policy %v, routes %v", declared, routed)
33
+ }
34
+ for i := range declared {
35
+ if declared[i] != routed[i] {
36
+ t.Fatalf("policy %v, routes %v", declared, routed)
37
+ }
38
+ }
39
+ }
@@ -204,7 +204,7 @@ func (s *Service) discover(ctx context.Context, r *http.Request, folder string,
204
204
  // Browse lists share roots (empty folder) or one folder through the caller's
205
205
  // own DSM session, so File Station's current permissions decide what is
206
206
  // visible. It is read-only metadata and stores no grant; background jobs use it
207
- // to let an administrator pick the file that ReadFile will then import.
207
+ // to let an authenticated caller pick the file that ReadFile will then import.
208
208
  func (s *Service) Browse(ctx context.Context, c api.Caller, r *http.Request, folder string, offset int) (Page, error) {
209
209
  if !c.Authenticated() {
210
210
  return Page{}, fail("session_required")
@@ -16,7 +16,11 @@ The transport accepts same-origin JSON POST requests:
16
16
  - `identity.logout {}` removes the app session and clears the cookie.
17
17
 
18
18
  NormalUser.get is the only DSM cookie identity check; anonymous Initdata success
19
- cannot establish identity. Local `user.Lookup` binds the username to its UID.
19
+ cannot establish identity. DSM web logins enable SynoToken, and DSM refuses
20
+ their cookie alone (119), so the check also forwards the request's single
21
+ `X-SYNO-TOKEN` header, which the DSM window's `ctx.call` sends. Sessions without
22
+ SynoToken still verify with the cookie alone. The token is never put in a URL,
23
+ logged or stored. Local `user.Lookup` binds the username to its UID.
20
24
  Auth login creates a temporary SID, then immediately logs it out before issuing
21
25
  an app session. A logout failure refuses the app login. No password, OTP, DSM SID
22
26
  or DSM cookie is logged or persisted. Only hashes of app bearer IDs are stored.
@@ -9,6 +9,7 @@ import (
9
9
  "net/url"
10
10
  "strings"
11
11
  "time"
12
+ "unicode"
12
13
  )
13
14
 
14
15
  type dsmReply struct {
@@ -25,7 +26,9 @@ type dsmReply struct {
25
26
  } `json:"error"`
26
27
  }
27
28
 
28
- func (s *Service) dsmCall(ctx context.Context, form url.Values, cookie string) (dsmReply, error) {
29
+ // dsmCall sends token only as X-SYNO-TOKEN: DSM web logins enable SynoToken,
30
+ // and their cookie alone is refused with 119.
31
+ func (s *Service) dsmCall(ctx context.Context, form url.Values, cookie, token string) (dsmReply, error) {
29
32
  var out dsmReply
30
33
  ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
31
34
  defer cancel()
@@ -37,6 +40,9 @@ func (s *Service) dsmCall(ctx context.Context, form url.Values, cookie string) (
37
40
  if cookie != "" {
38
41
  req.AddCookie(&http.Cookie{Name: "id", Value: cookie})
39
42
  }
43
+ if token != "" {
44
+ req.Header.Set("X-SYNO-TOKEN", token)
45
+ }
40
46
  res, err := s.cfg.Client.Do(req)
41
47
  if err != nil {
42
48
  return out, errors.New("DSM unavailable")
@@ -48,19 +54,32 @@ func (s *Service) dsmCall(ctx context.Context, form url.Values, cookie string) (
48
54
  }
49
55
  return out, nil
50
56
  }
51
- func (s *Service) verify(ctx context.Context, cookie string) (Subject, error) {
52
- out, err := s.dsmCall(ctx, url.Values{"api": {"SYNO.Core.NormalUser"}, "method": {"get"}, "version": {"1"}}, cookie)
57
+
58
+ // verify checks the caller's DSM session with the request's cookie and, when the
59
+ // browser sent one, its SynoToken. Neither is stored or logged.
60
+ func (s *Service) verify(r *http.Request, cookie string) (Subject, error) {
61
+ token := synoToken(r)
62
+ out, err := s.dsmCall(r.Context(), url.Values{"api": {"SYNO.Core.NormalUser"}, "method": {"get"}, "version": {"1"}}, cookie, token)
53
63
  if err != nil || !out.Success {
54
64
  return Subject{}, errors.New("DSM session refused")
55
65
  }
56
66
  return s.resolve(out.Data.Username)
57
67
  }
68
+
69
+ // synoToken returns the single, well-formed X-SYNO-TOKEN header or "".
70
+ func synoToken(r *http.Request) string {
71
+ values := r.Header.Values("X-SYNO-TOKEN")
72
+ if len(values) != 1 || len(values[0]) > 4096 || strings.ContainsFunc(values[0], unicode.IsControl) {
73
+ return ""
74
+ }
75
+ return values[0]
76
+ }
58
77
  func (s *Service) login(ctx context.Context, account, password, otp string) (Subject, string) {
59
78
  form := url.Values{"api": {"SYNO.API.Auth"}, "method": {"login"}, "version": {"7"}, "account": {account}, "passwd": {password}, "format": {"sid"}}
60
79
  if otp != "" {
61
80
  form.Set("otp_code", otp)
62
81
  }
63
- out, err := s.dsmCall(ctx, form, "")
82
+ out, err := s.dsmCall(ctx, form, "", "")
64
83
  if err != nil {
65
84
  return Subject{}, "auth_unavailable"
66
85
  }
@@ -84,7 +103,7 @@ func (s *Service) login(ctx context.Context, account, password, otp string) (Sub
84
103
  }
85
104
  // The temporary DSM session exists only to prove identity. Dispose of it
86
105
  // before issuing an application session, including when local lookup fails.
87
- loggedOut, err := s.dsmCall(context.WithoutCancel(ctx), url.Values{"api": {"SYNO.API.Auth"}, "method": {"logout"}, "version": {"7"}, "_sid": {out.Data.SID}}, "")
106
+ loggedOut, err := s.dsmCall(context.WithoutCancel(ctx), url.Values{"api": {"SYNO.API.Auth"}, "method": {"logout"}, "version": {"7"}, "_sid": {out.Data.SID}}, "", "")
88
107
  if err != nil || !loggedOut.Success {
89
108
  return Subject{}, "auth_unavailable"
90
109
  }
@@ -237,7 +237,7 @@ func (s *Service) handle(w http.ResponseWriter, r *http.Request, req request) {
237
237
  code = "session_required"
238
238
  return nil
239
239
  }
240
- sub, err := s.verify(r.Context(), dc.Value)
240
+ sub, err := s.verify(r, dc.Value)
241
241
  if err != nil {
242
242
  code = "session_required"
243
243
  return nil
@@ -27,6 +27,11 @@ type fixture struct {
27
27
  logout int
28
28
  calls []string
29
29
  dir string
30
+ // synoToken makes the fake DSM session a SynoToken session: NormalUser.get
31
+ // then needs the cookie and this X-SYNO-TOKEN header, as DSM web logins do.
32
+ synoToken string
33
+ // token is the X-SYNO-TOKEN the browser sends with each App request.
34
+ token string
30
35
  }
31
36
 
32
37
  func setup(t *testing.T) *fixture {
@@ -40,7 +45,10 @@ func setup(t *testing.T) *fixture {
40
45
  switch name {
41
46
  case "SYNO.Core.NormalUser.get":
42
47
  c, err := r.Cookie("id")
43
- if f.valid && err == nil && c.Value == "valid-dsm-cookie" {
48
+ if r.Form.Has("SynoToken") {
49
+ t.Error("SynoToken must travel only in the header")
50
+ }
51
+ if f.valid && err == nil && c.Value == "valid-dsm-cookie" && (f.synoToken == "" || r.Header.Get("X-SYNO-TOKEN") == f.synoToken) {
44
52
  fmt.Fprint(w, `{"success":true,"data":{"username":"alice"}}`)
45
53
  } else {
46
54
  fmt.Fprint(w, `{"success":false,"error":{"code":119}}`)
@@ -93,6 +101,9 @@ func (f *fixture) call(method, params string, appCookie *http.Cookie, dsmCookie
93
101
  if dsmCookie {
94
102
  req.AddCookie(&http.Cookie{Name: "id", Value: "valid-dsm-cookie"})
95
103
  }
104
+ if f.token != "" {
105
+ req.Header.Set("X-SYNO-TOKEN", f.token)
106
+ }
96
107
  out := httptest.NewRecorder()
97
108
  f.service.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98
109
  id, err := f.service.Authenticate(r)
@@ -148,6 +159,42 @@ func TestCookieExchangeAndNoInitdataShortcut(t *testing.T) {
148
159
  }
149
160
  }
150
161
  }
162
+ func TestSynoTokenSessionBootstrapsAndRevalidates(t *testing.T) {
163
+ f := setup(t)
164
+ f.synoToken = "secret-syno-token"
165
+ if _, out := f.call("identity.session", `{"bootstrap":true}`, nil, true); errorCode(out) != "session_required" {
166
+ t.Fatalf("cookie alone must not pass a SynoToken session: %v", out)
167
+ }
168
+ f.token = "secret-syno-token"
169
+ rec, out := f.call("identity.session", `{"bootstrap":true}`, nil, true)
170
+ if errorCode(out) != "" {
171
+ t.Fatalf("bootstrap with SynoToken: %v", out)
172
+ }
173
+ c := sessionCookie(t, rec)
174
+ if _, out = f.call("greeting.set", `{"text":"x"}`, c, true); errorCode(out) != "" {
175
+ t.Fatalf("live revalidation with SynoToken: %v", out)
176
+ }
177
+ filepath.WalkDir(f.dir, func(path string, d os.DirEntry, err error) error {
178
+ if err != nil {
179
+ t.Fatal(err)
180
+ }
181
+ if raw, _ := os.ReadFile(path); !d.IsDir() && strings.Contains(string(raw), "secret-syno-token") {
182
+ t.Fatalf("SynoToken written to %s", path)
183
+ }
184
+ return nil
185
+ })
186
+ f.token = ""
187
+ if _, out = f.call("greeting.set", `{"text":"x"}`, c, true); errorCode(out) != "session_required" {
188
+ t.Fatalf("revalidation without SynoToken: %v", out)
189
+ }
190
+ }
191
+ func TestTokenlessSessionStillVerifiesWhenBrowserSendsToken(t *testing.T) {
192
+ f := setup(t)
193
+ f.token = "unused-token"
194
+ if _, out := f.call("identity.session", `{"bootstrap":true}`, nil, true); errorCode(out) != "" {
195
+ t.Fatal(out)
196
+ }
197
+ }
151
198
  func TestLoginOTPLogoutAndNoCredentialsOnDisk(t *testing.T) {
152
199
  f := setup(t)
153
200
  f.otp = true
@@ -145,7 +145,7 @@ func (s *Service) current(st *state, r *http.Request, revalidate bool) (string,
145
145
  if err != nil || dc.Value == "" {
146
146
  return "", record{}, errRevalidation
147
147
  }
148
- live, err := s.verify(r.Context(), dc.Value)
148
+ live, err := s.verify(r, dc.Value)
149
149
  if err != nil || live != sub {
150
150
  delete(st.Sessions, key)
151
151
  return "", record{}, api.ErrNoSession
@@ -0,0 +1,138 @@
1
+ // Package portal reads the DSM Application Portal alias an administrator set
2
+ // for this package. DSM owns the setting: the package never writes it, and
3
+ // anything this reader does not fully understand reads as Unavailable.
4
+ package portal
5
+
6
+ import (
7
+ "bytes"
8
+ "encoding/json"
9
+ "errors"
10
+ "io"
11
+ "io/fs"
12
+ "os"
13
+ "regexp"
14
+ "syscall"
15
+ "unicode/utf8"
16
+
17
+ "dsmapp/internal/jsonlimits"
18
+ )
19
+
20
+ // MaxConfigBytes bounds DSM's saved setting; its real size is a few hundred bytes.
21
+ const MaxConfigBytes = 64 << 10
22
+
23
+ const (
24
+ // Active means users can open the app at /<alias>/ after DSM sign-in.
25
+ Active = "active"
26
+ // Inactive means no administrator has set an alias yet.
27
+ Inactive = "inactive"
28
+ // Unavailable means the setting could not be read safely.
29
+ Unavailable = "unavailable"
30
+ )
31
+
32
+ // Status is what portal.status returns; Alias is set only when Active.
33
+ type Status struct {
34
+ State string `json:"state"`
35
+ Alias string `json:"alias,omitempty"`
36
+ }
37
+
38
+ // A single URL path segment; DSM's own validation is looser than this.
39
+ var aliasPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`)
40
+
41
+ // ConfigPath is where DSM saves the package's portal setting
42
+ // (/var/packages/<package>/etc is the package's @appconf directory).
43
+ func ConfigPath(packageID string) string {
44
+ return "/var/packages/" + packageID + "/etc/AppPortal.json"
45
+ }
46
+
47
+ // Read returns the saved alias when the setting belongs to appID.
48
+ func Read(path, appID string) Status {
49
+ raw, err := readBounded(path)
50
+ if errors.Is(err, fs.ErrNotExist) {
51
+ return Status{State: Inactive}
52
+ }
53
+ if err != nil {
54
+ return Status{State: Unavailable}
55
+ }
56
+ id, alias, ok := parse(raw)
57
+ if !ok || id != appID {
58
+ return Status{State: Unavailable}
59
+ }
60
+ if alias == "" {
61
+ return Status{State: Inactive}
62
+ }
63
+ if !aliasPattern.MatchString(alias) {
64
+ return Status{State: Unavailable}
65
+ }
66
+ return Status{State: Active, Alias: alias}
67
+ }
68
+
69
+ func readBounded(path string) ([]byte, error) {
70
+ f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK, 0)
71
+ if err != nil {
72
+ return nil, err
73
+ }
74
+ defer f.Close()
75
+ info, err := f.Stat()
76
+ if err != nil {
77
+ return nil, err
78
+ }
79
+ if !info.Mode().IsRegular() {
80
+ return nil, errors.New("not a regular file")
81
+ }
82
+ raw, err := io.ReadAll(io.LimitReader(f, MaxConfigBytes+1))
83
+ if err != nil {
84
+ return nil, err
85
+ }
86
+ if len(raw) > MaxConfigBytes {
87
+ return nil, errors.New("too large")
88
+ }
89
+ return raw, nil
90
+ }
91
+
92
+ // parse accepts exactly one JSON object with unique top-level keys, a string
93
+ // id and a string or null alias; other DSM fields are skipped unread.
94
+ func parse(raw []byte) (id, alias string, ok bool) {
95
+ if !utf8.Valid(raw) || !jsonlimits.WithinLimits(raw) {
96
+ return "", "", false
97
+ }
98
+ d := json.NewDecoder(bytes.NewReader(raw))
99
+ if token, err := d.Token(); err != nil || token != json.Delim('{') {
100
+ return "", "", false
101
+ }
102
+ seen := map[string]bool{}
103
+ hasID := false
104
+ for d.More() {
105
+ token, err := d.Token()
106
+ key, isKey := token.(string)
107
+ if err != nil || !isKey || seen[key] {
108
+ return "", "", false
109
+ }
110
+ seen[key] = true
111
+ var value json.RawMessage
112
+ if err := d.Decode(&value); err != nil {
113
+ return "", "", false
114
+ }
115
+ switch key {
116
+ case "id":
117
+ if json.Unmarshal(value, &id) != nil || id == "" {
118
+ return "", "", false
119
+ }
120
+ hasID = true
121
+ case "alias":
122
+ var text *string
123
+ if json.Unmarshal(value, &text) != nil {
124
+ return "", "", false
125
+ }
126
+ if text != nil {
127
+ alias = *text
128
+ }
129
+ }
130
+ }
131
+ if token, err := d.Token(); err != nil || token != json.Delim('}') {
132
+ return "", "", false
133
+ }
134
+ if _, err := d.Token(); err != io.EOF {
135
+ return "", "", false
136
+ }
137
+ return id, alias, hasID
138
+ }