feat(bootstrap): shared docker provider + foundation-net precursor (ADR-006)
Composition substrate for Wave 2 (T03+): - lib/context.ts: one Docker-over-SSH provider + DeployCtx threaded to component factories; FOUNDATION_DOCKER_HOST override for ephemeral validation. - lib/versions.ts: resolve pinned images from VERSIONS; FOUNDATION_ALLOW_UNPINNED for local validation when digests are still PIN_DIGEST. - components/network.ts: foundation-net (CONTRACT_003 §3.1). - index.ts: phase-orchestration entrypoint with dependsOn gates; Wave-2 slots. - ADR-006: shared-provider + per-component-factory model (egg does not route its phased bootstrap through the monolithic vendored DockerDeployments). Validated: pulumi up over Docker-over-SSH created+verified+destroyed foundation-net on crunchy01 (x86_64); ephemeral, nothing persisted. tsc + preview clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
57c4eadea7
commit
6a29db386f
8 changed files with 287 additions and 32 deletions
21
bootstrap/components/network.ts
Normal file
21
bootstrap/components/network.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// components/network.ts
|
||||||
|
//
|
||||||
|
// The foundation-net user-defined bridge (CONTRACT_003 §3.1). Created once, on the
|
||||||
|
// shared provider; every service container attaches to it and reaches peers by
|
||||||
|
// container name via Docker's embedded DNS. This is the first thing the bootstrap
|
||||||
|
// creates — all data-plane and forge components depend on it.
|
||||||
|
import * as docker from "@pulumi/docker";
|
||||||
|
import { BaseCtx } from "../lib/context";
|
||||||
|
|
||||||
|
export function deployNetwork(ctx: BaseCtx): docker.Network {
|
||||||
|
return new docker.Network(
|
||||||
|
"foundation-net",
|
||||||
|
{
|
||||||
|
name: ctx.cfg.network.name, // "foundation-net" (CONTRACT_003)
|
||||||
|
driver: "bridge",
|
||||||
|
attachable: true,
|
||||||
|
ipamConfigs: [{ subnet: ctx.cfg.network.subnet }], // "172.30.0.0/24"
|
||||||
|
},
|
||||||
|
{ provider: ctx.provider, deleteBeforeReplace: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,37 +1,50 @@
|
||||||
// index.ts — the foundation egg entrypoint (PLAN-002 §0, Layer 0).
|
// index.ts — foundation bootstrap entrypoint (the egg, PLAN-002 §0, Layer 0).
|
||||||
//
|
//
|
||||||
// T02 SCAFFOLD STATE: this entrypoint is intentionally a NO-OP beyond config
|
// Phase orchestration (PLAN-002 §2, §5). Components are created against the shared
|
||||||
// validation. It calls loadConfig() so that `pulumi preview` exercises the
|
// provider + foundation-net (ADR-006); GATES between phases are Pulumi dependsOn
|
||||||
// CONTRACT_001 fail-closed validation (acceptance T02), but it creates NO real
|
// edges, NOT imperative sequencing, so `pulumi up` derives the order. Wave-2+ tasks
|
||||||
// resources yet. The data plane / Vault / RustFS / Postgres / Forgejo / Caddy /
|
// fill the marked slots — this file is the single composition point; components stay
|
||||||
// runner components are LATER tasks (PLAN-002 §10: T03–T15) and are deliberately
|
// pure factories in components/*.
|
||||||
// NOT authored here.
|
import { loadConfig } from "./config";
|
||||||
import * as pulumi from "@pulumi/pulumi";
|
import { buildBaseContext, DeployCtx } from "./lib/context";
|
||||||
|
import { deployNetwork } from "./components/network";
|
||||||
|
|
||||||
import { loadConfig, sshPrivateKeyPath } from "./config";
|
const cfg = loadConfig();
|
||||||
|
|
||||||
// Fail closed here: if required config is missing/malformed, loadConfig throws
|
// --- shared substrate: provider + network (always first) ---
|
||||||
// and `pulumi preview` reports the full gap (CONTRACT_001 §Validation).
|
const base = buildBaseContext(cfg);
|
||||||
const config = loadConfig();
|
const network = deployNetwork(base);
|
||||||
|
const ctx: DeployCtx = { ...base, network };
|
||||||
|
|
||||||
// The vendored @olsitec/pulumi-docker provider (CONTRACT_003) will, in T03+, use
|
// =============================================================================
|
||||||
// this key path + config.vm.{host,user} to reach the foundation VM over SSH.
|
// PHASE 3 — DATA PLANE (depends on: network)
|
||||||
// Resolved here only to prove the ENV channel is wired; not yet consumed.
|
// T03 postgres · T04 rustfs · T05 vault (sealed)
|
||||||
const sshKeyPath = sshPrivateKeyPath();
|
// -----------------------------------------------------------------------------
|
||||||
|
// const postgres = deployPostgres(ctx);
|
||||||
|
// const rustfs = deployRustfs(ctx);
|
||||||
|
// const vault = deployVault(ctx);
|
||||||
|
//
|
||||||
|
// --- GATE A: Vault init + unseal (T05) → writes unseal keys to encrypted config;
|
||||||
|
// credentials.ts (T06) dependsOn the init resource.
|
||||||
|
// const credentials = deployCredentials(ctx, { postgres, rustfs, vault });
|
||||||
|
//
|
||||||
|
// =============================================================================
|
||||||
|
// PHASE 6 — FORGE (depends on: credentials, GATE A)
|
||||||
|
// T07 caddy · T08 forgejo · T10 runner
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// const proxy = deployProxy(ctx);
|
||||||
|
// const forgejo = deployForgejo(ctx, { postgres, rustfs, credentials, proxy });
|
||||||
|
// --- GATE B: Forgejo healthy → handover (T11) + runner registration (T10).
|
||||||
|
// const runner = deployRunner(ctx, { forgejo, credentials });
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
pulumi.log.info(
|
// Stack outputs (extended as phases land).
|
||||||
`foundation config loaded (no-op scaffold): ` +
|
export const phase = "T03-precursor"; // network + shared provider only
|
||||||
`baseDomain=${config.baseDomain}, vm=${config.vm.user}@${config.vm.host}, ` +
|
export const networkName = network.name;
|
||||||
`network=${config.network.name} (${config.network.subnet}), tls=${config.tls.mode}`,
|
export const vmTarget = `${cfg.vm.user}@${cfg.vm.host}`;
|
||||||
);
|
export const enabledFeatures = Object.entries(cfg.features)
|
||||||
|
|
||||||
// Stack outputs — safe, non-secret echoes so `pulumi preview`/`up` has something
|
|
||||||
// to show while no resources exist. Replaced by real component outputs in T03+.
|
|
||||||
export const phase = "T02-scaffold";
|
|
||||||
export const baseDomain = config.baseDomain;
|
|
||||||
export const networkName = config.network.name;
|
|
||||||
export const vmTarget = pulumi.interpolate`${config.vm.user}@${config.vm.host}`;
|
|
||||||
export const sshKeyConfigured = sshKeyPath.length > 0;
|
|
||||||
export const enabledFeatures = Object.entries(config.features)
|
|
||||||
.filter(([, on]) => on)
|
.filter(([, on]) => on)
|
||||||
.map(([name]) => name);
|
.map(([name]) => name);
|
||||||
|
|
||||||
|
// ctx is consumed by the Wave-2 slots above once uncommented.
|
||||||
|
void ctx;
|
||||||
|
|
|
||||||
58
bootstrap/lib/context.ts
Normal file
58
bootstrap/lib/context.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// lib/context.ts
|
||||||
|
//
|
||||||
|
// The shared deploy context every component receives (ADR-006). The bootstrap
|
||||||
|
// creates ONE docker.Provider (Docker-over-SSH to the foundation VM) and ONE
|
||||||
|
// foundation-net network; components then create their own container(s) against
|
||||||
|
// this shared provider/network, which gives the bootstrap full control over
|
||||||
|
// ordering and the phase GATES (e.g. Vault init between data-plane and Forgejo)
|
||||||
|
// that the vendored monolithic DockerDeployments cannot express.
|
||||||
|
//
|
||||||
|
// Validation override: the committed Pulumi.foundation.yaml carries placeholder
|
||||||
|
// VM coordinates (RFC-5737). For local/ephemeral validation against a dev Docker
|
||||||
|
// host, export FOUNDATION_DOCKER_HOST=ssh://user@host to point the provider there
|
||||||
|
// without editing committed config.
|
||||||
|
import * as pulumi from "@pulumi/pulumi";
|
||||||
|
import * as docker from "@pulumi/docker";
|
||||||
|
import { FoundationConfig, sshPrivateKeyPath } from "../config";
|
||||||
|
import { image } from "./versions";
|
||||||
|
|
||||||
|
/** Base context: shared provider + helpers, before the network exists. */
|
||||||
|
export interface BaseCtx {
|
||||||
|
cfg: FoundationConfig;
|
||||||
|
provider: docker.Provider;
|
||||||
|
sshKeyPath: string;
|
||||||
|
/** Resolve a pinned image by VERSIONS key suffix, e.g. ctx.image("POSTGRES"). */
|
||||||
|
image: (name: string) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Full context handed to every component: base + the shared network. */
|
||||||
|
export interface DeployCtx extends BaseCtx {
|
||||||
|
network: docker.Network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Signature every Wave-2+ component factory follows (ADR-006). */
|
||||||
|
export type ComponentFactory<T> = (ctx: DeployCtx) => T;
|
||||||
|
|
||||||
|
function providerHost(cfg: FoundationConfig): string {
|
||||||
|
return process.env.FOUNDATION_DOCKER_HOST || `ssh://${cfg.vm.user}@${cfg.vm.host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the shared Docker-over-SSH provider. SSH options mirror the vendored
|
||||||
|
* pulumi-docker wrapper (non-interactive host-key handling for automation).
|
||||||
|
*/
|
||||||
|
export function buildBaseContext(cfg: FoundationConfig): BaseCtx {
|
||||||
|
const sshKeyPath = sshPrivateKeyPath();
|
||||||
|
const provider = new docker.Provider("foundation-host", {
|
||||||
|
host: providerHost(cfg),
|
||||||
|
sshOpts: [
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=no",
|
||||||
|
"-o",
|
||||||
|
"UserKnownHostsFile=/dev/null",
|
||||||
|
"-i",
|
||||||
|
sshKeyPath,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return { cfg, provider, sshKeyPath, image };
|
||||||
|
}
|
||||||
59
bootstrap/lib/versions.ts
Normal file
59
bootstrap/lib/versions.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// lib/versions.ts
|
||||||
|
//
|
||||||
|
// Reads foundation/VERSIONS (the determinism pin-file, T01 / baseline D5) and
|
||||||
|
// resolves an image reference by key. Image refs live in VERSIONS, NOT in Pulumi
|
||||||
|
// config (config.ts comment; CONTRACT_001 §1.1). Components ask for an image by
|
||||||
|
// its logical name; this module returns the pinned `repo:tag@sha256:<digest>`.
|
||||||
|
//
|
||||||
|
// PIN_DIGEST handling (D5 "no floating tags"):
|
||||||
|
// VERSIONS may carry `@sha256:PIN_DIGEST` placeholders until digests are pinned
|
||||||
|
// online (T01 documented the `pin-digests` procedure). For a real deploy that is
|
||||||
|
// a hard error. For local/ephemeral VALIDATION against a dev Docker host, export
|
||||||
|
// FOUNDATION_ALLOW_UNPINNED=1 to fall back to the bare tag (the digest is dropped).
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
const VERSIONS_PATH = path.resolve(__dirname, "..", "..", "VERSIONS");
|
||||||
|
|
||||||
|
let cache: Record<string, string> | undefined;
|
||||||
|
|
||||||
|
function load(): Record<string, string> {
|
||||||
|
if (cache) return cache;
|
||||||
|
const text = fs.readFileSync(VERSIONS_PATH, "utf8");
|
||||||
|
const out: Record<string, string> = {};
|
||||||
|
for (const raw of text.split("\n")) {
|
||||||
|
const line = raw.trim();
|
||||||
|
if (line === "" || line.startsWith("#")) continue;
|
||||||
|
const eq = line.indexOf("=");
|
||||||
|
if (eq < 0) continue;
|
||||||
|
out[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
|
||||||
|
}
|
||||||
|
cache = out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve a container image by its VERSIONS key suffix, e.g. image("FORGEJO"). */
|
||||||
|
export function image(name: string): string {
|
||||||
|
const key = `IMAGE_${name.toUpperCase()}`;
|
||||||
|
const v = load()[key];
|
||||||
|
if (!v) {
|
||||||
|
throw new Error(`VERSIONS: ${key} is not defined (foundation/VERSIONS)`);
|
||||||
|
}
|
||||||
|
if (v.includes("PIN_DIGEST")) {
|
||||||
|
if (process.env.FOUNDATION_ALLOW_UNPINNED === "1") {
|
||||||
|
// validation only: drop the unresolved @sha256:PIN_DIGEST, use the tag
|
||||||
|
return v.replace(/@sha256:PIN_DIGEST$/, "");
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`VERSIONS: ${key} is not digest-pinned ('${v}'). Run the pin-digests ` +
|
||||||
|
`procedure (see VERSIONS header) before a real deploy, or set ` +
|
||||||
|
`FOUNDATION_ALLOW_UNPINNED=1 for local validation.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimum version string for a host tool, e.g. toolMin("VAULT"). */
|
||||||
|
export function toolMin(name: string): string | undefined {
|
||||||
|
return load()[`TOOL_${name.toUpperCase()}_MIN`];
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@olsitec/pulumi-docker": "workspace:*",
|
"@olsitec/pulumi-docker": "workspace:*",
|
||||||
"@olsitec/pulumi-vault": "workspace:*",
|
"@olsitec/pulumi-vault": "workspace:*",
|
||||||
|
"@pulumi/docker": "^4.5.8",
|
||||||
"@pulumi/pulumi": "^3.138.0"
|
"@pulumi/pulumi": "^3.138.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
39
bun.lock
39
bun.lock
|
|
@ -14,6 +14,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@olsitec/pulumi-docker": "workspace:*",
|
"@olsitec/pulumi-docker": "workspace:*",
|
||||||
"@olsitec/pulumi-vault": "workspace:*",
|
"@olsitec/pulumi-vault": "workspace:*",
|
||||||
|
"@pulumi/docker": "^4.5.8",
|
||||||
"@pulumi/pulumi": "^3.138.0",
|
"@pulumi/pulumi": "^3.138.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -584,7 +585,7 @@
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
"semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
"semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
|
||||||
|
|
||||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
|
|
@ -690,18 +691,32 @@
|
||||||
|
|
||||||
"@npmcli/arborist/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
"@npmcli/arborist/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
|
"@npmcli/arborist/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
|
"@npmcli/fs/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@npmcli/git/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
"@npmcli/git/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
||||||
|
|
||||||
|
"@npmcli/git/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@npmcli/git/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
"@npmcli/git/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
||||||
|
|
||||||
"@npmcli/map-workspaces/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
"@npmcli/map-workspaces/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
|
"@npmcli/metavuln-calculator/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
|
"@npmcli/package-json/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@npmcli/promise-spawn/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
"@npmcli/promise-spawn/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
||||||
|
|
||||||
"@pulumi/docker/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
|
"@opentelemetry/instrumentation/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sdk-trace-node/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@pulumi/eslint-plugin/typescript": ["typescript@4.9.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="],
|
"@pulumi/eslint-plugin/typescript": ["typescript@4.9.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="],
|
||||||
|
|
||||||
|
"@pulumi/pulumi/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@tufjs/models/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
"@tufjs/models/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/utils": "8.62.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/utils": "8.62.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg=="],
|
||||||
|
|
@ -720,8 +735,12 @@
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" } }, "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw=="],
|
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" } }, "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" } }, "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w=="],
|
"@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" } }, "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/utils/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="],
|
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||||
|
|
@ -750,10 +769,20 @@
|
||||||
|
|
||||||
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||||
|
|
||||||
|
"node-gyp/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
"node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
||||||
|
|
||||||
"normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
|
"normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
|
||||||
|
|
||||||
|
"normalize-package-data/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
|
"npm-install-checks/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
|
"npm-package-arg/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
|
"npm-pick-manifest/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
|
"spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
|
||||||
|
|
||||||
"validate-npm-package-license/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
|
"validate-npm-package-license/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
|
||||||
|
|
@ -782,6 +811,8 @@
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" } }, "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw=="],
|
"@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" } }, "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw=="],
|
||||||
|
|
@ -812,8 +843,12 @@
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
--- 2026-06-30T16:17:52Z ---
|
||||||
|
HOST: mac-studio -> docker:ssh://andiolsi@192.168.1.2 (crunchy01)
|
||||||
|
CWD: /Users/andiolsi/work/olsitec-foundation/foundation/bootstrap
|
||||||
|
REPO: olsitec-foundation BRANCH: master ENVIRONMENT: validation(ephemeral)
|
||||||
|
CMD: pulumi up --yes (create foundation-net only) then inspect then pulumi destroy --yes
|
||||||
|
EXIT: RUNNING
|
||||||
|
NOTE: T03 precursor live validation — prove Docker-over-SSH provider creates foundation-net on crunchy01; ephemeral, destroyed immediately, nothing persisted (per user constraint).
|
||||||
|
---
|
||||||
|
--- 2026-06-30T16:18:39Z UPDATE ---
|
||||||
|
CMD: (above) pulumi up/inspect/destroy — foundation-net on crunchy01
|
||||||
|
EXIT: 0 — created (subnet 172.30.0.0/24, bridge, attachable, verified) then destroyed clean; nothing persisted.
|
||||||
|
---
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
# ADR-006 — Bootstrap Composition Model: Shared Provider + Per-Component Factories
|
||||||
|
|
||||||
|
**Date**: 2026-06-30
|
||||||
|
**Status**: Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The vendored `@olsitec/pulumi-docker` `DockerDeployments` class (T02) is **monolithic**: its
|
||||||
|
constructor takes the full provider map + a flat list of all containers and creates them together,
|
||||||
|
with no mechanism for ordering between containers beyond the network. The foundation bootstrap,
|
||||||
|
however, needs **phase gates** (PLAN-002 §2/§5): Vault must be `operator init`'d and unsealed
|
||||||
|
*between* the data-plane and Forgejo consuming its secrets, and Forgejo must be healthy *before*
|
||||||
|
runner registration/handover. Expressing those gates through the monolithic wrapper is awkward
|
||||||
|
(it would create the provider/network multiple times and cannot interleave an imperative init step).
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
The bootstrap creates the **shared `docker.Provider` and `foundation-net` once** (`bootstrap/lib/context.ts`,
|
||||||
|
`bootstrap/components/network.ts`), and each service is a **pure factory** in
|
||||||
|
`bootstrap/components/<svc>.ts` with signature `(ctx: DeployCtx) => <outputs>` that creates its own
|
||||||
|
`docker.Container`(s) against the shared provider/network. `bootstrap/index.ts` is the single
|
||||||
|
composition point; **phase gates are Pulumi `dependsOn` edges**, not imperative sequencing, so
|
||||||
|
`pulumi up` derives the order.
|
||||||
|
|
||||||
|
The vendored `DockerDeployments` is **retained** as the published `@olsitec/pulumi-docker` API for
|
||||||
|
simple *downstream* (Layer-1) use — deploying a flat list of containers — but the egg does **not**
|
||||||
|
route its phased bootstrap through it.
|
||||||
|
|
||||||
|
Validation override: `FOUNDATION_DOCKER_HOST=ssh://user@host` points the provider at a dev Docker
|
||||||
|
host without editing committed config; `FOUNDATION_ALLOW_UNPINNED=1` lets `VERSIONS` PIN_DIGEST
|
||||||
|
placeholders fall back to tags for local validation only.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Easier**:
|
||||||
|
- Phase gates (Vault init, Forgejo-before-handover) are natural dependency edges.
|
||||||
|
- One file per service, each independently ownable by a Wave-2+ agent (clean parallelism).
|
||||||
|
- Validated end-to-end: `pulumi up` over Docker-over-SSH created `foundation-net` on crunchy01
|
||||||
|
(172.30.0.0/24, attachable) and `destroy` removed it cleanly (2026-06-30).
|
||||||
|
|
||||||
|
**Harder**:
|
||||||
|
- More explicit plumbing than the one-call wrapper (a shared `ctx` threaded through factories).
|
||||||
|
- Two composition styles coexist (egg = direct; downstream = `DockerDeployments`) — documented here
|
||||||
|
to avoid confusion.
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
- **Route the whole bootstrap through `DockerDeployments`**: rejected — cannot express phase gates
|
||||||
|
or the imperative Vault-init step without recreating the provider/network per call.
|
||||||
|
- **Modify the vendored module to support phases**: rejected — it must stay a faithful Stage-1 vendor
|
||||||
|
(ADR-005) and remain useful as the simple downstream API; forking its behaviour now splits it.
|
||||||
|
|
||||||
|
## Confidence
|
||||||
|
|
||||||
|
**High** — the model is implemented and the Docker-over-SSH path is proven against a real x86_64
|
||||||
|
host (crunchy01). Companion: CONTRACT_003, PLAN-002 §5.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue