refactor: move Device Core source to standalone repository

This commit is contained in:
Codex
2026-08-21 12:23:21 +03:00
parent 40fbfcf351
commit 827bf0a58a
212 changed files with 0 additions and 41120 deletions
@@ -1,213 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { cp, lstat, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const devicePlaneRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [
patchId = "device-control-core-release-v2-20260812-025",
predecessorPatchId,
predecessorSha256,
...extra
] = process.argv.slice(2);
if (
extra.length
|| !/^device-control-core-release-[A-Za-z0-9._-]{1,67}$/.test(patchId)
|| ((predecessorPatchId === undefined) !== (predecessorSha256 === undefined))
|| (
predecessorPatchId !== undefined
&& (
!/^device-control-core-release-[A-Za-z0-9._-]{1,67}$/.test(predecessorPatchId)
|| predecessorPatchId === patchId
|| !/^[0-9a-f]{64}$/.test(predecessorSha256)
)
)
) {
throw new Error(
"usage: build-device-control-core-release-artifact.mjs "
+ "[device-control-core-release-id] [predecessor-release-id predecessor-sha256]",
);
}
const isV2 = patchId.startsWith("device-control-core-release-v2-");
const expectedV2Predecessor = Object.freeze({
patchId: predecessorPatchId ?? "device-control-core-release-20260812-024",
artifactSha256: predecessorSha256 ?? "a289e909283109642e6bba3d9822a31f63423cfe0bbcd52705979681bd2bc793",
});
const descriptorPath = isV2
? "deployment/device-control-core-release-v2.json"
: "deployment/device-control-core-release-v1.json";
const entries = [
".dockerignore",
"package.json",
"package-lock.json",
"packages/device-protocol-contract",
"packages/device-edge-channel-contract",
"services/device-control-core",
descriptorPath,
];
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-control-core-release-"));
const payload = join(stage, "payload");
const target = join(artifactDir, `nodedc-device-plane-${patchId}.tgz`);
try {
await mkdir(payload, { recursive: true });
for (const entry of entries) {
if (entry === descriptorPath) {
const descriptor = JSON.parse(await readFile(resolve(devicePlaneRoot, entry), "utf8"));
if (descriptor.releaseId !== "__PATCH_ID__") {
throw new Error("device_control_core_release_template_id_mismatch");
}
descriptor.releaseId = patchId;
if (predecessorPatchId !== undefined) {
descriptor.predecessor = {
kind: "release",
patchId: predecessorPatchId,
artifactSha256: predecessorSha256,
};
}
const destination = join(payload, entry);
await mkdir(dirname(destination), { recursive: true });
await writeFile(destination, `${JSON.stringify(descriptor, null, 2)}\n`, "utf8");
continue;
}
await copySafe(resolve(devicePlaneRoot, entry), join(payload, entry), devicePlaneRoot);
}
await validateDockerCopySources(
join(payload, "services/device-control-core/Dockerfile"),
payload,
);
for (const modulePath of [
"services/device-control-core/src/device-gateway-core-runtime.mjs",
"packages/device-protocol-contract/src/index.mjs",
"packages/device-edge-channel-contract/src/index.mjs",
]) {
const imported = spawnSync(
process.execPath,
["--input-type=module", "--eval", `import(${JSON.stringify(pathToFileURL(join(payload, modulePath)).href)})`],
{ cwd: payload, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 },
);
if (imported.status !== 0) {
throw new Error(`device_control_core_release_staged_module_import_failed:${modulePath}:${imported.stderr || imported.stdout}`);
}
}
const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8"));
if (
descriptor.schemaVersion !== `nodedc.device-plane.device-control-core-release.${isV2 ? "v2" : "v1"}`
|| descriptor.releaseId !== patchId
|| descriptor.action !== "upgrade"
|| descriptor.service !== "device-control-core"
|| descriptor.composeActivation !== "preserve-active-v4-topology"
|| descriptor.identityRecovery !== "forbidden-valid-existing-identity-required"
|| descriptor.tlsPurpose !== "clientAuth"
|| descriptor.direction !== "core-initiated"
|| descriptor.endpointPolicy !== "public-ipv4-standard-https-tcp-443-only"
|| JSON.stringify(descriptor.coreNetworks) !== JSON.stringify(["device-plane-private", "device-plane-egress"])
|| descriptor.publicIngress !== "none-on-synology"
|| descriptor.edgeRegistrations !== "preserved"
|| descriptor.commandTransport !== (isV2 ? "typed-service-ping-v1" : "disabled")
|| descriptor.gelios !== (isV2 ? "untouched-legacy-only" : "untouched")
|| descriptor.rollback !== "restore-preapply-source-and-core-runtime"
|| (
isV2
&& (
descriptor.commandCatalog !== "allowlisted-adapter-typed-commands-only"
|| descriptor.credentialBoundary !== "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned"
|| descriptor.predecessor?.patchId !== expectedV2Predecessor.patchId
|| descriptor.predecessor?.artifactSha256 !== expectedV2Predecessor.artifactSha256
)
)
) {
throw new Error("device_control_core_release_contract_mismatch");
}
await writeFile(join(stage, "manifest.env"), `id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`, "utf8");
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync("python3", ["-c", canonicalTarScript(), target, stage], {
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
});
if (tar.status !== 0) throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
const sha256 = createHash("sha256").update(await readFile(target)).digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
component: "device-plane",
artifact: target,
sha256,
entries,
services: ["device-control-core"],
preserved: ["device-manager", "device-gateway", "device-postgres", "device-backhaul-target", "edge registrations", "mTLS identity", "Gelios"],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function copySafe(source, destination, sourceBoundary) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) throw new Error(`source_symlink_rejected:${relative(sourceBoundary, source)}`);
if (sourceStat.isFile()) {
if (source.endsWith(".test.mjs") || source.endsWith(".map")) return;
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, { force: true, verbatimSymlinks: true });
return;
}
if (!sourceStat.isDirectory()) throw new Error(`source_type_rejected:${source}`);
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if ([".DS_Store", ".git", "node_modules", "test"].includes(entry.name) || entry.name.startsWith(".env")) continue;
await copySafe(join(source, entry.name), join(destination, entry.name), sourceBoundary);
}
}
async function validateDockerCopySources(dockerfilePath, buildContext) {
const dockerfile = await readFile(dockerfilePath, "utf8");
for (const [index, rawLine] of dockerfile.split("\n").entries()) {
const line = rawLine.trim();
if (!/^COPY\s+/i.test(line)) continue;
if (line.endsWith("\\") || /^COPY\s+\[/i.test(line)) {
throw new Error(`unsupported_docker_copy_syntax:${dockerfilePath}:${index + 1}`);
}
const tokens = line.split(/\s+/).slice(1);
while (tokens[0]?.startsWith("--")) tokens.shift();
if (tokens.length < 2) throw new Error(`invalid_docker_copy:${dockerfilePath}:${index + 1}`);
for (const source of tokens.slice(0, -1)) {
if (/[*?[\]{}]/.test(source)) throw new Error(`docker_copy_glob_rejected:${dockerfilePath}:${index + 1}:${source}`);
const resolvedSource = resolve(buildContext, source);
const relativeSource = relative(buildContext, resolvedSource);
if (!relativeSource || relativeSource.startsWith("..") || resolve(buildContext, relativeSource) !== resolvedSource) {
throw new Error(`docker_copy_source_outside_context:${dockerfilePath}:${index + 1}:${source}`);
}
try {
await lstat(resolvedSource);
} catch (error) {
if (error?.code === "ENOENT") throw new Error(`docker_copy_source_missing:${dockerfilePath}:${index + 1}:${source}`);
throw error;
}
}
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix()); info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,278 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { cp, lstat, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const devicePlaneRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [patchId = "device-edge-core-channel-bootstrap-20260811-017", ...extra] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error("usage: build-device-edge-core-channel-bootstrap-artifact.mjs [patch-id]");
}
const upgradeV4 = patchId.startsWith("device-edge-core-channel-upgrade-v4-");
const upgradeV2 = !upgradeV4 && patchId.startsWith("device-edge-core-channel-upgrade-v2-");
const upgradeV1 = !upgradeV4 && !upgradeV2 && patchId.startsWith("device-edge-core-channel-upgrade-");
const upgrade = upgradeV1 || upgradeV2 || upgradeV4;
const descriptorPath = upgradeV4
? "deployment/device-edge-core-channel-upgrade-v4.json"
: upgradeV2
? "deployment/device-edge-core-channel-upgrade-v2.json"
: upgradeV1
? "deployment/device-edge-core-channel-upgrade-v1.json"
: "deployment/device-edge-core-channel-bootstrap-v1.json";
const composePath = upgradeV4
? "docker-compose.device-plane.yml"
: "docker-compose.device-edge-core-channel.yml";
const entries = [
".dockerignore",
"package.json",
"package-lock.json",
composePath,
"packages/device-protocol-contract",
"packages/device-edge-channel-contract",
"services/device-control-core",
descriptorPath,
];
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-edge-core-channel-"));
const payload = join(stage, "payload");
const target = join(artifactDir, `nodedc-device-plane-${patchId}.tgz`);
try {
await mkdir(payload, { recursive: true });
for (const entry of entries) {
if (entry === descriptorPath) {
const descriptor = JSON.parse(await readFile(resolve(devicePlaneRoot, entry), "utf8"));
if (descriptor.transitionId !== "__PATCH_ID__") {
throw new Error("device_edge_core_channel_template_id_mismatch");
}
descriptor.transitionId = patchId;
const destination = join(payload, entry);
await mkdir(dirname(destination), { recursive: true });
await writeFile(destination, `${JSON.stringify(descriptor, null, 2)}\n`, "utf8");
continue;
}
await copySafe(resolve(devicePlaneRoot, entry), join(payload, entry), devicePlaneRoot);
}
await validateDockerCopySources(
join(payload, "services/device-control-core/Dockerfile"),
payload,
);
for (const modulePath of [
"services/device-control-core/src/sensitive-reference-management.mjs",
"services/device-control-core/src/device-gateway-core-runtime.mjs",
"packages/device-edge-channel-contract/src/index.mjs",
]) {
const imported = spawnSync(
process.execPath,
["--input-type=module", "--eval", `import(${JSON.stringify(pathToFileURL(join(payload, modulePath)).href)})`],
{ cwd: payload, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 },
);
if (imported.status !== 0) {
throw new Error(`device_edge_core_channel_staged_module_import_failed:${modulePath}:${imported.stderr || imported.stdout}`);
}
}
const compose = await readFile(join(payload, composePath), "utf8");
const commonComposeRequired = [
"device-control-core:",
];
const composeRequired = upgradeV4
? [
...commonComposeRequired,
" - device-plane-private",
" - device-plane-control",
]
: [
...commonComposeRequired,
"DEVICE_EDGE_CHANNEL_ENABLED: \"true\"",
"DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: /run/nodedc-secrets/device-edge-channel/core-private-key.pem",
"DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: /run/nodedc-secrets/device-edge-channel/core-certificate.pem",
"DEVICE_EDGE_CHANNEL_TRUST_ROOT: /run/nodedc-secrets/device-edge-channel/peers",
"source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-private-key.pem",
"source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-certificate.pem",
"source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/peers",
"name: nodedc-device-plane-egress",
];
for (const required of composeRequired) {
if (!compose.includes(required)) {
throw new Error(`device_edge_core_channel_compose_contract_missing:${required}`);
}
}
if (upgradeV4) {
const coreBlock = compose.split("\n device-control-core:", 2)[1]?.split("\n device-gateway:", 1)[0] || "";
const gatewayBlock = compose.split("\n device-gateway:", 2)[1]?.split("\nnetworks:", 1)[0] || "";
if (
!coreBlock.includes(" - device-plane-private")
|| coreBlock.includes(" - device-plane-control")
|| !gatewayBlock.includes(" - device-plane-private")
|| !gatewayBlock.includes(" - device-plane-control")
) {
throw new Error("device_edge_core_channel_v4_network_boundary_mismatch");
}
}
const composeForbidden = upgradeV4
? ["gw_priority:", "network_mode:", "privileged:"]
: [
"device-manager:",
"device-gateway:",
"device-postgres:",
"PRIVATE KEY",
"ports:",
"network_mode:",
"privileged:",
];
for (const forbidden of composeForbidden) {
if (compose.includes(forbidden)) {
throw new Error(`device_edge_core_channel_compose_boundary_violation:${forbidden}`);
}
}
const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8"));
const descriptorContractMatches = upgradeV4
? (
descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-upgrade.v4"
&& descriptor.action === "upgrade"
&& descriptor.composeActivation === "replace-core-network-membership-with-private-plus-egress"
&& descriptor.identityRecovery === "forbidden-valid-existing-identity-required"
&& descriptor.endpointPolicy === "public-ipv4-standard-https-tcp-443-only"
&& descriptor.upgradePredecessor?.patchId === "device-edge-core-channel-upgrade-v2-20260812-021"
&& descriptor.upgradePredecessor?.artifactSha256 === "e40a6fd24edfecac09e42cd82635a77850541bcf047788db3e9c55d2b9e58867"
&& descriptor.failedAttempt?.patchId === "device-edge-core-channel-upgrade-v3-20260812-022"
&& descriptor.failedAttempt?.artifactSha256 === "9e2b409a4b2d19711db434e90d03ac8e3db77bd74949f83cace7949f33caf613"
&& descriptor.failedAttempt?.backupId === "device-plane-device-edge-core-channel-upgrade-v3-20260812-022-20260812-123620"
&& JSON.stringify(descriptor.coreNetworks) === JSON.stringify(["device-plane-private", "device-plane-egress"])
&& descriptor.removedCoreNetwork === "device-plane-control"
&& descriptor.composeCompatibility === "synology-compose-v2.20-no-gw-priority"
&& descriptor.edgeRegistrations === "preserved"
&& descriptor.rollback === "restore-upgrade-v2-021-source-and-preapply-core-runtime"
)
: upgradeV2
? (
descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-upgrade.v2"
&& descriptor.action === "upgrade"
&& descriptor.composeActivation === "preserve-dedicated-additive-override"
&& descriptor.identityRecovery === "forbidden-valid-existing-identity-required"
&& descriptor.endpointPolicy === "public-ipv4-standard-https-tcp-443-only"
&& descriptor.upgradePredecessor?.patchId === "device-edge-core-channel-upgrade-20260812-019"
&& descriptor.upgradePredecessor?.artifactSha256 === "8e9a220275959f378c1c4b00be5c7192e79afe2134eaab808a64e515870a8438"
&& descriptor.edgeRegistrations === "preserved"
&& descriptor.rollback === "restore-upgrade-019-source-and-preapply-core-runtime"
)
: upgradeV1
? (
descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-upgrade.v1"
&& descriptor.action === "upgrade"
&& descriptor.composeActivation === "preserve-dedicated-additive-override"
&& descriptor.identityRecovery === "forbidden-valid-existing-identity-required"
&& descriptor.endpointPolicy === "public-ipv4-standard-https-tcp-443-only"
&& descriptor.bootstrapPredecessor?.patchId === "device-edge-core-channel-bootstrap-20260812-018"
&& descriptor.bootstrapPredecessor?.artifactSha256 === "5598b7388b491fe524ab46038ce476482a93a6cf07d8ca5e00206c69ded02931"
)
: (
descriptor.schemaVersion === "nodedc.device-plane.device-edge-core-channel-bootstrap.v1"
&& descriptor.action === "activate"
&& descriptor.composeActivation === "dedicated-additive-override"
&& descriptor.identityRecovery === "exact-invalid-unexported-failed-predecessor-only"
);
if (
!descriptorContractMatches
|| descriptor.transitionId !== patchId
|| descriptor.service !== "device-control-core"
|| descriptor.tlsPurpose !== "clientAuth"
|| descriptor.direction !== "core-initiated"
|| descriptor.publicIngress !== "none-on-synology"
|| descriptor.commandTransport !== "disabled"
|| descriptor.gelios !== "untouched"
) {
throw new Error("device_edge_core_channel_bootstrap_contract_mismatch");
}
await writeFile(join(stage, "manifest.env"), `id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`, "utf8");
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync("python3", ["-c", canonicalTarScript(), target, stage], {
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
});
if (tar.status !== 0) throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
const sha256 = createHash("sha256").update(await readFile(target)).digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
component: "device-plane",
artifact: target,
sha256,
entries,
services: ["device-control-core"],
preserved: ["device-manager", "device-gateway", "device-postgres", "device-backhaul-target", "Gelios"],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function copySafe(source, destination, sourceBoundary) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) throw new Error(`source_symlink_rejected:${relative(sourceBoundary, source)}`);
if (sourceStat.isFile()) {
if (source.endsWith(".test.mjs") || source.endsWith(".map")) return;
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, { force: true, verbatimSymlinks: true });
return;
}
if (!sourceStat.isDirectory()) throw new Error(`source_type_rejected:${source}`);
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if ([".DS_Store", ".git", "node_modules", "test"].includes(entry.name) || entry.name.startsWith(".env")) continue;
await copySafe(join(source, entry.name), join(destination, entry.name), sourceBoundary);
}
}
async function validateDockerCopySources(dockerfilePath, buildContext) {
const dockerfile = await readFile(dockerfilePath, "utf8");
for (const [index, rawLine] of dockerfile.split("\n").entries()) {
const line = rawLine.trim();
if (!/^COPY\s+/i.test(line)) continue;
if (line.endsWith("\\") || /^COPY\s+\[/i.test(line)) {
throw new Error(`unsupported_docker_copy_syntax:${dockerfilePath}:${index + 1}`);
}
const tokens = line.split(/\s+/).slice(1);
while (tokens[0]?.startsWith("--")) tokens.shift();
if (tokens.length < 2) throw new Error(`invalid_docker_copy:${dockerfilePath}:${index + 1}`);
for (const source of tokens.slice(0, -1)) {
if (/[*?[\]{}]/.test(source)) throw new Error(`docker_copy_glob_rejected:${dockerfilePath}:${index + 1}:${source}`);
const resolvedSource = resolve(buildContext, source);
const relativeSource = relative(buildContext, resolvedSource);
if (!relativeSource || relativeSource.startsWith("..") || resolve(buildContext, relativeSource) !== resolvedSource) {
throw new Error(`docker_copy_source_outside_context:${dockerfilePath}:${index + 1}:${source}`);
}
try {
await lstat(resolvedSource);
} catch (error) {
if (error?.code === "ENOENT") throw new Error(`docker_copy_source_missing:${dockerfilePath}:${index + 1}:${source}`);
throw error;
}
}
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix()); info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,265 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-edge-admission-gate-20260804-002",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-edge-ingress-artifact.mjs [patch-id]",
);
}
const files = [
"docker-compose.device-edge.yml",
"docker-compose.device-edge.ingress.yml",
"services/device-edge-relay/Dockerfile",
"services/device-edge-relay/src",
"deployment/device-edge-admission-gate-v1.json",
];
const ignoredBasenames = new Set([".DS_Store", ".git", "node_modules"]);
const descriptor = await assertBoundary();
if (descriptor.ingressIpv4Approval !== "approved-outside-dhcp-pool") {
throw new Error("device_edge_ingress_ipv4_approval_pending");
}
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-edge-ingress-"));
const payload = join(stage, "payload");
const target = join(artifactDir, `nodedc-device-edge-${patchId}.tgz`);
try {
await mkdir(payload, { recursive: true });
for (const sourceRelative of files) {
await copySafe(
resolve(sourceRoot, sourceRelative),
join(payload, sourceRelative),
);
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-edge\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{ encoding: "utf8", maxBuffer: 128 * 1024 * 1024 },
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const digest = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256: digest,
component: "device-edge",
transition: "reviewed-ipvlan-b2-relay-admission-gate",
entries: files,
services: ["device-edge-relay"],
preservedRuntime: ["device-edge-backhaul", "tailnet", "Gelios"],
ingress: {
parent: descriptor.parentInterface,
subnet: descriptor.lanSubnet,
gateway: descriptor.lanGateway,
ipv4: descriptor.ingressIpv4,
ipv4Approval: descriptor.ingressIpv4Approval,
tcp: 9921,
hostPortPublication: "disabled",
sourceAdmission: descriptor.sourceAdmission,
maxTrackedSourceAddresses: descriptor.maxTrackedSourceAddresses,
maxBytesPerDirection: descriptor.maxBytesPerDirection,
lifecycle: "quarantine",
commandTransport: "disabled",
},
rollback: descriptor.rollback,
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertBoundary() {
const baseline = await readFile(
resolve(sourceRoot, "docker-compose.device-edge.yml"),
"utf8",
);
const ingress = await readFile(
resolve(sourceRoot, "docker-compose.device-edge.ingress.yml"),
"utf8",
);
const descriptor = JSON.parse(await readFile(
resolve(
sourceRoot,
"deployment/device-edge-admission-gate-v1.json",
),
"utf8",
));
for (const fragment of [
"DEVICE_EDGE_RELAY_HEALTH_HOST: 127.0.0.1",
'DEVICE_EDGE_RELAY_INGRESS_ENABLED: "false"',
"read_only: true",
'user: "1000:1000"',
"no-new-privileges:true",
"cap_drop:",
"- ALL",
]) {
if (!baseline.includes(fragment)) {
throw new Error(`device_edge_baseline_boundary_missing:${fragment}`);
}
}
for (const forbidden of ["ports:", "device-edge-control"]){
if (baseline.includes(forbidden)) {
throw new Error(`device_edge_baseline_boundary_violation:${forbidden}`);
}
}
for (const fragment of [
'DEVICE_EDGE_RELAY_INGRESS_ENABLED: "true"',
"DEVICE_EDGE_RELAY_UPSTREAM_HOST: device-edge-backhaul",
'DEVICE_EDGE_RELAY_UPSTREAM_PORT: "19921"',
"DEVICE_EDGE_RELAY_SOURCE_POLICY: public-ipv4-only",
'DEVICE_EDGE_RELAY_MAX_TRACKED_SOURCE_ADDRESSES: "2048"',
'DEVICE_EDGE_RELAY_MAX_BYTES_PER_DIRECTION: "262144"',
"name: nodedc-device-edge-ingress",
"driver: ipvlan",
"parent: enp1s0f0",
"ipvlan_mode: l2",
"ipv4_address: 192.168.71.253",
"gw_priority: 100",
"subnet: 192.168.68.0/22",
"gateway: 192.168.68.1",
]) {
if (!ingress.includes(fragment)) {
throw new Error(`device_edge_ingress_boundary_missing:${fragment}`);
}
}
for (const forbidden of [
"ports:",
"network_mode: host",
"privileged: true",
"DEVICE_EDGE_RELAY_COMMAND",
"0.0.0.0:9921:9921",
]) {
if (ingress.includes(forbidden)) {
throw new Error(`device_edge_ingress_boundary_violation:${forbidden}`);
}
}
const expected = {
schemaVersion: "nodedc.device-edge.admission-gate.v1",
mode: "single-nic-ipvlan-b2-relay-only",
runtimeHost: "ndcmini12",
component: "device-edge",
selectedServices: ["device-edge-relay"],
preservedServices: ["device-edge-backhaul", "tailnet"],
composeProject: "nodedc-device-edge",
composeFiles: [
"docker-compose.device-edge.yml",
"docker-compose.device-edge.ingress.yml",
],
parentInterface: "enp1s0f0",
lanSubnet: "192.168.68.0/22",
lanGateway: "192.168.68.1",
ingressIpv4: "192.168.71.253",
ingressIpv4Approval: "approved-outside-dhcp-pool",
ingressNetwork: "nodedc-device-edge-ingress",
deviceTcpListen: "192.168.71.253:9921",
hostPortPublication: "disabled",
healthPublication: "disabled",
privateUpstream: "device-edge-backhaul:19921",
sourceAdmission: "public-ipv4-only",
maxTrackedSourceAddresses: 2048,
maxBytesPerDirection: 262144,
protocolInspection: "gateway-owned",
identityTrust: "claimed-not-ownership-proof",
discoveryLifecycle: "quarantine",
commandTransport: "disabled",
gelios: "untouched",
amneziaHostFullTunnel: "preserved",
routerNatFirewall: "separate-manual-gate",
rollback: "restore-reviewed-ipvlan-predecessor-without-network-or-router-mutation",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_edge_ingress_descriptor_mismatch");
}
return descriptor;
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
async function copySafe(source, destination) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) {
throw new Error(`source_symlink_rejected:${relative(sourceRoot, source)}`);
}
if (sourceStat.isFile()) {
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, { force: true, verbatimSymlinks: true });
return;
}
if (!sourceStat.isDirectory()) {
throw new Error(`source_type_rejected:${source}`);
}
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if (
ignoredBasenames.has(entry.name)
|| entry.name.startsWith(".env")
) {
continue;
}
const childSource = join(source, entry.name);
const childDestination = join(destination, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, childSource)}`,
);
}
await copySafe(childSource, childDestination);
}
}
@@ -1,465 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { basename, dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const runtimeCache = resolve(
process.env.NODEDC_DEVICE_EDGE_VPS_RUNTIME_DIR || "/tmp",
);
const [phase, patchId, ...extra] = process.argv.slice(2);
if (
extra.length
|| ![
"foundation",
"runtime-reconciliation",
"backhaul",
"relay",
"core-channel",
"tailscale-retirement",
"tracker-ingress",
"command-transport",
].includes(phase)
|| !/^[A-Za-z0-9._-]{1,96}$/.test(patchId || "")
) {
throw new Error(
"usage: build-device-edge-vps-artifact.mjs <foundation|runtime-reconciliation|backhaul|relay|core-channel|tailscale-retirement|tracker-ingress|command-transport> <patch-id>",
);
}
const supersededTransportPhases = new Set(["backhaul", "relay"]);
if (
supersededTransportPhases.has(phase)
&& process.env.NODEDC_ALLOW_SUPERSEDED_TRANSPORT !== "test-only"
) {
throw new Error("vps_initiated_transport_frozen:ADR-0001");
}
const acceptedSharedSourcePhases = new Set(["core-channel", "tracker-ingress"]);
if (acceptedSharedSourcePhases.has(phase)) {
throw new Error(`accepted_vps_phase_rebuild_frozen:${phase}:ADR-0001`);
}
const nodeArchive = "node-v22.23.2-linux-x64.tar.xz";
const tailscaleArchive = "tailscale_1.102.2_amd64.tgz";
const runtimeDigests = new Map([
[nodeArchive, "d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307"],
[tailscaleArchive, "ad2cde12f8de95f7b93a1e0401e652291c603d42b9d60a33fb1741eb38ab04d8"],
]);
const entriesByPhase = {
foundation: [
"vps/config/00-nodedc-b2-vps.conf",
"vps/config/nftables-foundation.conf",
"vps/systemd/nodedc-b2-tailscaled.service",
"deployment/device-edge-vps-foundation-v1.json",
`vendor/${nodeArchive}`,
`vendor/${tailscaleArchive}`,
],
"runtime-reconciliation": [
"deployment/device-edge-vps-runtime-reconciliation-v1.json",
],
backhaul: [
"vps/config/backhaul_ssh_config",
"vps/systemd/nodedc-b2-backhaul.service",
"deployment/device-edge-vps-backhaul-v1.json",
],
relay: [
"vps/config/nftables-relay.conf",
"vps/systemd/nodedc-b2-relay.service",
"services/device-edge-relay/src",
"deployment/device-edge-vps-relay-v1.json",
],
"core-channel": [
"packages/device-protocol-contract/package.json",
"packages/device-protocol-contract/src",
"packages/device-edge-channel-contract/package.json",
"packages/device-edge-channel-contract/src",
"services/device-edge-channel/package.json",
"services/device-edge-channel/src",
"vps/config/nftables-core-channel.conf",
"vps/systemd/nodedc-device-edge-channel.service",
"deployment/device-edge-vps-core-channel-v1.json",
],
"tailscale-retirement": [
"deployment/device-edge-vps-tailscale-retirement-v1.json",
],
"tracker-ingress": [
"packages/device-adapter-runtime/package.json",
"packages/device-adapter-runtime/src",
"packages/device-adapter-catalog/package.json",
"packages/device-adapter-catalog/src",
"packages/arusnavi-b2-adapter/package.json",
"packages/arusnavi-b2-adapter/src",
"services/device-gateway/src/runtime.mjs",
"vps/edge-process/device-edge-runtime.mjs",
"vps/config/nftables-tracker-ingress.conf",
"vps/systemd/nodedc-device-edge-runtime.service",
"deployment/device-edge-vps-tracker-ingress-v1.json",
],
"command-transport": [
"packages/device-edge-channel-contract/package.json",
"packages/device-edge-channel-contract/src",
"services/device-edge-channel/package.json",
"services/device-edge-channel/src",
"packages/device-adapter-runtime/package.json",
"packages/device-adapter-runtime/src",
"packages/device-adapter-catalog/package.json",
"packages/device-adapter-catalog/src",
"packages/arusnavi-b2-adapter/package.json",
"packages/arusnavi-b2-adapter/src",
"services/device-gateway/src/runtime.mjs",
"vps/edge-process/device-edge-runtime.mjs",
"deployment/device-edge-vps-command-transport-v1.json",
],
};
const entries = entriesByPhase[phase];
const ignoredBasenames = new Set([".DS_Store", ".git", "node_modules"]);
const stage = await mkdtemp(join(tmpdir(), `nodedc-device-edge-vps-${phase}-`));
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-edge-vps-${patchId}.tgz`,
);
await assertBoundary();
try {
await mkdir(payload, { recursive: true });
for (const entry of entries) {
if (entry.startsWith("vendor/")) {
const name = basename(entry);
const source = resolve(runtimeCache, name);
const actual = createHash("sha256").update(await readFile(source)).digest("hex");
if (actual !== runtimeDigests.get(name)) {
throw new Error(`runtime_digest_mismatch:${name}:${actual}`);
}
await mkdir(dirname(join(payload, entry)), { recursive: true });
await cp(source, join(payload, entry), { force: true });
continue;
}
await copySafe(resolve(sourceRoot, entry), join(payload, entry));
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-edge-vps\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{ encoding: "utf8", maxBuffer: 256 * 1024 * 1024 },
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const bytes = await readFile(target);
const digest = createHash("sha256").update(bytes).digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
phase,
artifact: target,
sha256: digest,
size: bytes.length,
component: "device-edge-vps",
entries,
publicIngress: phase === "relay"
? "tcp/9921"
: ["core-channel", "tailscale-retirement"].includes(phase)
? "tcp/443-mtls-only"
: ["tracker-ingress", "command-transport"].includes(phase)
? "tcp/443-mtls+tcp/9921-telemetry"
: "disabled",
commandTransport: phase === "command-transport"
? "typed-service-ping-v1"
: "disabled",
gelios: "untouched",
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertBoundary() {
const descriptorPath = resolve(
sourceRoot,
`deployment/device-edge-vps-${phase}-v1.json`,
);
const descriptor = JSON.parse(await readFile(descriptorPath, "utf8"));
if (
descriptor.component !== "device-edge-vps"
|| descriptor.runtimeHost !== "koffyvngij"
|| descriptor.commandTransport !== (phase === "command-transport"
? "typed-service-ping-v1"
: "disabled")
|| !String(descriptor.gelios || "").startsWith("untouched")
|| !String(descriptor.rollback || "").length
) {
throw new Error(`descriptor_boundary_mismatch:${phase}`);
}
const selectedText = await Promise.all(
entries
.filter((entry) => !entry.startsWith("vendor/") && !entry.endsWith("/src"))
.map((entry) => readFile(resolve(sourceRoot, entry), "utf8")),
);
const combined = selectedText.join("\n");
for (const forbidden of [
"PRIVATE KEY",
"AuthKey",
"TS_AUTHKEY",
"PasswordAuthentication yes",
"commandTransport\": \"enabled",
"device.dc.ru",
]) {
if (combined.includes(forbidden)) {
throw new Error(`vps_boundary_violation:${forbidden}`);
}
}
if (phase === "foundation") {
for (const required of [
"PermitRootLogin prohibit-password",
"PasswordAuthentication no",
"AllowTcpForwarding no",
"policy drop",
"tcp dport 22",
"--tun=userspace-networking",
"--socks5-server=127.0.0.1:1055",
]) {
if (!combined.includes(required)) {
throw new Error(`foundation_boundary_missing:${required}`);
}
}
if (combined.includes("tcp dport 9921")) {
throw new Error("foundation_must_not_open_9921");
}
}
if (phase === "runtime-reconciliation") {
for (const required of [
"recover-exact-runtime-executable-modes-after-failed-core-channel-publish",
"restore-root-owned-executable-mode-0755-for-exact-known-binaries",
'"publicCoreChannel": "disabled"',
'"trackerIngress": "disabled"',
]) {
if (!combined.includes(required)) {
throw new Error(`runtime_reconciliation_boundary_missing:${required}`);
}
}
}
if (phase === "backhaul") {
for (const required of [
"\"runtimeUser\": \"nodedc-backhaul\"",
"User=nodedc-backhaul",
"HostName 100.109.216.21",
"Port 2222",
"StrictHostKeyChecking yes",
"LocalForward 127.0.0.1:19921 127.0.0.1:9921",
"ProxyCommand /usr/bin/nc -X 5 -x 127.0.0.1:1055",
"MemoryMax=64M",
]) {
if (!combined.includes(required)) {
throw new Error(`backhaul_boundary_missing:${required}`);
}
}
}
if (phase === "relay") {
for (const required of [
"\"runtimeUser\": \"nodedc-relay\"",
"User=nodedc-relay",
"tcp dport 9921",
"DEVICE_EDGE_RELAY_UPSTREAM_HOST=127.0.0.1",
"DEVICE_EDGE_RELAY_UPSTREAM_PORT=19921",
"DEVICE_EDGE_RELAY_SOURCE_POLICY=public-ipv4-only",
"MemoryMax=192M",
]) {
if (!combined.includes(required)) {
throw new Error(`relay_boundary_missing:${required}`);
}
}
}
if (phase === "core-channel") {
for (const required of [
"\"runtimeUser\": \"nodedc-channel\"",
"\"trackerIngress\": \"disabled\"",
"User=nodedc-channel",
"ExecStart=/opt/nodedc-b2-vps/runtime/node/bin/node /opt/nodedc-b2-vps/services/device-edge-channel/src/server.mjs",
"MemoryDenyWriteExecute=no",
"CapabilityBoundingSet=CAP_NET_BIND_SERVICE",
"AmbientCapabilities=CAP_NET_BIND_SERVICE",
"tcp dport 443",
"MemoryMax=128M",
"MemorySwapMax=0",
"CPUQuota=50%",
"TasksMax=64",
"LimitNOFILE=1024",
]) {
if (!combined.includes(required)) {
throw new Error(`core_channel_boundary_missing:${required}`);
}
}
for (const forbidden of [
"tcp dport 9921",
"LocalForward",
"tailscale-userspace",
"DEVICE_EDGE_RELAY_UPSTREAM",
"--jitless",
]) {
if (combined.includes(forbidden)) {
throw new Error(`core_channel_boundary_violation:${forbidden}`);
}
}
}
if (phase === "tailscale-retirement") {
for (const required of [
'"predecessorPatch": "device-edge-vps-core-channel-20260812-010"',
'"runtimeAction": "stop-disable-remove-userspace-tailscale-runtime-state-and-superseded-trust"',
'"trackerIngress": "disabled"',
'"externalRevocation": "delete-exact-nodedc-b2-vps-machine-in-tailnet-after-deploy-ok"',
]) {
if (!combined.includes(required)) {
throw new Error(`tailscale_retirement_boundary_missing:${required}`);
}
}
for (const forbidden of [
"tcp dport 9921",
"LocalForward",
"commandTransport\": \"enabled",
]) {
if (combined.includes(forbidden)) {
throw new Error(`tailscale_retirement_boundary_violation:${forbidden}`);
}
}
}
if (phase === "tracker-ingress") {
for (const required of [
'"predecessorPatch": "device-edge-vps-tailscale-retirement-20260812-011"',
'"runtimeComposition": "single-process-core-channel-plus-universal-device-gateway"',
'"trackerIngress": "enabled:allowlisted-adapters-only"',
'"acknowledgementBoundary": "tracker-ack-only-after-core-durable-acceptance"',
'"initialAdapterProfile": "arusnavi.b2.internal.v1"',
"createDeviceGatewayRuntime",
"DEVICE_ADAPTER_CATALOG.registry",
"onDiscovery: (signal) => channel.submitDiscovery(signal)",
"onMessage: (message) => channel.submitAdapterMessage(message)",
"tcp dport 9921",
"User=nodedc-channel",
"MemoryMax=192M",
"MemorySwapMax=0",
"CPUQuota=75%",
"TasksMax=128",
"LimitNOFILE=1024",
]) {
if (!combined.includes(required)) {
throw new Error(`tracker_ingress_boundary_missing:${required}`);
}
}
for (const forbidden of [
"LocalForward",
"tailscale-userspace",
"DEVICE_EDGE_RELAY_UPSTREAM",
'commandTransport": "enabled',
"device.dc.ru",
]) {
if (combined.includes(forbidden)) {
throw new Error(`tracker_ingress_boundary_violation:${forbidden}`);
}
}
}
if (phase === "command-transport") {
for (const required of [
'"predecessorPatch": "device-edge-vps-tracker-ingress-20260812-012"',
'"runtimeService": "nodedc-device-edge-channel.service"',
'"runtimeComposition": "single-process-core-channel-plus-universal-device-gateway"',
'"commandTransport": "typed-service-ping-v1"',
'"commandCatalog": "allowlisted-adapter-typed-commands-only"',
'"responseBoundary": "exact-adapter-parser-serv-ok-only"',
"buildTypedCommand",
"parseTypedCommandResponse",
"submitCommandStatus",
'"service.ping"',
]) {
if (!combined.includes(required)) {
throw new Error(`command_transport_boundary_missing:${required}`);
}
}
for (const forbidden of [
"LocalForward",
"tailscale-userspace",
"DEVICE_EDGE_RELAY_UPSTREAM",
"device.dc.ru",
"PRIVATE KEY",
"TS_AUTHKEY",
]) {
if (combined.includes(forbidden)) {
throw new Error(`command_transport_boundary_violation:${forbidden}`);
}
}
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
async function copySafe(source, destination) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) {
throw new Error(`source_symlink_rejected:${relative(sourceRoot, source)}`);
}
if (sourceStat.isFile()) {
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, { force: true, verbatimSymlinks: true });
return;
}
if (!sourceStat.isDirectory()) {
throw new Error(`source_type_rejected:${source}`);
}
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if (ignoredBasenames.has(entry.name) || entry.name.startsWith(".env")) {
continue;
}
const childSource = join(source, entry.name);
const childDestination = join(destination, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(`source_symlink_rejected:${relative(sourceRoot, childSource)}`);
}
await copySafe(childSource, childDestination);
}
}
@@ -1,248 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { cp, lstat, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const devicePlaneRoot = resolve(platformRoot, "device-plane");
const designRoot = resolve(process.env.NODEDC_DEVICE_MANAGER_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_DESIGN_GUIDELINE"));
const managerRoot = resolve(designRoot, "apps/device-manager");
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
const [patchId = "device-manager-release-v3-20260812-026", ...extra] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
const descriptorPath = patchId.startsWith("device-manager-release-v3-")
? "deployment/device-manager-release-v3.json"
: "deployment/device-manager-release-v1.json";
const isV3 = descriptorPath.endsWith("release-v3.json");
const entries = isV3 ? [
"docker-compose.device-manager.yml",
"services/device-manager",
descriptorPath,
] : [
".dockerignore",
"package.json",
"package-lock.json",
"docker-compose.device-manager.yml",
"packages/device-protocol-contract",
"packages/device-edge-channel-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway/package.json",
"services/device-edge-relay/package.json",
"services/device-manager",
descriptorPath,
];
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-control-plane-"));
const payload = join(stage, "payload");
const target = join(artifactDir, `nodedc-device-plane-${patchId}.tgz`);
try {
const build = spawnSync("npm", ["run", "build", "--workspace", "@nodedc/device-manager"], {
cwd: designRoot,
encoding: "utf8",
maxBuffer: 64 * 1024 * 1024,
});
if (build.status !== 0) throw new Error(`device_manager_build_failed:${build.stderr || build.stdout}`);
await mkdir(payload, { recursive: true });
for (const entry of entries) {
if (entry === descriptorPath) {
const descriptor = JSON.parse(await readFile(resolve(devicePlaneRoot, entry), "utf8"));
if (descriptor.releaseId !== "__PATCH_ID__") {
throw new Error("device_manager_release_template_id_mismatch");
}
descriptor.releaseId = patchId;
const destination = join(payload, entry);
await mkdir(dirname(destination), { recursive: true });
await writeFile(destination, `${JSON.stringify(descriptor, null, 2)}\n`, "utf8");
continue;
}
if (entry === "services/device-manager") {
const destination = join(payload, entry);
await mkdir(destination, { recursive: true });
await copySafe(resolve(devicePlaneRoot, "services/device-manager/Dockerfile"), join(destination, "Dockerfile"), devicePlaneRoot);
await copySafe(resolve(managerRoot, "server"), join(destination, "server"), managerRoot);
await copySafe(resolve(managerRoot, "dist"), join(destination, "dist"), managerRoot);
continue;
}
await copySafe(resolve(devicePlaneRoot, entry), join(payload, entry), devicePlaneRoot);
}
if (!isV3) {
await validateDockerCopySources(
join(payload, "services/device-control-core/Dockerfile"),
payload,
);
}
await validateDockerCopySources(
join(payload, "services/device-manager/Dockerfile"),
join(payload, "services/device-manager"),
);
for (const modulePath of isV3 ? [] : [
"services/device-control-core/src/sensitive-reference-management.mjs",
"services/device-control-core/src/device-gateway-core-runtime.mjs",
"packages/device-edge-channel-contract/src/index.mjs",
]) {
const coreImport = spawnSync(
process.execPath,
[
"--input-type=module",
"--eval",
`import(${JSON.stringify(pathToFileURL(join(payload, modulePath)).href)})`,
],
{
cwd: payload,
encoding: "utf8",
maxBuffer: 16 * 1024 * 1024,
},
);
if (coreImport.status !== 0) {
throw new Error(
`device_control_core_staged_module_import_failed:${modulePath}:${coreImport.stderr || coreImport.stdout}`,
);
}
}
const compose = await readFile(join(payload, "docker-compose.device-manager.yml"), "utf8");
for (const required of [
"device-manager:",
"DEVICE_MANAGEMENT_API_ENABLED: \"true\"",
"NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE: /run/nodedc-secrets/device-core-internal-token",
"NODEDC_DEVICE_CORE_TOKEN_FILE: /run/nodedc-secrets/management-core-token",
"name: nodedc-platform_edge",
]) if (!compose.includes(required)) throw new Error(`device_manager_compose_contract_missing:${required}`);
for (const forbidden of [
"NODEDC_INTERNAL_ACCESS_TOKEN:",
"NODEDC_PLATFORM_SERVICE_TOKEN:",
"PRIVATE KEY",
"DEVICE_EDGE_CHANNEL_",
"device-edge-channel/",
"nodedc-device-plane-egress",
"0.0.0.0:18122",
"0.0.0.0:9921:9921",
"- \"9921:9921\"",
]) {
if (compose.includes(forbidden)) throw new Error(`device_manager_compose_boundary_violation:${forbidden}`);
}
const descriptor = JSON.parse(await readFile(join(payload, descriptorPath), "utf8"));
const predecessor = descriptor.predecessor;
const commonContractInvalid = (
descriptor.releaseId !== patchId
|| !["activate", "upgrade"].includes(descriptor.action)
|| !predecessor
|| !["reconciliation", "release"].includes(predecessor.kind)
|| !/^[A-Za-z0-9._-]{1,96}$/.test(predecessor.patchId || "")
|| !/^[a-f0-9]{64}$/.test(predecessor.artifactSha256 || "")
|| (descriptor.action === "activate") !== (predecessor.kind === "reconciliation")
|| descriptor.healthGate !== "bounded-container-grace+core-contract"
|| descriptor.rollback !== "restore-preapply-snapshot"
);
if (commonContractInvalid) throw new Error("device_manager_activation_successor_contract_mismatch");
if (descriptorPath.endsWith("release-v3.json")) {
if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v3"
|| descriptor.commandTransport !== "typed-service-ping-v1"
|| descriptor.commandCatalog !== "allowlisted-adapter-typed-commands-only"
|| descriptor.credentialBoundary !== "transient-core-memory-then-single-pinned-mtls-command-envelope-to-edge-never-persisted-never-logged-never-returned"
|| descriptor.controlCorePredecessor?.patchId !== "device-control-core-release-v2-20260812-025"
|| descriptor.controlCorePredecessor?.artifactSha256 !== "c61b1f0de1bae23de0caa7289036865ea419ff5705611416f736ca929d1592db"
|| descriptor.edgeChannelPredecessor?.patchId !== "device-edge-core-channel-upgrade-v4-20260812-023"
|| descriptor.edgeChannelPredecessor?.artifactSha256 !== "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc"
|| descriptor.edgeChannel !== "preserve-active-v4-core-initiated-pinned-mtls"
|| descriptor.edgeChannelEgress !== "preserve-dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-443-only"
|| descriptor.gelios !== "untouched-legacy-only"
) throw new Error("device_manager_v3_typed_command_contract_mismatch");
} else if (
descriptor.schemaVersion !== "nodedc.device-plane.device-manager-release.v1"
|| descriptor.commandTransport !== "disabled"
|| descriptor.gelios !== "untouched"
) {
throw new Error("device_manager_v1_contract_mismatch");
}
await writeFile(join(stage, "manifest.env"), `id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`, "utf8");
await writeFile(join(stage, "files.txt"), `${entries.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync("python3", ["-c", canonicalTarScript(), target, stage], { encoding: "utf8", maxBuffer: 128 * 1024 * 1024 });
if (tar.status !== 0) throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
const sha256 = createHash("sha256").update(await readFile(target)).digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
component: "device-plane",
artifact: target,
sha256,
entries,
services: isV3 ? ["device-manager"] : ["device-control-core", "device-manager"],
preserved: ["device-postgres", "device-gateway", "device-backhaul-target", "Gelios"],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function copySafe(source, destination, sourceBoundary) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) throw new Error(`source_symlink_rejected:${relative(sourceBoundary, source)}`);
if (sourceStat.isFile()) {
if (source.endsWith(".test.mjs") || source.endsWith(".map")) return;
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, { force: true, verbatimSymlinks: true });
return;
}
if (!sourceStat.isDirectory()) throw new Error(`source_type_rejected:${source}`);
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if ([".DS_Store", ".git", "node_modules", "test"].includes(entry.name) || entry.name.startsWith(".env")) continue;
await copySafe(join(source, entry.name), join(destination, entry.name), sourceBoundary);
}
}
async function validateDockerCopySources(dockerfilePath, buildContext) {
const dockerfile = await readFile(dockerfilePath, "utf8");
for (const [index, rawLine] of dockerfile.split("\n").entries()) {
const line = rawLine.trim();
if (!/^COPY\s+/i.test(line)) continue;
if (line.endsWith("\\") || /^COPY\s+\[/i.test(line)) {
throw new Error(`unsupported_docker_copy_syntax:${dockerfilePath}:${index + 1}`);
}
const tokens = line.split(/\s+/).slice(1);
while (tokens[0]?.startsWith("--")) tokens.shift();
if (tokens.length < 2) {
throw new Error(`invalid_docker_copy:${dockerfilePath}:${index + 1}`);
}
for (const source of tokens.slice(0, -1)) {
if (/[*?[\]{}]/.test(source)) {
throw new Error(`docker_copy_glob_rejected:${dockerfilePath}:${index + 1}:${source}`);
}
const resolvedSource = resolve(buildContext, source);
const relativeSource = relative(buildContext, resolvedSource);
if (!relativeSource || relativeSource.startsWith("..") || resolve(buildContext, relativeSource) !== resolvedSource) {
throw new Error(`docker_copy_source_outside_context:${dockerfilePath}:${index + 1}:${source}`);
}
try {
await lstat(resolvedSource);
} catch (error) {
if (error?.code === "ENOENT") {
throw new Error(`docker_copy_source_missing:${dockerfilePath}:${index + 1}:${source}`);
}
throw error;
}
}
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix()); info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,111 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-manager-control-plane-reconciliation-20260811-002",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-manager-control-plane-reconciliation-artifact.mjs [patch-id]",
);
}
const entry = "deployment/device-manager-control-plane-reconciliation-v1.json";
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-reconciliation-"));
const payload = join(stage, "payload");
const target = resolve(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
try {
const descriptor = JSON.parse(await readFile(resolve(sourceRoot, entry), "utf8"));
const expected = {
schemaVersion: "nodedc.device-plane.device-manager-control-plane-reconciliation.v1",
mode: "failed-control-plane-baseline-adoption",
failedPatchId: "device-manager-control-plane-20260810-001",
failedArtifactSha256:
"50e275c1085286bcb3bb2b273aefc8bbba70f446ca2c7bd464dc745710a291a6",
backupId:
"device-plane-device-manager-control-plane-20260810-001-20260811-000321",
sourceAction: "publish-reconciliation-marker-only",
runtimeAction: "read-only-acceptance",
preservedServices: [
"device-control-core",
"device-gateway",
"device-postgres",
"device-backhaul-target",
],
absentService: "device-manager",
databaseVolume: "nodedc-device-plane-postgres-data",
publicIngress: "disabled",
commandTransport: "disabled",
gelios: "untouched",
rollback: "marker-only-runtime-unchanged",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_manager_reconciliation_descriptor_mismatch");
}
await mkdir(dirname(join(payload, entry)), { recursive: true });
await cp(resolve(sourceRoot, entry), join(payload, entry), { force: true });
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${entry}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{ encoding: "utf8", maxBuffer: 16 * 1024 * 1024 },
);
if (tar.status !== 0) throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
const sha256 = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
component: "device-plane",
artifact: target,
sha256,
entries: [entry],
build: [],
services: [],
transition: "failed-control-plane-baseline-adoption",
runtimeAction: "read-only-acceptance",
sourceAction: "publish-reconciliation-marker-only",
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix()); info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,111 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-manager-control-plane-v2-reconciliation-20260811-004",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-manager-control-plane-v2-reconciliation-artifact.mjs [patch-id]",
);
}
const entry = "deployment/device-manager-control-plane-v2-reconciliation-v1.json";
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-manager-v2-reconciliation-"));
const payload = join(stage, "payload");
const target = resolve(artifactDir, `nodedc-device-plane-${patchId}.tgz`);
try {
const descriptor = JSON.parse(await readFile(resolve(sourceRoot, entry), "utf8"));
const expected = {
schemaVersion: "nodedc.device-plane.device-manager-control-plane-v2-reconciliation.v1",
mode: "failed-v2-control-plane-baseline-adoption",
failedPatchId: "device-manager-control-plane-20260811-003",
failedArtifactSha256:
"ba29618ffbfed55448768794f28b18dda439ddb39a1d2a4f1dece19de7f29990",
backupId:
"device-plane-device-manager-control-plane-20260811-003-20260811-012505",
failureClass: "deterministic-runtime-module-resolution",
missingModule: "/packages/external-provider-contract/src/credential-reference.mjs",
correctiveAction: "runtime-local-contract-adapter+staged-module-import-gate",
sourceAction: "publish-reconciliation-marker-only",
runtimeAction: "read-only-acceptance",
preservedServices: [
"device-control-core",
"device-gateway",
"device-postgres",
"device-backhaul-target",
],
absentService: "device-manager",
databaseVolume: "nodedc-device-plane-postgres-data",
publicIngress: "disabled",
commandTransport: "disabled",
gelios: "untouched",
rollback: "marker-only-runtime-unchanged",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_manager_v2_reconciliation_descriptor_mismatch");
}
await mkdir(dirname(join(payload, entry)), { recursive: true });
await cp(resolve(sourceRoot, entry), join(payload, entry), { force: true });
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${entry}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{ encoding: "utf8", maxBuffer: 16 * 1024 * 1024 },
);
if (tar.status !== 0) throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
const sha256 = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
component: "device-plane",
artifact: target,
sha256,
entries: [entry],
build: [],
services: [],
transition: "failed-v2-control-plane-baseline-adoption",
runtimeAction: "read-only-acceptance",
sourceAction: "publish-reconciliation-marker-only",
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix()); info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,201 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const failedFoundationCompose = resolve(
scriptDir,
"fixtures/device-plane-foundation-internal-only-v1.yml",
);
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [patchId = "device-plane-foundation-20260725-001", ...extra] =
process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error("usage: build-device-plane-artifact.mjs [patch-id]");
}
const files = [
".dockerignore",
"package.json",
"package-lock.json",
"docker-compose.device-plane.yml",
"packages/device-protocol-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway",
];
const ignoredBasenames = new Set([
".DS_Store",
".git",
"node_modules",
]);
const ignoredDirectoryNames = new Set(["test"]);
const stage = await mkdtemp(join(tmpdir(), "nodedc-device-plane-artifact-"));
const payload = join(stage, "payload");
const target = join(artifactDir, `nodedc-device-plane-${patchId}.tgz`);
await assertSourceBoundary();
try {
await mkdir(payload, { recursive: true });
for (const sourceRelative of files) {
const source = sourceRelative === "docker-compose.device-plane.yml"
? failedFoundationCompose
: resolve(sourceRoot, sourceRelative);
await copySafe(
source,
join(payload, sourceRelative),
);
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
},
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const digest = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256: digest,
component: "device-plane",
entries: files,
services: ["device-control-core", "device-gateway"],
preserved: [
"device-postgres",
"nodedc-device-plane-postgres-data",
"Gelios",
],
excluded: [
".env*",
"node_modules",
"**/test",
"docs",
"runtime",
"secrets",
],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertSourceBoundary() {
const composeSource = failedFoundationCompose;
const compose = await readFile(
composeSource,
"utf8",
);
for (const fragment of [
'DEVICE_DISCOVERY_INGEST_ENABLED: "false"',
'DEVICE_GATEWAY_LISTEN_ENABLED: "false"',
'"127.0.0.1:18120:18120"',
'"127.0.0.1:18121:18121"',
"source: /volume1/docker/nodedc-device-plane/secrets/postgres-password",
"create_host_path: false",
"name: nodedc-device-plane-postgres-data",
"pull_policy: never",
]) {
if (!compose.includes(fragment)) {
throw new Error(`device_plane_compose_boundary_missing:${fragment}`);
}
}
for (const forbidden of [
"9921:9921",
"0.0.0.0:9921",
"DEVICE_DISCOVERY_INGEST_ENABLED: \"true\"",
"DEVICE_GATEWAY_LISTEN_ENABLED: \"true\"",
"POSTGRES_PASSWORD:",
]) {
if (compose.includes(forbidden)) {
throw new Error(`device_plane_compose_boundary_violation:${forbidden}`);
}
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
async function copySafe(source, destination) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, source)}`,
);
}
if (sourceStat.isFile()) {
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, { force: true, verbatimSymlinks: true });
return;
}
if (!sourceStat.isDirectory()) {
throw new Error(`source_type_rejected:${source}`);
}
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if (
ignoredBasenames.has(entry.name)
|| entry.name.startsWith(".env")
|| (entry.isDirectory() && ignoredDirectoryNames.has(entry.name))
) {
continue;
}
const childSource = join(source, entry.name);
const childDestination = join(destination, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, childSource)}`,
);
}
await copySafe(childSource, childDestination);
}
}
@@ -1,271 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-plane-b2-discovery-loopback-20260726-002",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-plane-b2-discovery-ingress-artifact.mjs "
+ "[patch-id]",
);
}
const files = [
".dockerignore",
"package.json",
"package-lock.json",
"docker-compose.device-plane.yml",
"packages/device-protocol-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway",
"services/device-edge-relay/package.json",
"deployment/device-plane-b2-discovery-ingress-v1.json",
];
const ignoredBasenames = new Set([".DS_Store", ".git", "node_modules"]);
const ignoredDirectoryNames = new Set(["test"]);
const stage = await mkdtemp(
join(tmpdir(), "nodedc-device-plane-b2-discovery-ingress-"),
);
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
await assertBoundary();
try {
await mkdir(payload, { recursive: true });
for (const sourceRelative of files) {
await copySafe(
resolve(sourceRoot, sourceRelative),
join(payload, sourceRelative),
);
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(
join(stage, "files.txt"),
`${files.join("\n")}\n`,
"utf8",
);
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
},
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const digest = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256: digest,
component: "device-plane",
transition: "verified-b2-loopback-discovery-only",
entries: files,
services: ["device-control-core", "device-gateway"],
preservedRuntime: [
"device-postgres",
"nodedc-device-plane-postgres-data",
"Gelios",
],
ingress: {
transport: "tcp",
published: "127.0.0.1:9921:9921",
mode: "loopback-discovery-only",
framing: "verified-read-only",
lifecycle: "quarantine",
commandTransport: "disabled",
},
rollback: "restore-source-and-predecessor-stateless-runtime",
excluded: [
".env*",
"node_modules",
"**/test",
"docs",
"runtime",
"secrets",
],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertBoundary() {
const compose = await readFile(
resolve(sourceRoot, "docker-compose.device-plane.yml"),
"utf8",
);
for (const fragment of [
'DEVICE_DISCOVERY_INGEST_ENABLED: "true"',
'DEVICE_GATEWAY_LISTEN_ENABLED: "true"',
'DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "false"',
"DEVICE_GATEWAY_CORE_URL: http://device-control-core:18120",
"DEVICE_GATEWAY_CORE_TOKEN_FILE: /run/nodedc-secrets/gateway-core-token",
'"127.0.0.1:18120:18120"',
'"127.0.0.1:18121:18121"',
'"127.0.0.1:9921:9921"',
"name: nodedc-device-plane-private",
"internal: true",
"name: nodedc-device-plane-control",
"internal: false",
'com.docker.network.bridge.enable_ip_masquerade: "false"',
"name: nodedc-device-plane-postgres-data",
"pull_policy: never",
]) {
if (!compose.includes(fragment)) {
throw new Error(
`device_plane_b2_ingress_boundary_missing:${fragment}`,
);
}
}
for (const forbidden of [
"POSTGRES_PASSWORD:",
"DEVICE_GATEWAY_CORE_TOKEN:",
"DEVICE_IDENTIFIER_PEPPER:",
"DEVICE_GATEWAY_COMMAND",
"9921:9921/udp",
]) {
if (compose.includes(forbidden)) {
throw new Error(
`device_plane_b2_ingress_boundary_violation:${forbidden}`,
);
}
}
const descriptor = JSON.parse(await readFile(
resolve(
sourceRoot,
"deployment/device-plane-b2-discovery-ingress-v1.json",
),
"utf8",
));
const expected = {
schemaVersion: "nodedc.device-plane.b2-discovery-ingress.v1",
mode: "verified-b2-loopback-discovery-only",
predecessorPatchId:
"device-plane-foundation-network-publication-20260725-003",
predecessorArtifactSha256:
"6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf",
sourceAction: "publish-verified-b2-loopback-discovery-source",
runtimeAction: "build-and-recreate-stateless-services",
selectedServices: ["device-control-core", "device-gateway"],
preservedServices: ["device-postgres"],
privateNetwork: "nodedc-device-plane-private",
controlNetwork: "nodedc-device-plane-control",
publishedPorts: [
"127.0.0.1:18120:18120",
"127.0.0.1:18121:18121",
"127.0.0.1:9921:9921/tcp",
],
protocolProfile: "arusnavi.b2.internal.v1",
framingSpecification:
"arusnavi.internal.protocol-sheet.gid-12.v1",
identityTrust: "claimed-not-ownership-proof",
discoveryLifecycle: "quarantine",
commandTransport: "disabled",
gelios: "untouched",
databaseVolume: "nodedc-device-plane-postgres-data",
rollback: "restore-source-and-predecessor-stateless-runtime",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_plane_b2_ingress_descriptor_mismatch");
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
async function copySafe(source, destination) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, source)}`,
);
}
if (sourceStat.isFile()) {
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, {
force: true,
verbatimSymlinks: true,
});
return;
}
if (!sourceStat.isDirectory()) {
throw new Error(`source_type_rejected:${source}`);
}
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if (
ignoredBasenames.has(entry.name)
|| entry.name.startsWith(".env")
|| (
entry.isDirectory()
&& ignoredDirectoryNames.has(entry.name)
)
) {
continue;
}
const childSource = join(source, entry.name);
const childDestination = join(destination, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, childSource)}`,
);
}
await copySafe(childSource, childDestination);
}
}
@@ -1,175 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const descriptorRelative =
"deployment/device-plane-b2-discovery-loopback-recovery-v1.json";
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-plane-b2-discovery-loopback-recovery-20260802-004",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: "
+ "build-device-plane-b2-discovery-loopback-recovery-artifact.mjs "
+ "[patch-id]",
);
}
const files = [descriptorRelative];
const stage = await mkdtemp(
join(tmpdir(), "nodedc-device-plane-b2-loopback-recovery-"),
);
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
await assertRecoveryDescriptor();
try {
const source = resolve(sourceRoot, descriptorRelative);
const sourceStat = await lstat(source);
if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
throw new Error("device_plane_b2_recovery_descriptor_unsafe");
}
await mkdir(dirname(join(payload, descriptorRelative)), {
recursive: true,
});
await cp(source, join(payload, descriptorRelative), {
force: true,
verbatimSymlinks: true,
});
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(
join(stage, "files.txt"),
`${files.join("\n")}\n`,
"utf8",
);
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
},
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const digest = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256: digest,
component: "device-plane",
transition: "failed-b2-loopback-build-reconciliation",
entries: files,
build: [],
services: [],
preservedRuntime: [
"device-control-core",
"device-gateway",
"device-postgres",
"nodedc-device-plane-postgres-data",
"Gelios",
],
sourceAction: "publish-reconciliation-marker-only",
runtimeAction: "read-only-acceptance",
ingress: "disabled:127.0.0.1:9921/tcp:closed",
rollback: "marker-only-runtime-unchanged",
excluded: [
"application-source",
"compose",
"Dockerfile",
"secrets",
"runtime",
"database",
"Gelios",
],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertRecoveryDescriptor() {
const descriptor = JSON.parse(await readFile(
resolve(sourceRoot, descriptorRelative),
"utf8",
));
const expected = {
schemaVersion: "nodedc.device-plane.b2-discovery-loopback-recovery.v1",
mode: "failed-b2-loopback-build-reconciliation",
failedPatchId: "device-plane-b2-discovery-loopback-20260801-003",
failedArtifactSha256:
"7273c5bf67fe6bc1f1da66ad726009240d39ee3aee58201b96c23d6f707a3d84",
failedBackupId:
"device-plane-device-plane-b2-discovery-loopback-20260801-003-20260802-154311",
sourceAction: "publish-reconciliation-marker-only",
runtimeAction: "read-only-acceptance",
preservedServices: [
"device-control-core",
"device-gateway",
"device-postgres",
],
expectedLoopbackPorts: [
"127.0.0.1:18120:18120",
"127.0.0.1:18121:18121",
],
closedPort: "127.0.0.1:9921/tcp",
databaseVolume: "nodedc-device-plane-postgres-data",
commandTransport: "disabled",
gelios: "untouched",
rollback: "marker-only-runtime-unchanged",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_plane_b2_recovery_descriptor_mismatch");
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,256 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-plane-backhaul-target-tailnet-serve-20260804-002",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-plane-backhaul-target-artifact.mjs [patch-id]",
);
}
const files = [
"docker-compose.device-plane.backhaul-target.yml",
"services/device-backhaul-target",
"deployment/device-plane-backhaul-target-tailnet-serve-v1.json",
];
const ignoredBasenames = new Set([".DS_Store", ".git", "node_modules"]);
const stage = await mkdtemp(
join(tmpdir(), "nodedc-device-plane-backhaul-target-"),
);
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
await assertBoundary();
try {
await mkdir(payload, { recursive: true });
for (const sourceRelative of files) {
await copySafe(
resolve(sourceRoot, sourceRelative),
join(payload, sourceRelative),
);
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{ encoding: "utf8", maxBuffer: 128 * 1024 * 1024 },
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const digest = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256: digest,
component: "device-plane",
transition: "failed-backhaul-target-to-loopback-tailnet-serve",
entries: files,
services: ["device-backhaul-target"],
preservedRuntime: [
"device-control-core",
"device-gateway",
"device-postgres",
"nodedc-device-plane-postgres-data",
"Gelios",
],
ingress: {
loopbackListen: "127.0.0.1:2222/tcp",
tailnetListen: "100.109.216.21:2222/tcp",
transport: "tailscale-serve-private-ssh",
serveTarget: "tcp://127.0.0.1:2222",
permittedTarget: "127.0.0.1:9921",
dockerPortPublication: "disabled",
routerNatFirewall: "unchanged",
edgePublicIngress: "disabled",
funnel: "disabled",
commandTransport: "disabled",
},
runtimeTrust: "runner-managed-not-in-artifact",
rollback: "remove-tailnet-serve-target-and-restore-source",
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertBoundary() {
const compose = await readFile(
resolve(sourceRoot, "docker-compose.device-plane.backhaul-target.yml"),
"utf8",
);
for (const fragment of [
"device-backhaul-target:",
"image: nodedc/device-backhaul-target:local",
"network_mode: host",
'"127.0.0.1", "2222"',
"/secrets/backhaul-target/ssh_host_ed25519_key",
"/secrets/backhaul-target/authorized_keys",
"no-new-privileges:true",
]) {
if (!compose.includes(fragment)) {
throw new Error(`device_plane_backhaul_boundary_missing:${fragment}`);
}
}
for (const forbidden of [
"PasswordAuthentication yes",
"0.0.0.0:2222",
"9921:9921/udp",
"DEVICE_GATEWAY_COMMAND",
]) {
if (compose.includes(forbidden)) {
throw new Error(`device_plane_backhaul_boundary_violation:${forbidden}`);
}
}
const sshd = await readFile(
resolve(sourceRoot, "services/device-backhaul-target/sshd_config"),
"utf8",
);
for (const fragment of [
"ListenAddress 127.0.0.1",
"PasswordAuthentication no",
"KbdInteractiveAuthentication no",
"AllowTcpForwarding local",
"PermitOpen 127.0.0.1:9921",
"GatewayPorts no",
"PermitTunnel no",
"AllowAgentForwarding no",
"PermitTTY no",
"ForceCommand /bin/false",
]) {
if (!sshd.includes(fragment)) {
throw new Error(`device_plane_backhaul_sshd_boundary_missing:${fragment}`);
}
}
const descriptor = JSON.parse(await readFile(
resolve(
sourceRoot,
"deployment/device-plane-backhaul-target-tailnet-serve-v1.json",
),
"utf8",
));
const expected = {
schemaVersion: "nodedc.device-plane.backhaul-target-tailnet-serve.v1",
mode: "failed-backhaul-target-to-loopback-tailnet-serve",
failedPatchId: "device-plane-backhaul-target-20260803-001",
failedArtifactSha256:
"ed0bda4110a756c32be68990e2e0f647409d5a77eec7e26c18502bafbdc1bb76",
failedBackupId:
"device-plane-device-plane-backhaul-target-20260803-001-20260804-035519",
predecessorPatchId:
"device-plane-b2-discovery-loopback-20260803-006",
predecessorArtifactSha256:
"25f9e9e55e283e9b7bb5e128ff14a244f848b1c063acca9724a23206131c9adf",
sourceAction: "publish-loopback-backhaul-target-source",
runtimeAction: "build-create-target-and-register-private-tailnet-serve",
composeOverlay: "docker-compose.device-plane.backhaul-target.yml",
selectedServices: ["device-backhaul-target"],
preservedServices: [
"device-control-core",
"device-gateway",
"device-postgres",
],
loopbackListenAddress: "127.0.0.1",
listenPort: 2222,
tailnetAddress: "100.109.216.21",
tailnetExposure: "tailscale-serve-private",
tailscaleServeTarget: "tcp://127.0.0.1:2222",
permittedTarget: "127.0.0.1:9921",
networkMode: "host",
dockerPortPublication: "disabled",
routerNatFirewall: "unchanged",
edgePublicIngress: "disabled",
funnel: "disabled",
commandTransport: "disabled",
gelios: "untouched",
databaseVolume: "nodedc-device-plane-postgres-data",
runtimeTrust: "runner-managed",
rollback: "remove-tailnet-serve-target-and-restore-source",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_plane_backhaul_descriptor_mismatch");
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
async function copySafe(source, destination) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) {
throw new Error(`source_symlink_rejected:${relative(sourceRoot, source)}`);
}
if (sourceStat.isFile()) {
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, { force: true, verbatimSymlinks: true });
return;
}
if (!sourceStat.isDirectory()) {
throw new Error(`source_type_rejected:${source}`);
}
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if (
ignoredBasenames.has(entry.name)
|| entry.name.startsWith(".env")
|| entry.name.endsWith("~")
) {
continue;
}
await copySafe(join(source, entry.name), join(destination, entry.name));
}
}
@@ -1,160 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const descriptorRelative =
"deployment/device-plane-backhaul-vps-enrollment-v1.json";
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-plane-backhaul-vps-enrollment-20260806-001",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-plane-backhaul-vps-enrollment-artifact.mjs [patch-id]",
);
}
if (process.env.NODEDC_ALLOW_SUPERSEDED_TRANSPORT !== "test-only") {
throw new Error("vps_initiated_transport_frozen:ADR-0001");
}
const files = [descriptorRelative];
const stage = await mkdtemp(
join(tmpdir(), "nodedc-device-plane-vps-enrollment-"),
);
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
await assertDescriptor();
try {
const source = resolve(sourceRoot, descriptorRelative);
const sourceStat = await lstat(source);
if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
throw new Error("device_plane_vps_enrollment_descriptor_unsafe");
}
await mkdir(dirname(join(payload, descriptorRelative)), {
recursive: true,
});
await cp(source, join(payload, descriptorRelative), {
force: true,
verbatimSymlinks: true,
});
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{ encoding: "utf8", maxBuffer: 32 * 1024 * 1024 },
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const bytes = await readFile(target);
const sha256 = createHash("sha256").update(bytes).digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256,
component: "device-plane",
transition: "rotate-backhaul-client-mini-to-vps",
entries: files,
build: [],
services: ["device-backhaul-target"],
preservedRuntime: [
"device-control-core",
"device-gateway",
"device-postgres",
"nodedc-device-plane-postgres-data",
"Tailscale Serve",
"Gelios",
],
publicIngress: "disabled",
commandTransport: "disabled",
runtimeKeyMaterial: "external-enrollment-only",
rollback: "restore-previous-authorized-key-and-recreate-target",
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertDescriptor() {
const descriptor = JSON.parse(await readFile(
resolve(sourceRoot, descriptorRelative),
"utf8",
));
if (
descriptor.schemaVersion
!== "nodedc.device-plane.backhaul-vps-enrollment.v1"
|| descriptor.mode !== "rotate-backhaul-client-mini-to-vps"
|| descriptor.predecessorPatchId
!== "device-plane-backhaul-target-tailnet-serve-20260804-002"
|| descriptor.predecessorArtifactSha256
!== "219408705dd4d80a962ed00eeb53a69df0b9ab6458443734d5c9cd1d1f795eba"
|| descriptor.nextKeyFingerprint
!== "SHA256:HHTiDYiCRxSiKjBLCip6JMSzGfLGrDz5g8SIkosJcVw"
|| descriptor.commandTransport !== "disabled"
|| descriptor.gelios !== "untouched"
|| descriptor.edgePublicIngress !== "disabled"
) {
throw new Error("device_plane_vps_enrollment_descriptor_mismatch");
}
const text = JSON.stringify(descriptor);
for (const forbidden of [
"PRIVATE KEY",
"authorized_keys",
"TS_AUTHKEY",
"password",
]) {
if (text.includes(forbidden)) {
throw new Error(`device_plane_vps_enrollment_boundary:${forbidden}`);
}
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,274 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const networkPublicationCompose = resolve(
scriptDir,
"fixtures/device-plane-foundation-network-publication-v1.yml",
);
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-plane-foundation-network-publication-20260725-003",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-plane-foundation-network-publication-artifact.mjs "
+ "[patch-id]",
);
}
const files = [
".dockerignore",
"package.json",
"package-lock.json",
"docker-compose.device-plane.yml",
"packages/device-protocol-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway",
"deployment/device-plane-foundation-network-publication-v1.json",
];
const ignoredBasenames = new Set([".DS_Store", ".git", "node_modules"]);
const ignoredDirectoryNames = new Set(["test"]);
const stage = await mkdtemp(
join(tmpdir(), "nodedc-device-plane-network-publication-"),
);
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
await assertBoundary();
try {
await mkdir(payload, { recursive: true });
for (const sourceRelative of files) {
const source = sourceRelative === "docker-compose.device-plane.yml"
? networkPublicationCompose
: resolve(sourceRoot, sourceRelative);
await copySafe(
source,
join(payload, sourceRelative),
);
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(
join(stage, "files.txt"),
`${files.join("\n")}\n`,
"utf8",
);
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
},
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const digest = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256: digest,
component: "device-plane",
transition: "failed-foundation-network-publication-correction",
entries: files,
build: [],
services: ["device-control-core", "device-gateway"],
preservedRuntime: [
"device-postgres",
"nodedc-device-plane-postgres-data",
"Gelios",
],
networkChange: {
private: "preserved:internal",
control: "create:non-internal:no-masquerade",
published: [
"127.0.0.1:18120:18120",
"127.0.0.1:18121:18121",
],
disabled: ["9921", "public-ingress", "command-transport"],
},
rollback:
"restore-partial-source-and-internal-only-stateless-runtime",
excluded: [
".env*",
"node_modules",
"**/test",
"docs",
"runtime",
"secrets",
],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertBoundary() {
const compose = await readFile(
networkPublicationCompose,
"utf8",
);
for (const fragment of [
'DEVICE_DISCOVERY_INGEST_ENABLED: "false"',
'DEVICE_GATEWAY_LISTEN_ENABLED: "false"',
'"127.0.0.1:18120:18120"',
'"127.0.0.1:18121:18121"',
"name: nodedc-device-plane-private",
"internal: true",
"name: nodedc-device-plane-control",
"internal: false",
'com.docker.network.bridge.enable_ip_masquerade: "false"',
"name: nodedc-device-plane-postgres-data",
"pull_policy: never",
]) {
if (!compose.includes(fragment)) {
throw new Error(
`device_plane_network_publication_boundary_missing:${fragment}`,
);
}
}
for (const forbidden of [
"9921:9921",
"0.0.0.0:9921",
'DEVICE_DISCOVERY_INGEST_ENABLED: "true"',
'DEVICE_GATEWAY_LISTEN_ENABLED: "true"',
"POSTGRES_PASSWORD:",
]) {
if (compose.includes(forbidden)) {
throw new Error(
`device_plane_network_publication_boundary_violation:${forbidden}`,
);
}
}
const descriptor = JSON.parse(await readFile(
resolve(
sourceRoot,
"deployment/device-plane-foundation-network-publication-v1.json",
),
"utf8",
));
const expected = {
schemaVersion:
"nodedc.device-plane.foundation-network-publication.v1",
mode: "failed-foundation-network-publication-correction",
failedRecoveryPatchId:
"device-plane-foundation-recovery-20260725-002",
failedRecoveryArtifactSha256:
"9183cc385142584bfd12510bb0a3e6b833b2fd26607436f2486a564c628ea1bf",
failedRecoveryBackupId:
"device-plane-device-plane-foundation-recovery-20260725-002-20260725-232447",
sourceAction: "publish-network-corrected-foundation-source",
runtimeAction: "recreate-stateless-services-no-build",
selectedServices: ["device-control-core", "device-gateway"],
preservedServices: ["device-postgres"],
privateNetwork: "nodedc-device-plane-private",
controlNetwork: "nodedc-device-plane-control",
publishedLoopbackPorts: [
"127.0.0.1:18120:18120",
"127.0.0.1:18121:18121",
],
databaseVolume: "nodedc-device-plane-postgres-data",
rollback:
"restore-partial-source-and-internal-only-stateless-runtime",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error(
"device_plane_network_publication_descriptor_mismatch",
);
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
async function copySafe(source, destination) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, source)}`,
);
}
if (sourceStat.isFile()) {
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, {
force: true,
verbatimSymlinks: true,
});
return;
}
if (!sourceStat.isDirectory()) {
throw new Error(`source_type_rejected:${source}`);
}
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if (
ignoredBasenames.has(entry.name)
|| entry.name.startsWith(".env")
|| (
entry.isDirectory()
&& ignoredDirectoryNames.has(entry.name)
)
) {
continue;
}
const childSource = join(source, entry.name);
const childDestination = join(destination, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, childSource)}`,
);
}
await copySafe(childSource, childDestination);
}
}
@@ -1,255 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const predecessorCompose = resolve(
scriptDir,
"fixtures/device-plane-foundation-internal-only-v1.yml",
);
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-plane-foundation-recovery-20260725-002",
...extra
] = process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-plane-foundation-recovery-artifact.mjs [patch-id]",
);
}
const files = [
".dockerignore",
"package.json",
"package-lock.json",
"docker-compose.device-plane.yml",
"packages/device-protocol-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway",
"deployment/device-plane-foundation-recovery-v1.json",
];
const ignoredBasenames = new Set([
".DS_Store",
".git",
"node_modules",
]);
const ignoredDirectoryNames = new Set(["test"]);
const stage = await mkdtemp(
join(tmpdir(), "nodedc-device-plane-foundation-recovery-"),
);
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
await assertRecoveryBoundary();
try {
await mkdir(payload, { recursive: true });
for (const sourceRelative of files) {
const source = sourceRelative === "docker-compose.device-plane.yml"
? predecessorCompose
: resolve(sourceRoot, sourceRelative);
await copySafe(
source,
join(payload, sourceRelative),
);
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(
join(stage, "files.txt"),
`${files.join("\n")}\n`,
"utf8",
);
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
},
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const digest = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256: digest,
component: "device-plane",
transition: "failed-foundation-live-runtime-adoption",
entries: files,
build: [],
services: [],
preservedRuntime: [
"device-control-core",
"device-gateway",
"device-postgres",
"nodedc-device-plane-postgres-data",
],
sourceAction: "publish-exact-failed-artifact-source",
runtimeAction: "read-only-acceptance",
excluded: [
".env*",
"node_modules",
"**/test",
"docs",
"runtime",
"secrets",
],
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertRecoveryBoundary() {
const compose = await readFile(
predecessorCompose,
"utf8",
);
for (const fragment of [
'DEVICE_DISCOVERY_INGEST_ENABLED: "false"',
'DEVICE_GATEWAY_LISTEN_ENABLED: "false"',
'"127.0.0.1:18120:18120"',
'"127.0.0.1:18121:18121"',
"name: nodedc-device-plane-postgres-data",
]) {
if (!compose.includes(fragment)) {
throw new Error(
`device_plane_recovery_compose_boundary_missing:${fragment}`,
);
}
}
for (const forbidden of [
"9921:9921",
"0.0.0.0:9921",
'DEVICE_DISCOVERY_INGEST_ENABLED: "true"',
'DEVICE_GATEWAY_LISTEN_ENABLED: "true"',
"POSTGRES_PASSWORD:",
]) {
if (compose.includes(forbidden)) {
throw new Error(
`device_plane_recovery_compose_boundary_violation:${forbidden}`,
);
}
}
const descriptor = JSON.parse(await readFile(
resolve(
sourceRoot,
"deployment/device-plane-foundation-recovery-v1.json",
),
"utf8",
));
const expected = {
schemaVersion: "nodedc.device-plane.foundation-recovery.v1",
mode: "failed-foundation-live-runtime-adoption",
failedPatchId: "device-plane-foundation-20260725-001",
failedArtifactSha256:
"23d428de547854ad8b1a026671e2f850386ab0be98bde80f016f1e9db631ee24",
backupId:
"device-plane-device-plane-foundation-20260725-001-20260725-223441",
sourceAction: "publish-exact-failed-artifact-source",
runtimeAction: "read-only-acceptance",
preservedServices: [
"device-control-core",
"device-gateway",
"device-postgres",
],
databaseVolume: "nodedc-device-plane-postgres-data",
rollback: "source-only-runtime-unchanged",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_plane_recovery_descriptor_mismatch");
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
async function copySafe(source, destination) {
const sourceStat = await lstat(source);
if (sourceStat.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, source)}`,
);
}
if (sourceStat.isFile()) {
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, {
force: true,
verbatimSymlinks: true,
});
return;
}
if (!sourceStat.isDirectory()) {
throw new Error(`source_type_rejected:${source}`);
}
await mkdir(destination, { recursive: true });
for (const entry of await readdir(source, { withFileTypes: true })) {
if (
ignoredBasenames.has(entry.name)
|| entry.name.startsWith(".env")
|| (
entry.isDirectory()
&& ignoredDirectoryNames.has(entry.name)
)
) {
continue;
}
const childSource = join(source, entry.name);
const childDestination = join(destination, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(
`source_symlink_rejected:${relative(sourceRoot, childSource)}`,
);
}
await copySafe(childSource, childDestination);
}
}
@@ -1,162 +0,0 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const platformRoot = resolve(scriptDir, "../..");
const sourceRoot = resolve(platformRoot, "device-plane");
const artifactDir = resolve(
process.env.NODEDC_DEPLOY_ARTIFACT_DIR
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [patchId = "device-plane-postgres-bootstrap-20260725-001", ...extra] =
process.argv.slice(2);
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) {
throw new Error(
"usage: build-device-plane-postgres-bootstrap-artifact.mjs [patch-id]",
);
}
const files = [
"docker-compose.device-plane.yml",
"deployment/device-postgres-bootstrap-v1.json",
];
const stage = await mkdtemp(
join(tmpdir(), "nodedc-device-plane-postgres-bootstrap-"),
);
const payload = join(stage, "payload");
const target = join(
artifactDir,
`nodedc-device-plane-${patchId}.tgz`,
);
await assertSourceBoundary();
try {
await mkdir(payload, { recursive: true });
for (const relativePath of files) {
const source = resolve(sourceRoot, relativePath);
const sourceStat = await lstat(source);
if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
throw new Error(`bootstrap_source_file_required:${relativePath}`);
}
const destination = join(payload, relativePath);
await mkdir(dirname(destination), { recursive: true });
await cp(source, destination, {
force: true,
verbatimSymlinks: false,
});
}
await writeFile(
join(stage, "manifest.env"),
`id=${patchId}\ncomponent=device-plane\ntype=app-overlay\n`,
"utf8",
);
await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8");
await mkdir(artifactDir, { recursive: true });
const tar = spawnSync(
"python3",
["-c", canonicalTarScript(), target, stage],
{
encoding: "utf8",
maxBuffer: 128 * 1024 * 1024,
},
);
if (tar.status !== 0) {
throw new Error(`tar_failed:${tar.stderr || tar.stdout}`);
}
const sha256 = createHash("sha256")
.update(await readFile(target))
.digest("hex");
console.log(JSON.stringify({
ok: true,
patchId,
artifact: target,
sha256,
component: "device-plane",
entries: files,
services: ["device-postgres"],
mode: "create-if-absent",
rollbackVolumePolicy: "preserve",
}, null, 2));
} finally {
await rm(stage, { recursive: true, force: true });
}
async function assertSourceBoundary() {
const descriptor = JSON.parse(
await readFile(
resolve(
sourceRoot,
"deployment/device-postgres-bootstrap-v1.json",
),
"utf8",
),
);
const expected = {
schemaVersion: "nodedc.device-plane.postgres-bootstrap.v1",
service: "device-postgres",
volume: "nodedc-device-plane-postgres-data",
mode: "create-if-absent",
ordinaryApplicationSelection: "forbidden",
rollbackVolumePolicy: "preserve",
};
if (JSON.stringify(descriptor) !== JSON.stringify(expected)) {
throw new Error("device_plane_postgres_bootstrap_descriptor_mismatch");
}
const compose = await readFile(
resolve(sourceRoot, "docker-compose.device-plane.yml"),
"utf8",
);
for (const required of [
"device-postgres:",
"name: nodedc-device-plane-postgres-data",
"POSTGRES_PASSWORD_FILE: /run/nodedc-secrets/postgres-password",
"create_host_path: false",
]) {
if (!compose.includes(required)) {
throw new Error(`device_plane_postgres_boundary_missing:${required}`);
}
}
const postgresStart = compose.indexOf(" device-postgres:");
const postgresEnd = compose.indexOf("\n device-control-core:");
if (
postgresStart < 0
|| postgresEnd <= postgresStart
|| compose.slice(postgresStart, postgresEnd).includes("\n ports:")
) {
throw new Error("device_plane_postgres_host_port_forbidden");
}
}
function canonicalTarScript() {
return [
"import gzip,io,pathlib,sys,tarfile",
"root=pathlib.Path(sys.argv[2])",
"with open(sys.argv[1],'wb') as out:",
" with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:",
" with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:",
" for top in ('manifest.env','files.txt','payload'):",
" p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])",
" for x in paths:",
" info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())",
" info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644",
" with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)",
].join("\n");
}
@@ -1,118 +0,0 @@
services:
device-postgres:
image: postgres:16-alpine
pull_policy: missing
restart: unless-stopped
environment:
POSTGRES_DB: device_plane
POSTGRES_USER: device_plane
POSTGRES_PASSWORD_FILE: /run/nodedc-secrets/postgres-password
volumes:
- type: volume
source: device-plane-postgres-data
target: /var/lib/postgresql/data
- type: bind
source: /volume1/docker/nodedc-device-plane/secrets/postgres-password
target: /run/nodedc-secrets/postgres-password
read_only: true
bind:
create_host_path: false
networks:
- device-plane-private
healthcheck:
test: ["CMD-SHELL", "pg_isready -U device_plane -d device_plane"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
device-control-core:
image: nodedc/device-control-core:local
pull_policy: never
restart: unless-stopped
user: "1000:1000"
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
environment:
HOST: 0.0.0.0
PORT: "18120"
DEVICE_DATABASE_HOST: device-postgres
DEVICE_DATABASE_PORT: "5432"
DEVICE_DATABASE_NAME: device_plane
DEVICE_DATABASE_USER: device_plane
DEVICE_DATABASE_PASSWORD_FILE: /run/nodedc-secrets/postgres-password
DEVICE_DATABASE_POOL_SIZE: "10"
DEVICE_DISCOVERY_INGEST_ENABLED: "false"
volumes:
- type: bind
source: /volume1/docker/nodedc-device-plane/secrets/postgres-password
target: /run/nodedc-secrets/postgres-password
read_only: true
bind:
create_host_path: false
ports:
- "127.0.0.1:18120:18120"
networks:
- device-plane-private
depends_on:
device-postgres:
condition: service_healthy
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:18120/healthz').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
device-gateway:
image: nodedc/device-gateway:local
pull_policy: never
restart: unless-stopped
user: "1000:1000"
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
environment:
DEVICE_GATEWAY_HEALTH_HOST: 0.0.0.0
DEVICE_GATEWAY_HEALTH_PORT: "18121"
DEVICE_GATEWAY_LISTEN_ENABLED: "false"
DEVICE_GATEWAY_TCP_HOST: 127.0.0.1
DEVICE_GATEWAY_TCP_PORT: "9921"
DEVICE_GATEWAY_MAX_SESSIONS: "100"
DEVICE_GATEWAY_SESSION_TIMEOUT_MS: "10000"
ports:
- "127.0.0.1:18121:18121"
networks:
- device-plane-private
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:18121/healthz').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
networks:
device-plane-private:
name: nodedc-device-plane-private
internal: true
volumes:
device-plane-postgres-data:
name: nodedc-device-plane-postgres-data
@@ -1,126 +0,0 @@
services:
device-postgres:
image: postgres:16-alpine
pull_policy: missing
restart: unless-stopped
environment:
POSTGRES_DB: device_plane
POSTGRES_USER: device_plane
POSTGRES_PASSWORD_FILE: /run/nodedc-secrets/postgres-password
volumes:
- type: volume
source: device-plane-postgres-data
target: /var/lib/postgresql/data
- type: bind
source: /volume1/docker/nodedc-device-plane/secrets/postgres-password
target: /run/nodedc-secrets/postgres-password
read_only: true
bind:
create_host_path: false
networks:
- device-plane-private
healthcheck:
test: ["CMD-SHELL", "pg_isready -U device_plane -d device_plane"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
device-control-core:
image: nodedc/device-control-core:local
pull_policy: never
restart: unless-stopped
user: "1000:1000"
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
environment:
HOST: 0.0.0.0
PORT: "18120"
DEVICE_DATABASE_HOST: device-postgres
DEVICE_DATABASE_PORT: "5432"
DEVICE_DATABASE_NAME: device_plane
DEVICE_DATABASE_USER: device_plane
DEVICE_DATABASE_PASSWORD_FILE: /run/nodedc-secrets/postgres-password
DEVICE_DATABASE_POOL_SIZE: "10"
DEVICE_DISCOVERY_INGEST_ENABLED: "false"
volumes:
- type: bind
source: /volume1/docker/nodedc-device-plane/secrets/postgres-password
target: /run/nodedc-secrets/postgres-password
read_only: true
bind:
create_host_path: false
ports:
- "127.0.0.1:18120:18120"
networks:
- device-plane-private
- device-plane-control
depends_on:
device-postgres:
condition: service_healthy
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:18120/healthz').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
device-gateway:
image: nodedc/device-gateway:local
pull_policy: never
restart: unless-stopped
user: "1000:1000"
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
environment:
DEVICE_GATEWAY_HEALTH_HOST: 0.0.0.0
DEVICE_GATEWAY_HEALTH_PORT: "18121"
DEVICE_GATEWAY_LISTEN_ENABLED: "false"
DEVICE_GATEWAY_TCP_HOST: 127.0.0.1
DEVICE_GATEWAY_TCP_PORT: "9921"
DEVICE_GATEWAY_MAX_SESSIONS: "100"
DEVICE_GATEWAY_SESSION_TIMEOUT_MS: "10000"
ports:
- "127.0.0.1:18121:18121"
networks:
- device-plane-private
- device-plane-control
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:18121/healthz').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
networks:
device-plane-private:
name: nodedc-device-plane-private
internal: true
device-plane-control:
name: nodedc-device-plane-control
driver: bridge
internal: false
driver_opts:
com.docker.network.bridge.enable_ip_masquerade: "false"
volumes:
device-plane-postgres-data:
name: nodedc-device-plane-postgres-data
File diff suppressed because it is too large Load Diff
-868
View File
@@ -1,868 +0,0 @@
#!/usr/bin/env python3
"""Canonical data-only deploy runner for the dedicated NODE.DC Device Edge."""
from __future__ import annotations
import hashlib
import json
import os
import re
import select
import shutil
import socket
import struct
import subprocess
import sys
import tarfile
import tempfile
import time
from datetime import datetime, timezone
from pathlib import Path, PurePosixPath
RUNNER_PATH = Path("/usr/local/sbin/nodedc-edge-deploy")
LIVE_ROOT = Path("/home/ndcsudo/nodedc-device-edge/source")
INBOX_ROOT = Path("/home/ndcsudo/nodedc-device-edge/deploy/inbox")
STATE_ROOT = Path("/var/lib/nodedc-edge-deploy")
APPLIED_ROOT = STATE_ROOT / "applied"
FAILED_ROOT = STATE_ROOT / "failed"
BACKUP_ROOT = STATE_ROOT / "backups"
APPLIED_JOURNAL = STATE_ROOT / "state/applied.jsonl"
FAILED_JOURNAL = STATE_ROOT / "state/failed.jsonl"
DEPLOY_LOCK = STATE_ROOT / "state/deploy.lock"
DOCKER = "/usr/bin/docker"
COMPONENT = "device-edge"
ARTIFACT_TYPE = "app-overlay"
PATCH_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
MAX_ARTIFACT_BYTES = 16 * 1024 * 1024
COMPOSE_PROJECT = "nodedc-device-edge"
BASE_COMPOSE = LIVE_ROOT / "docker-compose.device-edge.yml"
INGRESS_COMPOSE = LIVE_ROOT / "docker-compose.device-edge.ingress.yml"
RELAY_SERVICE = "device-edge-relay"
RELAY_CONTAINER = "nodedc-device-edge-device-edge-relay-1"
BACKHAUL_CONTAINER = "nodedc-device-edge-device-edge-backhaul-1"
TAILNET_CONTAINER = "nodedc-device-edge-tailnet-1"
RELAY_IMAGE = "nodedc/device-edge-relay:local"
INGRESS_PARENT = "enp1s0f0"
INGRESS_SUBNET = "192.168.68.0/22"
INGRESS_GATEWAY = "192.168.68.1"
INGRESS_IPV4 = "192.168.71.253"
INGRESS_PORT = 9921
INGRESS_NETWORK = "nodedc-device-edge-ingress"
INGRESS_IPV4_APPROVED = True
INGRESS_IPV4_APPROVAL = "approved-outside-dhcp-pool"
ENTRIES = (
"docker-compose.device-edge.yml",
"docker-compose.device-edge.ingress.yml",
"services/device-edge-relay/Dockerfile",
"services/device-edge-relay/src",
"deployment/device-edge-admission-gate-v1.json",
)
PAYLOAD_FILE_SHA256 = {
"docker-compose.device-edge.yml":
"666945ffd9512355e610ecd36a9df96936477315150555def93e0243e8ff1e22",
"docker-compose.device-edge.ingress.yml":
"11bedfd7fdea749ca1bdb3b35b9c136c86b330f51a9001f0b38c4618f6f96108",
"services/device-edge-relay/Dockerfile":
"f2f15b7618ac2ab3a1d4dd40041d06d1e9a695edcfc168d8835f9560b4897e70",
"services/device-edge-relay/src/runtime.mjs":
"21e83678980aa61127bf9f3d77982dd485c4aaae208c43818db7bb1cc150b83a",
"services/device-edge-relay/src/server.mjs":
"1b99ec944f1d3fbadded045b159f08624e2829620cec39a97f6b4b8cdcd2be22",
"deployment/device-edge-admission-gate-v1.json":
"e6c1f21ff297b451c42b6746bc2063484874435dfa9f1614410a7cbe84f0ce6f",
}
PREDECESSOR_FILE_SHA256 = {
"docker-compose.device-edge.yml":
"7f13c11d6d4d541964053c0a8cf791e401947d34c42e0f7c26f9f9df26fa00b5",
"docker-compose.device-edge.ingress.yml":
"a4afd04755530fc3b9be64d1a65f0f7282a9539bcc1985bfd880aa904e1c4d8f",
"services/device-edge-relay/Dockerfile":
"f2f15b7618ac2ab3a1d4dd40041d06d1e9a695edcfc168d8835f9560b4897e70",
"services/device-edge-relay/src/runtime.mjs":
"ae8bf8b55603bab266b6fa6e9bc65c9f310a9d94a54db04e2130704e38622ffc",
"services/device-edge-relay/src/server.mjs":
"e4b051b74f934bd37322440e6a013fb6774a76607da08f9cc1e844fc109c83c1",
"deployment/device-edge-ingress-ipvlan-v1.json":
"b9ce402db0c059a76f07a8d4a34297aff2250fd1c0d1aed9970b3a88f4e75d7f",
}
PREDECESSOR_ABSENT = {
"deployment/device-edge-admission-gate-v1.json",
}
class DeployError(RuntimeError):
pass
def die(message: str) -> None:
raise DeployError(message)
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def run(command, *, check=True, capture=True, cwd=None, timeout=180):
result = subprocess.run(
[str(value) for value in command],
check=False,
capture_output=capture,
text=True,
cwd=str(cwd) if cwd else None,
timeout=timeout,
)
if check and result.returncode != 0:
detail = (result.stderr or result.stdout or "command failed").strip()
die(f"command failed: {command[0]}: {detail}")
return result
def docker_json(*args):
result = run([DOCKER, *args])
try:
return json.loads(result.stdout)
except json.JSONDecodeError as error:
die(f"Docker JSON response invalid: {error}")
def expected_descriptor():
return {
"schemaVersion": "nodedc.device-edge.admission-gate.v1",
"mode": "single-nic-ipvlan-b2-relay-only",
"runtimeHost": "ndcmini12",
"component": COMPONENT,
"selectedServices": [RELAY_SERVICE],
"preservedServices": ["device-edge-backhaul", "tailnet"],
"composeProject": COMPOSE_PROJECT,
"composeFiles": [
"docker-compose.device-edge.yml",
"docker-compose.device-edge.ingress.yml",
],
"parentInterface": INGRESS_PARENT,
"lanSubnet": INGRESS_SUBNET,
"lanGateway": INGRESS_GATEWAY,
"ingressIpv4": INGRESS_IPV4,
"ingressIpv4Approval": INGRESS_IPV4_APPROVAL,
"ingressNetwork": INGRESS_NETWORK,
"deviceTcpListen": f"{INGRESS_IPV4}:{INGRESS_PORT}",
"hostPortPublication": "disabled",
"healthPublication": "disabled",
"privateUpstream": "device-edge-backhaul:19921",
"sourceAdmission": "public-ipv4-only",
"maxTrackedSourceAddresses": 2048,
"maxBytesPerDirection": 262144,
"protocolInspection": "gateway-owned",
"identityTrust": "claimed-not-ownership-proof",
"discoveryLifecycle": "quarantine",
"commandTransport": "disabled",
"gelios": "untouched",
"amneziaHostFullTunnel": "preserved",
"routerNatFirewall": "separate-manual-gate",
"rollback": "restore-reviewed-ipvlan-predecessor-without-network-or-router-mutation",
}
def assert_root():
if os.geteuid() != 0:
die("nodedc-edge-deploy must run as root")
def assert_regular_nonsymlink(path: Path, label: str):
if not path.exists() or path.is_symlink() or not path.is_file():
die(f"{label} must be a regular non-symlink file")
def parse_manifest(raw: str):
values = {}
for line in raw.splitlines():
if not line or "=" not in line:
die("artifact manifest is malformed")
key, value = line.split("=", 1)
if key in values or key not in {"id", "component", "type"}:
die("artifact manifest key set is invalid")
values[key] = value
if set(values) != {"id", "component", "type"}:
die("artifact manifest key set is incomplete")
if not PATCH_ID_RE.fullmatch(values["id"]):
die("artifact patch id is invalid")
if values["component"] != COMPONENT or values["type"] != ARTIFACT_TYPE:
die("artifact component/type mismatch")
return values
def safe_tar_member(member: tarfile.TarInfo):
path = PurePosixPath(member.name)
if path.is_absolute() or ".." in path.parts or not path.parts:
die("artifact contains an unsafe path")
if not (member.isfile() or member.isdir()):
die("artifact contains a non-file/non-directory member")
lowered = {part.lower() for part in path.parts}
if any(
part.startswith(".env")
or part in {
".git",
"node_modules",
"secrets",
"keys",
"trust",
"runtime",
"logs",
"uploads",
}
for part in lowered
):
die("artifact contains a forbidden boundary")
if any(part.startswith("._") for part in path.parts):
die("artifact contains AppleDouble metadata")
def load_artifact(artifact: Path, extraction_root: Path):
artifact = artifact.resolve(strict=True)
if artifact.parent != INBOX_ROOT.resolve(strict=True):
die("artifact must be an explicit file in the Device Edge inbox")
assert_regular_nonsymlink(artifact, "artifact")
if artifact.suffix != ".tgz" or artifact.stat().st_size > MAX_ARTIFACT_BYTES:
die("artifact extension/size rejected")
seen = set()
with tarfile.open(artifact, "r:gz") as archive:
for member in archive.getmembers():
safe_tar_member(member)
if member.name in seen:
die("artifact contains duplicate members")
seen.add(member.name)
required = {"manifest.env", "files.txt", "payload"}
if not required.issubset(seen):
die("artifact top-level contract is incomplete")
if any(name.split("/", 1)[0] not in required for name in seen):
die("artifact contains an unexpected top-level member")
archive.extractall(extraction_root, filter="data")
manifest = parse_manifest(
(extraction_root / "manifest.env").read_text(encoding="utf-8")
)
entries = tuple(
line for line in
(extraction_root / "files.txt").read_text(encoding="utf-8").splitlines()
if line
)
if entries != ENTRIES or len(entries) != len(set(entries)):
die("Device Edge artifact file selection mismatch")
payload = extraction_root / "payload"
validate_payload(payload)
return manifest, entries, payload, sha256_file(artifact), artifact
def validate_payload(payload: Path):
actual_files = {
path.relative_to(payload).as_posix(): sha256_file(path)
for path in payload.rglob("*")
if path.is_file()
}
if actual_files != PAYLOAD_FILE_SHA256:
die("Device Edge artifact payload digest set mismatch")
descriptor = json.loads(
(payload / "deployment/device-edge-admission-gate-v1.json")
.read_text(encoding="utf-8")
)
if descriptor != expected_descriptor():
die("Device Edge ingress descriptor mismatch")
def journal_records(path: Path):
if not path.exists():
return []
records = []
for line in path.read_text(encoding="utf-8").splitlines():
if not line:
continue
try:
records.append(json.loads(line))
except json.JSONDecodeError:
die(f"journal is malformed: {path}")
return records
def assert_new_identity(patch_id: str, artifact_sha256: str):
records = journal_records(APPLIED_JOURNAL) + journal_records(FAILED_JOURNAL)
if any(record.get("patch") == patch_id for record in records):
die("Device Edge patch id is terminally recorded")
if any(record.get("sha256") == artifact_sha256 for record in records):
die("Device Edge artifact digest is terminally recorded")
def current_source_state():
state = {}
for relative, expected in PREDECESSOR_FILE_SHA256.items():
path = LIVE_ROOT / relative
assert_regular_nonsymlink(path, f"predecessor {relative}")
state[relative] = sha256_file(path)
if state[relative] != expected:
die(f"Device Edge predecessor drift: {relative}")
for relative in PREDECESSOR_ABSENT:
if (LIVE_ROOT / relative).exists():
die(f"Device Edge predecessor unexpected path: {relative}")
return state
def inspect_container(name: str):
response = docker_json("inspect", name)
if len(response) != 1:
die(f"container inspect cardinality mismatch: {name}")
return response[0]
def container_health(container):
health = container.get("State", {}).get("Health")
return health.get("Status") if health else None
def preserved_runtime_snapshot():
snapshot = {}
for name in (BACKHAUL_CONTAINER, TAILNET_CONTAINER):
container = inspect_container(name)
if container.get("State", {}).get("Status") != "running":
die(f"preserved Device Edge service is not running: {name}")
if name == BACKHAUL_CONTAINER and container_health(container) != "healthy":
die("Device Edge backhaul is not healthy")
snapshot[name] = {
"Id": container.get("Id"),
"Image": container.get("Image"),
"StartedAt": container.get("State", {}).get("StartedAt"),
"RestartCount": container.get("RestartCount"),
"PortBindings": container.get("HostConfig", {}).get("PortBindings"),
}
return snapshot
def assert_preserved_runtime(snapshot):
current_snapshot = preserved_runtime_snapshot()
for name, expected in snapshot.items():
current = current_snapshot[name]
if current != expected:
die(f"preserved Device Edge runtime changed: {name}")
def validate_predecessor_runtime():
relay = inspect_container(RELAY_CONTAINER)
if relay.get("State", {}).get("Status") != "running":
die("Device Edge IPvlan predecessor relay is not running")
if container_health(relay) != "healthy":
die("Device Edge IPvlan predecessor relay is not healthy")
environment = set(relay.get("Config", {}).get("Env") or [])
required = {
"DEVICE_EDGE_RELAY_HEALTH_HOST=127.0.0.1",
"DEVICE_EDGE_RELAY_INGRESS_ENABLED=true",
"DEVICE_EDGE_RELAY_TCP_HOST=0.0.0.0",
"DEVICE_EDGE_RELAY_TCP_PORT=9921",
"DEVICE_EDGE_RELAY_UPSTREAM_HOST=device-edge-backhaul",
"DEVICE_EDGE_RELAY_UPSTREAM_PORT=19921",
}
if not required.issubset(environment):
die("Device Edge IPvlan predecessor environment mismatch")
bindings = relay.get("HostConfig", {}).get("PortBindings") or {}
if bindings not in ({}, None):
die("Device Edge IPvlan predecessor host publication mismatch")
validate_network_runtime(relay)
def validate_host_network_boundary():
if socket.gethostname() != "ndcmini12":
die("Device Edge runtime host mismatch")
route = run(["/usr/sbin/ip", "-4", "route", "show"]).stdout
for line in (
"0.0.0.0/1 dev amn0 metric 1",
"128.0.0.0/1 dev amn0 metric 1",
"default via 192.168.68.1 dev enp1s0f0",
"192.168.68.0/22 dev enp1s0f0",
):
if line not in route:
die(f"Device Edge host route boundary mismatch: {line}")
if run(["/usr/bin/systemctl", "is-active", "AmneziaVPN.service"]).stdout.strip() != "active":
die("AmneziaVPN must remain active for this transition")
interface = run([
"/usr/sbin/ip", "-4", "-brief", "address", "show", "dev", INGRESS_PARENT,
]).stdout
if "192.168.68.54/22" not in interface or "UP" not in interface:
die("Device Edge physical interface boundary mismatch")
def arp_duplicate_detected(target_ip: str, interface: str, attempts=3):
protocol = 0x0806
raw = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(protocol))
try:
raw.bind((interface, 0))
source_mac = raw.getsockname()[4]
target = socket.inet_aton(target_ip)
ethernet = b"\xff" * 6 + source_mac + struct.pack("!H", protocol)
arp = struct.pack(
"!HHBBH6s4s6s4s",
1,
0x0800,
6,
4,
1,
source_mac,
b"\x00" * 4,
b"\x00" * 6,
target,
)
raw.setblocking(False)
for _ in range(attempts):
raw.send(ethernet + arp)
deadline = time.monotonic() + 0.7
while time.monotonic() < deadline:
ready, _, _ = select.select([raw], [], [], deadline - time.monotonic())
if not ready:
break
packet = raw.recv(2048)
if len(packet) < 42 or packet[12:14] != b"\x08\x06":
continue
if packet[28:32] == target and packet[22:28] != source_mac:
return True
return False
finally:
raw.close()
def preflight(manifest, artifact_sha256):
if not INGRESS_IPV4_APPROVED:
die("Device Edge ingress IPv4 approval is not granted")
if INGRESS_IPV4_APPROVAL != "approved-outside-dhcp-pool":
die("Device Edge ingress IPv4 approval contract mismatch")
assert_new_identity(manifest["id"], artifact_sha256)
current_source_state()
validate_predecessor_runtime()
preserved = preserved_runtime_snapshot()
validate_host_network_boundary()
return preserved
def compose_command(*args, baseline=False):
command = [
DOCKER,
"compose",
"--project-name",
COMPOSE_PROJECT,
"--file",
str(BASE_COMPOSE),
]
if not baseline:
command.extend(["--file", str(INGRESS_COMPOSE)])
command.extend(args)
return command
def ensure_state_directories():
for path in (
APPLIED_ROOT,
FAILED_ROOT,
BACKUP_ROOT,
APPLIED_JOURNAL.parent,
):
path.mkdir(parents=True, exist_ok=True, mode=0o750)
os.chmod(path, 0o750)
def acquire_lock():
ensure_state_directories()
try:
descriptor = os.open(
DEPLOY_LOCK,
os.O_WRONLY | os.O_CREAT | os.O_EXCL,
0o600,
)
except FileExistsError:
die("Device Edge deploy lock is present")
os.write(descriptor, f"pid={os.getpid()}\n".encode())
os.close(descriptor)
def release_lock():
try:
DEPLOY_LOCK.unlink()
except FileNotFoundError:
pass
def create_backup(patch_id: str):
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
backup_id = f"{patch_id}-{timestamp}"
backup = BACKUP_ROOT / backup_id
backup.mkdir(parents=False, mode=0o750)
present = []
absent = []
for relative in ENTRIES:
source = LIVE_ROOT / relative
target = backup / "payload" / relative
if not source.exists():
absent.append(relative)
continue
present.append(relative)
target.parent.mkdir(parents=True, exist_ok=True)
if source.is_dir():
shutil.copytree(source, target, symlinks=False)
else:
shutil.copy2(source, target, follow_symlinks=False)
(backup / "backup.json").write_text(json.dumps({
"schemaVersion": "nodedc.device-edge.backup.v1",
"patch": patch_id,
"present": present,
"absent": absent,
}, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return backup_id, backup
def publish_payload(payload: Path):
for relative in ENTRIES:
source = payload / relative
target = LIVE_ROOT / relative
if target.exists():
if target.is_dir():
shutil.rmtree(target)
else:
target.unlink()
target.parent.mkdir(parents=True, exist_ok=True)
if source.is_dir():
shutil.copytree(source, target, symlinks=False)
else:
shutil.copy2(source, target, follow_symlinks=False)
def restore_backup(backup: Path):
descriptor = json.loads((backup / "backup.json").read_text(encoding="utf-8"))
for relative in ENTRIES:
target = LIVE_ROOT / relative
if target.exists():
if target.is_dir():
shutil.rmtree(target)
else:
target.unlink()
for relative in descriptor["present"]:
source = backup / "payload" / relative
target = LIVE_ROOT / relative
target.parent.mkdir(parents=True, exist_ok=True)
if source.is_dir():
shutil.copytree(source, target, symlinks=False)
else:
shutil.copy2(source, target, follow_symlinks=False)
def build_relay():
run([
DOCKER,
"build",
"--no-cache",
"--network=host",
"--file",
"services/device-edge-relay/Dockerfile",
"--tag",
RELAY_IMAGE,
".",
], cwd=LIVE_ROOT, timeout=900, capture=False)
def wait_healthy(name: str, timeout_seconds=150):
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
try:
container = inspect_container(name)
except DeployError:
time.sleep(2)
continue
if (
container.get("State", {}).get("Status") == "running"
and container_health(container) == "healthy"
):
return container
if container.get("State", {}).get("Status") in {"exited", "dead"}:
die(f"container stopped before health acceptance: {name}")
time.sleep(2)
die(f"container health timeout: {name}")
def validate_network_runtime(relay):
networks = relay.get("NetworkSettings", {}).get("Networks") or {}
if set(networks) != {"nodedc-device-edge-private", INGRESS_NETWORK}:
die("Device Edge relay network set mismatch")
if networks[INGRESS_NETWORK].get("IPAddress") != INGRESS_IPV4:
die("Device Edge relay IPvlan address mismatch")
response = docker_json("network", "inspect", INGRESS_NETWORK)
if len(response) != 1:
die("Device Edge ingress network cardinality mismatch")
network = response[0]
if network.get("Driver") != "ipvlan" or network.get("Internal") is True:
die("Device Edge ingress network driver mismatch")
options = network.get("Options") or {}
if options.get("parent") != INGRESS_PARENT or options.get("ipvlan_mode") != "l2":
die("Device Edge ingress network option mismatch")
configs = network.get("IPAM", {}).get("Config") or []
if len(configs) != 1:
die("Device Edge ingress IPAM cardinality mismatch")
if configs[0].get("Subnet") != INGRESS_SUBNET or configs[0].get("Gateway") != INGRESS_GATEWAY:
die("Device Edge ingress IPAM mismatch")
def validate_relay_runtime(preserved):
relay = wait_healthy(RELAY_CONTAINER)
if relay.get("Config", {}).get("User") != "1000:1000":
die("Device Edge relay user mismatch")
host = relay.get("HostConfig", {})
if host.get("ReadonlyRootfs") is not True or host.get("Privileged") is not False:
die("Device Edge relay filesystem/privilege mismatch")
if set(host.get("CapDrop") or []) != {"ALL"}:
die("Device Edge relay capability mismatch")
if host.get("PortBindings") not in ({}, None):
die("Device Edge relay host port publication detected")
environment = set(relay.get("Config", {}).get("Env") or [])
required = {
"DEVICE_EDGE_RELAY_HEALTH_HOST=127.0.0.1",
"DEVICE_EDGE_RELAY_INGRESS_ENABLED=true",
"DEVICE_EDGE_RELAY_TCP_HOST=0.0.0.0",
"DEVICE_EDGE_RELAY_TCP_PORT=9921",
"DEVICE_EDGE_RELAY_UPSTREAM_HOST=device-edge-backhaul",
"DEVICE_EDGE_RELAY_UPSTREAM_PORT=19921",
"DEVICE_EDGE_RELAY_SOURCE_POLICY=public-ipv4-only",
"DEVICE_EDGE_RELAY_MAX_TRACKED_SOURCE_ADDRESSES=2048",
"DEVICE_EDGE_RELAY_MAX_BYTES_PER_DIRECTION=262144",
}
if not required.issubset(environment):
die("Device Edge relay environment mismatch")
validate_network_runtime(relay)
health_result = run([
DOCKER,
"exec",
RELAY_CONTAINER,
"node",
"-e",
"fetch('http://127.0.0.1:18221/healthz').then(async r=>{if(!r.ok)process.exit(2);console.log(await r.text())}).catch(()=>process.exit(3))",
])
try:
health = json.loads(health_result.stdout)
except json.JSONDecodeError:
die("Device Edge relay health JSON invalid")
expected_health = {
"ok": True,
"service": "nodedc-device-edge-relay",
"ingress": "relay-only",
"protocolInspection": "disabled",
"commandTransport": "disabled",
"sourceAdmission": "public-ipv4-only",
}
for key, expected in expected_health.items():
if health.get(key) != expected:
die(f"Device Edge relay health contract mismatch: {key}")
run([
DOCKER,
"exec",
RELAY_CONTAINER,
"node",
"-e",
"const n=require('node:net');const s=n.connect({host:'device-edge-backhaul',port:19921});s.setTimeout(5000);s.once('connect',()=>{s.destroy();process.exit(0)});s.once('timeout',()=>process.exit(2));s.once('error',()=>process.exit(3))",
])
validate_host_network_boundary()
assert_preserved_runtime(preserved)
def write_journal(path: Path, record):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(record, sort_keys=True) + "\n")
def archive_artifact(artifact: Path, destination_root: Path):
destination = destination_root / artifact.name
if destination.exists():
die("Device Edge artifact archive collision")
os.replace(artifact, destination)
return destination
def rollback(backup: Path, preserved):
restore_backup(backup)
run(compose_command(
"up",
"--detach",
"--no-deps",
"--force-recreate",
"--pull",
"never",
RELAY_SERVICE,
), cwd=LIVE_ROOT, timeout=300, capture=False)
wait_healthy(RELAY_CONTAINER)
current_source_state()
validate_predecessor_runtime()
assert_preserved_runtime(preserved)
def plan_artifact(artifact_argument: str):
assert_root()
artifact = Path(artifact_argument)
with tempfile.TemporaryDirectory(prefix="nodedc-edge-plan-") as directory:
manifest, entries, _payload, digest, resolved = load_artifact(
artifact,
Path(directory),
)
preflight(manifest, digest)
print("== plan ==")
print(f"artifact={resolved.name}")
print(f"sha256={digest}")
print(f"id={manifest['id']}")
print(f"component={COMPONENT}")
print(f"type={ARTIFACT_TYPE}")
print(f"payload_root={LIVE_ROOT}")
print(f"compose_root={LIVE_ROOT}")
print(f"compose_project={COMPOSE_PROJECT}")
print("compose_files=docker-compose.device-edge.yml docker-compose.device-edge.ingress.yml")
print("build=/usr/bin/docker build --no-cache --network=host -f services/device-edge-relay/Dockerfile -t nodedc/device-edge-relay:local .")
print("services=device-edge-relay")
print("preserved_services=device-edge-backhaul tailnet")
print(f"device_edge_ingress=ipvlan:l2:{INGRESS_PARENT}:{INGRESS_IPV4}:{INGRESS_PORT}/tcp")
print(f"device_edge_lan={INGRESS_SUBNET}:gateway:{INGRESS_GATEWAY}")
print(f"device_edge_ingress_ipv4_approval={INGRESS_IPV4_APPROVAL}")
print("device_edge_host_port_publication=disabled")
print("device_edge_health_publication=disabled")
print("device_edge_private_upstream=device-edge-backhaul:19921")
print("device_edge_source_admission=public-ipv4-only")
print("device_edge_source_table_limit=2048")
print("device_edge_byte_limit_per_direction=262144")
print("device_edge_command_transport=disabled")
print("device_edge_discovery_lifecycle=quarantine")
print("device_edge_gelios=untouched")
print("device_edge_amnezia=preserved:active:host-full-tunnel")
print("device_edge_router_nat_firewall=unchanged")
print("device_edge_rollback=restore-reviewed-ipvlan-predecessor-no-router-mutation")
print("state=new")
print("== files ==")
for entry in entries:
print(f" {entry}")
def apply_artifact(artifact_argument: str):
assert_root()
artifact = Path(artifact_argument)
acquire_lock()
manifest = None
digest = None
resolved = None
backup_id = None
backup = None
preserved = None
try:
with tempfile.TemporaryDirectory(prefix="nodedc-edge-apply-") as directory:
manifest, _entries, payload, digest, resolved = load_artifact(
artifact,
Path(directory),
)
preserved = preflight(manifest, digest)
backup_id, backup = create_backup(manifest["id"])
publish_payload(payload)
build_relay()
run(compose_command(
"up",
"--detach",
"--no-deps",
"--force-recreate",
"--pull",
"never",
RELAY_SERVICE,
), cwd=LIVE_ROOT, timeout=300, capture=False)
validate_relay_runtime(preserved)
archived = archive_artifact(resolved, APPLIED_ROOT)
write_journal(APPLIED_JOURNAL, {
"status": "ok",
"patch": manifest["id"],
"component": COMPONENT,
"sha256": digest,
"artifact": archived.name,
"backup": backup_id,
"appliedAt": datetime.now(timezone.utc).isoformat(),
})
print(
f"deploy-ok patch={manifest['id']} component={COMPONENT} "
f"backup={backup_id}"
)
except Exception as error:
rollback_status = "not-started"
if backup is not None and preserved is not None:
try:
rollback(backup, preserved)
rollback_status = "ok"
except Exception as rollback_error:
rollback_status = f"failed:{type(rollback_error).__name__}"
if resolved is not None and resolved.exists():
failed_name = (
FAILED_ROOT
/ f"{resolved.name}.{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
)
os.replace(resolved, failed_name)
if manifest is not None and digest is not None:
write_journal(FAILED_JOURNAL, {
"status": "failed",
"patch": manifest["id"],
"component": COMPONENT,
"sha256": digest,
"backup": backup_id,
"rollback": rollback_status,
"error": type(error).__name__,
"failedAt": datetime.now(timezone.utc).isoformat(),
})
if rollback_status.startswith("failed"):
die(f"apply failed and rollback failed: {error}")
die(f"apply failed; automatic rollback={rollback_status}: {error}")
finally:
release_lock()
def verify_install():
assert_root()
path = RUNNER_PATH if RUNNER_PATH.exists() else Path(__file__).resolve()
assert_regular_nonsymlink(path, "runner")
docker_version = run([DOCKER, "version", "--format", "{{.Server.Version}}"]).stdout.strip()
compose_version = run([DOCKER, "compose", "version", "--short"]).stdout.strip()
print(f"path={path}")
print(f"sha256={sha256_file(path)}")
print(f"python={sys.version.split()[0]}")
print(f"docker={docker_version}")
print(f"compose={compose_version}")
print(f"device_edge_ingress_ipv4={INGRESS_IPV4}")
print(f"device_edge_ingress_ipv4_approval={INGRESS_IPV4_APPROVAL}")
print("device_edge_source_admission=public-ipv4-only")
print("verify-install-ok")
def main(arguments):
if len(arguments) == 1 and arguments[0] == "verify-install":
verify_install()
return 0
if len(arguments) == 2 and arguments[0] == "plan":
plan_artifact(arguments[1])
return 0
if len(arguments) == 2 and arguments[0] == "apply":
apply_artifact(arguments[1])
return 0
print(
"usage: nodedc-edge-deploy verify-install | plan <artifact.tgz> | apply <artifact.tgz>",
file=sys.stderr,
)
return 2
if __name__ == "__main__":
try:
raise SystemExit(main(sys.argv[1:]))
except DeployError as error:
print(f"ERROR: {error}", file=sys.stderr)
raise SystemExit(1)
@@ -1,274 +0,0 @@
#!/usr/bin/env python3
import importlib.machinery
import importlib.util
import os
import shutil
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_edge_core_channel_deploy_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DeviceEdgeCoreChannelBootstrapTest(unittest.TestCase):
def test_identity_generation_ignores_synology_global_ca_extensions(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-edge-openssl-",
) as directory:
root = Path(directory)
malicious = root / "synology-openssl.cnf"
malicious.write_text(
"""[ req ]
prompt = no
distinguished_name = dn
x509_extensions = v3_ca
[ dn ]
CN = synology-global-default
[ v3_ca ]
basicConstraints = critical,CA:TRUE
keyUsage = critical,keyCertSign,cRLSign
""",
encoding="ascii",
)
private_key = root / "core-private-key.pem"
certificate = root / "core-certificate.pem"
with (
mock.patch.dict(
os.environ,
{"OPENSSL_CONF": str(malicious)},
),
mock.patch.object(
RUNNER,
"resolve_openssl_binary",
return_value=Path(shutil.which("openssl")),
),
):
RUNNER.generate_device_edge_channel_core_identity(
private_key,
certificate,
)
self.assertEqual(
RUNNER.validate_device_edge_channel_certificate_extensions(
certificate
),
"exact-clientAuth",
)
text = RUNNER.device_edge_channel_certificate_text(certificate)
self.assertEqual(
text.count("X509v3 Basic Constraints: critical"),
1,
)
self.assertIn("CA:FALSE", text)
self.assertNotIn("CA:TRUE", text)
RUNNER.run_openssl(
[
"verify",
"-purpose",
"sslclient",
"-CAfile",
str(certificate),
str(certificate),
],
"unit Device Edge client certificate",
)
def test_bootstrap_acceptance_is_core_only_and_preserves_manager(self):
entries = RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES
services = ("device-control-core",)
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
) as service_health,
mock.patch.object(RUNNER, "healthcheck_url") as url_health,
mock.patch.object(
RUNNER,
"validate_device_manager_control_plane_runtime",
) as runtime_acceptance,
):
RUNNER.run_healthchecks("device-plane", entries, services)
self.assertEqual(
[call.args for call in service_health.call_args_list],
[
("device-plane", "device-control-core"),
("device-plane", "device-manager"),
("device-plane", "device-gateway"),
("device-plane", "device-postgres"),
],
)
url_health.assert_called_once_with(
RUNNER.component_healthchecks(
"device-plane",
entries,
services,
)[0]
)
runtime_acceptance.assert_called_once_with(
require_edge_channel=True
)
def test_exact_failed_016_recovery_is_required_for_replacement(self):
with (
mock.patch.object(
RUNNER,
"device_edge_channel_invalid_identity_is_exact_recoverable",
return_value=False,
),
):
with self.assertRaisesRegex(
RUNNER.DeployError,
"does not match the exact unexported failed-016",
):
RUNNER.recover_invalid_device_edge_channel_core_identity()
def test_failed_016_recovery_accepts_actual_synology_constraint_shape(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-edge-failed-016-",
) as directory:
root = Path(directory)
private_key = root / "core-private-key.pem"
certificate = root / "core-certificate.pem"
peers = root / "peers"
private_key.write_text("private-placeholder\n", encoding="ascii")
certificate.write_text("certificate-placeholder\n", encoding="ascii")
peers.mkdir()
with (
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE",
private_key,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE",
certificate,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR",
peers,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE",
root / "exported-certificate.pem",
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE",
root / "exported-fingerprint.txt",
),
mock.patch.object(
RUNNER,
"device_edge_channel_certificate_fingerprint",
return_value=(
RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_INVALID_CERTIFICATE_FINGERPRINT
),
),
mock.patch.object(
RUNNER,
"device_edge_channel_certificate_text",
return_value="""
X509v3 Basic Constraints:
CA:TRUE
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Client Authentication
""",
),
mock.patch.object(
RUNNER,
"capture_openssl",
side_effect=[b"same-public-key", b"same-public-key"],
),
):
self.assertTrue(
RUNNER.device_edge_channel_invalid_identity_is_exact_recoverable()
)
def test_failed_016_recovery_rejects_ambiguous_constraint_shape(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-edge-ambiguous-",
) as directory:
root = Path(directory)
private_key = root / "core-private-key.pem"
certificate = root / "core-certificate.pem"
peers = root / "peers"
private_key.write_text("private-placeholder\n", encoding="ascii")
certificate.write_text("certificate-placeholder\n", encoding="ascii")
peers.mkdir()
with (
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE",
private_key,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE",
certificate,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR",
peers,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE",
root / "exported-certificate.pem",
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE",
root / "exported-fingerprint.txt",
),
mock.patch.object(
RUNNER,
"device_edge_channel_certificate_fingerprint",
return_value=(
RUNNER.DEVICE_PLANE_EDGE_CORE_CHANNEL_INVALID_CERTIFICATE_FINGERPRINT
),
),
mock.patch.object(
RUNNER,
"device_edge_channel_certificate_text",
return_value="""
X509v3 Basic Constraints:
CA:TRUE
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Basic Constraints: critical
CA:FALSE
""",
),
):
self.assertFalse(
RUNNER.device_edge_channel_invalid_identity_is_exact_recoverable()
)
if __name__ == "__main__":
unittest.main()
@@ -1,323 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = SCRIPT_DIR / "build-device-edge-ingress-artifact.mjs"
RUNNER_PATH = SCRIPT_DIR / "nodedc-edge-deploy"
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_edge_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DeviceEdgeIngressArtifactTest(unittest.TestCase):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
return subprocess.run(
["node", str(BUILDER), patch_id],
check=False,
capture_output=True,
text=True,
env=environment,
)
def test_builder_is_deterministic_narrow_and_secret_free(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-edge-artifact-",
) as directory:
artifact_dir = Path(directory)
patch_id = "device-edge-ingress-ipvlan-unit-001"
first_result = self.build(artifact_dir, patch_id)
self.assertEqual(first_result.returncode, 0, first_result.stderr)
first = json.loads(first_result.stdout)
first_bytes = Path(first["artifact"]).read_bytes()
second_result = self.build(artifact_dir, patch_id)
self.assertEqual(second_result.returncode, 0, second_result.stderr)
second = json.loads(second_result.stdout)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(first["sha256"], second["sha256"])
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(first["component"], "device-edge")
self.assertEqual(first["entries"], list(RUNNER.ENTRIES))
self.assertEqual(first["services"], ["device-edge-relay"])
self.assertEqual(
first["ingress"]["ipv4Approval"],
"approved-outside-dhcp-pool",
)
with tarfile.open(first["artifact"], "r:gz") as archive:
members = archive.getmembers()
names = {member.name for member in members}
manifest = archive.extractfile("manifest.env").read().decode()
files = archive.extractfile("files.txt").read().decode().splitlines()
payload_bytes = b"\n".join(
archive.extractfile(member).read()
for member in members
if member.isfile()
)
self.assertEqual(
manifest,
f"id={patch_id}\ncomponent=device-edge\ntype=app-overlay\n",
)
self.assertEqual(files, list(RUNNER.ENTRIES))
self.assertIn(
"payload/docker-compose.device-edge.ingress.yml",
names,
)
self.assertNotIn(b"PRIVATE KEY", payload_bytes)
self.assertFalse(any(
"/test/" in name
or "/secrets/" in name
or "/keys/" in name
or "/trust/" in name
or "/node_modules/" in name
or Path(name).name.startswith(".env")
for name in names
))
def test_production_builder_accepts_the_explicitly_approved_address(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-edge-address-gate-",
) as directory:
result = self.build(
Path(directory),
"device-edge-admission-gate-20260804-002",
)
self.assertEqual(result.returncode, 0, result.stderr)
built = json.loads(result.stdout)
self.assertEqual(
built["ingress"]["ipv4Approval"],
"approved-outside-dhcp-pool",
)
self.assertTrue(Path(built["artifact"]).is_file())
def test_runner_loads_exact_artifact_and_enters_runtime_preflight(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-edge-runner-load-",
) as directory:
workspace = Path(directory)
inbox = workspace / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"device-edge-admission-gate-20260804-003",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
extracted = workspace / "extracted"
extracted.mkdir()
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
manifest, entries, payload, digest, resolved = (
RUNNER.load_artifact(artifact, extracted)
)
finally:
RUNNER.INBOX_ROOT = old_inbox
self.assertEqual(manifest["component"], "device-edge")
self.assertEqual(entries, RUNNER.ENTRIES)
self.assertEqual(resolved, artifact.resolve())
self.assertEqual(digest, hashlib.sha256(artifact.read_bytes()).hexdigest())
self.assertEqual(
json.loads(
(payload / "deployment/device-edge-admission-gate-v1.json")
.read_text(encoding="utf-8")
),
RUNNER.expected_descriptor(),
)
preserved = {
RUNNER.BACKHAUL_CONTAINER: {"Id": "backhaul"},
RUNNER.TAILNET_CONTAINER: {"Id": "tailnet"},
}
with patch.object(RUNNER, "assert_new_identity"), patch.object(
RUNNER,
"current_source_state",
), patch.object(RUNNER, "validate_predecessor_runtime"), patch.object(
RUNNER,
"preserved_runtime_snapshot",
return_value=preserved,
), patch.object(RUNNER, "validate_host_network_boundary"), patch.object(
RUNNER,
"arp_duplicate_detected",
return_value=False,
), patch.object(
RUNNER,
"run",
return_value=subprocess.CompletedProcess([], 1, "", ""),
):
self.assertEqual(RUNNER.preflight(manifest, digest), preserved)
def test_backup_restore_preserves_exact_predecessor_partition(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-edge-backup-",
) as directory:
workspace = Path(directory)
live = workspace / "live"
backups = workspace / "backups"
live.mkdir()
backups.mkdir()
for relative in RUNNER.ENTRIES:
if relative in RUNNER.PREDECESSOR_ABSENT:
continue
target = live / relative
if relative.endswith("/src"):
target.mkdir(parents=True)
(target / "server.mjs").write_text("old\n", encoding="utf-8")
else:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(f"old:{relative}\n", encoding="utf-8")
old_live = RUNNER.LIVE_ROOT
old_backups = RUNNER.BACKUP_ROOT
RUNNER.LIVE_ROOT = live
RUNNER.BACKUP_ROOT = backups
try:
_backup_id, backup = RUNNER.create_backup("unit-backup")
for relative in RUNNER.ENTRIES:
target = live / relative
if target.exists():
if target.is_dir():
import shutil
shutil.rmtree(target)
else:
target.unlink()
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("candidate\n", encoding="utf-8")
RUNNER.restore_backup(backup)
finally:
RUNNER.LIVE_ROOT = old_live
RUNNER.BACKUP_ROOT = old_backups
for relative in RUNNER.PREDECESSOR_ABSENT:
self.assertFalse((live / relative).exists())
self.assertEqual(
(live / "docker-compose.device-edge.yml").read_text(),
"old:docker-compose.device-edge.yml\n",
)
self.assertEqual(
(live / "services/device-edge-relay/src/server.mjs").read_text(),
"old\n",
)
def test_runner_selection_and_compose_commands_are_exact(self):
self.assertTrue(RUNNER.INGRESS_IPV4_APPROVED)
self.assertEqual(
RUNNER.INGRESS_IPV4_APPROVAL,
"approved-outside-dhcp-pool",
)
self.assertEqual(RUNNER.RELAY_SERVICE, "device-edge-relay")
self.assertEqual(
RUNNER.expected_descriptor()["preservedServices"],
["device-edge-backhaul", "tailnet"],
)
self.assertEqual(
RUNNER.compose_command(
"up",
"--detach",
"--no-deps",
"--force-recreate",
"--pull",
"never",
RUNNER.RELAY_SERVICE,
),
[
RUNNER.DOCKER,
"compose",
"--project-name",
RUNNER.COMPOSE_PROJECT,
"--file",
str(RUNNER.BASE_COMPOSE),
"--file",
str(RUNNER.INGRESS_COMPOSE),
"up",
"--detach",
"--no-deps",
"--force-recreate",
"--pull",
"never",
RUNNER.RELAY_SERVICE,
],
)
self.assertNotIn("down", RUNNER_PATH.read_text(encoding="utf-8"))
def test_preserved_runtime_is_compared_from_one_atomic_snapshot(self):
expected = {
RUNNER.BACKHAUL_CONTAINER: {"Id": "backhaul"},
RUNNER.TAILNET_CONTAINER: {"Id": "tailnet"},
}
with patch.object(
RUNNER,
"preserved_runtime_snapshot",
return_value=expected,
) as snapshot:
RUNNER.assert_preserved_runtime(expected)
snapshot.assert_called_once_with()
def test_network_acceptance_rejects_any_non_ipvlan_substitution(self):
relay = {
"NetworkSettings": {
"Networks": {
"nodedc-device-edge-private": {"IPAddress": "172.18.0.4"},
RUNNER.INGRESS_NETWORK: {"IPAddress": RUNNER.INGRESS_IPV4},
},
},
}
accepted_network = [{
"Driver": "ipvlan",
"Internal": False,
"Options": {
"parent": RUNNER.INGRESS_PARENT,
"ipvlan_mode": "l2",
},
"IPAM": {
"Config": [{
"Subnet": RUNNER.INGRESS_SUBNET,
"Gateway": RUNNER.INGRESS_GATEWAY,
}],
},
}]
with patch.object(RUNNER, "docker_json", return_value=accepted_network):
RUNNER.validate_network_runtime(relay)
rejected_network = json.loads(json.dumps(accepted_network))
rejected_network[0]["Driver"] = "bridge"
with patch.object(RUNNER, "docker_json", return_value=rejected_network):
with self.assertRaisesRegex(
RUNNER.DeployError,
"ingress network driver mismatch",
):
RUNNER.validate_network_runtime(relay)
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -1,765 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = SCRIPT_DIR / "build-device-edge-vps-artifact.mjs"
RUNNER_PATH = SCRIPT_DIR / "nodedc-b2-vps-deploy"
DEFAULT_RUNTIME_CACHE = Path(
os.environ.get("NODEDC_DEVICE_EDGE_VPS_RUNTIME_DIR", "/tmp")
)
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_b2_vps_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DeviceEdgeVpsArtifactTest(unittest.TestCase):
def build(self, artifact_dir, phase, patch_id, runtime_cache=None):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
environment["NODEDC_DEVICE_EDGE_VPS_RUNTIME_DIR"] = str(
runtime_cache or DEFAULT_RUNTIME_CACHE
)
if phase in {"backhaul", "relay"}:
environment["NODEDC_ALLOW_SUPERSEDED_TRANSPORT"] = "test-only"
return subprocess.run(
["node", str(BUILDER), phase, patch_id],
check=False,
capture_output=True,
text=True,
env=environment,
)
def test_superseded_transport_builds_fail_closed_by_default(self):
environment = os.environ.copy()
environment.pop("NODEDC_ALLOW_SUPERSEDED_TRANSPORT", None)
with tempfile.TemporaryDirectory(prefix="nodedc-vps-frozen-") as directory:
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = directory
for phase in ("backhaul", "relay"):
with self.subTest(phase=phase):
result = subprocess.run(
[
"node",
str(BUILDER),
phase,
f"device-edge-vps-{phase}-frozen-001",
],
check=False,
capture_output=True,
text=True,
env=environment,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn(
"vps_initiated_transport_frozen:ADR-0001",
result.stderr,
)
def test_runner_rejects_superseded_transport_before_host_preflight(self):
for phase in ("backhaul", "relay"):
with self.subTest(phase=phase), self.assertRaises(RUNNER.DeployError):
RUNNER.preflight({"phase": phase})
def test_accepted_shared_source_phases_cannot_be_rebuilt(self):
for phase in ("core-channel", "tracker-ingress"):
with self.subTest(phase=phase), tempfile.TemporaryDirectory(
prefix=f"nodedc-vps-frozen-{phase}-"
) as directory:
result = self.build(
Path(directory),
phase,
f"device-edge-vps-{phase}-frozen-001",
)
self.assertNotEqual(result.returncode, 0)
self.assertIn(
f"accepted_vps_phase_rebuild_frozen:{phase}:ADR-0001",
result.stderr,
)
def require_runtime_cache(self):
for name, digest in (
(RUNNER.NODE_ARCHIVE, RUNNER.NODE_ARCHIVE_SHA256),
(RUNNER.TAILSCALE_ARCHIVE, RUNNER.TAILSCALE_ARCHIVE_SHA256),
):
path = DEFAULT_RUNTIME_CACHE / name
self.assertTrue(path.is_file(), f"missing runtime fixture: {path}")
self.assertEqual(hashlib.sha256(path.read_bytes()).hexdigest(), digest)
def test_builders_are_deterministic_narrow_and_secret_free(self):
self.require_runtime_cache()
for phase in (
"command-transport",
):
with self.subTest(phase=phase), tempfile.TemporaryDirectory(
prefix=f"nodedc-vps-{phase}-"
) as directory:
root = Path(directory)
patch_id = f"device-edge-vps-{phase}-unit-001"
first = self.build(root, phase, patch_id)
self.assertEqual(first.returncode, 0, first.stderr)
first_result = json.loads(first.stdout)
first_bytes = Path(first_result["artifact"]).read_bytes()
second = self.build(root, phase, patch_id)
self.assertEqual(second.returncode, 0, second.stderr)
second_result = json.loads(second.stdout)
second_bytes = Path(second_result["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(first_result["sha256"], second_result["sha256"])
self.assertEqual(
first_result["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(first_result["entries"], list(RUNNER.PHASE_ENTRIES[phase]))
with tarfile.open(first_result["artifact"], "r:gz") as archive:
members = archive.getmembers()
names = {member.name for member in members}
payload = b"\n".join(
archive.extractfile(member).read()
for member in members
if member.isfile() and member.size < 2 * 1024 * 1024
)
self.assertIn("manifest.env", names)
self.assertIn("files.txt", names)
self.assertFalse(any(
"/secrets/" in name
or "/keys/" in name
or "/trust/" in name
or "/runtime/" in name
or "/node_modules/" in name
or Path(name).name.startswith(".env")
for name in names
))
self.assertNotIn(b"PRIVATE KEY", payload)
self.assertNotIn(b"TS_AUTHKEY", payload)
def test_foundation_builder_rejects_modified_runtime_archive(self):
self.require_runtime_cache()
with tempfile.TemporaryDirectory(prefix="nodedc-vps-corrupt-") as directory:
cache = Path(directory) / "cache"
artifacts = Path(directory) / "artifacts"
cache.mkdir()
for name in (RUNNER.NODE_ARCHIVE, RUNNER.TAILSCALE_ARCHIVE):
(cache / name).write_bytes((DEFAULT_RUNTIME_CACHE / name).read_bytes())
with (cache / RUNNER.NODE_ARCHIVE).open("ab") as handle:
handle.write(b"corrupt")
result = self.build(
artifacts,
"foundation",
"device-edge-vps-foundation-corrupt-001",
runtime_cache=cache,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("runtime_digest_mismatch", result.stderr)
def test_runner_loads_each_exact_phase(self):
self.require_runtime_cache()
with tempfile.TemporaryDirectory(prefix="nodedc-vps-load-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
for phase in (
"command-transport",
):
result = self.build(
inbox,
phase,
f"device-edge-vps-{phase}-load-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
extraction = Path(directory) / f"extract-{phase}"
extraction.mkdir()
loaded = RUNNER.load_artifact(artifact, extraction)
self.assertEqual(loaded["phase"], phase)
self.assertEqual(loaded["entries"], RUNNER.PHASE_ENTRIES[phase])
self.assertEqual(
loaded["sha256"],
hashlib.sha256(artifact.read_bytes()).hexdigest(),
)
finally:
RUNNER.INBOX_ROOT = old_inbox
def test_plan_is_exact_and_never_claims_dns_or_b2_mutation(self):
self.require_runtime_cache()
with tempfile.TemporaryDirectory(prefix="nodedc-vps-plan-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"foundation",
"device-edge-vps-foundation-plan-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
with patch.object(RUNNER, "assert_root"), patch.object(
RUNNER,
"preflight",
return_value={"predecessor": "unit-predecessor"},
), patch("builtins.print") as output:
RUNNER.plan_artifact(str(artifact))
finally:
RUNNER.INBOX_ROOT = old_inbox
rendered = "\n".join(
" ".join(str(arg) for arg in call.args)
for call in output.call_args_list
)
self.assertIn("phase=foundation", rendered)
self.assertIn("public_b2_ingress=disabled", rendered)
self.assertIn("dns=unchanged", rendered)
self.assertIn("b2_routes=unchanged", rendered)
self.assertIn("command_transport=disabled", rendered)
@unittest.skip("accepted core-channel builder generation is frozen")
def test_core_channel_plan_is_exact_and_keeps_tracker_ingress_closed(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-channel-plan-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"core-channel",
"device-edge-vps-core-channel-plan-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
with patch.object(RUNNER, "assert_root"), patch.object(
RUNNER,
"preflight",
return_value={"predecessor": "accepted-foundation-closed-channel"},
), patch("builtins.print") as output:
RUNNER.plan_artifact(str(artifact))
finally:
RUNNER.INBOX_ROOT = old_inbox
rendered = "\n".join(
" ".join(str(arg) for arg in call.args)
for call in output.call_args_list
)
self.assertIn("phase=core-channel", rendered)
self.assertIn(
"predecessor=accepted-foundation-closed-channel",
rendered,
)
self.assertIn(
"public_core_channel=155.212.211.15:443/tcp:tls13-mtls-h2",
rendered,
)
self.assertIn("tracker_tcp_9921=closed", rendered)
self.assertIn("public_b2_ingress=disabled", rendered)
self.assertIn(
"peer_trust=preprovisioned-pinned-self-signed-core-certificate+fingerprint",
rendered,
)
self.assertIn("command_transport=disabled", rendered)
self.assertIn("gelios=untouched", rendered)
def test_runtime_reconciliation_plan_is_exact_and_opens_no_port(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-reconcile-plan-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"runtime-reconciliation",
"device-edge-vps-runtime-reconciliation-plan-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
with patch.object(RUNNER, "assert_root"), patch.object(
RUNNER,
"preflight",
return_value={
"predecessor": (
"failed-core-channel-001-rollback-runtime-mode-drift"
),
},
), patch("builtins.print") as output:
RUNNER.plan_artifact(str(artifact))
finally:
RUNNER.INBOX_ROOT = old_inbox
rendered = "\n".join(
" ".join(str(arg) for arg in call.args)
for call in output.call_args_list
)
self.assertIn("phase=runtime-reconciliation", rendered)
self.assertIn(
"runtime_reconciliation=exact-known-binaries:0644=>0755",
rendered,
)
self.assertIn("public_core_channel=disabled", rendered)
self.assertIn("tracker_tcp_9921=closed", rendered)
def test_tailscale_retirement_plan_preserves_channel_and_opens_no_tracker_port(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-retirement-plan-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"tailscale-retirement",
"device-edge-vps-tailscale-retirement-plan-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
with patch.object(RUNNER, "assert_root"), patch.object(
RUNNER,
"preflight",
return_value={
"predecessor": "accepted-core-channel-010-with-live-tailnet",
},
), patch("builtins.print") as output:
RUNNER.plan_artifact(str(artifact))
finally:
RUNNER.INBOX_ROOT = old_inbox
rendered = "\n".join(
" ".join(str(arg) for arg in call.args)
for call in output.call_args_list
)
self.assertIn("phase=tailscale-retirement", rendered)
self.assertIn(
"predecessor=accepted-core-channel-010-with-live-tailnet",
rendered,
)
self.assertIn(
"public_core_channel=preserved:155.212.211.15:443/tcp:tls13-mtls-h2",
rendered,
)
self.assertIn(
"tailscale=stop+disable+destroy-local-runtime-state",
rendered,
)
self.assertIn("tailscale_socks_1055=removed", rendered)
self.assertIn("superseded_backhaul_private_key=removed", rendered)
self.assertIn("tracker_tcp_9921=closed", rendered)
self.assertIn("external_tailnet_machine_cleanup=required-after-deploy-ok", rendered)
self.assertIn("command_transport=disabled", rendered)
self.assertIn("gelios=untouched", rendered)
@unittest.skip("accepted tracker-ingress builder generation is frozen")
def test_tracker_ingress_plan_is_single_process_bounded_and_command_free(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-ingress-plan-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"tracker-ingress",
"device-edge-vps-tracker-ingress-plan-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
with patch.object(RUNNER, "assert_root"), patch.object(
RUNNER,
"preflight",
return_value={"predecessor": "accepted-tailscale-retirement-011"},
), patch("builtins.print") as output:
RUNNER.plan_artifact(str(artifact))
finally:
RUNNER.INBOX_ROOT = old_inbox
rendered = "\n".join(
" ".join(str(arg) for arg in call.args)
for call in output.call_args_list
)
self.assertIn("phase=tracker-ingress", rendered)
self.assertIn("predecessor=accepted-tailscale-retirement-011", rendered)
self.assertIn(
"public_b2_ingress=155.212.211.15:9921/tcp:telemetry-only",
rendered,
)
self.assertIn("tracker_adapter=arusnavi-b2", rendered)
self.assertIn("tracker_ack=after-core-durable-acceptance-only", rendered)
self.assertIn("runtime_composition=single-non-root-process", rendered)
self.assertIn("tailscale=preserved:absent", rendered)
self.assertIn("command_transport=disabled", rendered)
self.assertIn("gelios=untouched", rendered)
def test_command_transport_plan_is_typed_single_process_and_bounded(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-command-plan-") as directory:
inbox = Path(directory) / "inbox"
inbox.mkdir()
result = self.build(
inbox,
"command-transport",
"device-edge-vps-command-transport-plan-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
old_inbox = RUNNER.INBOX_ROOT
RUNNER.INBOX_ROOT = inbox
try:
with patch.object(RUNNER, "assert_root"), patch.object(
RUNNER,
"preflight",
return_value={"predecessor": "accepted-tracker-ingress-012"},
), patch("builtins.print") as output:
RUNNER.plan_artifact(str(artifact))
finally:
RUNNER.INBOX_ROOT = old_inbox
rendered = "\n".join(
" ".join(str(arg) for arg in call.args)
for call in output.call_args_list
)
self.assertIn("phase=command-transport", rendered)
self.assertIn("predecessor=accepted-tracker-ingress-012", rendered)
self.assertIn("command_transport=typed-service-ping-v1", rendered)
self.assertIn("command_catalog=allowlisted-adapter-typed-commands-only", rendered)
self.assertIn("runtime_composition=single-non-root-process", rendered)
self.assertIn("public_b2_ingress=155.212.211.15:9921/tcp:bidirectional-session", rendered)
self.assertIn("gelios=untouched-legacy-only", rendered)
def test_publish_payload_preserves_unselected_executable_modes(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-publish-scope-") as directory:
root = Path(directory)
live = root / "live"
payload = root / "payload"
runtime = live / "runtime/node/bin/node"
marker = payload / "deployment/reconciliation.json"
runtime.parent.mkdir(parents=True)
marker.parent.mkdir(parents=True)
runtime.write_bytes(b"runtime-binary")
runtime.chmod(0o755)
marker.write_text("{}\n", encoding="utf-8")
old_live = RUNNER.LIVE_ROOT
RUNNER.LIVE_ROOT = live
try:
with patch.object(RUNNER.os, "chown"):
RUNNER.publish_payload(payload, ("deployment/reconciliation.json",))
finally:
RUNNER.LIVE_ROOT = old_live
self.assertEqual(runtime.stat().st_mode & 0o777, 0o755)
self.assertEqual(
(live / "deployment/reconciliation.json").stat().st_mode & 0o777,
0o644,
)
def test_source_baseline_is_pinned_to_the_exact_accepted_predecessor(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-baseline-") as directory:
journal = Path(directory) / "applied.jsonl"
old_journal = RUNNER.APPLIED_JOURNAL
RUNNER.APPLIED_JOURNAL = journal
try:
journal.write_text(json.dumps({
"patch": "device-edge-vps-foundation-20260806-003",
"phase": "foundation",
"sha256": (
"1be852f144e9f0fea32af70bebd07a2607b6a1818825094bd4c1b4062064716a"
),
"status": "ok",
}) + "\n", encoding="utf-8")
expected = RUNNER.phase_file_sha256("foundation")
self.assertEqual(
expected["vps/config/00-nodedc-b2-vps.conf"],
"cc94d0579f85d0af9746b9ce760bc72980f4a22fb59027e1f5f9c7bf3aaebd64",
)
self.assertEqual(
expected["vps/config/nftables-foundation.conf"],
"4d44f902d8d98d1aa8506fca9d9582e700f6424def2b1d667ab2cd5a5ee84934",
)
self.assertEqual(
expected["deployment/device-edge-vps-foundation-v1.json"],
"317c98b42520fff3238275908482de7aa611b4ee41c6b1f8062abd2730ab072a",
)
journal.write_text(json.dumps({
"patch": "device-edge-vps-foundation-20260806-003",
"phase": "foundation",
"sha256": "0" * 64,
"status": "ok",
}) + "\n", encoding="utf-8")
unexpected = RUNNER.phase_file_sha256("foundation")
self.assertEqual(
unexpected["vps/config/00-nodedc-b2-vps.conf"],
RUNNER.PHASE_FILE_SHA256[
"foundation"
]["vps/config/00-nodedc-b2-vps.conf"],
)
finally:
RUNNER.APPLIED_JOURNAL = old_journal
def test_units_and_firewalls_keep_the_required_boundaries(self):
source_root = SCRIPT_DIR.parent.parent / "device-plane"
foundation = (source_root / "vps/config/nftables-foundation.conf").read_text()
relay = (source_root / "vps/config/nftables-relay.conf").read_text()
channel = (source_root / "vps/config/nftables-core-channel.conf").read_text()
tracker = (source_root / "vps/config/nftables-tracker-ingress.conf").read_text()
sshd = (source_root / "vps/config/00-nodedc-b2-vps.conf").read_text()
backhaul = (source_root / "vps/config/backhaul_ssh_config").read_text()
tailscale_unit = (
source_root / "vps/systemd/nodedc-b2-tailscaled.service"
).read_text()
relay_unit = (source_root / "vps/systemd/nodedc-b2-relay.service").read_text()
backhaul_unit = (
source_root / "vps/systemd/nodedc-b2-backhaul.service"
).read_text()
channel_unit = (
source_root / "vps/systemd/nodedc-device-edge-channel.service"
).read_text()
tracker_unit = (
source_root / "vps/systemd/nodedc-device-edge-runtime.service"
).read_text()
self.assertIn("policy drop", foundation)
self.assertIn("tcp dport 22", foundation)
self.assertNotIn("tcp dport 9921", foundation)
self.assertIn("tcp dport 9921", relay)
self.assertIn("tcp dport 443", channel)
self.assertNotIn("tcp dport 9921", channel)
self.assertIn("tcp dport 443", tracker)
self.assertIn("tcp dport 9921", tracker)
self.assertIn("PasswordAuthentication no", sshd)
self.assertIn("AllowTcpForwarding no", sshd)
self.assertIn("StrictHostKeyChecking yes", backhaul)
self.assertIn("ProxyCommand /usr/bin/nc -X 5 -x 127.0.0.1:1055", backhaul)
self.assertIn("AF_NETLINK", tailscale_unit)
self.assertIn("User=nodedc-edge", tailscale_unit)
self.assertIn("StateDirectoryMode=0700", tailscale_unit)
self.assertIn("User=nodedc-backhaul", backhaul_unit)
self.assertNotIn("User=nodedc-edge", backhaul_unit)
self.assertIn("User=nodedc-relay", relay_unit)
self.assertNotIn("User=nodedc-edge", relay_unit)
self.assertIn("DEVICE_EDGE_RELAY_SOURCE_POLICY=public-ipv4-only", relay_unit)
self.assertIn("MemoryMax=192M", relay_unit)
self.assertIn("User=nodedc-channel", channel_unit)
self.assertIn(
"ExecStart=/opt/nodedc-b2-vps/runtime/node/bin/node "
"/opt/nodedc-b2-vps/services/device-edge-channel/src/server.mjs",
channel_unit,
)
self.assertIn("MemoryDenyWriteExecute=no", channel_unit)
self.assertIn(
"CapabilityBoundingSet=CAP_NET_BIND_SERVICE",
channel_unit,
)
self.assertIn(
"AmbientCapabilities=CAP_NET_BIND_SERVICE",
channel_unit,
)
self.assertNotIn("--jitless", channel_unit)
self.assertIn("MemoryMax=128M", channel_unit)
self.assertIn("MemorySwapMax=0", channel_unit)
self.assertIn("CPUQuota=50%", channel_unit)
self.assertIn("TasksMax=64", channel_unit)
self.assertIn("LimitNOFILE=1024", channel_unit)
self.assertNotIn("LocalForward", channel_unit)
self.assertNotIn("DEVICE_EDGE_RELAY_UPSTREAM", channel_unit)
self.assertIn("User=nodedc-channel", tracker_unit)
self.assertIn("vps/edge-process/device-edge-runtime.mjs", tracker_unit)
self.assertIn("DEVICE_GATEWAY_TCP_PORT=9921", tracker_unit)
self.assertIn("DEVICE_GATEWAY_MAX_SESSIONS=128", tracker_unit)
self.assertIn("MemoryMax=192M", tracker_unit)
self.assertIn("MemorySwapMax=0", tracker_unit)
self.assertIn("CPUQuota=75%", tracker_unit)
self.assertIn("TasksMax=128", tracker_unit)
self.assertIn("LimitNOFILE=1024", tracker_unit)
self.assertNotIn("LocalForward", tracker_unit)
self.assertNotIn("DEVICE_EDGE_RELAY_UPSTREAM", tracker_unit)
def test_runner_has_registered_rollback_and_no_generic_latest(self):
source = RUNNER_PATH.read_text(encoding="utf-8")
self.assertIn("def rollback(", source)
self.assertIn('TAILSCALE_REQUIRED_TAG = "tag:device-edge-vps"', source)
self.assertIn("assign_backhaul_trust", source)
self.assertIn("deploy-ok patch=", source)
self.assertNotIn("apply-latest", source)
self.assertNotIn("compose down", source)
self.assertNotIn("docker system prune", source)
def test_executable_preflight_accepts_a_valid_alternatives_symlink(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-tool-") as directory:
root = Path(directory)
target = root / "netcat.openbsd"
target.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
target.chmod(0o755)
command = root / "nc"
command.symlink_to(target.name)
self.assertEqual(
RUNNER.assert_executable_command_path(command, "test command"),
target.resolve(),
)
def test_executable_preflight_rejects_a_broken_symlink(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-tool-") as directory:
command = Path(directory) / "nc"
command.symlink_to("missing-netcat")
with self.assertRaises(RUNNER.DeployError):
RUNNER.assert_executable_command_path(command, "test command")
def test_backup_restore_preserves_the_exact_relay_partition(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-backup-") as directory:
root = Path(directory)
live = root / "live"
backups = root / "backups"
nft = root / "etc/nftables.conf"
relay_unit = root / "etc/nodedc-b2-relay.service"
backups.mkdir()
nft.parent.mkdir(parents=True)
nft.write_text("foundation-firewall\n", encoding="utf-8")
relay_unit.write_text("old-unit\n", encoding="utf-8")
for relative in RUNNER.RELAY_ENTRIES:
target = live / relative
if relative.endswith("/src"):
target.mkdir(parents=True)
(target / "server.mjs").write_text("old-source\n", encoding="utf-8")
else:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(f"old:{relative}\n", encoding="utf-8")
old_live = RUNNER.LIVE_ROOT
old_backups = RUNNER.BACKUP_ROOT
old_nft = RUNNER.NFTABLES_CONFIG
old_relay_unit = RUNNER.RELAY_UNIT
RUNNER.LIVE_ROOT = live
RUNNER.BACKUP_ROOT = backups
RUNNER.NFTABLES_CONFIG = nft
RUNNER.RELAY_UNIT = relay_unit
completed = subprocess.CompletedProcess([], 0, "table inet old {}\n", "")
try:
with patch.object(RUNNER, "run", return_value=completed), patch.object(
RUNNER,
"service_active",
return_value=False,
), patch.object(
RUNNER,
"systemctl",
return_value=completed,
), patch.object(
RUNNER,
"user_exists",
return_value=False,
):
_backup_id, backup = RUNNER.create_backup("relay-unit", "relay")
nft.write_text("candidate-firewall\n", encoding="utf-8")
relay_unit.write_text("candidate-unit\n", encoding="utf-8")
(live / "services/device-edge-relay/src/server.mjs").write_text(
"candidate-source\n",
encoding="utf-8",
)
RUNNER.restore_backup(backup, "relay")
finally:
RUNNER.LIVE_ROOT = old_live
RUNNER.BACKUP_ROOT = old_backups
RUNNER.NFTABLES_CONFIG = old_nft
RUNNER.RELAY_UNIT = old_relay_unit
self.assertEqual(nft.read_text(), "foundation-firewall\n")
self.assertEqual(relay_unit.read_text(), "old-unit\n")
self.assertEqual(
(live / "services/device-edge-relay/src/server.mjs").read_text(),
"old-source\n",
)
def test_backup_restore_preserves_core_channel_source_trust_and_firewall(self):
with tempfile.TemporaryDirectory(prefix="nodedc-vps-channel-backup-") as directory:
root = Path(directory)
live = root / "live"
backups = root / "backups"
nft = root / "etc/nftables.conf"
channel_unit = root / "etc/nodedc-device-edge-channel.service"
trust = root / "state/channel-trust"
backups.mkdir()
nft.parent.mkdir(parents=True)
trust.mkdir(parents=True)
nft.write_text("foundation-firewall\n", encoding="utf-8")
channel_unit.parent.mkdir(parents=True, exist_ok=True)
channel_unit.write_text("old-channel-unit\n", encoding="utf-8")
(trust / "runtime.json").write_text("old-runtime\n", encoding="utf-8")
for relative in RUNNER.CORE_CHANNEL_ENTRIES:
target = live / relative
if relative.endswith("/src"):
target.mkdir(parents=True)
(target / "server.mjs").write_text("old-channel-source\n", encoding="utf-8")
else:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(f"old:{relative}\n", encoding="utf-8")
old_live = RUNNER.LIVE_ROOT
old_backups = RUNNER.BACKUP_ROOT
old_nft = RUNNER.NFTABLES_CONFIG
old_unit = RUNNER.CHANNEL_UNIT
old_trust = RUNNER.CHANNEL_TRUST_ROOT
RUNNER.LIVE_ROOT = live
RUNNER.BACKUP_ROOT = backups
RUNNER.NFTABLES_CONFIG = nft
RUNNER.CHANNEL_UNIT = channel_unit
RUNNER.CHANNEL_TRUST_ROOT = trust
completed = subprocess.CompletedProcess([], 0, "table inet old {}\n", "")
try:
with patch.object(RUNNER, "run", return_value=completed), patch.object(
RUNNER,
"service_active",
return_value=False,
), patch.object(
RUNNER,
"systemctl",
return_value=completed,
), patch.object(
RUNNER,
"user_exists",
return_value=False,
):
_backup_id, backup = RUNNER.create_backup(
"channel-unit",
"core-channel",
)
nft.write_text("candidate-firewall\n", encoding="utf-8")
channel_unit.write_text("candidate-channel-unit\n", encoding="utf-8")
(trust / "runtime.json").write_text("candidate-runtime\n", encoding="utf-8")
(live / "services/device-edge-channel/src/server.mjs").write_text(
"candidate-channel-source\n",
encoding="utf-8",
)
RUNNER.restore_backup(backup, "core-channel")
finally:
RUNNER.LIVE_ROOT = old_live
RUNNER.BACKUP_ROOT = old_backups
RUNNER.NFTABLES_CONFIG = old_nft
RUNNER.CHANNEL_UNIT = old_unit
RUNNER.CHANNEL_TRUST_ROOT = old_trust
self.assertEqual(nft.read_text(), "foundation-firewall\n")
self.assertEqual(channel_unit.read_text(), "old-channel-unit\n")
self.assertEqual((trust / "runtime.json").read_text(), "old-runtime\n")
self.assertEqual(
(live / "services/device-edge-channel/src/server.mjs").read_text(),
"old-channel-source\n",
)
if __name__ == "__main__":
unittest.main(verbosity=2)
File diff suppressed because it is too large Load Diff
@@ -1,376 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
BUILDER = (
SCRIPT_DIR
/ "build-device-manager-control-plane-reconciliation-artifact.mjs"
)
V2_BUILDER = (
SCRIPT_DIR
/ "build-device-manager-control-plane-v2-reconciliation-artifact.mjs"
)
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_manager_reconciliation_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DeviceManagerControlPlaneReconciliationArtifactTest(unittest.TestCase):
def build(self, artifact_dir, patch_id, builder=BUILDER):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(builder), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_artifact_is_marker_only_exact_and_deterministic(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-manager-reconciliation-artifact-",
) as directory:
artifact_dir = Path(directory)
first = self.build(
artifact_dir,
"device-manager-control-plane-reconciliation-unit-002",
)
first_bytes = Path(first["artifact"]).read_bytes()
second = self.build(
artifact_dir,
"device-manager-control-plane-reconciliation-unit-002",
)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(first["component"], "device-plane")
self.assertEqual(
first["entries"],
list(RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_ENTRIES),
)
self.assertEqual(first["build"], [])
self.assertEqual(first["services"], [])
self.assertEqual(
first["runtimeAction"],
"read-only-acceptance",
)
with tarfile.open(first["artifact"], "r:gz") as archive:
names = [member.name for member in archive.getmembers()]
descriptor = json.loads(
archive.extractfile(
"payload/"
+ RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_REL
).read().decode("utf-8")
)
self.assertEqual(
descriptor,
RUNNER.expected_device_plane_manager_reconciliation_descriptor(),
)
self.assertEqual(
names,
[
"manifest.env",
"files.txt",
"payload",
"payload/deployment",
"payload/"
+ RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_REL,
],
)
self.assertEqual(
RUNNER.component_services(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_ENTRIES,
),
(),
)
self.assertEqual(
RUNNER.component_builds(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_ENTRIES,
),
(),
)
def test_failed_control_plane_is_terminal(self):
with self.assertRaisesRegex(
RUNNER.DeployError,
"exact v2 activation successor",
):
RUNNER.reject_terminal_device_plane_manager_artifact(
{"id": RUNNER.DEVICE_PLANE_MANAGER_FAILED_PATCH_ID},
"0" * 64,
)
with self.assertRaisesRegex(
RUNNER.DeployError,
"exact v2 activation successor",
):
RUNNER.reject_terminal_device_plane_manager_artifact(
{"id": "different"},
RUNNER.DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256,
)
with self.assertRaisesRegex(
RUNNER.DeployError,
"exact v2 activation successor",
):
RUNNER.reject_terminal_device_plane_manager_artifact(
{"id": "different", "component": "device-plane"},
"1" * 64,
RUNNER.DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES,
)
def test_v2_reconciliation_is_marker_only_exact_and_deterministic(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-manager-v2-reconciliation-artifact-",
) as directory:
artifact_dir = Path(directory)
first = self.build(
artifact_dir,
"device-manager-control-plane-v2-reconciliation-unit-004",
V2_BUILDER,
)
first_bytes = Path(first["artifact"]).read_bytes()
second = self.build(
artifact_dir,
"device-manager-control-plane-v2-reconciliation-unit-004",
V2_BUILDER,
)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(
first["entries"],
list(RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ENTRIES),
)
self.assertEqual(first["build"], [])
self.assertEqual(first["services"], [])
with tarfile.open(first["artifact"], "r:gz") as archive:
descriptor = json.loads(
archive.extractfile(
"payload/"
+ RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL
).read().decode("utf-8")
)
self.assertEqual(
descriptor,
RUNNER.expected_device_plane_manager_v2_reconciliation_descriptor(),
)
self.assertEqual(
RUNNER.component_services(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ENTRIES,
),
(),
)
self.assertEqual(
RUNNER.component_builds(
"device-plane",
RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ENTRIES,
),
(),
)
def test_failed_v2_control_plane_is_terminal(self):
with self.assertRaisesRegex(
RUNNER.DeployError,
"exact v2 reconciliation successor",
):
RUNNER.reject_terminal_device_plane_manager_artifact(
{"id": RUNNER.DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID},
"0" * 64,
)
with self.assertRaisesRegex(
RUNNER.DeployError,
"exact v2 reconciliation successor",
):
RUNNER.reject_terminal_device_plane_manager_artifact(
{"id": "different"},
RUNNER.DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256,
)
with self.assertRaisesRegex(
RUNNER.DeployError,
"exact v2 reconciliation successor",
):
RUNNER.reject_terminal_device_plane_manager_artifact(
{"id": "different", "component": "device-plane"},
"1" * 64,
RUNNER.DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES,
)
def test_health_grace_waits_through_unhealthy_and_exited(self):
results = [
mock.Mock(stdout="unhealthy\n", stderr="", returncode=0),
mock.Mock(stdout="exited\n", stderr="", returncode=0),
mock.Mock(stdout="starting\n", stderr="", returncode=0),
mock.Mock(stdout="healthy\n", stderr="", returncode=0),
]
with (
mock.patch.object(
RUNNER.subprocess,
"run",
side_effect=results,
) as inspect,
mock.patch.object(RUNNER.time, "sleep") as sleep,
):
RUNNER.healthcheck_container_with_grace("container-id")
self.assertEqual(inspect.call_count, 4)
self.assertEqual(sleep.call_count, 3)
def test_manager_rollback_uses_bounded_grace_for_restored_core(self):
entries = RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES
runtime_before = {
"schemaVersion": "nodedc.device-plane.runtime-inventory.v1",
"composeProject": "nodedc-device-plane",
"services": [
{
"service": "device-control-core",
"containerId": "a" * 64,
"imageId": "sha256:" + "b" * 64,
"status": "running",
"running": True,
"health": "healthy",
"restartCount": 0,
},
{
"service": "device-gateway",
"containerId": "c" * 64,
"imageId": "sha256:" + "d" * 64,
"status": "running",
"running": True,
"health": "healthy",
"restartCount": 0,
},
{
"service": "device-postgres",
"containerId": "e" * 64,
"imageId": "sha256:" + "f" * 64,
"status": "running",
"running": True,
"health": "healthy",
"restartCount": 0,
},
],
}
missing_set = {
RUNNER.DEVICE_PLANE_MANAGER_COMPOSE_REL,
"services/device-manager",
RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL,
}
existing = [entry for entry in entries if entry not in missing_set]
missing = [entry for entry in entries if entry in missing_set]
with (
mock.patch.object(
RUNNER,
"read_backup_path_list",
side_effect=[existing, missing],
),
mock.patch.object(
RUNNER,
"read_strict_json",
return_value=runtime_before,
),
mock.patch.object(
RUNNER,
"stop_and_remove_compose_services",
) as stop,
mock.patch.object(
RUNNER,
"restore_platform_overlay",
return_value=len(entries),
),
mock.patch.object(RUNNER, "run_component_runtime") as runtime,
mock.patch.object(
RUNNER,
"healthcheck_compose_service_with_grace",
) as health,
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=("core",),
),
mock.patch.object(RUNNER, "healthcheck_url") as url_health,
mock.patch.object(RUNNER, "run_healthchecks") as generic_health,
):
restored = RUNNER.rollback_device_plane_apply(
Path("/live"),
Path("/backup"),
entries,
"stamp",
True,
("device-control-core", "device-manager"),
)
self.assertEqual(
restored,
f"source+runtime-restored:{len(entries)}",
)
stop.assert_called_once_with("device-plane", ("device-manager",))
runtime.assert_called_once_with(
"device-plane",
existing,
("device-control-core",),
)
health.assert_called_once_with(
"device-plane",
"device-control-core",
)
url_health.assert_called_once_with("core")
generic_health.assert_not_called()
def test_reconciliation_runtime_phase_is_read_only(self):
for entries in (
RUNNER.DEVICE_PLANE_MANAGER_RECONCILIATION_ENTRIES,
RUNNER.DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ENTRIES,
):
with (
mock.patch.object(RUNNER, "run_build") as build,
mock.patch.object(RUNNER, "prepare_component_runtime") as prepare,
mock.patch.object(RUNNER, "run_compose") as compose,
):
RUNNER.run_component_runtime("device-plane", entries, ())
RUNNER.run_device_plane_runtime_for_apply(
entries,
(),
mock.Mock(),
)
build.assert_not_called()
prepare.assert_not_called()
compose.assert_not_called()
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -1,156 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = SCRIPT_DIR / "build-device-plane-artifact.mjs"
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
EXPECTED_ENTRIES = [
".dockerignore",
"package.json",
"package-lock.json",
"docker-compose.device-plane.yml",
"packages/device-protocol-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway",
]
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_artifact_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DevicePlaneArtifactTest(unittest.TestCase):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(BUILDER), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_artifact_is_narrow_safe_and_deterministic(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-artifact-",
) as directory:
artifact_dir = Path(directory)
first = self.build(
artifact_dir,
"device-plane-foundation-unit-001",
)
artifact = Path(first["artifact"])
first_bytes = artifact.read_bytes()
second = self.build(
artifact_dir,
"device-plane-foundation-unit-001",
)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first["component"], "device-plane")
self.assertEqual(first["entries"], EXPECTED_ENTRIES)
self.assertEqual(
first["services"],
["device-control-core", "device-gateway"],
)
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(first["sha256"], second["sha256"])
self.assertEqual(first_bytes, second_bytes)
with tarfile.open(artifact, "r:gz") as archive:
members = archive.getmembers()
names = {member.name for member in members}
files = (
archive.extractfile("files.txt")
.read()
.decode("utf-8")
.splitlines()
)
manifest = (
archive.extractfile("manifest.env")
.read()
.decode("utf-8")
)
compose = (
archive.extractfile(
"payload/docker-compose.device-plane.yml",
)
.read()
.decode("utf-8")
)
regular_payloads = [
archive.extractfile(member).read()
for member in members
if member.isfile()
]
self.assertEqual(files, EXPECTED_ENTRIES)
self.assertEqual(
manifest,
"id=device-plane-foundation-unit-001\n"
"component=device-plane\n"
"type=app-overlay\n",
)
self.assertIn(
"payload/services/device-control-core/Dockerfile",
names,
)
self.assertIn(
"payload/services/device-gateway/Dockerfile",
names,
)
self.assertFalse(any(
"/test/" in name
or "/node_modules/" in name
or Path(name).name.startswith(".env")
or name.startswith("payload/docs/")
or name.startswith("payload/runtime/")
or name.startswith("payload/secrets/")
for name in names
))
self.assertNotIn("9921:9921", compose)
self.assertIn('DEVICE_GATEWAY_LISTEN_ENABLED: "false"', compose)
self.assertIn('DEVICE_DISCOVERY_INGEST_ENABLED: "false"', compose)
self.assertNotIn(b"-----BEGIN PRIVATE KEY-----", b"\n".join(
regular_payloads,
))
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-runner-load-",
) as work_directory:
manifest_loaded, entries_loaded, payload_loaded = (
RUNNER.load_artifact(artifact, Path(work_directory))
)
self.assertEqual(manifest_loaded["component"], "device-plane")
self.assertEqual(entries_loaded, EXPECTED_ENTRIES)
self.assertEqual(payload_loaded.name, "payload")
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -1,477 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = (
SCRIPT_DIR / "build-device-plane-b2-discovery-ingress-artifact.mjs"
)
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
COMPOSE = (
SCRIPT_DIR.parent.parent
/ "device-plane/docker-compose.device-plane.yml"
)
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_b2_ingress_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DevicePlaneB2DiscoveryIngressArtifactTest(unittest.TestCase):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(BUILDER), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_artifact_is_exact_deterministic_and_database_free(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-b2-ingress-",
) as directory:
artifact_dir = Path(directory)
patch_id = "device-plane-b2-discovery-ingress-unit-001"
first = self.build(artifact_dir, patch_id)
first_bytes = Path(first["artifact"]).read_bytes()
second = self.build(artifact_dir, patch_id)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(
first["transition"],
"verified-b2-loopback-discovery-only",
)
self.assertEqual(
first["services"],
["device-control-core", "device-gateway"],
)
self.assertNotIn("device-postgres", first["services"])
self.assertEqual(
first["entries"],
list(RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES),
)
with tarfile.open(first["artifact"], "r:gz") as archive:
files = (
archive.extractfile("files.txt")
.read()
.decode("utf-8")
.splitlines()
)
compose = archive.extractfile(
"payload/docker-compose.device-plane.yml"
).read()
descriptor = json.loads(
archive.extractfile(
"payload/deployment/"
"device-plane-b2-discovery-ingress-v1.json"
)
.read()
.decode("utf-8")
)
edge_manifest = json.loads(
archive.extractfile(
"payload/services/device-edge-relay/package.json"
)
.read()
.decode("utf-8")
)
dockerignore = archive.extractfile(
"payload/.dockerignore"
).read().decode("utf-8")
core_dockerfile = archive.extractfile(
"payload/services/device-control-core/Dockerfile"
).read().decode("utf-8")
gateway_dockerfile = archive.extractfile(
"payload/services/device-gateway/Dockerfile"
).read().decode("utf-8")
self.assertEqual(files, first["entries"])
self.assertEqual(
hashlib.sha256(compose).hexdigest(),
RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_COMPOSE_SHA256,
)
self.assertEqual(
descriptor,
RUNNER.expected_device_plane_b2_discovery_ingress_descriptor(),
)
self.assertEqual(
edge_manifest["name"],
"@nodedc/device-edge-relay",
)
self.assertIn("**/*.prev-*", dockerignore.splitlines())
self.assertIn("**/*.next-*", dockerignore.splitlines())
for dockerfile in (core_dockerfile, gateway_dockerfile):
self.assertNotIn("COPY packages ./packages", dockerfile)
self.assertIn(
"COPY packages/device-protocol-contract "
"./packages/device-protocol-contract",
dockerfile,
)
self.assertIn(
"COPY packages/arusnavi-b2-adapter "
"./packages/arusnavi-b2-adapter",
dockerfile,
)
def test_compose_opens_only_discovery_tcp_and_preserves_database(self):
compose = COMPOSE.read_text(encoding="utf-8")
postgres, stateless = compose.split(" device-control-core:", 1)
self.assertNotIn("9921", postgres)
self.assertNotIn("device-plane-control", postgres)
self.assertIn(
'DEVICE_DISCOVERY_INGEST_ENABLED: "true"',
stateless,
)
self.assertIn(
'DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "false"',
stateless,
)
self.assertIn('"127.0.0.1:9921:9921"', stateless)
self.assertNotIn('"0.0.0.0:9921:9921"', stateless)
self.assertNotIn("DEVICE_GATEWAY_COMMAND", compose)
self.assertNotIn("POSTGRES_PASSWORD:", compose)
def test_runner_builds_only_stateless_services_and_accepts_new_health(self):
entries = RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES
self.assertEqual(
RUNNER.component_services("device-plane", entries),
("device-control-core", "device-gateway"),
)
builds = RUNNER.component_builds("device-plane", entries)
self.assertEqual(len(builds), 2)
checks = RUNNER.component_healthchecks(
"device-plane",
entries,
("device-control-core", "device-gateway"),
)
self.assertEqual(
checks[0]["expected_json"]["discoveryIngest"],
"enabled",
)
self.assertEqual(
checks[1]["expected_json"]["publicIngress"],
"disabled",
)
self.assertEqual(
checks[1]["expected_json"]["commandTransport"],
"disabled",
)
def test_ingress_preflight_requires_terminal_recovery_marker(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-b2-recovery-barrier-",
) as directory:
workspace = Path(directory)
built = self.build(
workspace,
"device-plane-b2-discovery-ingress-unit-recovery-barrier",
)
extracted = workspace / "extracted"
extracted.mkdir()
_manifest, _entries, payload = RUNNER.load_artifact(
Path(built["artifact"]),
extracted,
)
def has_patch(patch_id):
return (
patch_id
== RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_PATCH_ID
)
with (
mock.patch.object(
RUNNER,
"state_has_patch_id",
side_effect=has_patch,
),
mock.patch.object(
RUNNER,
"state_has_sha",
return_value=True,
),
):
with self.assertRaisesRegex(
RUNNER.DeployError,
"rollback recovery patch is not applied",
):
RUNNER.validate_device_plane_b2_discovery_ingress_evidence(
payload
)
def test_runtime_acceptance_preserves_postgres_and_replaces_stateless(self):
before = {
"schemaVersion": "nodedc.device-plane.runtime-inventory.v1",
"composeProject": "nodedc-device-plane",
"services": [
runtime_item("device-control-core", "1", "a"),
runtime_item("device-gateway", "2", "b"),
runtime_item("device-postgres", "3", "c"),
],
}
containers = {
"core": stateless_container(
service="device-control-core",
container_id="4" * 64,
image_id="sha256:" + "d" * 64,
image=RUNNER.DEVICE_PLANE_CONTROL_CORE_IMAGE,
ports={
"18120/tcp": [{
"HostIp": "127.0.0.1",
"HostPort": "18120",
}],
},
environment={
"DEVICE_DISCOVERY_INGEST_ENABLED": "true",
"DEVICE_GATEWAY_CORE_TOKEN_FILE":
"/run/nodedc-secrets/gateway-core-token",
"DEVICE_IDENTIFIER_PEPPER_FILE":
"/run/nodedc-secrets/identifier-pepper",
},
mounts=[
secret_mount(
RUNNER.DEVICE_PLANE_POSTGRES_PASSWORD_FILE,
"/run/nodedc-secrets/postgres-password",
),
secret_mount(
RUNNER.DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE,
"/run/nodedc-secrets/gateway-core-token",
),
secret_mount(
RUNNER.DEVICE_PLANE_IDENTIFIER_PEPPER_FILE,
"/run/nodedc-secrets/identifier-pepper",
),
],
),
"gateway": stateless_container(
service="device-gateway",
container_id="5" * 64,
image_id="sha256:" + "e" * 64,
image=RUNNER.DEVICE_PLANE_GATEWAY_IMAGE,
ports={
"18121/tcp": [{
"HostIp": "127.0.0.1",
"HostPort": "18121",
}],
"9921/tcp": [{
"HostIp": "127.0.0.1",
"HostPort": "9921",
}],
},
environment={
"DEVICE_GATEWAY_LISTEN_ENABLED": "true",
"DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED": "false",
"DEVICE_GATEWAY_TCP_HOST": "127.0.0.1",
"DEVICE_GATEWAY_TCP_PORT": "9921",
"DEVICE_GATEWAY_CORE_URL":
"http://device-control-core:18120",
"DEVICE_GATEWAY_CORE_TOKEN_FILE":
"/run/nodedc-secrets/gateway-core-token",
"DEVICE_GATEWAY_CORE_TIMEOUT_MS": "5000",
"DEVICE_GATEWAY_MAX_BUFFERED_BYTES": "65536",
"DEVICE_GATEWAY_MAX_SESSIONS": "100",
"DEVICE_GATEWAY_MAX_SESSIONS_PER_ADDRESS": "10",
"DEVICE_GATEWAY_MAX_CONNECTIONS_PER_MINUTE_PER_ADDRESS":
"30",
"DEVICE_GATEWAY_SESSION_TIMEOUT_MS": "10000",
},
mounts=[
secret_mount(
RUNNER.DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE,
"/run/nodedc-secrets/gateway-core-token",
),
],
),
"postgres": postgres_container(),
}
service_ids = {
"device-control-core": ("core",),
"device-gateway": ("gateway",),
"device-postgres": ("postgres",),
}
with (
mock.patch.object(
RUNNER,
"validate_device_plane_runtime_secret_metadata",
),
mock.patch.object(
RUNNER,
"device_plane_service_container_ids",
side_effect=lambda service: service_ids[service],
),
mock.patch.object(
RUNNER,
"inspect_device_plane_container",
side_effect=lambda container_id: containers[container_id],
),
mock.patch.object(
RUNNER,
"validate_device_plane_network_contract",
) as network,
mock.patch.object(
RUNNER,
"assert_loopback_tcp_port_open",
) as port_open,
):
accepted = (
RUNNER.validate_device_plane_b2_discovery_ingress_runtime(
before
)
)
self.assertEqual(
accepted["device-postgres"]["containerId"],
"3" * 64,
)
self.assertEqual(network.call_count, 2)
port_open.assert_called_once_with(9921)
def runtime_item(service, container_digit, image_digit):
return {
"service": service,
"containerId": container_digit * 64,
"imageId": "sha256:" + image_digit * 64,
"status": "running",
"running": True,
"health": "healthy",
"restartCount": 0,
}
def secret_mount(source, destination):
return {
"Type": "bind",
"Source": str(source),
"Destination": destination,
"RW": False,
}
def stateless_container(
*,
service,
container_id,
image_id,
image,
ports,
environment,
mounts,
):
return {
"Id": container_id,
"Image": image_id,
"RestartCount": 0,
"State": {
"Status": "running",
"Running": True,
"Restarting": False,
"ExitCode": 0,
"Error": "",
"Health": {"Status": "healthy"},
},
"Config": {
"Image": image,
"User": "1000:1000",
"Labels": {
"com.docker.compose.project": "nodedc-device-plane",
"com.docker.compose.service": service,
},
"Env": [f"{key}={value}" for key, value in environment.items()],
},
"HostConfig": {
"PortBindings": ports,
"RestartPolicy": {"Name": "unless-stopped"},
"ReadonlyRootfs": True,
"CapDrop": ["ALL"],
"SecurityOpt": ["no-new-privileges:true"],
},
"NetworkSettings": {
"Ports": ports,
"Networks": {
RUNNER.DEVICE_PLANE_PRIVATE_NETWORK: {},
RUNNER.DEVICE_PLANE_CONTROL_NETWORK: {},
},
},
"Mounts": mounts,
}
def postgres_container():
return {
"Id": "3" * 64,
"Image": "sha256:" + "c" * 64,
"RestartCount": 0,
"State": {
"Status": "running",
"Running": True,
"Restarting": False,
"ExitCode": 0,
"Error": "",
"Health": {"Status": "healthy"},
},
"Config": {
"Image": "postgres:16-alpine",
"User": "",
"Labels": {
"com.docker.compose.project": "nodedc-device-plane",
"com.docker.compose.service": "device-postgres",
},
"Env": [],
},
"HostConfig": {
"PortBindings": {},
"RestartPolicy": {"Name": "unless-stopped"},
},
"NetworkSettings": {
"Ports": {},
"Networks": {
RUNNER.DEVICE_PLANE_PRIVATE_NETWORK: {},
},
},
"Mounts": [
secret_mount(
RUNNER.DEVICE_PLANE_POSTGRES_PASSWORD_FILE,
"/run/nodedc-secrets/postgres-password",
),
{
"Type": "volume",
"Name": RUNNER.DEVICE_PLANE_POSTGRES_VOLUME,
"Destination": "/var/lib/postgresql/data",
"RW": True,
},
],
}
if __name__ == "__main__":
unittest.main()
@@ -1,323 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = (
SCRIPT_DIR
/ "build-device-plane-b2-discovery-loopback-recovery-artifact.mjs"
)
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_b2_recovery_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DevicePlaneB2DiscoveryLoopbackRecoveryArtifactTest(
unittest.TestCase,
):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(BUILDER), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_artifact_is_marker_only_exact_and_deterministic(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-b2-recovery-artifact-",
) as directory:
artifact_dir = Path(directory)
patch_id = RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_PATCH_ID
first = self.build(artifact_dir, patch_id)
first_bytes = Path(first["artifact"]).read_bytes()
second = self.build(artifact_dir, patch_id)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(first["build"], [])
self.assertEqual(first["services"], [])
self.assertEqual(
first["entries"],
list(
RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_ENTRIES
),
)
with tarfile.open(first["artifact"], "r:gz") as archive:
names = {
member.name
for member in archive.getmembers()
if member.isfile()
}
descriptor = json.loads(
archive.extractfile(
"payload/"
+ RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL
)
.read()
.decode("utf-8")
)
self.assertEqual(
names,
{
"manifest.env",
"files.txt",
"payload/"
+ RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL,
},
)
self.assertEqual(
descriptor,
RUNNER.expected_device_plane_b2_discovery_rollback_recovery_descriptor(),
)
entries = (
RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_ENTRIES
)
self.assertEqual(
RUNNER.component_services("device-plane", entries),
(),
)
self.assertEqual(
RUNNER.component_builds("device-plane", entries),
(),
)
def test_recovery_preflight_requires_exact_failed_evidence(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-b2-recovery-preflight-",
) as directory:
workspace = Path(directory)
artifacts = workspace / "artifacts"
failed_root = workspace / "failed"
backups_root = workspace / "backups"
state_root = workspace / "state"
temp_root = workspace / "tmp"
live_root = workspace / "live"
for path in (
artifacts,
failed_root,
backups_root,
state_root,
temp_root,
live_root,
):
path.mkdir(parents=True, exist_ok=True)
recovery = self.build(
artifacts,
RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_PATCH_ID,
)
extracted = workspace / "extracted"
extracted.mkdir()
_manifest, entries, payload = RUNNER.load_artifact(
Path(recovery["artifact"]),
extracted,
)
self.assertEqual(
tuple(entries),
RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_ENTRIES,
)
failed_artifact = (
failed_root / RUNNER.DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT
)
failed_artifact.write_bytes(b"failed-b2-artifact-fixture\n")
failed_sha = RUNNER.DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT_SHA256
backup = (
backups_root
/ RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_ID
)
backup.mkdir()
backup_names = (
"manifest.env",
"files.txt",
"existing-files.txt",
"missing-files.txt",
"runtime-before.json",
"source-before.tgz",
)
for name in backup_names:
(backup / name).write_text(
f"fixture:{name}\n",
encoding="utf-8",
)
backup_hashes = {
name: hashlib.sha256((backup / name).read_bytes()).hexdigest()
for name in backup_names
}
(state_root / "failed.jsonl").write_text(
json.dumps({
"artifact": RUNNER.DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT,
"backup_id": (
RUNNER.DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_ID
),
"component": "device-plane",
"id": RUNNER.DEVICE_PLANE_B2_DISCOVERY_FAILED_PATCH_ID,
"message": (
"Command '['/usr/local/bin/docker', 'build', "
"'--no-cache', '--network=host', '-f', "
"'services/device-control-core/Dockerfile', '-t', "
"'nodedc/device-control-core:local', '.']' returned "
"non-zero exit status 1."
),
"rollback_status": "failed:CalledProcessError",
"sha256": failed_sha,
"started_apply": True,
"status": "failed",
})
+ "\n",
encoding="utf-8",
)
runtime = {"accepted": True}
failed_manifest = {
"id": RUNNER.DEVICE_PLANE_B2_DISCOVERY_FAILED_PATCH_ID,
"component": "device-plane",
"type": "app-overlay",
}
with (
mock.patch.object(RUNNER, "BACKUPS_DIR", backups_root),
mock.patch.object(RUNNER, "FAILED_DIR", failed_root),
mock.patch.object(
RUNNER,
"FAILED_STATE_FILE",
state_root / "failed.jsonl",
),
mock.patch.object(RUNNER, "TMP_DIR", temp_root),
mock.patch.object(
RUNNER,
"component_root",
return_value=live_root,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_SHA256",
backup_hashes,
),
mock.patch.object(
RUNNER,
"sha256_file",
side_effect=lambda path: (
failed_sha
if Path(path) == failed_artifact
else hashlib.sha256(Path(path).read_bytes()).hexdigest()
),
),
mock.patch.object(
RUNNER,
"load_artifact",
return_value=(
failed_manifest,
list(RUNNER.DEVICE_PLANE_B2_DISCOVERY_FAILED_ENTRIES),
workspace / "unused-payload",
),
),
mock.patch.object(
RUNNER,
"validate_device_plane_foundation_network_publication_installed_source",
),
mock.patch.object(
RUNNER,
"validate_device_plane_foundation_runtime",
return_value=runtime,
),
mock.patch.object(RUNNER, "assert_loopback_tcp_port_closed"),
):
evidence = (
RUNNER.validate_device_plane_b2_discovery_rollback_recovery_evidence(
payload
)
)
self.assertEqual(
evidence["mode"],
"failed-b2-loopback-build-reconciliation",
)
self.assertEqual(evidence["runtime"], runtime)
def test_build_failure_does_not_mark_runtime_started(self):
marker = mock.Mock()
entries = RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES
services = ("device-control-core", "device-gateway")
failure = subprocess.CalledProcessError(1, ["docker", "build"])
with (
mock.patch.object(RUNNER, "run_build", side_effect=failure),
mock.patch.object(RUNNER, "prepare_component_runtime") as prepare,
mock.patch.object(RUNNER, "run_compose") as compose,
):
with self.assertRaises(subprocess.CalledProcessError):
RUNNER.run_device_plane_runtime_for_apply(
entries,
services,
marker,
)
marker.assert_not_called()
prepare.assert_not_called()
compose.assert_not_called()
def test_compose_failure_is_marked_after_build_and_prepare(self):
events = []
def mark():
events.append("mark")
with (
mock.patch.object(
RUNNER,
"run_build",
side_effect=lambda *_args: events.append("build"),
),
mock.patch.object(
RUNNER,
"prepare_component_runtime",
side_effect=lambda *_args: events.append("prepare"),
),
mock.patch.object(
RUNNER,
"run_compose",
side_effect=RuntimeError("compose failed"),
),
):
with self.assertRaisesRegex(RuntimeError, "compose failed"):
RUNNER.run_device_plane_runtime_for_apply(
RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES,
("device-control-core", "device-gateway"),
mark,
)
self.assertEqual(events, ["build", "prepare", "mark"])
if __name__ == "__main__":
unittest.main()
@@ -1,649 +0,0 @@
#!/usr/bin/env python3
import base64
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = SCRIPT_DIR / "build-device-plane-backhaul-target-artifact.mjs"
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
COMPOSE = (
SCRIPT_DIR.parent.parent
/ "device-plane/docker-compose.device-plane.backhaul-target.yml"
)
PREDECESSOR_COMPOSE = (
SCRIPT_DIR.parent.parent / "device-plane/docker-compose.device-plane.yml"
)
SSHD_CONFIG = (
SCRIPT_DIR.parent.parent
/ "device-plane/services/device-backhaul-target/sshd_config"
)
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_backhaul_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
def valid_public_key(comment="nodedc-device-edge-backhaul"):
blob = b"\x00\x00\x00\x0bssh-ed25519\x00\x00\x00\x20" + bytes(range(32))
return f"ssh-ed25519 {base64.b64encode(blob).decode()} {comment}\n"
def healthy_inventory():
return {
"schemaVersion": "nodedc.device-plane.runtime-inventory.v1",
"composeProject": "nodedc-device-plane",
"services": [
{
"service": service,
"containerId": character * 64,
"imageId": f"sha256:{character * 64}",
"status": "running",
"running": True,
"health": "healthy",
"restartCount": 0,
}
for service, character in (
("device-control-core", "a"),
("device-gateway", "b"),
("device-postgres", "c"),
)
],
}
class DevicePlaneBackhaulTargetArtifactTest(unittest.TestCase):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(BUILDER), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_artifact_is_exact_deterministic_and_contains_no_keys(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-backhaul-artifact-",
) as directory:
target = Path(directory)
first = self.build(target, "device-plane-backhaul-target-unit-001")
first_bytes = Path(first["artifact"]).read_bytes()
second = self.build(target, "device-plane-backhaul-target-unit-001")
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(first["sha256"], hashlib.sha256(first_bytes).hexdigest())
self.assertEqual(
first["entries"],
list(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES),
)
self.assertEqual(first["services"], ["device-backhaul-target"])
with tarfile.open(first["artifact"], "r:gz") as archive:
names = archive.getnames()
files = archive.extractfile("files.txt").read().decode().splitlines()
descriptor = json.loads(
archive.extractfile(
"payload/deployment/"
"device-plane-backhaul-target-tailnet-serve-v1.json"
).read()
)
self.assertEqual(files, first["entries"])
self.assertEqual(
descriptor,
RUNNER.expected_device_plane_backhaul_target_descriptor(),
)
self.assertFalse(any(
name.endswith((".key", ".pem", "authorized_keys"))
for name in names
))
def test_registry_selects_only_target_and_preserves_red_boundaries(self):
entries = RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES
self.assertEqual(
RUNNER.component_services("device-plane", entries),
("device-backhaul-target",),
)
builds = RUNNER.component_builds("device-plane", entries)
self.assertEqual(len(builds), 1)
self.assertIn("services/device-backhaul-target/Dockerfile", builds[0][1])
self.assertIn(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_IMAGE, builds[0][1])
for path in (
RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL,
"services/device-backhaul-target/Dockerfile",
"services/device-backhaul-target/sshd_config",
RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_REL,
):
self.assertTrue(RUNNER.allowed_payload_path("device-plane", path))
compose = COMPOSE.read_text(encoding="utf-8")
sshd = SSHD_CONFIG.read_text(encoding="utf-8")
self.assertIn("network_mode: host", compose)
self.assertNotIn("0.0.0.0:2222", compose)
self.assertIn('"127.0.0.1", "2222"', compose)
self.assertIn("ListenAddress 127.0.0.1", sshd)
self.assertIn("AllowTcpForwarding local", sshd)
self.assertIn("PermitOpen 127.0.0.1:9921", sshd)
self.assertIn("ForceCommand /bin/false", sshd)
self.assertIn("PasswordAuthentication no", sshd)
def test_preflight_requires_exact_applied_006_and_enrollment_key(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-backhaul-preflight-",
) as directory:
work = Path(directory)
built = self.build(work, "device-plane-backhaul-target-unit-002")
extracted = work / "extracted"
extracted.mkdir()
_manifest, _entries, payload = RUNNER.load_artifact(
Path(built["artifact"]),
extracted,
)
live = work / "live"
live.mkdir()
(live / "docker-compose.device-plane.yml").write_bytes(
PREDECESSOR_COMPOSE.read_bytes()
)
descriptor = live / RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL
descriptor.parent.mkdir(parents=True)
descriptor.write_text(
json.dumps(RUNNER.expected_device_plane_b2_discovery_ingress_descriptor()),
encoding="utf-8",
)
enrollment = work / "device-edge-backhaul.pub"
enrollment.write_text(valid_public_key(), encoding="ascii")
def has_patch(value):
return value == RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_PATCH_ID
def has_sha(value):
return value == RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_ARTIFACT_SHA256
with (
mock.patch.object(RUNNER, "component_root", return_value=live),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_BACKHAUL_ENROLLMENT_PUBLIC_KEY_FILE",
enrollment,
),
mock.patch.object(RUNNER, "state_has_patch_id", side_effect=has_patch),
mock.patch.object(RUNNER, "state_has_sha", side_effect=has_sha),
mock.patch.object(
RUNNER,
"device_plane_service_container_ids",
return_value=[],
),
mock.patch.object(
RUNNER,
"device_plane_runtime_inventory",
return_value=healthy_inventory(),
),
mock.patch.object(RUNNER, "assert_loopback_tcp_port_open"),
mock.patch.object(
RUNNER,
"validate_device_plane_backhaul_failed_evidence",
return_value={
"backup": work / "failed-backup",
"failedArtifact": work / "failed-artifact.tgz",
},
),
mock.patch.object(
RUNNER,
"validate_device_plane_tailscale_cli",
return_value={
"binary": str(RUNNER.DEVICE_PLANE_TAILSCALE),
"uid": 1024,
"gid": 1024,
"binarySha256": "d" * 64,
},
),
mock.patch.object(
RUNNER,
"validate_device_plane_tailscale_runtime",
return_value={
"self": {
"Online": True,
"TailscaleIPs": ["100.109.216.21"],
},
"serve": {},
},
),
):
accepted = RUNNER.validate_device_plane_backhaul_target_evidence(payload)
self.assertEqual(
accepted["mode"],
"failed-backhaul-target-to-loopback-tailnet-serve",
)
self.assertRegex(accepted["enrollmentPublicKeySha256"], r"^[a-f0-9]{64}$")
self.assertEqual(accepted["tailscaleServeBefore"], {})
self.assertEqual(accepted["tailscaleCli"]["uid"], 1024)
def test_registered_health_gate_checks_preserved_and_target_services(self):
with mock.patch.object(RUNNER, "healthcheck_compose_service") as health:
RUNNER.run_healthchecks(
"device-plane",
RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES,
(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,),
)
self.assertEqual(
[call.args[1] for call in health.call_args_list],
[
"device-control-core",
"device-gateway",
"device-postgres",
"device-backhaul-target",
],
)
def test_candidate_rollback_removes_only_target_and_preserves_runtime(self):
runtime = healthy_inventory()
with (
mock.patch.object(
RUNNER,
"read_backup_path_list",
side_effect=[[], list(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES)],
),
mock.patch.object(
RUNNER,
"validate_backup_partition",
return_value=(set(), set(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES)),
),
mock.patch.object(
RUNNER,
"read_strict_json",
side_effect=[runtime, {}],
),
mock.patch.object(
RUNNER,
"disable_device_plane_tailscale_serve",
) as disable_serve,
mock.patch.object(RUNNER, "stop_and_remove_compose_services") as stop,
mock.patch.object(RUNNER, "restore_platform_overlay", return_value=3),
mock.patch.object(
RUNNER,
"device_plane_service_container_ids",
return_value=[],
),
mock.patch.object(
RUNNER,
"device_plane_runtime_inventory",
return_value=runtime,
),
mock.patch.object(
RUNNER,
"validate_device_plane_tailscale_runtime",
return_value={"serve": {}},
),
mock.patch.object(RUNNER, "assert_loopback_tcp_port_open") as port,
):
result = RUNNER.rollback_device_plane_apply(
Path("/unused/live"),
Path("/unused/backup"),
RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES,
"20260803-000000",
True,
(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,),
)
self.assertEqual(
result,
"tailscale-serve-restored-source-restored-target-removed-"
"preserved-runtime-unchanged:3",
)
stop.assert_called_once_with(
"device-plane",
(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,),
)
disable_serve.assert_called_once_with({})
port.assert_called_once_with(9921)
def test_tailscale_serve_port_parser_rejects_funnel_and_nested_collision(self):
clean = {"TCP": {"443": {"HTTPS": True}}}
self.assertEqual(
RUNNER.device_plane_tailscale_handlers_for_port(clean, 2222),
[],
)
active = {
"TCP": {"2222": {"TCPForward": "127.0.0.1:2222"}},
"Foreground": {
"session": {
"TCP": {"443": {"HTTPS": True}},
},
},
}
self.assertEqual(
RUNNER.device_plane_tailscale_handlers_for_port(active, 2222),
[((), {"TCPForward": "127.0.0.1:2222"})],
)
self.assertFalse(
RUNNER.device_plane_tailscale_funnel_uses_port(active, 2222)
)
active["AllowFunnel"] = {"edge.example.ts.net:2222": True}
self.assertTrue(
RUNNER.device_plane_tailscale_funnel_uses_port(active, 2222)
)
def test_runtime_activation_enables_private_tailscale_serve_after_health(self):
calls = []
with (
mock.patch.object(RUNNER, "run_build", side_effect=lambda *a: calls.append("build")),
mock.patch.object(
RUNNER,
"prepare_component_runtime",
side_effect=lambda *a: calls.append("prepare"),
),
mock.patch.object(
RUNNER,
"run_compose",
side_effect=lambda *a: calls.append("compose"),
),
mock.patch.object(
RUNNER,
"healthcheck_compose_service",
side_effect=lambda *a: calls.append("health"),
),
mock.patch.object(
RUNNER,
"enable_device_plane_tailscale_serve",
side_effect=lambda *a: calls.append("serve"),
) as enable,
):
RUNNER.run_device_plane_runtime_for_apply(
RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES,
(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,),
lambda: calls.append("started"),
backhaul_serve_before={"TCP": {"443": {"HTTPS": True}}},
)
self.assertEqual(
calls,
["build", "prepare", "started", "compose", "health", "serve"],
)
enable.assert_called_once_with({"TCP": {"443": {"HTTPS": True}}})
def test_tailscale_serve_enable_and_disable_preserve_unrelated_routes(self):
before = {"TCP": {"443": {"HTTPS": True}}}
active = {
"TCP": {
"443": {"HTTPS": True},
"2222": {"TCPForward": "127.0.0.1:2222"},
},
}
with (
mock.patch.object(
RUNNER,
"validate_device_plane_tailscale_runtime",
side_effect=[{"serve": before}, {"serve": active}],
),
mock.patch.object(RUNNER, "run_device_plane_tailscale") as run,
):
result = RUNNER.enable_device_plane_tailscale_serve(before)
self.assertEqual(result, active)
self.assertEqual(
run.call_args.args[0],
[
"serve",
"--bg",
"--yes",
"--tcp=2222",
"tcp://127.0.0.1:2222",
],
)
with (
mock.patch.object(
RUNNER,
"read_device_plane_tailscale_json",
return_value=active,
),
mock.patch.object(
RUNNER,
"validate_device_plane_tailscale_runtime",
return_value={"serve": before},
),
mock.patch.object(RUNNER, "run_device_plane_tailscale") as run,
):
changed = RUNNER.disable_device_plane_tailscale_serve(before)
self.assertTrue(changed)
self.assertEqual(
run.call_args.args[0],
[
"serve",
"--tcp=2222",
"off",
],
)
def test_tailscale_cli_runs_as_official_package_account(self):
context = {
"binary": "/var/packages/Tailscale/target/bin/tailscale",
"uid": 1051,
"gid": 1051,
"binarySha256": "e" * 64,
}
with (
mock.patch.object(
RUNNER,
"validate_device_plane_tailscale_cli",
return_value=context,
),
mock.patch.object(RUNNER.subprocess, "run") as run,
mock.patch.object(
RUNNER,
"device_plane_tailscale_drop_privileges",
return_value="drop-to-package-account",
) as drop,
):
RUNNER.run_device_plane_tailscale(
["status", "--json"],
check=False,
capture_output=True,
text=True,
)
self.assertEqual(
run.call_args.args[0],
[context["binary"], "status", "--json"],
)
self.assertEqual(
run.call_args.kwargs["preexec_fn"],
"drop-to-package-account",
)
drop.assert_called_once_with(1051, 1051)
def test_tailscale_cli_accepts_package_owned_binary_without_root_execution(self):
privilege_path = mock.MagicMock()
privilege_path.__str__.return_value = (
"/var/packages/Tailscale/conf/privilege"
)
privilege_path.lstat.return_value = SimpleNamespace(
st_mode=RUNNER.stat.S_IFREG | 0o644,
st_uid=0,
)
binary_path = mock.MagicMock()
binary_path.__str__.return_value = (
"/var/packages/Tailscale/target/bin/tailscale"
)
binary_path.lstat.return_value = SimpleNamespace(
st_mode=RUNNER.stat.S_IFREG | 0o755,
st_uid=1051,
st_gid=1051,
st_size=32 * 1024 * 1024,
)
account = SimpleNamespace(pw_uid=1051, pw_gid=1051)
group = SimpleNamespace(gr_gid=1051)
help_result = SimpleNamespace(
stdout="--tcp --bg --yes",
stderr="",
)
with (
mock.patch.object(
RUNNER,
"DEVICE_PLANE_TAILSCALE_PRIVILEGE",
privilege_path,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_TAILSCALE",
binary_path,
),
mock.patch.object(
RUNNER,
"read_strict_json",
return_value={
"defaults": {"run-as": "package"},
"username": "tailscale",
"groupname": "tailscale",
},
),
mock.patch.object(RUNNER.pwd, "getpwnam", return_value=account),
mock.patch.object(RUNNER.grp, "getgrnam", return_value=group),
mock.patch.object(
RUNNER,
"sha256_file",
return_value="f" * 64,
),
mock.patch.object(
RUNNER.subprocess,
"run",
return_value=help_result,
) as run,
):
context = RUNNER.validate_device_plane_tailscale_cli()
self.assertEqual(context["uid"], 1051)
self.assertEqual(context["gid"], 1051)
self.assertEqual(context["binarySha256"], "f" * 64)
self.assertTrue(callable(run.call_args.kwargs["preexec_fn"]))
def test_failed_001_evidence_is_exact_and_terminal(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-backhaul-failed-evidence-",
) as directory:
root = Path(directory)
backups = root / "backups"
failed = root / "failed"
state = root / "state"
tmp = root / "tmp"
for path in (backups, failed, state, tmp):
path.mkdir()
backup = backups / RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_ID
backup.mkdir()
backup_hashes = {}
for name in (
"manifest.env",
"files.txt",
"source-before.tgz",
"existing-files.txt",
"missing-files.txt",
"runtime-before.json",
):
payload = f"fixture:{name}\n".encode()
(backup / name).write_bytes(payload)
backup_hashes[name] = hashlib.sha256(payload).hexdigest()
stage = root / "failed-stage"
payload = stage / "payload"
service = payload / "services/device-backhaul-target"
deployment = payload / "deployment"
service.mkdir(parents=True)
deployment.mkdir(parents=True)
(stage / "manifest.env").write_text(
"id=device-plane-backhaul-target-20260803-001\n"
"component=device-plane\n"
"type=app-overlay\n",
encoding="utf-8",
)
(stage / "files.txt").write_text(
"\n".join(RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_TARGET_ENTRIES)
+ "\n",
encoding="utf-8",
)
(payload / RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL).write_text(
"services: {}\n",
encoding="utf-8",
)
(service / "Dockerfile").write_text(
"FROM scratch\n",
encoding="utf-8",
)
(service / "sshd_config").write_text(
"PasswordAuthentication no\n",
encoding="utf-8",
)
(payload / RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL).write_text(
json.dumps(
RUNNER.expected_failed_device_plane_backhaul_target_descriptor()
)
+ "\n",
encoding="utf-8",
)
failed_artifact = failed / RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT
with tarfile.open(failed_artifact, "w:gz") as archive:
for name in ("manifest.env", "files.txt", "payload"):
archive.add(stage / name, arcname=name)
failed_sha = hashlib.sha256(failed_artifact.read_bytes()).hexdigest()
failed_state = state / "failed.jsonl"
failed_state.write_text(
json.dumps({
"artifact": RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT,
"backup_id": RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_ID,
"component": "device-plane",
"id": RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_PATCH_ID,
"message": RUNNER.DEVICE_PLANE_BACKHAUL_FAILED_MESSAGE,
"rollback_status": (
"ok:device-plane-overlay:source-restored-target-removed-"
"preserved-runtime-unchanged:3"
),
"sha256": failed_sha,
"started_apply": True,
"status": "failed",
})
+ "\n",
encoding="utf-8",
)
with (
mock.patch.object(RUNNER, "BACKUPS_DIR", backups),
mock.patch.object(RUNNER, "FAILED_DIR", failed),
mock.patch.object(RUNNER, "FAILED_STATE_FILE", failed_state),
mock.patch.object(RUNNER, "TMP_DIR", tmp),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_SHA256",
backup_hashes,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT_SHA256",
failed_sha,
),
):
evidence = RUNNER.validate_device_plane_backhaul_failed_evidence()
self.assertEqual(evidence["backup"], backup)
self.assertEqual(evidence["failedArtifact"], failed_artifact)
if __name__ == "__main__":
unittest.main()
@@ -1,236 +0,0 @@
#!/usr/bin/env python3
import base64
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = (
SCRIPT_DIR / "build-device-plane-backhaul-vps-enrollment-artifact.mjs"
)
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_vps_enrollment_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DevicePlaneBackhaulVpsEnrollmentArtifactTest(unittest.TestCase):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
environment["NODEDC_ALLOW_SUPERSEDED_TRANSPORT"] = "test-only"
return subprocess.run(
["node", str(BUILDER), patch_id],
check=False,
capture_output=True,
text=True,
env=environment,
)
def test_builder_fails_closed_without_test_only_reconstruction(self):
environment = os.environ.copy()
environment.pop("NODEDC_ALLOW_SUPERSEDED_TRANSPORT", None)
with tempfile.TemporaryDirectory(
prefix="nodedc-vps-enrollment-frozen-",
) as directory:
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = directory
result = subprocess.run(
["node", str(BUILDER), "device-plane-vps-enrollment-frozen-001"],
check=False,
capture_output=True,
text=True,
env=environment,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn(
"vps_initiated_transport_frozen:ADR-0001",
result.stderr,
)
def test_runner_rejects_a_prebuilt_superseded_enrollment_artifact(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-vps-enrollment-frozen-runner-",
) as directory:
root = Path(directory)
result = self.build(
root,
"device-plane-vps-enrollment-frozen-runner-001",
)
self.assertEqual(result.returncode, 0, result.stderr)
artifact = Path(json.loads(result.stdout)["artifact"])
extracted = root / "extracted"
extracted.mkdir()
with self.assertRaises(RUNNER.DeployError) as raised:
RUNNER.load_artifact(artifact, extracted)
self.assertIn(
"vps_initiated_transport_frozen:ADR-0001",
str(raised.exception),
)
def test_artifact_is_deterministic_marker_only_and_secret_free(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-vps-enrollment-artifact-",
) as directory:
root = Path(directory)
first = self.build(root, "device-plane-vps-enrollment-unit-001")
self.assertEqual(first.returncode, 0, first.stderr)
first_result = json.loads(first.stdout)
first_bytes = Path(first_result["artifact"]).read_bytes()
second = self.build(root, "device-plane-vps-enrollment-unit-001")
self.assertEqual(second.returncode, 0, second.stderr)
second_result = json.loads(second.stdout)
second_bytes = Path(second_result["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(
first_result["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(
first_result["entries"],
list(RUNNER.DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_ENTRIES),
)
with tarfile.open(first_result["artifact"], "r:gz") as archive:
names = archive.getnames()
payload = b"\n".join(
archive.extractfile(member).read()
for member in archive.getmembers()
if member.isfile()
)
self.assertEqual(
set(names),
{
"manifest.env",
"files.txt",
"payload",
"payload/deployment",
"payload/deployment/device-plane-backhaul-vps-enrollment-v1.json",
},
)
self.assertNotIn(b"PRIVATE KEY", payload)
self.assertNotIn(b"authorized_keys", payload)
def test_registry_selects_only_existing_target_without_build(self):
entries = RUNNER.DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_ENTRIES
self.assertTrue(
RUNNER.is_device_plane_backhaul_vps_enrollment_slice(
"device-plane",
entries,
)
)
self.assertEqual(
RUNNER.component_services("device-plane", entries),
(RUNNER.DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,),
)
self.assertEqual(RUNNER.component_builds("device-plane", entries), ())
def test_vps_public_key_is_pinned_by_computed_fingerprint(self):
blob = (
b"\x00\x00\x00\x0bssh-ed25519\x00\x00\x00\x20"
+ bytes(range(32))
)
key = (
"ssh-ed25519 "
+ base64.b64encode(blob).decode("ascii")
+ " source-comment\n"
)
fingerprint = (
"SHA256:"
+ base64.b64encode(hashlib.sha256(blob).digest())
.decode("ascii")
.rstrip("=")
)
with tempfile.TemporaryDirectory(
prefix="nodedc-vps-enrollment-key-",
) as directory:
path = Path(directory) / "device-edge-vps-backhaul.pub"
path.write_text(key, encoding="ascii")
with (
mock.patch.object(
RUNNER,
"DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PUBLIC_KEY_FILE",
path,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_FINGERPRINT",
fingerprint,
),
):
enrollment = (
RUNNER.read_device_plane_backhaul_vps_enrollment_public_key()
)
self.assertEqual(enrollment["fingerprint"], fingerprint)
self.assertTrue(
enrollment["line"].endswith(
" nodedc-device-edge-vps-backhaul"
)
)
def test_runtime_authorized_key_is_external_and_atomic(self):
blob = (
b"\x00\x00\x00\x0bssh-ed25519\x00\x00\x00\x20"
+ bytes(reversed(range(32)))
)
enrollment = {
"line": (
"ssh-ed25519 "
+ base64.b64encode(blob).decode("ascii")
+ " nodedc-device-edge-vps-backhaul"
)
}
with tempfile.TemporaryDirectory(
prefix="nodedc-vps-enrollment-runtime-",
) as directory:
secret_dir = Path(directory) / "secret"
authorized = secret_dir / "authorized_keys"
with (
mock.patch.object(
RUNNER,
"DEVICE_PLANE_BACKHAUL_SECRET_DIR",
secret_dir,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE",
authorized,
),
mock.patch.object(RUNNER.os, "chown"),
):
digest = RUNNER.install_device_plane_backhaul_authorized_key(
enrollment
)
expected = (
'restrict,port-forwarding,permitopen="127.0.0.1:9921" '
+ enrollment["line"]
+ "\n"
)
self.assertEqual(authorized.read_text(encoding="ascii"), expected)
self.assertEqual(
digest,
hashlib.sha256(expected.encode("ascii")).hexdigest(),
)
self.assertEqual(authorized.stat().st_mode & 0o777, 0o444)
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -1,302 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = (
SCRIPT_DIR
/ "build-device-plane-foundation-network-publication-artifact.mjs"
)
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
COMPOSE = (
SCRIPT_DIR
/ "fixtures/device-plane-foundation-network-publication-v1.yml"
)
PREDECESSOR_COMPOSE = (
SCRIPT_DIR
/ "fixtures/device-plane-foundation-internal-only-v1.yml"
)
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_network_publication_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DevicePlaneFoundationNetworkPublicationArtifactTest(
unittest.TestCase
):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(BUILDER), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_artifact_is_exact_deterministic_and_database_free(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-network-publication-",
) as directory:
artifact_dir = Path(directory)
patch_id = (
"device-plane-foundation-network-publication-unit-003"
)
first = self.build(artifact_dir, patch_id)
first_bytes = Path(first["artifact"]).read_bytes()
second = self.build(artifact_dir, patch_id)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(
first["transition"],
"failed-foundation-network-publication-correction",
)
self.assertEqual(first["build"], [])
self.assertEqual(
first["services"],
["device-control-core", "device-gateway"],
)
self.assertNotIn("device-postgres", first["services"])
self.assertEqual(
first["entries"],
list(
RUNNER
.DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_ENTRIES
),
)
with tarfile.open(first["artifact"], "r:gz") as archive:
files = (
archive.extractfile("files.txt")
.read()
.decode("utf-8")
.splitlines()
)
compose = (
archive.extractfile(
"payload/docker-compose.device-plane.yml"
)
.read()
)
descriptor = json.loads(
archive.extractfile(
"payload/deployment/"
"device-plane-foundation-network-publication-v1.json"
)
.read()
.decode("utf-8")
)
self.assertEqual(files, first["entries"])
self.assertEqual(
hashlib.sha256(compose).hexdigest(),
RUNNER
.DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_COMPOSE_SHA256,
)
self.assertEqual(
descriptor,
RUNNER
.expected_device_plane_foundation_network_publication_descriptor(),
)
def test_compose_preserves_database_and_adds_control_network_only_to_stateless_services(
self,
):
compose = COMPOSE.read_text(encoding="utf-8")
predecessor = PREDECESSOR_COMPOSE.read_text(encoding="utf-8")
self.assertEqual(
hashlib.sha256(PREDECESSOR_COMPOSE.read_bytes()).hexdigest(),
RUNNER.DEVICE_PLANE_FOUNDATION_PREDECESSOR_COMPOSE_SHA256,
)
self.assertEqual(
hashlib.sha256(COMPOSE.read_bytes()).hexdigest(),
RUNNER
.DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_COMPOSE_SHA256,
)
self.assertIn("name: nodedc-device-plane-control", compose)
self.assertIn(
'com.docker.network.bridge.enable_ip_masquerade: "false"',
compose,
)
self.assertNotIn("nodedc-device-plane-control", predecessor)
postgres_section, stateless = compose.split(
" device-control-core:",
1,
)
self.assertNotIn("device-plane-control", postgres_section)
self.assertEqual(stateless.count(" - device-plane-control"), 2)
self.assertNotIn("9921:9921", compose)
def test_runner_selects_exact_services_no_build_and_no_deps(self):
entries = (
RUNNER.DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_ENTRIES
)
services = RUNNER.component_services("device-plane", entries)
self.assertEqual(
services,
("device-control-core", "device-gateway"),
)
self.assertEqual(
RUNNER.component_builds("device-plane", entries),
(),
)
with (
mock.patch.object(RUNNER, "run_build") as run_build,
mock.patch.object(
RUNNER,
"prepare_component_runtime",
) as prepare,
mock.patch.object(RUNNER, "run_compose") as run_compose,
):
RUNNER.run_component_runtime(
"device-plane",
entries,
services,
)
run_build.assert_not_called()
prepare.assert_called_once_with("device-plane", entries)
run_compose.assert_called_once_with(
"device-plane",
services,
entries,
)
with mock.patch.object(RUNNER.subprocess, "run") as run:
RUNNER.run_compose("device-plane", services, entries)
command = run.call_args_list[0].args[0]
self.assertIn("--force-recreate", command)
self.assertIn("--no-deps", command)
self.assertEqual(
command[-2:],
["device-control-core", "device-gateway"],
)
def test_terminal_failed_recovery_is_rejected(self):
with self.assertRaisesRegex(
RUNNER.DeployError,
"recovery 002 is terminal failed",
):
RUNNER.reject_terminal_device_plane_foundation_artifact(
{
"id":
RUNNER
.DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID,
},
"0" * 64,
)
with self.assertRaisesRegex(
RUNNER.DeployError,
"recovery 002 is terminal failed",
):
RUNNER.reject_terminal_device_plane_foundation_artifact(
{"id": "unrelated"},
RUNNER
.DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT_SHA256,
)
def test_rollback_is_stateless_and_removes_only_control_network(self):
entries = (
RUNNER.DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_ENTRIES
)
services = ("device-control-core", "device-gateway")
runtime = {
"device-control-core": {"containerId": "1" * 64},
"device-gateway": {"containerId": "2" * 64},
"device-postgres": {
"containerId":
RUNNER
.DEVICE_PLANE_FOUNDATION_PREDECESSOR_CONTAINER_IDS[
"device-postgres"
],
},
}
with (
mock.patch.object(
RUNNER,
"stop_and_remove_compose_services",
) as stop,
mock.patch.object(
RUNNER,
"restore_platform_overlay",
return_value=9,
) as restore,
mock.patch.object(
RUNNER,
"remove_device_plane_control_network_if_unused",
return_value="removed",
) as remove_network,
mock.patch.object(RUNNER, "run_compose") as compose,
mock.patch.object(
RUNNER,
"healthcheck_compose_service",
) as health,
mock.patch.object(
RUNNER,
"validate_device_plane_foundation_runtime",
return_value=runtime,
) as validate_runtime,
mock.patch.object(
RUNNER,
"assert_loopback_tcp_port_closed",
) as closed,
):
result = (
RUNNER
.rollback_device_plane_network_publication_apply(
Path("/live"),
Path("/backup"),
entries,
"stamp",
True,
services,
)
)
self.assertEqual(result, "source+internal-runtime-restored:9")
stop.assert_called_once_with("device-plane", services)
restore.assert_called_once()
remove_network.assert_called_once_with()
compose.assert_called_once_with(
"device-plane",
services,
("docker-compose.device-plane.yml",),
)
self.assertEqual(health.call_count, 3)
validate_runtime.assert_called_once_with()
self.assertEqual(
[call.args[0] for call in closed.call_args_list],
[18120, 18121, 9921],
)
if __name__ == "__main__":
unittest.main()
@@ -1,386 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import shutil
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
RECOVERY_BUILDER = (
SCRIPT_DIR / "build-device-plane-foundation-recovery-artifact.mjs"
)
FOUNDATION_BUILDER = SCRIPT_DIR / "build-device-plane-artifact.mjs"
PREDECESSOR_COMPOSE = (
SCRIPT_DIR
/ "fixtures/device-plane-foundation-internal-only-v1.yml"
)
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
RECOVERY_ENTRIES = [
".dockerignore",
"package.json",
"package-lock.json",
"docker-compose.device-plane.yml",
"packages/device-protocol-contract",
"packages/arusnavi-b2-adapter",
"services/device-control-core",
"services/device-gateway",
"deployment/device-plane-foundation-recovery-v1.json",
]
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_recovery_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DevicePlaneFoundationRecoveryArtifactTest(unittest.TestCase):
def build(self, builder, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(builder), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_recovery_artifact_is_source_only_exact_and_deterministic(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-recovery-artifact-",
) as directory:
artifact_dir = Path(directory)
first = self.build(
RECOVERY_BUILDER,
artifact_dir,
"device-plane-foundation-recovery-unit-002",
)
artifact = Path(first["artifact"])
first_bytes = artifact.read_bytes()
second = self.build(
RECOVERY_BUILDER,
artifact_dir,
"device-plane-foundation-recovery-unit-002",
)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first["component"], "device-plane")
self.assertEqual(first["entries"], RECOVERY_ENTRIES)
self.assertEqual(first["build"], [])
self.assertEqual(first["services"], [])
self.assertEqual(
first["transition"],
"failed-foundation-live-runtime-adoption",
)
self.assertEqual(first_bytes, second_bytes)
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
with tarfile.open(artifact, "r:gz") as archive:
names = {member.name for member in archive.getmembers()}
files = (
archive.extractfile("files.txt")
.read()
.decode("utf-8")
.splitlines()
)
descriptor = json.loads(
archive.extractfile(
"payload/deployment/"
"device-plane-foundation-recovery-v1.json"
)
.read()
.decode("utf-8")
)
self.assertEqual(files, RECOVERY_ENTRIES)
self.assertIn(
"payload/services/device-control-core/src/server.mjs",
names,
)
self.assertIn(
"payload/services/device-gateway/src/server.mjs",
names,
)
self.assertFalse(any(
"/test/" in name
or "/node_modules/" in name
or Path(name).name.startswith(".env")
or name.startswith("payload/runtime/")
or name.startswith("payload/secrets/")
for name in names
))
self.assertEqual(
descriptor,
RUNNER.expected_device_plane_foundation_recovery_descriptor(),
)
self.assertEqual(
RUNNER.component_services(
"device-plane",
tuple(RECOVERY_ENTRIES),
),
(),
)
self.assertEqual(
RUNNER.component_builds(
"device-plane",
tuple(RECOVERY_ENTRIES),
),
(),
)
def test_preflight_requires_exact_failed_evidence_partial_source_and_runtime(
self,
):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-recovery-preflight-",
) as directory:
workspace = Path(directory)
artifacts = workspace / "artifacts"
failed_root = workspace / "failed"
backups_root = workspace / "backups"
state_root = workspace / "state"
temp_root = workspace / "tmp"
live_root = workspace / "live"
for path in (
artifacts,
failed_root,
backups_root,
state_root,
temp_root,
live_root / "deployment",
):
path.mkdir(parents=True, exist_ok=True)
failed_build = self.build(
FOUNDATION_BUILDER,
artifacts,
RUNNER.DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID,
)
self.assertEqual(
failed_build["sha256"],
hashlib.sha256(
Path(failed_build["artifact"]).read_bytes()
).hexdigest(),
)
failed_artifact = (
failed_root / RUNNER.DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT
)
shutil.copy2(failed_build["artifact"], failed_artifact)
recovery_build = self.build(
RECOVERY_BUILDER,
artifacts,
"device-plane-foundation-recovery-unit-002",
)
recovery_extract = workspace / "recovery-extract"
recovery_extract.mkdir()
_manifest, entries, payload = RUNNER.load_artifact(
Path(recovery_build["artifact"]),
recovery_extract,
)
self.assertEqual(entries, RECOVERY_ENTRIES)
backup = (
backups_root
/ RUNNER.DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_ID
)
backup.mkdir()
for name in (
"manifest.env",
"files.txt",
"existing-files.txt",
"missing-files.txt",
"source-before.tgz",
):
(backup / name).write_text(
f"fixture:{name}\n",
encoding="utf-8",
)
backup_hashes = {
name: hashlib.sha256((backup / name).read_bytes()).hexdigest()
for name in (
"manifest.env",
"files.txt",
"existing-files.txt",
"missing-files.txt",
"source-before.tgz",
)
}
(state_root / "failed.jsonl").write_text(
json.dumps({
"artifact":
RUNNER.DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT,
"backup_id":
RUNNER.DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_ID,
"component": "device-plane",
"id": RUNNER.DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID,
"message":
"healthcheck failed for "
"http://127.0.0.1:18120/healthz: "
"<urlopen error [Errno 111] Connection refused>",
"rollback_status": "failed:DeployError",
"sha256":
failed_build["sha256"],
"started_apply": True,
"status": "failed",
})
+ "\n",
encoding="utf-8",
)
source_root = SCRIPT_DIR.parent.parent / "device-plane"
shutil.copy2(
PREDECESSOR_COMPOSE,
live_root / "docker-compose.device-plane.yml",
)
shutil.copy2(
source_root
/ "deployment/device-postgres-bootstrap-v1.json",
live_root
/ "deployment/device-postgres-bootstrap-v1.json",
)
runtime = {
service: {
"containerId": service,
"imageId":
RUNNER.DEVICE_PLANE_FOUNDATION_RECOVERY_IMAGE_IDS[
service
],
"health": "healthy",
"restartCount": 0,
}
for service in (
"device-control-core",
"device-gateway",
"device-postgres",
)
}
with (
mock.patch.object(RUNNER, "BACKUPS_DIR", backups_root),
mock.patch.object(RUNNER, "FAILED_DIR", failed_root),
mock.patch.object(
RUNNER,
"FAILED_STATE_FILE",
state_root / "failed.jsonl",
),
mock.patch.object(RUNNER, "TMP_DIR", temp_root),
mock.patch.object(
RUNNER,
"component_root",
return_value=live_root,
),
mock.patch.object(
RUNNER,
"DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT_SHA256",
failed_build["sha256"],
),
mock.patch.dict(
RUNNER.DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_SHA256,
backup_hashes,
clear=True,
),
mock.patch.object(
RUNNER,
"validate_device_plane_foundation_runtime",
return_value=runtime,
),
):
result = (
RUNNER
.validate_device_plane_foundation_recovery_evidence(
payload
)
)
self.assertEqual(
result["mode"],
"failed-foundation-live-runtime-adoption",
)
self.assertEqual(result["runtime"], runtime)
def test_recovery_health_acceptance_never_mutates_runtime(self):
entries = tuple(RECOVERY_ENTRIES)
checks = ("core-health", "gateway-health")
with (
mock.patch.object(
RUNNER,
"healthcheck_compose_service",
) as compose_health,
mock.patch.object(
RUNNER,
"component_healthchecks",
return_value=checks,
),
mock.patch.object(RUNNER, "healthcheck_url") as url_health,
mock.patch.object(
RUNNER,
"validate_device_plane_foundation_installed_source",
) as source_acceptance,
mock.patch.object(
RUNNER,
"validate_device_plane_foundation_runtime",
) as runtime_acceptance,
mock.patch.object(RUNNER, "run_compose") as compose_mutation,
mock.patch.object(RUNNER, "run_build") as build_mutation,
):
RUNNER.run_healthchecks("device-plane", entries, ())
self.assertEqual(
[call.args for call in compose_health.call_args_list],
[
("device-plane", "device-control-core"),
("device-plane", "device-gateway"),
("device-plane", "device-postgres"),
],
)
self.assertEqual(
[call.args[0] for call in url_health.call_args_list],
list(checks),
)
source_acceptance.assert_called_once_with()
runtime_acceptance.assert_called_once_with()
compose_mutation.assert_not_called()
build_mutation.assert_not_called()
def test_recovery_runtime_phase_has_no_runtime_mutation(self):
entries = tuple(RECOVERY_ENTRIES)
with (
mock.patch.object(
RUNNER,
"prepare_component_runtime",
) as prepare,
mock.patch.object(RUNNER, "run_compose") as compose_mutation,
mock.patch.object(RUNNER, "run_build") as build_mutation,
):
RUNNER.run_component_runtime("device-plane", entries, ())
prepare.assert_not_called()
compose_mutation.assert_not_called()
build_mutation.assert_not_called()
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -1,210 +0,0 @@
#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
SCRIPT_DIR = Path(__file__).resolve().parent
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
BUILDER = (
SCRIPT_DIR
/ "build-device-plane-postgres-bootstrap-artifact.mjs"
)
EXPECTED_ENTRIES = [
"docker-compose.device-plane.yml",
"deployment/device-postgres-bootstrap-v1.json",
]
def load_runner():
loader = importlib.machinery.SourceFileLoader(
"nodedc_device_plane_postgres_runner_under_test",
str(RUNNER_PATH),
)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
RUNNER = load_runner()
class DevicePlanePostgresBootstrapTest(unittest.TestCase):
def build(self, artifact_dir, patch_id):
environment = os.environ.copy()
environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
result = subprocess.run(
["node", str(BUILDER), patch_id],
check=True,
capture_output=True,
text=True,
env=environment,
)
return json.loads(result.stdout)
def test_bootstrap_artifact_is_exact_deterministic_and_runner_accepted(self):
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-postgres-artifact-",
) as directory:
artifact_dir = Path(directory)
first = self.build(
artifact_dir,
"device-plane-postgres-bootstrap-unit-001",
)
artifact = Path(first["artifact"])
first_bytes = artifact.read_bytes()
second = self.build(
artifact_dir,
"device-plane-postgres-bootstrap-unit-001",
)
second_bytes = Path(second["artifact"]).read_bytes()
self.assertEqual(first["entries"], EXPECTED_ENTRIES)
self.assertEqual(first["services"], ["device-postgres"])
self.assertEqual(first["mode"], "create-if-absent")
self.assertEqual(first["rollbackVolumePolicy"], "preserve")
self.assertEqual(
first["sha256"],
hashlib.sha256(first_bytes).hexdigest(),
)
self.assertEqual(first_bytes, second_bytes)
with tarfile.open(artifact, "r:gz") as archive:
names = {member.name for member in archive.getmembers()}
self.assertEqual(
archive.extractfile("files.txt")
.read()
.decode("utf-8")
.splitlines(),
EXPECTED_ENTRIES,
)
self.assertEqual(
names,
{
"manifest.env",
"files.txt",
"payload",
"payload/docker-compose.device-plane.yml",
"payload/deployment",
"payload/deployment/device-postgres-bootstrap-v1.json",
},
)
with tempfile.TemporaryDirectory(
prefix="nodedc-device-plane-postgres-load-",
) as work_directory:
manifest, entries, _payload = RUNNER.load_artifact(
artifact,
Path(work_directory),
)
self.assertEqual(manifest["component"], "device-plane")
self.assertEqual(entries, EXPECTED_ENTRIES)
self.assertTrue(
RUNNER.is_device_plane_postgres_bootstrap_slice(
manifest["component"],
entries,
),
)
self.assertEqual(
RUNNER.component_services("device-plane", entries),
("device-postgres",),
)
self.assertEqual(
RUNNER.component_builds("device-plane", entries),
(),
)
def test_preflight_accepts_only_absent_container_and_volume(self):
absent_container = mock.Mock(returncode=0, stdout="")
absent_volume = mock.Mock(returncode=1)
with mock.patch.object(
RUNNER.subprocess,
"run",
side_effect=[absent_container, absent_volume],
):
self.assertEqual(
RUNNER.preflight_device_plane_postgres_bootstrap(),
"absent",
)
existing_container = mock.Mock(
returncode=0,
stdout="abc123def456\n",
)
with mock.patch.object(
RUNNER.subprocess,
"run",
return_value=existing_container,
):
with self.assertRaisesRegex(
RUNNER.DeployError,
"container already exists",
):
RUNNER.preflight_device_plane_postgres_bootstrap()
with mock.patch.object(
RUNNER.subprocess,
"run",
side_effect=[
absent_container,
mock.Mock(returncode=0),
],
):
with self.assertRaisesRegex(
RUNNER.DeployError,
"volume already exists",
):
RUNNER.preflight_device_plane_postgres_bootstrap()
def test_plan_selection_is_unambiguous_for_bootstrap_and_application(self):
self.assertEqual(
RUNNER.device_plane_postgres_plan_selection(None),
"preserved-prerequisite:not-selected",
)
self.assertEqual(
RUNNER.device_plane_postgres_plan_selection("absent"),
"bootstrap-selected:create-if-absent",
)
with self.assertRaisesRegex(
RUNNER.DeployError,
"plan preflight state is invalid",
):
RUNNER.device_plane_postgres_plan_selection("unknown")
def test_bootstrap_health_acceptance_is_database_service_only(self):
with mock.patch.object(
RUNNER,
"healthcheck_compose_service",
) as health:
RUNNER.run_healthchecks(
"device-plane",
EXPECTED_ENTRIES,
("device-postgres",),
)
health.assert_called_once_with(
"device-plane",
"device-postgres",
)
with self.assertRaisesRegex(
RUNNER.DeployError,
"service set mismatch",
):
RUNNER.run_healthchecks(
"device-plane",
EXPECTED_ENTRIES,
("device-control-core",),
)
if __name__ == "__main__":
unittest.main(verbosity=2)