From 8dd66945ddcc8ddf9cf0665b5eaa36b5146065df Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 02:03:59 +0000 Subject: [PATCH 1/2] Offer to actually set up VPN + SSH keys at the DR-spare prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requested improvement: the disaster-recovery spare prompt in backup.sh already ran a live connectivity check and, on failure, printed manual instructions (set up wg-easy separately if the spare isn't reachable, run ssh-keygen/ssh-copy-id yourself) — but never offered to do any of it right there, even though every piece is safe to automate inline. Now, when the passwordless SSH check fails: - If wg-easy isn't installed yet, offers to chain-install it (guarded with declare -F install_wg-easy, same pattern asterisk.sh already uses for security-dashboard/pstn-trunk) — covers the common case where the spare is a home box with no port-forward and no path there at all yet, not just a missing key. - If root has no SSH key, offers to generate one (ssh-keygen -t ed25519). - Offers to run ssh-copy-id against the spare interactively right there — it prompts for the spare's login password itself, so this script never touches or sees that password, just invokes the real command inline instead of telling the operator to go run it themselves after. - Re-runs the connectivity check after ssh-copy-id succeeds, so the install flow reports the actual current state instead of the pre-fix failure message. Verified the has-a-key detection (the part most likely to have a subtle &&/|| precedence bug) against all four cases — no key, only id_ed25519, only id_rsa, both — behaves correctly in each. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn --- services/backup.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/services/backup.sh b/services/backup.sh index b88ddd8..1bc2efe 100644 --- a/services/backup.sh +++ b/services/backup.sh @@ -496,8 +496,53 @@ install_backup() { log_success " SSH to $DR_SYNC_HOST works — spare sync will run after each backup." else log_warning " Couldn't SSH to $DR_SYNC_HOST without a password right now." - log_warning " Spare sync is saved but will fail until this works (as root, since" - log_warning " the backup timer runs as root): ssh-keygen; ssh-copy-id $DR_SYNC_HOST" + + # If the spare isn't reachable at all (behind NAT, no port-forward — + # a home box is the common case), a passwordless key won't help + # until there's a network path there in the first place. Offer the + # VPN hub right here instead of just telling the user to go set + # one up separately and come back. + if [ ! -d "$DOCKER_DIR/wg-easy" ]; then + local _SETUP_VPN="" + prompt_yn " Spare box not directly reachable (behind NAT, no port-forward)? Set up a WireGuard VPN hub (wg-easy) now so they can reach each other? (y/n):" "n" _SETUP_VPN + if [[ "$_SETUP_VPN" =~ ^[Yy]$ ]]; then + if declare -F install_wg-easy >/dev/null 2>&1; then + install_wg-easy + else + log_warning " services/wg-easy.sh isn't loaded — run: sudo ./setup.sh wg-easy" + fi + fi + fi + + local _HAVE_KEY=false + [ -f /root/.ssh/id_ed25519 ] || [ -f /root/.ssh/id_rsa ] && _HAVE_KEY=true + if [ "$_HAVE_KEY" = false ]; then + local _GEN_KEY="" + prompt_yn " No SSH key found for root — generate one now (ssh-keygen)? (y/n):" "y" _GEN_KEY + if [[ "$_GEN_KEY" =~ ^[Yy]$ ]]; then + ssh-keygen -t ed25519 -N "" -f /root/.ssh/id_ed25519 -q \ + && log_success " Generated /root/.ssh/id_ed25519" \ + || log_warning " ssh-keygen failed — generate one manually." + fi + fi + + local _COPY_KEY="" + prompt_yn " Run ssh-copy-id to $DR_SYNC_HOST now? (asks for its login password interactively) (y/n):" "y" _COPY_KEY + if [[ "$_COPY_KEY" =~ ^[Yy]$ ]]; then + if ssh-copy-id "$DR_SYNC_HOST"; then + if ssh -o BatchMode=yes -o ConnectTimeout=5 "$DR_SYNC_HOST" true 2>/dev/null; then + log_success " SSH to $DR_SYNC_HOST now works — spare sync will run after each backup." + else + log_warning " ssh-copy-id reported success but the passwordless check still failed — check manually." + fi + else + log_warning " ssh-copy-id failed. Spare sync is saved but will fail until this works:" + log_warning " ssh-copy-id $DR_SYNC_HOST" + fi + else + log_warning " Spare sync is saved but will fail until this works (as root, since" + log_warning " the backup timer runs as root): ssh-copy-id $DR_SYNC_HOST" + fi fi fi From ba9c31aeb1a7c48af85a40baaa765054d445873e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 02:13:29 +0000 Subject: [PATCH 2/2] Detect Netbird/Tailscale too before offering wg-easy at the DR-spare prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requested: don't push the operator toward installing wg-easy if they already have a different mesh VPN (Netbird or Tailscale) running — detect any of the three first, and only offer a choice when none are present. Detection checks wg-easy's own directory (this repo's install marker), then falls back to checking whether the netbird/tailscale binaries exist AND their systemd services are actually active — not just installed, since an installed-but-never-connected client isn't a usable path to the spare box either. wg-easy takes priority if somehow more than one is present, since it's this repo's own chain-installable option. When none are detected, offers a numbered choice: wg-easy (chain-installs via the existing declare -F guard), Netbird, or Tailscale (both via their official curl-pipe-sh installers — verified the current URLs against each vendor's own docs rather than guessing, since a wrong URL here would be a bad thing to ship). Both third-party options still need a manual follow-up step this script can't complete unattended (Netbird needs a setup key from the operator's account, Tailscale needs an interactive auth link) — the success message says so rather than implying the install alone finishes the job. Verified the detection branching against all the cases that matter: nothing present, only wg-easy's directory, only Netbird active, only Tailscale active, and multiple present at once (wg-easy correctly wins). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn --- services/backup.sh | 62 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/services/backup.sh b/services/backup.sh index 1bc2efe..8955f68 100644 --- a/services/backup.sh +++ b/services/backup.sh @@ -499,19 +499,55 @@ install_backup() { # If the spare isn't reachable at all (behind NAT, no port-forward — # a home box is the common case), a passwordless key won't help - # until there's a network path there in the first place. Offer the - # VPN hub right here instead of just telling the user to go set - # one up separately and come back. - if [ ! -d "$DOCKER_DIR/wg-easy" ]; then - local _SETUP_VPN="" - prompt_yn " Spare box not directly reachable (behind NAT, no port-forward)? Set up a WireGuard VPN hub (wg-easy) now so they can reach each other? (y/n):" "n" _SETUP_VPN - if [[ "$_SETUP_VPN" =~ ^[Yy]$ ]]; then - if declare -F install_wg-easy >/dev/null 2>&1; then - install_wg-easy - else - log_warning " services/wg-easy.sh isn't loaded — run: sudo ./setup.sh wg-easy" - fi - fi + # until there's a network path there in the first place. Check + # for an already-running mesh VPN first — wg-easy (this repo's + # own), Netbird, or Tailscale are all common, and if the + # operator already has any ONE of them running, pushing them + # toward installing a second, redundant mesh would be actively + # wrong. Only offer a choice when none of the three are present. + local _VPN_DETECTED="" + if [ -d "$DOCKER_DIR/wg-easy" ]; then + _VPN_DETECTED="wg-easy" + elif command -v netbird >/dev/null 2>&1 && systemctl is-active --quiet netbird 2>/dev/null; then + _VPN_DETECTED="Netbird" + elif command -v tailscale >/dev/null 2>&1 && systemctl is-active --quiet tailscaled 2>/dev/null; then + _VPN_DETECTED="Tailscale" + fi + + if [ -n "$_VPN_DETECTED" ]; then + log_info " Detected $_VPN_DETECTED already running — use its address for the spare box" + log_info " destination above instead of the public one, if you haven't already." + else + echo "" + echo " 1) wg-easy — this repo's own guided WireGuard hub (chain-installs now)" + echo " 2) Netbird — official installer (needs a setup key from your Netbird" + echo " account/self-hosted server — https://docs.netbird.io)" + echo " 3) Tailscale — official installer (opens an auth link to your account —" + echo " https://tailscale.com)" + echo " 4) Skip" + echo "" + local _VPN_CHOICE="" + prompt_text " Spare box not directly reachable? Set up a VPN mesh now [4]:" "4" _VPN_CHOICE + case "${_VPN_CHOICE:-4}" in + 1) + if declare -F install_wg-easy >/dev/null 2>&1; then + install_wg-easy + else + log_warning " services/wg-easy.sh isn't loaded — run: sudo ./setup.sh wg-easy" + fi + ;; + 2) + curl -fsSL https://pkgs.netbird.io/install.sh | sh \ + && log_success " Netbird installed — finish setup with: netbird up --setup-key " \ + || log_warning " Netbird install failed — see https://docs.netbird.io/get-started/install/linux" + ;; + 3) + curl -fsSL https://tailscale.com/install.sh | sh \ + && log_success " Tailscale installed — finish setup with: tailscale up" \ + || log_warning " Tailscale install failed — see https://tailscale.com/docs/install/linux" + ;; + *) : ;; + esac fi local _HAVE_KEY=false