@synopackageland/cli 0.4.1 → 0.5.0

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 (631) hide show
  1. package/README.md +7 -3
  2. package/dist/build-spk/delete-app-data.d.ts +27 -7
  3. package/dist/build-spk/delete-app-data.js +35 -34
  4. package/dist/build-spk/dsm-arch.d.ts +6 -1
  5. package/dist/build-spk/dsm-arch.js +25 -1
  6. package/dist/build-spk/dsm-version.d.ts +14 -0
  7. package/dist/build-spk/dsm-version.js +47 -0
  8. package/dist/build-spk/gateway.d.ts +31 -0
  9. package/dist/build-spk/gateway.js +55 -0
  10. package/dist/build-spk/generator.d.ts +5 -0
  11. package/dist/build-spk/generator.js +103 -107
  12. package/dist/build-spk/install-wizard.d.ts +20 -0
  13. package/dist/build-spk/install-wizard.js +76 -0
  14. package/dist/build-spk/native-lifecycle.d.ts +5 -0
  15. package/dist/build-spk/native-lifecycle.js +12 -2
  16. package/dist/build-spk/resource-worker.d.ts +9 -4
  17. package/dist/build-spk/resource-worker.js +15 -6
  18. package/dist/build-spk/share-combobox.d.ts +3 -0
  19. package/dist/build-spk/share-combobox.js +27 -0
  20. package/dist/build-spk/uninstall-wizard.d.ts +14 -0
  21. package/dist/build-spk/uninstall-wizard.js +33 -0
  22. package/dist/build-spk/wizard-i18n.d.ts +18 -0
  23. package/dist/build-spk/wizard-i18n.js +38 -0
  24. package/dist/build-spk/wizard-messages.d.ts +388 -0
  25. package/dist/build-spk/wizard-messages.js +387 -0
  26. package/dist/cli.js +130 -30
  27. package/dist/compose/analyze.d.ts +20 -0
  28. package/dist/compose/analyze.js +75 -11
  29. package/dist/contract/validate.js +33 -1
  30. package/dist/contract/web-launch.d.ts +22 -0
  31. package/dist/contract/web-launch.js +37 -0
  32. package/dist/deploy/deploy.d.ts +3 -0
  33. package/dist/deploy/deploy.js +11 -2
  34. package/dist/error-registry.js +60 -1
  35. package/dist/host/open-url.d.ts +2 -0
  36. package/dist/host/open-url.js +4 -1
  37. package/dist/images/pin.d.ts +52 -0
  38. package/dist/images/pin.js +249 -0
  39. package/dist/init.d.ts +27 -3
  40. package/dist/init.js +104 -15
  41. package/dist/inspect.d.ts +3 -0
  42. package/dist/inspect.js +15 -3
  43. package/dist/standalone/background.d.ts +21 -0
  44. package/dist/standalone/background.js +52 -0
  45. package/dist/standalone/binary.d.ts +1 -0
  46. package/dist/standalone/binary.js +1 -1
  47. package/dist/standalone/build.d.ts +2 -0
  48. package/dist/standalone/build.js +38 -6
  49. package/dist/standalone/dsm-window.js +2 -2
  50. package/dist/standalone/helper-inventory.d.ts +15 -0
  51. package/dist/standalone/helper-inventory.js +9 -0
  52. package/dist/standalone/helper.d.ts +1 -0
  53. package/dist/standalone/helper.js +28 -10
  54. package/dist/standalone/host-adapter.d.ts +1 -1
  55. package/dist/standalone/host-adapter.js +33 -15
  56. package/dist/standalone/host-file-navigation.d.ts +7 -0
  57. package/dist/standalone/host-file-navigation.js +172 -0
  58. package/dist/standalone/inspect-helper.js +9 -4
  59. package/dist/standalone/inspect-spk.d.ts +1 -1
  60. package/dist/standalone/inspect-spk.js +200 -7
  61. package/dist/standalone/job-diagnostics.d.ts +1 -0
  62. package/dist/standalone/job-diagnostics.js +49 -0
  63. package/dist/standalone/lifecycle.js +12 -2
  64. package/dist/standalone/manifest.d.ts +15 -2
  65. package/dist/standalone/manifest.js +37 -6
  66. package/dist/standalone/native-layout.d.ts +74 -0
  67. package/dist/standalone/native-layout.js +318 -0
  68. package/dist/standalone/resource.d.ts +11 -0
  69. package/dist/standalone/resource.js +13 -0
  70. package/dist/standalone/root-window.d.ts +3 -1
  71. package/dist/standalone/root-window.js +156 -38
  72. package/dist/standalone/shared-folder.d.ts +39 -0
  73. package/dist/standalone/shared-folder.js +141 -0
  74. package/dist/standalone/spk.d.ts +2 -0
  75. package/dist/standalone/spk.js +33 -3
  76. package/dist/standalone/worker-contract-json.d.ts +8 -0
  77. package/dist/standalone/worker-contract-json.js +141 -0
  78. package/dist/standalone/worker-contract.d.ts +189 -0
  79. package/dist/standalone/worker-contract.js +175 -0
  80. package/dist/standalone/worker-registry.d.ts +40 -0
  81. package/dist/standalone/worker-registry.js +167 -0
  82. package/dist/templates/_daemon/go.mod +2 -0
  83. package/dist/templates/_gateway/auth.go +101 -0
  84. package/dist/templates/_gateway/auth_test.go +216 -0
  85. package/dist/templates/_gateway/cgi.go +133 -0
  86. package/dist/templates/_gateway/cgi_test.go +156 -0
  87. package/dist/templates/_gateway/csrf.go +73 -0
  88. package/dist/templates/_gateway/csrf_test.go +155 -0
  89. package/dist/templates/_gateway/directory.go +108 -0
  90. package/dist/templates/_gateway/directory_test.go +202 -0
  91. package/dist/templates/_gateway/gateway.go +412 -0
  92. package/dist/templates/_gateway/gateway_test.go +676 -0
  93. package/dist/templates/_gateway/go.mod +3 -0
  94. package/dist/templates/_gateway/groups.go +66 -0
  95. package/dist/templates/_gateway/main.go +45 -0
  96. package/dist/templates/binary/synology-app.yaml +1 -1
  97. package/dist/templates/compose-import/compose.yaml +1 -1
  98. package/dist/templates/compose-import/synology-app.yaml +1 -1
  99. package/dist/templates/hello-files/compose.yaml +1 -1
  100. package/dist/templates/hello-files/synology-app.yaml +1 -1
  101. package/dist/templates/hello-web/compose.yaml +5 -2
  102. package/dist/templates/hello-web/synology-app.yaml +4 -1
  103. package/dist/templates/hello-web-page/compose.yaml +5 -2
  104. package/dist/templates/hello-web-page/synology-app.yaml +4 -1
  105. package/dist/templates/native-go/README.md +93 -4
  106. package/dist/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +3 -1
  107. package/dist/templates/native-go/backend/cmd/app/main.go +17 -0
  108. package/dist/templates/native-go/backend/cmd/app/sdk_main.go +60 -0
  109. package/dist/templates/native-go/backend/cmd/background-worker/main.go +38 -0
  110. package/dist/templates/native-go/backend/cmd/helper/main.go +10 -1
  111. package/dist/templates/native-go/backend/cmd/helper/sdk_registry.go +19 -0
  112. package/dist/templates/native-go/backend/go.mod +5 -3
  113. package/dist/templates/native-go/backend/go.sum +4 -4
  114. package/dist/templates/native-go/backend/internal/api/registry.go +17 -6
  115. package/dist/templates/native-go/backend/internal/api/server.go +7 -1
  116. package/dist/templates/native-go/backend/internal/app/background.go +270 -0
  117. package/dist/templates/native-go/backend/internal/app/background_none.go +8 -0
  118. package/dist/templates/native-go/backend/internal/app/background_owner.go +59 -0
  119. package/dist/templates/native-go/backend/internal/app/background_owner_test.go +145 -0
  120. package/dist/templates/native-go/backend/internal/app/background_schedule.go +74 -0
  121. package/dist/templates/native-go/backend/internal/app/background_schedule_test.go +62 -0
  122. package/dist/templates/native-go/backend/internal/app/background_test.go +346 -0
  123. package/dist/templates/native-go/backend/internal/app/integration_state.go +16 -0
  124. package/dist/templates/native-go/backend/internal/app/migrations.go +41 -0
  125. package/dist/templates/native-go/backend/internal/app/migrations_test.go +41 -0
  126. package/dist/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +8 -0
  127. package/dist/templates/native-go/backend/internal/app/routes.go +17 -3
  128. package/dist/templates/native-go/backend/internal/app/routes_test.go +20 -3
  129. package/dist/templates/native-go/backend/internal/app/sdk_jobs.go +70 -0
  130. package/dist/templates/native-go/backend/internal/app/sdk_jobs_test.go +116 -0
  131. package/dist/templates/native-go/backend/internal/app/sdk_routes.go +174 -0
  132. package/dist/templates/native-go/backend/internal/app/session_files.go +15 -0
  133. package/dist/templates/native-go/backend/internal/app/session_files_test.go +26 -0
  134. package/dist/templates/native-go/backend/internal/app/shared_folder.go +144 -0
  135. package/dist/templates/native-go/backend/internal/app/shared_folder_test.go +158 -0
  136. package/dist/templates/native-go/backend/internal/background/access.go +57 -0
  137. package/dist/templates/native-go/backend/internal/background/owner_test.go +126 -0
  138. package/dist/templates/native-go/backend/internal/background/runner.go +185 -0
  139. package/dist/templates/native-go/backend/internal/background/runner_test.go +209 -0
  140. package/dist/templates/native-go/backend/internal/background/schedule.go +300 -0
  141. package/dist/templates/native-go/backend/internal/background/schedule_test.go +335 -0
  142. package/dist/templates/native-go/backend/internal/background/store.go +807 -0
  143. package/dist/templates/native-go/backend/internal/background/store_test.go +407 -0
  144. package/dist/templates/native-go/backend/internal/datamigrate/migrate.go +191 -0
  145. package/dist/templates/native-go/backend/internal/datamigrate/migrate_test.go +177 -0
  146. package/dist/templates/native-go/backend/internal/filestation/browse_test.go +67 -0
  147. package/dist/templates/native-go/backend/internal/filestation/client.go +251 -0
  148. package/dist/templates/native-go/backend/internal/filestation/client_test.go +155 -0
  149. package/dist/templates/native-go/backend/internal/filestation/doc.go +4 -0
  150. package/dist/templates/native-go/backend/internal/filestation/read_file_test.go +90 -0
  151. package/dist/templates/native-go/backend/internal/filestation/repository.go +207 -0
  152. package/dist/templates/native-go/backend/internal/filestation/repository_test.go +104 -0
  153. package/dist/templates/native-go/backend/internal/filestation/sdk_session.go +241 -0
  154. package/dist/templates/native-go/backend/internal/filestation/sdk_session_test.go +183 -0
  155. package/dist/templates/native-go/backend/internal/filestation/service.go +254 -0
  156. package/dist/templates/native-go/backend/internal/filestation/service_test.go +94 -0
  157. package/dist/templates/native-go/backend/internal/filestation/session_semantics_test.go +383 -0
  158. package/dist/templates/native-go/backend/internal/filestation/write.go +72 -0
  159. package/dist/templates/native-go/backend/internal/filestation/write_test.go +123 -0
  160. package/dist/templates/native-go/backend/internal/helper/background_proof.go +163 -0
  161. package/dist/templates/native-go/backend/internal/helper/background_proof_contract.go +46 -0
  162. package/dist/templates/native-go/backend/internal/helper/background_proof_test.go +161 -0
  163. package/dist/templates/native-go/backend/internal/helper/data_contract.go +3 -0
  164. package/dist/templates/native-go/backend/internal/helper/data_protocol.go +3 -0
  165. package/dist/templates/native-go/backend/internal/helper/data_protocol_test.go +1 -1
  166. package/dist/templates/native-go/backend/internal/helper/grant_authority.go +3 -0
  167. package/dist/templates/native-go/backend/internal/helper/grant_authority_test.go +1 -1
  168. package/dist/templates/native-go/backend/internal/helper/grant_repository_linux.go +2 -1
  169. package/dist/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +4 -3
  170. package/dist/templates/native-go/backend/internal/helper/grant_snapshot.go +3 -0
  171. package/dist/templates/native-go/backend/internal/helper/jobcontract/bindings.go +48 -0
  172. package/dist/templates/native-go/backend/internal/helper/jobcontract/contract_test.go +436 -0
  173. package/dist/templates/native-go/backend/internal/helper/jobcontract/decode.go +185 -0
  174. package/dist/templates/native-go/backend/internal/helper/jobcontract/digest.go +55 -0
  175. package/dist/templates/native-go/backend/internal/helper/jobcontract/registry.go +85 -0
  176. package/dist/templates/native-go/backend/internal/helper/jobcontract/registry_test.go +73 -0
  177. package/dist/templates/native-go/backend/internal/helper/jobcontract/result.go +53 -0
  178. package/dist/templates/native-go/backend/internal/helper/jobcontract/stub.go +8 -0
  179. package/dist/templates/native-go/backend/internal/helper/jobcontract/testdata/input-digests.json +127 -0
  180. package/dist/templates/native-go/backend/internal/helper/jobcontract/types.go +234 -0
  181. package/dist/templates/native-go/backend/internal/helper/jobcontract/types_test.go +29 -0
  182. package/dist/templates/native-go/backend/internal/helper/jobcontract/validate.go +462 -0
  183. package/dist/templates/native-go/backend/internal/helper/legacy_authority_schema_linux_test.go +34 -0
  184. package/dist/templates/native-go/backend/internal/helper/legacy_authority_schema_test.go +6 -0
  185. package/dist/templates/native-go/backend/internal/helper/management.go +79 -0
  186. package/dist/templates/native-go/backend/internal/helper/observation.go +42 -2
  187. package/dist/templates/native-go/backend/internal/helper/observation_health_test.go +13 -1
  188. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic.go +56 -10
  189. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +6 -0
  190. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_writer.go +40 -0
  191. package/dist/templates/native-go/backend/internal/helper/process_linux.go +3 -0
  192. package/dist/templates/native-go/backend/internal/helper/query_deadline_linux_test.go +115 -0
  193. package/dist/templates/native-go/backend/internal/helper/query_pending_diagnostic_test.go +254 -0
  194. package/dist/templates/native-go/backend/internal/helper/relaxed_invalidation.go +3 -0
  195. package/dist/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +23 -4
  196. package/dist/templates/native-go/backend/internal/helper/sdk_assembly_linux.go +51 -0
  197. package/dist/templates/native-go/backend/internal/helper/sdk_attach.go +83 -0
  198. package/dist/templates/native-go/backend/internal/helper/sdk_attach_contract.go +32 -0
  199. package/dist/templates/native-go/backend/internal/helper/sdk_attach_test.go +184 -0
  200. package/dist/templates/native-go/backend/internal/helper/sdk_authority_schema_test.go +6 -0
  201. package/dist/templates/native-go/backend/internal/helper/sdk_background_authority.go +832 -0
  202. package/dist/templates/native-go/backend/internal/helper/sdk_background_authority_test.go +714 -0
  203. package/dist/templates/native-go/backend/internal/helper/sdk_background_invalidation.go +218 -0
  204. package/dist/templates/native-go/backend/internal/helper/sdk_background_lease.go +218 -0
  205. package/dist/templates/native-go/backend/internal/helper/sdk_background_snapshot.go +110 -0
  206. package/dist/templates/native-go/backend/internal/helper/sdk_background_store_linux_test.go +261 -0
  207. package/dist/templates/native-go/backend/internal/helper/sdk_background_store_test.go +58 -0
  208. package/dist/templates/native-go/backend/internal/helper/sdk_bindings.go +60 -0
  209. package/dist/templates/native-go/backend/internal/helper/sdk_data_contract.go +251 -0
  210. package/dist/templates/native-go/backend/internal/helper/sdk_data_protocol.go +424 -0
  211. package/dist/templates/native-go/backend/internal/helper/sdk_dispatch_absence_test.go +51 -0
  212. package/dist/templates/native-go/backend/internal/helper/sdk_display.go +488 -0
  213. package/dist/templates/native-go/backend/internal/helper/sdk_display_contract.go +121 -0
  214. package/dist/templates/native-go/backend/internal/helper/sdk_display_test.go +459 -0
  215. package/dist/templates/native-go/backend/internal/helper/sdk_grant_authority.go +559 -0
  216. package/dist/templates/native-go/backend/internal/helper/sdk_grant_repository_linux.go +417 -0
  217. package/dist/templates/native-go/backend/internal/helper/sdk_grant_snapshot.go +94 -0
  218. package/dist/templates/native-go/backend/internal/helper/sdk_history.go +80 -0
  219. package/dist/templates/native-go/backend/internal/helper/sdk_history_test.go +76 -0
  220. package/dist/templates/native-go/backend/internal/helper/sdk_job.go +697 -0
  221. package/dist/templates/native-go/backend/internal/helper/sdk_job_child_linux.go +309 -0
  222. package/dist/templates/native-go/backend/internal/helper/sdk_job_contract.go +122 -0
  223. package/dist/templates/native-go/backend/internal/helper/sdk_job_linux_test.go +159 -0
  224. package/dist/templates/native-go/backend/internal/helper/sdk_job_process_linux.go +327 -0
  225. package/dist/templates/native-go/backend/internal/helper/sdk_job_test.go +501 -0
  226. package/dist/templates/native-go/backend/internal/helper/sdk_job_vm_linux_test.go +343 -0
  227. package/dist/templates/native-go/backend/internal/helper/sdk_journal.go +77 -0
  228. package/dist/templates/native-go/backend/internal/helper/sdk_journal_linux.go +244 -0
  229. package/dist/templates/native-go/backend/internal/helper/sdk_journal_linux_test.go +295 -0
  230. package/dist/templates/native-go/backend/internal/helper/sdk_journal_test.go +71 -0
  231. package/dist/templates/native-go/backend/internal/helper/sdk_picker_test.go +50 -0
  232. package/dist/templates/native-go/backend/internal/helper/sdk_process_linux.go +184 -0
  233. package/dist/templates/native-go/backend/internal/helper/sdk_public_vm_linux_test.go +129 -0
  234. package/dist/templates/native-go/backend/internal/helper/sdk_relaxed_invalidation.go +242 -0
  235. package/dist/templates/native-go/backend/internal/helper/sdk_rpc.go +299 -0
  236. package/dist/templates/native-go/backend/internal/helper/sdk_rpc_contract.go +70 -0
  237. package/dist/templates/native-go/backend/internal/helper/sdk_rpc_test.go +135 -0
  238. package/dist/templates/native-go/backend/internal/helper/sdk_server_linux.go +152 -0
  239. package/dist/templates/native-go/backend/internal/helper/sdk_session.go +677 -0
  240. package/dist/templates/native-go/backend/internal/helper/sdk_session_contract.go +72 -0
  241. package/dist/templates/native-go/backend/internal/helper/sdk_session_fence_test.go +215 -0
  242. package/dist/templates/native-go/backend/internal/helper/sdk_session_test.go +475 -0
  243. package/dist/templates/native-go/backend/internal/helper/sdk_supervisor_frame.go +78 -0
  244. package/dist/templates/native-go/backend/internal/helper/sdk_supervisor_linux.go +414 -0
  245. package/dist/templates/native-go/backend/internal/helper/sdk_transport.go +79 -0
  246. package/dist/templates/native-go/backend/internal/helper/sdk_transport_linux.go +82 -0
  247. package/dist/templates/native-go/backend/internal/helper/sdk_transport_test.go +64 -0
  248. package/dist/templates/native-go/backend/internal/helper/server_linux.go +3 -0
  249. package/dist/templates/native-go/backend/internal/helper/stale_management_linux_test.go +126 -0
  250. package/dist/templates/native-go/backend/internal/helper/stale_management_test.go +122 -0
  251. package/dist/templates/native-go/backend/internal/helper/startup_diagnostic.go +68 -0
  252. package/dist/templates/native-go/backend/internal/helper/startup_diagnostic_linux_test.go +126 -0
  253. package/dist/templates/native-go/backend/internal/helper/startup_diagnostic_test.go +117 -0
  254. package/dist/templates/native-go/backend/internal/helper/status.go +22 -13
  255. package/dist/templates/native-go/backend/internal/helper/status_test.go +45 -0
  256. package/dist/templates/native-go/backend/internal/helper/supervision.go +1 -1
  257. package/dist/templates/native-go/backend/internal/helper/supervisor_frame.go +3 -0
  258. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +6 -2
  259. package/dist/templates/native-go/backend/internal/helper/unit_connection_test.go +22 -4
  260. package/dist/templates/native-go/backend/internal/helper/unit_linux.go +7 -3
  261. package/dist/templates/native-go/backend/internal/helper/unit_query_linux.go +5 -1
  262. package/dist/templates/native-go/backend/internal/helper/write_files_linux.go +7 -4
  263. package/dist/templates/native-go/backend/internal/helper/write_files_linux_test.go +26 -0
  264. package/dist/templates/native-go/dsm-package.yaml +1 -1
  265. package/dist/templates/native-go-background/BACKGROUND.md +111 -0
  266. package/dist/templates/native-go-background/backend/internal/app/background_on.go +7 -0
  267. package/dist/templates/native-go-background/backend/internal/app/background_routes_test.go +32 -0
  268. package/dist/templates/native-go-background/frontend/src/background/BackgroundPanel.vue +178 -0
  269. package/dist/templates/native-go-background/frontend/src/background/i18n/de.json +83 -0
  270. package/dist/templates/native-go-background/frontend/src/background/i18n/en.json +83 -0
  271. package/dist/templates/native-go-background/frontend/src/background/i18n/es.json +83 -0
  272. package/dist/templates/native-go-background/frontend/src/background/i18n/fr.json +83 -0
  273. package/dist/templates/native-go-background/frontend/src/background/i18n/it.json +83 -0
  274. package/dist/templates/native-go-background/frontend/src/background/i18n/ja.json +83 -0
  275. package/dist/templates/native-go-background/frontend/src/background/i18n/ko.json +83 -0
  276. package/dist/templates/native-go-background/frontend/src/background/i18n/zh-CN.json +83 -0
  277. package/dist/templates/native-go-background/frontend/src/background/i18n/zh-TW.json +83 -0
  278. package/dist/templates/native-go-background/frontend/src/background/i18n.ts +19 -0
  279. package/dist/templates/native-go-background/frontend/src/background/picker-provider.ts +13 -0
  280. package/dist/templates/native-go-background/frontend/test/background.test.ts +179 -0
  281. package/dist/templates/native-go-integration/backend/internal/app/personal.go +2 -2
  282. package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +37 -0
  283. package/dist/templates/native-go-vue/README.md +38 -7
  284. package/dist/templates/native-go-vue/frontend/DESIGN.md +15 -3
  285. package/dist/templates/native-go-vue/frontend/README-JOBS.md +75 -0
  286. package/dist/templates/native-go-vue/frontend/README-M2.md +64 -0
  287. package/dist/templates/native-go-vue/frontend/package-lock.json +5 -4
  288. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  289. package/dist/templates/native-go-vue/frontend/src/App.vue +39 -11
  290. package/dist/templates/native-go-vue/frontend/src/M2App.vue +57 -0
  291. package/dist/templates/native-go-vue/frontend/src/SDKApp.vue +203 -0
  292. package/dist/templates/native-go-vue/frontend/src/jobs/InteractiveJobForm.vue +70 -0
  293. package/dist/templates/native-go-vue/frontend/src/jobs/JobsAdminPanel.vue +33 -0
  294. package/dist/templates/native-go-vue/frontend/src/jobs/JobsPanel.vue +135 -0
  295. package/dist/templates/native-go-vue/frontend/src/jobs/connected-provider.ts +179 -0
  296. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/de.json +62 -0
  297. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/en.json +62 -0
  298. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/es.json +62 -0
  299. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/fr.json +62 -0
  300. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/it.json +62 -0
  301. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/ja.json +62 -0
  302. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/ko.json +62 -0
  303. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/zh-CN.json +62 -0
  304. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/zh-TW.json +62 -0
  305. package/dist/templates/native-go-vue/frontend/src/jobs/i18n.ts +15 -0
  306. package/dist/templates/native-go-vue/frontend/src/jobs/mock-provider.ts +67 -0
  307. package/dist/templates/native-go-vue/frontend/src/jobs/provider.ts +60 -0
  308. package/dist/templates/native-go-vue/frontend/src/jobs/validate.ts +44 -0
  309. package/dist/templates/native-go-vue/frontend/src/m2-main.ts +21 -0
  310. package/dist/templates/native-go-vue/frontend/src/m2-style.css +7 -0
  311. package/dist/templates/native-go-vue/frontend/src/nas/M2Files.vue +94 -0
  312. package/dist/templates/native-go-vue/frontend/src/nas/M2PathPicker.vue +70 -0
  313. package/dist/templates/native-go-vue/frontend/src/nas/M2Settings.vue +69 -0
  314. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/de.json +56 -0
  315. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/en.json +56 -0
  316. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/es.json +56 -0
  317. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/fr.json +56 -0
  318. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/it.json +56 -0
  319. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/ja.json +56 -0
  320. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/ko.json +56 -0
  321. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-CN.json +56 -0
  322. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-TW.json +56 -0
  323. package/dist/templates/native-go-vue/frontend/src/nas/m2-contract.ts +33 -0
  324. package/dist/templates/native-go-vue/frontend/src/nas/m2-i18n.ts +24 -0
  325. package/dist/templates/native-go-vue/frontend/src/nas/m2-provider.ts +104 -0
  326. package/dist/templates/native-go-vue/frontend/src/nas/use-m2-request.ts +18 -0
  327. package/dist/templates/native-go-vue/frontend/src/sdk-main.ts +39 -0
  328. package/dist/templates/native-go-vue/frontend/src/shell/AppShell.vue +48 -0
  329. package/dist/templates/native-go-vue/frontend/src/shell/i18n/de.json +10 -0
  330. package/dist/templates/native-go-vue/frontend/src/shell/i18n/en.json +10 -0
  331. package/dist/templates/native-go-vue/frontend/src/shell/i18n/es.json +10 -0
  332. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +10 -0
  333. package/dist/templates/native-go-vue/frontend/src/shell/i18n/it.json +10 -0
  334. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +10 -0
  335. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ko.json +10 -0
  336. package/dist/templates/native-go-vue/frontend/src/shell/i18n/zh-CN.json +10 -0
  337. package/dist/templates/native-go-vue/frontend/src/shell/i18n/zh-TW.json +10 -0
  338. package/dist/templates/native-go-vue/frontend/src/shell/i18n.ts +17 -0
  339. package/dist/templates/native-go-vue/frontend/src/shell/pages.ts +34 -0
  340. package/dist/templates/native-go-vue/frontend/src/style.css +2 -6
  341. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +21 -0
  342. package/dist/templates/native-go-vue/frontend/test/jobs-connected.test.ts +163 -0
  343. package/dist/templates/native-go-vue/frontend/test/jobs-preview.html +22 -0
  344. package/dist/templates/native-go-vue/frontend/test/jobs.test.ts +176 -0
  345. package/dist/templates/native-go-vue/frontend/test/m2-preview.html +27 -0
  346. package/dist/templates/native-go-vue/frontend/test/m2-provider.test.ts +87 -0
  347. package/dist/templates/native-go-vue/frontend/test/m2-ui.test.ts +209 -0
  348. package/dist/templates/native-go-vue/frontend/test/native-controls.test.ts +27 -0
  349. package/dist/templates/native-go-vue/frontend/test/picker-wire.test.ts +110 -0
  350. package/dist/templates/native-go-vue/frontend/test/shell-preview.html +19 -0
  351. package/dist/templates/native-go-vue/frontend/test/shell-sdk.test.ts +73 -0
  352. package/dist/templates/native-go-vue/frontend/test/shell-ui.test.ts +114 -0
  353. package/dist/templates/native-go-vue/frontend/test/shell.test.ts +45 -0
  354. package/dist/templates/native-go-vue/frontend/vite.config.ts +2 -2
  355. package/dist/validate.js +6 -3
  356. package/package.json +1 -1
  357. package/schema/synology-app.schema.json +19 -0
  358. package/skills/reference/create.md +4 -0
  359. package/templates/_daemon/go.mod +2 -0
  360. package/templates/_gateway/auth.go +101 -0
  361. package/templates/_gateway/auth_test.go +216 -0
  362. package/templates/_gateway/cgi.go +133 -0
  363. package/templates/_gateway/cgi_test.go +156 -0
  364. package/templates/_gateway/csrf.go +73 -0
  365. package/templates/_gateway/csrf_test.go +155 -0
  366. package/templates/_gateway/directory.go +108 -0
  367. package/templates/_gateway/directory_test.go +202 -0
  368. package/templates/_gateway/gateway.go +412 -0
  369. package/templates/_gateway/gateway_test.go +676 -0
  370. package/templates/_gateway/go.mod +3 -0
  371. package/templates/_gateway/groups.go +66 -0
  372. package/templates/_gateway/main.go +45 -0
  373. package/templates/binary/synology-app.yaml +1 -1
  374. package/templates/compose-import/compose.yaml +1 -1
  375. package/templates/compose-import/synology-app.yaml +1 -1
  376. package/templates/hello-files/compose.yaml +1 -1
  377. package/templates/hello-files/synology-app.yaml +1 -1
  378. package/templates/hello-web/compose.yaml +5 -2
  379. package/templates/hello-web/synology-app.yaml +4 -1
  380. package/templates/hello-web-page/compose.yaml +5 -2
  381. package/templates/hello-web-page/synology-app.yaml +4 -1
  382. package/templates/native-go/README.md +93 -4
  383. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +3 -1
  384. package/templates/native-go/backend/cmd/app/main.go +17 -0
  385. package/templates/native-go/backend/cmd/app/sdk_main.go +60 -0
  386. package/templates/native-go/backend/cmd/background-worker/main.go +38 -0
  387. package/templates/native-go/backend/cmd/helper/main.go +10 -1
  388. package/templates/native-go/backend/cmd/helper/sdk_registry.go +19 -0
  389. package/templates/native-go/backend/go.mod +5 -3
  390. package/templates/native-go/backend/go.sum +4 -4
  391. package/templates/native-go/backend/internal/api/registry.go +17 -6
  392. package/templates/native-go/backend/internal/api/server.go +7 -1
  393. package/templates/native-go/backend/internal/app/background.go +270 -0
  394. package/templates/native-go/backend/internal/app/background_none.go +8 -0
  395. package/templates/native-go/backend/internal/app/background_owner.go +59 -0
  396. package/templates/native-go/backend/internal/app/background_owner_test.go +145 -0
  397. package/templates/native-go/backend/internal/app/background_schedule.go +74 -0
  398. package/templates/native-go/backend/internal/app/background_schedule_test.go +62 -0
  399. package/templates/native-go/backend/internal/app/background_test.go +346 -0
  400. package/templates/native-go/backend/internal/app/integration_state.go +16 -0
  401. package/templates/native-go/backend/internal/app/migrations.go +41 -0
  402. package/templates/native-go/backend/internal/app/migrations_test.go +41 -0
  403. package/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +8 -0
  404. package/templates/native-go/backend/internal/app/routes.go +17 -3
  405. package/templates/native-go/backend/internal/app/routes_test.go +20 -3
  406. package/templates/native-go/backend/internal/app/sdk_jobs.go +70 -0
  407. package/templates/native-go/backend/internal/app/sdk_jobs_test.go +116 -0
  408. package/templates/native-go/backend/internal/app/sdk_routes.go +174 -0
  409. package/templates/native-go/backend/internal/app/session_files.go +15 -0
  410. package/templates/native-go/backend/internal/app/session_files_test.go +26 -0
  411. package/templates/native-go/backend/internal/app/shared_folder.go +144 -0
  412. package/templates/native-go/backend/internal/app/shared_folder_test.go +158 -0
  413. package/templates/native-go/backend/internal/background/access.go +57 -0
  414. package/templates/native-go/backend/internal/background/owner_test.go +126 -0
  415. package/templates/native-go/backend/internal/background/runner.go +185 -0
  416. package/templates/native-go/backend/internal/background/runner_test.go +209 -0
  417. package/templates/native-go/backend/internal/background/schedule.go +300 -0
  418. package/templates/native-go/backend/internal/background/schedule_test.go +335 -0
  419. package/templates/native-go/backend/internal/background/store.go +807 -0
  420. package/templates/native-go/backend/internal/background/store_test.go +407 -0
  421. package/templates/native-go/backend/internal/datamigrate/migrate.go +191 -0
  422. package/templates/native-go/backend/internal/datamigrate/migrate_test.go +177 -0
  423. package/templates/native-go/backend/internal/filestation/browse_test.go +67 -0
  424. package/templates/native-go/backend/internal/filestation/client.go +251 -0
  425. package/templates/native-go/backend/internal/filestation/client_test.go +155 -0
  426. package/templates/native-go/backend/internal/filestation/doc.go +4 -0
  427. package/templates/native-go/backend/internal/filestation/read_file_test.go +90 -0
  428. package/templates/native-go/backend/internal/filestation/repository.go +207 -0
  429. package/templates/native-go/backend/internal/filestation/repository_test.go +104 -0
  430. package/templates/native-go/backend/internal/filestation/sdk_session.go +241 -0
  431. package/templates/native-go/backend/internal/filestation/sdk_session_test.go +183 -0
  432. package/templates/native-go/backend/internal/filestation/service.go +254 -0
  433. package/templates/native-go/backend/internal/filestation/service_test.go +94 -0
  434. package/templates/native-go/backend/internal/filestation/session_semantics_test.go +383 -0
  435. package/templates/native-go/backend/internal/filestation/write.go +72 -0
  436. package/templates/native-go/backend/internal/filestation/write_test.go +123 -0
  437. package/templates/native-go/backend/internal/helper/background_proof.go +163 -0
  438. package/templates/native-go/backend/internal/helper/background_proof_contract.go +46 -0
  439. package/templates/native-go/backend/internal/helper/background_proof_test.go +161 -0
  440. package/templates/native-go/backend/internal/helper/data_contract.go +3 -0
  441. package/templates/native-go/backend/internal/helper/data_protocol.go +3 -0
  442. package/templates/native-go/backend/internal/helper/data_protocol_test.go +1 -1
  443. package/templates/native-go/backend/internal/helper/grant_authority.go +3 -0
  444. package/templates/native-go/backend/internal/helper/grant_authority_test.go +1 -1
  445. package/templates/native-go/backend/internal/helper/grant_repository_linux.go +2 -1
  446. package/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +4 -3
  447. package/templates/native-go/backend/internal/helper/grant_snapshot.go +3 -0
  448. package/templates/native-go/backend/internal/helper/jobcontract/bindings.go +48 -0
  449. package/templates/native-go/backend/internal/helper/jobcontract/contract_test.go +436 -0
  450. package/templates/native-go/backend/internal/helper/jobcontract/decode.go +185 -0
  451. package/templates/native-go/backend/internal/helper/jobcontract/digest.go +55 -0
  452. package/templates/native-go/backend/internal/helper/jobcontract/registry.go +85 -0
  453. package/templates/native-go/backend/internal/helper/jobcontract/registry_test.go +73 -0
  454. package/templates/native-go/backend/internal/helper/jobcontract/result.go +53 -0
  455. package/templates/native-go/backend/internal/helper/jobcontract/stub.go +8 -0
  456. package/templates/native-go/backend/internal/helper/jobcontract/testdata/input-digests.json +127 -0
  457. package/templates/native-go/backend/internal/helper/jobcontract/types.go +234 -0
  458. package/templates/native-go/backend/internal/helper/jobcontract/types_test.go +29 -0
  459. package/templates/native-go/backend/internal/helper/jobcontract/validate.go +462 -0
  460. package/templates/native-go/backend/internal/helper/legacy_authority_schema_linux_test.go +34 -0
  461. package/templates/native-go/backend/internal/helper/legacy_authority_schema_test.go +6 -0
  462. package/templates/native-go/backend/internal/helper/management.go +79 -0
  463. package/templates/native-go/backend/internal/helper/observation.go +42 -2
  464. package/templates/native-go/backend/internal/helper/observation_health_test.go +13 -1
  465. package/templates/native-go/backend/internal/helper/observer_diagnostic.go +56 -10
  466. package/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +6 -0
  467. package/templates/native-go/backend/internal/helper/observer_diagnostic_writer.go +40 -0
  468. package/templates/native-go/backend/internal/helper/process_linux.go +3 -0
  469. package/templates/native-go/backend/internal/helper/query_deadline_linux_test.go +115 -0
  470. package/templates/native-go/backend/internal/helper/query_pending_diagnostic_test.go +254 -0
  471. package/templates/native-go/backend/internal/helper/relaxed_invalidation.go +3 -0
  472. package/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +23 -4
  473. package/templates/native-go/backend/internal/helper/sdk_assembly_linux.go +51 -0
  474. package/templates/native-go/backend/internal/helper/sdk_attach.go +83 -0
  475. package/templates/native-go/backend/internal/helper/sdk_attach_contract.go +32 -0
  476. package/templates/native-go/backend/internal/helper/sdk_attach_test.go +184 -0
  477. package/templates/native-go/backend/internal/helper/sdk_authority_schema_test.go +6 -0
  478. package/templates/native-go/backend/internal/helper/sdk_background_authority.go +832 -0
  479. package/templates/native-go/backend/internal/helper/sdk_background_authority_test.go +714 -0
  480. package/templates/native-go/backend/internal/helper/sdk_background_invalidation.go +218 -0
  481. package/templates/native-go/backend/internal/helper/sdk_background_lease.go +218 -0
  482. package/templates/native-go/backend/internal/helper/sdk_background_snapshot.go +110 -0
  483. package/templates/native-go/backend/internal/helper/sdk_background_store_linux_test.go +261 -0
  484. package/templates/native-go/backend/internal/helper/sdk_background_store_test.go +58 -0
  485. package/templates/native-go/backend/internal/helper/sdk_bindings.go +60 -0
  486. package/templates/native-go/backend/internal/helper/sdk_data_contract.go +251 -0
  487. package/templates/native-go/backend/internal/helper/sdk_data_protocol.go +424 -0
  488. package/templates/native-go/backend/internal/helper/sdk_dispatch_absence_test.go +51 -0
  489. package/templates/native-go/backend/internal/helper/sdk_display.go +488 -0
  490. package/templates/native-go/backend/internal/helper/sdk_display_contract.go +121 -0
  491. package/templates/native-go/backend/internal/helper/sdk_display_test.go +459 -0
  492. package/templates/native-go/backend/internal/helper/sdk_grant_authority.go +559 -0
  493. package/templates/native-go/backend/internal/helper/sdk_grant_repository_linux.go +417 -0
  494. package/templates/native-go/backend/internal/helper/sdk_grant_snapshot.go +94 -0
  495. package/templates/native-go/backend/internal/helper/sdk_history.go +80 -0
  496. package/templates/native-go/backend/internal/helper/sdk_history_test.go +76 -0
  497. package/templates/native-go/backend/internal/helper/sdk_job.go +697 -0
  498. package/templates/native-go/backend/internal/helper/sdk_job_child_linux.go +309 -0
  499. package/templates/native-go/backend/internal/helper/sdk_job_contract.go +122 -0
  500. package/templates/native-go/backend/internal/helper/sdk_job_linux_test.go +159 -0
  501. package/templates/native-go/backend/internal/helper/sdk_job_process_linux.go +327 -0
  502. package/templates/native-go/backend/internal/helper/sdk_job_test.go +501 -0
  503. package/templates/native-go/backend/internal/helper/sdk_job_vm_linux_test.go +343 -0
  504. package/templates/native-go/backend/internal/helper/sdk_journal.go +77 -0
  505. package/templates/native-go/backend/internal/helper/sdk_journal_linux.go +244 -0
  506. package/templates/native-go/backend/internal/helper/sdk_journal_linux_test.go +295 -0
  507. package/templates/native-go/backend/internal/helper/sdk_journal_test.go +71 -0
  508. package/templates/native-go/backend/internal/helper/sdk_picker_test.go +50 -0
  509. package/templates/native-go/backend/internal/helper/sdk_process_linux.go +184 -0
  510. package/templates/native-go/backend/internal/helper/sdk_public_vm_linux_test.go +129 -0
  511. package/templates/native-go/backend/internal/helper/sdk_relaxed_invalidation.go +242 -0
  512. package/templates/native-go/backend/internal/helper/sdk_rpc.go +299 -0
  513. package/templates/native-go/backend/internal/helper/sdk_rpc_contract.go +70 -0
  514. package/templates/native-go/backend/internal/helper/sdk_rpc_test.go +135 -0
  515. package/templates/native-go/backend/internal/helper/sdk_server_linux.go +152 -0
  516. package/templates/native-go/backend/internal/helper/sdk_session.go +677 -0
  517. package/templates/native-go/backend/internal/helper/sdk_session_contract.go +72 -0
  518. package/templates/native-go/backend/internal/helper/sdk_session_fence_test.go +215 -0
  519. package/templates/native-go/backend/internal/helper/sdk_session_test.go +475 -0
  520. package/templates/native-go/backend/internal/helper/sdk_supervisor_frame.go +78 -0
  521. package/templates/native-go/backend/internal/helper/sdk_supervisor_linux.go +414 -0
  522. package/templates/native-go/backend/internal/helper/sdk_transport.go +79 -0
  523. package/templates/native-go/backend/internal/helper/sdk_transport_linux.go +82 -0
  524. package/templates/native-go/backend/internal/helper/sdk_transport_test.go +64 -0
  525. package/templates/native-go/backend/internal/helper/server_linux.go +3 -0
  526. package/templates/native-go/backend/internal/helper/stale_management_linux_test.go +126 -0
  527. package/templates/native-go/backend/internal/helper/stale_management_test.go +122 -0
  528. package/templates/native-go/backend/internal/helper/startup_diagnostic.go +68 -0
  529. package/templates/native-go/backend/internal/helper/startup_diagnostic_linux_test.go +126 -0
  530. package/templates/native-go/backend/internal/helper/startup_diagnostic_test.go +117 -0
  531. package/templates/native-go/backend/internal/helper/status.go +22 -13
  532. package/templates/native-go/backend/internal/helper/status_test.go +45 -0
  533. package/templates/native-go/backend/internal/helper/supervision.go +1 -1
  534. package/templates/native-go/backend/internal/helper/supervisor_frame.go +3 -0
  535. package/templates/native-go/backend/internal/helper/supervisor_linux.go +6 -2
  536. package/templates/native-go/backend/internal/helper/unit_connection_test.go +22 -4
  537. package/templates/native-go/backend/internal/helper/unit_linux.go +7 -3
  538. package/templates/native-go/backend/internal/helper/unit_query_linux.go +5 -1
  539. package/templates/native-go/backend/internal/helper/write_files_linux.go +7 -4
  540. package/templates/native-go/backend/internal/helper/write_files_linux_test.go +26 -0
  541. package/templates/native-go/dsm-package.yaml +1 -1
  542. package/templates/native-go-background/BACKGROUND.md +111 -0
  543. package/templates/native-go-background/backend/internal/app/background_on.go +7 -0
  544. package/templates/native-go-background/backend/internal/app/background_routes_test.go +32 -0
  545. package/templates/native-go-background/frontend/src/background/BackgroundPanel.vue +178 -0
  546. package/templates/native-go-background/frontend/src/background/i18n/de.json +83 -0
  547. package/templates/native-go-background/frontend/src/background/i18n/en.json +83 -0
  548. package/templates/native-go-background/frontend/src/background/i18n/es.json +83 -0
  549. package/templates/native-go-background/frontend/src/background/i18n/fr.json +83 -0
  550. package/templates/native-go-background/frontend/src/background/i18n/it.json +83 -0
  551. package/templates/native-go-background/frontend/src/background/i18n/ja.json +83 -0
  552. package/templates/native-go-background/frontend/src/background/i18n/ko.json +83 -0
  553. package/templates/native-go-background/frontend/src/background/i18n/zh-CN.json +83 -0
  554. package/templates/native-go-background/frontend/src/background/i18n/zh-TW.json +83 -0
  555. package/templates/native-go-background/frontend/src/background/i18n.ts +19 -0
  556. package/templates/native-go-background/frontend/src/background/picker-provider.ts +13 -0
  557. package/templates/native-go-background/frontend/test/background.test.ts +179 -0
  558. package/templates/native-go-integration/backend/internal/app/personal.go +2 -2
  559. package/templates/native-go-integration/backend/internal/app/personal_test.go +37 -0
  560. package/templates/native-go-vue/README.md +38 -7
  561. package/templates/native-go-vue/frontend/DESIGN.md +15 -3
  562. package/templates/native-go-vue/frontend/README-JOBS.md +75 -0
  563. package/templates/native-go-vue/frontend/README-M2.md +64 -0
  564. package/templates/native-go-vue/frontend/package-lock.json +5 -4
  565. package/templates/native-go-vue/frontend/package.json +1 -1
  566. package/templates/native-go-vue/frontend/src/App.vue +39 -11
  567. package/templates/native-go-vue/frontend/src/M2App.vue +57 -0
  568. package/templates/native-go-vue/frontend/src/SDKApp.vue +203 -0
  569. package/templates/native-go-vue/frontend/src/jobs/InteractiveJobForm.vue +70 -0
  570. package/templates/native-go-vue/frontend/src/jobs/JobsAdminPanel.vue +33 -0
  571. package/templates/native-go-vue/frontend/src/jobs/JobsPanel.vue +135 -0
  572. package/templates/native-go-vue/frontend/src/jobs/connected-provider.ts +179 -0
  573. package/templates/native-go-vue/frontend/src/jobs/i18n/de.json +62 -0
  574. package/templates/native-go-vue/frontend/src/jobs/i18n/en.json +62 -0
  575. package/templates/native-go-vue/frontend/src/jobs/i18n/es.json +62 -0
  576. package/templates/native-go-vue/frontend/src/jobs/i18n/fr.json +62 -0
  577. package/templates/native-go-vue/frontend/src/jobs/i18n/it.json +62 -0
  578. package/templates/native-go-vue/frontend/src/jobs/i18n/ja.json +62 -0
  579. package/templates/native-go-vue/frontend/src/jobs/i18n/ko.json +62 -0
  580. package/templates/native-go-vue/frontend/src/jobs/i18n/zh-CN.json +62 -0
  581. package/templates/native-go-vue/frontend/src/jobs/i18n/zh-TW.json +62 -0
  582. package/templates/native-go-vue/frontend/src/jobs/i18n.ts +15 -0
  583. package/templates/native-go-vue/frontend/src/jobs/mock-provider.ts +67 -0
  584. package/templates/native-go-vue/frontend/src/jobs/provider.ts +60 -0
  585. package/templates/native-go-vue/frontend/src/jobs/validate.ts +44 -0
  586. package/templates/native-go-vue/frontend/src/m2-main.ts +21 -0
  587. package/templates/native-go-vue/frontend/src/m2-style.css +7 -0
  588. package/templates/native-go-vue/frontend/src/nas/M2Files.vue +94 -0
  589. package/templates/native-go-vue/frontend/src/nas/M2PathPicker.vue +70 -0
  590. package/templates/native-go-vue/frontend/src/nas/M2Settings.vue +69 -0
  591. package/templates/native-go-vue/frontend/src/nas/i18n/m2/de.json +56 -0
  592. package/templates/native-go-vue/frontend/src/nas/i18n/m2/en.json +56 -0
  593. package/templates/native-go-vue/frontend/src/nas/i18n/m2/es.json +56 -0
  594. package/templates/native-go-vue/frontend/src/nas/i18n/m2/fr.json +56 -0
  595. package/templates/native-go-vue/frontend/src/nas/i18n/m2/it.json +56 -0
  596. package/templates/native-go-vue/frontend/src/nas/i18n/m2/ja.json +56 -0
  597. package/templates/native-go-vue/frontend/src/nas/i18n/m2/ko.json +56 -0
  598. package/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-CN.json +56 -0
  599. package/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-TW.json +56 -0
  600. package/templates/native-go-vue/frontend/src/nas/m2-contract.ts +33 -0
  601. package/templates/native-go-vue/frontend/src/nas/m2-i18n.ts +24 -0
  602. package/templates/native-go-vue/frontend/src/nas/m2-provider.ts +104 -0
  603. package/templates/native-go-vue/frontend/src/nas/use-m2-request.ts +18 -0
  604. package/templates/native-go-vue/frontend/src/sdk-main.ts +39 -0
  605. package/templates/native-go-vue/frontend/src/shell/AppShell.vue +48 -0
  606. package/templates/native-go-vue/frontend/src/shell/i18n/de.json +10 -0
  607. package/templates/native-go-vue/frontend/src/shell/i18n/en.json +10 -0
  608. package/templates/native-go-vue/frontend/src/shell/i18n/es.json +10 -0
  609. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +10 -0
  610. package/templates/native-go-vue/frontend/src/shell/i18n/it.json +10 -0
  611. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +10 -0
  612. package/templates/native-go-vue/frontend/src/shell/i18n/ko.json +10 -0
  613. package/templates/native-go-vue/frontend/src/shell/i18n/zh-CN.json +10 -0
  614. package/templates/native-go-vue/frontend/src/shell/i18n/zh-TW.json +10 -0
  615. package/templates/native-go-vue/frontend/src/shell/i18n.ts +17 -0
  616. package/templates/native-go-vue/frontend/src/shell/pages.ts +34 -0
  617. package/templates/native-go-vue/frontend/src/style.css +2 -6
  618. package/templates/native-go-vue/frontend/test/apps.test.ts +21 -0
  619. package/templates/native-go-vue/frontend/test/jobs-connected.test.ts +163 -0
  620. package/templates/native-go-vue/frontend/test/jobs-preview.html +22 -0
  621. package/templates/native-go-vue/frontend/test/jobs.test.ts +176 -0
  622. package/templates/native-go-vue/frontend/test/m2-preview.html +27 -0
  623. package/templates/native-go-vue/frontend/test/m2-provider.test.ts +87 -0
  624. package/templates/native-go-vue/frontend/test/m2-ui.test.ts +209 -0
  625. package/templates/native-go-vue/frontend/test/native-controls.test.ts +27 -0
  626. package/templates/native-go-vue/frontend/test/picker-wire.test.ts +110 -0
  627. package/templates/native-go-vue/frontend/test/shell-preview.html +19 -0
  628. package/templates/native-go-vue/frontend/test/shell-sdk.test.ts +73 -0
  629. package/templates/native-go-vue/frontend/test/shell-ui.test.ts +114 -0
  630. package/templates/native-go-vue/frontend/test/shell.test.ts +45 -0
  631. package/templates/native-go-vue/frontend/vite.config.ts +2 -2
