@synopackageland/cli 0.3.10 → 0.4.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 (280) hide show
  1. package/README.md +20 -9
  2. package/dist/bin.js +1 -2
  3. package/dist/build-spk/generator.d.ts +0 -3
  4. package/dist/build-spk/generator.js +135 -287
  5. package/dist/build-spk/native-lifecycle.js +0 -3
  6. package/dist/build-spk/native-payload.js +0 -1
  7. package/dist/cli.js +171 -151
  8. package/dist/compose/analyze.d.ts +1 -2
  9. package/dist/compose/analyze.js +5 -48
  10. package/dist/contract/unsupported-types.d.ts +10 -0
  11. package/dist/contract/unsupported-types.js +49 -0
  12. package/dist/contract/validate.js +25 -2
  13. package/dist/contract/web-launch.js +0 -2
  14. package/dist/deploy/deploy.d.ts +1 -4
  15. package/dist/deploy/deploy.js +45 -156
  16. package/dist/deploy/homes-values.js +1 -1
  17. package/dist/deploy/plan.d.ts +18 -0
  18. package/dist/deploy/plan.js +54 -0
  19. package/dist/deploy/spk-version.d.ts +2 -14
  20. package/dist/deploy/spk-version.js +25 -111
  21. package/dist/dev.d.ts +1 -3
  22. package/dist/dev.js +14 -28
  23. package/dist/error-registry.js +810 -862
  24. package/dist/host/dsm-client.js +12 -272
  25. package/dist/host/dsm-port-profile.d.ts +0 -1
  26. package/dist/host/dsm-port-profile.js +0 -2
  27. package/dist/host/types.d.ts +1 -83
  28. package/dist/index.d.ts +0 -9
  29. package/dist/index.js +0 -6
  30. package/dist/info.d.ts +0 -1
  31. package/dist/info.js +3 -10
  32. package/dist/init.d.ts +11 -1
  33. package/dist/init.js +112 -4
  34. package/dist/log.js +2 -3
  35. package/dist/native/validate.js +0 -1
  36. package/dist/standalone/binary.d.ts +9 -0
  37. package/dist/standalone/binary.js +67 -0
  38. package/dist/standalone/build.d.ts +25 -0
  39. package/dist/standalone/build.js +108 -0
  40. package/dist/standalone/dsm-window.d.ts +26 -0
  41. package/dist/standalone/dsm-window.js +251 -0
  42. package/dist/standalone/helper.d.ts +31 -0
  43. package/dist/standalone/helper.js +104 -0
  44. package/dist/standalone/i18n.d.ts +28 -0
  45. package/dist/standalone/i18n.js +161 -0
  46. package/dist/standalone/inspect-spk.d.ts +21 -0
  47. package/dist/standalone/inspect-spk.js +160 -0
  48. package/dist/standalone/manifest.d.ts +71 -0
  49. package/dist/standalone/manifest.js +276 -0
  50. package/dist/standalone/root-messages.d.ts +7 -0
  51. package/dist/standalone/root-messages.js +250 -0
  52. package/dist/standalone/root-ops.d.ts +42 -0
  53. package/dist/standalone/root-ops.js +101 -0
  54. package/dist/standalone/root-window.d.ts +35 -0
  55. package/dist/standalone/root-window.js +290 -0
  56. package/dist/standalone/spk.d.ts +22 -0
  57. package/dist/standalone/spk.js +205 -0
  58. package/dist/templates/binary/synology-app.yaml +7 -0
  59. package/dist/templates/hello-files/compose.yaml +0 -1
  60. package/dist/templates/hello-web/compose.yaml +13 -10
  61. package/dist/templates/hello-web/html/app.js +1 -23
  62. package/dist/templates/hello-web/html/index.html +1 -3
  63. package/dist/templates/hello-web-page/compose.yaml +13 -10
  64. package/dist/templates/hello-web-page/html/app.js +1 -23
  65. package/dist/templates/hello-web-page/html/index.html +1 -3
  66. package/dist/templates/native-go/README.md +268 -0
  67. package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
  68. package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
  69. package/dist/templates/native-go/backend/go.mod +3 -0
  70. package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
  71. package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
  72. package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
  73. package/dist/templates/native-go/backend/internal/api/server.go +155 -0
  74. package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
  75. package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
  76. package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
  77. package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
  78. package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  79. package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
  80. package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
  81. package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  82. package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  83. package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  84. package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
  85. package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  86. package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
  87. package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
  88. package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
  89. package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  90. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  91. package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  92. package/dist/templates/native-go/dsm-package.yaml +58 -0
  93. package/dist/templates/native-go/gitignore +1 -0
  94. package/dist/templates/native-go/i18n/glossary.json +662 -0
  95. package/dist/templates/native-go/i18n/messages/de.json +28 -0
  96. package/dist/templates/native-go/i18n/messages/en.json +28 -0
  97. package/dist/templates/native-go/i18n/messages/es.json +28 -0
  98. package/dist/templates/native-go/i18n/messages/fr.json +28 -0
  99. package/dist/templates/native-go/i18n/messages/it.json +28 -0
  100. package/dist/templates/native-go/i18n/messages/ja.json +28 -0
  101. package/dist/templates/native-go/i18n/messages/ko.json +28 -0
  102. package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
  103. package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
  104. package/dist/templates/native-go/ui/app.css +35 -0
  105. package/dist/templates/native-go/ui/app.js +94 -0
  106. package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  107. package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  108. package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
  109. package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
  110. package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
  111. package/dist/templates/native-go-better-auth/node/package.json +1 -0
  112. package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  113. package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  114. package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  115. package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  116. package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
  117. package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  118. package/dist/templates/native-go-root/root/whoami.sh +6 -0
  119. package/dist/templates/native-go-vue/README.md +110 -0
  120. package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
  121. package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
  122. package/dist/templates/native-go-vue/frontend/package.json +6 -0
  123. package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
  124. package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
  125. package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
  126. package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
  127. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  128. package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
  129. package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
  130. package/dist/templates/native-go-vue/gitignore +2 -0
  131. package/dist/types.d.ts +1 -1
  132. package/dist/validate.js +11 -7
  133. package/package.json +3 -7
  134. package/schema/synology-app.schema.json +36 -37
  135. package/skills/SKILL.md +4 -48
  136. package/skills/reference/create.md +11 -83
  137. package/skills/reference/deploy.md +7 -78
  138. package/skills/reference/diagnose.md +4 -101
  139. package/skills/reference/files.md +5 -41
  140. package/skills/reference/identity.md +3 -29
  141. package/skills/reference/wrap.md +0 -2
  142. package/templates/binary/synology-app.yaml +7 -0
  143. package/templates/hello-files/compose.yaml +0 -1
  144. package/templates/hello-web/compose.yaml +13 -10
  145. package/templates/hello-web/html/app.js +1 -23
  146. package/templates/hello-web/html/index.html +1 -3
  147. package/templates/hello-web-page/compose.yaml +13 -10
  148. package/templates/hello-web-page/html/app.js +1 -23
  149. package/templates/hello-web-page/html/index.html +1 -3
  150. package/templates/native-go/README.md +268 -0
  151. package/templates/native-go/backend/cmd/app/main.go +49 -0
  152. package/templates/native-go/backend/cmd/helper/main.go +38 -0
  153. package/templates/native-go/backend/go.mod +3 -0
  154. package/templates/native-go/backend/internal/api/errors.go +33 -0
  155. package/templates/native-go/backend/internal/api/registry.go +94 -0
  156. package/templates/native-go/backend/internal/api/registry_test.go +329 -0
  157. package/templates/native-go/backend/internal/api/server.go +155 -0
  158. package/templates/native-go/backend/internal/app/root_none.go +9 -0
  159. package/templates/native-go/backend/internal/app/routes.go +127 -0
  160. package/templates/native-go/backend/internal/app/routes_test.go +167 -0
  161. package/templates/native-go/backend/internal/dsm/auth.go +118 -0
  162. package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  163. package/templates/native-go/backend/internal/helper/control.go +219 -0
  164. package/templates/native-go/backend/internal/helper/control_test.go +242 -0
  165. package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  166. package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  167. package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  168. package/templates/native-go/backend/internal/helper/process_other.go +5 -0
  169. package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  170. package/templates/native-go/backend/internal/helper/status.go +271 -0
  171. package/templates/native-go/backend/internal/helper/status_test.go +205 -0
  172. package/templates/native-go/backend/internal/helper/supervision.go +84 -0
  173. package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  174. package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  175. package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  176. package/templates/native-go/dsm-package.yaml +58 -0
  177. package/templates/native-go/gitignore +1 -0
  178. package/templates/native-go/i18n/glossary.json +662 -0
  179. package/templates/native-go/i18n/messages/de.json +28 -0
  180. package/templates/native-go/i18n/messages/en.json +28 -0
  181. package/templates/native-go/i18n/messages/es.json +28 -0
  182. package/templates/native-go/i18n/messages/fr.json +28 -0
  183. package/templates/native-go/i18n/messages/it.json +28 -0
  184. package/templates/native-go/i18n/messages/ja.json +28 -0
  185. package/templates/native-go/i18n/messages/ko.json +28 -0
  186. package/templates/native-go/i18n/messages/zh-CN.json +28 -0
  187. package/templates/native-go/i18n/messages/zh-TW.json +28 -0
  188. package/templates/native-go/ui/app.css +35 -0
  189. package/templates/native-go/ui/app.js +94 -0
  190. package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  191. package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  192. package/templates/native-go-better-auth/node/app.mjs +72 -0
  193. package/templates/native-go-better-auth/node/build.mjs +2 -0
  194. package/templates/native-go-better-auth/node/package-lock.json +862 -0
  195. package/templates/native-go-better-auth/node/package.json +1 -0
  196. package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  197. package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  198. package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  199. package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  200. package/templates/native-go-root/backend/internal/app/root.go +107 -0
  201. package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  202. package/templates/native-go-root/root/whoami.sh +6 -0
  203. package/templates/native-go-vue/README.md +110 -0
  204. package/templates/native-go-vue/frontend/DESIGN.md +78 -0
  205. package/templates/native-go-vue/frontend/package-lock.json +2507 -0
  206. package/templates/native-go-vue/frontend/package.json +6 -0
  207. package/templates/native-go-vue/frontend/src/App.vue +132 -0
  208. package/templates/native-go-vue/frontend/src/context.ts +17 -0
  209. package/templates/native-go-vue/frontend/src/main.ts +26 -0
  210. package/templates/native-go-vue/frontend/src/style.css +14 -0
  211. package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  212. package/templates/native-go-vue/frontend/tsconfig.json +1 -0
  213. package/templates/native-go-vue/frontend/vite.config.ts +8 -0
  214. package/templates/native-go-vue/gitignore +2 -0
  215. package/dist/attestation/app-package.d.ts +0 -86
  216. package/dist/attestation/app-package.js +0 -206
  217. package/dist/attestation/install-on-host.d.ts +0 -14
  218. package/dist/attestation/install-on-host.js +0 -52
  219. package/dist/attestation/keyring.d.ts +0 -21
  220. package/dist/attestation/keyring.js +0 -168
  221. package/dist/attestation/platform-service.d.ts +0 -70
  222. package/dist/attestation/platform-service.js +0 -250
  223. package/dist/build-spk/broker-glue.d.ts +0 -16
  224. package/dist/build-spk/broker-glue.js +0 -40
  225. package/dist/deploy/catalog-publish.d.ts +0 -3
  226. package/dist/deploy/catalog-publish.js +0 -7
  227. package/dist/doctor.d.ts +0 -22
  228. package/dist/doctor.js +0 -822
  229. package/dist/error-collector.d.ts +0 -2
  230. package/dist/error-collector.js +0 -46
  231. package/dist/host/broker-paths.d.ts +0 -1
  232. package/dist/host/broker-paths.js +0 -1
  233. package/dist/host/broker-upgrade-preflight.d.ts +0 -61
  234. package/dist/host/broker-upgrade-preflight.js +0 -156
  235. package/dist/host/digests.d.ts +0 -5
  236. package/dist/host/digests.js +0 -63
  237. package/dist/host/fake-nas.d.ts +0 -49
  238. package/dist/host/fake-nas.js +0 -193
  239. package/dist/mock-broker/bootstrap.d.ts +0 -2
  240. package/dist/mock-broker/bootstrap.js +0 -8
  241. package/dist/mock-broker/files.d.ts +0 -60
  242. package/dist/mock-broker/files.js +0 -408
  243. package/dist/mock-broker/oidc.d.ts +0 -31
  244. package/dist/mock-broker/oidc.js +0 -161
  245. package/dist/mock-broker/path.d.ts +0 -9
  246. package/dist/mock-broker/path.js +0 -50
  247. package/dist/mock-broker/server.d.ts +0 -19
  248. package/dist/mock-broker/server.js +0 -350
  249. package/dist/publish/build-ledger.d.ts +0 -22
  250. package/dist/publish/build-ledger.js +0 -26
  251. package/dist/publish/community-path.d.ts +0 -6
  252. package/dist/publish/community-path.js +0 -41
  253. package/dist/publish/curated-release.d.ts +0 -78
  254. package/dist/publish/curated-release.js +0 -883
  255. package/dist/publish/file-build-ledger.d.ts +0 -18
  256. package/dist/publish/file-build-ledger.js +0 -268
  257. package/dist/publish/github-pr.d.ts +0 -25
  258. package/dist/publish/github-pr.js +0 -85
  259. package/dist/publish/github-repo.d.ts +0 -8
  260. package/dist/publish/github-repo.js +0 -21
  261. package/dist/publish/platform-service-release.d.ts +0 -60
  262. package/dist/publish/platform-service-release.js +0 -333
  263. package/dist/publish/preflight.d.ts +0 -24
  264. package/dist/publish/preflight.js +0 -138
  265. package/dist/publish/publish.d.ts +0 -27
  266. package/dist/publish/publish.js +0 -136
  267. package/dist/publish/sync-definition.d.ts +0 -4
  268. package/dist/publish/sync-definition.js +0 -48
  269. package/dist/release-bin.d.ts +0 -2
  270. package/dist/release-bin.js +0 -23
  271. package/dist/templates/hello-web/html/sdk.js +0 -68
  272. package/dist/templates/hello-web-page/html/sdk.js +0 -68
  273. package/dist/templates/native/synology-app.yaml +0 -7
  274. package/dist/test-host.d.ts +0 -15
  275. package/dist/test-host.js +0 -37
  276. package/templates/hello-web/html/sdk.js +0 -68
  277. package/templates/hello-web-page/html/sdk.js +0 -68
  278. package/templates/native/synology-app.yaml +0 -7
  279. /package/dist/templates/{native → binary}/native.yaml +0 -0
  280. /package/templates/{native → binary}/native.yaml +0 -0
