@synopackageland/cli 0.14.1 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +33 -18
  3. package/dist/build-spk/generator.d.ts +3 -2
  4. package/dist/build-spk/generator.js +6 -846
  5. package/dist/build-spk/resource-worker.d.ts +2 -14
  6. package/dist/build-spk/resource-worker.js +3 -166
  7. package/dist/cli.js +208 -56
  8. package/dist/compose/analyze.d.ts +3 -61
  9. package/dist/compose/analyze.js +35 -776
  10. package/dist/compose/env.d.ts +5 -3
  11. package/dist/compose/env.js +17 -25
  12. package/dist/compose/runner.js +23 -6
  13. package/dist/contract/unsupported-types.js +1 -1
  14. package/dist/contract/validate.js +2 -2
  15. package/dist/contract/web-launch.d.ts +1 -12
  16. package/dist/contract/web-launch.js +10 -74
  17. package/dist/ctl/cli.d.ts +36 -0
  18. package/dist/ctl/cli.js +218 -0
  19. package/dist/ctl/descriptor.d.ts +81 -0
  20. package/dist/ctl/descriptor.js +93 -0
  21. package/dist/ctl/dsm-auth.d.ts +10 -0
  22. package/dist/ctl/dsm-auth.js +30 -0
  23. package/dist/ctl/errors.d.ts +17 -0
  24. package/dist/ctl/errors.js +32 -0
  25. package/dist/ctl/flows.d.ts +11 -0
  26. package/dist/ctl/flows.js +52 -0
  27. package/dist/ctl/input.d.ts +20 -0
  28. package/dist/ctl/input.js +128 -0
  29. package/dist/ctl/json.d.ts +2 -0
  30. package/dist/ctl/json.js +38 -0
  31. package/dist/ctl/redaction.d.ts +15 -0
  32. package/dist/ctl/redaction.js +198 -0
  33. package/dist/ctl/service.d.ts +38 -0
  34. package/dist/ctl/service.js +156 -0
  35. package/dist/ctl/store.d.ts +51 -0
  36. package/dist/ctl/store.js +93 -0
  37. package/dist/ctl/transfers.d.ts +14 -0
  38. package/dist/ctl/transfers.js +74 -0
  39. package/dist/ctl/transport.d.ts +11 -0
  40. package/dist/ctl/transport.js +79 -0
  41. package/dist/deploy/deploy.d.ts +4 -3
  42. package/dist/deploy/deploy.js +5 -210
  43. package/dist/deploy/plan.js +26 -7
  44. package/dist/dev.js +5 -4
  45. package/dist/e2e/dsm.d.ts +2 -2
  46. package/dist/e2e/dsm.js +10 -6
  47. package/dist/error-registry.js +1020 -834
  48. package/dist/host/share-permission-lookup.d.ts +1 -1
  49. package/dist/host/share-permission.d.ts +1 -1
  50. package/dist/host/share-permission.js +1 -1
  51. package/dist/info.js +120 -28
  52. package/dist/init.js +188 -64
  53. package/dist/inspect.js +21 -6
  54. package/dist/runtime/executor-digests.json +5 -5
  55. package/dist/skill.js +51 -9
  56. package/dist/standalone/api-description-inspect.d.ts +4 -0
  57. package/dist/standalone/api-description-inspect.js +12 -0
  58. package/dist/standalone/api-description.d.ts +7 -0
  59. package/dist/standalone/api-description.js +73 -0
  60. package/dist/standalone/build.d.ts +3 -0
  61. package/dist/standalone/build.js +140 -33
  62. package/dist/standalone/compose-errors.d.ts +3 -0
  63. package/dist/standalone/compose-errors.js +243 -0
  64. package/dist/standalone/compose.d.ts +97 -10
  65. package/dist/standalone/compose.js +306 -75
  66. package/dist/standalone/daemon.js +111 -29
  67. package/dist/standalone/dsm-window.js +39 -8
  68. package/dist/standalone/identity-gateway.d.ts +9 -2
  69. package/dist/standalone/identity-gateway.js +282 -66
  70. package/dist/standalone/inspect-spk.d.ts +12 -0
  71. package/dist/standalone/inspect-spk.js +666 -136
  72. package/dist/standalone/lifecycle.js +124 -42
  73. package/dist/standalone/manifest.d.ts +8 -1
  74. package/dist/standalone/manifest.js +303 -86
  75. package/dist/standalone/resource.js +6 -2
  76. package/dist/standalone/spk.d.ts +4 -1
  77. package/dist/standalone/spk.js +138 -39
  78. package/dist/template-paths.d.ts +0 -2
  79. package/dist/template-paths.js +0 -5
  80. package/dist/test-command.d.ts +1 -1
  81. package/dist/test-command.js +1 -1
  82. package/dist/validate.d.ts +3 -3
  83. package/dist/validate.js +32 -140
  84. package/package.json +5 -3
  85. package/schema/synopkgland-api-v1.schema.json +423 -0
  86. package/skills/SKILL.md +4 -2
  87. package/skills/reference/compose.md +349 -0
  88. package/skills/reference/create.md +9 -18
  89. package/skills/reference/ctl.md +41 -0
  90. package/skills/reference/deploy.md +1 -1
  91. package/skills/reference/files.md +5 -1
  92. package/skills/reference/identity-gateway.md +8 -144
  93. package/skills/reference/wrap.md +10 -39
  94. package/assets/go/daemon/fixture_linux_test.go +0 -15
  95. package/assets/go/daemon/fixture_other_test.go +0 -7
  96. package/assets/go/daemon/go.mod +0 -5
  97. package/assets/go/daemon/main.go +0 -636
  98. package/assets/go/daemon/main_test.go +0 -315
  99. package/assets/go/daemon/network.go +0 -107
  100. package/assets/go/daemon/network_test.go +0 -86
  101. package/assets/go/daemon/process_linux.go +0 -55
  102. package/assets/go/daemon/process_linux_test.go +0 -161
  103. package/assets/go/daemon/process_other.go +0 -14
  104. package/assets/go/daemon/testdata/child.go +0 -44
  105. package/assets/go/gateway/auth.go +0 -101
  106. package/assets/go/gateway/auth_test.go +0 -216
  107. package/assets/go/gateway/cgi.go +0 -133
  108. package/assets/go/gateway/cgi_test.go +0 -156
  109. package/assets/go/gateway/csrf.go +0 -73
  110. package/assets/go/gateway/csrf_test.go +0 -155
  111. package/assets/go/gateway/directory.go +0 -108
  112. package/assets/go/gateway/directory_test.go +0 -202
  113. package/assets/go/gateway/gateway.go +0 -412
  114. package/assets/go/gateway/gateway_test.go +0 -676
  115. package/assets/go/gateway/go.mod +0 -5
  116. package/assets/go/gateway/groups.go +0 -66
  117. package/assets/go/gateway/main.go +0 -45
  118. package/dist/build-spk/gateway.d.ts +0 -31
  119. package/dist/build-spk/gateway.js +0 -60
  120. package/dist/compose/privilege.d.ts +0 -14
  121. package/dist/compose/privilege.js +0 -102
