141 lines
6.5 KiB
Text
141 lines
6.5 KiB
Text
let pb = import "schemas/lib/playbook.ncl" in
|
|
|
|
pb.make_playbook {
|
|
id = "bootstrap_initial",
|
|
name = "Initial Bootstrap",
|
|
description = "First-time full platform bootstrap. Builds buildkit-runner golden image off-platform, deploys libre-wuji with temporary bootstrap zot, migrates images to wuji S3-backed zot, deploys libre-daoshi (CI/build cluster), deploys ops-vm (signing), and bootstraps Radicle governance repos. Run exactly once per platform instantiation.",
|
|
version = 1,
|
|
|
|
preconditions = [
|
|
"Hetzner Cloud API token available and project created",
|
|
"DigitalOcean API token available for ops-vm",
|
|
"S3 bucket for zot (libre-wuji-zot) exists with versioning enabled",
|
|
"NATS account server configured and account JWTs available",
|
|
"Domain DNS delegated (for ingress on wuji/daoshi)",
|
|
"SOPS age key generated and accessible",
|
|
"Radicle identity (rad auth) initialized for bootstrapping operator",
|
|
"Bootstrap zot: local registry or any reachable OCI registry for initial image push",
|
|
"Ed25519 key pair generated for initial ops-controller, keeper, and radicle delegate",
|
|
],
|
|
|
|
params = [
|
|
{ name = "workspace", description = "Primary workspace (must be libre-wuji)", required = true, default_val = "libre-wuji" },
|
|
{ name = "hetzner_context", description = "hcloud context name", required = true, default_val = "" },
|
|
{ name = "do_context", description = "DigitalOcean context (for ops-vm)", required = true, default_val = "" },
|
|
{ name = "bootstrap_zot_url", description = "Temporary OCI registry for image bootstrap", required = true, default_val = "" },
|
|
{ name = "s3_bucket", description = "S3 bucket for wuji zot", required = true, default_val = "libre-wuji-zot" },
|
|
{ name = "ops_vm_host", description = "ops-vm SSH host (once provisioned)", required = false, default_val = "" },
|
|
{ name = "ops_controller_key", description = "Path to ops-controller Ed25519 private key PEM", required = true, default_val = "" },
|
|
{ name = "keeper_key", description = "Path to keeper Ed25519 private key PEM", required = true, default_val = "" },
|
|
{ name = "initial_delegate_did", description = "Radicle DID of first governance delegate", required = true, default_val = "" },
|
|
{ name = "skip_golden_image", description = "Skip golden image build if already pushed", required = false, default_val = "false" },
|
|
],
|
|
|
|
steps = [
|
|
{
|
|
id = "verify_prerequisites",
|
|
name = "Verify all bootstrap prerequisites",
|
|
script = "steps/verify_prerequisites.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Stop,
|
|
depends_on = [],
|
|
},
|
|
{
|
|
id = "build_golden_image",
|
|
name = "Build buildkit-runner golden image off-platform",
|
|
script = "steps/build_golden_image.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Stop,
|
|
depends_on = ["verify_prerequisites"],
|
|
},
|
|
{
|
|
id = "push_to_bootstrap_zot",
|
|
name = "Push golden image to bootstrap registry",
|
|
script = "steps/push_to_bootstrap_zot.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Stop,
|
|
depends_on = ["build_golden_image"],
|
|
},
|
|
{
|
|
id = "deploy_libre_wuji",
|
|
name = "Deploy libre-wuji cluster (using bootstrap zot)",
|
|
script = "steps/deploy_libre_wuji.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Stop,
|
|
depends_on = ["push_to_bootstrap_zot"],
|
|
},
|
|
{
|
|
id = "verify_wuji_zot_live",
|
|
name = "Wait for wuji S3-backed zot to be healthy",
|
|
script = "steps/verify_wuji_zot_live.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Stop,
|
|
depends_on = ["deploy_libre_wuji"],
|
|
},
|
|
{
|
|
id = "migrate_to_wuji_zot",
|
|
name = "Migrate images from bootstrap registry to wuji zot",
|
|
script = "steps/migrate_to_wuji_zot.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Stop,
|
|
depends_on = ["verify_wuji_zot_live"],
|
|
},
|
|
{
|
|
id = "bootstrap_radicle_governance",
|
|
name = "Create initial Radicle governance repos and set delegation",
|
|
script = "steps/bootstrap_radicle_governance.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Stop,
|
|
depends_on = ["verify_wuji_zot_live"],
|
|
},
|
|
{
|
|
id = "deploy_libre_daoshi",
|
|
name = "Deploy libre-daoshi CI/build cluster",
|
|
script = "steps/deploy_libre_daoshi.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Continue,
|
|
depends_on = ["migrate_to_wuji_zot", "bootstrap_radicle_governance"],
|
|
},
|
|
{
|
|
id = "deploy_ops_vm",
|
|
name = "Deploy ops-vm on DigitalOcean (signing isolation)",
|
|
script = "steps/deploy_ops_vm.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Continue,
|
|
depends_on = ["bootstrap_radicle_governance"],
|
|
},
|
|
{
|
|
id = "verify_end_to_end",
|
|
name = "Run end-to-end ops pipeline smoke test",
|
|
script = "steps/verify_end_to_end.nu",
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Continue,
|
|
depends_on = ["deploy_libre_daoshi", "deploy_ops_vm"],
|
|
},
|
|
{
|
|
id = "emit_audit",
|
|
name = "Emit bootstrap-complete audit event",
|
|
script = "steps/emit_audit.nu",
|
|
params = { event_type = "bootstrap_complete" },
|
|
dry_run_arg = "--dry-run",
|
|
on_error = 'Continue,
|
|
depends_on = ["verify_end_to_end"],
|
|
},
|
|
],
|
|
|
|
rollback_strategy = 'manual,
|
|
|
|
success_criteria = [
|
|
"libre-wuji cluster running with all components healthy (zot, ops-controller, radicle-seed, observability)",
|
|
"zot serving images from S3 bucket — bootstrap registry no longer needed",
|
|
"Radicle repos created: policy-libre-wuji, desired-libre-wuji, state-libre-wuji, policy-libre-daoshi, desired-libre-daoshi, state-libre-daoshi",
|
|
"Initial delegation set (M=1 threshold) written to all policy repos",
|
|
"libre-daoshi cluster running with forgejo, woodpecker, postgresql, radicle-seed",
|
|
"ops-vm running on DigitalOcean AMS3 with keeper-daemon and radicle-seed",
|
|
"End-to-end: test op submitted via NATS → signed by keeper → applied → audit event in SurrealDB",
|
|
"ops history shows bootstrap-complete audit event",
|
|
],
|
|
|
|
emit_audit = true,
|
|
adr_refs = ["adr-037", "adr-038", "adr-039"],
|
|
}
|