Files
legion_kk/tests/unit/node-deployment-service.test.ts
self cfbed53f96 refactor: unify provider identity
Use ProviderId as the canonical provider model identity and remove resource-level/provider-kind/source model duplication.

Treat manual as the provider id for manual servers and external domains, deriving domain DNS association from same-name zones.
2026-06-27 21:38:45 +02:00

1772 lines
56 KiB
TypeScript

import assert from "node:assert/strict"
import { mkdtemp, rm, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import test from "node:test"
import {
NodeDeploymentService,
parseInternalStatusNodePublicKey,
parseBootstrapProbeState,
parseLegionDeploymentStatusMarker,
summarizeAcmeCertificateIssue,
type ClusterMembershipSyncRequest,
type NodeDeploymentReporter,
type NodeDeploymentInstallSession
} from "../../src/main/deployment/service"
import { deriveNodePublicKey, encryptNostrPrivateKey } from "../../src/main/bootstrap-crypto"
import type {
ClusterBootstrapState,
ClusterMembershipRecord,
LegionAdminConfig,
VpsRecord
} from "../../src/shared/app"
import type { TribesDeploymentProfile, TribesHostConfig } from "../../src/shared/tribes-deployment"
const testDeploymentProfile: TribesDeploymentProfile = {
schemaVersion: "1",
plugins: [],
tribes: {
workingDirectory: "/var/lib/tribes",
serviceUser: "tribes",
serviceGroup: "tribes",
listenAddress: "127.0.0.1",
listenPort: 4000,
publicScheme: "https",
publicPort: 443,
syncPort: 4413,
syncBindAddress: "0.0.0.0",
syncOverlapSeconds: 300,
databaseUser: "tribes",
databaseName: "tribes",
parrhesiaDatabaseName: "parrhesia",
databaseHost: "/var/run/postgresql",
adminPubkeys: [],
releaseDistribution: "none",
logFile: "/var/log/tribes.log"
},
edge: {
certificateProfile: "shortlived",
renewDays: 4,
httpPort: 80,
httpsPort: 443,
challengeAddress: "127.0.0.1",
challengePort: 8080,
cacheAddress: "127.0.0.1",
cachePort: 6081,
cacheStorage: ["malloc,256M"]
}
}
const testHostConfig: TribesHostConfig = {
schemaVersion: "1",
tribes: {
workingDirectory: "/var/lib/tribes",
serviceUser: "tribes",
serviceGroup: "tribes",
host: "203.0.113.10",
listenAddress: "127.0.0.1",
listenPort: 4000,
scheme: "https",
port: 443,
syncHost: "203.0.113.10",
syncPort: 4413,
syncBindAddress: "0.0.0.0",
adminPubkeys: [],
plugins: [],
syncOverlapSeconds: 300,
databaseUser: "tribes",
databaseName: "tribes",
parrhesiaDatabaseName: "parrhesia",
databaseHost: "/var/run/postgresql",
secretKeyBaseFile: "/var/lib/tribes/secrets/secret_key_base",
tokenSigningSecretFile: "/var/lib/tribes/secrets/token_signing_secret",
releaseCookieFile: "/var/lib/tribes/secrets/release_cookie",
releaseDistribution: "none",
extraEnvironmentVariables: ["TRIBES_BOOTSTRAP_FILE=/etc/tribes/bootstrap.json"],
logFile: "/var/log/tribes.log"
},
edge: {
certificateName: "203-0-113-10",
certificateSubjects: ["203.0.113.10"],
certificateProfile: "shortlived",
renewDays: 4,
httpPort: 80,
httpsPort: 443,
challengeAddress: "127.0.0.1",
challengePort: 8080,
cacheAddress: "127.0.0.1",
cachePort: 6081,
cacheStorage: ["malloc,256M"]
}
}
type ServiceTestInternals = {
createAdminApiClient: (
request: { clusterBootstrap?: unknown },
target: { publicIpv4?: string }
) => {
clusterNodeUpsert(input: {
pubkey: string
transportAddress?: string
scope?: ClusterMembershipRecord["scope"]
status?: ClusterMembershipRecord["status"]
activatedAt?: string
deactivatedAt?: string
}): Promise<void>
clusterNodesList?(): Promise<{
nodes: Array<{
pubkey: string
transport_address: string
scope: ClusterMembershipRecord["scope"]
status: ClusterMembershipRecord["status"]
activated_at?: string
deactivated_at?: string | null
}>
}>
clusterStatus?(): Promise<{
servers: Array<{ auth_pubkey: string }>
}>
}
readClusterMembershipViews: (
request: { clusterBootstrap?: unknown },
targets: Array<{ publicIpv4?: string }>
) => Promise<Array<{ host: string; nodes: ClusterMembershipRecord[] }>>
}
function createTestClusterBootstrap(): ClusterBootstrapState {
return {
rootSecret: "root-secret",
relayPrivateKey: "relay-private-key",
legionAdmin: {
username: "legion-admin",
passwordHash: "password-hash",
nostrPublicKey: "f".repeat(64),
encryptedNostrPrivateKey: "ciphertext",
nostrPrivateKeyNonce: "nonce",
nostrPrivateKeySalt: "salt"
}
}
}
function createTestLegionAdmin(): LegionAdminConfig {
return {
id: "legion-managed-admin",
username: "legion-admin",
role: "admin",
publicKeyHex: "f".repeat(64),
npub: "npub-test",
encryptedPrivateKey: encryptNostrPrivateKey("root-secret", "1".repeat(64)),
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z"
}
}
function createManagedBootstrapSession(pubkey: string): NodeDeploymentInstallSession {
return {
request: {
server: {
id: "node-a",
publicIp: "203.0.113.10",
sshUsername: "root",
sshPrivateKey: "private-key"
},
bootstrapMode: "init",
managedBootstrap: {
nodePrivateKey: "a".repeat(64),
nodePublicKey: pubkey,
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https"
},
nbde: {
mode: "degraded",
tangPort: 7654,
recoverySecret: "secret",
localBootKeyPresent: true,
peerTangNodeIds: [],
peerTangUrls: []
},
kexecImagePath: "/tmp/guix-kexec-installer.tar.gz",
bootMode: "bios"
} as unknown as NodeDeploymentInstallSession["request"],
ssh: {
host: "203.0.113.10",
username: "root",
privateKey: "private-key"
},
nodePublicKey: pubkey,
deploymentProfile: testDeploymentProfile,
hostConfig: testHostConfig,
hostConfigJson: `${JSON.stringify(testHostConfig, null, 2)}\n`,
bootstrapJson: '{\n "contract_version": "1"\n}\n',
serviceSecrets: {
secretKeyBase: "secret-key-base",
tokenSigningSecret: "token-signing-secret"
},
releaseCookie: "release-cookie",
publicHost: "203.0.113.10"
}
}
async function immediateDelay<T = void>(_delay?: number, value?: T): Promise<T> {
return value as T
}
function createClusterMembershipTarget(
seed: string,
host: string
): {
nodePrivateKey: string
publicIpv4: string
publicPort: number
publicScheme: "https"
} {
return {
nodePrivateKey: seed.repeat(64),
publicIpv4: host,
publicPort: 443,
publicScheme: "https"
}
}
function createClusterMembershipRecord(seed: string, host: string): ClusterMembershipRecord {
return {
pubkey: seed.repeat(64),
transportAddress: `wss://${host}:4413/relay`,
scope: "all",
status: "active",
activatedAt: "2026-04-06T12:00:00.000Z"
}
}
function createServerRecord(overrides: Partial<VpsRecord> = {}): VpsRecord {
return {
id: "node-a",
provider: "hetzner",
role: "primary",
sshUsername: "root",
sshPrivateKey: "private-key",
status: "running",
publicIp: "203.0.113.10",
region: "fsn1",
planName: "CAX11",
commercialMetadata: {
effectiveMonthlyGrossEur: 4,
source: "provider",
notes: []
},
deployment: {
status: "ready",
snippets: []
},
createdAt: "2026-04-06T12:00:00.000Z",
updatedAt: "2026-04-06T12:00:00.000Z",
...overrides
}
}
test("reconcileNbdeNode uses transient SSH retry wrappers", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
let uploaded = false
let command: string | undefined
service["uploadSshFilesWithTransientSshRetry"] = async (
ssh: { host: string },
files: Array<{ remotePath: string; mode: number }>
) => {
uploaded = true
assert.equal(ssh.host, "203.0.113.10")
assert.equal(files[0]?.remotePath, "/tmp/legion/nbde-sync")
assert.equal(files[0]?.mode, 0o700)
}
service["runStreamingCommandWithTransientSshRetry"] = async (
ssh: { host: string },
nextCommand: string,
streamOutput: boolean
) => {
assert.equal(ssh.host, "203.0.113.10")
command = nextCommand
assert.equal(streamOutput, true)
}
await service.reconcileNbdeNode({
server: createServerRecord(),
managedBootstrap: {
nodePrivateKey: "a".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https"
},
nbde: {
mode: "degraded",
luksUuid: "test-luks-uuid",
recoverySecret: "test-recovery-secret",
localBootKey: "test-local-boot-key",
peerTangUrls: []
}
} as never)
assert.equal(uploaded, true)
assert.match(command ?? "", /LEGION_NBDE_MODE='degraded'/)
assert.match(command ?? "", /LEGION_NBDE_LUKS_UUID='test-luks-uuid'/)
assert.match(command ?? "", /sh \/tmp\/legion\/nbde-sync/)
})
test("waitForNodeReady returns the observed pubkey for managed bootstrap nodes", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
service["waitForTangReady"] = async () => {}
service["waitForBootstrapReady"] = async () => {}
service["readNodePublicKey"] = async () => "a".repeat(64)
service["readNbdeLuksUuid"] = async () => "uuid-1"
const result = await service.waitForNodeReady(session)
assert.equal(result.nodePublicKey, "a".repeat(64))
assert.equal(result.nbde.luksUuid, "uuid-1")
})
test("LEGION_TEST_CERT_MODE=self-signed forces self-signed deployment profiles", async () => {
const previousMode = process.env["LEGION_TEST_CERT_MODE"]
process.env["LEGION_TEST_CERT_MODE"] = "self-signed"
try {
const service = new NodeDeploymentService()
const sshPublicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBkGzctnVw9laH2L8N8Pqj9QY0aY4vS2d15YvH8lU2rA"
const session = await service.prepareReconfigureSession({
server: createServerRecord({
sshPublicKey
}),
bootstrapMode: "init",
managedBootstrap: {
nodePrivateKey: "a".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https"
},
publicHost: "203.0.113.10",
certificateName: "203-0-113-10",
certificateSubjects: ["203.0.113.10"],
certificateChallengeMode: "http",
desiredClusterMembership: [
{
nodeId: "node-a",
pubkey: "a".repeat(64),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
}
],
clusterTargets: [],
nbde: {
mode: "degraded",
tangPort: 7654,
recoverySecret: "secret",
localBootKeyPresent: true,
peerTangNodeIds: [],
peerTangUrls: []
},
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
tribeName: "Test Tribe",
tribeDescription: "Test tribe",
tribeVisibility: "invite_only",
kexecImagePath: "/tmp/guix-kexec-installer.tar.gz",
bootMode: "bios"
})
assert.equal(session.deploymentProfile.edge.certificateProfile, "self-signed")
assert.equal(session.hostConfig.edge.certificateProfile, "self-signed")
} finally {
if (typeof previousMode === "string") {
process.env["LEGION_TEST_CERT_MODE"] = previousMode
} else {
delete process.env["LEGION_TEST_CERT_MODE"]
}
}
})
test("join deployment profile uses resolved rollout plugins from upstream", async () => {
const service = new NodeDeploymentService()
const internals = service as unknown as Record<string, unknown>
const requestedTarget = {
channels: [
{
channel_id: "channel-1",
commit: "commit-a",
position: 10
}
],
plugins: [
{
plugin_name: "tribe-one-kobold",
channel_id: null,
enabled: true
}
],
rolloutPolicy: {
prepare_timeout_seconds: 1800,
commit_timeout_seconds: 300,
require_all_nodes_ready: true
}
}
internals["waitForPublicAdminEndpointReady"] = async () => {}
internals["createAdminApiClient"] = () => ({
clusterPreviewRollout: async () => ({
ok: true,
schema_version: "1",
mode: "auto_switch",
target: requestedTarget,
preview: {},
plan: {
plan_schema_version: "1",
plan_hash: "plan-test",
resolved_channels: [
{
channel_id: "channel-1",
name: "tribes",
url: "https://git.example.test/guix-tribes.git",
branch: "master",
commit: "commit-a",
introduction: {
commit: "intro-a",
fingerprint: "FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF"
},
position: 10
}
],
resolved_plugins: [
{
name: "tribe-one-trust",
enabled: true
},
{
name: "tribe-one-kobold",
enabled: true
}
],
resolved_extra_packages: [],
core_migration_target: false,
core_destructive_rollback_migrations: [],
closure_estimate_bytes: false
}
}),
clusterUpdateSourcesExport: async () => ({
ok: true,
schema_version: "1",
generated_at: "2026-06-18T00:00:00.000Z",
update_defaults: {
trusted_signers: [
{
id: "signer-id-1",
label: "cluster signer",
fingerprint: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
enabled: true
}
],
channels: [
{
id: "channel-id-1",
name: "tribes",
url: "https://git.example.test/guix-tribes.git",
branch: "master",
tracked_commit: "commit-a",
introduction: {
commit: "intro-a",
fingerprint: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
},
allowed_signer_ids: ["signer-id-1"],
position: 10,
enabled: true
}
],
substitute_servers: [
{
id: "server-id-1",
name: "guix.example.test",
url: "https://guix.example.test",
position: 10,
enabled: true
}
],
substitute_keys: [
{
id: "key-id-1",
label: "guix.example.test",
public_key: "guix.example.test:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
enabled: true
}
]
}
})
})
const sshPublicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBkGzctnVw9laH2L8N8Pqj9QY0aY4vS2d15YvH8lU2rA"
const session = await service.prepareReconfigureSession({
server: createServerRecord({ id: "node-b", publicIp: "203.0.113.11", sshPublicKey }),
bootstrapMode: "join",
managedBootstrap: {
nodePrivateKey: "b".repeat(64),
publicIpv4: "203.0.113.11",
publicPort: 443,
publicScheme: "https"
},
publicHost: "203.0.113.11",
certificateName: "203-0-113-11",
certificateSubjects: ["203.0.113.11"],
certificateChallengeMode: "http",
upstream: {
server: createServerRecord({ id: "node-a", sshPublicKey }),
managedBootstrap: {
nodePrivateKey: "a".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https"
}
},
desiredClusterMembership: [
{
pubkey: "a".repeat(64),
transportAddress: "https://203.0.113.10:4413",
scope: "all",
status: "active",
activatedAt: "2026-05-28T00:00:00.000Z"
}
],
clusterTargets: [],
nbde: {
mode: "degraded",
tangPort: 7654,
recoverySecret: "secret",
localBootKeyPresent: true,
peerTangNodeIds: [],
peerTangUrls: []
},
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
tribeName: "Test Tribe",
tribeDescription: "Test tribe",
tribeVisibility: "invite_only",
bootMode: "bios"
})
const bootstrap = JSON.parse(session.bootstrapJson)
assert.deepEqual(session.deploymentProfile.plugins, ["tribe-one-kobold", "tribe-one-trust"])
assert.deepEqual(session.hostConfig.tribes.plugins, ["tribe-one-kobold", "tribe-one-trust"])
assert.equal(session.deploymentProfile.channel?.url, "https://git.example.test/guix-tribes.git")
assert.equal(session.deploymentProfile.channel?.commit, "commit-a")
assert.equal(bootstrap.update_defaults.trusted_signers[0].id, "signer-id-1")
assert.equal(bootstrap.update_defaults.channels[0].id, "channel-id-1")
assert.deepEqual(bootstrap.update_defaults.channels[0].allowed_signer_ids, ["signer-id-1"])
assert.equal(bootstrap.update_defaults.substitute_servers[0].id, "server-id-1")
assert.equal(bootstrap.update_defaults.substitute_keys[0].id, "key-id-1")
})
test("summarizeAcmeCertificateIssue reports ACME rate limits explicitly", () => {
const summary = summarizeAcmeCertificateIssue(
"203.0.113.10",
"lego: ACME rate-limited; skipping immediate retries and keeping existing/self-signed certificate.\nacme:error:ratelimited: too many certificates already issued for this exact set of identifiers"
)
assert.equal(
summary,
"ACME rate limit detected for 203.0.113.10: acme:error:ratelimited: too many certificates already issued for this exact set of identifiers. 203.0.113.10 kept serving the bootstrap self-signed certificate because no fresh certificate was issued."
)
})
test("summarizeAcmeCertificateIssue reports specific ACME failures", () => {
const summary = summarizeAcmeCertificateIssue(
"203.0.113.10",
"Error: one or more domains had a problem:\n[203.0.113.10] authorization failed: HTTP 400 urn:ietf:params:acme:error:connection: timeout during connect"
)
assert.equal(
summary,
"ACME certificate provisioning failed for 203.0.113.10: [203.0.113.10] authorization failed: HTTP 400 urn:ietf:params:acme:error:connection: timeout during connect. HAProxy may still be serving the bootstrap self-signed certificate."
)
})
test("summarizeAcmeCertificateIssue falls back to the latest lego line when parsing is inconclusive", () => {
const summary = summarizeAcmeCertificateIssue(
"203.0.113.10",
"mode=run\nstatus=ok\ncertificate updated"
)
assert.equal(
summary,
"ACME certificate diagnostics for 203.0.113.10 were inconclusive. Latest lego output: certificate updated. The node still served the bootstrap self-signed certificate after provisioning, so certificate installation or reload may have failed."
)
})
test("waitForNodeReady publishes join membership to existing targets before waiting for sync", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
const request = session.request as NodeDeploymentInstallSession["request"] &
Record<string, unknown>
const joinTarget = {
nodePrivateKey: "a".repeat(64),
nodePublicKey: "a".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
}
const upstreamTarget = {
nodePrivateKey: "c".repeat(64),
nodePublicKey: "c".repeat(64),
publicIpv4: "203.0.113.20",
publicPort: 443,
publicScheme: "https" as const
}
request.bootstrapMode = "join"
request.managedBootstrap = joinTarget
request.clusterBootstrap = createTestClusterBootstrap()
request.desiredClusterMembership = [
{
nodeId: "node-a",
pubkey: "a".repeat(64),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
},
{
nodeId: "node-upstream",
pubkey: "c".repeat(64),
transportAddress: "wss://203.0.113.20:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-09T00:00:00.000Z"
}
]
request.clusterTargets = [
{
managedBootstrap: joinTarget,
server: createServerRecord({
id: "node-a",
publicIp: "203.0.113.10",
sshPrivateKey: "join-key"
})
},
{
managedBootstrap: upstreamTarget,
server: createServerRecord({
id: "node-upstream",
publicIp: "203.0.113.20",
sshPrivateKey: "upstream-key"
})
}
]
let syncRequest: ClusterMembershipSyncRequest | undefined
const restartCommands: string[] = []
const order: string[] = []
service["waitForTangReady"] = async () => {}
service["syncClusterMembership"] = async (input: ClusterMembershipSyncRequest) => {
order.push("sync")
syncRequest = input
}
service["waitForClusterMeshPeerReady"] = async (input: { peerPubkey: string }) => {
order.push("mesh")
assert.equal(input.peerPubkey, "a".repeat(64))
}
service["runStreamingCommand"] = async (_ssh: unknown, command: string) => {
order.push("restart")
restartCommands.push(command)
}
service["waitForPublicAdminEndpointReady"] = async () => {}
service["waitForBootstrapPublicationBarrier"] = async () => {
order.push("barrier")
}
service["readNodePublicKey"] = async () => "a".repeat(64)
service["readNbdeLuksUuid"] = async () => "uuid-join"
const result = await service.waitForNodeReady(session)
assert.equal(result.nodePublicKey, "a".repeat(64))
assert.equal(result.nbde.luksUuid, "uuid-join")
assert.ok(syncRequest)
assert.deepEqual(syncRequest.clusterTargets, [upstreamTarget])
assert.deepEqual(syncRequest.preflightTargets, [upstreamTarget])
assert.equal(syncRequest.preserveExistingMembers, true)
assert.equal(syncRequest.allowSyncingForActive, true)
assert.deepEqual(order, ["sync", "mesh", "restart", "barrier"])
assert.deepEqual(restartCommands, [
"herd restart tribes || { herd stop tribes || true; herd start tribes; }"
])
})
test("reconcileClusterMembership allows syncing views for active members", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
const request = session.request as NodeDeploymentInstallSession["request"] &
Record<string, unknown>
const target = {
nodePrivateKey: "a".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
}
let syncRequest: ClusterMembershipSyncRequest | undefined
request.bootstrapMode = "join"
request.managedBootstrap = target
request.clusterBootstrap = createTestClusterBootstrap()
request.clusterTargets = [
{
managedBootstrap: target,
server: createServerRecord({ id: "node-a", publicIp: "203.0.113.10" })
}
]
service["syncClusterMembership"] = async (input: ClusterMembershipSyncRequest) => {
syncRequest = input
}
await service.reconcileClusterMembership(session)
assert.ok(syncRequest)
assert.equal(syncRequest.allowSyncingForActive, true)
})
test("syncClusterMembership preserves members discovered during convergence", async () => {
const service = new NodeDeploymentService(undefined, {
delay: immediateDelay
}) as NodeDeploymentService & Record<string, unknown>
const targetA = createClusterMembershipTarget("a", "203.0.113.10")
const targetB = createClusterMembershipTarget("b", "203.0.113.11")
const nodeA = createClusterMembershipRecord("a", "203.0.113.10")
const nodeB = createClusterMembershipRecord("b", "203.0.113.11")
const nodeC = createClusterMembershipRecord("c", "203.0.113.12")
const viewsByHost = new Map<string, ClusterMembershipRecord[]>([
["203.0.113.10", [nodeA, nodeB, nodeC]],
["203.0.113.11", [nodeA, nodeB]]
])
const upserts: Array<{ host: string; pubkey: string; status?: string }> = []
service["waitForPublicAdminEndpointReady"] = async () => {}
;(service as unknown as ServiceTestInternals).createAdminApiClient = (_request, target) => ({
clusterNodeUpsert: async (input) => {
const host = target.publicIpv4 ?? "unknown"
const current = viewsByHost.get(host) ?? []
const nextEntry: ClusterMembershipRecord = {
pubkey: input.pubkey,
transportAddress: input.transportAddress ?? `wss://${host}:4413/relay`,
scope: input.scope ?? "all",
status: input.status ?? "active",
activatedAt: input.activatedAt ?? "2026-04-06T12:00:00.000Z",
deactivatedAt: input.deactivatedAt
}
viewsByHost.set(host, [
...current.filter((entry) => entry.pubkey !== input.pubkey),
nextEntry
])
upserts.push({ host, pubkey: input.pubkey, status: input.status })
},
clusterNodesList: async () => ({
nodes: (viewsByHost.get(target.publicIpv4 ?? "unknown") ?? []).map((entry) => ({
pubkey: entry.pubkey,
transport_address: entry.transportAddress,
scope: entry.scope,
status: entry.status,
activated_at: entry.activatedAt,
deactivated_at: entry.deactivatedAt ?? null
}))
})
})
await service.syncClusterMembership({
desiredClusterMembership: [nodeA, nodeB],
clusterTargets: [targetA, targetB],
preflightTargets: [],
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
preserveExistingMembers: true
})
assert.ok(upserts.some((entry) => entry.host === "203.0.113.11" && entry.pubkey === nodeC.pubkey))
assert.deepEqual(
[...(viewsByHost.get("203.0.113.11") ?? [])].map((entry) => entry.pubkey).sort(),
[nodeA.pubkey, nodeB.pubkey, nodeC.pubkey].sort()
)
})
test("syncClusterMembership keeps desired decommissioned status over observed active members", async () => {
const service = new NodeDeploymentService(undefined, {
delay: immediateDelay
}) as NodeDeploymentService & Record<string, unknown>
const target = createClusterMembershipTarget("a", "203.0.113.10")
const nodeA = createClusterMembershipRecord("a", "203.0.113.10")
const failedNode = createClusterMembershipRecord("b", "203.0.113.11")
const desiredFailedNode = {
...failedNode,
status: "decommissioned" as const,
deactivatedAt: "2026-04-06T13:00:00.000Z"
}
const view = [nodeA, failedNode]
service["waitForPublicAdminEndpointReady"] = async () => {}
;(service as unknown as ServiceTestInternals).createAdminApiClient = () => ({
clusterNodeUpsert: async (input) => {
const nextEntry: ClusterMembershipRecord = {
pubkey: input.pubkey,
transportAddress: input.transportAddress ?? `wss://203.0.113.10:4413/relay`,
scope: input.scope ?? "all",
status: input.status ?? "active",
activatedAt: input.activatedAt ?? "2026-04-06T12:00:00.000Z",
deactivatedAt: input.deactivatedAt
}
const index = view.findIndex((entry) => entry.pubkey === input.pubkey)
if (index >= 0) {
view[index] = nextEntry
} else {
view.push(nextEntry)
}
},
clusterNodesList: async () => ({
nodes: view.map((entry) => ({
pubkey: entry.pubkey,
transport_address: entry.transportAddress,
scope: entry.scope,
status: entry.status,
activated_at: entry.activatedAt,
deactivated_at: entry.deactivatedAt ?? null
}))
})
})
await service.syncClusterMembership({
desiredClusterMembership: [nodeA, desiredFailedNode],
clusterTargets: [target],
preflightTargets: [],
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
preserveExistingMembers: true
})
assert.equal(view.find((entry) => entry.pubkey === failedNode.pubkey)?.status, "decommissioned")
})
test("waitForClusterMeshPeerReady observes mesh status for every existing target", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const targets = [
{
nodePrivateKey: "1".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
},
{
nodePrivateKey: "2".repeat(64),
publicIpv4: "203.0.113.11",
publicPort: 443,
publicScheme: "https" as const
}
]
const statusReads: string[] = []
const serviceInternals = service as unknown as ServiceTestInternals
serviceInternals.createAdminApiClient = (_request, target) => ({
clusterNodeUpsert: async () => {},
clusterStatus: async () => {
statusReads.push(target.publicIpv4 ?? "unknown")
return {
servers: [{ auth_pubkey: "A".repeat(64) }]
}
}
})
await service["waitForClusterMeshPeerReady"]({
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
targets,
peerPubkey: "a".repeat(64)
})
assert.deepEqual(statusReads, ["203.0.113.10", "203.0.113.11"])
})
test("waitForNodeReady rejects managed bootstrap pubkey mismatches", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
service["waitForTangReady"] = async () => {}
service["waitForBootstrapReady"] = async () => {}
service["readNodePublicKey"] = async () => "b".repeat(64)
service["readNbdeLuksUuid"] = async () => "uuid-1"
await assert.rejects(
service.waitForNodeReady(session),
/Node pubkey mismatch after managed bootstrap: expected a{64}, observed b{64}\./
)
})
test("syncClusterMembership writes the desired snapshot to every target and verifies convergence", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const desiredClusterMembership = [
{
nodeId: "node-a",
pubkey: "a".repeat(64),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all" as const,
status: "active" as const,
activatedAt: "2026-04-10T00:00:00.000Z"
},
{
nodeId: "node-b",
pubkey: "b".repeat(64),
transportAddress: "wss://203.0.113.11:4413/relay",
scope: "all" as const,
status: "decommissioned" as const,
activatedAt: "2026-04-09T00:00:00.000Z",
deactivatedAt: "2026-04-11T00:00:00.000Z"
}
]
const clusterTargets = [
{
nodePrivateKey: "1".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
},
{
nodePrivateKey: "2".repeat(64),
publicIpv4: "203.0.113.11",
publicPort: 443,
publicScheme: "https" as const
}
]
const upserts: Array<{ host: string; pubkey: string }> = []
let viewReads = 0
service["waitForPublicAdminEndpointReady"] = async () => {}
const serviceInternals = service as unknown as ServiceTestInternals
serviceInternals.createAdminApiClient = (
request: { clusterBootstrap?: unknown },
target: { publicIpv4?: string }
) => {
assert.ok(request.clusterBootstrap)
return {
clusterNodeUpsert: async (input: { pubkey: string }) => {
upserts.push({
host: target.publicIpv4 ?? "unknown",
pubkey: input.pubkey
})
}
}
}
serviceInternals.readClusterMembershipViews = async (
request: { clusterBootstrap?: unknown },
targets: Array<{ publicIpv4?: string }>
) => {
assert.ok(request.clusterBootstrap)
viewReads += 1
if (viewReads === 1) {
return targets.map((target) => ({
host: target.publicIpv4 ?? "unknown",
nodes: [desiredClusterMembership[0]]
}))
}
return targets.map((target) => ({
host: target.publicIpv4 ?? "unknown",
nodes: desiredClusterMembership
}))
}
await service.syncClusterMembership({
desiredClusterMembership,
clusterTargets,
preflightTargets: [clusterTargets[0]],
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin()
})
assert.equal(viewReads, 2)
assert.deepEqual(upserts, [
{ host: "203.0.113.10", pubkey: "a".repeat(64) },
{ host: "203.0.113.10", pubkey: "b".repeat(64) },
{ host: "203.0.113.11", pubkey: "a".repeat(64) },
{ host: "203.0.113.11", pubkey: "b".repeat(64) }
])
})
test("syncClusterMembership can preserve peers already present in a preflight view", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const desiredClusterMembership: ClusterMembershipRecord[] = [
{
nodeId: "node-a",
pubkey: "a".repeat(64),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
},
{
nodeId: "node-c",
pubkey: "c".repeat(64),
transportAddress: "wss://203.0.113.12:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-12T00:00:00.000Z"
}
]
const existingPeer: ClusterMembershipRecord = {
nodeId: "node-b",
pubkey: "b".repeat(64),
transportAddress: "wss://203.0.113.11:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-11T00:00:00.000Z"
}
const mergedMembership = [
desiredClusterMembership[0]!,
existingPeer,
desiredClusterMembership[1]!
]
const clusterTargets = [
{
nodePrivateKey: "1".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
}
]
const upserts: string[] = []
let viewReads = 0
const serviceInternals = service as unknown as ServiceTestInternals
service["waitForPublicAdminEndpointReady"] = async () => {}
serviceInternals.createAdminApiClient = () => ({
clusterNodeUpsert: async (input: { pubkey: string }) => {
upserts.push(input.pubkey)
}
})
serviceInternals.readClusterMembershipViews = async (
_request: { clusterBootstrap?: unknown },
targets: Array<{ publicIpv4?: string }>
) => {
viewReads += 1
if (viewReads === 1) {
return targets.map((target) => ({
host: target.publicIpv4 ?? "unknown",
nodes: [desiredClusterMembership[0]!, existingPeer]
}))
}
return targets.map((target) => ({
host: target.publicIpv4 ?? "unknown",
nodes: mergedMembership
}))
}
await service.syncClusterMembership({
desiredClusterMembership,
clusterTargets,
preflightTargets: clusterTargets,
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
preserveExistingMembers: true
})
assert.equal(viewReads, 2)
assert.deepEqual(upserts.sort(), ["a".repeat(64), "b".repeat(64), "c".repeat(64)])
})
test("syncClusterMembership accepts syncing observations for active join requests when allowed", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const desiredClusterMembership: ClusterMembershipRecord[] = [
{
nodeId: "node-a",
pubkey: "a".repeat(64),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
}
]
const observedClusterMembership: ClusterMembershipRecord[] = [
{
...desiredClusterMembership[0]!,
status: "syncing"
}
]
const clusterTargets = [
{
nodePrivateKey: "1".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
}
]
let viewReads = 0
const serviceInternals = service as unknown as ServiceTestInternals
service["waitForPublicAdminEndpointReady"] = async () => {}
serviceInternals.createAdminApiClient = () => ({
clusterNodeUpsert: async () => {}
})
serviceInternals.readClusterMembershipViews = async (
_request: { clusterBootstrap?: unknown },
targets: Array<{ publicIpv4?: string }>
) => {
viewReads += 1
return targets.map((target) => ({
host: target.publicIpv4 ?? "unknown",
nodes: observedClusterMembership
}))
}
await service.syncClusterMembership({
desiredClusterMembership,
clusterTargets,
preflightTargets: [],
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
allowSyncingForActive: true
})
assert.equal(viewReads, 2)
})
test("syncClusterMembership accepts divergent syncing views for active join requests when allowed", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const desiredClusterMembership: ClusterMembershipRecord[] = [
{
nodeId: "node-a",
pubkey: "a".repeat(64),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
},
{
nodeId: "node-b",
pubkey: "b".repeat(64),
transportAddress: "wss://203.0.113.11:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-11T00:00:00.000Z"
}
]
const clusterTargets = [
{
nodePrivateKey: "1".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
},
{
nodePrivateKey: "2".repeat(64),
publicIpv4: "203.0.113.11",
publicPort: 443,
publicScheme: "https" as const
}
]
let viewReads = 0
const serviceInternals = service as unknown as ServiceTestInternals
service["waitForPublicAdminEndpointReady"] = async () => {}
serviceInternals.createAdminApiClient = () => ({
clusterNodeUpsert: async () => {}
})
serviceInternals.readClusterMembershipViews = async () => {
viewReads += 1
return [
{
host: "203.0.113.10",
nodes: desiredClusterMembership
},
{
host: "203.0.113.11",
nodes: [
{ ...desiredClusterMembership[0]!, status: "syncing" },
desiredClusterMembership[1]!
]
}
]
}
await service.syncClusterMembership({
desiredClusterMembership,
clusterTargets,
preflightTargets: [],
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
allowSyncingForActive: true
})
assert.equal(viewReads, 2)
})
test("syncClusterMembership reconciles divergent preflight cluster views", async () => {
const reports: string[] = []
const reporter: NodeDeploymentReporter = {
info: (line) => reports.push(line),
stdout: () => {},
stderr: () => {}
}
const service = new NodeDeploymentService(reporter) as NodeDeploymentService &
Record<string, unknown>
const desiredClusterMembership: ClusterMembershipRecord[] = [
{
nodeId: "node-a",
pubkey: "a".repeat(64),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
},
{
nodeId: "node-b",
pubkey: "b".repeat(64),
transportAddress: "wss://203.0.113.11:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-11T00:00:00.000Z"
},
{
nodeId: "node-c",
pubkey: "c".repeat(64),
transportAddress: "wss://203.0.113.12:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-12T00:00:00.000Z"
}
]
const clusterTargets = [
{
nodePrivateKey: "1".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https" as const
},
{
nodePrivateKey: "2".repeat(64),
publicIpv4: "203.0.113.11",
publicPort: 443,
publicScheme: "https" as const
}
]
const upserts: Array<{ host: string; pubkey: string }> = []
let viewReads = 0
const serviceInternals = service as unknown as ServiceTestInternals
service["waitForPublicAdminEndpointReady"] = async () => {}
serviceInternals.createAdminApiClient = (_request, target) => ({
clusterNodeUpsert: async (input: { pubkey: string }) => {
upserts.push({
host: target.publicIpv4 ?? "unknown",
pubkey: input.pubkey
})
}
})
serviceInternals.readClusterMembershipViews = async (
_request: { clusterBootstrap?: unknown },
targets: Array<{ publicIpv4?: string }>
) => {
viewReads += 1
if (viewReads === 1) {
return targets.map((target, index) => ({
host: target.publicIpv4 ?? "unknown",
nodes:
index === 0
? desiredClusterMembership
: [desiredClusterMembership[0]!, desiredClusterMembership[1]!]
}))
}
return targets.map((target) => ({
host: target.publicIpv4 ?? "unknown",
nodes: desiredClusterMembership
}))
}
await service.syncClusterMembership({
desiredClusterMembership,
clusterTargets,
preflightTargets: clusterTargets,
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin()
})
assert.equal(viewReads, 2)
assert.equal(upserts.length, 6)
assert.match(reports.join("\n"), /cluster membership preflight: observed divergent views/)
})
test("parseBootstrapProbeState recognizes ready output", () => {
assert.deepEqual(
parseBootstrapProbeState(
'{"health":"ok","bootstrap":{"managed":true,"ready":true,"waiting":false,"terminal":false,"status":"ready","detail":null},"node":{"pubkey":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}\n'
),
{
kind: "ready"
}
)
})
test("parseBootstrapProbeState recognizes terminal failed output", () => {
assert.deepEqual(
parseBootstrapProbeState(
'{"health":"ok","bootstrap":{"managed":true,"ready":false,"waiting":true,"terminal":true,"status":"failed","detail":"singleton tribe already exists"},"node":{"pubkey":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}\n'
),
{
kind: "failed",
reason: "singleton tribe already exists"
}
)
})
test("parseBootstrapProbeState treats other output as waiting", () => {
assert.deepEqual(
parseBootstrapProbeState(
'{"health":"ok","bootstrap":{"managed":true,"ready":false,"waiting":true,"terminal":false,"status":"bootstrapping","detail":null},"node":{"pubkey":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}\n'
),
{
kind: "waiting"
}
)
})
test("parseInternalStatusNodePublicKey extracts the lowercase pubkey from internal status JSON", () => {
assert.equal(
parseInternalStatusNodePublicKey(
'{"health":"ok","bootstrap":{"managed":true,"ready":true,"waiting":false,"terminal":false,"status":"ready","detail":null},"node":{"pubkey":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}}\n'
),
"a".repeat(64)
)
})
test("parseInternalStatusNodePublicKey returns null for invalid payloads", () => {
assert.equal(parseInternalStatusNodePublicKey("not json\n"), null)
})
test("parseLegionDeploymentStatusMarker recognizes fixed deployment status lines", () => {
assert.deepEqual(
parseLegionDeploymentStatusMarker(
"@LEGION_STATUS v=1 phase=guix-install step=system-init state=started\n"
),
{
version: 1,
phase: "guix-install",
step: "system-init",
state: "started"
}
)
assert.equal(parseLegionDeploymentStatusMarker("@LEGION_STATUS v=2 phase=x step=y state=z"), null)
assert.equal(parseLegionDeploymentStatusMarker("ordinary output"), null)
})
test("prepareInstallSession derives the session pubkey from the stored private key", async () => {
const tempDir = await mkdtemp(join(tmpdir(), "node-deployment-service-"))
const kexecImagePath = join(tempDir, "guix-kexec-installer.tar.gz")
const reporterLines: string[] = []
const reporter: NodeDeploymentReporter = {
info(line) {
reporterLines.push(line)
},
stdout(chunk) {
void chunk
},
stderr(chunk) {
void chunk
}
}
try {
await writeFile(kexecImagePath, "")
const service = new NodeDeploymentService(reporter)
const nodePrivateKey = "1".repeat(64)
const derivedNodePublicKey = deriveNodePublicKey(nodePrivateKey)
const session = await service.prepareInstallSession({
server: {
id: "node-a",
provider: "manual",
role: "primary",
publicIp: "203.0.113.10",
sshUsername: "root",
sshPrivateKey: "private-key",
sshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample",
status: "running",
region: "local",
planName: "manual",
commercialMetadata: {
effectiveMonthlyGrossEur: 0,
source: "override",
notes: []
},
deployment: {
status: "running"
},
createdAt: "2026-04-10T00:00:00.000Z",
updatedAt: "2026-04-10T00:00:00.000Z"
},
managedBootstrap: {
nodePrivateKey,
nodePublicKey: "f".repeat(64),
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https"
},
publicHost: "203.0.113.10",
certificateName: "203-0-113-10",
certificateSubjects: ["203.0.113.10"],
certificateChallengeMode: "http",
desiredClusterMembership: [
{
nodeId: "node-a",
pubkey: derivedNodePublicKey,
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
}
],
clusterTargets: [],
nbde: {
mode: "degraded",
tangPort: 7654,
recoverySecret: "secret",
localBootKeyPresent: true,
peerTangNodeIds: [],
peerTangUrls: []
},
clusterBootstrap: {
...createTestClusterBootstrap(),
relayPrivateKey: "9".repeat(64)
},
tribeName: "Tribe One",
tribeDescription: "Test tribe",
tribeVisibility: "invite_only",
kexecImagePath,
bootMode: "bios"
} as unknown as Parameters<NodeDeploymentService["prepareInstallSession"]>[0])
assert.equal(session.nodePublicKey, derivedNodePublicKey)
const bootstrap = JSON.parse(session.bootstrapJson ?? "{}")
assert.equal(bootstrap.cluster_secret.relay_private_key, "9".repeat(64))
assert.equal(bootstrap.tribe_admin.username, "legion-admin")
assert.equal(bootstrap.human_admin, undefined)
assert.equal(bootstrap.update_defaults.channels[0].name, "tribes")
assert.equal(
bootstrap.update_defaults.channels[0].url,
"https://git.teralink.net/tribes/guix-tribes.git"
)
assert.deepEqual(
bootstrap.update_defaults.substitute_servers.map((server: { url: string }) => server.url),
["https://guix.tribe-one.org", "https://bordeaux.guix.gnu.org", "https://ci.guix.gnu.org"]
)
assert.equal(bootstrap.update_defaults.substitute_keys[0].label, "guix.tribe-one.org")
assert.match(
reporterLines.join("\n"),
/managed bootstrap pubkey mismatch in local state for node-a; using the key derived from the stored private key/
)
} finally {
await rm(tempDir, { recursive: true, force: true })
}
})
test("prepareReconfigureSession does not require a kexec image path", async () => {
const service = new NodeDeploymentService()
const nodePrivateKey = "1".repeat(64)
const session = await service.prepareReconfigureSession({
server: {
id: "node-a",
provider: "hetzner",
role: "primary",
publicIp: "203.0.113.10",
sshUsername: "root",
sshPrivateKey: "private-key",
sshPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITest legion@test",
status: "running",
region: "fsn1",
planName: "CAX11",
commercialMetadata: {
effectiveMonthlyGrossEur: 4,
source: "provider",
notes: []
},
deployment: {
status: "ready",
snippets: []
},
createdAt: "2026-04-06T12:00:00.000Z",
updatedAt: "2026-04-06T12:00:00.000Z"
},
bootstrapMode: "init",
desiredClusterMembership: [
{
nodeId: "node-a",
pubkey: deriveNodePublicKey(nodePrivateKey),
transportAddress: "wss://203.0.113.10:4413/relay",
scope: "all",
status: "active",
activatedAt: "2026-04-10T00:00:00.000Z"
}
],
clusterTargets: [],
managedBootstrap: {
nodePrivateKey,
nodeName: "node-a",
publicIpv4: "203.0.113.10",
publicPort: 443,
publicScheme: "https"
},
publicHost: "203.0.113.10",
certificateName: "203-0-113-10",
certificateSubjects: ["203.0.113.10"],
certificateChallengeMode: "http",
clusterBootstrap: createTestClusterBootstrap(),
legionAdmin: createTestLegionAdmin(),
tribeName: "Test Tribe",
tribeDescription: "Test tribe",
tribeVisibility: "invite_only",
nbde: {
mode: "degraded",
tangPort: 7654,
recoverySecret: "secret",
localBootKeyPresent: true,
peerTangNodeIds: [],
peerTangUrls: []
},
kexecImagePath: "/tmp/does-not-need-to-exist.tar.gz",
bootMode: "bios"
})
assert.equal(session.request.server.id, "node-a")
assert.equal(session.nodePublicKey, deriveNodePublicKey(nodePrivateKey))
})
test("kexecIntoInstaller is idempotent when the Guix installer is already reachable", async () => {
const reporterLines: string[] = []
const service = new NodeDeploymentService({
info: (line) => reporterLines.push(line),
stdout: () => {},
stderr: () => {}
}) as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
let uploaded = false
let kexecCommandRun = false
let waitedForInstaller = false
service["isGuixInstallerReachable"] = async () => true
service["uploadKexecFiles"] = async () => {
uploaded = true
}
service["runStreamingCommand"] = async () => {
kexecCommandRun = true
}
service["waitForGuixInstaller"] = async () => {
waitedForInstaller = true
}
const result = await service.kexecIntoInstaller(session)
assert.equal(result, session)
assert.equal(uploaded, false)
assert.equal(kexecCommandRun, false)
assert.equal(waitedForInstaller, false)
assert.deepEqual(reporterLines, ["Guix installer is already reachable on 203.0.113.10"])
})
test("kexecIntoInstaller waits for installer identity instead of SSH outage", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
session.request.server.sshPublicKey = "ssh-ed25519 AAAATEST"
let uploaded = false
let kexecCommand = ""
let waitLabel = ""
service["isGuixInstallerReachable"] = async () => false
service["uploadKexecFiles"] = async () => {
uploaded = true
}
service["runStreamingCommand"] = async (_ssh: unknown, command: string) => {
kexecCommand = command
}
service["waitForGuixInstaller"] = async (_ssh: unknown, label: string) => {
waitLabel = label
}
await service.kexecIntoInstaller(session)
assert.equal(uploaded, true)
assert.match(kexecCommand, /guix-kexec/)
assert.equal(waitLabel, "waiting for the Guix installer SSH session")
})
test("kexecIntoInstaller stages files with sudo for non-root provider images", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
session.request.server.sshUsername = "ubuntu"
session.request.server.sshPublicKey = "ssh-ed25519 AAAATEST"
session.ssh.username = "ubuntu"
const uploadedPaths: string[] = []
const commands: string[] = []
let installerWaitUser = ""
service["isGuixInstallerReachable"] = async () => false
service["uploadKexecFiles"] = async (_ssh: unknown, files: Array<{ remotePath: string }>) => {
uploadedPaths.push(...files.map((file) => file.remotePath))
}
service["runStreamingCommand"] = async (_ssh: unknown, command: string) => {
commands.push(command)
}
service["waitForGuixInstaller"] = async (ssh: { username: string }) => {
installerWaitUser = ssh.username
}
const result = await service.kexecIntoInstaller(session)
assert.deepEqual(uploadedPaths, [
"/tmp/legion-kexec/guix-kexec-installer.tar.gz",
"/tmp/legion-kexec/guix-kexec.sh"
])
assert.match(commands[0], /sudo -n sh -c/)
assert.match(commands[0], /\/root\/kexec\/guix-kexec\.sh/)
assert.match(commands[1], /^sudo -n sh -c /)
assert.match(commands[1], /\/root\/kexec\/guix-kexec\.sh/)
assert.equal(installerWaitUser, "root")
assert.equal(result.ssh.username, "root")
assert.equal(result.request.server.sshUsername, "root")
})
test("kexecIntoInstaller retries non-root kexec commands with sudo password", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
session.request.server.sshUsername = "debian"
session.request.server.sshPublicKey = "ssh-ed25519 AAAATEST"
session.request.userPassword = "user-secret"
session.ssh.username = "debian"
const commands: Array<{ command: string; stdin?: string | Buffer }> = []
let installerWaitUser = ""
service["isGuixInstallerReachable"] = async () => false
service["uploadKexecFiles"] = async () => undefined
service["runStreamingCommand"] = async (
_ssh: unknown,
command: string,
_streamOutput: boolean,
_allowNonZero?: boolean,
stdin?: string | Buffer
) => {
commands.push({ command, stdin })
if (command.startsWith("sudo -n ")) {
throw new Error("sudo: a password is required")
}
}
service["waitForGuixInstaller"] = async (ssh: { username: string }) => {
installerWaitUser = ssh.username
}
const result = await service.kexecIntoInstaller(session)
assert.equal(commands.length, 4)
assert.match(commands[0]?.command ?? "", /^sudo -n sh -c /)
assert.match(commands[1]?.command ?? "", /^sudo -S -p '' sh -c /)
assert.equal(commands[1]?.stdin?.toString(), "user-secret\n")
assert.doesNotMatch(commands[1]?.command ?? "", /user-secret/)
assert.match(commands[2]?.command ?? "", /^sudo -n sh -c /)
assert.match(commands[3]?.command ?? "", /^sudo -S -p '' sh -c /)
assert.equal(commands[3]?.stdin?.toString(), "user-secret\n")
assert.doesNotMatch(commands[3]?.command ?? "", /user-secret/)
assert.equal(installerWaitUser, "root")
assert.equal(result.ssh.username, "root")
})
test("sudo password retry errors redact the password", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
service["runStreamingCommand"] = async (_ssh: unknown, command: string) => {
if (command.startsWith("sudo -n ")) {
throw new Error("sudo: a password is required")
}
throw new Error("user-secret\ncurl: not found")
}
await assert.rejects(
() =>
service["runRootStreamingCommand"](
{
host: "203.0.113.10",
username: "debian"
},
"run installer",
"user-secret"
) as Promise<void>,
(error) => {
assert.ok(error instanceof Error)
assert.match(error.message, /\[redacted\]/)
assert.doesNotMatch(error.message, /user-secret/)
assert.match(error.message, /curl: not found/)
return true
}
)
})
test("rebootIntoInstalledSystem is idempotent when installed Guix is already reachable", async () => {
const reporterLines: string[] = []
const service = new NodeDeploymentService({
info: (line) => reporterLines.push(line),
stdout: () => {},
stderr: () => {}
}) as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
let rebootCommandRun = false
let waitedForInstalledSystem = false
service["isInstalledSystemReachable"] = async () => true
service["runStreamingCommand"] = async () => {
rebootCommandRun = true
}
service["waitForInstalledSystem"] = async () => {
waitedForInstalledSystem = true
}
const result = await service.rebootIntoInstalledSystem(session)
assert.equal(result, session)
assert.equal(rebootCommandRun, false)
assert.equal(waitedForInstalledSystem, false)
assert.deepEqual(reporterLines, ["installed system is already reachable on 203.0.113.10"])
})
test("rebootIntoInstalledSystem waits for installed system identity instead of SSH outage", async () => {
const service = new NodeDeploymentService() as NodeDeploymentService & Record<string, unknown>
const session = createManagedBootstrapSession("a".repeat(64))
let rebootCommand = ""
let waitLabel = ""
service["isInstalledSystemReachable"] = async () => false
service["runStreamingCommand"] = async (_ssh: unknown, command: string) => {
rebootCommand = command
}
service["waitForInstalledSystem"] = async (_ssh: unknown, label: string) => {
waitLabel = label
}
await service.rebootIntoInstalledSystem(session)
assert.match(rebootCommand, /reboot/)
assert.equal(waitLabel, "waiting for the installed system SSH session")
})