2026-01-08 16:56:19 +01:00
#!/usr/bin/env bash
#
# Destroy a client's infrastructure
#
# Usage: ./scripts/destroy-client.sh <client_name>
#
# This script will:
# 1. Remove all Docker containers and volumes on the server
# 2. Destroy the VPS server via OpenTofu
# 3. Remove DNS records
#
# WARNING: This is DESTRUCTIVE and IRREVERSIBLE!
set -euo pipefail
# Colors for output
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
NC = '\033[0m' # No Color
# Script directory
SCRIPT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd ) "
PROJECT_ROOT = " $( dirname " $SCRIPT_DIR " ) "
# Check arguments
if [ $# -ne 1 ] ; then
echo -e " ${ RED } Error: Client name required ${ NC } "
echo " Usage: $0 <client_name> "
echo ""
echo " Example: $0 test "
exit 1
fi
CLIENT_NAME = " $1 "
# Check if secrets file exists
SECRETS_FILE = " $PROJECT_ROOT /secrets/clients/ ${ CLIENT_NAME } .sops.yaml "
if [ ! -f " $SECRETS_FILE " ] ; then
echo -e " ${ RED } Error: Secrets file not found: $SECRETS_FILE ${ NC } "
exit 1
fi
2026-01-18 18:17:15 +01:00
# Load Hetzner API token from SOPS if not already set
2026-01-08 16:56:19 +01:00
if [ -z " ${ HCLOUD_TOKEN :- } " ] ; then
2026-01-18 18:17:15 +01:00
echo -e " ${ BLUE } Loading Hetzner API token from SOPS... ${ NC } "
# shellcheck source=scripts/load-secrets-env.sh
source " $SCRIPT_DIR /load-secrets-env.sh "
echo ""
2026-01-08 16:56:19 +01:00
fi
if [ -z " ${ SOPS_AGE_KEY_FILE :- } " ] ; then
echo -e " ${ YELLOW } Warning: SOPS_AGE_KEY_FILE not set, using default ${ NC } "
export SOPS_AGE_KEY_FILE = " $PROJECT_ROOT /keys/age-key.txt "
fi
# Confirmation prompt
echo -e " ${ RED } ======================================== ${ NC } "
echo -e " ${ RED } WARNING: DESTRUCTIVE OPERATION ${ NC } "
echo -e " ${ RED } ======================================== ${ NC } "
echo ""
echo -e " This will ${ RED } PERMANENTLY DELETE ${ NC } : "
echo " - VPS server for client: $CLIENT_NAME "
echo " - All Docker containers and volumes"
echo " - All DNS records"
echo " - All data on the server"
echo ""
echo -e " ${ YELLOW } This operation CANNOT be undone! ${ NC } "
echo ""
read -p " Type the client name ' $CLIENT_NAME ' to confirm: " confirmation
if [ " $confirmation " != " $CLIENT_NAME " ] ; then
echo -e " ${ RED } Confirmation failed. Aborting. ${ NC } "
exit 1
fi
echo ""
echo -e " ${ YELLOW } Starting destruction of client: $CLIENT_NAME ${ NC } "
echo ""
2026-01-18 18:55:33 +01:00
# Step 0: Remove from monitoring
echo -e " ${ YELLOW } [0/7] Removing client from monitoring... ${ NC } "
echo ""
if [ -f " $SCRIPT_DIR /remove-client-from-monitoring.sh " ] ; then
" $SCRIPT_DIR /remove-client-from-monitoring.sh " " $CLIENT_NAME "
else
echo -e " ${ YELLOW } ⚠ Monitoring script not found ${ NC } "
echo "Manually remove monitors at: https://status.vrije.cloud"
fi
echo ""
2026-01-13 09:52:23 +01:00
# Step 1: Delete Mailgun SMTP credentials
2026-01-18 18:55:33 +01:00
echo -e " ${ YELLOW } [1/7] Deleting Mailgun SMTP credentials... ${ NC } "
2026-01-08 16:56:19 +01:00
cd " $PROJECT_ROOT /ansible "
2026-01-13 09:52:23 +01:00
# Run cleanup playbook to delete SMTP credentials
~/.local/bin/ansible-playbook -i hcloud.yml playbooks/cleanup.yml --limit " $CLIENT_NAME " 2>/dev/null || echo -e " ${ YELLOW } ⚠ Could not delete SMTP credentials (API key may not be configured) ${ NC } "
echo -e " ${ GREEN } ✓ SMTP credentials cleanup attempted ${ NC } "
echo ""
# Step 2: Clean up Docker containers and volumes on the server (if reachable)
2026-01-18 18:17:15 +01:00
echo -e " ${ YELLOW } [2/7] Cleaning up Docker containers and volumes... ${ NC } "
# Try to use per-client SSH key if it exists
SSH_KEY_ARG = ""
if [ -f " $PROJECT_ROOT /keys/ssh/ ${ CLIENT_NAME } " ] ; then
SSH_KEY_ARG = " --private-key= $PROJECT_ROOT /keys/ssh/ ${ CLIENT_NAME } "
fi
2026-01-13 09:52:23 +01:00
2026-01-18 18:17:15 +01:00
if ~/.local/bin/ansible -i hcloud.yml " $CLIENT_NAME " $SSH_KEY_ARG -m ping -o & >/dev/null; then
2026-01-08 16:56:19 +01:00
echo "Server is reachable, cleaning up Docker resources..."
# Stop and remove all containers
2026-01-18 18:17:15 +01:00
~/.local/bin/ansible -i hcloud.yml " $CLIENT_NAME " $SSH_KEY_ARG -m shell -a "docker ps -aq | xargs -r docker stop" -b 2>/dev/null || true
~/.local/bin/ansible -i hcloud.yml " $CLIENT_NAME " $SSH_KEY_ARG -m shell -a "docker ps -aq | xargs -r docker rm -f" -b 2>/dev/null || true
2026-01-08 16:56:19 +01:00
# Remove all volumes
2026-01-18 18:17:15 +01:00
~/.local/bin/ansible -i hcloud.yml " $CLIENT_NAME " $SSH_KEY_ARG -m shell -a "docker volume ls -q | xargs -r docker volume rm -f" -b 2>/dev/null || true
2026-01-08 16:56:19 +01:00
# Remove all networks (except defaults)
2026-01-18 18:17:15 +01:00
~/.local/bin/ansible -i hcloud.yml " $CLIENT_NAME " $SSH_KEY_ARG -m shell -a "docker network ls --filter type=custom -q | xargs -r docker network rm" -b 2>/dev/null || true
2026-01-08 16:56:19 +01:00
echo -e " ${ GREEN } ✓ Docker cleanup complete ${ NC } "
else
echo -e " ${ YELLOW } ⚠ Server not reachable, skipping Docker cleanup ${ NC } "
fi
echo ""
2026-01-13 09:52:23 +01:00
# Step 3: Destroy infrastructure with OpenTofu
2026-01-18 18:17:15 +01:00
echo -e " ${ YELLOW } [3/7] Destroying infrastructure with OpenTofu... ${ NC } "
2026-01-08 16:56:19 +01:00
cd " $PROJECT_ROOT /tofu "
2026-01-18 18:17:15 +01:00
# Destroy all resources for this client (server, volume, SSH key, DNS)
2026-01-08 16:56:19 +01:00
echo "Checking current infrastructure..."
2026-01-18 18:17:15 +01:00
tofu plan -destroy -var-file= "terraform.tfvars" \
-target= " hcloud_server.client[\" $CLIENT_NAME \"] " \
-target= " hcloud_volume.nextcloud_data[\" $CLIENT_NAME \"] " \
-target= " hcloud_volume_attachment.nextcloud_data[\" $CLIENT_NAME \"] " \
-target= " hcloud_ssh_key.client_keys[\" $CLIENT_NAME \"] " \
-target= " hetznerdns_record.client_domain[\" $CLIENT_NAME \"] " \
-target= " hetznerdns_record.client_wildcard[\" $CLIENT_NAME \"] " \
-out= destroy.tfplan
2026-01-08 16:56:19 +01:00
echo ""
echo "Applying destruction..."
tofu apply destroy.tfplan
# Cleanup plan file
rm -f destroy.tfplan
2026-01-18 18:17:15 +01:00
echo -e " ${ GREEN } ✓ Infrastructure destroyed ${ NC } "
echo ""
# Step 4: Remove client from terraform.tfvars
echo -e " ${ YELLOW } [4/7] Removing client from terraform.tfvars... ${ NC } "
TFVARS_FILE = " $PROJECT_ROOT /tofu/terraform.tfvars "
if grep -q " ^[[:space:]]* ${ CLIENT_NAME } [[:space:]]*= " " $TFVARS_FILE " ; then
# Create backup
cp " $TFVARS_FILE " " $TFVARS_FILE .bak "
# Remove the client block (from "client_name = {" to the closing "}")
# This uses awk to find and remove the entire block
awk -v client = " $CLIENT_NAME " '
BEGIN { skip = 0; in_block = 0 }
/^[ [ :space:] ] *#.*[ Cc] lient/ { if ( skip = = 0) print; next }
$0 ~ "^[[:space:]]*" client "[[:space:]]*=" { skip = 1; in_block = 1; brace_count = 0; next }
skip = = 1 {
for ( i = 1; i<= length( $0 ) ; i++) {
c = substr( $0 ,i,1)
if ( c = = "{" ) brace_count++
if ( c = = "}" ) brace_count--
}
if ( brace_count<0 || ( brace_count = = 0 && $0 ~ /^[ [ :space:] ] *} /) ) {
skip = 0
in_block = 0
next
}
next
}
{ print }
' " $TFVARS_FILE " > " $TFVARS_FILE .tmp "
mv " $TFVARS_FILE .tmp " " $TFVARS_FILE "
echo -e " ${ GREEN } ✓ Removed $CLIENT_NAME from terraform.tfvars ${ NC } "
else
echo -e " ${ YELLOW } ⚠ Client not found in terraform.tfvars ${ NC } "
fi
echo ""
# Step 5: Remove SSH keys
echo -e " ${ YELLOW } [5/7] Removing SSH keys... ${ NC } "
SSH_PRIVATE = " $PROJECT_ROOT /keys/ssh/ ${ CLIENT_NAME } "
SSH_PUBLIC = " $PROJECT_ROOT /keys/ssh/ ${ CLIENT_NAME } .pub "
if [ -f " $SSH_PRIVATE " ] ; then
rm -f " $SSH_PRIVATE "
echo -e " ${ GREEN } ✓ Removed private key: $SSH_PRIVATE ${ NC } "
else
echo -e " ${ YELLOW } ⚠ Private key not found ${ NC } "
fi
if [ -f " $SSH_PUBLIC " ] ; then
rm -f " $SSH_PUBLIC "
echo -e " ${ GREEN } ✓ Removed public key: $SSH_PUBLIC ${ NC } "
else
echo -e " ${ YELLOW } ⚠ Public key not found ${ NC } "
fi
2026-01-17 20:24:53 +01:00
echo ""
2026-01-18 18:17:15 +01:00
# Step 6: Remove secrets file
echo -e " ${ YELLOW } [6/7] Removing secrets file... ${ NC } "
if [ -f " $SECRETS_FILE " ] ; then
rm -f " $SECRETS_FILE "
echo -e " ${ GREEN } ✓ Removed secrets file: $SECRETS_FILE ${ NC } "
else
echo -e " ${ YELLOW } ⚠ Secrets file not found ${ NC } "
fi
echo ""
# Step 7: Update client registry
echo -e " ${ YELLOW } [7/7] Updating client registry... ${ NC } "
2026-01-17 20:24:53 +01:00
" $SCRIPT_DIR /update-registry.sh " " $CLIENT_NAME " destroy
echo ""
echo -e " ${ GREEN } ✓ Registry updated ${ NC } "
2026-01-08 16:56:19 +01:00
echo ""
echo -e " ${ GREEN } ======================================== ${ NC } "
echo -e " ${ GREEN } ✓ Client ' $CLIENT_NAME ' destroyed successfully ${ NC } "
echo -e " ${ GREEN } ======================================== ${ NC } "
echo ""
echo "The following have been removed:"
2026-01-13 09:52:23 +01:00
echo " ✓ Mailgun SMTP credentials"
2026-01-08 16:56:19 +01:00
echo " ✓ VPS server"
2026-01-18 18:17:15 +01:00
echo " ✓ Hetzner Volume"
echo " ✓ SSH keys (Hetzner + local)"
echo " ✓ DNS records"
echo " ✓ Firewall rules"
echo " ✓ Secrets file"
echo " ✓ terraform.tfvars entry"
echo " ✓ Registry entry"
2026-01-08 16:56:19 +01:00
echo ""
2026-01-18 18:17:15 +01:00
echo "The client has been completely removed from the infrastructure."
2026-01-08 16:56:19 +01:00
echo ""