@@ -0,0 +1,302 @@
1
+ package helper
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "net"
7
+ "os"
8
+ "os/exec"
9
+ "path/filepath"
10
+ "strconv"
11
+ "strings"
12
+ "syscall"
13
+ "testing"
14
+ "time"
15
+ )
16
+
17
+ // This opt-in fixture uses a root-only, disposable Linux VM and an independently
18
+ // cross-compiled cmd/helper. Never creates accounts or accesses a NAS.
19
+ func linuxFixture(t *testing.T) LinuxProcesses {
20
+ t.Helper()
21
+ source := os.Getenv("M1C_HELPER_FIXTURE")
22
+ if source == "" {
23
+ t.Skip("requires disposable Linux root VM and M1C_HELPER_FIXTURE")
24
+ }
25
+ if os.Geteuid() != 0 {
26
+ t.Fatal("VM fixture requires root")
27
+ }
28
+ digest, err := executableDigest(source)
29
+ if err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ i := Inventory{Package: "root", Version: "1.0.0-0001", Protocol: 1, PayloadDigest: digest, RegistryDigest: strings.Repeat("b", 64)}
33
+ c := NewClient(i)
34
+ for _, dir := range []string{c.StateDir, c.RunDir} {
35
+ if err := os.Mkdir(dir, 0700); err != nil {
36
+ t.Fatal(err)
37
+ }
38
+ t.Cleanup(func() { os.RemoveAll(dir) })
39
+ }
40
+ exe := filepath.Join(c.StateDir, "payload-"+digest)
41
+ b, err := os.ReadFile(source)
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if err := os.WriteFile(exe, b, 0700); err != nil {
46
+ t.Fatal(err)
47
+ }
48
+ return LinuxProcesses{Client: c, Binary: exe}
49
+ }
50
+ func startVMHelper(t *testing.T, p LinuxProcesses) Process {
51
+ t.Helper()
52
+ ctl := Controller{Client: p.Client, Processes: p}
53
+ if err := ctl.Control(context.Background(), "enable"); err != nil {
54
+ t.Fatal(err)
55
+ }
56
+ record, err := readProcess(p.RunDir, 0)
57
+ if err != nil {
58
+ t.Fatal(err)
59
+ }
60
+ t.Cleanup(func() { ctl.Control(context.Background(), "disable") })
61
+ return record
62
+ }
63
+ func supervisedPID(t *testing.T, pid int) int {
64
+ t.Helper()
65
+ paths, _ := filepath.Glob("/proc/" + strconv.Itoa(pid) + "/task/*/children")
66
+ for _, path := range paths {
67
+ b, _ := os.ReadFile(path)
68
+ for _, s := range strings.Fields(string(b)) {
69
+ child, _ := strconv.Atoi(s)
70
+ if child > 1 {
71
+ return child
72
+ }
73
+ }
74
+ }
75
+ t.Fatal("supervisor has no child")
76
+ return 0
77
+ }
78
+
79
+ // Observation only: an absent process or zombie has exited. No signal is sent
80
+ // to this PID, including if it has been reused. These fixtures run in a fresh VM.
81
+ func waitPIDExit(t *testing.T, pid int) {
82
+ t.Helper()
83
+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
84
+ defer cancel()
85
+ if waitHealthy(ctx, func(context.Context) bool {
86
+ b, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
87
+ if os.IsNotExist(err) {
88
+ return true
89
+ }
90
+ end := strings.LastIndex(string(b), ") ")
91
+ return err == nil && end >= 0 && strings.HasPrefix(string(b)[end+2:], "Z ")
92
+ }) != nil {
93
+ t.Fatal("owned process survived")
94
+ }
95
+ }
96
+ func TestLinuxActivationAlwaysUsesSupervisorAndNormalStop(t *testing.T) {
97
+ p := linuxFixture(t)
98
+ for n := 0; n < 20; n++ {
99
+ t.Run("cycle-"+strconv.Itoa(n), func(t *testing.T) {
100
+ proc := startVMHelper(t, p)
101
+ if proc.Mode != "supervised" {
102
+ t.Fatalf("activation did not select supervisor: %+v", proc)
103
+ }
104
+ child := supervisedPID(t, proc.PID)
105
+ if p.Status(context.Background()).State != "ready" {
106
+ t.Fatal("missing live handshake")
107
+ }
108
+ if err := (Controller{Client: p.Client, Processes: p}).Control(context.Background(), "disable"); err != nil {
109
+ t.Fatal(err)
110
+ }
111
+ waitPIDExit(t, child)
112
+ if err := p.CheckStopped(context.Background()); err != nil {
113
+ t.Fatal(err)
114
+ }
115
+ })
116
+ }
117
+ }
118
+
119
+ func TestLinuxHelperSelfExitIsReapedWithoutSignallingReusedPID(t *testing.T) {
120
+ p := linuxFixture(t)
121
+ proc := startVMHelper(t, p)
122
+ if proc.Mode != "supervised" {
123
+ t.Fatal("not supervised")
124
+ }
125
+ child := supervisedPID(t, proc.PID)
126
+ // The VM-only helper fixture exits itself when this root-owned marker exists.
127
+ if err := os.WriteFile(filepath.Join(p.RunDir, "fixture-exit"), nil, 0600); err != nil {
128
+ t.Fatal(err)
129
+ }
130
+ waitPIDExit(t, child)
131
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
132
+ defer cancel()
133
+ if waitHealthy(ctx, func(context.Context) bool {
134
+ _, err := os.Stat("/proc/" + strconv.Itoa(child))
135
+ return os.IsNotExist(err)
136
+ }) != nil {
137
+ t.Fatal("supervisor did not reap")
138
+ }
139
+ if err := p.Stop(proc); err != nil {
140
+ t.Fatal(err)
141
+ }
142
+ }
143
+ func TestLinuxSupervisorDeathKillsHelperAndPreservesFailedCleanup(t *testing.T) {
144
+ p := linuxFixture(t)
145
+ record(t, p.Client, Record{Inventory: p.Expected, Enabled: true})
146
+ // Keep the supervisor as our own unreaped child; only its parent signals it.
147
+ cmd := exec.Command(p.Binary, "supervise")
148
+ if err := cmd.Start(); err != nil {
149
+ t.Fatal(err)
150
+ }
151
+ owned := &ownedLinuxChild{process: cmd.Process}
152
+ t.Cleanup(func() { stopChild(context.Background(), owned, time.Millisecond) })
153
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
154
+ err := waitHealthy(ctx, func(ctx context.Context) bool { return p.Status(ctx).State == "ready" })
155
+ cancel()
156
+ if err != nil {
157
+ t.Fatal(err)
158
+ }
159
+ proc, err := readProcess(p.RunDir, 0)
160
+ if err != nil || proc.Mode != "supervised" || proc.PID != cmd.Process.Pid {
161
+ t.Fatalf("%+v %v", proc, err)
162
+ }
163
+ child := supervisedPID(t, proc.PID)
164
+ if err := owned.signal(syscall.SIGKILL); err != nil {
165
+ t.Fatal(err)
166
+ }
167
+ waitPIDExit(t, child)
168
+ waitPIDExit(t, proc.PID)
169
+ ctl := Controller{Client: p.Client, Processes: p}
170
+ if ctl.Control(context.Background(), "teardown") == nil {
171
+ t.Fatal("lost supervisor falsely cleaned")
172
+ }
173
+ s := p.Status(context.Background())
174
+ if s.State != "failed" || s.Cleanup != "required" {
175
+ t.Fatal(s)
176
+ }
177
+ if ctl.Control(context.Background(), "disable") == nil {
178
+ t.Fatal("lost supervisor falsely disabled cleanly")
179
+ }
180
+ r, err := readRecord(p.StateDir, 0)
181
+ if err != nil || r.Enabled || !r.Residual {
182
+ t.Fatalf("%+v %v", r, err)
183
+ }
184
+ if _, err := os.Stat(filepath.Join(p.RunDir, "process.json")); err != nil {
185
+ t.Fatal("failure evidence removed")
186
+ }
187
+ }
188
+ func TestLinuxUnsupervisedRecordAndDirectServeRefuse(t *testing.T) {
189
+ p := linuxFixture(t)
190
+ proc := startVMHelper(t, p)
191
+ for _, mode := range []string{"", "unknown"} {
192
+ foreign := proc
193
+ foreign.Mode = mode
194
+ if p.Stop(foreign) == nil {
195
+ t.Fatal("unsupervised record accepted")
196
+ }
197
+ }
198
+ // Remove stopped sockets before testing the retired action, so a bind
199
+ // conflict cannot masquerade as refusal of unsupervised startup.
200
+ if err := (Controller{Client: p.Client, Processes: p}).Control(context.Background(), "teardown"); err != nil {
201
+ t.Fatal(err)
202
+ }
203
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
204
+ defer cancel()
205
+ cmd := exec.CommandContext(ctx, p.Binary, "serve")
206
+ if cmd.Run() == nil || ctx.Err() != nil {
207
+ t.Fatal("direct unsupervised serve accepted")
208
+ }
209
+ if _, err := os.Stat(filepath.Join(p.RunDir, "helper.sock")); !os.IsNotExist(err) {
210
+ t.Fatal("direct serve opened socket")
211
+ }
212
+ }
213
+
214
+ func TestLinuxOwnedChildSelfExitAndECHILDRefusal(t *testing.T) {
215
+ for _, externallyReaped := range []bool{false, true} {
216
+ cmd := exec.Command("/bin/sh", "-c", "exit 0")
217
+ if err := cmd.Start(); err != nil {
218
+ t.Fatal(err)
219
+ }
220
+ child := &ownedLinuxChild{process: cmd.Process}
221
+ if externallyReaped {
222
+ if err := cmd.Wait(); err != nil {
223
+ t.Fatal(err)
224
+ }
225
+ if err := stopChild(context.Background(), child, time.Millisecond); !errors.Is(err, syscall.ECHILD) {
226
+ t.Fatal("ECHILD did not refuse", err)
227
+ }
228
+ } else {
229
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
230
+ err := waitHealthy(ctx, func(context.Context) bool { done, err := child.exited(); return done && err == nil })
231
+ cancel()
232
+ if err != nil {
233
+ t.Fatal(err)
234
+ }
235
+ if err := stopChild(context.Background(), child, time.Millisecond); err != nil {
236
+ t.Fatal(err)
237
+ }
238
+ if child.signal(syscall.SIGTERM) == nil {
239
+ t.Fatal("reaped PID signalled")
240
+ }
241
+ }
242
+ }
243
+ }
244
+ func TestLinuxSupervisorIdentityConflictsAndSocketSubstitutionRefuse(t *testing.T) {
245
+ p := linuxFixture(t)
246
+ proc := startVMHelper(t, p)
247
+ for _, field := range []string{"boot", "start", "inode", "digest", "mode"} {
248
+ foreign := proc
249
+ switch field {
250
+ case "boot":
251
+ foreign.Boot = "reboot"
252
+ case "start":
253
+ foreign.Start = "reuse"
254
+ case "inode":
255
+ foreign.Inode++
256
+ case "digest":
257
+ foreign.Inventory.PayloadDigest = strings.Repeat("c", 64)
258
+ case "mode":
259
+ foreign.Mode = "unknown"
260
+ }
261
+ if p.Stop(foreign) == nil {
262
+ t.Fatal("foreign supervisor accepted", field)
263
+ }
264
+ }
265
+ socket := filepath.Join(p.RunDir, "supervisor.sock")
266
+ old := socket + ".owned"
267
+ if err := os.Rename(socket, old); err != nil {
268
+ t.Fatal(err)
269
+ }
270
+ fake, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"})
271
+ if err != nil {
272
+ t.Fatal(err)
273
+ }
274
+ if err := os.Chmod(socket, 0600); err != nil {
275
+ t.Fatal(err)
276
+ }
277
+ if p.Stop(proc) == nil {
278
+ t.Fatal("other root PID substituted supervisor")
279
+ }
280
+ fake.Close()
281
+ if err := os.Rename(old, socket); err != nil {
282
+ t.Fatal(err)
283
+ }
284
+ if p.Status(context.Background()).State != "ready" {
285
+ t.Fatal("refusal stopped real helper")
286
+ }
287
+ if err := p.Stop(proc); err != nil {
288
+ t.Fatal(err)
289
+ }
290
+ }
291
+ func TestLinuxReparentedChildRefusesBeforeSocket(t *testing.T) {
292
+ p := linuxFixture(t)
293
+ record(t, p.Client, Record{Inventory: p.Expected, Enabled: true})
294
+ cmd := exec.Command(p.Binary, "serve-child")
295
+ cmd.Env = []string{"PATH=/usr/sbin:/usr/bin:/sbin:/bin", "SYNOPKGLAND_PARENT=1"}
296
+ if cmd.Run() == nil {
297
+ t.Fatal("already reparented child served")
298
+ }
299
+ if _, err := os.Stat(filepath.Join(p.RunDir, "helper.sock")); !os.IsNotExist(err) {
300
+ t.Fatal("unsafe child opened socket")
301
+ }
302
+ }
@@ -0,0 +1,58 @@
1
+ # Standalone native DSM package: an unsigned SPK that runs as its own package
2
+ # `synopkgland build-spk` reads this file.
3
+ schema: 1
4
+
5
+ # INFO.package: letters and digits, starting with a letter. It also names the
6
+ # DSM app (SYNO.SDS.<package>) and the CGI URL /webman/3rdparty/<package>/.
7
+ package: __PACKAGE__
8
+ version: 0.1.0
9
+ # Increase for every SPK you hand out with the same version (INFO 0.1.0-0001).
10
+ build: 1
11
+ displayname: __DISPLAY_NAME__
12
+ description: A native DSM package with a Go backend and a DSM window UI.
13
+ maintainer: Your Name
14
+ os_min_ver: "7.2-64570"
15
+
16
+ # One SPK per architecture. Supported: x86_64, aarch64.
17
+ arches: [x86_64, aarch64]
18
+
19
+ backend:
20
+ # Build the Go module in ./backend (package ./cmd/app) as the CGI.
21
+ go:
22
+ dir: backend
23
+ main: ./cmd/app
24
+ # Or ship a CGI executable you already have (remove `go:` above). Each file
25
+ # must be a static Linux ELF for its arch; it then owns authentication itself.
26
+ # binary:
27
+ # x86_64: prebuilt/api-x86_64
28
+ # aarch64: prebuilt/api-aarch64
29
+
30
+ ui:
31
+ # Static files served at /webman/3rdparty/<package>/app/. app.js and app.css
32
+ # are required; anything else (images, chunks) is copied as is.
33
+ dir: ui
34
+
35
+ # UI languages: i18n/messages/<locale>.json for en, zh-TW, zh-CN, ja, ko, de,
36
+ # fr, es and it (DSM's other languages fall back to English). build-spk checks
37
+ # that every language has the en.json keys and placeholders, and that the keys
38
+ # listed in the glossary use Synology's own DSM wording.
39
+ i18n:
40
+ messages: i18n/messages
41
+ glossary: i18n/glossary.json
42
+ # Package Center text per language (INFO displayname_<lang>/description_<lang>).
43
+ # English comes from displayname/description above.
44
+ info:
45
+ zh-TW: { description: 以 Go 後端與 DSM 視窗介面打造的原生 DSM 套件。 }
46
+ zh-CN: { description: 以 Go 后端与 DSM 窗口界面构建的原生 DSM 套件。 }
47
+ ja: { description: Go バックエンドと DSM ウィンドウ UI を備えたネイティブ DSM パッケージ。 }
48
+ ko: { description: Go 백엔드와 DSM 창 UI를 갖춘 네이티브 DSM 패키지. }
49
+ de: { description: Ein natives DSM-Paket mit Go-Backend und DSM-Fenster-Oberfläche. }
50
+ fr: { description: Un paquet DSM natif avec un backend Go et une interface en fenêtre DSM. }
51
+ es: { description: Un paquete nativo de DSM con backend en Go e interfaz en ventana de DSM. }
52
+ it: { description: Un pacchetto DSM nativo con backend Go e interfaccia in una finestra DSM. }
53
+
54
+ # none: everything runs as the package account.
55
+ # root: also fixed root operations, one root/<name>.sh each, that run only
56
+ # after a DSM administrator approves them in the package window.
57
+ # See README.md "Root access" before using it.
58
+ privilege: __PRIVILEGE__
@@ -0,0 +1 @@
1
+ dist/