forked from finn/tinyboard
130 lines
4.1 KiB
Bash
130 lines
4.1 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
RCLONE_CONF="${HOME}/.config/rclone/rclone.conf"
|
||
|
|
SSH_DIR="${HOME}/.ssh"
|
||
|
|
REGISTRY="${HOME}/.config/tinyboard/spokes"
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
if [ "$(id -u)" -eq 0 ]; then
|
||
|
|
die "Run as the hub user, not root."
|
||
|
|
fi
|
||
|
|
|
||
|
|
check_deps rclone crontab fusermount python3
|
||
|
|
|
||
|
|
header "TinyBoard Hub — Offboard Spoke"
|
||
|
|
|
||
|
|
[ -f "$REGISTRY" ] || die "No spoke registry found at $REGISTRY. No spokes to offboard."
|
||
|
|
|
||
|
|
echo "Registered spokes:"
|
||
|
|
echo ""
|
||
|
|
awk '{print " " $1 " (port " $2 ", mount " $4 ")"}' "$REGISTRY"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
read -rp "Spoke name to offboard: " SPOKE_NAME
|
||
|
|
[ -n "$SPOKE_NAME" ] || die "Spoke name cannot be empty"
|
||
|
|
|
||
|
|
SPOKE_LINE=$(grep "^$SPOKE_NAME " "$REGISTRY" 2>/dev/null || true)
|
||
|
|
[ -n "$SPOKE_LINE" ] || die "Spoke '$SPOKE_NAME' not found in registry."
|
||
|
|
|
||
|
|
TUNNEL_PORT=$(echo "$SPOKE_LINE" | awk '{print $2}')
|
||
|
|
KEY_PATH=$(echo "$SPOKE_LINE" | awk '{print $3}')
|
||
|
|
MOUNT_POINT=$(echo "$SPOKE_LINE" | awk '{print $4}')
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo -e " Spoke: ${YELLOW}$SPOKE_NAME${NC}"
|
||
|
|
echo -e " Port: ${YELLOW}$TUNNEL_PORT${NC}"
|
||
|
|
echo -e " Key: ${YELLOW}$KEY_PATH${NC}"
|
||
|
|
echo -e " Mount: ${YELLOW}$MOUNT_POINT${NC}"
|
||
|
|
echo ""
|
||
|
|
read -rp "Are you sure you want to offboard $SPOKE_NAME? [y/N]: " CONFIRM
|
||
|
|
[[ "${CONFIRM,,}" == "y" ]] || die "Aborted."
|
||
|
|
|
||
|
|
header "Unmounting Spoke"
|
||
|
|
if mountpoint -q "$MOUNT_POINT" 2>/dev/null; then
|
||
|
|
if fusermount -u "$MOUNT_POINT" 2>/dev/null; then
|
||
|
|
info "Unmounted $MOUNT_POINT."
|
||
|
|
else
|
||
|
|
warn "Could not unmount $MOUNT_POINT — may already be unmounted."
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
warn "$MOUNT_POINT is not currently mounted."
|
||
|
|
fi
|
||
|
|
|
||
|
|
header "Removing Crontab Entry"
|
||
|
|
EXISTING=$(crontab -l 2>/dev/null || true)
|
||
|
|
UPDATED=$(echo "$EXISTING" | grep -v "${SPOKE_NAME}-remote:" || true)
|
||
|
|
if [ "$EXISTING" = "$UPDATED" ]; then
|
||
|
|
warn "No crontab entry found for $SPOKE_NAME."
|
||
|
|
elif [ -z "$UPDATED" ]; then
|
||
|
|
crontab -r 2>/dev/null || true
|
||
|
|
info "Crontab entry for $SPOKE_NAME removed (crontab now empty)."
|
||
|
|
else
|
||
|
|
echo "$UPDATED" | crontab -
|
||
|
|
info "Crontab entry for $SPOKE_NAME removed."
|
||
|
|
fi
|
||
|
|
|
||
|
|
header "Removing rclone Remote"
|
||
|
|
if grep -q "\[${SPOKE_NAME}-remote\]" "$RCLONE_CONF" 2>/dev/null; then
|
||
|
|
python3 - "$RCLONE_CONF" "$SPOKE_NAME" <<'PYEOF'
|
||
|
|
import sys
|
||
|
|
path, name = sys.argv[1], sys.argv[2]
|
||
|
|
lines = open(path).readlines()
|
||
|
|
out, skip = [], False
|
||
|
|
for line in lines:
|
||
|
|
if line.strip() == f"[{name}-remote]":
|
||
|
|
skip = True
|
||
|
|
elif skip and line.strip().startswith("["):
|
||
|
|
skip = False
|
||
|
|
if not skip:
|
||
|
|
out.append(line)
|
||
|
|
open(path, 'w').writelines(out)
|
||
|
|
PYEOF
|
||
|
|
info "rclone remote [${SPOKE_NAME}-remote] removed from $RCLONE_CONF."
|
||
|
|
else
|
||
|
|
warn "rclone remote [${SPOKE_NAME}-remote] not found in $RCLONE_CONF."
|
||
|
|
fi
|
||
|
|
|
||
|
|
header "Removing SSH Key"
|
||
|
|
read -rp "Remove hub SSH key for $SPOKE_NAME ($KEY_PATH)? [y/N]: " REMOVE_KEY
|
||
|
|
if [[ "${REMOVE_KEY,,}" == "y" ]]; then
|
||
|
|
if [ -f "$KEY_PATH" ]; then
|
||
|
|
rm -f "$KEY_PATH" "$KEY_PATH.pub"
|
||
|
|
info "SSH key removed."
|
||
|
|
else
|
||
|
|
warn "Key not found at $KEY_PATH."
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
info "SSH key left in place."
|
||
|
|
fi
|
||
|
|
|
||
|
|
header "Removing from Registry"
|
||
|
|
(grep -v "^$SPOKE_NAME " "$REGISTRY" || true) > "${REGISTRY}.tmp" && mv "${REGISTRY}.tmp" "$REGISTRY"
|
||
|
|
info "$SPOKE_NAME removed from registry."
|
||
|
|
|
||
|
|
header "Offboarding Complete"
|
||
|
|
echo -e " Spoke ${GREEN}$SPOKE_NAME${NC} has been offboarded."
|
||
|
|
echo ""
|