Sk1-exam/remove-app.sh
Gigi Saji 260b60622f Initial commit: Expense Tracker on Oracle Cloud (sk1 exam)
Public-cloud deployment of a single-user expense tracker:
- 4-container stack: Caddy (HTTPS via Let's Encrypt), nginx (React/Vite SPA), Express API, Postgres 16
- Repeatable deployment via prepare-app.sh using only OCI CLI (no web console)
- Persistent Postgres volume, auto-restart policies, healthchecks, backup + restore scripts
- DuckDNS dynamic DNS for the public hostname; secrets isolated to gitignored .env

Author: Gigi Saji
Live URL: https://savesave.duckdns.org
2026-05-14 12:53:45 +05:30

110 lines
4.9 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# remove-app.sh — tear down everything created by prepare-app.sh.
#
# Does NOT touch any existing resources outside sk1-* and the named VM.
# Idempotent: safe to re-run; each step skips if already gone.
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
if [ ! -f .env ]; then
echo "ERROR: .env not found in $SCRIPT_DIR" >&2
exit 1
fi
set -a; source .env; set +a
: "${OCI_VM_NAME:=sk1-expense-tracker}"
: "${OCI_SSH_PRIVATE_KEY_PATH:=$HOME/.ssh/id_ed25519}"
OCI_SSH_PRIVATE_KEY_PATH="${OCI_SSH_PRIVATE_KEY_PATH/#\~/$HOME}"
command -v oci >/dev/null || { echo "ERROR: oci CLI not installed"; exit 1; }
TENANCY_OCID="$(oci iam compartment list --query 'data[0]."compartment-id"' --raw-output 2>/dev/null)"
COMPARTMENT_ID="${OCI_COMPARTMENT_ID:-$TENANCY_OCID}"
VCN_NAME="sk1-vcn"
SUBNET_NAME="sk1-subnet"
IG_NAME="sk1-ig"
SL_NAME="sk1-seclist"
RT_NAME="sk1-rt"
# ---- Find the VM by name ----
INSTANCE_ID="$(oci compute instance list --compartment-id "$COMPARTMENT_ID" \
--display-name "$OCI_VM_NAME" \
--query 'data[?"lifecycle-state" != `TERMINATED`] | [0].id' --raw-output 2>/dev/null || true)"
if [ -n "$INSTANCE_ID" ] && [ "$INSTANCE_ID" != "null" ]; then
echo "[remove] tearing down Compose stack on the VM..."
VNIC_ID="$(oci compute instance list-vnics --instance-id "$INSTANCE_ID" --query 'data[0].id' --raw-output 2>/dev/null || true)"
PUBLIC_IP="$(oci network vnic get --vnic-id "$VNIC_ID" --query 'data."public-ip"' --raw-output 2>/dev/null || true)"
if [ -n "$PUBLIC_IP" ] && [ "$PUBLIC_IP" != "null" ]; then
ssh -i "$OCI_SSH_PRIVATE_KEY_PATH" -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 \
ubuntu@"$PUBLIC_IP" \
'cd /opt/sk1 && sg docker -c "docker compose down -v --rmi local --remove-orphans" || true' \
|| echo "[remove] (could not reach VM via SSH — proceeding to terminate)"
fi
echo "[remove] terminating VM $OCI_VM_NAME ($INSTANCE_ID)..."
oci compute instance terminate --instance-id "$INSTANCE_ID" --force \
--preserve-boot-volume false --wait-for-state TERMINATED
else
echo "[remove] no running VM named $OCI_VM_NAME — skipping"
fi
# ---- Delete network resources (in reverse dependency order) ----
VCN_ID="$(oci network vcn list --compartment-id "$COMPARTMENT_ID" --display-name "$VCN_NAME" \
--query 'data[0].id' --raw-output 2>/dev/null || true)"
if [ -n "$VCN_ID" ] && [ "$VCN_ID" != "null" ]; then
SUBNET_ID="$(oci network subnet list --compartment-id "$COMPARTMENT_ID" --vcn-id "$VCN_ID" \
--display-name "$SUBNET_NAME" --query 'data[0].id' --raw-output 2>/dev/null || true)"
if [ -n "$SUBNET_ID" ] && [ "$SUBNET_ID" != "null" ]; then
echo "[remove] deleting subnet..."
oci network subnet delete --subnet-id "$SUBNET_ID" --force --wait-for-state TERMINATED || true
fi
SL_ID="$(oci network security-list list --compartment-id "$COMPARTMENT_ID" --vcn-id "$VCN_ID" \
--display-name "$SL_NAME" --query 'data[0].id' --raw-output 2>/dev/null || true)"
if [ -n "$SL_ID" ] && [ "$SL_ID" != "null" ]; then
echo "[remove] deleting security list..."
oci network security-list delete --security-list-id "$SL_ID" --force --wait-for-state TERMINATED || true
fi
RT_ID="$(oci network route-table list --compartment-id "$COMPARTMENT_ID" --vcn-id "$VCN_ID" \
--display-name "$RT_NAME" --query 'data[0].id' --raw-output 2>/dev/null || true)"
if [ -n "$RT_ID" ] && [ "$RT_ID" != "null" ]; then
echo "[remove] deleting route table..."
oci network route-table delete --rt-id "$RT_ID" --force --wait-for-state TERMINATED || true
fi
IG_ID="$(oci network internet-gateway list --compartment-id "$COMPARTMENT_ID" --vcn-id "$VCN_ID" \
--display-name "$IG_NAME" --query 'data[0].id' --raw-output 2>/dev/null || true)"
if [ -n "$IG_ID" ] && [ "$IG_ID" != "null" ]; then
echo "[remove] deleting internet gateway..."
oci network internet-gateway delete --ig-id "$IG_ID" --force --wait-for-state TERMINATED || true
fi
echo "[remove] deleting VCN..."
oci network vcn delete --vcn-id "$VCN_ID" --force --wait-for-state TERMINATED || true
else
echo "[remove] no VCN named $VCN_NAME — skipping"
fi
# ---- Reset DuckDNS A record (best effort) ----
if [ -n "${DUCKDNS_DOMAIN:-}" ] && [ -n "${DUCKDNS_TOKEN:-}" ]; then
echo "[remove] clearing DuckDNS A record..."
curl -fsS "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN%%.duckdns.org}&token=${DUCKDNS_TOKEN}&clear=true" \
|| echo "(DuckDNS clear failed — non-fatal)"
fi
echo ""
echo "================================================================"
echo " All sk1 resources removed. Existing VMs and unrelated"
echo " resources are untouched."
echo "================================================================"