replace NPM with vanilla nginx + certbot
Deploy nginx / deploy (push) Failing after 3s
Deploy timothykim.net / deploy (push) Successful in 3s

- nginx:alpine replaces nginx-proxy-manager
- certbot on host with standalone issuance and auto-renewal
- per-site nginx configs in conf.d/ (HTTP->HTTPS, bare->www)
- container names use underscores (timothykim_net)
- setup.sh: automated cert issuance, dynamic app startup
- deploy.sh: nginx -t guard, graceful image-not-found
- new-app.sh: Gitea API repo creation, nginx conf generation
- add ARCHITECTURE.md, USECASES.md, CLAUDE.md
- remove old NPM data/certs from tracking
This commit is contained in:
2026-03-16 00:55:13 -04:00
parent 31c6235726
commit 5c38b0631a
52 changed files with 681 additions and 307 deletions
+14 -1
View File
@@ -12,5 +12,18 @@ APP="${CMD#deploy-}"
cd /opt/hantim
git pull
cd "docker/$APP"
docker compose pull
if ! docker compose pull; then
echo "Image not yet available for $APP — skipping. It will deploy when the app repo is first pushed."
exit 0
fi
if [ "$APP" = "nginx" ]; then
# Test config before applying — a bad config (e.g. missing cert) would take down all sites
docker compose run --rm -T nginx nginx -t
fi
docker compose up -d
if [ "$APP" = "nginx" ]; then
docker exec nginx nginx -s reload
fi
+123 -5
View File
@@ -1,13 +1,18 @@
#!/bin/bash
set -euo pipefail
GITEA_URL="https://git.timothykim.net"
GITEA_ORG="hantim"
TEMPLATE_REPO="static-site-template"
if [ -z "${1:-}" ]; then
echo "Usage: ./scripts/new-app.sh <app-name>"
echo "Example: ./scripts/new-app.sh my-blog"
echo "Usage: ./scripts/new-app.sh <domain>"
echo "Example: ./scripts/new-app.sh hcsuzuki.net"
exit 1
fi
APP="$1"
CONTAINER_NAME="${APP//./_}"
if ! [[ "$APP" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
echo "Error: app name must be alphanumeric (hyphens, dots, underscores allowed)."
@@ -21,6 +26,60 @@ if [ -d "$REPO_ROOT/docker/$APP" ]; then
exit 1
fi
# --- Check dependencies ---
if ! command -v bw &>/dev/null; then
echo "Error: Bitwarden CLI (bw) is not installed."
echo " npm install -g @bitwarden/cli"
exit 1
fi
if ! command -v jq &>/dev/null; then
echo "Error: jq is not installed."
exit 1
fi
# --- Get Gitea API token from Bitwarden ---
if bw status 2>/dev/null | grep -q '"status":"unauthenticated"'; then
echo "Log in to Bitwarden:"
bw login
fi
if bw status 2>/dev/null | grep -q '"status":"locked"'; then
BW_SESSION=$(bw unlock --raw)
export BW_SESSION
fi
bw sync --session "${BW_SESSION:-}"
GITEA_TOKEN=$(bw get notes hantim-new-app-script --session "${BW_SESSION:-}")
# --- Create Gitea repo from template ---
echo "Creating Gitea repo $GITEA_ORG/$APP from template $TEMPLATE_REPO..."
HTTP_CODE=$(curl -s -o /tmp/new-app-response.json -w "%{http_code}" \
-X POST "$GITEA_URL/api/v1/repos/$GITEA_ORG/$TEMPLATE_REPO/generate" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"owner\": \"$GITEA_ORG\",
\"name\": \"$APP\",
\"git_content\": true,
\"labels\": false,
\"webhooks\": false
}")
if [ "$HTTP_CODE" != "201" ]; then
echo "Error: Failed to create repo (HTTP $HTTP_CODE)"
cat /tmp/new-app-response.json
rm -f /tmp/new-app-response.json
exit 1
fi
rm -f /tmp/new-app-response.json
echo " Created: $GITEA_URL/$GITEA_ORG/$APP"
# --- Create local files in hantim-server ---
echo "Creating docker/$APP/compose.yml..."
mkdir -p "$REPO_ROOT/docker/$APP"
cat > "$REPO_ROOT/docker/$APP/compose.yml" <<EOF
@@ -28,6 +87,7 @@ services:
app:
image: git.timothykim.net/hantim/$APP:latest
restart: unless-stopped
container_name: $CONTAINER_NAME
networks:
- shared
@@ -60,12 +120,70 @@ jobs:
OUTER
sed -i "s/APP_PLACEHOLDER/$APP/g" "$REPO_ROOT/.gitea/workflows/deploy-$APP.yml"
echo "Creating docker/nginx/conf.d/$APP.conf..."
cat > "$REPO_ROOT/docker/nginx/conf.d/$APP.conf" <<NGINX
# Redirect HTTP to HTTPS, bare domain to www
server {
listen 80;
listen [::]:80;
server_name $APP www.$APP;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://www.$APP\$request_uri;
}
}
# Redirect bare HTTPS domain to www
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name $APP;
ssl_certificate /etc/letsencrypt/live/$APP/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$APP/privkey.pem;
return 301 https://www.$APP\$request_uri;
}
# Main site
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name www.$APP;
ssl_certificate /etc/letsencrypt/live/$APP/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$APP/privkey.pem;
add_header Strict-Transport-Security "max-age=63072000; preload" always;
location / {
proxy_pass http://$CONTAINER_NAME:80;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
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. Edit docker/$APP/compose.yml to fit your app"
echo " 2. Commit and push to deploy"
echo " 3. Configure the proxy host in Nginx Proxy Manager"
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"