151 lines
5.9 KiB
Bash
Executable file
151 lines
5.9 KiB
Bash
Executable file
#!/bin/sh
|
|
# host-apply — render THIS device's view of the fleet from data/mesh-hosts.json.
|
|
#
|
|
# Unlike the other renderers (which emit a uniform artifact), host-apply detects
|
|
# which host it runs on and computes addresses from THAT device's vantage point:
|
|
#
|
|
# ssh HostName for self→target =
|
|
# target.public if the target has a public IP (robust, always up)
|
|
# target.lan elif target has a LAN IP AND self can reach the LAN
|
|
# (self has a LAN IP, or self is the roaming laptop —
|
|
# the wg tunnel routes the LAN /24, and the
|
|
# smart-lan-router daemon makes it direct when home)
|
|
# target.wg else (mesh-only)
|
|
#
|
|
# It writes a single managed block (Host <name> <aliases> → HostName/User) to the
|
|
# invoking user's ~/.ssh/config, placed at the TOP so it wins first-match over
|
|
# any hand-maintained stanzas. Old names are kept as Host aliases (alias-first).
|
|
#
|
|
# Self is identified by matching the box's hostname/short-name or any local IPv4
|
|
# (incl. the wg IP) against hosts[].{name,aliases,lan,wg}.
|
|
#
|
|
# Usage:
|
|
# host-apply # --ssh-print : print this device's ssh block (default)
|
|
# host-apply --ssh-diff # diff against current ~/.ssh/config
|
|
# host-apply --ssh-apply # splice/replace the managed block (backs up first)
|
|
# host-apply --whoami # just print which host this device resolves to
|
|
#
|
|
# Companion (run separately, needs root): `mesh-hosts-render --install` writes
|
|
# this device's /etc/hosts view (the .wg/.lan names). Together they cover a
|
|
# device's ssh + hosts views from the one source of truth.
|
|
|
|
set -eu
|
|
|
|
mode=ssh-print
|
|
case "${1:-}" in
|
|
""|--ssh-print) mode=ssh-print ;;
|
|
--ssh-diff) mode=ssh-diff ;;
|
|
--ssh-apply) mode=ssh-apply ;;
|
|
--whoami) mode=whoami ;;
|
|
*) echo "host-apply: unknown arg '$1'" >&2; exit 1 ;;
|
|
esac
|
|
|
|
BEGIN='# >>> net-tools fleet (managed by host-apply) — do not edit by hand'
|
|
END='# <<< net-tools fleet'
|
|
SSH_CONFIG="$HOME/.ssh/config"
|
|
|
|
# --- locate data file (symlink-resolving walk) ---------------------------------
|
|
self_path=$0
|
|
while [ -L "$self_path" ]; do
|
|
link=$(readlink "$self_path")
|
|
case $link in /*) self_path=$link ;; *) self_path=$(dirname "$self_path")/$link ;; esac
|
|
done
|
|
root=$(cd "$(dirname "$self_path")" && pwd)
|
|
while [ "$root" != "/" ] && [ ! -f "$root/data/mesh-hosts.json" ]; do root=$(dirname "$root"); done
|
|
data_file="$root/data/mesh-hosts.json"
|
|
[ -f "$data_file" ] || { echo "host-apply: cannot locate data/mesh-hosts.json" >&2; exit 1; }
|
|
command -v jq >/dev/null || { echo "host-apply: jq not installed" >&2; exit 1; }
|
|
|
|
# Overlay: current LAN IPs discovered by the daemon (data/lan-state.json, a
|
|
# {name: ip} map) override the static `lan` seed, so ssh tracks DHCP drift.
|
|
overlay='{}'
|
|
state_file="$root/data/lan-state.json"
|
|
if [ -f "$state_file" ] && jq -e . "$state_file" >/dev/null 2>&1; then
|
|
overlay=$(cat "$state_file")
|
|
fi
|
|
|
|
# --- identify self -------------------------------------------------------------
|
|
short=$(hostname 2>/dev/null | cut -d. -f1)
|
|
[ -n "$short" ] || short=$(uname -n | cut -d. -f1)
|
|
if command -v ip >/dev/null 2>&1; then
|
|
local_ips=$(ip -o -4 addr show 2>/dev/null | awk '{print $4}' | cut -d/ -f1)
|
|
else
|
|
local_ips=$(ifconfig 2>/dev/null | awk '/inet /{print $2}')
|
|
fi
|
|
ips_json=$(printf '%s\n' $local_ips | jq -R . | jq -s .)
|
|
|
|
self=$(jq -r --arg h "$short" --argjson ips "$ips_json" '
|
|
[ .hosts[]
|
|
| . as $x
|
|
| select( ($x.name == $h)
|
|
or ($x.aliases | index($h))
|
|
or ($x.lan != null and ($ips | index($x.lan)))
|
|
or ($ips | index($x.wg)) )
|
|
| $x.name ] | first // empty
|
|
' "$data_file")
|
|
|
|
[ -n "$self" ] || { echo "host-apply: could not identify this host (short=$short, ips=$local_ips) in mesh-hosts.json" >&2; exit 1; }
|
|
|
|
if [ "$mode" = "whoami" ]; then
|
|
echo "$self"
|
|
exit 0
|
|
fi
|
|
|
|
# self_reaches_lan: a host with its own LAN IP, or the roaming laptop (tunnel
|
|
# routes 10.0.0.0/24; the daemon makes it direct when home).
|
|
reachlan=$(jq -r --arg s "$self" '
|
|
.hosts[] | select(.name == $s)
|
|
| ((.lan != null) or (.class == "laptop"))
|
|
' "$data_file")
|
|
|
|
# --- render this device's ssh block --------------------------------------------
|
|
render_block() {
|
|
printf '%s\n' "$BEGIN"
|
|
printf '# rendered for: %s (vantage: %s)\n' "$self" \
|
|
"$( [ "$reachlan" = "true" ] && echo 'LAN-capable → prefer .lan' || echo 'mesh-only → prefer .wg' )"
|
|
jq -r --arg s "$self" --argjson reachlan "$reachlan" --argjson ov "$overlay" '
|
|
.hosts[]
|
|
| select(.name != $s)
|
|
| . as $h
|
|
| (($ov[$h.name]) // $h.lan) as $lan
|
|
| ( $h.public
|
|
// (if $reachlan and $lan != null then $lan else null end)
|
|
// $h.wg ) as $addr
|
|
| "\nHost \(([$h.name] + $h.aliases) | join(" "))\n HostName \($addr)\n User \($h.ssh_user // "lilith")"
|
|
' "$data_file"
|
|
printf '\n%s\n' "$END"
|
|
}
|
|
|
|
block=$(render_block)
|
|
|
|
if [ "$mode" = "ssh-print" ]; then
|
|
printf '%s\n' "$block"
|
|
exit 0
|
|
fi
|
|
|
|
# Strip any existing managed block, then prepend the fresh one (top = wins).
|
|
current=""
|
|
[ -f "$SSH_CONFIG" ] && current=$(cat "$SSH_CONFIG")
|
|
stripped=$(printf '%s\n' "$current" | awk -v b="$BEGIN" -v e="$END" '
|
|
$0 == b { skip = 1 } skip != 1 { print } $0 == e { skip = 0 }')
|
|
new=$(printf '%s\n\n%s\n' "$block" "$stripped")
|
|
|
|
if [ "$mode" = "ssh-diff" ]; then
|
|
if command -v diff >/dev/null 2>&1; then
|
|
printf '%s\n' "$new" | diff -u "${SSH_CONFIG:-/dev/null}" - || true
|
|
else
|
|
printf '%s\n' "$new"
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# --ssh-apply
|
|
if [ -f "$SSH_CONFIG" ] && printf '%s\n' "$new" | cmp -s - "$SSH_CONFIG"; then
|
|
echo "host-apply: $SSH_CONFIG already up to date for $self"
|
|
exit 0
|
|
fi
|
|
mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh"
|
|
[ -f "$SSH_CONFIG" ] && cp "$SSH_CONFIG" "$SSH_CONFIG.netbak"
|
|
printf '%s\n' "$new" > "$SSH_CONFIG"
|
|
chmod 600 "$SSH_CONFIG"
|
|
echo "host-apply: wrote $self's fleet block to $SSH_CONFIG (backup: $SSH_CONFIG.netbak)"
|