# rigging — multi-repo infrastructure management CLI # Aggregates bosun-enabled repos and provides unified host/job/secret management. def config-path []: nothing -> string { $env.HOME | path join ".config" "bosun" "config.toml" } def load-config []: nothing -> record { let p = (config-path) if ($p | path exists) { open $p } else { { repos: {} } } } def save-config [cfg: record]: nothing -> nothing { let p = (config-path) let dir = ($p | path dirname) if not ($dir | path exists) { mkdir $dir } $cfg | to toml | save -f $p } # Build manifest derivation and read the JSON def read-manifest [repo_path: string]: nothing -> record { let out = (^nix build $"($repo_path)#bosun-manifest" --no-link --print-out-paths | str trim) open $out } # Load all repos with their manifests def load-all []: nothing -> list { let cfg = (load-config) if ($cfg.repos | is-empty) { print $"(ansi yellow)No repos registered. Use `rigging repo add ` first.(ansi reset)" return [] } $cfg.repos | transpose name meta | each { |r| let manifest = (read-manifest $r.meta.path) { name: $r.name, path: $r.meta.path, manifest: $manifest } } } # Find which repo owns a given job def find-job-repo [name: string]: nothing -> record { let repos = (load-all) let matches = ($repos | where { |r| $name in ($r.manifest.jobs | columns) }) if ($matches | is-empty) { print $"(ansi red)Error: job '($name)' not found in any registered repo(ansi reset)" exit 1 } $matches | first } # Find which repo owns a given host def find-host-repo [name: string]: nothing -> record { let repos = (load-all) let matches = ($repos | where { |r| $name in ($r.manifest.hosts | columns) }) if ($matches | is-empty) { print $"(ansi red)Error: host '($name)' not found in any registered repo(ansi reset)" exit 1 } $matches | first } # --- Top-level --- # Rigging — multi-repo infrastructure management def main []: nothing -> nothing { print "rigging — multi-repo infrastructure management" print "" print "Usage: rigging " print "" print "Commands:" print " repo Manage registered repos" print " status Aggregated overview of all repos" print " host Host management (list, deploy, build)" print " job Nomad job management (list, run, plan, stop, ...)" print " secret Secret management (list, rekey)" print "" print $"Config: (config-path)" } # --- Repo management --- # Manage registered repos def "main repo" []: nothing -> nothing { main repo list } # Register a new repo def "main repo add" [ path: string # Path to the repo (must contain a flake with bosun-manifest) ]: nothing -> nothing { let abs_path = ($path | path expand) if not ($"($abs_path)/flake.nix" | path exists) { print $"(ansi red)Error: no flake.nix found at ($abs_path)(ansi reset)" exit 1 } print $"(ansi blue)→(ansi reset) Building manifest for ($abs_path)..." let manifest = (read-manifest $abs_path) let name = $manifest.name let cfg = (load-config) let repos = ($cfg.repos | upsert $name { path: $abs_path }) save-config { repos: $repos } let n_hosts = ($manifest.hosts | columns | length) let n_jobs = ($manifest.jobs | columns | length) print $"(ansi green)✓(ansi reset) Registered '($name)' — ($n_hosts) hosts, ($n_jobs) jobs" } # Remove a registered repo def "main repo remove" [ name: string # Name of the repo to remove ]: nothing -> nothing { let cfg = (load-config) if $name not-in ($cfg.repos | columns) { print $"(ansi red)Error: repo '($name)' not registered(ansi reset)" exit 1 } let repos = ($cfg.repos | reject $name) save-config { repos: $repos } print $"(ansi green)✓(ansi reset) Removed '($name)'" } # List registered repos def "main repo list" []: nothing -> nothing { let cfg = (load-config) if ($cfg.repos | is-empty) { print "No repos registered. Use `rigging repo add ` to add one." return } $cfg.repos | transpose name meta | each { |r| let manifest = (try { read-manifest $r.meta.path } catch { null }) if $manifest != null { let n_hosts = ($manifest.hosts | columns | length) let n_jobs = ($manifest.jobs | columns | length) { name: $r.name, path: $r.meta.path, hosts: $n_hosts, jobs: $n_jobs } } else { { name: $r.name, path: $r.meta.path, hosts: "?", jobs: "?" } } } | table | print } # --- Status --- # Aggregated overview of all repos def "main status" []: nothing -> nothing { let repos = (load-all) if ($repos | is-empty) { return } print $"(ansi blue_bold)Repos(ansi reset)" $repos | each { |r| let n_hosts = ($r.manifest.hosts | columns | length) let n_jobs = ($r.manifest.jobs | columns | length) { repo: $r.name, path: $r.path, hosts: $n_hosts, jobs: $n_jobs } } | table | print print "" print $"(ansi blue_bold)Hosts(ansi reset)" let hosts = ($repos | each { |r| $r.manifest.hosts | transpose name cfg | each { |h| { host: $h.name repo: $r.name system: $h.cfg.system class: $h.cfg.class target: ($h.cfg.targetHost? | default "local") tags: ($h.cfg.tags | str join ", ") } } } | flatten) if ($hosts | is-empty) { print " (none)" } else { $hosts | table | print } print "" print $"(ansi blue_bold)Jobs(ansi reset)" let jobs = ($repos | each { |r| $r.manifest.jobs | transpose name meta | each { |j| { job: $j.name repo: $r.name type: $j.meta.type datacenters: ($j.meta.datacenters | str join ", ") parameterized: $j.meta.parameterized } } } | flatten) if ($jobs | is-empty) { print " (none)" } else { $jobs | table | print } } # --- Host management --- # Host management def "main host" []: nothing -> nothing { main host list } # List all hosts across repos def "main host list" [ --tag: string # Filter by tag ]: nothing -> nothing { let repos = (load-all) if ($repos | is-empty) { return } let hosts = ($repos | each { |r| $r.manifest.hosts | transpose name cfg | each { |h| { host: $h.name repo: $r.name system: $h.cfg.system class: $h.cfg.class target: ($h.cfg.targetHost? | default "local") tags: ($h.cfg.tags | default []) } } } | flatten) let filtered = if $tag != null { $hosts | where { |h| $tag in $h.tags } } else { $hosts } $filtered | update tags { |r| $r.tags | str join ", " } | table | print } # Deploy a host (nixos-rebuild or darwin-rebuild) def "main host deploy" [ name: string # Host name --dry-run # Print command without executing ]: nothing -> nothing { let repo = (find-host-repo $name) let host_cfg = ($repo.manifest.hosts | get $name) let cmd = (build-deploy-cmd $repo.path $name $host_cfg false) if $dry_run { print $"(ansi yellow)DRY RUN(ansi reset) — would execute:" print $" ($cmd | str join ' ')" return } print $"(ansi blue)→(ansi reset) Deploying ($name) from ($repo.name)..." ^...$cmd } # Build a host (without activating) def "main host build" [ name: string # Host name --dry-run # Print command without executing ]: nothing -> nothing { let repo = (find-host-repo $name) let host_cfg = ($repo.manifest.hosts | get $name) let cmd = (build-deploy-cmd $repo.path $name $host_cfg true) if $dry_run { print $"(ansi yellow)DRY RUN(ansi reset) — would execute:" print $" ($cmd | str join ' ')" return } print $"(ansi blue)→(ansi reset) Building ($name) from ($repo.name)..." ^...$cmd } # Construct the rebuild command for a host def build-deploy-cmd [ repo_path: string name: string host_cfg: record build_only: bool ]: nothing -> list { let rebuilder = if $host_cfg.class == "darwin" { "darwin-rebuild" } else { "nixos-rebuild" } let action = if $build_only { "build" } else { "switch" } mut cmd = [$rebuilder $action "--flake" $"($repo_path)#($name)"] if $host_cfg.targetHost? != null { $cmd = ($cmd | append ["--target-host" $host_cfg.targetHost "--use-remote-sudo"]) } if $host_cfg.buildHost? != null { $cmd = ($cmd | append ["--build-host" $host_cfg.buildHost]) } $cmd } # --- Job management --- # Nomad job management def "main job" []: nothing -> nothing { main job list } # List all Nomad jobs across repos def "main job list" []: nothing -> nothing { let repos = (load-all) if ($repos | is-empty) { return } let jobs = ($repos | each { |r| $r.manifest.jobs | transpose name meta | each { |j| { job: $j.name repo: $r.name type: $j.meta.type datacenters: ($j.meta.datacenters | str join ", ") parameterized: $j.meta.parameterized } } } | flatten) if ($jobs | is-empty) { print "No jobs found." } else { $jobs | table | print } } # Compile and deploy a job to Nomad def "main job run" [ name: string # Job name --dry-run # Print command without executing ...rest: string # Extra args passed to the repo-local bosun CLI (e.g. -v KEY=VALUE) ]: nothing -> nothing { let repo = (find-job-repo $name) let args = if $dry_run { ["run" $name "--dry-run" ...$rest] } else { ["run" $name ...$rest] } print $"(ansi blue)→(ansi reset) Running job ($name) from ($repo.name)..." ^nix run $"($repo.path)#bosun" -- ...$args } # Plan a job deployment (dry-run) def "main job plan" [ name: string # Job name ...rest: string # Extra args ]: nothing -> nothing { let repo = (find-job-repo $name) print $"(ansi blue)→(ansi reset) Planning job ($name) from ($repo.name)..." ^nix run $"($repo.path)#bosun" -- "plan" $name ...$rest } # Stop a running job def "main job stop" [ name: string # Job name --dry-run # Print command without executing ]: nothing -> nothing { let repo = (find-job-repo $name) let args = if $dry_run { ["stop" $name "--dry-run"] } else { ["stop" $name] } print $"(ansi blue)→(ansi reset) Stopping job ($name)..." ^nix run $"($repo.path)#bosun" -- ...$args } # Show job status def "main job status" [ name?: string # Job name (omit for all) ]: nothing -> nothing { if $name == null { # Find any repo with nomad enabled and query status let repos = (load-all) let nomad_repos = ($repos | where { |r| not ($r.manifest.nomad | is-empty) }) if ($nomad_repos | is-empty) { print "No repos with Nomad enabled." return } let repo = ($nomad_repos | first) ^nix run $"($repo.path)#bosun" -- "status" } else { let repo = (find-job-repo $name) ^nix run $"($repo.path)#bosun" -- "status" $name } } # Show job logs def "main job logs" [ name: string # Job name task?: string # Task name (optional) ]: nothing -> nothing { let repo = (find-job-repo $name) if $task != null { ^nix run $"($repo.path)#bosun" -- "logs" $name $task } else { ^nix run $"($repo.path)#bosun" -- "logs" $name } } # Pretty-print compiled job JSON def "main job inspect" [ name: string # Job name ...rest: string # Extra args (e.g. -v KEY=VALUE) ]: nothing -> nothing { let repo = (find-job-repo $name) ^nix run $"($repo.path)#bosun" -- "inspect" $name ...$rest } # Generate all job JSON files def "main job generate" [ dir?: string # Output directory (default: ./generated) ...rest: string # Extra args ]: nothing -> nothing { let repos = (load-all) let nomad_repos = ($repos | where { |r| not ($r.manifest.jobs | is-empty) }) for repo in $nomad_repos { print $"(ansi blue)→(ansi reset) Generating jobs for ($repo.name)..." let args = if $dir != null { ["generate" $dir ...$rest] } else { ["generate" ...$rest] } ^nix run $"($repo.path)#bosun" -- ...$args } } # Dispatch a parameterized job def "main job dispatch" [ name?: string # Job name (omit to list parameterized jobs) ...rest: string # Extra args (e.g. -m KEY=VALUE) ]: nothing -> nothing { if $name == null { # List parameterized jobs let repos = (load-all) let param_jobs = ($repos | each { |r| $r.manifest.jobs | transpose jname meta | where { |j| $j.meta.parameterized } | each { |j| { job: $j.jname, repo: $r.name } } } | flatten) if ($param_jobs | is-empty) { print "No parameterized jobs found." } else { print "Parameterized jobs:" $param_jobs | table | print } return } let repo = (find-job-repo $name) ^nix run $"($repo.path)#bosun" -- "dispatch" $name ...$rest } # --- Secret management --- # Secret management def "main secret" []: nothing -> nothing { main secret list } # List secrets across repos def "main secret list" []: nothing -> nothing { let cfg = (load-config) if ($cfg.repos | is-empty) { print "No repos registered." return } $cfg.repos | transpose name meta | each { |r| let secrets_dir = $"($r.meta.path)/secrets" if ($secrets_dir | path exists) { let files = (glob $"($secrets_dir)/**/*.age") $files | each { |f| let rel = ($f | str replace $"($r.meta.path)/" "") { repo: $r.name, secret: $rel } } } else { [] } } | flatten | table | print } # Rekey secrets for a repo def "main secret rekey" [ --repo: string # Repo name (omit to rekey all) ]: nothing -> nothing { let cfg = (load-config) let targets = if $repo != null { if $repo not-in ($cfg.repos | columns) { print $"(ansi red)Error: repo '($repo)' not registered(ansi reset)" exit 1 } [{ name: $repo, path: ($cfg.repos | get $repo | get path) }] } else { $cfg.repos | transpose name meta | each { |r| { name: $r.name, path: $r.meta.path } } } for target in $targets { print $"(ansi blue)→(ansi reset) Rekeying secrets for ($target.name)..." ^nix run $"($target.path)#agenix" -- rekey -a print $"(ansi green)✓(ansi reset) ($target.name) rekeyed" } }