feat: ssh auth, protocol management, and cleanup

This commit is contained in:
infosave2007
2026-01-23 17:55:40 +03:00
parent 60fc55fd47
commit bbab877eac
70 changed files with 16225 additions and 986 deletions
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env bash
set -euo pipefail
PANEL_URL="${PANEL_URL:-http://localhost:8082}"
EMAIL="${EMAIL:-admin@amnez.ia}"
PASSWORD="${PASSWORD:-admin123}"
SERVER_HOST="${SERVER_HOST:-217.26.25.6}"
PROTOCOL_SLUG="${PROTOCOL_SLUG:-amnezia-wg-advanced}"
CLIENT_NAME="${CLIENT_NAME:-api-selfcheck}"
CLIENT_LOGIN="${CLIENT_LOGIN:-api-selfcheck}"
OUT_DIR="${OUT_DIR:-scripts/_cycle_out}"
mkdir -p "$OUT_DIR"
AUTH_RESP=$(curl -sS -X POST "$PANEL_URL/api/auth/token" -d "email=$EMAIL&password=$PASSWORD" || true)
TOKEN=$(printf '%s' "$AUTH_RESP" | python3 -c 'import sys,json; raw=sys.stdin.read().strip();
import sys
if not raw: sys.exit(2)
j=json.loads(raw)
print(j.get("token",""))
') || {
echo "ERROR: failed to parse /api/auth/token response" >&2
echo "PANEL_URL=$PANEL_URL" >&2
echo "Response (first 500 chars):" >&2
printf '%s' "$AUTH_RESP" | head -c 500 >&2
echo >&2
exit 1
}
if [[ -z "${TOKEN:-}" ]]; then
echo "ERROR: token is empty" >&2
printf '%s' "$AUTH_RESP" | head -c 500 >&2
echo >&2
exit 1
fi
echo "TOKEN_OK"
SERVER_JSON=$(curl -fsS "$PANEL_URL/api/servers" -H "Authorization: Bearer $TOKEN")
SERVER_ID=$(printf '%s' "$SERVER_JSON" | python3 -c 'import sys,json; j=json.load(sys.stdin); host=sys.argv[1];
out="";
for s in j.get("servers",[]):
if str(s.get("host","" )).strip()==host:
out=str(s.get("id",""))
break
print(out)
' "$SERVER_HOST")
if [[ -z "${SERVER_ID:-}" ]]; then
echo "ERROR: server with host $SERVER_HOST not found" >&2
printf '%s' "$SERVER_JSON" | python3 -m json.tool | head -200
exit 1
fi
echo "SERVER_ID=$SERVER_ID"
PROTO_JSON=$(curl -fsS "$PANEL_URL/api/protocols/active" -H "Authorization: Bearer $TOKEN")
PROTOCOL_ID=$(printf '%s' "$PROTO_JSON" | python3 -c 'import sys,json; j=json.load(sys.stdin); slug=sys.argv[1];
out="";
for p in j.get("protocols",[]):
if p.get("slug")==slug:
out=str(p.get("id",""))
break
print(out)
' "$PROTOCOL_SLUG")
if [[ -z "${PROTOCOL_ID:-}" ]]; then
echo "ERROR: protocol $PROTOCOL_SLUG not found" >&2
printf '%s' "$PROTO_JSON" | python3 -m json.tool | head -200
exit 1
fi
echo "PROTOCOL_ID=$PROTOCOL_ID"
pretty_print() {
# Reads JSON from stdin and pretty-prints it. If it's not JSON, prints raw.
local data
data=$(cat)
if python3 -m json.tool >/dev/null 2>&1 <<<"$data"; then
python3 -m json.tool <<<"$data"
else
printf '%s' "$data"
fi
}
echo "--- UNINSTALL $PROTOCOL_SLUG (ignore errors)"
set +e
UNINSTALL_RESP=$(curl -sS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/$PROTOCOL_SLUG/uninstall" \
-H "Authorization: Bearer $TOKEN" || true)
printf '%s' "$UNINSTALL_RESP" >"$OUT_DIR/uninstall_${PROTOCOL_SLUG}.txt"
printf '%s' "$UNINSTALL_RESP" | pretty_print | head -200
set -e
echo "--- INSTALL protocol_id=$PROTOCOL_ID"
INSTALL_RESP=$(curl -sS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/install" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"protocol_id\":$PROTOCOL_ID}" || true)
printf '%s' "$INSTALL_RESP" >"$OUT_DIR/install_${PROTOCOL_ID}.txt"
printf '%s' "$INSTALL_RESP" | pretty_print | head -200
echo "--- CREATE client"
CLIENT_RESP=$(curl -fsS -X POST "$PANEL_URL/api/clients/create" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"server_id\":$SERVER_ID,\"protocol_id\":$PROTOCOL_ID,\"name\":\"$CLIENT_NAME\",\"login\":\"$CLIENT_LOGIN\"}")
printf '%s' "$CLIENT_RESP" >"$OUT_DIR/client_create_${PROTOCOL_ID}.txt"
printf '%s' "$CLIENT_RESP" | pretty_print | head -200
CLIENT_ID=$(printf '%s' "$CLIENT_RESP" | python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("client",{}).get("id",""))')
if [[ -z "${CLIENT_ID:-}" ]]; then
echo "ERROR: client_id missing" >&2
exit 1
fi
echo "CLIENT_ID=$CLIENT_ID"
echo "--- SELFTEST"
SELFTEST_RESP=$(curl -fsS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/selftest" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"client_id\":$CLIENT_ID,\"create_client\":false,\"install\":false,\"protocol_id\":$PROTOCOL_ID}")
printf '%s' "$SELFTEST_RESP" >"$OUT_DIR/selftest_${CLIENT_ID}.txt"
printf '%s' "$SELFTEST_RESP" | pretty_print | head -260
NEED_DIAG=$(printf '%s' "$SELFTEST_RESP" | python3 -c 'import sys,json; j=json.load(sys.stdin); peer=(j.get("wg") or {}).get("peer") or {}; hs=int(peer.get("latest_handshake") or 0); ep=str(peer.get("endpoint") or ""); print("1" if (ep=="(none)" or hs==0) else "0")')
if [[ "$NEED_DIAG" == "1" ]]; then
echo "--- DIAGNOSE HANDSHAKE"
DIAG_RESP=$(curl -sS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/diagnose-handshake" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"client_id\":$CLIENT_ID,\"duration_seconds\":5}" || true)
printf '%s' "$DIAG_RESP" >"$OUT_DIR/diagnose_${CLIENT_ID}.txt"
printf '%s' "$DIAG_RESP" | pretty_print | head -260
fi
echo "DONE (responses saved in $OUT_DIR)"
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
PANEL_URL="${PANEL_URL:-http://localhost:8082}"
EMAIL="${EMAIL:-admin@amnez.ia}"
PASSWORD="${PASSWORD:-admin123}"
SERVER_ID="${SERVER_ID:-5}"
CLIENT_ID="${CLIENT_ID:-}"
DURATION="${DURATION:-10}"
OUT_FILE="${OUT_FILE:-}"
if [[ -z "${CLIENT_ID}" ]]; then
echo "ERROR: CLIENT_ID is required" >&2
exit 2
fi
if [[ -z "${OUT_FILE}" ]]; then
OUT_FILE="scripts/_cycle_out/diagnose_client_${CLIENT_ID}.json"
fi
mkdir -p "$(dirname "$OUT_FILE")"
TOKEN_JSON=$(curl -sS -X POST "$PANEL_URL/api/auth/token" -d "email=$EMAIL&password=$PASSWORD")
TOKEN=$(printf '%s' "$TOKEN_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("token",""))')
if [[ -z "${TOKEN}" ]]; then
echo "ERROR: token empty" >&2
printf '%s' "$TOKEN_JSON" | head -c 300 >&2
echo >&2
exit 3
fi
RESP_WITH_STATUS=$(curl -sS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/diagnose-handshake" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"client_id\":$CLIENT_ID,\"duration_seconds\":$DURATION}" \
-w "\n__HTTP_STATUS__%{http_code}")
HTTP_STATUS=$(printf '%s' "$RESP_WITH_STATUS" | awk -F'__HTTP_STATUS__' 'END{print $2}')
RESP=$(printf '%s' "$RESP_WITH_STATUS" | awk -F'__HTTP_STATUS__' '{print $1}')
if [[ -z "${RESP:-}" ]]; then
echo "ERROR: empty response (http_status=${HTTP_STATUS:-unknown})" >&2
exit 4
fi
TMP_FILE="${OUT_FILE}.tmp"
printf '%s' "$RESP" >"$TMP_FILE"
mv "$TMP_FILE" "$OUT_FILE"
echo "saved:$OUT_FILE (http_status=${HTTP_STATUS:-unknown})"
if [[ "${HTTP_STATUS:-}" =~ ^[0-9]+$ ]] && [[ "${HTTP_STATUS}" -ge 400 ]]; then
exit 5
fi
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
PANEL_URL="${PANEL_URL:-http://localhost:8082}"
EMAIL="${EMAIL:-admin@amnez.ia}"
PASSWORD="${PASSWORD:-admin123}"
SERVER_ID="${SERVER_ID:-5}"
OUT_FILE="${OUT_FILE:-scripts/_cycle_out/diagnose_no_client.json}"
DURATION="${DURATION:-2}"
mkdir -p "$(dirname "$OUT_FILE")"
TOKEN_JSON=$(curl -sS -X POST "$PANEL_URL/api/auth/token" -d "email=$EMAIL&password=$PASSWORD")
TOKEN=$(printf '%s' "$TOKEN_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
TMP_FILE="${OUT_FILE}.tmp"
RESP=$(curl -fsS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/diagnose-handshake" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"duration_seconds\":$DURATION}" || true)
if [[ -z "${RESP:-}" ]]; then
echo "ERROR: empty response from diagnose-handshake" >&2
echo "PANEL_URL=$PANEL_URL SERVER_ID=$SERVER_ID" >&2
echo "Token JSON (first 200):" >&2
printf '%s' "$TOKEN_JSON" | head -c 200 >&2
echo >&2
exit 2
fi
printf '%s' "$RESP" > "$TMP_FILE"
mv "$TMP_FILE" "$OUT_FILE"
echo "saved:$OUT_FILE"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
PANEL_URL="${PANEL_URL:-http://localhost:8082}"
EMAIL="${EMAIL:-admin@amnez.ia}"
PASSWORD="${PASSWORD:-admin123}"
TOKEN_JSON=$(curl -sS -X POST "$PANEL_URL/api/auth/token" -d "email=$EMAIL&password=$PASSWORD")
TOKEN=$(printf '%s' "$TOKEN_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("token",""))')
if [[ -z "${TOKEN:-}" ]]; then
echo "ERROR: token empty" >&2
printf '%s' "$TOKEN_JSON" | head -c 200 >&2
echo >&2
exit 3
fi
curl -fsS "$PANEL_URL/api/clients" -H "Authorization: Bearer $TOKEN" | \
python3 -c 'import sys,json; j=json.load(sys.stdin); cs=j.get("clients",[]);
print("id\tname\tprotocol\tserver_id")
for c in cs:
print(f"{c.get("id")}\t{c.get("name")}\t{c.get("protocol")}\t{c.get("server_id")}")'
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail
PANEL_URL="${PANEL_URL:-http://localhost:8082}"
EMAIL="${EMAIL:-}"
PASSWORD="${PASSWORD:-}"
TOKEN="${TOKEN:-}"
SERVER_ID="${SERVER_ID:-1}"
PROTOCOL_ID="${PROTOCOL_ID:-}"
UNINSTALL_SLUG="${UNINSTALL_SLUG:-}"
CLIENT_NAME="${CLIENT_NAME:-smoke-client}"
CLIENT_LOGIN="${CLIENT_LOGIN:-smoke-client}"
SELFTEST="${SELFTEST:-1}"
DIAGNOSE="${DIAGNOSE:-1}"
if [[ -z "$TOKEN" ]]; then
if [[ -z "$EMAIL" || -z "$PASSWORD" ]]; then
echo "ERROR: set TOKEN or (EMAIL and PASSWORD)" >&2
exit 1
fi
echo "[1/6] Getting JWT token..." >&2
TOKEN="$(curl -fsS -X POST "$PANEL_URL/api/auth/token" -d "email=$EMAIL&password=$PASSWORD" | php -r '$j=json_decode(stream_get_contents(STDIN),true); echo $j["token"] ?? "";')"
fi
if [[ -z "$TOKEN" ]]; then
echo "ERROR: failed to obtain token" >&2
exit 1
fi
auth=(-H "Authorization: Bearer $TOKEN")
echo "[2/6] Listing active protocols..." >&2
curl -fsS "$PANEL_URL/api/protocols/active" "${auth[@]}" | cat
if [[ -n "$UNINSTALL_SLUG" ]]; then
echo "[3/6] Uninstalling protocol slug=$UNINSTALL_SLUG on server=$SERVER_ID ..." >&2
curl -fsS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/$UNINSTALL_SLUG/uninstall" "${auth[@]}" | cat
else
echo "[3/6] Skipping uninstall (set UNINSTALL_SLUG to run)." >&2
fi
if [[ -n "$PROTOCOL_ID" ]]; then
echo "[4/6] Installing protocol_id=$PROTOCOL_ID on server=$SERVER_ID ..." >&2
curl -fsS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/install" \
"${auth[@]}" \
-H "Content-Type: application/json" \
-d "{\"protocol_id\": $PROTOCOL_ID}" | cat
else
echo "[4/6] Skipping install (set PROTOCOL_ID to run)." >&2
fi
echo "[5/6] Creating client on server=$SERVER_ID (protocol_id=${PROTOCOL_ID:-auto})..." >&2
CREATE_PAYLOAD=$(php -r '$d=["server_id"=>(int)getenv("SERVER_ID"),"name"=>getenv("CLIENT_NAME"),"login"=>getenv("CLIENT_LOGIN")]; $pid=getenv("PROTOCOL_ID"); if($pid!==false && $pid!==""){$d["protocol_id"]= (int)$pid;} echo json_encode($d, JSON_UNESCAPED_SLASHES);')
RESP="$(curl -fsS -X POST "$PANEL_URL/api/clients/create" "${auth[@]}" -H "Content-Type: application/json" -d "$CREATE_PAYLOAD")"
echo "$RESP" | cat
CLIENT_ID=$(echo "$RESP" | php -r '$j=json_decode(stream_get_contents(STDIN),true); echo $j["client"]["id"] ?? "";')
if [[ -n "$CLIENT_ID" ]]; then
echo "[6/6] Fetching client details (includes stats sync)..." >&2
curl -fsS "$PANEL_URL/api/clients/$CLIENT_ID/details" "${auth[@]}" | cat
if [[ "$SELFTEST" == "1" ]]; then
echo >&2
echo "[selftest] Verifying generated config vs server wg0..." >&2
SELFTEST_PAYLOAD=$(php -r '$d=["protocol_id"=>getenv("PROTOCOL_ID")!==false && getenv("PROTOCOL_ID")!=="" ? (int)getenv("PROTOCOL_ID") : 0, "install"=>false, "create_client"=>false, "client_id"=>(int)getenv("CLIENT_ID")]; echo json_encode($d, JSON_UNESCAPED_SLASHES);')
SELFTEST_RESP=$(curl -fsS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/selftest" \
"${auth[@]}" \
-H "Content-Type: application/json" \
-d "$SELFTEST_PAYLOAD")
echo "$SELFTEST_RESP" | cat
if [[ "$DIAGNOSE" == "1" ]]; then
# If peer endpoint is none OR latest_handshake=0, run server-side diagnostics
NEED_DIAG=$(echo "$SELFTEST_RESP" | php -r '$j=json_decode(stream_get_contents(STDIN),true); $hs=$j["wg"]["peer"]["latest_handshake"] ?? null; $ep=$j["wg"]["peer"]["endpoint"] ?? null; echo ((string)$ep==="(none)" || (int)$hs===0) ? "1" : "0";')
if [[ "$NEED_DIAG" == "1" ]]; then
echo >&2
echo "[diagnose] Collecting server-side evidence (wg/ports/firewall/tcpdump)..." >&2
DIAG_PAYLOAD=$(php -r '$d=["client_id"=>(int)getenv("CLIENT_ID"),"duration_seconds"=>5]; echo json_encode($d, JSON_UNESCAPED_SLASHES);')
curl -fsS -X POST "$PANEL_URL/api/servers/$SERVER_ID/protocols/diagnose-handshake" \
"${auth[@]}" \
-H "Content-Type: application/json" \
-d "$DIAG_PAYLOAD" | cat
fi
fi
fi
else
echo "[6/6] No client id returned; skipping details." >&2
fi
echo >&2
echo "Done." >&2
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -euo pipefail
PANEL_URL="${PANEL_URL:-http://localhost:8082}"
EMAIL="${EMAIL:-admin@amnez.ia}"
PASSWORD="${PASSWORD:-admin123}"
CLIENT_NAME="${CLIENT_NAME:-}"
CLIENT_ID="${CLIENT_ID:-}"
OUT_DIR="${OUT_DIR:-scripts/_cycle_out}"
mkdir -p "$OUT_DIR"
if [[ -z "$CLIENT_ID" && -z "$CLIENT_NAME" ]]; then
echo "ERROR: set CLIENT_ID or CLIENT_NAME" >&2
exit 2
fi
TOKEN_JSON=$(curl -sS -X POST "$PANEL_URL/api/auth/token" -d "email=$EMAIL&password=$PASSWORD")
TOKEN=$(printf '%s' "$TOKEN_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("token",""))')
if [[ -z "${TOKEN:-}" ]]; then
echo "ERROR: token empty" >&2
printf '%s' "$TOKEN_JSON" | head -c 200 >&2
echo >&2
exit 3
fi
if [[ -z "$CLIENT_ID" ]]; then
CLIENTS_JSON=$(curl -fsS "$PANEL_URL/api/clients" -H "Authorization: Bearer $TOKEN")
CLIENT_ID=$(printf '%s' "$CLIENTS_JSON" | python3 -c 'import sys,json; j=json.load(sys.stdin); needle=sys.argv[1];
for c in j.get("clients",[]):
if str(c.get("name",""))==needle:
print(c.get("id",""));
raise SystemExit
print("")' "$CLIENT_NAME")
fi
if [[ -z "${CLIENT_ID:-}" ]]; then
echo "ERROR: client not found" >&2
exit 4
fi
RESP=$(curl -fsS -X POST "$PANEL_URL/api/clients/$CLIENT_ID/regenerate-config" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}' )
JSON_OUT="$OUT_DIR/regenerate_${CLIENT_ID}.json"
CONF_OUT="$OUT_DIR/${CLIENT_NAME:-client_${CLIENT_ID}}_regenerated.conf"
printf '%s' "$RESP" >"$JSON_OUT"
# Extract config field
printf '%s' "$RESP" | python3 -c 'import sys,json; j=json.load(sys.stdin); c=(j.get("client") or {}).get("config") or ""; sys.stdout.write(c)' >"$CONF_OUT"
echo "saved_json:$JSON_OUT"
echo "saved_conf:$CONF_OUT"
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
# Universal cleanup script for all Amnezia containers
# Based on remove_all_containers.sh from amnezia-client
# Usage: ./cleanup_amnezia.sh
set -euo pipefail
echo "========================================="
echo "Amnezia VPN - Complete Cleanup Script"
echo "========================================="
echo ""
echo "WARNING: This will remove ALL Amnezia containers, images, and data!"
echo "Press Ctrl+C to cancel, or Enter to continue..."
read -r
echo ""
echo "Step 1: Stopping all Amnezia containers..."
CONTAINERS=$(docker ps -a | grep amnezia | awk '{print $1}' || true)
if [ -n "$CONTAINERS" ]; then
echo "$CONTAINERS" | xargs docker stop || true
echo "✓ Containers stopped"
else
echo "✓ No running containers found"
fi
echo ""
echo "Step 2: Removing all Amnezia containers..."
CONTAINERS=$(docker ps -a | grep amnezia | awk '{print $1}' || true)
if [ -n "$CONTAINERS" ]; then
echo "$CONTAINERS" | xargs docker rm -fv || true
echo "✓ Containers removed"
else
echo "✓ No containers to remove"
fi
echo ""
echo "Step 3: Removing all Amnezia images..."
IMAGES=$(docker images -a | grep amnezia | awk '{print $3}' || true)
if [ -n "$IMAGES" ]; then
echo "$IMAGES" | xargs docker rmi -f || true
echo "✓ Images removed"
else
echo "✓ No images to remove"
fi
echo ""
echo "Step 4: Removing Amnezia DNS network..."
docker network rm amnezia-dns-net 2>/dev/null && echo "✓ Network removed" || echo "✓ Network not found"
echo ""
echo "Step 5: Removing Amnezia data directory..."
if [ -d "/opt/amnezia" ]; then
rm -rf /opt/amnezia
echo "✓ Data directory removed"
else
echo "✓ Data directory not found"
fi
echo ""
echo "========================================="
echo "Cleanup completed successfully!"
echo "========================================="
echo ""
echo "Summary:"
echo "- All Amnezia containers stopped and removed"
echo "- All Amnezia Docker images removed"
echo "- Amnezia DNS network removed"
echo "- All configuration data removed from /opt/amnezia"
echo ""
+19
View File
@@ -0,0 +1,19 @@
<?php
$priv = getenv('WG_PRIV_B64') ?: '';
$priv = trim($priv);
$raw = base64_decode($priv, true);
if ($raw === false) {
fwrite(STDERR, "invalid_base64\n");
exit(2);
}
echo "raw_len=" . strlen($raw) . "\n";
if (strlen($raw) !== 32) {
fwrite(STDERR, "invalid_length\n");
exit(3);
}
if (!function_exists('sodium_crypto_scalarmult_base')) {
fwrite(STDERR, "libsodium_missing\n");
exit(4);
}
$pub = sodium_crypto_scalarmult_base($raw);
echo "pub=" . base64_encode($pub) . "\n";
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# Remove single Amnezia container
# Based on remove_container.sh from amnezia-client
# Usage: ./remove_container.sh <container_name>
set -euo pipefail
if [ $# -eq 0 ]; then
echo "Usage: $0 <container_name>"
echo "Example: $0 amnezia-awg"
exit 1
fi
CONTAINER_NAME="$1"
echo "Removing Amnezia container: $CONTAINER_NAME"
echo ""
# Stop the container
echo "Stopping container..."
docker stop "$CONTAINER_NAME" 2>/dev/null && echo "✓ Container stopped" || echo "✓ Container not running"
# Remove the container with volumes
echo "Removing container..."
docker rm -fv "$CONTAINER_NAME" 2>/dev/null && echo "✓ Container removed" || echo "✓ Container not found"
# Remove the image
echo "Removing image..."
docker rmi "$CONTAINER_NAME" 2>/dev/null && echo "✓ Image removed" || echo "✓ Image not found"
echo ""
echo "Container $CONTAINER_NAME has been removed successfully!"