Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
7.2 KB bash
#!/usr/bin/env bash
set -euo pipefail

DEPLOY_DIR="${DEPLOY_DIR:-/opt/phoboswg}"
COMPOSE_FILE="docker-compose.yml"
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-phoboswg}"
export COMPOSE_PROJECT_NAME

log()  { printf '\e[1;34m==>\e[0m %s\n' "$*"; }
ok()   { printf '\e[1;32m  ✓\e[0m %s\n' "$*"; }
info() { printf '\e[1;30m  ·\e[0m %s\n' "$*"; }
warn() { printf '\e[1;33m  !\e[0m %s\n' "$*"; }
fail() { printf '\e[1;31mERROR:\e[0m %s\n' "$*" >&2; exit 1; }

[ "$(id -u)" -eq 0 ] || fail "Run as root: sudo bash $0"

if ! command -v docker >/dev/null 2>&1; then
    warn "docker not found — will only clean host files"
    HAS_DOCKER=0
else
    HAS_DOCKER=1
fi

CONTAINER_NAMES=(phobos phobos-dev)
VOLUME_KEYS_RE='etc_wireguard|sqlite_data|certs_data'
VOLUME_RE="(^|_)phobos_(${VOLUME_KEYS_RE})$"
NETWORK_RE='^(phobos|phoboswg)_(wg|default)$'
IMAGE_RE='^(ghcr\.io/)?ground-zerro/phobos(:|@)'

# Set when `compose down --volumes --remove-orphans` succeeded — subsequent
# manual cleanup steps then expect to find nothing left, so "no match" is
# informational rather than a warning.
COMPOSE_CLEAN=0

printf '\e[1;31m'
printf '┌─────────────────────────────────────────────┐\n'
printf '│  DESTRUCTIVE OPERATION — NO UNDO POSSIBLE   │\n'
printf '│                                             │\n'
printf '│  Will permanently remove:                   │\n'
printf '│    • PhobosWG containers                    │\n'
printf '│    • PhobosWG Docker volumes                │\n'
printf '│    • PhobosWG Docker networks               │\n'
printf '│    • PhobosWG Docker image                  │\n'
printf '│    • Deploy directory %-21s │\n' "$DEPLOY_DIR"
printf '│    • PhobosWG logs and env files            │\n'
printf '│                                             │\n'
printf '│  Other Docker workloads remain untouched.   │\n'
printf '└─────────────────────────────────────────────┘\n'
printf '\e[0m'
printf '\nType YES to continue: '
read -r CONFIRM
[ "$CONFIRM" = "YES" ] || { warn "Aborted."; exit 0; }

cd /