@@ -1,66 +0,0 @@
1
- package main
2
-
3
- import (
4
- "bufio"
5
- "context"
6
- "fmt"
7
- "os"
8
- "slices"
9
- "strings"
10
- )
11
-
12
- // groupsOf reads local group files fresh on each request. Directory accounts
13
- // use NSS afresh; DSM provider caching may delay directory membership changes.
14
- // DSM keeps no member list for the group
15
- // that is an account's primary group (users), so membership is the member
16
- // list or the primary gid from the passwd file. Accounts absent from passwd use
17
- // DSM NSS; no local member-list fallback is permitted on lookup failure.
18
- func (a *dsmAuthenticator) groupsOf(ctx context.Context, user string) ([]string, error) {
19
- primary, err := a.primaryGID(user)
20
- if err != nil {
21
- return nil, err
22
- }
23
- if primary == "" {
24
- if a.directoryGroups == nil {
25
- return nil, fmt.Errorf("directory group lookup unavailable")
26
- }
27
- return a.directoryGroups(ctx, user)
28
- }
29
- f, err := os.Open(a.groupFile)
30
- if err != nil {
31
- return nil, fmt.Errorf("read groups: %w", err)
32
- }
33
- defer f.Close()
34
- var groups []string
35
- scanner := bufio.NewScanner(f)
36
- for scanner.Scan() {
37
- fields := strings.Split(scanner.Text(), ":")
38
- if len(fields) == 4 && (fields[2] == primary || slices.Contains(strings.Split(fields[3], ","), user)) {
39
- groups = append(groups, fields[0])
40
- }
41
- }
42
- if err := scanner.Err(); err != nil {
43
- return nil, fmt.Errorf("read groups: %w", err)
44
- }
45
- return groups, nil
46
- }
47
-
48
- // primaryGID is the account's gid in the passwd file; "" when the account has
49
- // no local entry, which requires a fresh directory lookup.
50
- func (a *dsmAuthenticator) primaryGID(user string) (string, error) {
51
- f, err := os.Open(a.passwdFile)
52
- if err != nil {
53
- return "", fmt.Errorf("read accounts: %w", err)
54
- }
55
- defer f.Close()
56
- scanner := bufio.NewScanner(f)
57
- for scanner.Scan() {
58
- if fields := strings.Split(scanner.Text(), ":"); len(fields) >= 4 && fields[0] == user {
59
- return fields[3], nil
60
- }
61
- }
62
- if err := scanner.Err(); err != nil {
63
- return "", fmt.Errorf("read accounts: %w", err)
64
- }
65
- return "", nil
66
- }
@@ -1,45 +0,0 @@
1
- // Command gateway is the DSM-authenticated front door of a container package.
2
- // DSM runs it as a CGI (ui/cgi/gateway.cgi in the package) under the package
3
- // account, once per request.
4
- package main
5
-
6
- import (
7
- "bytes"
8
- "encoding/json"
9
- "fmt"
10
- "log"
11
- "net/http/cgi"
12
- "os"
13
- )
14
-
15
- // Set by `synopkgland build-spk` through -ldflags -X; never read from a request.
16
- var (
17
- packageID = "dev"
18
- configJSON = ""
19
- )
20
-
21
- func main() {
22
- log.SetFlags(0)
23
- log.SetPrefix(packageID + ": ")
24
- if os.Getenv("GATEWAY_INTERFACE") == "" {
25
- fmt.Fprintf(os.Stderr, "%s: this program runs as the package's DSM CGI; open the app from the DSM main menu.\n", packageID)
26
- os.Exit(2)
27
- }
28
- var cfg config
29
- decoder := json.NewDecoder(bytes.NewReader([]byte(configJSON)))
30
- decoder.DisallowUnknownFields()
31
- if err := decoder.Decode(&cfg); err != nil {
32
- log.Fatalf("invalid gateway configuration: %v", err)
33
- }
34
- g, err := newGateway(cfg, newDSMAuthenticator())
35
- if err != nil {
36
- log.Fatal(err)
37
- }
38
- req, err := cgi.Request()
39
- if err != nil {
40
- log.Fatal(err)
41
- }
42
- if err := serveCGI(g, req, os.Stdin, os.Stdout); err != nil {
43
- log.Fatal(err)
44
- }
45
- }
@@ -1,31 +0,0 @@
1
- import { type CommandRunner } from "../standalone/build.js";
2
- import type { AppContract } from "../types.js";
3
- import type { NativeArchFamily } from "./dsm-arch.js";
4
- export { gatewayEntryPath } from "../contract/web-launch.js";
5
- /** Where the gateway CGI sits in the package payload; DSM serves `ui/` under /webman/3rdparty/<package>/. */
6
- export declare const GATEWAY_CGI_PAYLOAD = "ui/cgi/gateway.cgi";
7
- /** Readable copy of the baked-in configuration, for inspection; the CGI does not read it. */
8
- export declare const GATEWAY_CONFIG_PAYLOAD = "gateway.json";
9
- export type GatewayErrorCode = "GATEWAY_GO_MISSING" | "GATEWAY_BUILD_FAILED";
10
- export interface GatewayConfig {
11
- upstream: string;
12
- access: "users" | "admins" | "groups";
13
- groups?: string[];
14
- }
15
- /** The access rule and container address the gateway enforces; undefined for an App without one. */
16
- export declare function gatewayConfig(contract: AppContract, adminport: number): GatewayConfig | undefined;
17
- /** Compiles the fixed gateway module for `arch`; the result is a static ELF or a coded failure. */
18
- export declare function buildGatewayBinary(input: {
19
- packageId: string;
20
- arch: NativeArchFamily;
21
- config: GatewayConfig;
22
- work: string;
23
- run?: CommandRunner;
24
- }): Promise<{
25
- binary: string;
26
- } | {
27
- error: GatewayErrorCode;
28
- detail: string;
29
- }>;
30
- /** Puts the CGI (mode 0755) and its readable configuration into the package payload. */
31
- export declare function writeGatewayPayload(packageDir: string, binary: string, config: GatewayConfig): void;
@@ -1,60 +0,0 @@
1
- import { goChildEnv } from "../runtime/go-env.js";
2
- import { chmodSync, copyFileSync, mkdirSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { checkCgiBinary } from "../standalone/binary.js";
5
- import { runCommand } from "../standalone/build.js";
6
- import { resolvedWebAccess } from "../contract/web-launch.js";
7
- import { goAssetDir } from "../template-paths.js";
8
- import { PINNED_GO_HINT, pinnedGoBuildEnv, pinnedGoProblem } from "./go-toolchain.js";
9
- export { gatewayEntryPath } from "../contract/web-launch.js";
10
- /** Where the gateway CGI sits in the package payload; DSM serves `ui/` under /webman/3rdparty/<package>/. */
11
- export const GATEWAY_CGI_PAYLOAD = "ui/cgi/gateway.cgi";
12
- /** Readable copy of the baked-in configuration, for inspection; the CGI does not read it. */
13
- export const GATEWAY_CONFIG_PAYLOAD = "gateway.json";
14
- const ACCESS = { "dsm-users": "users", "dsm-admins": "admins", "dsm-groups": "groups" };
15
- /** The access rule and container address the gateway enforces; undefined for an App without one. */
16
- export function gatewayConfig(contract, adminport) {
17
- const access = resolvedWebAccess(contract);
18
- if (!access || access.mode === "public") {
19
- return undefined;
20
- }
21
- return {
22
- upstream: `127.0.0.1:${adminport}`,
23
- access: ACCESS[access.mode],
24
- ...(access.mode === "dsm-groups" ? { groups: access.groups } : {}),
25
- };
26
- }
27
- /** Compiles the fixed gateway module for `arch`; the result is a static ELF or a coded failure. */
28
- export async function buildGatewayBinary(input) {
29
- const out = join(input.work, `gateway-${input.arch}.cgi`);
30
- const ldflags = ["-s", "-w", "-buildid=", `-X main.packageID=${input.packageId}`, `-X 'main.configJSON=${JSON.stringify(input.config)}'`].join(" ");
31
- const result = await (input.run ?? runCommand)("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, "."], {
32
- cwd: goAssetDir("gateway"),
33
- env: goChildEnv({ ...process.env, ...pinnedGoBuildEnv(input.arch) }),
34
- });
35
- if (result.missing) {
36
- return { error: "GATEWAY_GO_MISSING", detail: `The go command was not found. Building the DSM verification gateway needs Go 1.21 or later (https://go.dev/dl/). ${PINNED_GO_HINT}` };
37
- }
38
- if (result.status !== 0) {
39
- return { error: "GATEWAY_BUILD_FAILED", detail: `go build (${input.arch}) failed. ${PINNED_GO_HINT}\n${result.stderr}` };
40
- }
41
- const problem = checkCgiBinary(input.work, out, input.arch);
42
- if (problem) {
43
- return { error: "GATEWAY_BUILD_FAILED", detail: problem.message };
44
- }
45
- const toolchainProblem = pinnedGoProblem(out);
46
- if (toolchainProblem) {
47
- return { error: "GATEWAY_BUILD_FAILED", detail: toolchainProblem };
48
- }
49
- return { binary: out };
50
- }
51
- /** Puts the CGI (mode 0755) and its readable configuration into the package payload. */
52
- export function writeGatewayPayload(packageDir, binary, config) {
53
- const cgi = join(packageDir, GATEWAY_CGI_PAYLOAD);
54
- mkdirSync(join(cgi, ".."), { recursive: true });
55
- copyFileSync(binary, cgi);
56
- chmodSync(cgi, 0o755);
57
- const configPath = join(packageDir, GATEWAY_CONFIG_PAYLOAD);
58
- writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
59
- chmodSync(configPath, 0o644);
60
- }
@@ -1,14 +0,0 @@
1
- import type { AppContract, ComposeDocument } from "../types.js";
2
- export declare const PRIVILEGE_FLAGS: readonly ["privileged", "host_network", "host_pid", "host_ipc", "devices", "cap_add"];
3
- export type PrivilegeFlag = (typeof PRIVILEGE_FLAGS)[number];
4
- export interface PrivilegeFinding {
5
- flag: PrivilegeFlag;
6
- code: string;
7
- serviceName: string;
8
- path: string;
9
- message: string;
10
- suggestion: string;
11
- }
12
- export declare function declaredPrivilegeFlags(contract: AppContract): Set<PrivilegeFlag>;
13
- export declare function scanComposePrivilege(compose: ComposeDocument): PrivilegeFinding[];
14
- export declare function undeclaredPrivilegeFindings(compose: ComposeDocument, declared: ReadonlySet<PrivilegeFlag>): PrivilegeFinding[];
@@ -1,102 +0,0 @@
1
- export const PRIVILEGE_FLAGS = [
2
- "privileged",
3
- "host_network",
4
- "host_pid",
5
- "host_ipc",
6
- "devices",
7
- "cap_add",
8
- ];
9
- const FLAG_ERRORS = {
10
- privileged: {
11
- code: "COMPOSE_PRIVILEGED_FORBIDDEN",
12
- message: "Compose uses a privileged container but the contract does not declare privilege.privileged.",
13
- suggestion: "Add privilege.privileged: true to synology-app.yaml, or remove privileged: true.",
14
- },
15
- host_network: {
16
- code: "COMPOSE_HOST_NETWORK_FORBIDDEN",
17
- message: "Compose uses host network but the contract does not declare privilege.host_network.",
18
- suggestion: "Add privilege.host_network: true to synology-app.yaml, or use a controlled network mode.",
19
- },
20
- host_pid: {
21
- code: "COMPOSE_HOST_PID_FORBIDDEN",
22
- message: "Compose uses host PID but the contract does not declare privilege.host_pid.",
23
- suggestion: "Add privilege.host_pid: true to synology-app.yaml, or remove pid: host.",
24
- },
25
- host_ipc: {
26
- code: "COMPOSE_HOST_IPC_FORBIDDEN",
27
- message: "Compose uses host IPC but the contract does not declare privilege.host_ipc.",
28
- suggestion: "Add privilege.host_ipc: true to synology-app.yaml, or remove ipc: host.",
29
- },
30
- devices: {
31
- code: "COMPOSE_DEVICES_FORBIDDEN",
32
- message: "Compose mounts devices but the contract does not declare privilege.devices.",
33
- suggestion: "Add privilege.devices: true to synology-app.yaml, or remove devices.",
34
- },
35
- cap_add: {
36
- code: "COMPOSE_CAP_ADD_FORBIDDEN",
37
- message: "Compose uses extra cap_add but the contract does not declare privilege.cap_add.",
38
- suggestion: "Add privilege.cap_add: true to synology-app.yaml, or remove cap_add.",
39
- },
40
- };
41
- export function declaredPrivilegeFlags(contract) {
42
- const raw = contract.privilege;
43
- const flags = new Set();
44
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
45
- return flags;
46
- }
47
- const record = raw;
48
- for (const flag of PRIVILEGE_FLAGS) {
49
- if (record[flag] === true) {
50
- flags.add(flag);
51
- }
52
- }
53
- return flags;
54
- }
55
- export function scanComposePrivilege(compose) {
56
- const findings = [];
57
- if (!compose.services) {
58
- return findings;
59
- }
60
- for (const [serviceName, service] of Object.entries(compose.services)) {
61
- const basePath = `services.${serviceName}`;
62
- const add = (flag, path) => {
63
- const spec = FLAG_ERRORS[flag];
64
- findings.push({
65
- flag,
66
- code: spec.code,
67
- serviceName,
68
- path,
69
- message: spec.message,
70
- suggestion: spec.suggestion,
71
- });
72
- };
73
- if (service.privileged === true) {
74
- add("privileged", `${basePath}.privileged`);
75
- }
76
- if (service.network_mode === "host") {
77
- add("host_network", `${basePath}.network_mode`);
78
- }
79
- if (service.pid === "host") {
80
- add("host_pid", `${basePath}.pid`);
81
- }
82
- if (service.ipc === "host") {
83
- add("host_ipc", `${basePath}.ipc`);
84
- }
85
- if (hasEntries(service.devices)) {
86
- add("devices", `${basePath}.devices`);
87
- }
88
- if (hasEntries(service.cap_add)) {
89
- add("cap_add", `${basePath}.cap_add`);
90
- }
91
- }
92
- return findings;
93
- }
94
- export function undeclaredPrivilegeFindings(compose, declared) {
95
- return scanComposePrivilege(compose).filter((item) => !declared.has(item.flag));
96
- }
97
- function hasEntries(value) {
98
- if (Array.isArray(value)) {
99
- return value.length > 0;
100
- }
101
- return typeof value === "string" && value.trim() !== "";
102
- }