Fix deployment issues found during end-to-end testing
- install.sh: Use tar --strip-components=1 instead of mv for robust extraction when install dir partially exists (root-owned Docker artifacts) - config.sh: Add --non-interactive mode (--domain, --admin-password, --enable-all flags) for CI/CD and automated deployments - docker-entrypoint.sh: Validate critical env vars on startup, fail early with clear messages instead of silent failures - docker-compose.yml: Change Redis eviction policy from allkeys-lru to noeviction (required by BullMQ job queues) - Prisma: Add missing petitions.coverVideoId migration (schema had the column but migration omitted it, causing 500 on public endpoint) - Add scripts/uninstall.sh for clean removal including root-owned files - Add scripts/test-deployment.sh for automated post-install verification Bunker Admin
This commit is contained in:
@@ -26,7 +26,6 @@ HEALTH_TIMEOUT=180
|
||||
HEALTH_INTERVAL=5
|
||||
|
||||
# --- State flags for cleanup ---
|
||||
EXTRACT_DIR=""
|
||||
TARBALL_PATH=""
|
||||
CONFIG_COMPLETE=false
|
||||
|
||||
@@ -47,10 +46,6 @@ error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
# Clean up temp extraction directory
|
||||
if [[ -n "$EXTRACT_DIR" ]] && [[ -d "$EXTRACT_DIR" ]]; then
|
||||
rm -rf "$EXTRACT_DIR"
|
||||
fi
|
||||
# Clean up downloaded tarball (but not user-provided ones)
|
||||
if [[ -z "$LOCAL_TARBALL" ]] && [[ -n "$TARBALL_PATH" ]] && [[ -f "$TARBALL_PATH" ]]; then
|
||||
rm -f "$TARBALL_PATH"
|
||||
@@ -192,22 +187,32 @@ fi
|
||||
# =============================================================================
|
||||
|
||||
info "Extracting to ${INSTALL_DIR}..."
|
||||
mkdir -p "$(dirname "$INSTALL_DIR")"
|
||||
|
||||
# Extract to temp, then move (handles tarball root directory naming)
|
||||
EXTRACT_DIR=$(mktemp -d)
|
||||
tar xzf "$TARBALL_PATH" -C "$EXTRACT_DIR"
|
||||
|
||||
# Find the extracted directory (tarball might have any root name)
|
||||
EXTRACTED=$(find "$EXTRACT_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)
|
||||
if [[ -z "$EXTRACTED" ]]; then
|
||||
error "Tarball extraction failed — no directory found"
|
||||
exit 1
|
||||
# Ensure the install directory exists and is empty
|
||||
if [[ -d "$INSTALL_DIR" ]]; then
|
||||
# Remove any leftover files (may need sudo for root-owned Docker artifacts)
|
||||
rm -rf "${INSTALL_DIR:?}"/* "${INSTALL_DIR}"/.[!.]* 2>/dev/null || true
|
||||
if [[ -n "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]]; then
|
||||
if command -v sudo &>/dev/null; then
|
||||
warn "Removing root-owned leftovers from previous install..."
|
||||
sudo rm -rf "${INSTALL_DIR:?}"/* "${INSTALL_DIR}"/.[!.]* 2>/dev/null || true
|
||||
fi
|
||||
if [[ -n "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]]; then
|
||||
error "Cannot clean install directory ${INSTALL_DIR} — remove manually (may need sudo)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
mv "$EXTRACTED" "$INSTALL_DIR"
|
||||
rm -rf "$EXTRACT_DIR"
|
||||
EXTRACT_DIR="" # Clear so cleanup doesn't try to remove it
|
||||
# Extract directly into INSTALL_DIR, stripping the tarball's root directory
|
||||
tar xzf "$TARBALL_PATH" --strip-components=1 -C "$INSTALL_DIR"
|
||||
|
||||
if [[ ! -f "$INSTALL_DIR/docker-compose.yml" ]]; then
|
||||
error "Tarball extraction failed — docker-compose.yml not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up downloaded tarball
|
||||
if [[ -z "$LOCAL_TARBALL" ]] && [[ -f "$TARBALL_PATH" ]]; then
|
||||
|
||||
253
scripts/test-deployment.sh
Executable file
253
scripts/test-deployment.sh
Executable file
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Changemaker Lite — Deployment Test Suite
|
||||
#
|
||||
# Verifies that all core services are running and responding correctly.
|
||||
# Run after install or upgrade to validate the deployment.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/test-deployment.sh [OPTIONS]
|
||||
#
|
||||
# Options:
|
||||
# --wait SECS Wait for services before testing (default: 0)
|
||||
# --domain DOM Test tunnel connectivity for this domain
|
||||
# --quiet Only show failures and summary
|
||||
# --help Show this help
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WAIT_SECS=0
|
||||
TEST_DOMAIN=""
|
||||
QUIET=false
|
||||
|
||||
# Colors
|
||||
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
||||
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
||||
BOLD='\033[1m' NC='\033[0m'
|
||||
else
|
||||
RED='' GREEN='' YELLOW='' BOLD='' NC=''
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--wait) WAIT_SECS="$2"; shift 2 ;;
|
||||
--domain) TEST_DOMAIN="$2"; shift 2 ;;
|
||||
--quiet) QUIET=true; shift ;;
|
||||
--help|-h)
|
||||
sed -n '2,16p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||
exit 0 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
PASS=0 FAIL=0 WARN=0 SKIP=0
|
||||
|
||||
pass() { PASS=$((PASS+1)); [[ "$QUIET" == "false" ]] && echo -e " ${GREEN}PASS${NC} $1"; }
|
||||
fail() { FAIL=$((FAIL+1)); echo -e " ${RED}FAIL${NC} $1"; }
|
||||
warn() { WARN=$((WARN+1)); [[ "$QUIET" == "false" ]] && echo -e " ${YELLOW}WARN${NC} $1"; }
|
||||
skip() { SKIP=$((SKIP+1)); [[ "$QUIET" == "false" ]] && echo -e " ${YELLOW}SKIP${NC} $1"; }
|
||||
|
||||
# Load .env
|
||||
if [[ -f "$SCRIPT_DIR/.env" ]]; then
|
||||
source <(grep -E '^(API_PORT|ADMIN_PORT|MEDIA_API_PORT|DOMAIN|ENABLE_|INITIAL_ADMIN_EMAIL)=' "$SCRIPT_DIR/.env" 2>/dev/null || true)
|
||||
fi
|
||||
API_PORT="${API_PORT:-4000}"
|
||||
ADMIN_PORT="${ADMIN_PORT:-3000}"
|
||||
MEDIA_API_PORT="${MEDIA_API_PORT:-4100}"
|
||||
DOMAIN="${DOMAIN:-cmlite.org}"
|
||||
[[ -n "$TEST_DOMAIN" ]] && DOMAIN="$TEST_DOMAIN"
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo -e "${BOLD}Changemaker Lite — Deployment Test${NC}"
|
||||
echo ""
|
||||
|
||||
# Wait if requested
|
||||
if [[ "$WAIT_SECS" -gt 0 ]]; then
|
||||
echo "Waiting ${WAIT_SECS}s for services to start..."
|
||||
sleep "$WAIT_SECS"
|
||||
fi
|
||||
|
||||
# ─── Core Health ─────────────────────────────────────────────────────────
|
||||
echo -e "${BOLD}Core Health${NC}"
|
||||
|
||||
HEALTH=$(curl -sf "http://localhost:${API_PORT}/api/health" 2>/dev/null || echo "")
|
||||
if echo "$HEALTH" | grep -q '"healthy"'; then
|
||||
pass "API health"
|
||||
else
|
||||
fail "API health (${HEALTH:-no response})"
|
||||
fi
|
||||
|
||||
MEDIA=$(curl -sf "http://localhost:${MEDIA_API_PORT}/health" 2>/dev/null || echo "")
|
||||
if echo "$MEDIA" | grep -q '"ok"'; then
|
||||
pass "Media API health"
|
||||
else
|
||||
fail "Media API health"
|
||||
fi
|
||||
|
||||
ADMIN_CODE=$(curl -sf -o /dev/null -w "%{http_code}" "http://localhost:${ADMIN_PORT}/" 2>/dev/null || echo "0")
|
||||
if [[ "$ADMIN_CODE" == "200" ]]; then
|
||||
pass "Admin GUI (HTTP $ADMIN_CODE)"
|
||||
else
|
||||
fail "Admin GUI (HTTP $ADMIN_CODE)"
|
||||
fi
|
||||
|
||||
# ─── Authentication ──────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Authentication${NC}"
|
||||
|
||||
# Get admin email from .env
|
||||
ADMIN_EMAIL="${INITIAL_ADMIN_EMAIL:-admin@${DOMAIN}}"
|
||||
# Try to login — write payload to file to avoid ! escaping
|
||||
LOGIN_FILE=$(mktemp)
|
||||
# Read password from .env directly (avoid bash expansion issues with !)
|
||||
ADMIN_PW=$(grep "^INITIAL_ADMIN_PASSWORD=" "$SCRIPT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2-)
|
||||
printf '{"email":"%s","password":"%s"}' "$ADMIN_EMAIL" "$ADMIN_PW" > "$LOGIN_FILE"
|
||||
|
||||
TOKEN=$(curl -sf -X POST "http://localhost:${API_PORT}/api/auth/login" \
|
||||
-H "Content-Type: application/json" -d "@$LOGIN_FILE" 2>/dev/null \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin).get('accessToken',''))" 2>/dev/null || echo "")
|
||||
rm -f "$LOGIN_FILE"
|
||||
|
||||
if [[ -n "$TOKEN" ]]; then
|
||||
pass "Admin login"
|
||||
else
|
||||
fail "Admin login ($ADMIN_EMAIL)"
|
||||
fi
|
||||
|
||||
# ─── Authenticated Endpoints ─────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Authenticated API${NC}"
|
||||
|
||||
if [[ -n "$TOKEN" ]]; then
|
||||
AUTH="Authorization: Bearer $TOKEN"
|
||||
for ep in "/api/settings" "/api/users" "/api/dashboard/summary" "/api/services/status"; do
|
||||
CODE=$(curl -sf -o /dev/null -w "%{http_code}" "http://localhost:${API_PORT}${ep}" -H "$AUTH" 2>/dev/null || echo "0")
|
||||
if [[ "$CODE" == "200" ]]; then
|
||||
pass "GET $ep"
|
||||
else
|
||||
fail "GET $ep (HTTP $CODE)"
|
||||
fi
|
||||
done
|
||||
else
|
||||
skip "Authenticated endpoints (no token)"
|
||||
fi
|
||||
|
||||
# ─── Public Endpoints ────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Public API${NC}"
|
||||
|
||||
for ep in "/api/campaigns/public" "/api/map/shifts/public" "/api/pages/listed" \
|
||||
"/api/donation-pages" "/api/homepage" "/api/qr?text=test" \
|
||||
"/api/petitions/public" "/api/payments/plans" "/api/payments/products" \
|
||||
"/api/map/cuts/public"; do
|
||||
CODE=$(curl -sf -o /dev/null -w "%{http_code}" "http://localhost:${API_PORT}${ep}" 2>/dev/null || echo "0")
|
||||
if [[ "$CODE" == "200" ]]; then
|
||||
pass "GET $ep"
|
||||
else
|
||||
fail "GET $ep (HTTP $CODE)"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── Supporting Services ─────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Supporting Services${NC}"
|
||||
|
||||
declare -A SERVICES=(
|
||||
[gitea]=3030 [nocodb]=8091 [listmonk]=9001 [n8n]=5678
|
||||
[homepage]=3010 [excalidraw]=8090 [vaultwarden]=8445
|
||||
[mailhog]=8025 [mkdocs-dev]=4003 [mkdocs-static]=4004 [mini-qr]=8089
|
||||
)
|
||||
|
||||
for svc in "${!SERVICES[@]}"; do
|
||||
port=${SERVICES[$svc]}
|
||||
CODE=$(curl -sf -o /dev/null -w "%{http_code}" --max-time 5 "http://localhost:${port}/" 2>/dev/null || echo "0")
|
||||
if [[ "$CODE" == "200" || "$CODE" == "302" ]]; then
|
||||
pass "$svc (:$port)"
|
||||
else
|
||||
fail "$svc (:$port) (HTTP $CODE)"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── Nginx Proxy ─────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Nginx Proxy${NC}"
|
||||
|
||||
NGINX_HEALTH=$(docker compose ps nginx --format '{{.Health}}' 2>/dev/null || echo "unknown")
|
||||
if [[ "$NGINX_HEALTH" == "healthy" ]]; then
|
||||
pass "Nginx container healthy"
|
||||
|
||||
API_PROXY=$(curl -sf -H "Host: api.${DOMAIN}" "http://localhost:80/api/health" 2>/dev/null || echo "")
|
||||
if echo "$API_PROXY" | grep -q '"healthy"'; then
|
||||
pass "Nginx -> API proxy"
|
||||
else
|
||||
fail "Nginx -> API proxy"
|
||||
fi
|
||||
|
||||
ADMIN_PROXY=$(curl -sf -o /dev/null -w "%{http_code}" -H "Host: app.${DOMAIN}" "http://localhost:80/" 2>/dev/null || echo "0")
|
||||
if [[ "$ADMIN_PROXY" == "200" ]]; then
|
||||
pass "Nginx -> Admin proxy"
|
||||
else
|
||||
fail "Nginx -> Admin proxy (HTTP $ADMIN_PROXY)"
|
||||
fi
|
||||
else
|
||||
fail "Nginx container ($NGINX_HEALTH)"
|
||||
fi
|
||||
|
||||
# ─── Tunnel (optional) ──────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Tunnel${NC}"
|
||||
|
||||
NEWT_STATUS=$(docker compose ps newt --format '{{.Status}}' 2>/dev/null || echo "")
|
||||
if echo "$NEWT_STATUS" | grep -q "Up"; then
|
||||
pass "Newt container running"
|
||||
|
||||
TUNNEL_API=$(curl -sf -o /dev/null -w "%{http_code}" --max-time 10 "https://api.${DOMAIN}/api/health" 2>/dev/null || echo "0")
|
||||
if [[ "$TUNNEL_API" == "200" ]]; then
|
||||
pass "Tunnel -> api.${DOMAIN}"
|
||||
else
|
||||
warn "Tunnel -> api.${DOMAIN} (HTTP $TUNNEL_API)"
|
||||
fi
|
||||
|
||||
TUNNEL_APP=$(curl -sf -o /dev/null -w "%{http_code}" --max-time 10 "https://app.${DOMAIN}/" 2>/dev/null || echo "0")
|
||||
if [[ "$TUNNEL_APP" == "200" ]]; then
|
||||
pass "Tunnel -> app.${DOMAIN}"
|
||||
else
|
||||
warn "Tunnel -> app.${DOMAIN} (HTTP $TUNNEL_APP — check Pangolin resource auth)"
|
||||
fi
|
||||
else
|
||||
skip "Tunnel (Newt not running)"
|
||||
fi
|
||||
|
||||
# ─── Database ────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}Database${NC}"
|
||||
|
||||
USER_COUNT=$(docker compose exec -T v2-postgres psql -U changemaker -d changemaker_v2 -t -c "SELECT COUNT(*) FROM users" 2>/dev/null | tr -d ' ' || echo "0")
|
||||
if [[ "$USER_COUNT" -gt "0" ]] 2>/dev/null; then
|
||||
pass "Users in DB: $USER_COUNT"
|
||||
else
|
||||
fail "No users found in database"
|
||||
fi
|
||||
|
||||
TABLE_COUNT=$(docker compose exec -T v2-postgres psql -U changemaker -d changemaker_v2 -t -c "SELECT COUNT(*) FROM pg_tables WHERE schemaname='public'" 2>/dev/null | tr -d ' ' || echo "0")
|
||||
if [[ "$TABLE_COUNT" -gt "100" ]] 2>/dev/null; then
|
||||
pass "Tables in DB: $TABLE_COUNT"
|
||||
else
|
||||
warn "Table count: $TABLE_COUNT (expected 180+)"
|
||||
fi
|
||||
|
||||
# ─── Summary ─────────────────────────────────────────────────────────────
|
||||
TOTAL=$((PASS + FAIL + WARN + SKIP))
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
if [[ "$FAIL" -eq 0 ]]; then
|
||||
echo -e " ${GREEN}${BOLD}ALL TESTS PASSED${NC} ($PASS passed, $WARN warnings, $SKIP skipped)"
|
||||
else
|
||||
echo -e " ${RED}${BOLD}$FAIL FAILURES${NC} ($PASS passed, $FAIL failed, $WARN warnings, $SKIP skipped)"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
exit "$FAIL"
|
||||
127
scripts/uninstall.sh
Executable file
127
scripts/uninstall.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Changemaker Lite — Uninstall Script
|
||||
#
|
||||
# Safely stops and removes all Changemaker containers, volumes, and files.
|
||||
# Handles root-owned files created by Docker containers.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/uninstall.sh [OPTIONS]
|
||||
#
|
||||
# Options:
|
||||
# --keep-data Keep database volumes (PostgreSQL, Redis, etc.)
|
||||
# --keep-media Keep the media/ directory (uploaded files)
|
||||
# --yes, -y Skip confirmation prompt
|
||||
# --help Show this help
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
KEEP_DATA=false
|
||||
KEEP_MEDIA=false
|
||||
SKIP_CONFIRM=false
|
||||
|
||||
# Colors
|
||||
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
||||
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m'
|
||||
else
|
||||
RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
|
||||
fi
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--keep-data) KEEP_DATA=true; shift ;;
|
||||
--keep-media) KEEP_MEDIA=true; shift ;;
|
||||
--yes|-y) SKIP_CONFIRM=true; shift ;;
|
||||
--help|-h)
|
||||
sed -n '2,16p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||
exit 0 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo -e "${BOLD}Changemaker Lite — Uninstall${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ ! -f "$SCRIPT_DIR/docker-compose.yml" ]]; then
|
||||
error "Not a Changemaker Lite installation: $SCRIPT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Install directory: $SCRIPT_DIR"
|
||||
[[ "$KEEP_DATA" == "true" ]] && info "Database volumes will be preserved"
|
||||
[[ "$KEEP_MEDIA" == "true" ]] && info "Media files will be preserved"
|
||||
echo ""
|
||||
|
||||
if [[ "$SKIP_CONFIRM" == "false" ]]; then
|
||||
echo -e "${RED}${BOLD}WARNING: This will permanently remove Changemaker Lite.${NC}"
|
||||
read -rp "Type 'uninstall' to confirm: " confirm
|
||||
if [[ "$confirm" != "uninstall" ]]; then
|
||||
echo "Cancelled."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 1: Stop and remove containers
|
||||
echo ""
|
||||
info "Stopping containers..."
|
||||
cd "$SCRIPT_DIR"
|
||||
if [[ "$KEEP_DATA" == "true" ]]; then
|
||||
docker compose down --remove-orphans 2>/dev/null || true
|
||||
else
|
||||
docker compose down -v --remove-orphans 2>/dev/null || true
|
||||
fi
|
||||
success "Containers stopped"
|
||||
|
||||
# Step 2: Remove systemd units (if installed)
|
||||
if command -v systemctl &>/dev/null; then
|
||||
for unit in changemaker-upgrade.path changemaker-upgrade.service changemaker-backup.timer changemaker-backup.service; do
|
||||
if systemctl is-enabled "$unit" &>/dev/null 2>&1; then
|
||||
info "Removing systemd unit: $unit"
|
||||
sudo systemctl disable --now "$unit" 2>/dev/null || true
|
||||
sudo rm -f "/etc/systemd/system/$unit" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
sudo systemctl daemon-reload 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Step 3: Remove files
|
||||
info "Removing installation files..."
|
||||
|
||||
# Try normal rm first
|
||||
cd "$HOME"
|
||||
if [[ "$KEEP_MEDIA" == "true" ]]; then
|
||||
# Move media out, remove everything, move media back
|
||||
MEDIA_TMP=$(mktemp -d)
|
||||
mv "$SCRIPT_DIR/media" "$MEDIA_TMP/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -rf "$SCRIPT_DIR" 2>/dev/null || true
|
||||
|
||||
# Handle root-owned files from Docker
|
||||
if [[ -d "$SCRIPT_DIR" ]]; then
|
||||
warn "Some files are root-owned (from Docker). Using sudo to clean up..."
|
||||
sudo rm -rf "$SCRIPT_DIR" 2>/dev/null || {
|
||||
error "Could not remove $SCRIPT_DIR — manual cleanup needed:"
|
||||
echo " sudo rm -rf $SCRIPT_DIR"
|
||||
}
|
||||
fi
|
||||
|
||||
# Restore media if requested
|
||||
if [[ "$KEEP_MEDIA" == "true" ]] && [[ -d "${MEDIA_TMP:-}/media" ]]; then
|
||||
mkdir -p "$SCRIPT_DIR"
|
||||
mv "$MEDIA_TMP/media" "$SCRIPT_DIR/"
|
||||
rm -rf "$MEDIA_TMP"
|
||||
success "Media files preserved at $SCRIPT_DIR/media/"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
success "Changemaker Lite has been uninstalled."
|
||||
[[ "$KEEP_DATA" == "true" ]] && info "Database volumes were preserved (use 'docker volume ls' to manage)"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user