582 lines
27 KiB
Text
582 lines
27 KiB
Text
#!/usr/bin/env nu
|
|
# provisioning registrar — Domain registrar management via the configured middleware.
|
|
# Reads infra/lib/registrar_domains.ncl for account and domain declarations.
|
|
# Credentials are decrypted from infra/*/secrets/<shared_secret_ref>.sops.yaml via SOPS/Age.
|
|
|
|
use ../../../catalog/providers/prov_lib/middleware.nu [
|
|
mw_registrar_list_domains
|
|
mw_registrar_domain_info
|
|
mw_registrar_get_nameservers
|
|
mw_registrar_set_nameservers
|
|
mw_dns_record_list
|
|
]
|
|
|
|
def load_dns_zones [ws_path: string]: nothing -> record {
|
|
let config_file = ($ws_path | path join "infra/lib/dns_zones.ncl")
|
|
if not ($config_file | path exists) { return {} }
|
|
let r = (do { ^nickel export --format json $config_file } | complete)
|
|
if $r.exit_code != 0 { return {} }
|
|
$r.stdout | from json | get -o zones | default {}
|
|
}
|
|
|
|
# Maps each zone to its DNS provider name string (e.g. "cloudflare", "internal").
|
|
def build_dns_provider_map [zones: record]: nothing -> record {
|
|
$zones
|
|
| transpose name zone
|
|
| each {|z| { ($z.name): ($z.zone.provider | into string) } }
|
|
| into record
|
|
}
|
|
|
|
def registrar_cache_path [ws: string]: nothing -> string {
|
|
let abs = ($ws | path expand)
|
|
let slug = ($abs | hash md5 | str substring 0..12)
|
|
$env.HOME | path join ".cache" "provisioning" "registrar" $"($slug).json"
|
|
}
|
|
|
|
def read_domain_cache [cache_path: string]: nothing -> list<record> {
|
|
if not ($cache_path | path exists) { return [] }
|
|
open $cache_path | get -o domains | default []
|
|
}
|
|
|
|
def write_domain_cache [cache_path: string, domains: list<record>]: nothing -> nothing {
|
|
let dir = ($cache_path | path dirname)
|
|
mkdir $dir
|
|
{ saved_at: (date now | into string), domains: $domains }
|
|
| to json --indent 2
|
|
| save --force $cache_path
|
|
}
|
|
|
|
def cache_saved_at [ws: string]: nothing -> string {
|
|
let p = (registrar_cache_path $ws)
|
|
if not ($p | path exists) { return "" }
|
|
open $p | get -o saved_at | default ""
|
|
}
|
|
|
|
# Returns declared domains from cache (or fetches live if cache empty or --live).
|
|
# --all bypasses cache entirely (superset, not cacheable with declared-domain scope).
|
|
def fetch_cached_domains [config: record, ws: string, live: bool]: nothing -> list<record> {
|
|
let cache = (registrar_cache_path $ws)
|
|
if $live {
|
|
let fresh = (fetch_and_enrich_domains $config false $ws)
|
|
write_domain_cache $cache $fresh
|
|
$fresh
|
|
} else {
|
|
let cached = (read_domain_cache $cache)
|
|
if ($cached | is-not-empty) { $cached } else {
|
|
let fresh = (fetch_and_enrich_domains $config false $ws)
|
|
write_domain_cache $cache $fresh
|
|
$fresh
|
|
}
|
|
}
|
|
}
|
|
|
|
def fetch_and_enrich_domains [config: record, all: bool, ws: string]: nothing -> list<record> {
|
|
let accounts = ($config.accounts? | default {})
|
|
let groups = (build_account_groups $config $all)
|
|
let dns_prov = (build_dns_provider_map (load_dns_zones $ws))
|
|
|
|
let results = ($groups | each {|grp|
|
|
let acc = ($accounts | get -o $grp.account_ref | default {})
|
|
if ($acc | is-empty) {
|
|
print $"(ansi yellow)Warning(ansi reset): account '($grp.account_ref)' not found in registrar_domains.ncl"
|
|
[]
|
|
} else {
|
|
let provider = ($acc.provider | into string)
|
|
set_registrar_creds $acc $ws
|
|
let domains = (mw_registrar_list_domains $provider { domains: $grp.domains })
|
|
$domains | each {|d|
|
|
let ns = (mw_registrar_get_nameservers $provider $d.name)
|
|
$d | upsert nameservers $ns
|
|
}
|
|
}
|
|
} | flatten)
|
|
|
|
if ($dns_prov | columns | is-not-empty) {
|
|
$results | each {|d| $d | upsert external_dns ($dns_prov | get -o $d.name | default "") }
|
|
} else { $results }
|
|
}
|
|
|
|
def load_registrar_config [workspace?: string]: nothing -> record {
|
|
let ws_path = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default "."))
|
|
let config_file = ($ws_path | path join "infra/lib/registrar_domains.ncl")
|
|
if not ($config_file | path exists) {
|
|
error make --unspanned { msg: $"registrar_domains.ncl not found at ($config_file)" }
|
|
}
|
|
let r = (do { ^nickel export --format json $config_file } | complete)
|
|
if $r.exit_code != 0 {
|
|
error make --unspanned { msg: $"Failed to evaluate registrar_domains.ncl: ($r.stderr)" }
|
|
}
|
|
$r.stdout | from json
|
|
}
|
|
|
|
def --env ensure_age_key [ws_path: string]: nothing -> nothing {
|
|
if ($env | get -o SOPS_AGE_KEY_FILE | default "" | is-not-empty) { return }
|
|
let kage_hits = (glob ($"($ws_path)/infra/*/.kage") | append (glob ($"($ws_path)/.p/.kage")))
|
|
if ($kage_hits | is-not-empty) {
|
|
load-env { SOPS_AGE_KEY_FILE: ($kage_hits | first) }
|
|
}
|
|
}
|
|
|
|
def decrypt_sops [sops_file: string, ws_path: string]: nothing -> record {
|
|
ensure_age_key $ws_path
|
|
let r = (do { ^sops --decrypt $sops_file } | complete)
|
|
if $r.exit_code != 0 {
|
|
error make --unspanned { msg: $"sops --decrypt failed for ($sops_file): ($r.stderr)" }
|
|
}
|
|
$r.stdout | from yaml
|
|
}
|
|
|
|
def --env set_registrar_creds [account: record, ws_path: string]: nothing -> nothing {
|
|
let provider = ($account.provider | into string)
|
|
let secret_ref = ($account.shared_secret_ref? | default "")
|
|
if ($secret_ref | is-empty) { return }
|
|
|
|
match $provider {
|
|
"porkbun" => {
|
|
if ($env | get -o PORKBUN_API_KEY | default "" | is-not-empty) { return }
|
|
let matches = (glob ($"($ws_path)/infra/*/secrets/($secret_ref).sops.yaml"))
|
|
if ($matches | is-empty) {
|
|
error make --unspanned { msg: $"No SOPS file found for ($secret_ref) in ($ws_path)/infra/*/secrets/" }
|
|
}
|
|
let secrets = (decrypt_sops ($matches | first) $ws_path)
|
|
let key = ($secrets | get -o api_key | default "")
|
|
let secret = ($secrets | get -o secret_key | default "")
|
|
if ($key | is-empty) { error make --unspanned { msg: $"Key 'api_key' missing in ($secret_ref).sops.yaml" } }
|
|
if ($secret | is-empty) { error make --unspanned { msg: $"Key 'secret_key' missing in ($secret_ref).sops.yaml" } }
|
|
load-env { PORKBUN_API_KEY: $key, PORKBUN_SECRET_KEY: $secret }
|
|
}
|
|
"cloudflare" => {
|
|
let has_token = ($env | get -o CF_API_TOKEN | default "" | is-not-empty)
|
|
let has_acct_id = ($env | get -o CF_ACCOUNT_ID | default "" | is-not-empty)
|
|
if $has_token and $has_acct_id { return }
|
|
let matches = (glob ($"($ws_path)/infra/*/secrets/($secret_ref).sops.yaml"))
|
|
if ($matches | is-empty) {
|
|
error make --unspanned { msg: $"No SOPS file found for ($secret_ref) in ($ws_path)/infra/*/secrets/" }
|
|
}
|
|
let secrets = (decrypt_sops ($matches | first) $ws_path)
|
|
let token = ($secrets | get -o api_token | default "")
|
|
let account_id = ($secrets | get -o account_id | default "")
|
|
if ($token | is-empty) { error make --unspanned { msg: $"Key 'api_token' missing in ($secret_ref).sops.yaml" } }
|
|
load-env { CF_API_TOKEN: $token, CF_ACCOUNT_ID: $account_id }
|
|
}
|
|
_ => {
|
|
error make --unspanned { msg: $"Unknown registrar provider: ($provider)" }
|
|
}
|
|
}
|
|
}
|
|
|
|
# Load per-zone Cloudflare token from dns-<slug>.sops.yaml.
|
|
# Always reloads — each zone has its own scoped token, never shared.
|
|
def --env set_dns_creds [domain: string, ws_path: string]: nothing -> nothing {
|
|
ensure_age_key $ws_path
|
|
let slug = ($domain | str replace --all '.' '-')
|
|
let matches = (glob ($"($ws_path)/infra/*/secrets/dns-($slug).sops.yaml"))
|
|
if ($matches | is-empty) {
|
|
error make --unspanned { msg: $"DNS credentials not found for ($domain) — expected dns-($slug).sops.yaml in infra/*/secrets/" }
|
|
}
|
|
let token = (decrypt_sops ($matches | first) $ws_path | get -o token | default "")
|
|
if ($token | is-empty) {
|
|
error make --unspanned { msg: $"Key 'token' missing in dns-($slug).sops.yaml" }
|
|
}
|
|
load-env { CF_API_TOKEN: $token }
|
|
}
|
|
|
|
def resolve_account [config: record, domain: string]: nothing -> record {
|
|
let domains = ($config.domains? | default {})
|
|
let accounts = ($config.accounts? | default {})
|
|
let d_info = ($domains | get -o $domain | default {})
|
|
if ($d_info | is-empty) {
|
|
error make --unspanned { msg: $"Domain '($domain)' not declared in registrar_domains.ncl" }
|
|
}
|
|
let acc_ref = ($d_info.account | into string)
|
|
let acc = ($accounts | get -o $acc_ref | default {})
|
|
if ($acc | is-empty) {
|
|
error make --unspanned { msg: $"Account '($acc_ref)' not found in registrar_domains.ncl" }
|
|
}
|
|
$acc
|
|
}
|
|
|
|
def build_account_groups [config: record, all: bool]: nothing -> list<record> {
|
|
let accounts = ($config.accounts? | default {})
|
|
let domains = ($config.domains? | default {})
|
|
if $all {
|
|
$accounts | columns | each {|acc_ref| { account_ref: $acc_ref, domains: [] } }
|
|
} else {
|
|
let rows = ($domains | transpose name info | each {|d|
|
|
{ account_ref: ($d.info.account | into string), domain: $d.name }
|
|
})
|
|
let acc_refs = ($rows | get account_ref | uniq)
|
|
$acc_refs | each {|acc_ref|
|
|
let acc_domains = ($rows | where account_ref == $acc_ref | get domain)
|
|
{ account_ref: $acc_ref, domains: $acc_domains }
|
|
}
|
|
}
|
|
}
|
|
|
|
# List domains for this workspace's declared registrars.
|
|
# Without --all: only domains declared in registrar_domains.ncl.
|
|
# With --all: every domain in each registrar account (admin view).
|
|
export def "registrar list" [
|
|
--all (-a)
|
|
--expiring-before (-e): string
|
|
--expiring-in (-x): int # expiring within N months from today
|
|
--workspace (-w): string
|
|
--raw (-r)
|
|
--live (-l)
|
|
--sort-by (-s): string = "expires" # expires | name
|
|
]: nothing -> any {
|
|
let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default "."))
|
|
let config = (load_registrar_config $ws)
|
|
|
|
mut results = if $all {
|
|
fetch_and_enrich_domains $config true $ws
|
|
} else {
|
|
fetch_cached_domains $config $ws $live
|
|
}
|
|
|
|
mut active_filter: string = ""
|
|
|
|
if ($expiring_before | is-not-empty) {
|
|
$results = ($results | where {|d| ($d.expire_date | str substring 0..9) <= $expiring_before })
|
|
$active_filter = $"before ($expiring_before)"
|
|
}
|
|
|
|
if ($expiring_in | is-not-empty) {
|
|
let cutoff = (date now) + ($expiring_in * 30 * 24 * 60 * 60 * 1_000_000_000 | into int | into duration --unit ns)
|
|
let cutoff_str = ($cutoff | format date "%Y-%m-%d")
|
|
$results = ($results | where {|d| ($d.expire_date | str substring 0..9) <= $cutoff_str })
|
|
$active_filter = $"next ($expiring_in)mo — before ($cutoff_str)"
|
|
}
|
|
|
|
if $raw { return $results }
|
|
|
|
let ordered = match $sort_by {
|
|
"name" => ($results | sort-by name),
|
|
_ => ($results | sort-by expire_date),
|
|
}
|
|
let rows = ($ordered | each {|d|
|
|
{
|
|
domain: $d.name,
|
|
registrar: $d.registrar,
|
|
status: $d.status,
|
|
expires: ($d.expire_date | str substring 0..9),
|
|
auto_renew: (if ($d.auto_renew? | default false) { "✓" } else { "✗" }),
|
|
locked: (if ($d.locked? | default false) { "✓" } else { "✗" }),
|
|
privacy: (if ($d.privacy? | default false) { "✓" } else { "✗" }),
|
|
ns: ($d.nameservers? | default [] | str join ", "),
|
|
}
|
|
})
|
|
$rows | table | print
|
|
let registrars = ($results | get -o registrar | compact | uniq | str join ", ")
|
|
if not $all {
|
|
let ts = (cache_saved_at $ws)
|
|
let filter_part = if ($active_filter | is-not-empty) { $" · filter: ($active_filter)" } else { "" }
|
|
let ts_part = if ($ts | is-not-empty) { $" · cache: ($ts)" } else { "" }
|
|
if ($registrars | is-not-empty) or ($ts | is-not-empty) {
|
|
print $"(ansi dark_gray) source: ($registrars) · registrar($filter_part)($ts_part)(ansi reset)"
|
|
}
|
|
} else {
|
|
if ($registrars | is-not-empty) {
|
|
let filter_part = if ($active_filter | is-not-empty) { $" · filter: ($active_filter)" } else { "" }
|
|
print $"(ansi dark_gray) source: ($registrars) · registrar · live($filter_part)(ansi reset)"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Show full domain record including current nameservers.
|
|
export def "registrar show" [
|
|
domain: string
|
|
--workspace (-w): string
|
|
]: nothing -> record {
|
|
let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default "."))
|
|
let config = (load_registrar_config $ws)
|
|
let acc = (resolve_account $config $domain)
|
|
set_registrar_creds $acc $ws
|
|
mw_registrar_domain_info ($acc.provider | into string) $domain
|
|
}
|
|
|
|
# List DNS records declared in Cloudflare for a domain zone.
|
|
# Credentials loaded from infra/*/secrets/dns-<zone-slug>.sops.yaml (per-zone token).
|
|
export def "registrar dns" [
|
|
domain: string
|
|
--type (-t): string
|
|
--workspace (-w): string
|
|
]: nothing -> nothing {
|
|
let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default "."))
|
|
let dns_prov = (build_dns_provider_map (load_dns_zones $ws))
|
|
let provider = ($dns_prov | get -o $domain | default "cloudflare")
|
|
set_dns_creds $domain $ws
|
|
let records = (mw_dns_record_list $provider $domain)
|
|
let filtered = if ($type | is-not-empty) {
|
|
$records | where {|r| $r.type == ($type | str upcase)}
|
|
} else { $records }
|
|
let rows = ($filtered | sort-by type name | each {|r|
|
|
let short_name = if $r.name == $domain { "@" } else { $r.name | str replace $".($domain)" "" }
|
|
{
|
|
type: $r.type,
|
|
name: $short_name,
|
|
value: $r.value,
|
|
ttl: $r.ttl,
|
|
proxied: (if ($r.proxied? | default false) { "✓" } else { "-" }),
|
|
priority: ($r.priority? | default ""),
|
|
}
|
|
})
|
|
$rows | table | print
|
|
print $"(ansi dark_gray) source: ($provider) · dns-provider — ($domain)(ansi reset)"
|
|
}
|
|
|
|
# Get nameservers currently delegated for domain.
|
|
export def "registrar ns get" [
|
|
domain: string
|
|
--workspace (-w): string
|
|
]: nothing -> list<string> {
|
|
let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default "."))
|
|
let config = (load_registrar_config $ws)
|
|
let acc = (resolve_account $config $domain)
|
|
set_registrar_creds $acc $ws
|
|
mw_registrar_get_nameservers ($acc.provider | into string) $domain
|
|
}
|
|
|
|
# Replace nameserver delegation for domain. Requires --confirm.
|
|
export def "registrar ns set" [
|
|
domain: string
|
|
...ns: string
|
|
--workspace (-w): string
|
|
--confirm
|
|
]: nothing -> bool {
|
|
if not $confirm {
|
|
error make --unspanned { msg: "Pass --confirm to change nameservers" }
|
|
}
|
|
if ($ns | is-empty) {
|
|
error make --unspanned { msg: "Provide at least one nameserver" }
|
|
}
|
|
let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default "."))
|
|
let config = (load_registrar_config $ws)
|
|
let acc = (resolve_account $config $domain)
|
|
set_registrar_creds $acc $ws
|
|
mw_registrar_set_nameservers ($acc.provider | into string) $domain $ns
|
|
}
|
|
|
|
# Security and expiry audit for declared domains.
|
|
# Reports per-domain risks: transfer-unlocked, whois-exposed, no-auto-renew, expires-soon.
|
|
# Prints "All N domains clean." when no risks are found.
|
|
export def "registrar audit" [
|
|
--days (-d): int = 60
|
|
--workspace (-w): string
|
|
--live (-l)
|
|
]: nothing -> nothing {
|
|
let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default "."))
|
|
let config = (load_registrar_config $ws)
|
|
let domains = (fetch_cached_domains $config $ws $live)
|
|
|
|
let risks = ($domains | each {|d|
|
|
let exp_str = ($d.expire_date | str substring 0..10)
|
|
let days_left = ((($exp_str | into datetime) - (date now)) / 1day | into int)
|
|
mut r: list<string> = []
|
|
if not ($d.locked? | default false) { $r = ($r | append "transfer-unlocked") }
|
|
if not ($d.privacy? | default false) { $r = ($r | append "whois-exposed") }
|
|
if not ($d.auto_renew? | default false) { $r = ($r | append "no-auto-renew") }
|
|
if $days_left <= $days { $r = ($r | append $"expires-in-($days_left)d") }
|
|
if ($r | is-not-empty) {
|
|
[{ domain: $d.name, expires: $exp_str, days_left: $days_left, risks: ($r | str join ", ") }]
|
|
} else { [] }
|
|
} | flatten)
|
|
|
|
if ($risks | is-empty) {
|
|
print $"(ansi green)All ($domains | length) domains clean.(ansi reset)"
|
|
} else {
|
|
$risks | table | print
|
|
}
|
|
let registrars = ($domains | get -o registrar | compact | uniq | str join ", ")
|
|
let ts = (cache_saved_at $ws)
|
|
let ts_part = if ($ts | is-not-empty) { $" · cache: ($ts)" } else { "" }
|
|
if ($registrars | is-not-empty) {
|
|
print $"(ansi dark_gray) source: ($registrars) · registrar($ts_part)(ansi reset)"
|
|
}
|
|
}
|
|
|
|
def print_help []: nothing -> nothing {
|
|
let h = (ansi green_bold)
|
|
let c = (ansi cyan)
|
|
let y = (ansi yellow)
|
|
let d = (ansi dark_gray)
|
|
let r = (ansi reset)
|
|
let ex = (ansi white)
|
|
|
|
print $"($h)prvng registrar($r) — Domain registrar management"
|
|
print $"($d)Reads infra/lib/registrar_domains.ncl · credentials from infra/*/secrets/<ref>.sops.yaml($r)"
|
|
print ""
|
|
|
|
print $"($h)LIST($r)"
|
|
print $" ($c)ls($r) / ($c)l($r) / ($c)list($r) declared domains — reads cache"
|
|
print $" ($c)ll($r) fetch live from API + refresh cache"
|
|
print $" ($c)la($r) all domains in registrar account — admin"
|
|
print $" ($c)lal($r) all domains, live refresh"
|
|
print $" ($d)Columns: domain · registrar · status · expires · auto_renew · locked · privacy · ns($r)"
|
|
print ""
|
|
print $" ($ex)$ prvng registrar ls($r)"
|
|
print $" ($ex)$ prvng registrar ls --expiring-in 2($r) ($d)expiring in next 2 months($r)"
|
|
print $" ($ex)$ prvng registrar ls --expiring-before 2026-08-01($r) ($d)before a specific date($r)"
|
|
print $" ($ex)$ prvng registrar ls --sort-by name($r) ($d)alphabetical order($r)"
|
|
print $" ($ex)$ prvng registrar ll($r) ($d)refresh cache then show($r)"
|
|
print $" ($ex)$ prvng registrar la($r) ($d)all porkbun account domains($r)"
|
|
print ""
|
|
|
|
print $"($h)DNS RECORDS($r)"
|
|
print $" ($c)dns($r) / ($c)d($r) <domain> [--type TYPE] Cloudflare records for that zone"
|
|
print $" ($d)Columns: type · name · value · ttl · proxied · priority($r)"
|
|
print $" ($d)Token loaded from infra/*/secrets/dns-<zone-slug>.sops.yaml — scoped per zone($r)"
|
|
print ""
|
|
print $" ($ex)$ prvng registrar dns rust-laspalmas.dev($r) ($d)all records($r)"
|
|
print $" ($ex)$ prvng registrar d rust-laspalmas.dev --type A($r) ($d)A records only($r)"
|
|
print $" ($ex)$ prvng registrar d jesusperez.pro --type MX($r) ($d)mail records($r)"
|
|
print $" ($ex)$ prvng registrar d rustelo.com --type TXT($r) ($d)SPF / DKIM / DMARC($r)"
|
|
print ""
|
|
|
|
print $"($h)NAMESERVERS($r)"
|
|
print $" ($c)ns get($r) / ($c)ng($r) <domain> current NS from registrar API"
|
|
print $" ($c)ns set($r) / ($c)ns($r) <domain> <ns1> <ns2> --confirm replace NS delegation"
|
|
print ""
|
|
print $" ($ex)$ prvng registrar ng rust-laspalmas.dev($r)"
|
|
print $" ($ex)$ prvng registrar ns rust-laspalmas.dev ns1.example.com ns2.example.com --confirm($r)"
|
|
print ""
|
|
|
|
print $"($h)SECURITY AUDIT($r)"
|
|
print $" ($c)audit($r) / ($c)a($r) [--days N] risks per domain: transfer-unlocked, whois-exposed, no-auto-renew, expiry"
|
|
print $" ($d)Default threshold: 60 days. --live fetches fresh data.($r)"
|
|
print ""
|
|
print $" ($ex)$ prvng registrar audit($r) ($d)domains with any risk (default 60d)($r)"
|
|
print $" ($ex)$ prvng registrar audit --days 30($r) ($d)expiry threshold 30 days($r)"
|
|
print $" ($ex)$ prvng registrar a --live($r) ($d)audit from fresh API data($r)"
|
|
print ""
|
|
|
|
print $"($h)DOMAIN DETAIL($r)"
|
|
print $" ($c)show($r) / ($c)s($r) <domain> full record from registrar — includes NS"
|
|
print ""
|
|
print $" ($ex)$ prvng registrar show rustelo.dev($r)"
|
|
print ""
|
|
|
|
print $"($h)CACHE($r)"
|
|
let cache_dir = ($env.HOME | path join ".cache" "provisioning" "registrar")
|
|
print $" ($d)($cache_dir)/<ws-hash>.json($r)"
|
|
print $" ($d)Written on first call or --live. Stores domain list + NS. No TTL — use ll to refresh.($r)"
|
|
print ""
|
|
|
|
print $"($h)CONFIG($r)"
|
|
print $" ($d)Declared domains :($r) infra/lib/registrar_domains.ncl"
|
|
print $" ($d)DNS zones :($r) infra/lib/dns_zones.ncl"
|
|
print $" ($d)Registrar secret :($r) infra/*/secrets/<shared_secret_ref>.sops.yaml"
|
|
print $" ($d)porkbun → api_key + secret_key($r)"
|
|
print $" ($d)cloudflare → api_token + account_id($r)"
|
|
print $" ($d)DNS secret :($r) infra/*/secrets/dns-<zone-slug>.sops.yaml ($d)field: token($r)"
|
|
print $" ($y)auto-renew toggle not in Porkbun API v3 — manage at porkbun.com($r)"
|
|
}
|
|
|
|
# Short aliases — main dispatch entry points
|
|
def "main list" [--all (-a) --expiring-before (-e): string --expiring-in (-x): int --workspace (-w): string --raw (-r) --live (-l) --sort-by (-s): string = "expires"]: nothing -> any {
|
|
registrar list --all=$all --expiring-before $expiring_before --expiring-in $expiring_in --workspace $workspace --raw=$raw --live=$live --sort-by $sort_by
|
|
}
|
|
def "main l" [--workspace (-w): string --raw (-r) --live (-l) --sort-by (-s): string = "expires" --expiring-in (-x): int]: nothing -> any {
|
|
registrar list --workspace $workspace --raw=$raw --live=$live --sort-by $sort_by --expiring-in $expiring_in
|
|
}
|
|
def "main ls" [--workspace (-w): string --raw (-r) --live (-l) --sort-by (-s): string = "expires" --expiring-in (-x): int --expiring-before (-e): string]: nothing -> any {
|
|
registrar list --workspace $workspace --raw=$raw --live=$live --sort-by $sort_by --expiring-in $expiring_in --expiring-before $expiring_before
|
|
}
|
|
def "main ll" [--workspace (-w): string --raw (-r) --sort-by (-s): string = "expires"]: nothing -> any {
|
|
registrar list --live --workspace $workspace --raw=$raw --sort-by $sort_by
|
|
}
|
|
def "main la" [--expiring-before (-e): string --expiring-in (-x): int --workspace (-w): string --raw (-r) --sort-by (-s): string = "expires"]: nothing -> any {
|
|
registrar list --all --expiring-before $expiring_before --expiring-in $expiring_in --workspace $workspace --raw=$raw --sort-by $sort_by
|
|
}
|
|
def "main lal" [--expiring-before (-e): string --expiring-in (-x): int --workspace (-w): string --raw (-r) --sort-by (-s): string = "expires"]: nothing -> any {
|
|
registrar list --all --live --expiring-before $expiring_before --expiring-in $expiring_in --workspace $workspace --raw=$raw --sort-by $sort_by
|
|
}
|
|
def "main show" [domain: string --workspace (-w): string]: nothing -> record {
|
|
registrar show $domain --workspace $workspace
|
|
}
|
|
def "main s" [domain: string --workspace (-w): string]: nothing -> record {
|
|
registrar show $domain --workspace $workspace
|
|
}
|
|
def "main dns" [domain: string --type (-t): string --workspace (-w): string]: nothing -> nothing {
|
|
registrar dns $domain --type $type --workspace $workspace
|
|
}
|
|
def "main d" [domain: string --type (-t): string --workspace (-w): string]: nothing -> nothing {
|
|
registrar dns $domain --type $type --workspace $workspace
|
|
}
|
|
def "main ns get" [domain: string --workspace (-w): string]: nothing -> list<string> {
|
|
registrar ns get $domain --workspace $workspace
|
|
}
|
|
def "main ng" [domain: string --workspace (-w): string]: nothing -> list<string> {
|
|
registrar ns get $domain --workspace $workspace
|
|
}
|
|
def "main ns set" [domain: string ...ns: string --workspace (-w): string --confirm]: nothing -> bool {
|
|
registrar ns set $domain ...$ns --workspace $workspace --confirm=$confirm
|
|
}
|
|
def "main ns" [domain: string ...ns: string --workspace (-w): string --confirm]: nothing -> bool {
|
|
registrar ns set $domain ...$ns --workspace $workspace --confirm=$confirm
|
|
}
|
|
def "main audit" [--days (-d): int = 60 --workspace (-w): string --live (-l)]: nothing -> nothing {
|
|
registrar audit --days $days --workspace $workspace --live=$live
|
|
}
|
|
def "main a" [--days (-d): int = 60 --workspace (-w): string --live (-l)]: nothing -> nothing {
|
|
registrar audit --days $days --workspace $workspace --live=$live
|
|
}
|
|
def "main help" []: nothing -> nothing { print_help }
|
|
|
|
def main [--help (-h) ...args: string]: nothing -> nothing {
|
|
if $help { print_help; return }
|
|
let first = ($args | get -o 0 | default "")
|
|
let rest = ($args | skip 1)
|
|
match $first {
|
|
"" | "help" | "h" => { print_help }
|
|
"list" | "l" | "ls" => {
|
|
let a = ($rest | any {|x| $x == "--all" or $x == "-a"})
|
|
let lv = ($rest | any {|x| $x == "--live" or $x == "-l"})
|
|
registrar list --all=$a --live=$lv
|
|
}
|
|
"ll" => { registrar list --live }
|
|
"la" => { registrar list --all }
|
|
"lal" => { registrar list --all --live }
|
|
"audit" | "a" => {
|
|
let d = ($rest | where {|x| $x != "--days" and not ($x | str starts-with "-")} | get -o 0 | default "60" | into int)
|
|
let lv = ($rest | any {|x| $x == "--live" or $x == "-l"})
|
|
registrar audit --days $d --live=$lv
|
|
}
|
|
"show" | "s" => {
|
|
let domain = ($rest | get -o 0 | default "")
|
|
if ($domain | is-empty) { error make --unspanned { msg: "show requires <domain>" } }
|
|
registrar show $domain
|
|
}
|
|
"dns" | "d" => {
|
|
let domain = ($rest | get -o 0 | default "")
|
|
if ($domain | is-empty) { error make --unspanned { msg: "dns requires <domain>" } }
|
|
let t = ($rest | skip 1 | where {|x| not ($x | str starts-with "-")} | get -o 0 | default "")
|
|
if ($t | is-not-empty) { registrar dns $domain --type $t } else { registrar dns $domain }
|
|
}
|
|
"ns" => {
|
|
let sub = ($rest | get -o 0 | default "")
|
|
match $sub {
|
|
"get" | "ng" => {
|
|
let domain = ($rest | get -o 1 | default "")
|
|
if ($domain | is-empty) { error make --unspanned { msg: "ns get requires <domain>" } }
|
|
registrar ns get $domain
|
|
}
|
|
"set" | "ns" => {
|
|
let domain = ($rest | get -o 1 | default "")
|
|
if ($domain | is-empty) { error make --unspanned { msg: "ns set requires <domain> <ns...>" } }
|
|
let ns_list = ($rest | skip 2 | where {|x| not ($x | str starts-with "--") })
|
|
let confirm = ($rest | any {|x| $x == "--confirm"})
|
|
if not $confirm { error make --unspanned { msg: "Pass --confirm to change nameservers" } }
|
|
registrar ns set $domain ...$ns_list --confirm
|
|
}
|
|
_ => { print $"Unknown ns subcommand: ($sub)"; print_help }
|
|
}
|
|
}
|
|
"ng" => {
|
|
let domain = ($rest | get -o 0 | default "")
|
|
if ($domain | is-empty) { error make --unspanned { msg: "ng requires <domain>" } }
|
|
registrar ns get $domain
|
|
}
|
|
_ => { print $"Unknown subcommand: ($first)"; print_help }
|
|
}
|
|
}
|