automate new-app.sh: DNS, cert, commit, build, verify

- resolve server IP from hantim.net instead of env var
- create Vultr DNS zone and A records via API
- wait for DNS propagation before cert issuance
- issue SSL cert via webroot (zero downtime)
- auto commit and push hantim-server
- trigger initial app build via Gitea API
- verify site is live with curl check
- update all docs to reflect single-command flow
This commit is contained in:
2026-03-16 01:57:10 -04:00
parent 796a0003c7
commit b51fe596c6
5 changed files with 184 additions and 62 deletions
+140 -16
View File
@@ -7,12 +7,23 @@ TEMPLATE_REPO="static-site-template"
if [ -z "${1:-}" ]; then
echo "Usage: ./scripts/new-app.sh <domain>"
echo "Example: ./scripts/new-app.sh hcsuzuki.net"
echo " Example: ./scripts/new-app.sh hcsuzuki.net"
echo ""
echo "Requires environment variables:"
echo " DEPLOY_USER - SSH username (default: \$USER)"
exit 1
fi
APP="$1"
CONTAINER_NAME="${APP//./_}"
SERVER_IP=$(dig +short hantim.net | head -1)
if [ -z "$SERVER_IP" ]; then
echo "Error: Could not resolve hantim.net to get server IP."
exit 1
fi
SSH_TARGET="${DEPLOY_USER:-$USER}@$SERVER_IP"
if ! [[ "$APP" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
echo "Error: app name must be alphanumeric (hyphens, dots, underscores allowed)."
@@ -39,6 +50,13 @@ if ! command -v jq &>/dev/null; then
exit 1
fi
if ! command -v dig &>/dev/null; then
echo "Error: dig is not installed."
echo " On macOS: brew install bind"
echo " On Linux: dnf install bind-utils"
exit 1
fi
# --- Get Gitea API token from Bitwarden ---
if bw status 2>/dev/null | grep -q '"status":"unauthenticated"'; then
@@ -53,6 +71,73 @@ fi
bw sync --session "${BW_SESSION:-}"
GITEA_TOKEN=$(bw get notes hantim-new-app-script --session "${BW_SESSION:-}")
VULTR_API_KEY=$(bw get notes hantim-vultr-api-key --session "${BW_SESSION:-}")
# --- Set up DNS records on Vultr ---
VULTR_API="https://api.vultr.com/v2"
VULTR_AUTH="Authorization: Bearer $VULTR_API_KEY"
echo "==> Checking DNS zone for $APP on Vultr..."
ZONE_CHECK=$(curl -s -o /dev/null -w "%{http_code}" \
"$VULTR_API/domains/$APP" \
-H "$VULTR_AUTH")
if [ "$ZONE_CHECK" != "200" ]; then
echo " Creating DNS zone for $APP..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "$VULTR_API/domains" \
-H "$VULTR_AUTH" \
-H "Content-Type: application/json" \
-d "{\"domain\": \"$APP\"}")
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
echo "Error: Failed to create DNS zone (HTTP $HTTP_CODE)"
exit 1
fi
echo " Created DNS zone for $APP"
fi
echo "==> Checking A records for $APP..."
RECORDS=$(curl -s "$VULTR_API/domains/$APP/records" -H "$VULTR_AUTH")
# Check/create root A record
ROOT_EXISTS=$(echo "$RECORDS" | jq -r ".records[] | select(.type == \"A\" and .name == \"\" and .data == \"$SERVER_IP\") | .id")
if [ -z "$ROOT_EXISTS" ]; then
echo " Creating A record: $APP -> $SERVER_IP"
curl -sf -X POST "$VULTR_API/domains/$APP/records" \
-H "$VULTR_AUTH" \
-H "Content-Type: application/json" \
-d "{\"name\": \"\", \"type\": \"A\", \"data\": \"$SERVER_IP\", \"ttl\": 3600}" > /dev/null
else
echo " A record for $APP already exists."
fi
# Check/create www A record
WWW_EXISTS=$(echo "$RECORDS" | jq -r ".records[] | select(.type == \"A\" and .name == \"www\" and .data == \"$SERVER_IP\") | .id")
if [ -z "$WWW_EXISTS" ]; then
echo " Creating A record: www.$APP -> $SERVER_IP"
curl -sf -X POST "$VULTR_API/domains/$APP/records" \
-H "$VULTR_AUTH" \
-H "Content-Type: application/json" \
-d "{\"name\": \"www\", \"type\": \"A\", \"data\": \"$SERVER_IP\", \"ttl\": 3600}" > /dev/null
else
echo " A record for www.$APP already exists."
fi
echo "==> Waiting for DNS to propagate..."
for i in $(seq 1 30); do
RESOLVED=$(dig +short "$APP" @ns1.vultr.com 2>/dev/null)
if [ "$RESOLVED" = "$SERVER_IP" ]; then
echo " DNS is live."
break
fi
if [ "$i" = "30" ]; then
echo "Error: DNS for $APP did not resolve to $SERVER_IP after 5 minutes."
echo " If this is a new domain, make sure nameservers are set to Vultr on directnic.com."
exit 1
fi
sleep 10
done
# --- Create Gitea repo from template ---
@@ -172,18 +257,57 @@ server {
}
NGINX
echo ""
echo "Done! Files created:"
echo " - Gitea repo: $GITEA_URL/$GITEA_ORG/$APP"
echo " - docker/$APP/compose.yml"
echo " - .gitea/workflows/deploy-$APP.yml"
echo " - docker/nginx/conf.d/$APP.conf"
echo ""
echo "Next steps:"
echo " 1. Issue SSL cert on the server (BEFORE pushing nginx config):"
echo " docker stop nginx"
echo " certbot certonly --standalone --non-interactive --agree-tos --register-unsafely-without-email --cert-name $APP -d $APP -d www.$APP"
echo " docker start nginx"
echo " 2. Commit and push hantim-server to deploy"
echo " 3. Clone $GITEA_URL/$GITEA_ORG/$APP and customize it"
echo " 4. Edit docker/$APP/compose.yml if needed"
# --- Issue SSL cert (zero downtime) ---
echo "==> Writing temporary HTTP-only config on server..."
TEMP_CONF="server {
listen 80;
listen [::]:80;
server_name $APP www.$APP;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 444;
}
}"
echo "$TEMP_CONF" | ssh "$SSH_TARGET" "sudo tee /opt/hantim/docker/nginx/conf.d/$APP.conf > /dev/null"
ssh "$SSH_TARGET" "sudo docker exec nginx nginx -t && sudo docker exec nginx nginx -s reload"
echo "==> Issuing SSL certificate..."
ssh "$SSH_TARGET" "sudo certbot certonly --webroot -w /opt/hantim/docker/nginx/certbot/www --non-interactive --agree-tos --register-unsafely-without-email --cert-name $APP -d $APP -d www.$APP"
# --- Commit and push ---
echo "==> Committing and pushing hantim-server..."
cd "$REPO_ROOT"
git add "docker/$APP/compose.yml" ".gitea/workflows/deploy-$APP.yml" "docker/nginx/conf.d/$APP.conf"
git commit -m "add $APP"
git push
# --- Trigger initial build ---
echo "==> Triggering initial build for $APP..."
curl -sf -X POST "$GITEA_URL/api/v1/repos/$GITEA_ORG/$APP/contents/.deploy-trigger" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"content\": \"$(echo -n "initial deploy" | base64)\", \"message\": \"trigger initial build\"}" > /dev/null
# --- Verify ---
echo "==> Waiting for deployment (up to 3 minutes)..."
for i in $(seq 1 18); do
if curl -sf "https://www.$APP" > /dev/null 2>&1; then
echo "Site is live at https://www.$APP"
exit 0
fi
sleep 10
done
echo "WARNING: https://www.$APP is not responding after 3 minutes."
echo " Check that:"
echo " - DNS for $APP and www.$APP points to $SERVER_IP"
echo " - The build completed: $GITEA_URL/$GITEA_ORG/$APP/actions"
echo " - The container is running: ssh $SSH_TARGET sudo docker ps"