install: add scripts/ccp-deregister.sh + ship in tarball
Pairs with pangolin-teardown.sh. For instances that were phone-home registered with a CCP, this script removes the CCP-side Instance row during teardown so the slug is freed for re-registration. Without it, tearing down a CCP-registered instance leaves a stale Instance row in CCP's DB. The next phone-home-registration with the same slug hits the unique-constraint violation we saw in marcelle testing. Matching: by agentUrl (default from .env CCP_AGENT_URL), --slug, or --instance-id. Dry-run by default; --yes to execute. CCP admin token via --token or CCP_ADMIN_TOKEN env var. Added to build-release.sh whitelist so release tarballs include it alongside pangolin-teardown.sh and validate-env.sh. Bunker Admin
This commit is contained in:
parent
c2f12aa2bf
commit
ce8c5aaf1f
@ -122,7 +122,7 @@ for script in nocodb-init.sh gitea-init.sh mkdocs-entrypoint.sh \
|
||||
backup.sh restore.sh \
|
||||
upgrade.sh upgrade-check.sh upgrade-watcher.sh \
|
||||
uninstall.sh test-deployment.sh \
|
||||
validate-env.sh pangolin-teardown.sh; do
|
||||
validate-env.sh pangolin-teardown.sh ccp-deregister.sh; do
|
||||
if [[ -f "$PROJECT_DIR/scripts/$script" ]]; then
|
||||
cp "$PROJECT_DIR/scripts/$script" "$STAGE_DIR/scripts/"
|
||||
fi
|
||||
|
||||
150
scripts/ccp-deregister.sh
Executable file
150
scripts/ccp-deregister.sh
Executable file
@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# ccp-deregister.sh — Remove this host's Instance from the CCP
|
||||
#
|
||||
# Pairs with scripts/pangolin-teardown.sh. Use when wiping a test instance
|
||||
# that was phone-home-registered with a CCP. Without this, the CCP retains
|
||||
# a stale Instance row after the underlying stack is gone and will block
|
||||
# re-registration of the same slug (CCP's slug-collision behaviour).
|
||||
#
|
||||
# Credentials are read from .env (CCP_URL, CCP_AGENT_URL) and a CCP admin
|
||||
# token, unless overridden by flags.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/ccp-deregister.sh --token CCP_ADMIN_TOKEN
|
||||
# ./scripts/ccp-deregister.sh --token T --yes # execute (default: dry-run)
|
||||
# ./scripts/ccp-deregister.sh --ccp-url URL --agent-url URL --token T --yes
|
||||
#
|
||||
# Matching: by default, matches Instances whose agentUrl equals this host's
|
||||
# CCP_AGENT_URL. Pass --slug SLUG or --instance-id ID for explicit targeting.
|
||||
#
|
||||
# Exit codes: 0 success, 1 error, 2 nothing matched
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ENV_FILE="${PROJECT_DIR}/.env"
|
||||
|
||||
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
||||
|
||||
CCP_URL=""; CCP_AGENT_URL=""; TOKEN=""; SLUG=""; INSTANCE_ID=""
|
||||
CONFIRM=false
|
||||
|
||||
usage() {
|
||||
sed -n '2,22p' "$0" | sed 's/^# \?//'
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--yes|-y) CONFIRM=true; shift ;;
|
||||
--ccp-url) CCP_URL="$2"; shift 2 ;;
|
||||
--agent-url) CCP_AGENT_URL="$2"; shift 2 ;;
|
||||
--token) TOKEN="$2"; shift 2 ;;
|
||||
--slug) SLUG="$2"; shift 2 ;;
|
||||
--instance-id) INSTANCE_ID="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
*) echo "Unknown flag: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Load missing fields from .env
|
||||
if [[ -z "$CCP_URL" || -z "$CCP_AGENT_URL" ]]; then
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
CCP_URL="${CCP_URL:-$(grep -E '^CCP_URL=' "$ENV_FILE" | head -1 | cut -d= -f2-)}"
|
||||
CCP_AGENT_URL="${CCP_AGENT_URL:-$(grep -E '^CCP_AGENT_URL=' "$ENV_FILE" | head -1 | cut -d= -f2-)}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Token may also come from env var
|
||||
TOKEN="${TOKEN:-${CCP_ADMIN_TOKEN:-}}"
|
||||
|
||||
if [[ -z "$CCP_URL" ]]; then
|
||||
echo -e "${RED}ERROR:${NC} CCP_URL not set. Pass --ccp-url or set in .env."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo -e "${RED}ERROR:${NC} No CCP admin token. Pass --token or set CCP_ADMIN_TOKEN env var."
|
||||
echo " Obtain via: curl -X POST \$CCP_URL/api/auth/login -d @login.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo -e "${RED}ERROR:${NC} python3 is required for JSON parsing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BOLD}CCP deregister${NC}"
|
||||
echo " CCP: $CCP_URL"
|
||||
[[ -n "$CCP_AGENT_URL" ]] && echo " Agent URL: $CCP_AGENT_URL (match target)"
|
||||
[[ -n "$SLUG" ]] && echo " Slug: $SLUG (match target)"
|
||||
[[ -n "$INSTANCE_ID" ]] && echo " Instance ID: $INSTANCE_ID (explicit)"
|
||||
[[ "$CONFIRM" == "false" ]] && echo -e " ${YELLOW}Mode: DRY RUN${NC} (pass --yes to execute)"
|
||||
echo ""
|
||||
|
||||
# Fetch instances
|
||||
RAW=$(curl -sf -H "Authorization: Bearer $TOKEN" "$CCP_URL/api/instances" 2>&1 || true)
|
||||
if [[ -z "$RAW" ]]; then
|
||||
echo -e "${RED}ERROR:${NC} Could not fetch /api/instances (check token + URL)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build match list — the API may wrap instances in {data: [...]} or return raw [].
|
||||
# Also: the /api/instances list omits pangolinEndpoint for brevity, so match fields are scoped to slug/agentUrl/id.
|
||||
MATCHES=$(echo "$RAW" | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
items = d.get('data', d) if isinstance(d, dict) else d
|
||||
target_slug = '${SLUG}' or None
|
||||
target_id = '${INSTANCE_ID}' or None
|
||||
target_agent = '${CCP_AGENT_URL}' or None
|
||||
|
||||
for inst in items:
|
||||
if target_id:
|
||||
if inst.get('id') == target_id:
|
||||
print(f\"{inst['id']}\t{inst.get('slug','?')}\t{inst.get('agentUrl','?')}\")
|
||||
elif target_slug:
|
||||
if inst.get('slug') == target_slug:
|
||||
print(f\"{inst['id']}\t{inst.get('slug','?')}\t{inst.get('agentUrl','?')}\")
|
||||
elif target_agent:
|
||||
if inst.get('agentUrl') == target_agent:
|
||||
print(f\"{inst['id']}\t{inst.get('slug','?')}\t{inst.get('agentUrl','?')}\")
|
||||
")
|
||||
|
||||
MATCH_COUNT=$(echo -n "$MATCHES" | grep -c . || true)
|
||||
echo -e "${CYAN}Matches: $MATCH_COUNT${NC}"
|
||||
if [[ "$MATCH_COUNT" -eq 0 ]]; then
|
||||
echo " (no instances matched the criteria)"
|
||||
exit 2
|
||||
fi
|
||||
echo "$MATCHES" | awk -F'\t' '{printf " - [%s] slug=%s agentUrl=%s\n", $1, $2, $3}'
|
||||
|
||||
if [[ "$CONFIRM" == "false" ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Dry run complete.${NC} Re-run with --yes to actually delete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}Deleting...${NC}"
|
||||
|
||||
FAILURES=0
|
||||
while IFS=$'\t' read -r iid slug aurl; do
|
||||
[[ -z "$iid" ]] && continue
|
||||
code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
"$CCP_URL/api/instances/$iid" \
|
||||
-H "Authorization: Bearer $TOKEN")
|
||||
if [[ "$code" == "200" || "$code" == "204" ]]; then
|
||||
echo -e " ${GREEN}OK${NC} instance $iid ($slug) deleted"
|
||||
else
|
||||
echo -e " ${RED}FAIL${NC} instance $iid ($slug) HTTP $code"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
fi
|
||||
done <<< "$MATCHES"
|
||||
|
||||
if [[ $FAILURES -gt 0 ]]; then
|
||||
echo -e "${YELLOW}Completed with $FAILURES failure(s).${NC}"
|
||||
exit 2
|
||||
fi
|
||||
echo -e "${GREEN}Deregister complete.${NC}"
|
||||
Loading…
x
Reference in New Issue
Block a user