@synopackageland/cli 0.3.9 → 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.
- package/README.md +20 -9
- package/dist/bin.js +1 -2
- package/dist/build-spk/generator.d.ts +0 -3
- package/dist/build-spk/generator.js +135 -287
- package/dist/build-spk/native-lifecycle.js +0 -3
- package/dist/build-spk/native-payload.js +0 -1
- package/dist/cli.js +171 -151
- package/dist/compose/analyze.d.ts +1 -2
- package/dist/compose/analyze.js +5 -48
- package/dist/contract/unsupported-types.d.ts +10 -0
- package/dist/contract/unsupported-types.js +49 -0
- package/dist/contract/validate.js +25 -2
- package/dist/contract/web-launch.js +0 -2
- package/dist/deploy/deploy.d.ts +1 -4
- package/dist/deploy/deploy.js +57 -156
- package/dist/deploy/homes-values.js +1 -1
- package/dist/deploy/plan.d.ts +18 -0
- package/dist/deploy/plan.js +54 -0
- package/dist/deploy/port-preflight.d.ts +21 -0
- package/dist/deploy/port-preflight.js +42 -0
- package/dist/deploy/spk-version.d.ts +2 -14
- package/dist/deploy/spk-version.js +25 -111
- package/dist/dev.d.ts +1 -3
- package/dist/dev.js +14 -28
- package/dist/error-registry.js +810 -857
- package/dist/host/dsm-client.js +23 -263
- package/dist/host/dsm-port-profile.d.ts +47 -0
- package/dist/host/dsm-port-profile.js +206 -0
- package/dist/host/port-info-lookup.d.ts +9 -0
- package/dist/host/port-info-lookup.js +87 -0
- package/dist/host/port-info.d.ts +33 -0
- package/dist/host/port-info.js +66 -0
- package/dist/host/types.d.ts +6 -78
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -6
- package/dist/info.d.ts +0 -1
- package/dist/info.js +3 -10
- package/dist/init.d.ts +11 -1
- package/dist/init.js +112 -4
- package/dist/log.js +2 -3
- package/dist/native/validate.js +0 -1
- package/dist/standalone/binary.d.ts +9 -0
- package/dist/standalone/binary.js +67 -0
- package/dist/standalone/build.d.ts +25 -0
- package/dist/standalone/build.js +108 -0
- package/dist/standalone/dsm-window.d.ts +26 -0
- package/dist/standalone/dsm-window.js +251 -0
- package/dist/standalone/helper.d.ts +31 -0
- package/dist/standalone/helper.js +104 -0
- package/dist/standalone/i18n.d.ts +28 -0
- package/dist/standalone/i18n.js +161 -0
- package/dist/standalone/inspect-spk.d.ts +21 -0
- package/dist/standalone/inspect-spk.js +160 -0
- package/dist/standalone/manifest.d.ts +71 -0
- package/dist/standalone/manifest.js +276 -0
- package/dist/standalone/root-messages.d.ts +7 -0
- package/dist/standalone/root-messages.js +250 -0
- package/dist/standalone/root-ops.d.ts +42 -0
- package/dist/standalone/root-ops.js +101 -0
- package/dist/standalone/root-window.d.ts +35 -0
- package/dist/standalone/root-window.js +290 -0
- package/dist/standalone/spk.d.ts +22 -0
- package/dist/standalone/spk.js +205 -0
- package/dist/templates/binary/synology-app.yaml +7 -0
- package/dist/templates/hello-files/compose.yaml +0 -1
- package/dist/templates/hello-web/compose.yaml +13 -10
- package/dist/templates/hello-web/html/app.js +1 -23
- package/dist/templates/hello-web/html/index.html +1 -3
- package/dist/templates/hello-web-page/compose.yaml +13 -10
- package/dist/templates/hello-web-page/html/app.js +1 -23
- package/dist/templates/hello-web-page/html/index.html +1 -3
- package/dist/templates/native-go/README.md +268 -0
- package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
- package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/dist/templates/native-go/backend/go.mod +3 -0
- package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
- package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
- package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/dist/templates/native-go/backend/internal/api/server.go +155 -0
- package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
- package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
- package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
- package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/dist/templates/native-go/dsm-package.yaml +58 -0
- package/dist/templates/native-go/gitignore +1 -0
- package/dist/templates/native-go/i18n/glossary.json +662 -0
- package/dist/templates/native-go/i18n/messages/de.json +28 -0
- package/dist/templates/native-go/i18n/messages/en.json +28 -0
- package/dist/templates/native-go/i18n/messages/es.json +28 -0
- package/dist/templates/native-go/i18n/messages/fr.json +28 -0
- package/dist/templates/native-go/i18n/messages/it.json +28 -0
- package/dist/templates/native-go/i18n/messages/ja.json +28 -0
- package/dist/templates/native-go/i18n/messages/ko.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/dist/templates/native-go/ui/app.css +35 -0
- package/dist/templates/native-go/ui/app.js +94 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
- package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
- package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/dist/templates/native-go-better-auth/node/package.json +1 -0
- package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/dist/templates/native-go-root/root/whoami.sh +6 -0
- package/dist/templates/native-go-vue/README.md +110 -0
- package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/dist/templates/native-go-vue/frontend/package.json +6 -0
- package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/dist/templates/native-go-vue/gitignore +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate.js +11 -7
- package/package.json +3 -7
- package/schema/synology-app.schema.json +36 -37
- package/skills/SKILL.md +4 -48
- package/skills/reference/create.md +11 -83
- package/skills/reference/deploy.md +7 -78
- package/skills/reference/diagnose.md +4 -101
- package/skills/reference/files.md +5 -41
- package/skills/reference/identity.md +3 -29
- package/skills/reference/wrap.md +0 -2
- package/templates/binary/synology-app.yaml +7 -0
- package/templates/hello-files/compose.yaml +0 -1
- package/templates/hello-web/compose.yaml +13 -10
- package/templates/hello-web/html/app.js +1 -23
- package/templates/hello-web/html/index.html +1 -3
- package/templates/hello-web-page/compose.yaml +13 -10
- package/templates/hello-web-page/html/app.js +1 -23
- package/templates/hello-web-page/html/index.html +1 -3
- package/templates/native-go/README.md +268 -0
- package/templates/native-go/backend/cmd/app/main.go +49 -0
- package/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/templates/native-go/backend/go.mod +3 -0
- package/templates/native-go/backend/internal/api/errors.go +33 -0
- package/templates/native-go/backend/internal/api/registry.go +94 -0
- package/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/templates/native-go/backend/internal/api/server.go +155 -0
- package/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/templates/native-go/backend/internal/app/routes.go +127 -0
- package/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/templates/native-go/backend/internal/helper/control.go +219 -0
- package/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/templates/native-go/backend/internal/helper/status.go +271 -0
- package/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/templates/native-go/dsm-package.yaml +58 -0
- package/templates/native-go/gitignore +1 -0
- package/templates/native-go/i18n/glossary.json +662 -0
- package/templates/native-go/i18n/messages/de.json +28 -0
- package/templates/native-go/i18n/messages/en.json +28 -0
- package/templates/native-go/i18n/messages/es.json +28 -0
- package/templates/native-go/i18n/messages/fr.json +28 -0
- package/templates/native-go/i18n/messages/it.json +28 -0
- package/templates/native-go/i18n/messages/ja.json +28 -0
- package/templates/native-go/i18n/messages/ko.json +28 -0
- package/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/templates/native-go/ui/app.css +35 -0
- package/templates/native-go/ui/app.js +94 -0
- package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/templates/native-go-better-auth/node/app.mjs +72 -0
- package/templates/native-go-better-auth/node/build.mjs +2 -0
- package/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/templates/native-go-better-auth/node/package.json +1 -0
- package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/templates/native-go-root/root/whoami.sh +6 -0
- package/templates/native-go-vue/README.md +110 -0
- package/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/templates/native-go-vue/frontend/package.json +6 -0
- package/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/templates/native-go-vue/frontend/src/style.css +14 -0
- package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/templates/native-go-vue/gitignore +2 -0
- package/dist/attestation/app-package.d.ts +0 -86
- package/dist/attestation/app-package.js +0 -206
- package/dist/attestation/install-on-host.d.ts +0 -14
- package/dist/attestation/install-on-host.js +0 -52
- package/dist/attestation/keyring.d.ts +0 -21
- package/dist/attestation/keyring.js +0 -168
- package/dist/attestation/platform-service.d.ts +0 -70
- package/dist/attestation/platform-service.js +0 -250
- package/dist/build-spk/broker-glue.d.ts +0 -16
- package/dist/build-spk/broker-glue.js +0 -40
- package/dist/deploy/catalog-publish.d.ts +0 -3
- package/dist/deploy/catalog-publish.js +0 -7
- package/dist/doctor.d.ts +0 -22
- package/dist/doctor.js +0 -721
- package/dist/error-collector.d.ts +0 -2
- package/dist/error-collector.js +0 -46
- package/dist/host/broker-paths.d.ts +0 -1
- package/dist/host/broker-paths.js +0 -1
- package/dist/host/broker-upgrade-preflight.d.ts +0 -61
- package/dist/host/broker-upgrade-preflight.js +0 -156
- package/dist/host/digests.d.ts +0 -5
- package/dist/host/digests.js +0 -63
- package/dist/host/fake-nas.d.ts +0 -41
- package/dist/host/fake-nas.js +0 -174
- package/dist/mock-broker/bootstrap.d.ts +0 -2
- package/dist/mock-broker/bootstrap.js +0 -8
- package/dist/mock-broker/files.d.ts +0 -60
- package/dist/mock-broker/files.js +0 -408
- package/dist/mock-broker/oidc.d.ts +0 -31
- package/dist/mock-broker/oidc.js +0 -161
- package/dist/mock-broker/path.d.ts +0 -9
- package/dist/mock-broker/path.js +0 -50
- package/dist/mock-broker/server.d.ts +0 -19
- package/dist/mock-broker/server.js +0 -350
- package/dist/publish/build-ledger.d.ts +0 -22
- package/dist/publish/build-ledger.js +0 -26
- package/dist/publish/community-path.d.ts +0 -6
- package/dist/publish/community-path.js +0 -41
- package/dist/publish/curated-release.d.ts +0 -78
- package/dist/publish/curated-release.js +0 -883
- package/dist/publish/file-build-ledger.d.ts +0 -18
- package/dist/publish/file-build-ledger.js +0 -268
- package/dist/publish/github-pr.d.ts +0 -25
- package/dist/publish/github-pr.js +0 -85
- package/dist/publish/github-repo.d.ts +0 -8
- package/dist/publish/github-repo.js +0 -21
- package/dist/publish/platform-service-release.d.ts +0 -60
- package/dist/publish/platform-service-release.js +0 -333
- package/dist/publish/preflight.d.ts +0 -24
- package/dist/publish/preflight.js +0 -138
- package/dist/publish/publish.d.ts +0 -27
- package/dist/publish/publish.js +0 -136
- package/dist/publish/sync-definition.d.ts +0 -4
- package/dist/publish/sync-definition.js +0 -48
- package/dist/release-bin.d.ts +0 -2
- package/dist/release-bin.js +0 -23
- package/dist/templates/hello-web/html/sdk.js +0 -68
- package/dist/templates/hello-web-page/html/sdk.js +0 -68
- package/dist/templates/native/synology-app.yaml +0 -7
- package/dist/test-host.d.ts +0 -15
- package/dist/test-host.js +0 -37
- package/templates/hello-web/html/sdk.js +0 -68
- package/templates/hello-web-page/html/sdk.js +0 -68
- package/templates/native/synology-app.yaml +0 -7
- /package/dist/templates/{native → binary}/native.yaml +0 -0
- /package/templates/{native → binary}/native.yaml +0 -0
|
@@ -13,31 +13,17 @@ import { writeDsmWindowIcons, writePackageCenterIcons } from "./dsm-icons.js";
|
|
|
13
13
|
import { infoArchValue } from "./dsm-arch.js";
|
|
14
14
|
import { createReproducibleTar } from "./reproducible-archive.js";
|
|
15
15
|
import { renderDeleteAppDataBlock } from "./delete-app-data.js";
|
|
16
|
-
import { BROKER_INSTALLER_VARS, BROKER_SOCKET_REQUIRED_SCRIPT, renderBrokerStatusProbe, } from "./broker-glue.js";
|
|
17
16
|
import { loadNative } from "../native/load.js";
|
|
18
17
|
import { materializeNativePayload } from "./native-payload.js";
|
|
19
|
-
import { renderNativePrivilege, renderNativeStartStopStatus, renderNativePostuninst, } from "./native-lifecycle.js";
|
|
18
|
+
import { renderNativePostinst, renderNativePrivilege, renderNativeStartStopStatus, renderNativePostuninst, renderNativePreuninst, } from "./native-lifecycle.js";
|
|
20
19
|
import { renderHeadlessResourceMarker, renderResourceConfig, renderResourceWorkerStartStopStatus, } from "./resource-worker.js";
|
|
21
20
|
const FILE_MODE = 0o644;
|
|
22
21
|
const SCRIPT_MODE = 0o755;
|
|
23
|
-
function hasInterpolation(value) {
|
|
24
|
-
if (typeof value === "string") {
|
|
25
|
-
return /\$\{([^}]+)\}/.test(value);
|
|
26
|
-
}
|
|
27
|
-
if (Array.isArray(value)) {
|
|
28
|
-
return value.some(hasInterpolation);
|
|
29
|
-
}
|
|
30
|
-
if (value && typeof value === "object") {
|
|
31
|
-
return Object.values(value).some(hasInterpolation);
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
22
|
const INSTALL_UIFILE = "install_uifile";
|
|
36
23
|
const UPGRADE_UIFILE = "upgrade_uifile";
|
|
37
24
|
const UNINSTALL_UIFILE = "uninstall_uifile";
|
|
38
25
|
const HEADLESS_PROBE_VOLUME = "./.synopkgland:/run/synopkgland:ro";
|
|
39
26
|
const HEADLESS_PROBE_README = "Reserved bind target for package-user headless status probing.\n";
|
|
40
|
-
export const EMBEDDED_ATTESTATION_RELATIVE_PATH = ".synopkgland/attestation.jwt";
|
|
41
27
|
const SCRIPT_NAMES = [
|
|
42
28
|
"preinst",
|
|
43
29
|
"postinst",
|
|
@@ -48,9 +34,6 @@ const SCRIPT_NAMES = [
|
|
|
48
34
|
"start-stop-status",
|
|
49
35
|
];
|
|
50
36
|
export async function buildSpk(options) {
|
|
51
|
-
if (options.embeddedAttestationToken !== undefined && options.embeddedAttestationToken.length === 0) {
|
|
52
|
-
return { ok: false, errors: ["EMBEDDED_ATTESTATION_TOKEN_EMPTY"] };
|
|
53
|
-
}
|
|
54
37
|
const validation = validateProject(options.project);
|
|
55
38
|
if (!validation.ok) {
|
|
56
39
|
return {
|
|
@@ -123,12 +106,6 @@ export async function buildSpk(options) {
|
|
|
123
106
|
}
|
|
124
107
|
copyFileSync(options.project.contractPath, join(packageDir, "synology-app.yaml"));
|
|
125
108
|
copyFileSync(nativePath, join(packageDir, "native.yaml"));
|
|
126
|
-
if (options.embeddedAttestationToken !== undefined) {
|
|
127
|
-
const attestationPath = join(packageDir, EMBEDDED_ATTESTATION_RELATIVE_PATH);
|
|
128
|
-
mkdirSync(join(packageDir, ".synopkgland"), { recursive: true });
|
|
129
|
-
writeFileSync(attestationPath, options.embeddedAttestationToken, "utf8");
|
|
130
|
-
chmodSync(attestationPath, FILE_MODE);
|
|
131
|
-
}
|
|
132
109
|
chmodSync(join(staging, "INFO"), FILE_MODE);
|
|
133
110
|
chmodSync(join(staging, "privilege"), FILE_MODE);
|
|
134
111
|
chmodSync(join(staging, "WIZARD_UIFILES", INSTALL_UIFILE), FILE_MODE);
|
|
@@ -152,7 +129,10 @@ export async function buildSpk(options) {
|
|
|
152
129
|
if (route && route.errors.length > 0) {
|
|
153
130
|
return { ok: false, errors: route.errors.map((error) => error.code) };
|
|
154
131
|
}
|
|
155
|
-
|
|
132
|
+
if (!route || route.route === "reject") {
|
|
133
|
+
return { ok: false, errors: ["COMPOSE_ROUTE_UNSUPPORTED"] };
|
|
134
|
+
}
|
|
135
|
+
const rwEligibility = route.eligibility;
|
|
156
136
|
const isDynamicRoute = route?.route === "dynamic";
|
|
157
137
|
const isResourceWorkerRoute = route?.route === "static-zero-config" ||
|
|
158
138
|
isDynamicRoute;
|
|
@@ -186,7 +166,7 @@ export async function buildSpk(options) {
|
|
|
186
166
|
mkdirSync(resourceProjectDir, { recursive: true });
|
|
187
167
|
const dsmUserParam = collectInstallParameters(contract).find((p) => p.param.type === "dsm_user");
|
|
188
168
|
let composeContent;
|
|
189
|
-
if (dsmUserParam || rwEligibility?.headless === true
|
|
169
|
+
if (dsmUserParam || rwEligibility?.headless === true) {
|
|
190
170
|
const rawYaml = readFileSync(options.project.composePath, "utf8");
|
|
191
171
|
const doc = parseDocument(rawYaml);
|
|
192
172
|
const services = doc.get("services");
|
|
@@ -198,28 +178,6 @@ export async function buildSpk(options) {
|
|
|
198
178
|
doc.setIn(["services", sName, "user"], userVal);
|
|
199
179
|
}
|
|
200
180
|
}
|
|
201
|
-
if (hasOidcCompat(contract) && services && typeof services === "object" && typeof services.toJSON === "function") {
|
|
202
|
-
const rawServices = services.toJSON();
|
|
203
|
-
for (const [sName, sValue] of Object.entries(rawServices)) {
|
|
204
|
-
if (sValue && typeof sValue === "object" && hasInterpolation(sValue)) {
|
|
205
|
-
const str = JSON.stringify(sValue);
|
|
206
|
-
if (str.includes("SYNOPKGLAND_OIDC_ISSUER")) {
|
|
207
|
-
const serviceDoc = doc.getIn(["services", sName]);
|
|
208
|
-
const existingExtraHosts = serviceDoc?.get?.("extra_hosts");
|
|
209
|
-
const hostGatewayMapping = "${SYNOPKGLAND_OIDC_ISSUER_HOST}:host-gateway";
|
|
210
|
-
if (!existingExtraHosts) {
|
|
211
|
-
doc.setIn(["services", sName, "extra_hosts"], [hostGatewayMapping]);
|
|
212
|
-
}
|
|
213
|
-
else if (Array.isArray(existingExtraHosts.toJSON?.())) {
|
|
214
|
-
const arr = existingExtraHosts.toJSON();
|
|
215
|
-
if (!arr.includes(hostGatewayMapping)) {
|
|
216
|
-
doc.setIn(["services", sName, "extra_hosts"], [...arr, hostGatewayMapping]);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
181
|
if (rwEligibility?.headless === true) {
|
|
224
182
|
injectHeadlessProbeVolume(doc);
|
|
225
183
|
}
|
|
@@ -241,11 +199,6 @@ export async function buildSpk(options) {
|
|
|
241
199
|
writeFileSync(join(probePayloadDir, "README"), HEADLESS_PROBE_README, "utf8");
|
|
242
200
|
chmodSync(join(probePayloadDir, "README"), FILE_MODE);
|
|
243
201
|
}
|
|
244
|
-
const routeMarkerDir = join(staging, "package", ".synopkgland");
|
|
245
|
-
mkdirSync(routeMarkerDir, { recursive: true });
|
|
246
|
-
const routeMarker = join(routeMarkerDir, "route");
|
|
247
|
-
writeFileSync(routeMarker, "package-only", "utf8");
|
|
248
|
-
chmodSync(routeMarker, FILE_MODE);
|
|
249
202
|
}
|
|
250
203
|
const htmlDir = join(options.project.root, "html");
|
|
251
204
|
if (existsSync(htmlDir)) {
|
|
@@ -254,12 +207,6 @@ export async function buildSpk(options) {
|
|
|
254
207
|
cpSync(htmlDir, join(staging, "package", "compose", "html"), { recursive: true });
|
|
255
208
|
}
|
|
256
209
|
}
|
|
257
|
-
if (options.embeddedAttestationToken !== undefined) {
|
|
258
|
-
const attestationPath = join(staging, "package", EMBEDDED_ATTESTATION_RELATIVE_PATH);
|
|
259
|
-
mkdirSync(join(staging, "package", ".synopkgland"), { recursive: true });
|
|
260
|
-
writeFileSync(attestationPath, options.embeddedAttestationToken, "utf8");
|
|
261
|
-
chmodSync(attestationPath, FILE_MODE);
|
|
262
|
-
}
|
|
263
210
|
if (contract.adminport !== undefined && resolvedWebLaunch(contract) === "dsm-window") {
|
|
264
211
|
const uiDir = join(staging, "package", "ui");
|
|
265
212
|
mkdirSync(uiDir, { recursive: true });
|
|
@@ -312,12 +259,6 @@ function renderInfo(contract, archFamily, isResourceWorker = false) {
|
|
|
312
259
|
add("install_dep_packages", "WebStation");
|
|
313
260
|
}
|
|
314
261
|
}
|
|
315
|
-
else {
|
|
316
|
-
// synopkg's Container Manager dep gate reports "not ready" even when CM is
|
|
317
|
-
// running. Depend on Broker only; Broker's adapter.probe() checks CM at
|
|
318
|
-
// App install time (CM_CAPABILITY_MISSING).
|
|
319
|
-
add("install_dep_packages", "SynoPkgLandService>=0.1.0-0016");
|
|
320
|
-
}
|
|
321
262
|
add("startable", "yes");
|
|
322
263
|
const launch = resolvedWebLaunch(contract);
|
|
323
264
|
if (contract.adminprotocol) {
|
|
@@ -354,47 +295,15 @@ function renderPrivilege() {
|
|
|
354
295
|
},
|
|
355
296
|
}, null, 2)}\n`;
|
|
356
297
|
}
|
|
357
|
-
const SHARED_FOLDER_MOUNT_CONSENT = "此 App 會用你在這台 NAS 選的資料夾;整顆應用都看得到。Shared Folder Mount 是整個 App Instance 的 container access:能打開這個 App 的使用者,容器裡的程式都看得到該 Share,即使該使用者在 File Station 看不到。這不是逐使用者 DSM ACL
|
|
358
|
-
const HOMES_MOUNT_CONSENT = "此 App 會使用這台 NAS 已啟用的 User Home;整顆應用的容器都看得到,容器路徑由 Compose 決定。這不是逐使用者 DSM ACL
|
|
359
|
-
const POSIX_ALL_SHARES_MAX_WARNING = "
|
|
298
|
+
const SHARED_FOLDER_MOUNT_CONSENT = "此 App 會用你在這台 NAS 選的資料夾;整顆應用都看得到。Shared Folder Mount 是整個 App Instance 的 container access:能打開這個 App 的使用者,容器裡的程式都看得到該 Share,即使該使用者在 File Station 看不到。這不是逐使用者 DSM ACL。檔案權限取決於容器服務帳號;App 必須自行實作使用者授權。";
|
|
299
|
+
const HOMES_MOUNT_CONSENT = "此 App 會使用這台 NAS 已啟用的 User Home;整顆應用的容器都看得到,容器路徑由 Compose 決定。這不是逐使用者 DSM ACL;檔案權限取決於容器服務帳號;App 必須自行實作使用者授權。";
|
|
300
|
+
const POSIX_ALL_SHARES_MAX_WARNING = "此 App 會使用安裝當下這台 NAS 的全部 Shared Folder(App-wide,不依個別 DSM ACL);容器內可讀(或讀寫,依 Compose 模式)所有這些資料夾的檔案。安裝後新建的共用資料夾不會自動加入。Package Center 無法提供安裝當下的 Shared Folder 清單快照;要以全部 Share 安裝,請用 CLI `synopkgland deploy`,由 CLI 以管理員權限列舉全部 Share 並寫入快照。由管理員決定是否安裝。";
|
|
301
|
+
const POSIX_ALL_SHARES_UPGRADE_NOTICE = "升級會保留既有快照(安裝當下由 CLI 列舉的全部 Shared Folder);變更掛載範圍只能由 CLI 明確提供(`synopkgland deploy`)。Package Center 升級不會改動現有快照。";
|
|
360
302
|
const DSM_USER_RUN_AS_CONSENT = "寫進 Share 的新檔在 File Station 顯示為你選的那位 DSM 使用者。點開此 App 的人共用這個磁碟身分,不是逐登入 ACL。";
|
|
361
303
|
const DSM_USER_UPGRADE_CONSENT = "升級時更換 Container Run-as 不會 chown Share 上的舊檔;只有升級後寫入的新檔才會使用新的磁碟身分。";
|
|
304
|
+
const UPGRADE_VALUE_REUSE_DESC = "留空=沿用目前設定";
|
|
362
305
|
const SHARED_FOLDER_RW_UPGRADE_CONSENT = "此版本把 Share 掛載改為讀寫;容器可讀取並寫入你選的 Share,不再是唯讀。";
|
|
363
306
|
const APP_OWN_SPACE_CONSENT = "此 App 只用 App 自己的空間。";
|
|
364
|
-
const OIDC_COMPAT_CONSENT = "此 App 可用你的 DSM 帳號登入;如果這台 NAS 尚未啟用相容模式,App 會使用自己的登入。登入不等於 File Station ACL。";
|
|
365
|
-
function hasOidcCompat(contract) {
|
|
366
|
-
const integration = contract.integration;
|
|
367
|
-
if (!integration || typeof integration !== "object") {
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
const identity = integration.identity;
|
|
371
|
-
if (!identity || typeof identity !== "object") {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
const oidcCompat = identity.oidc_compat;
|
|
375
|
-
return Boolean(oidcCompat && typeof oidcCompat === "object");
|
|
376
|
-
}
|
|
377
|
-
function renderFilesCapability(contract) {
|
|
378
|
-
const permissions = contract.permissions;
|
|
379
|
-
const files = permissions?.files;
|
|
380
|
-
if (!files) {
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
|
-
const parts = [];
|
|
384
|
-
if (files.read) {
|
|
385
|
-
parts.push("讀取檔案");
|
|
386
|
-
}
|
|
387
|
-
if (files.write) {
|
|
388
|
-
parts.push("寫入檔案");
|
|
389
|
-
}
|
|
390
|
-
if (files.delete) {
|
|
391
|
-
parts.push("刪除檔案");
|
|
392
|
-
}
|
|
393
|
-
if (parts.length === 0) {
|
|
394
|
-
return null;
|
|
395
|
-
}
|
|
396
|
-
return `此 App 需要以下檔案能力:${parts.join("、")}。這是每位登入者的 DSM ACL,經 File Gateway。`;
|
|
397
|
-
}
|
|
398
307
|
function volumeSources(compose) {
|
|
399
308
|
return volumeMounts(compose).map((mount) => mount.source);
|
|
400
309
|
}
|
|
@@ -484,17 +393,11 @@ function renderInstallWizard(contract, compose) {
|
|
|
484
393
|
const hasSharedFolder = params.some((item) => item.param.type === "shared_folder");
|
|
485
394
|
const hasHomes = params.some((item) => item.param.type === "homes");
|
|
486
395
|
const hasDsmUser = params.some((item) => item.param.type === "dsm_user");
|
|
487
|
-
const capabilityText = renderFilesCapability(contract);
|
|
488
396
|
if (compose && scanComposePrivilege(compose).length > 0) {
|
|
489
397
|
items.push({
|
|
490
398
|
desc: PRIVILEGE_MAX_WARNING,
|
|
491
399
|
});
|
|
492
400
|
}
|
|
493
|
-
if (hasOidcCompat(contract)) {
|
|
494
|
-
items.push({
|
|
495
|
-
desc: OIDC_COMPAT_CONSENT,
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
401
|
if (hasSharedFolder) {
|
|
499
402
|
items.push({
|
|
500
403
|
desc: SHARED_FOLDER_MOUNT_CONSENT,
|
|
@@ -510,15 +413,7 @@ function renderInstallWizard(contract, compose) {
|
|
|
510
413
|
desc: DSM_USER_RUN_AS_CONSENT,
|
|
511
414
|
});
|
|
512
415
|
}
|
|
513
|
-
if (
|
|
514
|
-
items.push({
|
|
515
|
-
desc: capabilityText,
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
// No compose at all is the native branch: the binary gets PKGVAR and nothing
|
|
519
|
-
// else, so it earns the same line. Requiring a compose left native wizards
|
|
520
|
-
// empty, which DSM rejects as an invalid package.
|
|
521
|
-
if (!hasSharedFolder && !capabilityText && (!compose || composeUsesOnlyAppSpace(compose))) {
|
|
416
|
+
if (!hasSharedFolder && (!compose || composeUsesOnlyAppSpace(compose))) {
|
|
522
417
|
items.push({
|
|
523
418
|
desc: APP_OWN_SPACE_CONSENT,
|
|
524
419
|
});
|
|
@@ -584,36 +479,19 @@ function renderInstallWizard(contract, compose) {
|
|
|
584
479
|
continue;
|
|
585
480
|
}
|
|
586
481
|
if (param.type === "posix_all_shares") {
|
|
482
|
+
// A47 (install side): DSM's declarative wizard only ever renders a
|
|
483
|
+
// single-select combo here, so the former multiSelect control silently
|
|
484
|
+
// mounted exactly one Share instead of every Share. Package Center can
|
|
485
|
+
// therefore never carry the full Share list, and there is no honest
|
|
486
|
+
// control to render -- so this branch produces only an informational
|
|
487
|
+
// textblock. The full snapshot arrives exclusively via CLI
|
|
488
|
+
// extra_values (`synopkgland deploy`), which the preinst materializer
|
|
489
|
+
// expands. No subitem key is emitted, so the install uifile contains no
|
|
490
|
+
// ALL_SHARES and no CHANGE_ALL_SHARES.
|
|
587
491
|
items.push({
|
|
588
492
|
type: "textblock",
|
|
589
493
|
desc: POSIX_ALL_SHARES_MAX_WARNING,
|
|
590
494
|
});
|
|
591
|
-
items.push({
|
|
592
|
-
type: "combobox",
|
|
593
|
-
multiSelect: true,
|
|
594
|
-
desc: `POSIX all shares: ${name}`,
|
|
595
|
-
subitems: [{
|
|
596
|
-
key: name,
|
|
597
|
-
desc: name,
|
|
598
|
-
displayField: "name",
|
|
599
|
-
valueField: "name",
|
|
600
|
-
multiSelect: true,
|
|
601
|
-
editable: false,
|
|
602
|
-
forceSelection: true,
|
|
603
|
-
autoSelect: false,
|
|
604
|
-
mode: "remote",
|
|
605
|
-
api_store: {
|
|
606
|
-
api: "SYNO.Core.Share",
|
|
607
|
-
method: "list",
|
|
608
|
-
version: 1,
|
|
609
|
-
baseParams: { limit: -1, offset: 0 },
|
|
610
|
-
root: "shares",
|
|
611
|
-
idProperty: "name",
|
|
612
|
-
fields: ["name"],
|
|
613
|
-
},
|
|
614
|
-
validator: { allowBlank: false },
|
|
615
|
-
}],
|
|
616
|
-
});
|
|
617
495
|
continue;
|
|
618
496
|
}
|
|
619
497
|
if (param.type === "dsm_user") {
|
|
@@ -651,6 +529,75 @@ function renderInstallWizard(contract, compose) {
|
|
|
651
529
|
function renderUpgradeWizard(contract, compose) {
|
|
652
530
|
const installWizard = renderInstallWizard(contract, compose);
|
|
653
531
|
const params = collectInstallParameters(contract);
|
|
532
|
+
// Every type whose value must survive an upgrade the administrator did not
|
|
533
|
+
// deliberately change. Two mechanisms are needed because DSM renders them
|
|
534
|
+
// differently: a control that can be left blank just allows blank, while one
|
|
535
|
+
// that always carries a value needs an explicit "change this" gate.
|
|
536
|
+
// - dsm_user : measurably comes up blank, so allowBlank is enough
|
|
537
|
+
// - string : blank once the new version's default is withheld (A44)
|
|
538
|
+
// - shared_folder : DSM prefills the first store row
|
|
539
|
+
// - boolean : a checkbox has no blank state at all
|
|
540
|
+
// posix_all_shares is intentionally absent: it renders as a textblock with
|
|
541
|
+
// no subitem, so there is nothing to blank or gate on the upgrade wizard.
|
|
542
|
+
// Its value can only arrive via CLI extra_values, and the preinst
|
|
543
|
+
// change-gate shell (`upgrade_change_enabled`, `CHANGE_<name>`) still
|
|
544
|
+
// governs that path, reusing the canonical snapshot on an untouched upgrade.
|
|
545
|
+
const upgradeReuseTypes = new Map(params
|
|
546
|
+
.filter(({ param }) => param.type === "shared_folder" ||
|
|
547
|
+
param.type === "dsm_user" ||
|
|
548
|
+
param.type === "string" ||
|
|
549
|
+
param.type === "boolean")
|
|
550
|
+
.map(({ name, param }) => [name, param.type]));
|
|
551
|
+
const needsChangeGate = (type) => type === "shared_folder" || type === "boolean";
|
|
552
|
+
const upgradeItems = installWizard[0].items.flatMap((item) => {
|
|
553
|
+
if (!Array.isArray(item.subitems)) {
|
|
554
|
+
return [item];
|
|
555
|
+
}
|
|
556
|
+
const reuseKeys = [];
|
|
557
|
+
const subitems = item.subitems.map((subitem) => {
|
|
558
|
+
if (!subitem || typeof subitem !== "object") {
|
|
559
|
+
return subitem;
|
|
560
|
+
}
|
|
561
|
+
const record = subitem;
|
|
562
|
+
if (typeof record.key !== "string" || !upgradeReuseTypes.has(record.key)) {
|
|
563
|
+
return subitem;
|
|
564
|
+
}
|
|
565
|
+
reuseKeys.push(record.key);
|
|
566
|
+
const reused = {
|
|
567
|
+
...record,
|
|
568
|
+
validator: { allowBlank: true },
|
|
569
|
+
};
|
|
570
|
+
// Withholding the default is what makes an untouched upgrade blank, and
|
|
571
|
+
// blank is what lets preinst fall through to the canonical .env value.
|
|
572
|
+
// Install keeps the default -- only this upgrade copy drops it.
|
|
573
|
+
delete reused.defaultValue;
|
|
574
|
+
return reused;
|
|
575
|
+
});
|
|
576
|
+
if (reuseKeys.length === 0) {
|
|
577
|
+
return [item];
|
|
578
|
+
}
|
|
579
|
+
const control = {
|
|
580
|
+
...item,
|
|
581
|
+
desc: `${String(item.desc ?? "")}(${UPGRADE_VALUE_REUSE_DESC})`,
|
|
582
|
+
subitems,
|
|
583
|
+
};
|
|
584
|
+
const changeControls = reuseKeys
|
|
585
|
+
.filter((key) => needsChangeGate(upgradeReuseTypes.get(key)))
|
|
586
|
+
.map((key) => {
|
|
587
|
+
const type = upgradeReuseTypes.get(key);
|
|
588
|
+
const label = type === "shared_folder" ? "Shared Folder" : key;
|
|
589
|
+
return {
|
|
590
|
+
type: "multiselect",
|
|
591
|
+
desc: `變更 ${label}(不勾=沿用目前設定)`,
|
|
592
|
+
subitems: [{
|
|
593
|
+
key: `CHANGE_${key}`,
|
|
594
|
+
desc: `變更 ${label}`,
|
|
595
|
+
defaultValue: false,
|
|
596
|
+
}],
|
|
597
|
+
};
|
|
598
|
+
});
|
|
599
|
+
return [...changeControls, control];
|
|
600
|
+
});
|
|
654
601
|
const sharedFolderNames = params
|
|
655
602
|
.filter((item) => item.param.type === "shared_folder")
|
|
656
603
|
.map((item) => item.name);
|
|
@@ -661,6 +608,14 @@ function renderUpgradeWizard(contract, compose) {
|
|
|
661
608
|
volumeMounts(compose).some((mount) => !mount.readOnly &&
|
|
662
609
|
sharedFolderNames.some((name) => mount.source === `\${${name}}` || mount.source.startsWith(`\${${name}}/`)));
|
|
663
610
|
const upgradeConsent = [];
|
|
611
|
+
const posixAllSharesNames = params
|
|
612
|
+
.filter((item) => item.param.type === "posix_all_shares")
|
|
613
|
+
.map((item) => item.name);
|
|
614
|
+
if (posixAllSharesNames.length > 0) {
|
|
615
|
+
upgradeConsent.push({
|
|
616
|
+
desc: POSIX_ALL_SHARES_UPGRADE_NOTICE,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
664
619
|
if (hasReadWriteSharedFolder) {
|
|
665
620
|
upgradeConsent.push({
|
|
666
621
|
desc: SHARED_FOLDER_RW_UPGRADE_CONSENT,
|
|
@@ -675,11 +630,12 @@ function renderUpgradeWizard(contract, compose) {
|
|
|
675
630
|
return [{
|
|
676
631
|
...installWizard[0],
|
|
677
632
|
step_title: "升級設定",
|
|
633
|
+
items: upgradeItems,
|
|
678
634
|
}];
|
|
679
635
|
}
|
|
680
|
-
const firstControl =
|
|
681
|
-
const insertAt = firstControl === -1 ?
|
|
682
|
-
const items = [...
|
|
636
|
+
const firstControl = upgradeItems.findIndex((item) => item.subitems !== undefined);
|
|
637
|
+
const insertAt = firstControl === -1 ? upgradeItems.length : firstControl;
|
|
638
|
+
const items = [...upgradeItems];
|
|
683
639
|
items.splice(insertAt, 0, ...upgradeConsent);
|
|
684
640
|
return [{
|
|
685
641
|
...installWizard[0],
|
|
@@ -708,7 +664,6 @@ function renderUninstallWizard() {
|
|
|
708
664
|
],
|
|
709
665
|
}];
|
|
710
666
|
}
|
|
711
|
-
const LEGACY_BROKER_STATE_REGISTRY_CODE = "COMPOSE_LEGACY_BROKER_STATE_PRESENT";
|
|
712
667
|
function renderDynamicPreinstMaterializer(contract) {
|
|
713
668
|
const allParams = collectInstallParameters(contract);
|
|
714
669
|
const scalarParams = allParams.filter(({ param }) => param.type === "string" || param.type === "boolean");
|
|
@@ -721,18 +676,6 @@ function renderDynamicPreinstMaterializer(contract) {
|
|
|
721
676
|
"umask 077",
|
|
722
677
|
"# D3/D5.4 package-only carrier; values are wizard answers, canonical values, or defaults.",
|
|
723
678
|
"die() { echo \"dynamic route materialization failed: $1\" >&2; exit 1; }",
|
|
724
|
-
`# registry-code="${LEGACY_BROKER_STATE_REGISTRY_CODE}"`,
|
|
725
|
-
'APPDATA="${SYNOPKG_PKGDEST_VOL:?}/@appdata/${SYNOPKG_PKGNAME:?}"',
|
|
726
|
-
'if [ -d "$APPDATA" ]; then',
|
|
727
|
-
' broker_uid="$(id -u SynoPkgLandService 2>/dev/null || true)"',
|
|
728
|
-
' if [ -n "$broker_uid" ]; then',
|
|
729
|
-
' for item in "$APPDATA"/* "$APPDATA"/.[!.]* "$APPDATA"/..?*; do',
|
|
730
|
-
' [ -e "$item" ] || continue',
|
|
731
|
-
' item_uid="$(stat -c %u "$item" 2>/dev/null || true)"',
|
|
732
|
-
' [ "$item_uid" = "$broker_uid" ] && die "MIGRATION_UNSUPPORTED: ${SYNOPKG_PKGNAME} 曾由 Broker installer 安裝;dynamic 路線不支援就地遷移,請以新的 Package Identity 安裝或先完整卸載並清除 App Data"',
|
|
733
|
-
" done",
|
|
734
|
-
" fi",
|
|
735
|
-
"fi",
|
|
736
679
|
'T="${SYNOPKG_PKGINST_TEMP_DIR:?}"',
|
|
737
680
|
'STATUS="${SYNOPKG_PKG_STATUS:?}"',
|
|
738
681
|
'PROJECT="$T/compose"',
|
|
@@ -776,6 +719,16 @@ function renderDynamicPreinstMaterializer(contract) {
|
|
|
776
719
|
" esac",
|
|
777
720
|
"}",
|
|
778
721
|
];
|
|
722
|
+
// Emitted whenever anything reads a CHANGE_ gate. Boolean parameters (A44) use
|
|
723
|
+
// it too, and a package with only booleans has no Share resolvers -- leaving it
|
|
724
|
+
// behind that condition made `! upgrade_change_enabled ...` a missing command,
|
|
725
|
+
// which is true, so the gate silently always reused the canonical value.
|
|
726
|
+
const usesUpgradeChangeGate = sharedFolderParams.length > 0 ||
|
|
727
|
+
posixAllSharesParams.length > 0 ||
|
|
728
|
+
collectInstallParameters(contract).some(({ param }) => param.type === "boolean");
|
|
729
|
+
if (usesUpgradeChangeGate) {
|
|
730
|
+
lines.push("upgrade_change_enabled() {", " normalized=\"$(printf '%s' \"$1\" | LC_ALL=C tr '[:upper:]' '[:lower:]')\"", " case \"$normalized\" in", " true|1|yes|on) return 0 ;;", " *) return 1 ;;", " esac", "}");
|
|
731
|
+
}
|
|
779
732
|
if (sharedFolderParams.length > 0 || posixAllSharesParams.length > 0) {
|
|
780
733
|
lines.push("resolve_shared_folder() {", " share_name=\"$1\"", " case \"$share_name\" in", " */*|*..*) die \"invalid share name $share_name\" ;;", " '') die \"share name is empty\" ;;", " esac", " mounts_file=\"${D54_MOUNTS_FILE:-/proc/mounts}\"", " if [ ! -r \"$mounts_file\" ] && [ -r \"/etc/mtab\" ]; then", " mounts_file=\"/etc/mtab\"", " fi", " if [ ! -r \"$mounts_file\" ]; then", " die \"cannot read mount points from $mounts_file\"", " fi", " vols=\"$(awk '$2 ~ /^\\/volume[0-9]+$/ {print $2}' \"$mounts_file\" | sort -u)\"", ' if [ -z "$vols" ]; then', " vols=\"$(awk '{print $2}' \"$mounts_file\" | sort -u)\"", " fi", " matched=\"\"", " count=0", " for v in $vols; do", " if [ -d \"$v/$share_name\" ]; then", " matched=\"$v/$share_name\"", " count=$((count + 1))", " fi", " done", " if [ \"$count\" -eq 0 ]; then", " die \"share $share_name not found on any volume\"", " elif [ \"$count\" -gt 1 ]; then", " die \"share $share_name matched multiple volumes\"", " fi", " printf '%s' \"$matched\"", "}");
|
|
781
734
|
}
|
|
@@ -785,14 +738,6 @@ function renderDynamicPreinstMaterializer(contract) {
|
|
|
785
738
|
if (dsmUserParam) {
|
|
786
739
|
lines.push("resolve_dsm_user() {", " user_name=\"$1\"", " case \"$user_name\" in", " */*|*..*|*[[:cntrl:]]*) die \"invalid user name $user_name\" ;;", " '') die \"user name is empty\" ;;", " esac", " u_uid=\"$(id -u -- \"$user_name\" 2>/dev/null || true)\"", " case \"$u_uid\" in", " ''|*[!0-9]*) die \"resolved uid for $user_name is not a valid integer\" ;;", " 0) die \"resolved uid for $user_name cannot be 0 (root)\" ;;", " esac", " u_gid=\"$(id -g -- \"$user_name\" 2>/dev/null || true)\"", " case \"$u_gid\" in", " ''|*[!0-9]*) die \"resolved gid for $user_name is not a valid integer\" ;;", " esac", " # id -G returns all group ids including primary", " raw_groups=\"$(id -G -- \"$user_name\" 2>/dev/null || true)\"", " # filter out primary gid, deduplicate, keep numeric only", " supp_groups=\"\"", " for g in $raw_groups; do", " case \"$g\" in", " ''|*[!0-9]*) continue ;;", " esac", " if [ \"$g\" != \"$u_gid\" ]; then", " already=0", " for sg in $supp_groups; do", " if [ \"$sg\" = \"$g\" ]; then", " already=1", " break", " fi", " done", " if [ \"$already\" -eq 0 ]; then", " supp_groups=\"${supp_groups}${supp_groups:+ }$g\"", " fi", " fi", " done", " printf '%s %s %s' \"$u_uid\" \"$u_gid\" \"$supp_groups\"", "}", "rewrite_group_add() {", " p_name=\"$1\"", " g_list=\"$2\"", " compose_file=\"$3\"", " [ -f \"$compose_file\" ] || die \"compose file missing at $compose_file\"", " [ ! -L \"$compose_file\" ] || die \"compose file is a symlink\"", " tmp_compose=\"$(mktemp \"${compose_file}.tmp.XXXXXX\")\"", " awk -v param=\"$p_name\" -v g_list=\"$g_list\" '", " BEGIN {", " n = split(g_list, g_arr, \" \")", " }", " {", " regex = \"^[ \\t]*# synopkgland:group_add \" param \"$\"", " if ($0 ~ regex) {", " match($0, /^[ \\t]*/)", " indent = substr($0, RSTART, RLENGTH)", " if (n > 0) {", " print indent \"group_add:\"", " for (i = 1; i <= n; i++) {", " print indent \" - ${\" param \"_GROUP_\" i \"}\"", " }", " }", " } else {", " print $0", " }", " }", " ' \"$compose_file\" > \"$tmp_compose\" || die \"failed to rewrite group_add in $compose_file\"", " mv \"$tmp_compose\" \"$compose_file\" || die \"failed to replace $compose_file\"", "}");
|
|
787
740
|
}
|
|
788
|
-
if (hasOidcCompat(contract)) {
|
|
789
|
-
const identity = contract.integration?.identity;
|
|
790
|
-
const redirectPath = identity?.oidc_compat?.redirect_path ?? "";
|
|
791
|
-
const adminProtocol = contract.adminprotocol ?? "http";
|
|
792
|
-
const adminPort = Number(contract.adminport ?? 0);
|
|
793
|
-
const escapedRedirectPath = redirectPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
794
|
-
lines.push("oidc_register() {", " # DSM compose does not create missing bind sources: the secrets dir must exist even when compat is off.", " (umask 077 && mkdir -p \"$PROJECT/secrets\" && chmod 700 \"$PROJECT/secrets\") || die \"failed to create $PROJECT/secrets\"", " OIDC_ISSUER=\"\"", " OIDC_ISSUER_HOST=\"oidc.invalid\"", " OIDC_CLIENT_ID=\"\"", " OIDC_CLIENT_SECRET=\"\"", " identity_file=\"${SYNOPKGLAND_IDENTITY_FILE:-/var/packages/SynoPkgLandService/var/identity.json}\"", " if [ ! -r \"$identity_file\" ]; then", " identity_file=\"/volume1/@appdata/SynoPkgLandService/identity.json\"", " fi", " if [ ! -r \"$identity_file\" ]; then", " # Identity endpoint not found -> Broker not installed -> skip as 404", " rm -f \"$PROJECT/secrets/SYNOPKGLAND_OIDC_CLIENT_SECRET\"", " return 0", " fi", " base_url=\"$(python3 -c 'import json, sys; data=json.load(open(sys.argv[1])); print(data.get(\"baseUrl\", \"\"))' \"$identity_file\" 2>/dev/null || true)\"", " if [ -z \"$base_url\" ]; then", " rm -f \"$PROJECT/secrets/SYNOPKGLAND_OIDC_CLIENT_SECRET\"", " return 0", " fi", " attestation_file=\"$T/.synopkgland/attestation.jwt\"", " if [ ! -f \"$attestation_file\" ]; then", " rm -f \"$PROJECT/secrets/SYNOPKGLAND_OIDC_CLIENT_SECRET\"", " return 0", " fi", " attestation_token=\"$(cat \"$attestation_file\")\"", " phase=\"install\"", " case \"$STATUS\" in", " UPGRADE) phase=\"upgrade\" ;;", " *) phase=\"install\" ;;", " esac", " req_body=\"$(python3 -c 'import json, sys; print(json.dumps({\"protocolVersion\": 1, \"attestation\": sys.argv[1], \"packageIdentity\": sys.argv[2], \"packageVersion\": sys.argv[3], \"redirectPath\": sys.argv[4], \"adminProtocol\": sys.argv[5], \"adminPort\": int(sys.argv[6]), \"phase\": sys.argv[7]}))' \"$attestation_token\" \"${SYNOPKG_PKGNAME:?}\" \"${SYNOPKG_PKGVER:?}\" \"" + escapedRedirectPath + "\" \"" + String(adminProtocol) + "\" \"" + String(adminPort) + "\" \"$phase\")\"", " resp_file=\"$(mktemp \"$T/oidc_resp.XXXXXX\")\"", " http_code=\"$(curl -s -S --connect-timeout 5 --max-time 30 -o \"$resp_file\" -w '%{http_code}' -X POST -H 'Content-Type: application/json' --data-binary \"$req_body\" \"${base_url}/internal/v1/oidc/register\" 2>/dev/null || true)\"", " case \"$http_code\" in [0-9][0-9][0-9]) ;; *) http_code=\"000\" ;; esac", " if [ \"$http_code\" = \"200\" ]; then", " parsed_res=\"$(python3 -c 'import json, sys; d=json.load(open(sys.argv[1])); print(d.get(\"issuer\", \"\")); print(d.get(\"issuerHost\", \"\")); print(d.get(\"clientId\", \"\")); print(d.get(\"clientSecret\", \"\"))' \"$resp_file\" 2>/dev/null || true)\"", " rm -f \"$resp_file\"", " OIDC_ISSUER=\"$(printf '%s\\n' \"$parsed_res\" | sed -n '1p')\"", " OIDC_ISSUER_HOST=\"$(printf '%s\\n' \"$parsed_res\" | sed -n '2p')\"", " OIDC_CLIENT_ID=\"$(printf '%s\\n' \"$parsed_res\" | sed -n '3p')\"", " OIDC_CLIENT_SECRET=\"$(printf '%s\\n' \"$parsed_res\" | sed -n '4p')\"", " (umask 077 && mkdir -p \"$PROJECT/secrets\" && printf '%s' \"$OIDC_CLIENT_SECRET\" > \"$PROJECT/secrets/SYNOPKGLAND_OIDC_CLIENT_SECRET\")", " elif [ \"$http_code\" = \"404\" ] || [ \"$http_code\" = \"000\" ]; then", " rm -f \"$resp_file\"", " rm -f \"$PROJECT/secrets/SYNOPKGLAND_OIDC_CLIENT_SECRET\"", " OIDC_ISSUER=\"\"", " OIDC_ISSUER_HOST=\"oidc.invalid\"", " OIDC_CLIENT_ID=\"\"", " else", " rm -f \"$resp_file\"", " die \"OIDC_REGISTER_FAILED $http_code\"", " fi", "}", "oidc_register");
|
|
795
|
-
}
|
|
796
741
|
lines.push('tmp="$(mktemp "$PROJECT/.env.tmp.XXXXXX")"', 'chmod 600 "$tmp"', '[ "$(stat -c %a "$tmp")" = "600" ] || die "temporary env mode is not 0600"', '[ "$(stat -c %u "$tmp")" = "$(id -u)" ] || die "temporary env owner is not the package user"', "trap 'rm -f \"$tmp\"' EXIT HUP INT TERM", "{");
|
|
797
742
|
const valueVars = [];
|
|
798
743
|
for (const { name, param } of scalarParams) {
|
|
@@ -804,16 +749,27 @@ function renderDynamicPreinstMaterializer(contract) {
|
|
|
804
749
|
: type === "boolean"
|
|
805
750
|
? "false"
|
|
806
751
|
: "";
|
|
807
|
-
|
|
752
|
+
// A44: on UPGRADE the wizard always submits something for these controls, so
|
|
753
|
+
// "was it submitted" cannot mean "did the administrator choose it". A blank
|
|
754
|
+
// string, or a boolean whose CHANGE_ gate is unchecked, means "keep what is
|
|
755
|
+
// installed" -- otherwise the new version's default would silently replace a
|
|
756
|
+
// value chosen at install time. INSTALL is unaffected: STATUS is not UPGRADE,
|
|
757
|
+
// so the reuse branch is skipped and the default still applies.
|
|
758
|
+
const reuseGuard = type === "boolean"
|
|
759
|
+
? `! upgrade_change_enabled "\${CHANGE_${name}:-}"`
|
|
760
|
+
: `[ -z "\${${name}:-}" ]`;
|
|
761
|
+
lines.push(` if [ "$STATUS" = "UPGRADE" ] && ${reuseGuard} && [ -f "$CANONICAL_ENV" ] && ` +
|
|
762
|
+
`[ ! -L "$CANONICAL_ENV" ] && ${valueVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", ` elif [ "\${${name}+x}" = x ]; then`, ` ${valueVar}="\${${name}}"`, ' elif [ "$STATUS" = "UPGRADE" ] && [ -f "$CANONICAL_ENV" ] && [ ! -L "$CANONICAL_ENV" ] && ' +
|
|
808
763
|
`${valueVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", " else", " " + valueVar + "=" + shellQuote(fallback), " fi");
|
|
809
764
|
}
|
|
810
765
|
const shareVars = [];
|
|
811
766
|
for (const { name } of sharedFolderParams) {
|
|
812
767
|
const rawVar = "D54_RAW_SHARE_" + name;
|
|
813
768
|
const pathVar = "D54_RESOLVED_PATH_" + name;
|
|
769
|
+
const changeVar = "CHANGE_" + name;
|
|
814
770
|
shareVars.push({ name, pathVar });
|
|
815
|
-
lines.push(` if [ "\${${name}+x}" = x ]; then`, `
|
|
816
|
-
`${rawVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", " else", ` die "missing required shared folder parameter ${name}"`, " fi", ` case "$${rawVar}" in`, " /*) " + pathVar + "=\"$" + rawVar + "\" ;;", " *) " + pathVar + "=\"$(resolve_shared_folder \"$" + rawVar + "\")\" ;;", " esac");
|
|
771
|
+
lines.push(` if [ "$STATUS" = "UPGRADE" ] && upgrade_change_enabled "\${${changeVar}:-}"; then`, ` if [ "\${${name}+x}" = x ] && [ -n "\${${name}}" ]; then`, ` ${rawVar}="\${${name}}"`, " else", ` die "missing required shared folder parameter ${name}"`, " fi", ' elif [ "$STATUS" = "UPGRADE" ] && [ -f "$CANONICAL_ENV" ] && [ ! -L "$CANONICAL_ENV" ] && ' +
|
|
772
|
+
`${rawVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", ' elif [ "$STATUS" = "UPGRADE" ]; then', ` die "missing canonical shared folder parameter ${name}"`, ` elif [ "\${${name}+x}" = x ]; then`, ` ${rawVar}="\${${name}}"`, " else", ` die "missing required shared folder parameter ${name}"`, " fi", ` case "$${rawVar}" in`, " /*) " + pathVar + "=\"$" + rawVar + "\" ;;", " *) " + pathVar + "=\"$(resolve_shared_folder \"$" + rawVar + "\")\" ;;", " esac");
|
|
817
773
|
}
|
|
818
774
|
const posixVars = [];
|
|
819
775
|
for (const { name } of posixAllSharesParams) {
|
|
@@ -821,22 +777,18 @@ function renderDynamicPreinstMaterializer(contract) {
|
|
|
821
777
|
const namesVar = "D54_POSIX_NAMES_" + name;
|
|
822
778
|
const pathsVar = "D54_POSIX_PATHS_" + name;
|
|
823
779
|
const countVar = "D54_POSIX_COUNT_" + name;
|
|
780
|
+
const changeVar = "CHANGE_" + name;
|
|
824
781
|
posixVars.push({ name, namesVar, pathsVar, countVar });
|
|
825
|
-
lines.push(` if [ "\${${name}+x}" = x ]; then`, `
|
|
826
|
-
`${rawVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", " else", ` die "missing required POSIX all-shares parameter ${name}"`, " fi", ` posix_info="$(resolve_posix_all_shares "$${rawVar}")"`, ` ${namesVar}="$(printf '%s\\n' "$posix_info" | sed -n '1p')"`, ` ${pathsVar}="$(printf '%s\\n' "$posix_info" | sed -n '2p')"`, ` ${countVar}="$(printf '%s\\n' "$posix_info" | sed -n '3p')"`, ` rewrite_posix_all_shares "${name}" "$${namesVar}" "$${pathsVar}" "$PROJECT/docker-compose.yml"`);
|
|
782
|
+
lines.push(` if [ "$STATUS" = "UPGRADE" ] && upgrade_change_enabled "\${${changeVar}:-}"; then`, ` if [ "\${${name}+x}" = x ] && [ -n "\${${name}}" ]; then`, ` ${rawVar}="\${${name}}"`, " else", ` die "missing required POSIX all-shares parameter ${name}"`, " fi", ' elif [ "$STATUS" = "UPGRADE" ] && [ -f "$CANONICAL_ENV" ] && [ ! -L "$CANONICAL_ENV" ] && ' +
|
|
783
|
+
`${rawVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", ' elif [ "$STATUS" = "UPGRADE" ]; then', ` die "missing canonical POSIX all-shares parameter ${name}"`, ` elif [ "\${${name}+x}" = x ]; then`, ` ${rawVar}="\${${name}}"`, " else", ` die "missing required POSIX all-shares parameter ${name}"`, " fi", ` posix_info="$(resolve_posix_all_shares "$${rawVar}")"`, ` ${namesVar}="$(printf '%s\\n' "$posix_info" | sed -n '1p')"`, ` ${pathsVar}="$(printf '%s\\n' "$posix_info" | sed -n '2p')"`, ` ${countVar}="$(printf '%s\\n' "$posix_info" | sed -n '3p')"`, ` rewrite_posix_all_shares "${name}" "$${namesVar}" "$${pathsVar}" "$PROJECT/docker-compose.yml"`);
|
|
827
784
|
}
|
|
828
785
|
if (dsmUserParam) {
|
|
829
786
|
const name = dsmUserParam.name;
|
|
830
787
|
const rawUserVar = "D54_RAW_USER_" + name;
|
|
831
|
-
lines.push(` if [ "\${${name}+x}" = x ]; then`, ` ${rawUserVar}="\${${name}}"`, ' elif [ "$STATUS" = "UPGRADE" ] && [ -f "$CANONICAL_ENV" ] && [ ! -L "$CANONICAL_ENV" ] && ' +
|
|
832
|
-
`${rawUserVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", " else", ` die "missing required dsm user parameter ${name}"`, " fi", ` resolved_user_info="$(resolve_dsm_user "$${rawUserVar}")"`, " set -- $resolved_user_info", ` ${name}_UID="$1"`, ` ${name}_GID="$2"`, " shift 2 || true", ` ${name}_GROUPS="$*"`, ` rewrite_group_add "${name}" "$${name}_GROUPS" "$PROJECT/docker-compose.yml"`);
|
|
833
|
-
}
|
|
834
|
-
if (hasOidcCompat(contract)) {
|
|
835
|
-
lines.push(' append_env "SYNOPKG_PKGVAR" "$PLATFORM_PKGVAR"', ' append_env "SYNO_APP_SECRETS_DIR" "${SYNOPKG_PKGDEST_VOL:?}/@appstore/${SYNOPKG_PKGNAME:?}/compose/secrets"', ' append_env "SYNOPKGLAND_OIDC_ISSUER" "$OIDC_ISSUER"', ' append_env "SYNOPKGLAND_OIDC_ISSUER_HOST" "$OIDC_ISSUER_HOST"', ' append_env "SYNOPKGLAND_OIDC_CLIENT_ID" "$OIDC_CLIENT_ID"');
|
|
836
|
-
}
|
|
837
|
-
else {
|
|
838
|
-
lines.push(' append_env "SYNOPKG_PKGVAR" "$PLATFORM_PKGVAR"', ' append_env "SYNO_APP_SECRETS_DIR" "$PLATFORM_SECRETS"');
|
|
788
|
+
lines.push(` if [ "\${${name}+x}" = x ] && [ -n "\${${name}}" ]; then`, ` ${rawUserVar}="\${${name}}"`, ' elif [ "$STATUS" = "UPGRADE" ] && [ -f "$CANONICAL_ENV" ] && [ ! -L "$CANONICAL_ENV" ] && ' +
|
|
789
|
+
`${rawUserVar}="$(read_env_value "$CANONICAL_ENV" "${name}")"; then`, " :", ` elif [ "\${${name}+x}" = x ]; then`, ` ${rawUserVar}="\${${name}}"`, " else", ` die "missing required dsm user parameter ${name}"`, " fi", ` resolved_user_info="$(resolve_dsm_user "$${rawUserVar}")"`, " set -- $resolved_user_info", ` ${name}_UID="$1"`, ` ${name}_GID="$2"`, " shift 2 || true", ` ${name}_GROUPS="$*"`, ` rewrite_group_add "${name}" "$${name}_GROUPS" "$PROJECT/docker-compose.yml"`);
|
|
839
790
|
}
|
|
791
|
+
lines.push(' append_env "SYNOPKG_PKGVAR" "$PLATFORM_PKGVAR"', ' append_env "SYNO_APP_SECRETS_DIR" "$PLATFORM_SECRETS"');
|
|
840
792
|
for (const { name, valueVar, type } of valueVars) {
|
|
841
793
|
lines.push(` append_env "${name}" "$${valueVar}" "${type}"`);
|
|
842
794
|
}
|
|
@@ -910,29 +862,11 @@ function renderManagedScript(name, contract, nativeContext, isResourceWorker = f
|
|
|
910
862
|
if (name === "start-stop-status") {
|
|
911
863
|
return renderNativeStartStopStatus(lifecycleInput);
|
|
912
864
|
}
|
|
913
|
-
if (name === "preinst" || name === "preupgrade") {
|
|
914
|
-
return BROKER_SOCKET_REQUIRED_SCRIPT;
|
|
915
|
-
}
|
|
916
865
|
if (name === "postinst" || name === "postupgrade") {
|
|
917
|
-
|
|
918
|
-
// The service process needs its scratch directory before it can start,
|
|
919
|
-
// and the installer call is the script's exit status, so widen the
|
|
920
|
-
// existing mkdir rather than appending after it.
|
|
921
|
-
return installerScript.replace('mkdir -p "${SYNOPKG_PKGVAR}" "${SYNOPKG_PKGHOME}/secrets"', 'mkdir -p "${SYNOPKG_PKGVAR}" "${SYNOPKG_PKGVAR}/tmp" "${SYNOPKG_PKGHOME}/secrets"');
|
|
866
|
+
return renderNativePostinst(lifecycleInput);
|
|
922
867
|
}
|
|
923
868
|
if (name === "preuninst") {
|
|
924
|
-
|
|
925
|
-
const installerScript = renderInstallerCallScript(contract, "uninstall").replace("set -eu\n", 'set -eu\nif [ "${SYNOPKG_PKG_STATUS:-}" = "UPGRADE" ]; then\n exit 0\nfi\n');
|
|
926
|
-
// Stop the process BEFORE deregistering: `set -eu` aborts the script if
|
|
927
|
-
// the installer fails, so a stop appended after it would never run. The
|
|
928
|
-
// stop itself is best effort -- DSM already calls `start-stop-status
|
|
929
|
-
// stop` directly (N0), and a non-zero preuninst blocks uninstall.
|
|
930
|
-
const preuninstStopBlock = `CONTROL="/var/packages/\${SYNOPKG_PKGNAME:-${contract.package}}/scripts/start-stop-status"
|
|
931
|
-
if [ -x "$CONTROL" ]; then
|
|
932
|
-
"$CONTROL" stop 2>/dev/null || true
|
|
933
|
-
fi
|
|
934
|
-
`;
|
|
935
|
-
return installerScript.replace('"$INSTALLER"', `${preuninstStopBlock}"$INSTALLER"`);
|
|
869
|
+
return renderNativePreuninst(lifecycleInput);
|
|
936
870
|
}
|
|
937
871
|
if (name === "postuninst") {
|
|
938
872
|
return renderNativePostuninst(lifecycleInput);
|
|
@@ -967,93 +901,7 @@ ${projectRoot}${renderDeleteAppDataBlock(deleteRoots)}exit 0
|
|
|
967
901
|
}
|
|
968
902
|
return `#!/bin/sh\nexit 0\n`;
|
|
969
903
|
}
|
|
970
|
-
|
|
971
|
-
return `#!/bin/sh
|
|
972
|
-
${BROKER_INSTALLER_VARS}
|
|
973
|
-
case "$1" in
|
|
974
|
-
start) exit 0 ;;
|
|
975
|
-
stop) exit 0 ;;
|
|
976
|
-
status)
|
|
977
|
-
${renderBrokerStatusProbe(String(contract.package))}
|
|
978
|
-
exit 0
|
|
979
|
-
;;
|
|
980
|
-
*) exit 1 ;;
|
|
981
|
-
esac
|
|
982
|
-
`;
|
|
983
|
-
}
|
|
984
|
-
if (name === "preinst" || name === "preupgrade") {
|
|
985
|
-
return BROKER_SOCKET_REQUIRED_SCRIPT;
|
|
986
|
-
}
|
|
987
|
-
if (name === "postinst" || name === "postupgrade") {
|
|
988
|
-
return renderInstallerCallScript(contract, name === "postupgrade" ? "update" : "install");
|
|
989
|
-
}
|
|
990
|
-
if (name === "preuninst") {
|
|
991
|
-
return renderInstallerCallScript(contract, "uninstall").replace("set -eu\n", 'set -eu\nif [ "${SYNOPKG_PKG_STATUS:-}" = "UPGRADE" ]; then\n exit 0\nfi\n');
|
|
992
|
-
}
|
|
993
|
-
if (name === "postuninst") {
|
|
994
|
-
return `#!/bin/sh
|
|
995
|
-
set -eu
|
|
996
|
-
PKGVAR="\${SYNOPKG_PKGVAR}"
|
|
997
|
-
PKGHOME="\${SYNOPKG_PKGHOME}"
|
|
998
|
-
SECRETS="\${PKGHOME}/secrets"
|
|
999
|
-
${renderDeleteAppDataBlock(["PKGVAR", "SECRETS"])}exit 0
|
|
1000
|
-
`;
|
|
1001
|
-
}
|
|
1002
|
-
return `#!/bin/sh
|
|
1003
|
-
exit 0
|
|
1004
|
-
`;
|
|
1005
|
-
}
|
|
1006
|
-
function renderInstallerCallScript(contract, phase) {
|
|
1007
|
-
const params = collectInstallParameters(contract);
|
|
1008
|
-
const secrets = params.filter((item) => item.param.type === "secret");
|
|
1009
|
-
const nonSecrets = params.filter((item) => item.param.type !== "secret");
|
|
1010
|
-
const lines = [
|
|
1011
|
-
"#!/bin/sh",
|
|
1012
|
-
"set -eu",
|
|
1013
|
-
'INSTALLER="${BROKER_INSTALLER:-/var/packages/SynoPkgLandService/target/bin/synopkgland-installer}"',
|
|
1014
|
-
'SOCKET="${BROKER_SOCKET:-/var/packages/SynoPkgLandService/var/installer.sock}"',
|
|
1015
|
-
`PACKAGE_ROOT="/var/packages/${contract.package}"`,
|
|
1016
|
-
'ATTEMPT_ID="${SYNOPKG_PKGNAME}-${SYNOPKG_PKGVER}-$$"',
|
|
1017
|
-
'WORK="$(mktemp -d /tmp/synopkgland-install.XXXXXX)"',
|
|
1018
|
-
'chmod 700 "$WORK"',
|
|
1019
|
-
"cleanup() { rm -rf \"$WORK\"; }",
|
|
1020
|
-
"trap cleanup EXIT",
|
|
1021
|
-
'mkdir -p "${SYNOPKG_PKGVAR}" "${SYNOPKG_PKGHOME}/secrets"',
|
|
1022
|
-
'chmod 770 "${SYNOPKG_PKGVAR}" "${SYNOPKG_PKGHOME}" "${SYNOPKG_PKGHOME}/secrets" 2>/dev/null || true',
|
|
1023
|
-
'chgrp synopkgs "${SYNOPKG_PKGVAR}" "${SYNOPKG_PKGHOME}" "${SYNOPKG_PKGHOME}/secrets" 2>/dev/null || true',
|
|
1024
|
-
'VARS=""',
|
|
1025
|
-
'SECRET_FILES=""',
|
|
1026
|
-
];
|
|
1027
|
-
for (const { name, param } of secrets) {
|
|
1028
|
-
if (param.generate === true) {
|
|
1029
|
-
continue;
|
|
1030
|
-
}
|
|
1031
|
-
lines.push(`if [ -n "\${${name}+x}" ]; then`);
|
|
1032
|
-
lines.push(" umask 077");
|
|
1033
|
-
lines.push(` printf '%s' "\$${name}" > "$WORK/${name}"`);
|
|
1034
|
-
lines.push(` unset ${name}`);
|
|
1035
|
-
lines.push(` SECRET_FILES="$SECRET_FILES --secret-file ${name}=$WORK/${name}"`);
|
|
1036
|
-
lines.push("fi");
|
|
1037
|
-
}
|
|
1038
|
-
for (const { name } of nonSecrets) {
|
|
1039
|
-
lines.push(`if [ -n "\${${name}+x}" ]; then`);
|
|
1040
|
-
lines.push(` VARS="$VARS --var ${name}=\$${name}"`);
|
|
1041
|
-
lines.push("fi");
|
|
1042
|
-
}
|
|
1043
|
-
const generateFlags = secrets
|
|
1044
|
-
.filter((item) => item.param.generate === true)
|
|
1045
|
-
.map((item) => `--generate-secret ${item.name}`)
|
|
1046
|
-
.join(" ");
|
|
1047
|
-
lines.push(`"$INSTALLER" --socket "$SOCKET" --phase ${phase} --attempt-id "$ATTEMPT_ID" \\`);
|
|
1048
|
-
lines.push(` --package-identity "${contract.package}" --package-version "\${SYNOPKG_PKGVER}" \\`);
|
|
1049
|
-
lines.push(' --package-path "$PACKAGE_ROOT/target" --bundle-path "$PACKAGE_ROOT/target" \\');
|
|
1050
|
-
lines.push(' --pkg-var "$PACKAGE_ROOT/var" --pkg-home "$PACKAGE_ROOT/home" \\');
|
|
1051
|
-
if (generateFlags) {
|
|
1052
|
-
lines.push(` ${generateFlags} \\`);
|
|
1053
|
-
}
|
|
1054
|
-
lines.push(' $VARS $SECRET_FILES');
|
|
1055
|
-
lines.push("exit $?");
|
|
1056
|
-
return `${lines.join("\n")}\n`;
|
|
904
|
+
throw new Error("Unsupported container route");
|
|
1057
905
|
}
|
|
1058
906
|
async function createDeterministicSpk(stagingDir, outputPath) {
|
|
1059
907
|
const payloadDir = join(stagingDir, "package");
|