Files
tinyboard/spoke/setup-network.sh

274 lines
8.2 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
header() { echo -e "\n${CYAN}══════════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════════${NC}"; }
check_deps() {
local missing=()
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
die "Missing required dependencies: ${missing[*]}"
fi
}
[ "$(id -u)" -eq 0 ] || die "Run as root"
check_deps ip netplan systemctl ping hostnamectl
header "TinyBoard Network Setup"
echo ""
echo " 0) Change hostname"
echo " 1) Configure static IP"
echo " 2) Prefer IPv4 over IPv6"
echo " 3) Prefer IPv6 over IPv4"
echo " q) Quit"
echo ""
read -rp "Choose: " NET_OPT
echo ""
case "$NET_OPT" in
0)
header "Change Hostname"
CURRENT_HOSTNAME=$(hostname)
echo -e "Current hostname: ${YELLOW}$CURRENT_HOSTNAME${NC}"
read -rp "Enter new hostname (e.g. rocky): " NEW_HOSTNAME
[ -n "$NEW_HOSTNAME" ] || die "Hostname cannot be empty."
[[ "$NEW_HOSTNAME" =~ ^[a-zA-Z0-9._-]+$ ]] || die "Invalid hostname — use only letters, numbers, dots, underscores, hyphens."
hostnamectl set-hostname "$NEW_HOSTNAME"
echo "$NEW_HOSTNAME" > /etc/hostname
sed -i "s/${CURRENT_HOSTNAME}/${NEW_HOSTNAME}/g" /etc/hosts
info "Hostname changed to: $NEW_HOSTNAME"
exit 0
;;
1)
;;
2)
header "Prefer IPv4 over IPv6"
if grep -q "precedence ::ffff:0:0/96" /etc/gai.conf 2>/dev/null; then
warn "IPv4 preference already set."
else
echo "precedence ::ffff:0:0/96 100" >> /etc/gai.conf
info "IPv4 preference set. Outgoing connections will prefer IPv4."
fi
exit 0
;;
3)
header "Prefer IPv6 over IPv4"
sed -i '/precedence ::ffff:0:0\/96/d' /etc/gai.conf 2>/dev/null || true
info "IPv4 preference removed. System will use default IPv6-first behavior."
exit 0
;;
q|Q)
exit 0
;;
*)
die "Invalid choice."
;;
esac
info "Available interfaces:"
ip -o link show | awk -F': ' 'NR>1 {print " " $2}'
echo ""
read -rp "Enter interface name to configure (e.g. wlan0, eth0, end0): " IFACE
[ -n "$IFACE" ] || die "Interface name cannot be empty"
ip link show "$IFACE" >/dev/null 2>&1 || die "Interface $IFACE not found"
IS_WIFI=false
if [[ "$IFACE" == wl* ]]; then
IS_WIFI=true
info "Wireless interface detected."
else
info "Wired interface detected — skipping WiFi credential setup."
fi
CURRENT_IP=$(ip -o -4 addr show "$IFACE" 2>/dev/null | awk '{print $4}' | head -1)
CURRENT_GW=$(ip route show default 2>/dev/null | awk '/default/ {print $3}' | head -1)
echo ""
info "Current IP: ${CURRENT_IP:-none}"
info "Current gateway: ${CURRENT_GW:-none}"
echo ""
read -rp "Set a static IP for this spoke? [Y/n]: " SET_STATIC
SET_STATIC="${SET_STATIC:-y}"
if [[ "${SET_STATIC,,}" != "y" ]]; then
info "Keeping DHCP. No changes made."
exit 0
fi
header "Static IP Configuration"
read -rp "Enter static IP with prefix (e.g. 192.168.1.69/24): " STATIC_IP
[ -n "$STATIC_IP" ] || die "IP address cannot be empty"
DEFAULT_GW="${CURRENT_GW:-192.168.1.1}"
read -rp "Gateway [${DEFAULT_GW}]: " GATEWAY
GATEWAY="${GATEWAY:-$DEFAULT_GW}"
read -rp "DNS servers (comma-separated) [${GATEWAY},8.8.8.8]: " DNS_INPUT
DNS_INPUT="${DNS_INPUT:-${GATEWAY},8.8.8.8}"
DNS_YAML=""
IFS=',' read -ra DNS_LIST <<< "$DNS_INPUT"
for DNS in "${DNS_LIST[@]}"; do
DNS=$(echo "$DNS" | tr -d ' ')
if [ -n "$DNS_YAML" ]; then
DNS_YAML="${DNS_YAML}"$'\n'
fi
DNS_YAML="${DNS_YAML} - ${DNS}"
done
info "Current netplan configs:"
ls /etc/netplan/ | sed 's/^/ /'
echo ""
NETPLAN_FILE=$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)
read -rp "Netplan file to update [${NETPLAN_FILE}]: " INPUT_FILE
NETPLAN_FILE="${INPUT_FILE:-$NETPLAN_FILE}"
NETPLAN_FILE="${NETPLAN_FILE:-$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)}"
[ -n "$NETPLAN_FILE" ] || die "No netplan file specified"
if $IS_WIFI; then
header "WiFi Credentials"
CURRENT_SSID=""
if [ -f "$NETPLAN_FILE" ]; then
CURRENT_SSID=$(python3 - "$NETPLAN_FILE" <<'PYEOF'
import sys, re
txt = open(sys.argv[1]).read()
m = re.search(r'access-points:\s*\n\s+["\']{0,1}([^"\':\n]+)["\']{0,1}:', txt)
print(m.group(1).strip() if m else "")
PYEOF
)
fi
KEEP_WIFI="n"
if [ -n "$CURRENT_SSID" ]; then
warn "Existing WiFi config found for: $CURRENT_SSID"
read -rp "Keep existing WiFi credentials? [Y/n]: " KEEP_WIFI
KEEP_WIFI="${KEEP_WIFI:-y}"
fi
if [[ "${KEEP_WIFI,,}" != "y" ]]; then
read -rp "WiFi SSID: " WIFI_SSID
[ -n "$WIFI_SSID" ] || die "SSID cannot be empty"
read -rsp "WiFi password: " WIFI_PASS
echo ""
[ -n "$WIFI_PASS" ] || die "Password cannot be empty"
else
WIFI_SSID="$CURRENT_SSID"
WIFI_PASS=$(grep -FA2 "\"${WIFI_SSID}\"" "$NETPLAN_FILE" 2>/dev/null | grep -F "password" | sed 's/^[^:]*: *//' | tr -d '"' || true)
[ -n "$WIFI_PASS" ] || die "Could not extract WiFi password from existing config — please re-enter credentials."
fi
fi
header "Writing Netplan Config"
NETPLAN_BACKUP_DIR="/root/.config/tinyboard/netplan-backups"
mkdir -p "$NETPLAN_BACKUP_DIR"
BACKUP_FILE=""
for OTHER_FILE in /etc/netplan/*.yaml; do
[ "$OTHER_FILE" = "$NETPLAN_FILE" ] && continue
BACKUP_OTHER="$NETPLAN_BACKUP_DIR/$(basename "${OTHER_FILE}").$(date +%Y%m%d%H%M%S)"
cp "$OTHER_FILE" "$BACKUP_OTHER"
rm "$OTHER_FILE"
warn "Removed conflicting netplan file: $OTHER_FILE (backed up to $BACKUP_OTHER)"
done
if [ -f "$NETPLAN_FILE" ]; then
BACKUP_FILE="$NETPLAN_BACKUP_DIR/$(basename "${NETPLAN_FILE}").$(date +%Y%m%d%H%M%S)"
cp "$NETPLAN_FILE" "$BACKUP_FILE"
info "Netplan config backed up to $BACKUP_FILE"
info "To restore: cp $BACKUP_FILE $NETPLAN_FILE && netplan apply"
fi
if $IS_WIFI; then
cat > "$NETPLAN_FILE" <<NETEOF
network:
version: 2
wifis:
${IFACE}:
dhcp4: no
addresses:
- ${STATIC_IP}
routes:
- to: default
via: ${GATEWAY}
nameservers:
addresses:
${DNS_YAML}
access-points:
"${WIFI_SSID}":
password: "${WIFI_PASS}"
NETEOF
else
cat > "$NETPLAN_FILE" <<NETEOF
network:
version: 2
ethernets:
${IFACE}:
dhcp4: no
addresses:
- ${STATIC_IP}
routes:
- to: default
via: ${GATEWAY}
nameservers:
addresses:
${DNS_YAML}
2026-04-16 09:59:23 -07:00
NETEOF
fi
info "Netplan config written to $NETPLAN_FILE"
header "Applying Configuration"
warn "Applying netplan config — will revert automatically if network is lost..."
netplan apply
CONNECTED=false
for i in $(seq 1 6); do
sleep 5
if ping -c 1 -W 2 "$GATEWAY" >/dev/null 2>&1; then
CONNECTED=true
break
fi
warn "Network check $i/6 failed, retrying..."
done
if [ "$CONNECTED" = "true" ]; then
info "Network connectivity confirmed — config applied permanently."
else
warn "No network connectivity detected after 30 seconds — reverting to backup config."
if [ -n "$BACKUP_FILE" ] && [ -f "$BACKUP_FILE" ]; then
cp "$BACKUP_FILE" "$NETPLAN_FILE"
netplan apply
die "Config reverted to backup. Check your settings and try again."
else
die "No backup found to revert to. Restore $NETPLAN_FILE manually."
fi
fi
STATIC_ADDR="${STATIC_IP%%/*}"
echo ""
echo -e "${YELLOW}══════════════════════════════════════════${NC}"
echo -e "${YELLOW} Network reconfigured.${NC}"
echo -e "${YELLOW} If you are connected via SSH, your session${NC}"
echo -e "${YELLOW} may drop. Reconnect to: ${STATIC_ADDR}${NC}"
echo -e "${YELLOW} Then run: cd .. && ./setup.sh${NC}"
echo -e "${YELLOW}══════════════════════════════════════════${NC}"
echo ""