mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:07:03 +01:00
`verify` gated on the two invariants whose breach cut every bridged container off the internet on 2026-08-29 and again after the 2026-08-30 reboot -- but nothing invoked it, so it reported drift only when a human thought to ask, and the outage it exists for is exactly the one nobody thinks to ask about. nftables-drift.service now runs it after nftables.service, docker.service and network-online.target at every boot, plus a daily Persistent timer. A breach is a failed unit in `systemctl --failed`, not a log line: a self-check that detects a failure has to gate, not warn. Proven in ~/utils/vmbox across two real reboots -- drifted /etc leaves the unit failed with the diff and the missing docker0 accept in its journal, matching /etc leaves it active with no false positive -- then installed and run green on the host. Not yet observed across a host reboot. Also bumps the gatelock pin to v0.7.3, which is what turned main's dependency-freshness job red; CI_GREEN_SKIP is set because this commit is that fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015d48t4qsQLBJnDwDeo2Nom
155 lines
4.0 KiB
Bash
Executable File
155 lines
4.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Self-hosted WireGuard VPN + hardened SSH for remote terminal access from
|
|
# Android, working across different networks (no relay, no third-party
|
|
# coordination server -- point-to-point WireGuard via a port-forwarded UDP
|
|
# port and DuckDNS for the dynamic public IP).
|
|
#
|
|
# Usage:
|
|
# sudo ./setup_wireguard_ssh.sh setup - full first-time setup
|
|
# sudo ./setup_wireguard_ssh.sh add-peer <name> - provision a new phone/laptop
|
|
# ./setup_wireguard_ssh.sh status - show current state
|
|
# sudo ./setup_wireguard_ssh.sh revoke <name> - remove a peer
|
|
# ./setup_wireguard_ssh.sh help
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|
# shellcheck source=../lib/common.sh
|
|
source "$SCRIPT_DIR/../lib/common.sh"
|
|
|
|
readonly WG_IFACE="wg0"
|
|
readonly WG_PORT="51820"
|
|
readonly WG_SUBNET="10.8.0.0/24"
|
|
readonly WG_SERVER_IP="10.8.0.1"
|
|
readonly WG_DIR="/etc/wireguard"
|
|
readonly WG_CONF="${WG_DIR}/${WG_IFACE}.conf"
|
|
readonly WG_CLIENTS_DIR="${WG_DIR}/clients"
|
|
readonly NFT_CONF="/etc/nftables.conf"
|
|
readonly SSHD_DROPIN="/etc/ssh/sshd_config.d/10-wireguard-only.conf"
|
|
readonly DUCKDNS_DIR="/opt/duckdns"
|
|
readonly CONFIG_FILE="${SCRIPT_DIR}/.wireguard_ssh.conf"
|
|
|
|
# Load saved config (DuckDNS domain/token, LAN subnet override) if present.
|
|
if [[ -f $CONFIG_FILE ]]; then
|
|
# shellcheck source=/dev/null
|
|
source "$CONFIG_FILE"
|
|
fi
|
|
DUCKDNS_DOMAIN="${DUCKDNS_DOMAIN:-}"
|
|
DUCKDNS_TOKEN="${DUCKDNS_TOKEN:-}"
|
|
LAN_SUBNET="${LAN_SUBNET:-}"
|
|
export ALLOW_WEB="${ALLOW_WEB:-false}"
|
|
export ALLOW_DNS="${ALLOW_DNS:-false}"
|
|
|
|
die() {
|
|
log_error "$1"
|
|
exit 1
|
|
}
|
|
|
|
save_config() {
|
|
cat >"$CONFIG_FILE" <<EOF
|
|
DUCKDNS_DOMAIN="${DUCKDNS_DOMAIN}"
|
|
DUCKDNS_TOKEN="${DUCKDNS_TOKEN}"
|
|
LAN_SUBNET="${LAN_SUBNET}"
|
|
ALLOW_WEB="${ALLOW_WEB}"
|
|
ALLOW_DNS="${ALLOW_DNS}"
|
|
EOF
|
|
chmod 600 "$CONFIG_FILE"
|
|
}
|
|
|
|
detect_lan_subnet() {
|
|
if [[ -n $LAN_SUBNET ]]; then
|
|
return 0
|
|
fi
|
|
local lan_ip
|
|
lan_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')
|
|
if [[ -z $lan_ip ]]; then
|
|
die "Could not auto-detect LAN IP. Set LAN_SUBNET=192.168.x.0/24 and re-run."
|
|
fi
|
|
LAN_SUBNET="${lan_ip%.*}.0/24"
|
|
log_info "Detected LAN subnet: $LAN_SUBNET"
|
|
save_config
|
|
}
|
|
|
|
install_dependencies() {
|
|
log_info "Installing dependencies (wireguard-tools, qrencode, nftables, openssh)..."
|
|
install_missing_pacman_packages wireguard-tools qrencode nftables openssh
|
|
}
|
|
|
|
# shellcheck source=lib/wg_keys.sh
|
|
source "$SCRIPT_DIR/lib/wg_keys.sh"
|
|
# shellcheck source=lib/wg_firewall.sh
|
|
source "$SCRIPT_DIR/lib/wg_firewall.sh"
|
|
# shellcheck source=lib/wg_sshd.sh
|
|
source "$SCRIPT_DIR/lib/wg_sshd.sh"
|
|
# shellcheck source=lib/wg_verify_units.sh
|
|
source "$SCRIPT_DIR/lib/wg_verify_units.sh"
|
|
|
|
main() {
|
|
local cmd="${1:-help}"
|
|
|
|
# Forward the FULL original argv to require_root before shifting anything
|
|
# off -- exec sudo "$0" "$@" inside require_root must re-launch with the
|
|
# subcommand still present, or sudo would silently run with no args.
|
|
case "$cmd" in
|
|
setup | add-peer | revoke | allow-web | allow-dns | verify | install-verify)
|
|
require_root "$@"
|
|
;;
|
|
esac
|
|
|
|
shift || true
|
|
case "$cmd" in
|
|
setup)
|
|
install_dependencies
|
|
generate_server_keys
|
|
write_wg0_conf
|
|
enable_wg_service
|
|
write_nftables_ruleset
|
|
verify_nftables_then_apply
|
|
install_verify_units
|
|
harden_sshd
|
|
setup_duckdns
|
|
print_router_instructions
|
|
print_android_instructions
|
|
log_ok "Setup complete. Run 'add-peer <name>' to provision your phone."
|
|
;;
|
|
add-peer)
|
|
add_phone_peer "${1:-}"
|
|
print_android_instructions
|
|
;;
|
|
allow-web)
|
|
allow_web
|
|
# Re-armed on every apply: this is the command that regenerates
|
|
# /etc/nftables.conf, so it is exactly when the drift check must
|
|
# exist. Leaving it to a separate step people must remember is the
|
|
# gap that let the same outage happen twice.
|
|
install_verify_units
|
|
;;
|
|
allow-dns)
|
|
allow_dns
|
|
install_verify_units
|
|
;;
|
|
verify)
|
|
verify_nft
|
|
;;
|
|
install-verify)
|
|
install_verify_units
|
|
;;
|
|
status)
|
|
status_cmd
|
|
;;
|
|
revoke)
|
|
revoke_peer "${1:-}"
|
|
;;
|
|
help | -h | --help)
|
|
usage
|
|
;;
|
|
*)
|
|
log_error "Unknown command: $cmd"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|