Menu
Public repository / 6c8df1c27074216aaa5c9e88744bdb87fd2c522e
AkurAI-EC2 / libexec/akurai-ec2-retire-app.py
Latest change d8aa29121486f4b02d0016888d94fa66cac36ffe - Checkpoint WIP + rustfmt + AkurAI Build CI/CD pipeline by Ólafur Búi Ólafsson
#!/usr/bin/env python3
"""Inventory and retire one AkurAI EC2 application using bounded, explicit deletion."""
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
import sys
REMOTE = r'''set -euo pipefail
name="$1"; domain="$2"; action="$3"; delete_data="$4"; delete_user="$5"; delete_cert="$6"
exists() { sudo test -e "$1" && printf true || printf false; }
unit=$(systemctl list-unit-files "$name.service" --no-legend 2>/dev/null | grep -q "$name.service" && printf true || printf false)
active=$(systemctl is-active --quiet "$name.service" 2>/dev/null && printf true || printf false)
user=$(id "$name" >/dev/null 2>&1 && printf true || printf false)
printf '{"name":"%s","domain":"%s","unit":%s,"active":%s,"app":%s,"data":%s,"config":%s,"backup":%s,"nginx_available":%s,"nginx_enabled":%s,"user":%s}\n' \
"$name" "$domain" "$unit" "$active" "$(exists "/opt/$name")" "$(exists "/var/lib/$name")" \
"$(exists "/etc/$name")" "$(exists "/var/backups/$name")" \
"$(exists "/etc/nginx/sites-available/$domain")" "$(exists "/etc/nginx/sites-enabled/$domain")" "$user"
[ "$action" = inventory ] && exit 0
sudo systemctl disable --now "$name.service" 2>/dev/null || true
sudo rm -f "/etc/systemd/system/$name.service"
sudo rm -rf "/opt/$name"
sudo rm -f "/etc/nginx/sites-enabled/$domain" "/etc/nginx/sites-available/$domain"
if [ "$delete_data" = true ]; then sudo rm -rf "/var/lib/$name" "/etc/$name" "/var/backups/$name"; fi
if [ "$delete_cert" = true ]; then sudo certbot delete --cert-name "$domain" --non-interactive 2>/dev/null || true; fi
if [ "$delete_user" = true ]; then sudo userdel "$name" 2>/dev/null || true; sudo groupdel "$name" 2>/dev/null || true; fi
sudo systemctl daemon-reload
sudo systemctl reset-failed
sudo nginx -t >/dev/null
sudo systemctl reload nginx
! systemctl list-unit-files "$name.service" --no-legend 2>/dev/null | grep -q "$name.service"
! sudo test -e "/opt/$name"
! sudo test -e "/etc/nginx/sites-enabled/$domain"
! sudo test -e "/etc/nginx/sites-available/$domain"
[ "$delete_data" != true ] || { ! sudo test -e "/var/lib/$name" && ! sudo test -e "/etc/$name" && ! sudo test -e "/var/backups/$name"; }
[ "$delete_user" != true ] || ! id "$name" >/dev/null 2>&1
printf '{"retired":"%s","domain":"%s","ok":true}\n' "$name" "$domain"
'''
def valid(value: str, pattern: str, label: str) -> str:
if not re.fullmatch(pattern, value):
raise SystemExit(f"invalid {label}: {value!r}")
return value
def main() -> int:
parser = argparse.ArgumentParser(prog="akurai-ec2 retire-app")
parser.add_argument("name")
parser.add_argument("--domain", required=True)
mode = parser.add_mutually_exclusive_group()
mode.add_argument("--inventory", action="store_true")
mode.add_argument("--apply", action="store_true")
parser.add_argument("--delete-data", action="store_true")
parser.add_argument("--delete-user", action="store_true")
parser.add_argument("--delete-certificate", action="store_true")
parser.add_argument("--delete-dns", action="store_true")
parser.add_argument("--dns-type", default="A")
parser.add_argument("--yes", action="store_true")
args = parser.parse_args()
name = valid(args.name, r"[a-z0-9][a-z0-9-]{0,62}", "application name")
domain = valid(args.domain.rstrip("."), r"[A-Za-z0-9][A-Za-z0-9.-]{1,252}[A-Za-z0-9]", "domain")
if args.apply and not args.yes:
raise SystemExit("retire-app --apply requires --yes")
if not args.apply and any((args.delete_data, args.delete_user, args.delete_certificate, args.delete_dns)):
raise SystemExit("deletion flags require --apply")
cli = os.environ.get("AKURAI_EC2_CLI", "akurai-ec2")
action = "apply" if args.apply else "inventory"
remote_cmd = f"bash -s -- {name} {domain} {action} {str(args.delete_data).lower()} {str(args.delete_user).lower()} {str(args.delete_certificate).lower()}"
result = subprocess.run([cli, "ssh", remote_cmd], input=REMOTE, text=True)
if result.returncode:
return result.returncode
if args.apply and args.delete_dns:
dns = subprocess.run([cli, "dns", "delete", domain, "--type", args.dns_type.upper(), "--yes"])
if dns.returncode:
return dns.returncode
verify = subprocess.run([cli, "dns", "verify", domain, "--type", args.dns_type.upper(), "--absent"])
if verify.returncode:
print(json.dumps({"ok": False, "error": "public DNS still resolves; propagation may be pending"}), file=sys.stderr)
return verify.returncode
return 0
if __name__ == "__main__":
raise SystemExit(main())