if [ "$HAS_DOCKER" -eq 1 ]; then
    log "Tearing down compose stack"
    if [ -d "$DEPLOY_DIR" ] && [ -f "$DEPLOY_DIR/$COMPOSE_FILE" ]; then
        if docker compose \
            --project-name "$COMPOSE_PROJECT_NAME" \
            --project-directory "$DEPLOY_DIR" \
            -f "$DEPLOY_DIR/$COMPOSE_FILE" \
            down --volumes --remove-orphans --timeout 15 >/dev/null 2>&1; then
            ok "Compose stack torn down (containers + volumes + network)"
            COMPOSE_CLEAN=1
        else
            warn "compose down failed — falling back to manual cleanup"
        fi
    else
        info "Compose file not found at $DEPLOY_DIR/$COMPOSE_FILE — skipping compose down"
    fi

    log "Removing PhobosWG containers by name"
    REMOVED_CT=0
    for ctr in "${CONTAINER_NAMES[@]}"; do
        if docker ps -a --format '{{.Names}}' | grep -qxF "$ctr"; then
            if docker rm -f "$ctr" >/dev/null 2>&1; then
                ok "Container removed: $ctr"
                REMOVED_CT=$((REMOVED_CT + 1))
            else
                warn "Failed to remove container: $ctr"
            fi
        fi
    done
    if [ "$REMOVED_CT" -eq 0 ]; then
        if [ "$COMPOSE_CLEAN" -eq 1 ]; then
            info "No residual containers (compose already removed them)"
        else
            info "No matching containers found"
        fi
    fi

    log "Removing PhobosWG volumes"
    mapfile -t VOLUMES < <(
        docker volume ls --format '{{.Name}}' 2>/dev/null | grep -E "$VOLUME_RE" || true
    )
    if [ "${#VOLUMES[@]}" -eq 0 ]; then
        if [ "$COMPOSE_CLEAN" -eq 1 ]; then
            info "No residual volumes (compose already removed them)"
        else
            warn "No matching volumes found"
        fi
    else
        for vol in "${VOLUMES[@]}"; do
            [ -n "$vol" ] || continue
            if docker volume rm "$vol" >/dev/null 2>&1; then
                ok "Volume removed: $vol"
            else
                warn "Failed to remove volume: $vol (in use?)"
            fi
        done
    fi

    log "Removing PhobosWG networks"
    mapfile -t NETS < <(
        docker network ls --format '{{.Name}}' 2>/dev/null | grep -E "$NETWORK_RE" || true
    )
    if [ "${#NETS[@]}" -eq 0 ]; then
        if [ "$COMPOSE_CLEAN" -eq 1 ]; then
            info "No residual networks (compose already removed them)"
        else
            warn "No matching networks found"
        fi
    else
        for net in "${NETS[@]}"; do
            [ -n "$net" ] || continue
            if docker network rm "$net" >/dev/null 2>&1; then
                ok "Network removed: $net"
            else
                warn "Failed to remove network: $net"
            fi
        done
    fi

    log "Removing PhobosWG image"
    mapfile -t IMGS < <(
        docker image ls --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -E "$IMAGE_RE" || true
    )
    if [ "${#IMGS[@]}" -eq 0 ]; then
        info "No PhobosWG image present"
    else
        for img in "${IMGS[@]}"; do
            [ -n "$img" ] || continue
            if docker rmi -f "$img" >/dev/null 2>&1; then
                ok "Image removed: $img"
            else
                warn "Failed to remove image: $img"
            fi
        done
    fi
fi

log "Removing deploy directory: $DEPLOY_DIR"
if [ -d "$DEPLOY_DIR" ]; then
    rm -rf "$DEPLOY_DIR"
    ok "Removed $DEPLOY_DIR"
else
    info "Deploy directory not present"
fi

log "Removing logs"
LOG_REMOVED=0
for LOG_PATH in /var/log/phoboswg /var/log/phoboswg.log; do
    if [ -e "$LOG_PATH" ]; then
        rm -rf "$LOG_PATH"
        ok "Removed $LOG_PATH"
        LOG_REMOVED=$((LOG_REMOVED + 1))
    fi
done
[ "$LOG_REMOVED" -eq 0 ] && info "No PhobosWG logs found"

log "Removing env residuals"
ENV_REMOVED=0
for ENV_FILE in /root/.phoboswg.env /etc/phoboswg.env; do
    if [ -f "$ENV_FILE" ]; then
        rm -f "$ENV_FILE"
        ok "Removed $ENV_FILE"
        ENV_REMOVED=$((ENV_REMOVED + 1))
    fi
done
[ "$ENV_REMOVED" -eq 0 ] && info "No env residuals found"

printf '\n\e[1;32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\e[0m\n'
printf '\e[1;32m  PhobosWG fully removed from this server\e[0m\n'
printf '\e[1;32m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\e[0m\n\n'

if [ "$HAS_DOCKER" -eq 1 ]; then
    REMAINING_CT=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -E '^(phobos$|phobos-)' || true)
    REMAINING_VOL=$(docker volume ls --format '{{.Name}}' 2>/dev/null | grep -E "$VOLUME_RE" || true)
    REMAINING_NET=$(docker network ls --format '{{.Name}}' 2>/dev/null | grep -E "$NETWORK_RE" || true)

    if [ -n "$REMAINING_CT" ] || [ -n "$REMAINING_VOL" ] || [ -n "$REMAINING_NET" ]; then
        warn "Residual PhobosWG resources still present:"
        [ -n "$REMAINING_CT" ] && printf '    containers: %s\n' $REMAINING_CT
        [ -n "$REMAINING_VOL" ] && printf '    volumes:    %s\n' $REMAINING_VOL
        [ -n "$REMAINING_NET" ] && printf '    networks:   %s\n' $REMAINING_NET
    fi
fi