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"], }