net-tools/bin/forge-dns-render
Natalie c78e7cde1f feat(dx): add forge-dns-render to net-tools infra installers
Makes the cloud DX forge shortcuts (mcforge, ctforge) first-class citizens of the shared net-tools layer:
- New bin/forge-dns-render (print/install/diff) that sources ~/.vault/*_forge_creds and emits a managed # >>> dx-forges block in /etc/hosts.
- `net sync` now also converges the DX forges (alongside mesh-hosts + ssh).
- Per-project ./run forge:dns now prefers the central renderer (with local fallback).
- Docs updated.

The mcforge:3000 / ctforge:3000 shortcuts are now installed and kept fresh as part of standard DX infra setup (`net sync` after net-tools install, or after any forge:up).
2026-06-28 10:45:47 -04:00

138 lines
4.6 KiB
Bash
Executable file

#!/bin/sh
# forge-dns-render — manage cloud DX forge shortnames (mcforge for magic-civilization,
# ctforge for cocottetech/lilithplatform, ...) in /etc/hosts as part of the shared
# net-tools infra installers for dev DX.
#
# Reads current FORGE_IP from ~/.vault/*_forge_creds (maintained by the per-project
# ./run forge:up / forge:dns in @magic-civilization and @cocottetech).
#
# Emits a marked, idempotently-replaceable block. `net sync` (and manual
# mesh-hosts-render + host-apply) now also converges the DX forge shortcuts.
# Per-project `./run forge:dns` prefers this central renderer when available.
#
# The block lives at the bottom (after the prepended mesh-hosts fleet block).
# Loose hand-managed lines for these names are adopted (removed) on --install.
#
# Usage:
# forge-dns-render # print the block (default)
# forge-dns-render --install # splice/replace in /etc/hosts (sudo)
# forge-dns-render --diff # show what --install would do
#
# Exit codes match mesh-hosts-render (0 success, 2 needs root).
set -eu
mode=print
case "${1:-}" in
""|--print) mode=print ;;
--install) mode=install ;;
--diff) mode=diff ;;
*) echo "forge-dns-render: unknown arg '$1' (use --print|--install|--diff)" >&2; exit 1 ;;
esac
BEGIN='# >>> dx-forges (managed by net-tools/bin/forge-dns-render)'
END='# <<< dx-forges'
HOSTS_FILE=/etc/hosts
VAULT_DIR="${HOME}/.vault"
# Map vault basename -> the short hostname users type for :3000
map_short() {
case "$1" in
mc_forge_creds) echo mcforge ;;
cocotte_forge_creds) echo ctforge ;;
*) echo "" ;;
esac
}
render_block() {
printf '%s\n' "$BEGIN"
printf '# Cloud DX Forgejo /etc/hosts shortcuts (mcforge, ctforge, ...).\n'
printf '# IPs from ~/.vault/*_forge_creds FORGE_IP (refreshed by project ./run forge:up).\n'
printf '# Re-run after any forge:up (droplet gets a new IP on restore-from-snapshot).\n'
printf '# http://<name>:3000 — also kept fresh by net-tools infra (net sync).\n'
had=0
for f in "$VAULT_DIR"/mc_forge_creds "$VAULT_DIR"/cocotte_forge_creds; do
[ -f "$f" ] || continue
base=$(basename "$f")
short=$(map_short "$base")
[ -n "$short" ] || continue
ip=$(grep -E '^FORGE_IP=' "$f" 2>/dev/null | head -1 | cut -d= -f2- | tr -d ' \t\r\n')
[ -n "$ip" ] || continue
printf '%s\t%s\n' "$ip" "$short"
had=1
done
if [ "$had" -eq 0 ]; then
printf '# (no active DX forges — ./run forge:up in the relevant project(s) first)\n'
fi
printf '%s\n' "$END"
}
block=$(render_block)
if [ "$mode" = "print" ]; then
printf '%s\n' "$block"
exit 0
fi
# All shortnames we own (for adoption of stale loose lines).
managed=$(printf '%s\n' "$block" | awk '!/^#/ && NF >= 2 { for (i = 2; i <= NF; i++) print $i }' | sort -u | tr '\n' ' ')
current=$(cat "$HOSTS_FILE" 2>/dev/null || true)
# Strip any previous copy of our block.
stripped=$(printf '%s\n' "$current" | awk -v b="$BEGIN" -v e="$END" '
$0 == b { skip = 1 }
skip != 1 { print }
$0 == e { skip = 0 }
')
# Adopt (remove) any loose lines or name tokens that match our managed forges.
# (Same policy as mesh-hosts-render: the tool owns these names now.)
stripped=$(printf '%s\n' "$stripped" | awk -v names="$managed" '
BEGIN { n = split(names, a, /[[:space:]]+/); for (i = 1; i <= n; i++) if (a[i] != "") set[a[i]] = 1 }
/^[[:space:]]*#/ || NF < 2 { print; next }
{
kept = ""; removed = 0
for (i = 2; i <= NF; i++) {
if ($i in set) removed++
else kept = kept " " $i
}
if (removed == 0) { print; next }
if (kept == "") next
print $1 kept
}
')
# Trim leading/trailing blank lines that accumulate from repeated splices.
stripped=$(printf '%s\n' "$stripped" | awk 'NF {f=1; p=NR} f {l[NR]=$0} END {for(i=1;i<=p;i++) if (i in l) print l[i]}')
# Append the dx-forges block at the bottom (mesh-hosts block is prepended at top).
new=$(printf '%s\n\n%s\n' "$stripped" "$block")
if [ "$mode" = "diff" ]; then
if command -v diff >/dev/null 2>&1; then
printf '%s\n' "$new" | diff -u "$HOSTS_FILE" - || true
else
printf '%s\n' "$new"
fi
exit 0
fi
# --install
if printf '%s\n' "$new" | cmp -s - "$HOSTS_FILE"; then
echo "forge-dns-render: $HOSTS_FILE already up to date"
exit 0
fi
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
echo "forge-dns-render: --install needs root" >&2
exit 2
fi
fi
printf '%s\n' "$new" | $SUDO tee "$HOSTS_FILE" >/dev/null
echo "forge-dns-render: updated $HOSTS_FILE"