@@ -0,0 +1,676 @@
1
+ package main
2
+
3
+ import (
4
+ "bufio"
5
+ "errors"
6
+ "io"
7
+ "net"
8
+ "net/http"
9
+ "net/http/httptest"
10
+ "slices"
11
+ "strconv"
12
+ "strings"
13
+ "sync/atomic"
14
+ "testing"
15
+ "time"
16
+ )
17
+
18
+ const base = "/webman/3rdparty/App/cgi/gateway.cgi"
19
+
20
+ type fakeAuth struct {
21
+ id identity
22
+ err error
23
+ calls int
24
+ }
25
+
26
+ func (f *fakeAuth) Authenticate(*http.Request) (identity, error) {
27
+ f.calls++
28
+ return f.id, f.err
29
+ }
30
+
31
+ // upstream records what reached the container.
32
+ type upstream struct {
33
+ *httptest.Server
34
+ hits atomic.Int32
35
+ request atomic.Pointer[http.Request]
36
+ body atomic.Pointer[string]
37
+ }
38
+
39
+ func newUpstream(t *testing.T, handler http.HandlerFunc) *upstream {
40
+ t.Helper()
41
+ u := &upstream{}
42
+ u.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
43
+ u.hits.Add(1)
44
+ u.request.Store(r)
45
+ b, _ := io.ReadAll(r.Body)
46
+ s := string(b)
47
+ u.body.Store(&s)
48
+ handler(w, r)
49
+ }))
50
+ t.Cleanup(u.Close)
51
+ return u
52
+ }
53
+
54
+ func hello(w http.ResponseWriter, r *http.Request) {
55
+ w.Header().Set("Content-Type", "text/plain")
56
+ io.WriteString(w, "container says hi: "+r.URL.RequestURI())
57
+ }
58
+
59
+ func newTestGateway(t *testing.T, cfg config, auth authenticator, up *upstream) http.Handler {
60
+ t.Helper()
61
+ cfg.Upstream = up.Listener.Addr().String()
62
+ g, err := newGateway(cfg, auth)
63
+ if err != nil {
64
+ t.Fatal(err)
65
+ }
66
+ return g
67
+ }
68
+
69
+ func do(h http.Handler, method, target string, hdr map[string]string, body string) *httptest.ResponseRecorder {
70
+ r := httptest.NewRequest(method, target, strings.NewReader(body))
71
+ r.RemoteAddr = "192.0.2.7:51234"
72
+ for k, v := range hdr {
73
+ r.Header.Set(k, v)
74
+ }
75
+ w := httptest.NewRecorder()
76
+ h.ServeHTTP(w, r)
77
+ return w
78
+ }
79
+
80
+ var signedIn = map[string]string{"Cookie": "id=abc"}
81
+
82
+ func TestSignedInUserReachesContainer(t *testing.T) {
83
+ up := newUpstream(t, hello)
84
+ auth := &fakeAuth{id: identity{User: "alice"}}
85
+ w := do(newTestGateway(t, config{Access: "users"}, auth, up), "GET", base+"/index.html?x=1", signedIn, "")
86
+ if w.Code != 200 || w.Body.String() != "container says hi: /index.html?x=1" {
87
+ t.Fatalf("%d %q", w.Code, w.Body.String())
88
+ }
89
+ }
90
+
91
+ func TestAnonymousRequestIsRejectedAndNeverReachesContainer(t *testing.T) {
92
+ up := newUpstream(t, hello)
93
+ auth := &fakeAuth{err: errNoSession}
94
+ w := do(newTestGateway(t, config{Access: "users"}, auth, up), "GET", base+"/", nil, "")
95
+ if w.Code != 401 || up.hits.Load() != 0 {
96
+ t.Fatalf("code=%d hits=%d", w.Code, up.hits.Load())
97
+ }
98
+ if !strings.Contains(w.Body.String(), "session_required") {
99
+ t.Errorf("body %q", w.Body.String())
100
+ }
101
+ }
102
+
103
+ func TestExpiredSessionIsRejected(t *testing.T) {
104
+ up := newUpstream(t, hello)
105
+ auth := &fakeAuth{err: errNoSession}
106
+ w := do(newTestGateway(t, config{Access: "users"}, auth, up), "GET", base+"/", signedIn, "")
107
+ if w.Code != 401 || up.hits.Load() != 0 {
108
+ t.Fatalf("code=%d hits=%d", w.Code, up.hits.Load())
109
+ }
110
+ }
111
+
112
+ func TestAuthenticatorFailureFailsClosed(t *testing.T) {
113
+ up := newUpstream(t, hello)
114
+ auth := &fakeAuth{err: errors.New("authenticate.cgi missing")}
115
+ w := do(newTestGateway(t, config{Access: "users"}, auth, up), "GET", base+"/", signedIn, "")
116
+ if w.Code != 520 || up.hits.Load() != 0 {
117
+ t.Fatalf("code=%d hits=%d", w.Code, up.hits.Load())
118
+ }
119
+ if got := w.Header().Get("X-Synopkgland-Error"); got != "auth_unavailable" {
120
+ t.Errorf("X-Synopkgland-Error = %q", got)
121
+ }
122
+ }
123
+
124
+ func TestForgedIdentityHeadersDoNotAuthenticate(t *testing.T) {
125
+ up := newUpstream(t, hello)
126
+ auth := &fakeAuth{err: errNoSession}
127
+ forged := map[string]string{
128
+ "X-Syno-User": "admin",
129
+ "X-Syno-Admin": "true",
130
+ "X-Forwarded-User": "admin",
131
+ "X-Remote-User": "admin",
132
+ "Remote-User": "admin",
133
+ "X-Forwarded-For": "127.0.0.1",
134
+ "X-Real-IP": "127.0.0.1",
135
+ "Authorization": "Basic YWRtaW46YWRtaW4=",
136
+ "X-Syno-Token": "bogus",
137
+ "Cookie": "id=forged; did=forged",
138
+ }
139
+ w := do(newTestGateway(t, config{Access: "users"}, auth, up), "GET", base+"/", forged, "")
140
+ if w.Code != 401 || up.hits.Load() != 0 {
141
+ t.Fatalf("code=%d hits=%d", w.Code, up.hits.Load())
142
+ }
143
+ }
144
+
145
+ func TestContainerNeverSeesClientIdentityOrSessionHeaders(t *testing.T) {
146
+ up := newUpstream(t, hello)
147
+ auth := &fakeAuth{id: identity{User: "bob"}}
148
+ hdr := map[string]string{
149
+ "Cookie": "id=abc; did=xyz",
150
+ "X-Syno-Token": "tok",
151
+ "X-Syno-User": "admin",
152
+ "X-Syno-Admin": "true",
153
+ "X-Forwarded-User": "admin",
154
+ "X-Remote-User": "admin",
155
+ "Remote-User": "admin",
156
+ "Remote-Groups": "administrators",
157
+ "X-Forwarded-For": "10.0.0.9",
158
+ "Forwarded": "for=10.0.0.9",
159
+ "X-Real-IP": "10.0.0.9",
160
+ "X-Auth-Request-User": "admin",
161
+ "Accept-Language": "zh-TW",
162
+ }
163
+ w := do(newTestGateway(t, config{Access: "users"}, auth, up), "GET", base+"/?SynoToken=secret&a=b", hdr, "")
164
+ if w.Code != 200 {
165
+ t.Fatalf("code=%d", w.Code)
166
+ }
167
+ got := up.request.Load()
168
+ for _, name := range []string{"Cookie", "X-Syno-Token", "X-Forwarded-User", "X-Remote-User", "Remote-User", "Remote-Groups", "X-Forwarded-For", "Forwarded", "X-Real-Ip", "X-Auth-Request-User"} {
169
+ if v := got.Header.Get(name); v != "" {
170
+ t.Errorf("%s reached the container: %q", name, v)
171
+ }
172
+ }
173
+ if got.Header.Get("X-Syno-User") != "bob" || got.Header.Get("X-Syno-Admin") != "false" {
174
+ t.Errorf("identity = %q / %q, want gateway-set bob/false", got.Header.Get("X-Syno-User"), got.Header.Get("X-Syno-Admin"))
175
+ }
176
+ if got.Header.Get("Accept-Language") != "zh-TW" {
177
+ t.Errorf("ordinary header dropped")
178
+ }
179
+ if strings.Contains(got.URL.RawQuery, "SynoToken") || got.URL.Query().Get("a") != "b" {
180
+ t.Errorf("query = %q", got.URL.RawQuery)
181
+ }
182
+ }
183
+
184
+ func TestAdminHeaderIsTrueForAdministrators(t *testing.T) {
185
+ up := newUpstream(t, hello)
186
+ auth := &fakeAuth{id: identity{User: "alice", Groups: []string{"administrators"}}}
187
+ do(newTestGateway(t, config{Access: "users"}, auth, up), "GET", base+"/", signedIn, "")
188
+ if got := up.request.Load().Header.Get("X-Syno-Admin"); got != "true" {
189
+ t.Errorf("X-Syno-Admin = %q", got)
190
+ }
191
+ }
192
+
193
+ func TestAdminsOnlyRejectsOrdinaryUsers(t *testing.T) {
194
+ up := newUpstream(t, hello)
195
+ cfg := config{Access: "admins"}
196
+ g := newTestGateway(t, cfg, &fakeAuth{id: identity{User: "bob", Groups: []string{"staff"}}}, up)
197
+ if w := do(g, "GET", base+"/", signedIn, ""); w.Code != 401 || up.hits.Load() != 0 {
198
+ t.Fatalf("ordinary: code=%d hits=%d", w.Code, up.hits.Load())
199
+ } else if w.Header().Get("X-Synopkgland-Error") != "forbidden" || w.Header().Get("X-Synopkgland-Status") != "403" {
200
+ t.Errorf("headers: %v", w.Header())
201
+ }
202
+ g = newTestGateway(t, cfg, &fakeAuth{id: identity{User: "alice", Groups: []string{"administrators"}}}, up)
203
+ if w := do(g, "GET", base+"/", signedIn, ""); w.Code != 200 {
204
+ t.Fatalf("admin: code=%d", w.Code)
205
+ }
206
+ }
207
+
208
+ func TestGroupsRejectUsersOutsideEveryListedGroup(t *testing.T) {
209
+ up := newUpstream(t, hello)
210
+ cfg := config{Access: "groups", Groups: []string{"staff", "ops"}}
211
+ g := newTestGateway(t, cfg, &fakeAuth{id: identity{User: "bob", Groups: []string{"users"}}}, up)
212
+ if w := do(g, "GET", base+"/", signedIn, ""); w.Code != 401 || up.hits.Load() != 0 || !strings.Contains(w.Body.String(), "forbidden") {
213
+ t.Fatalf("outsider: code=%d hits=%d body=%q", w.Code, up.hits.Load(), w.Body.String())
214
+ }
215
+ g = newTestGateway(t, cfg, &fakeAuth{id: identity{User: "carol", Groups: []string{"ops"}}}, up)
216
+ if w := do(g, "GET", base+"/", signedIn, ""); w.Code != 200 {
217
+ t.Fatalf("member: code=%d", w.Code)
218
+ }
219
+ }
220
+
221
+ func TestGroupsAccessWithoutGroupsIsRefusedAtStartup(t *testing.T) {
222
+ for _, cfg := range []config{
223
+ {Upstream: "127.0.0.1:1", Access: "groups"},
224
+ {Upstream: "127.0.0.1:1", Access: "everyone"},
225
+ {Upstream: "127.0.0.1:1", Access: ""},
226
+ {Upstream: "10.0.0.5:80", Access: "users"},
227
+ {Upstream: "localhost:80", Access: "users"},
228
+ } {
229
+ if _, err := newGateway(cfg, &fakeAuth{}); err == nil {
230
+ t.Errorf("config %+v accepted", cfg)
231
+ }
232
+ }
233
+ if _, err := newGateway(config{Upstream: "127.0.0.1:18333", Access: "users"}, nil); err == nil {
234
+ t.Error("nil authenticator accepted")
235
+ }
236
+ }
237
+
238
+ func TestStateChangingRequestNeedsSynoToken(t *testing.T) {
239
+ up := newUpstream(t, hello)
240
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
241
+ for _, method := range []string{"POST", "PUT", "PATCH", "DELETE"} {
242
+ if w := do(g, method, base+"/", signedIn, "x"); w.Code != 401 || !strings.Contains(w.Body.String(), "token_required") || w.Header().Get("X-Synopkgland-Error") != "token_required" {
243
+ t.Errorf("%s without token: %d %q", method, w.Code, w.Body.String())
244
+ }
245
+ }
246
+ if up.hits.Load() != 0 {
247
+ t.Fatalf("hits=%d", up.hits.Load())
248
+ }
249
+ hdr := browserPost("tok")
250
+ if w := do(g, "POST", base+"/save", hdr, "payload"); w.Code != 200 || *up.body.Load() != "payload" {
251
+ t.Fatalf("with token: %d body=%q", w.Code, *up.body.Load())
252
+ }
253
+ }
254
+
255
+ func TestQueryTokenDoesNotSatisfyStateChangingRequests(t *testing.T) {
256
+ up := newUpstream(t, hello)
257
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
258
+ hdr := browserPost("")
259
+ delete(hdr, "X-SYNO-TOKEN")
260
+ for _, method := range []string{"POST", "PUT", "PATCH", "DELETE"} {
261
+ w := do(g, method, base+"/?SynoToken=secret", hdr, "x")
262
+ if w.Code != 401 || w.Header().Get("X-Synopkgland-Error") != "token_required" || strings.Contains(w.Body.String(), "secret") {
263
+ t.Errorf("%s with query token only: %d %q", method, w.Code, w.Body.String())
264
+ }
265
+ }
266
+ if up.hits.Load() != 0 {
267
+ t.Fatalf("hits=%d", up.hits.Load())
268
+ }
269
+ }
270
+
271
+ func TestSafeRequestsNeedNoSynoToken(t *testing.T) {
272
+ up := newUpstream(t, hello)
273
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
274
+ for _, method := range []string{"GET", "HEAD"} {
275
+ if w := do(g, method, base+"/", signedIn, ""); w.Code != 200 {
276
+ t.Errorf("%s: %d", method, w.Code)
277
+ }
278
+ }
279
+ }
280
+
281
+ func TestOversizedBodyIsRefused(t *testing.T) {
282
+ up := newUpstream(t, func(w http.ResponseWriter, r *http.Request) {
283
+ if _, err := io.ReadAll(r.Body); err != nil {
284
+ w.WriteHeader(413)
285
+ }
286
+ })
287
+ g := newTestGateway(t, config{Access: "users", MaxBodyBytes: 4}, &fakeAuth{id: identity{User: "bob"}}, up)
288
+ hdr := browserPost("tok")
289
+ if w := do(g, "POST", base+"/", hdr, "0123456789"); w.Code != 413 {
290
+ t.Fatalf("code=%d", w.Code)
291
+ }
292
+ }
293
+
294
+ func TestContainerDownIsServiceUnavailable(t *testing.T) {
295
+ l, err := net.Listen("tcp", "127.0.0.1:0")
296
+ if err != nil {
297
+ t.Fatal(err)
298
+ }
299
+ addr := l.Addr().String()
300
+ l.Close()
301
+ g, err := newGateway(config{Upstream: addr, Access: "users"}, &fakeAuth{id: identity{User: "bob"}})
302
+ if err != nil {
303
+ t.Fatal(err)
304
+ }
305
+ w := do(g, "GET", base+"/", signedIn, "")
306
+ if w.Code != 521 || w.Header().Get("Retry-After") == "" || !strings.Contains(w.Body.String(), "upstream_unavailable") {
307
+ t.Fatalf("code=%d retry=%q body=%q", w.Code, w.Header().Get("Retry-After"), w.Body.String())
308
+ }
309
+ if w.Header().Get("X-Synopkgland-Error") != "upstream_unavailable" || w.Header().Get("X-Synopkgland-Status") != "503" {
310
+ t.Errorf("headers: %v", w.Header())
311
+ }
312
+ }
313
+
314
+ func TestSlowContainerIsGatewayTimeout(t *testing.T) {
315
+ up := newUpstream(t, func(w http.ResponseWriter, r *http.Request) { time.Sleep(300 * time.Millisecond) })
316
+ g := newTestGateway(t, config{Access: "users", ResponseTimeoutMillis: 50}, &fakeAuth{id: identity{User: "bob"}}, up)
317
+ w := do(g, "GET", base+"/", signedIn, "")
318
+ if w.Code != 524 || !strings.Contains(w.Body.String(), "upstream_timeout") || w.Header().Get("X-Synopkgland-Status") != "504" {
319
+ t.Fatalf("code=%d body=%q", w.Code, w.Body.String())
320
+ }
321
+ }
322
+
323
+ func TestContainerCannotSetCookiesOnTheDSMOrigin(t *testing.T) {
324
+ up := newUpstream(t, func(w http.ResponseWriter, r *http.Request) {
325
+ http.SetCookie(w, &http.Cookie{Name: "id", Value: "evil"})
326
+ io.WriteString(w, "ok")
327
+ })
328
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
329
+ w := do(g, "GET", base+"/", signedIn, "")
330
+ if w.Code != 200 || w.Header().Get("Set-Cookie") != "" {
331
+ t.Fatalf("code=%d set-cookie=%q", w.Code, w.Header().Get("Set-Cookie"))
332
+ }
333
+ }
334
+
335
+ func TestRedirectsStayInsideTheGateway(t *testing.T) {
336
+ up := newUpstream(t, func(w http.ResponseWriter, r *http.Request) {
337
+ switch r.URL.Path {
338
+ case "/abs":
339
+ http.Redirect(w, r, "http://"+r.Host+"/next?q=1", http.StatusFound)
340
+ case "/root":
341
+ http.Redirect(w, r, "/next", http.StatusFound)
342
+ case "/dir/rel":
343
+ http.Redirect(w, r, "next", http.StatusFound)
344
+ case "/rel":
345
+ w.Header().Set("Location", "next")
346
+ w.WriteHeader(http.StatusFound)
347
+ default:
348
+ http.Redirect(w, r, "https://example.org/away", http.StatusFound)
349
+ }
350
+ })
351
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
352
+ for path, want := range map[string]string{
353
+ "/abs": base + "/next?q=1",
354
+ "/root": base + "/next",
355
+ "/rel": "next",
356
+ "/dir/rel": base + "/dir/next",
357
+ "/ext": "https://example.org/away",
358
+ } {
359
+ w := do(g, "GET", base+path, signedIn, "")
360
+ if got := w.Header().Get("Location"); w.Code != 302 || got != want {
361
+ t.Errorf("%s: %d Location=%q want %q", path, w.Code, got, want)
362
+ }
363
+ }
364
+ }
365
+
366
+ func TestPathsCannotEscapeTheUpstream(t *testing.T) {
367
+ up := newUpstream(t, hello)
368
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
369
+ for _, target := range []string{
370
+ base + "/../../etc/passwd",
371
+ base + "/%2e%2e/%2e%2e/x",
372
+ base + "//evil.example/x",
373
+ base + "/a/../../x",
374
+ base + "/x%00y",
375
+ } {
376
+ if w := do(g, "GET", target, signedIn, ""); w.Code != 400 {
377
+ t.Errorf("%s: code=%d", target, w.Code)
378
+ }
379
+ }
380
+ if up.hits.Load() != 0 {
381
+ t.Fatalf("hits=%d", up.hits.Load())
382
+ }
383
+ }
384
+
385
+ func TestPathWithoutTrailingSlashRedirectsSoRelativeLinksWork(t *testing.T) {
386
+ up := newUpstream(t, hello)
387
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
388
+ w := do(g, "GET", base+"?x=1", signedIn, "")
389
+ if w.Code != 308 || w.Header().Get("Location") != "gateway.cgi/?x=1" {
390
+ t.Fatalf("code=%d location=%q", w.Code, w.Header().Get("Location"))
391
+ }
392
+ }
393
+
394
+ func TestRequestOutsideTheGatewayIsNotFound(t *testing.T) {
395
+ up := newUpstream(t, hello)
396
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
397
+ if w := do(g, "GET", "/somewhere/else", signedIn, ""); w.Code != 410 || w.Header().Get("X-Synopkgland-Status") != "404" {
398
+ t.Fatalf("code=%d headers=%v", w.Code, w.Header())
399
+ }
400
+ }
401
+
402
+ func TestResponsesAreNotSharedCacheable(t *testing.T) {
403
+ up := newUpstream(t, hello)
404
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
405
+ w := do(g, "GET", base+"/", signedIn, "")
406
+ if cc := w.Header().Get("Cache-Control"); !strings.Contains(cc, "private") {
407
+ t.Errorf("Cache-Control = %q", cc)
408
+ }
409
+ }
410
+
411
+ // DSM's nginx has `scgi_intercept_errors on` and `error_page 403 404 500 502 503 504`: a CGI reply with one of
412
+ // these statuses loses its body and headers to DSM's generic error page. Measured on DSM 7.4-90069.
413
+ func TestNoGatewayStatusIsOneThatDSMReplaces(t *testing.T) {
414
+ replaced := []int{403, 404, 500, 502, 503, 504}
415
+ for _, intended := range replaced {
416
+ w := httptest.NewRecorder()
417
+ reply(w, intended, "some_code", "message")
418
+ if slices.Contains(replaced, w.Code) || w.Code < 400 {
419
+ t.Errorf("reply(%d) sent %d, which DSM replaces or is not an error", intended, w.Code)
420
+ }
421
+ if got := w.Header().Get("X-Synopkgland-Status"); got != strconv.Itoa(intended) {
422
+ t.Errorf("reply(%d): X-Synopkgland-Status = %q", intended, got)
423
+ }
424
+ if w.Header().Get("X-Synopkgland-Error") != "some_code" || !strings.HasPrefix(w.Body.String(), "some_code\n") {
425
+ t.Errorf("reply(%d): error code lost: %v %q", intended, w.Header(), w.Body.String())
426
+ }
427
+ }
428
+ }
429
+
430
+ func TestStatusesDSMKeepsAreSentAsTheyAre(t *testing.T) {
431
+ for _, status := range []int{400, 401, 413, 501, 308} {
432
+ w := httptest.NewRecorder()
433
+ reply(w, status, "some_code", "message")
434
+ if w.Code != status || w.Header().Get("X-Synopkgland-Status") != "" || w.Header().Get("X-Synopkgland-Error") != "some_code" {
435
+ t.Errorf("reply(%d): sent %d headers=%v", status, w.Code, w.Header())
436
+ }
437
+ }
438
+ }
439
+
440
+ func TestContainerErrorBodiesSurviveDSMWhenTheAppAsks(t *testing.T) {
441
+ up := newUpstream(t, func(w http.ResponseWriter, r *http.Request) {
442
+ w.Header().Set("Content-Type", "application/json")
443
+ w.Header().Set("Retry-After", "7")
444
+ w.Header().Set("X-App-Reason", "no-such-note")
445
+ w.WriteHeader(map[string]int{"/missing": 404, "/boom": 500, "/busy": 503, "/denied": 401}[r.URL.Path])
446
+ io.WriteString(w, `{"error":"x"}`)
447
+ })
448
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
449
+ keep := map[string]string{"Cookie": "id=abc", "X-Synopkgland-Keep-Error-Body": "1"}
450
+ for path, want := range map[string][2]int{"/missing": {404, 410}, "/boom": {500, 520}, "/busy": {503, 521}, "/denied": {401, 401}} {
451
+ plain := do(g, "GET", base+path, signedIn, "")
452
+ if plain.Code != want[0] || plain.Header().Get("X-Synopkgland-Status") != "" {
453
+ t.Errorf("%s without the header: %d %v, want the container's own %d", path, plain.Code, plain.Header(), want[0])
454
+ }
455
+ w := do(g, "GET", base+path, keep, "")
456
+ if w.Code != want[1] || w.Body.String() != `{"error":"x"}` || w.Header().Get("Retry-After") != "7" || w.Header().Get("X-App-Reason") != "no-such-note" {
457
+ t.Errorf("%s with the header: %d %q %v", path, w.Code, w.Body.String(), w.Header())
458
+ }
459
+ if want[0] != want[1] && w.Header().Get("X-Synopkgland-Status") != strconv.Itoa(want[0]) {
460
+ t.Errorf("%s: X-Synopkgland-Status = %q", path, w.Header().Get("X-Synopkgland-Status"))
461
+ }
462
+ }
463
+ }
464
+
465
+ func TestKeepErrorBodyLeavesSuccessAndRedirectsAlone(t *testing.T) {
466
+ up := newUpstream(t, hello)
467
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
468
+ w := do(g, "GET", base+"/", map[string]string{"Cookie": "id=abc", "X-Synopkgland-Keep-Error-Body": "1"}, "")
469
+ if w.Code != 200 || w.Header().Get("X-Synopkgland-Status") != "" {
470
+ t.Fatalf("%d %v", w.Code, w.Header())
471
+ }
472
+ }
473
+
474
+ // Server-Sent Events work through DSM (nginx does not buffer SCGI replies there), provided the gateway hands
475
+ // each event on as it arrives instead of holding it until the container finishes.
476
+ func TestServerSentEventsAreForwardedAsTheyArrive(t *testing.T) {
477
+ release := make(chan struct{})
478
+ gone := make(chan struct{})
479
+ up := newUpstream(t, func(w http.ResponseWriter, r *http.Request) {
480
+ w.Header().Set("Content-Type", "text/event-stream")
481
+ io.WriteString(w, "data: one\n\n")
482
+ w.(http.Flusher).Flush()
483
+ select {
484
+ case <-release:
485
+ io.WriteString(w, "data: two\n\n")
486
+ case <-r.Context().Done():
487
+ close(gone)
488
+ }
489
+ })
490
+ srv := httptest.NewServer(newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up))
491
+ defer srv.Close()
492
+ req, _ := http.NewRequest("GET", srv.URL+base+"/events", nil)
493
+ req.Header.Set("Cookie", "id=abc")
494
+ resp, err := http.DefaultClient.Do(req)
495
+ if err != nil {
496
+ t.Fatal(err)
497
+ }
498
+ line := make(chan string, 1)
499
+ go func() {
500
+ buf := make([]byte, 64)
501
+ n, _ := resp.Body.Read(buf)
502
+ line <- string(buf[:n])
503
+ }()
504
+ select {
505
+ case got := <-line:
506
+ if got != "data: one\n\n" {
507
+ t.Fatalf("first event = %q", got)
508
+ }
509
+ case <-time.After(2 * time.Second):
510
+ t.Fatal("the first event was held back while the container kept the stream open")
511
+ }
512
+ resp.Body.Close()
513
+ select {
514
+ case <-gone:
515
+ case <-time.After(2 * time.Second):
516
+ t.Fatal("the container was not told the client went away")
517
+ }
518
+ }
519
+
520
+ // wsEchoHandler is a container that accepts a WebSocket upgrade and then answers every chunk with "echo:"+chunk.
521
+ func wsEchoHandler(w http.ResponseWriter, r *http.Request) {
522
+ conn, brw, err := w.(http.Hijacker).Hijack()
523
+ if err != nil {
524
+ return
525
+ }
526
+ defer conn.Close()
527
+ io.WriteString(brw, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n")
528
+ brw.Flush()
529
+ buf := make([]byte, 64)
530
+ for {
531
+ n, err := brw.Read(buf)
532
+ if n > 0 {
533
+ io.WriteString(conn, "echo:"+string(buf[:n]))
534
+ }
535
+ if err != nil {
536
+ return
537
+ }
538
+ }
539
+ }
540
+
541
+ func dialUpgrade(t *testing.T, srv *httptest.Server, headers string) (net.Conn, *bufio.Reader, string) {
542
+ t.Helper()
543
+ conn, err := net.Dial("tcp", srv.Listener.Addr().String())
544
+ if err != nil {
545
+ t.Fatal(err)
546
+ }
547
+ t.Cleanup(func() { conn.Close() })
548
+ conn.SetDeadline(time.Now().Add(5 * time.Second))
549
+ io.WriteString(conn, "GET "+base+"/ws HTTP/1.1\r\nHost: "+srv.Listener.Addr().String()+"\r\nCookie: id=abc\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n"+headers+"\r\n")
550
+ r := bufio.NewReader(conn)
551
+ status, err := r.ReadString('\n')
552
+ if err != nil {
553
+ t.Fatal(err)
554
+ }
555
+ return conn, r, strings.TrimSpace(status)
556
+ }
557
+
558
+ func TestWebSocketUpgradeIsProxiedBothWays(t *testing.T) {
559
+ up := newUpstream(t, wsEchoHandler)
560
+ srv := httptest.NewServer(newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up))
561
+ defer srv.Close()
562
+ conn, r, status := dialUpgrade(t, srv, "Origin: http://"+srv.Listener.Addr().String()+"\r\n")
563
+ if status != "HTTP/1.1 101 Switching Protocols" {
564
+ t.Fatalf("status = %q", status)
565
+ }
566
+ for line, err := r.ReadString('\n'); strings.TrimSpace(line) != ""; line, err = r.ReadString('\n') {
567
+ if err != nil {
568
+ t.Fatal(err)
569
+ }
570
+ }
571
+ io.WriteString(conn, "hello")
572
+ got := make([]byte, len("echo:hello"))
573
+ if _, err := io.ReadFull(r, got); err != nil || string(got) != "echo:hello" {
574
+ t.Fatalf("echo = %q, %v", got, err)
575
+ }
576
+ h := up.request.Load().Header
577
+ if h.Get("X-Syno-User") != "bob" || h.Get("Cookie") != "" || h.Get("Upgrade") != "websocket" {
578
+ t.Errorf("headers at the container: %v", h)
579
+ }
580
+ if up.request.Load().Host != srv.Listener.Addr().String() {
581
+ t.Errorf("Host at the container = %q, want the one the browser used so that same-origin checks pass", up.request.Load().Host)
582
+ }
583
+ }
584
+
585
+ // upgradeRefusal reads the rest of a refused handshake and returns its X-Synopkgland-Error and -Status values.
586
+ func upgradeRefusal(t *testing.T, r *bufio.Reader) (code, intended string) {
587
+ t.Helper()
588
+ for line, err := r.ReadString('\n'); strings.TrimSpace(line) != ""; line, err = r.ReadString('\n') {
589
+ if err != nil {
590
+ t.Fatal(err)
591
+ }
592
+ name, value, _ := strings.Cut(strings.TrimSpace(line), ": ")
593
+ switch strings.ToLower(name) {
594
+ case "x-synopkgland-error":
595
+ code = value
596
+ case "x-synopkgland-status":
597
+ intended = value
598
+ }
599
+ }
600
+ return code, intended
601
+ }
602
+
603
+ func TestWebSocketFromAnotherOriginIsRefused(t *testing.T) {
604
+ up := newUpstream(t, wsEchoHandler)
605
+ srv := httptest.NewServer(newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up))
606
+ defer srv.Close()
607
+ own := srv.Listener.Addr().String()
608
+ for name, headers := range map[string]string{
609
+ "foreign origin": "Origin: https://evil.example\r\n",
610
+ "sub-domain of host": "Origin: http://" + own + ".evil.example\r\n",
611
+ "origin null": "Origin: null\r\n",
612
+ "fetch cross-site": "Sec-Fetch-Site: cross-site\r\n",
613
+ "fetch same-site": "Sec-Fetch-Site: same-site\r\n",
614
+ "cross-site, own host": "Sec-Fetch-Site: cross-site\r\nOrigin: http://" + own + "\r\n",
615
+ } {
616
+ _, r, status := dialUpgrade(t, srv, headers)
617
+ code, intended := upgradeRefusal(t, r)
618
+ if status != "HTTP/1.1 401 Unauthorized" || code != "cross_site_blocked" || intended != "403" {
619
+ t.Errorf("%s: %q %q %q", name, status, code, intended)
620
+ }
621
+ }
622
+ if up.hits.Load() != 0 {
623
+ t.Fatalf("hits=%d", up.hits.Load())
624
+ }
625
+ }
626
+
627
+ // Browsers always send Origin on a WebSocket handshake, so a handshake with neither Origin nor
628
+ // Sec-Fetch-Site is not from a browser that can be trusted to scope it: the rule is the one state-changing requests use.
629
+ func TestWebSocketWithoutAnyOriginEvidenceIsRefused(t *testing.T) {
630
+ up := newUpstream(t, wsEchoHandler)
631
+ srv := httptest.NewServer(newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up))
632
+ defer srv.Close()
633
+ _, r, status := dialUpgrade(t, srv, "")
634
+ code, _ := upgradeRefusal(t, r)
635
+ if status != "HTTP/1.1 401 Unauthorized" || code != "cross_site_blocked" || up.hits.Load() != 0 {
636
+ t.Fatalf("%q %q hits=%d", status, code, up.hits.Load())
637
+ }
638
+ }
639
+
640
+ func TestWebSocketWithOneKindOfSameOriginEvidenceIsAllowed(t *testing.T) {
641
+ up := newUpstream(t, wsEchoHandler)
642
+ srv := httptest.NewServer(newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up))
643
+ defer srv.Close()
644
+ for name, headers := range map[string]string{
645
+ "origin only": "Origin: http://" + srv.Listener.Addr().String() + "\r\n",
646
+ "fetch metadata only": "Sec-Fetch-Site: same-origin\r\n",
647
+ } {
648
+ if _, _, status := dialUpgrade(t, srv, headers); status != "HTTP/1.1 101 Switching Protocols" {
649
+ t.Errorf("%s: %q", name, status)
650
+ }
651
+ }
652
+ }
653
+
654
+ func TestWebSocketNeedsTheSameAccessAsAnyRequest(t *testing.T) {
655
+ up := newUpstream(t, wsEchoHandler)
656
+ for name, auth := range map[string]*fakeAuth{"anonymous": {err: errNoSession}, "outsider": {id: identity{User: "bob", Groups: []string{"users"}}}} {
657
+ srv := httptest.NewServer(newTestGateway(t, config{Access: "admins"}, auth, up))
658
+ _, _, status := dialUpgrade(t, srv, "")
659
+ srv.Close()
660
+ if status != "HTTP/1.1 401 Unauthorized" {
661
+ t.Errorf("%s: %q", name, status)
662
+ }
663
+ }
664
+ if up.hits.Load() != 0 {
665
+ t.Fatalf("hits=%d", up.hits.Load())
666
+ }
667
+ }
668
+
669
+ func TestOtherProtocolUpgradesAreNotSupported(t *testing.T) {
670
+ up := newUpstream(t, hello)
671
+ g := newTestGateway(t, config{Access: "users"}, &fakeAuth{id: identity{User: "bob"}}, up)
672
+ w := do(g, "GET", base+"/", map[string]string{"Cookie": "id=abc", "Connection": "Upgrade", "Upgrade": "h2c"}, "")
673
+ if w.Code != 501 || up.hits.Load() != 0 || w.Header().Get("X-Synopkgland-Error") != "upgrade_unsupported" {
674
+ t.Fatalf("code=%d hits=%d headers=%v", w.Code, up.hits.Load(), w.Header())
675
+ }
676
+ }
@@ -0,0 +1,3 @@
1
+ module synopkgland-gateway
2
+
3
+ go 1.24