#!/usr/bin/env bash
# Runs AS ROOT. Converges sshd's access config and guarantees a listener on
# 2222. Installed by host-config/apply.sh, which also calls it, and re-run by
# buildbox-sshd-access.timer so a box that loses its listener recovers itself.
#
# These boxes have no console. sshd on 2222 is the only way in, and it fails two ways
# that no restart policy can fix: the ListenAddress it was hardened with no longer
# belongs to this node, or nothing ever brought the unit back. The first is removed
# outright by binding the wildcards, which no interface or daemon state can invalidate;
# the second is recovered here by restarting.
set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Every address set here is compared as sorted text; collation must not vary with the
# caller's locale, which differs between an interactive run and the timer.
export LC_ALL=C

CONF=/etc/ssh/sshd_config.d/10-tailscale.conf
SUPERSEDED=/etc/ssh/sshd_config.d/60-buildbox.conf
# Not under /var/lib/buildbox: that path is the scratch mount, absent when the disk is.
PROVEN=/var/lib/buildbox-sshd/proven.conf
LOCK="${BUILDBOX_SSHD_ACCESS_LOCK:-/run/buildbox-sshd-access.lock}"
LOCK_WAIT="${BUILDBOX_SSHD_ACCESS_LOCK_WAIT:-120}"

# The timer and a manual run can arrive together. Two of these rewriting the only sshd
# config and restarting sshd concurrently is the shape of the change that took two boxes
# off the network, so it is serialized before anything is read or written. The lock is
# held for the whole process and released when it exits, however it exits.
exec 9>"$LOCK" || { echo "buildbox-sshd-access: cannot open lock $LOCK" >&2; exit 3; }
if ! flock -w "$LOCK_WAIT" 9; then
  echo "buildbox-sshd-access: another run held $LOCK for more than ${LOCK_WAIT}s" >&2
  exit 3
fi

# The override is test-only: it permits the lock test to prove the path beyond flock
# without ever letting a root CI container reach the configuration-writing code.
EFFECTIVE_UID="${BUILDBOX_SSHD_ACCESS_EFFECTIVE_UID:-$(id -u)}"
[ "$EFFECTIVE_UID" = 0 ] || { echo "buildbox-sshd-access: must run as root" >&2; exit 2; }

listening() { [ -n "$(ss -H -lnt 'sport = :2222' 2>/dev/null)" ]; }
# What sshd actually bound, not what the file asked for: a half-won race leaves one
# family bound and the other missing, and only the bound set proves reachability.
bound_addrs() {
  ss -H -lnt 'sport = :2222' 2>/dev/null | awk '{print $4}' \
    | sed 's/:2222$//; s/^\[//; s/\]$//' | sort -u
}
# reload-or-restart will not start a unit systemd has given up on, which is the state
# this script exists to recover from.
start_sshd() { systemctl reset-failed ssh 2>/dev/null || true; systemctl reload-or-restart ssh; }
unit_ok() {
  systemctl is-active --quiet ssh \
    && systemctl is-enabled --quiet ssh \
    && systemctl is-enabled --quiet buildbox-sshd-access.timer
}
declared_addrs() { sed -n 's/^ListenAddress //p' "$CONF" 2>/dev/null | sort; }
# The wildcards, put through the same sort as the sets they are compared against.
want_addrs() { printf '0.0.0.0\n::\n' | sort -u; }
# The temp name has no .conf suffix, so sshd's Include glob never sees a partial file.
install_conf() {
  local tmp=/etc/ssh/sshd_config.d/.buildbox-sshd-access.tmp
  install -m 0644 -o root -g root "$1" "$tmp" && mv -f "$tmp" "$CONF"
}
wait_bound() { # $1 = address set that must be bound, empty for "anything on 2222"
  local _
  for _ in 1 2 3 4 5 6 7 8 9 10; do
    if [ -n "$1" ]; then [ "$(bound_addrs)" = "$1" ] && return 0
    else listening && return 0; fi
    sleep 0.5
  done
  return 1
}

render() {
  cat <<'CONF'
# sshd binds the wildcards: every interface, so the bind cannot fail and cannot depend
# on tailscaled being up or on this node keeping its tailnet addresses. A specific
# ListenAddress that stops existing takes the whole daemon down with it and leaves no
# way back in — 0.0.0.0 plus a specific address of the same family is that same trap,
# because the second bind fails and sshd exits.
#
# Port 2222, not 22: tailscaled intercepts tailnet traffic to port 22 to serve
# Tailscale SSH, which would shadow this daemon entirely. 2222 keeps OpenSSH
# reachable as an independent fallback path.
Port 2222

ListenAddress 0.0.0.0
ListenAddress ::

# The listener spans the LAN, so authentication is the only boundary left: keys only,
# no password path, for root and everyone else.
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication no
CONF
}

# Last rung: no ListenAddress at all, so the bind cannot fail. The next timer run
# replaces it as soon as the full config can be rendered again.
render_rescue() {
  cat <<'CONF'
Port 2222
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication no
CONF
}

addrs=$(want_addrs)

if [ "$(bound_addrs)" = "$addrs" ] && [ "$(declared_addrs)" = "$addrs" ] \
   && unit_ok && [ ! -e "$SUPERSEDED" ]; then
  exit 0
fi

systemctl enable ssh >/dev/null 2>&1 || true
systemctl enable buildbox-sshd-access.timer >/dev/null 2>&1 || true

# Never the file that was just there: the state this recovers from includes a config
# that is itself the reason nothing is bound.
restore() {
  local tmp
  rm -f "$SUPERSEDED"
  if [ -f "$PROVEN" ] && install_conf "$PROVEN" && start_sshd && wait_bound ""; then
    echo "buildbox-sshd-access: fell back to the last proven config" >&2
    return 0
  fi
  tmp=$(mktemp); render_rescue >"$tmp"
  install_conf "$tmp"; rm -f "$tmp"
  start_sshd || true
  wait_bound "" || echo "buildbox-sshd-access: rescue config did not bind either" >&2
  echo "buildbox-sshd-access: fell back to the rescue config" >&2
}

new=$(mktemp)
render >"$new"
install_conf "$new"
rm -f "$new" "$SUPERSEDED"

# ssh.service owns /run/sshd via RuntimeDirectory=, so it is gone exactly when the unit is
# down — the state this script recovers from — and sshd -T refuses to parse without it.
install -d -m 0755 -o root -g root /run/sshd
if ! effective=$(sshd -T 2>&1); then
  echo "buildbox-sshd-access: sshd rejected the config" >&2
  printf '%s\n' "$effective" >&2
  restore; exit 1
fi
if [ "$(printf '%s\n' "$effective" | sed -n 's/^port //p' | paste -sd, -)" != 2222 ]; then
  echo "buildbox-sshd-access: effective sshd ports are not exactly 2222 — an Include or a stray drop-in disagrees" >&2
  restore; exit 1
fi

start_sshd
if wait_bound "$addrs"; then
  # Same-directory temp plus rename, never install onto the live path: a reader must
  # never see a partial proven config, and a cross-filesystem move is not a rename.
  install -d -m 0755 -o root -g root "$(dirname "$PROVEN")"
  install -m 0644 -o root -g root "$CONF" "$PROVEN.tmp" && mv -f "$PROVEN.tmp" "$PROVEN"
  echo "SSHD  port 2222 on $(echo $addrs)"
  exit 0
fi
echo "buildbox-sshd-access: sshd did not bind 2222 on $(echo $addrs)" >&2
restore
exit 1
