231 lines
9.4 KiB
Text
231 lines
9.4 KiB
Text
#!/usr/bin/env nu
|
|
# Thin entry for backup commands. Dispatches to the prvng-backup binary
|
|
# (the Rust backup-manager crate) for all heavy lifting; this script handles
|
|
# argument parsing, help text, and the `policy validate` shortcut against
|
|
# workspace declarations.
|
|
#
|
|
# See: ADR-010 (backup-architecture), ADR-015 (config-driven via platform-config)
|
|
|
|
export-env {
|
|
let lib_dirs_raw = ($env.NU_LIB_DIRS? | default "")
|
|
let current_lib_dirs = if ($lib_dirs_raw | type) == "string" {
|
|
if ($lib_dirs_raw | is-empty) { [] } else { ($lib_dirs_raw | split row ":") }
|
|
} else {
|
|
$lib_dirs_raw
|
|
}
|
|
let dynamic = ($env.PROVISIONING? | default "" | path join "core" "nulib")
|
|
$env.NU_LIB_DIRS = ([
|
|
"/opt/provisioning/core/nulib"
|
|
"/usr/local/provisioning/core/nulib"
|
|
] | append $current_lib_dirs | append (if ($dynamic | is-not-empty) { [$dynamic] } else { [] }))
|
|
}
|
|
|
|
# Resolve the prvng-backup binary. Searches:
|
|
# 1. $PRVNG_BACKUP_BIN environment override
|
|
# 2. $PROVISIONING/platform/target/release/prvng-backup
|
|
# 3. $PROVISIONING/platform/target/debug/prvng-backup
|
|
# 4. PATH lookup (`which prvng-backup`)
|
|
# Returns "" when not found; callers handle the absence.
|
|
def resolve-backup-bin [] {
|
|
let override = ($env.PRVNG_BACKUP_BIN? | default "")
|
|
if ($override | is-not-empty) {
|
|
return $override
|
|
}
|
|
|
|
let prov = ($env.PROVISIONING? | default "")
|
|
if ($prov | is-not-empty) {
|
|
let release = ($prov | path join "platform" "target" "release" "prvng-backup")
|
|
if ($release | path exists) { return $release }
|
|
|
|
let debug = ($prov | path join "platform" "target" "debug" "prvng-backup")
|
|
if ($debug | path exists) { return $debug }
|
|
}
|
|
|
|
let lookup = (which prvng-backup | get path)
|
|
if ($lookup | is-not-empty) {
|
|
return ($lookup | first | into string)
|
|
}
|
|
|
|
""
|
|
}
|
|
|
|
# Default Nickel import path. Workspace components import from
|
|
# 'schemas/lib/...', so the parent of the schemas/ directory is required.
|
|
def resolve-import-path [] {
|
|
let override = ($env.NICKEL_IMPORT_PATH? | default "")
|
|
if ($override | is-not-empty) {
|
|
return $override
|
|
}
|
|
|
|
let prov = ($env.PROVISIONING? | default "")
|
|
if ($prov | is-not-empty) {
|
|
let candidate = ($prov | path expand)
|
|
if (($candidate | path join "schemas") | path exists) {
|
|
return $candidate
|
|
}
|
|
}
|
|
|
|
"provisioning"
|
|
}
|
|
|
|
def print-help []: nothing -> nothing {
|
|
print "Backup Manager — multi-context backup orchestrator"
|
|
print "==================================================="
|
|
print ""
|
|
print "Usage: prvng backup <subcommand> [args]"
|
|
print ""
|
|
print "Subcommands:"
|
|
print " status Print daemon and policy status (JSON or text)"
|
|
print " list <target> [--scope s] [--tag k=v ...]"
|
|
print " List snapshots for a component, group, or system target"
|
|
print " run <target> [--scope s] Trigger a one-shot backup outside cron"
|
|
print " restore <component> Restore a snapshot (use --snapshot, --to)"
|
|
print " restore --group <name> Restore a BackupGroup at a timestamp (use --at, --to)"
|
|
print " mount <component> Mount a snapshot via FUSE (workstation/standalone)"
|
|
print " verify <target> [--level lvl] Verify snapshots (quick|deep|restore-drill|full-dr)"
|
|
print " policy validate <path> Validate a Nickel policy file or directory"
|
|
print " policy render <path> Render a Nickel policy file as JSON"
|
|
print " providers List available BackupProvider definitions"
|
|
print " destinations [--check] List destinations; --check probes health"
|
|
print " daemon <action> status|reload|drain|start"
|
|
print " drill run <component> Invoke a verify-recipe drill on demand"
|
|
print " events tail [--subject s] Tail NATS events for backup.>"
|
|
print " help Show this message"
|
|
print ""
|
|
print "Environment:"
|
|
print " PRVNG_BACKUP_BIN Override path to the prvng-backup binary"
|
|
print " NICKEL_IMPORT_PATH Override Nickel --import-path (default: \$PROVISIONING)"
|
|
print " BACKUP_CONFIG Path to backup-manager.ncl config file"
|
|
print " BACKUP_LOG Tracing filter (default: 'info')"
|
|
print ""
|
|
print "Reference: ADR-010 (architecture), ADR-015 (config-driven), ADR-016 (events),"
|
|
print " ADR-017 (vault custody)."
|
|
}
|
|
|
|
def main [
|
|
...args: string
|
|
--workspace (-w): string = ""
|
|
--debug (-x)
|
|
]: nothing -> nothing {
|
|
if $debug { $env.PROVISIONING_DEBUG = true }
|
|
|
|
let rest = if (($args | length) > 0) and (($args | first) in ["backup", "bak"]) {
|
|
$args | skip 1
|
|
} else {
|
|
$args
|
|
}
|
|
|
|
let sub = if (($rest | length) > 0) { $rest | first } else { "help" }
|
|
let sub_args = if (($rest | length) > 1) { $rest | skip 1 } else { [] }
|
|
|
|
let bin = (resolve-backup-bin)
|
|
if ($bin | is-empty) {
|
|
print "ERROR: prvng-backup binary not found. Set PRVNG_BACKUP_BIN, build with"
|
|
print " 'cargo build -p backup-manager', or add it to PATH."
|
|
exit 1
|
|
}
|
|
let import_path = (resolve-import-path)
|
|
|
|
match $sub {
|
|
"help" | "-h" | "--help" | "h" => { print-help }
|
|
|
|
"policy" => {
|
|
let action = if (($sub_args | length) > 0) { $sub_args | first } else { "help" }
|
|
let action_args = if (($sub_args | length) > 1) { $sub_args | skip 1 } else { [] }
|
|
|
|
match $action {
|
|
"validate" => {
|
|
let paths = if (($action_args | length) > 0) {
|
|
$action_args
|
|
} else {
|
|
print "Usage: prvng backup policy validate <path> [<path> ...]"
|
|
exit 2
|
|
}
|
|
let cmd_args = (["policy", "validate"] | append $paths | append ["--import-path", $import_path])
|
|
^$bin ...$cmd_args
|
|
}
|
|
"render" => {
|
|
let target = if (($action_args | length) > 0) { $action_args | first } else { "" }
|
|
if ($target | is-empty) {
|
|
print "Usage: prvng backup policy render <path>"
|
|
exit 2
|
|
}
|
|
^$bin policy render $target --import-path $import_path
|
|
}
|
|
_ => {
|
|
print "Usage: prvng backup policy <validate|render> ..."
|
|
exit 2
|
|
}
|
|
}
|
|
}
|
|
|
|
"status" => { ^$bin daemon status ...$sub_args }
|
|
"list" => { ^$bin one-shot list ...$sub_args }
|
|
"run" => { ^$bin one-shot backup ...$sub_args }
|
|
"verify" => { ^$bin one-shot verify ...$sub_args }
|
|
"restore" => { ^$bin one-shot restore ...$sub_args }
|
|
|
|
"mount" => { ^$bin standalone mount ...$sub_args }
|
|
|
|
"providers" => {
|
|
print "Configured providers (from BackupProvider definitions):"
|
|
print " restic primary; mount + streaming + encryption"
|
|
print " kopia opt-in; global dedup, mount less battle-tested"
|
|
print ""
|
|
print "See provisioning/catalog/providers/backup/ for definitions."
|
|
}
|
|
"destinations" => {
|
|
# Workspace declarations live in lib/backup_policies.ncl. The binary
|
|
# will read them in a later iteration; for now we render the file.
|
|
let prov = ($env.PROVISIONING? | default "")
|
|
let workspace_root = ($env.WORKSPACE_ROOT? | default "")
|
|
let candidate = if ($workspace_root | is-not-empty) {
|
|
$workspace_root | path join "infra" "lib" "backup_policies.ncl"
|
|
} else {
|
|
"infra/lib/backup_policies.ncl"
|
|
}
|
|
if ($candidate | path exists) {
|
|
^$bin policy render $candidate --import-path $import_path
|
|
} else {
|
|
print $"Workspace destinations file not found: ($candidate)"
|
|
print "Set WORKSPACE_ROOT or run from the workspace directory."
|
|
exit 2
|
|
}
|
|
}
|
|
|
|
"daemon" => {
|
|
let action = if (($sub_args | length) > 0) { $sub_args | first } else { "status" }
|
|
let action_args = if (($sub_args | length) > 1) { $sub_args | skip 1 } else { [] }
|
|
^$bin daemon $action ...$action_args
|
|
}
|
|
|
|
"drill" => {
|
|
let action = if (($sub_args | length) > 0) { $sub_args | first } else { "" }
|
|
if $action != "run" {
|
|
print "Usage: prvng backup drill run <component>"
|
|
exit 2
|
|
}
|
|
let target = if (($sub_args | length) > 1) { $sub_args | get 1 } else { "" }
|
|
if ($target | is-empty) {
|
|
print "Usage: prvng backup drill run <component>"
|
|
exit 2
|
|
}
|
|
print $"Drill mode for ($target) is wired in the CLI surface;"
|
|
print "the daemon coordinator implementation lands in a subsequent commit."
|
|
exit 2
|
|
}
|
|
|
|
"events" => {
|
|
let action = if (($sub_args | length) > 0) { $sub_args | first } else { "tail" }
|
|
print $"NATS events ($action) wired in the CLI surface;"
|
|
print "the platform-nats integration lands in a subsequent commit."
|
|
exit 2
|
|
}
|
|
|
|
_ => {
|
|
print $"Unknown backup subcommand: ($sub)"
|
|
print "Run: prvng backup help"
|
|
exit 2
|
|
}
|
|
}
|
|
}
|