1
0
forked from finn/tinyboard
Files
tinyboard/hub/offboard-spoke.sh
2026-04-16 13:48:28 -07:00

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 ""