add cert command to deploy.sh, use deploy user for cert issuance
- deploy.sh: add cert-<domain> command for zero-downtime SSL issuance - new-app.sh: SSH as deploy user with key from Bitwarden instead of personal user - remove DEPLOY_USER/DEPLOY_HOST env var requirements - update all docs to reflect new flow
This commit is contained in:
+1
-1
@@ -45,7 +45,7 @@ configs, deploy scripts, and Gitea Actions workflows.
|
|||||||
|
|
||||||
```
|
```
|
||||||
setup.sh # Server provisioning (idempotent)
|
setup.sh # Server provisioning (idempotent)
|
||||||
scripts/deploy.sh # Receives deploy commands via SSH
|
scripts/deploy.sh # Receives deploy + cert commands via SSH
|
||||||
scripts/new-app.sh # Scaffolds + deploys a new app (single command)
|
scripts/new-app.sh # Scaffolds + deploys a new app (single command)
|
||||||
docker/nginx/ # nginx reverse proxy
|
docker/nginx/ # nginx reverse proxy
|
||||||
compose.yml # nginx:alpine container
|
compose.yml # nginx:alpine container
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Actions workflows.
|
|||||||
## Key files
|
## Key files
|
||||||
|
|
||||||
- `setup.sh` -- Server provisioning (idempotent, run as root)
|
- `setup.sh` -- Server provisioning (idempotent, run as root)
|
||||||
- `scripts/deploy.sh` -- SSH-triggered deploy (validates command, pulls, restarts)
|
- `scripts/deploy.sh` -- SSH-triggered deploy + cert issuance (deploy-* and cert-* commands)
|
||||||
- `scripts/new-app.sh` -- Single-command new app (DNS + Gitea repo + cert + deploy + verify)
|
- `scripts/new-app.sh` -- Single-command new app (DNS + Gitea repo + cert + deploy + verify)
|
||||||
- `docker/nginx/` -- Reverse proxy (nginx:alpine + certbot on host)
|
- `docker/nginx/` -- Reverse proxy (nginx:alpine + certbot on host)
|
||||||
- `docker/<domain>/compose.yml` -- Per-app compose files
|
- `docker/<domain>/compose.yml` -- Per-app compose files
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ docker/
|
|||||||
timothykim.net/ # timothykim.net static site
|
timothykim.net/ # timothykim.net static site
|
||||||
compose.yml
|
compose.yml
|
||||||
scripts/
|
scripts/
|
||||||
deploy.sh # Deploy script (validates command, git pull, docker compose up)
|
deploy.sh # Deploy + cert issuance script (called via SSH)
|
||||||
new-app.sh # Scaffolding script to add a new app
|
new-app.sh # Scaffolding script to add a new app
|
||||||
setup.sh # Server provisioning script (idempotent)
|
setup.sh # Server provisioning script (idempotent)
|
||||||
.gitea/workflows/ # Per-app deploy workflows triggered by path changes
|
.gitea/workflows/ # Per-app deploy workflows triggered by path changes
|
||||||
@@ -39,8 +39,9 @@ bash /opt/hantim/setup.sh
|
|||||||
4. Fetches the Docker registry token and deploy SSH public key from Bitwarden
|
4. Fetches the Docker registry token and deploy SSH public key from Bitwarden
|
||||||
5. Logs into the Gitea Docker registry
|
5. Logs into the Gitea Docker registry
|
||||||
6. Creates the `deploy` user with restricted SSH access and sudo
|
6. Creates the `deploy` user with restricted SSH access and sudo
|
||||||
7. Issues SSL certificates via certbot for all configured domains
|
7. Opens firewall ports (HTTP/HTTPS)
|
||||||
8. Starts all services (nginx first, then all apps)
|
8. Issues SSL certificates via certbot for all configured domains
|
||||||
|
9. Starts all services (nginx first, then all apps)
|
||||||
|
|
||||||
**Prerequisites**: DNS for all configured domains must point to the server
|
**Prerequisites**: DNS for all configured domains must point to the server
|
||||||
before running setup.
|
before running setup.
|
||||||
@@ -59,7 +60,8 @@ the SSL cert (zero downtime), commits and pushes, triggers the first build,
|
|||||||
and verifies the site is live.
|
and verifies the site is live.
|
||||||
|
|
||||||
**Prerequisites:** domain nameservers pointed to Vultr, `bw`/`jq`/`dig`
|
**Prerequisites:** domain nameservers pointed to Vultr, `bw`/`jq`/`dig`
|
||||||
installed, SSH access to the server. See USECASES.md for full details.
|
installed. Uses the deploy SSH key from Bitwarden -- no personal server
|
||||||
|
access needed. See USECASES.md for full details.
|
||||||
|
|
||||||
After the site is live, clone the app repo and customize it:
|
After the site is live, clone the app repo and customize it:
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -15,6 +15,7 @@ bash /opt/hantim/setup.sh
|
|||||||
- Fetches secrets from Bitwarden (git-crypt key, registry token, deploy SSH key)
|
- Fetches secrets from Bitwarden (git-crypt key, registry token, deploy SSH key)
|
||||||
- Unlocks git-crypt, logs into Docker registry
|
- Unlocks git-crypt, logs into Docker registry
|
||||||
- Creates deploy user with restricted SSH + sudo
|
- Creates deploy user with restricted SSH + sudo
|
||||||
|
- Opens firewall ports (HTTP/HTTPS)
|
||||||
- Issues SSL certs for all domains in `docker/nginx/conf.d/`
|
- Issues SSL certs for all domains in `docker/nginx/conf.d/`
|
||||||
- Starts nginx and all app containers
|
- Starts nginx and all app containers
|
||||||
|
|
||||||
@@ -43,14 +44,14 @@ From your dev machine (single command):
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
- Bitwarden vault must contain `hantim-new-app-script` and `hantim-vultr-api-key`
|
- Bitwarden vault must contain `hantim-new-app-script`, `hantim-vultr-api-key`,
|
||||||
|
and `hantim-server-deploy`
|
||||||
- Domain nameservers must be pointed to Vultr (configured on directnic.com)
|
- Domain nameservers must be pointed to Vultr (configured on directnic.com)
|
||||||
- SSH access to the server as your user with passwordless sudo
|
|
||||||
- Dependencies: `bw`, `jq`, `dig`
|
- Dependencies: `bw`, `jq`, `dig`
|
||||||
|
|
||||||
**What happens (fully automated):**
|
**What happens (fully automated):**
|
||||||
1. Resolves server IP from `hantim.net`
|
1. Resolves server IP from `hantim.net`
|
||||||
2. Fetches Gitea API token and Vultr API key from Bitwarden
|
2. Fetches Gitea API token, Vultr API key, and deploy SSH key from Bitwarden
|
||||||
3. Creates DNS zone on Vultr (if needed) and A records for bare + www
|
3. Creates DNS zone on Vultr (if needed) and A records for bare + www
|
||||||
4. Waits for DNS to propagate
|
4. Waits for DNS to propagate
|
||||||
5. Creates Gitea repo `hantim/hcsuzuki.net` from `static-site-template`
|
5. Creates Gitea repo `hantim/hcsuzuki.net` from `static-site-template`
|
||||||
@@ -58,8 +59,7 @@ From your dev machine (single command):
|
|||||||
- `docker/hcsuzuki.net/compose.yml` (container name: `hcsuzuki_net`)
|
- `docker/hcsuzuki.net/compose.yml` (container name: `hcsuzuki_net`)
|
||||||
- `.gitea/workflows/deploy-hcsuzuki.net.yml`
|
- `.gitea/workflows/deploy-hcsuzuki.net.yml`
|
||||||
- `docker/nginx/conf.d/hcsuzuki.net.conf` (HTTP->HTTPS, bare->www, proxy)
|
- `docker/nginx/conf.d/hcsuzuki.net.conf` (HTTP->HTTPS, bare->www, proxy)
|
||||||
7. SSHes to server: writes temporary HTTP-only nginx config, issues SSL cert
|
7. SSHes to server as deploy user, issues SSL cert via webroot (zero downtime)
|
||||||
via webroot (zero downtime)
|
|
||||||
8. Commits and pushes hantim-server (triggers deploy)
|
8. Commits and pushes hantim-server (triggers deploy)
|
||||||
9. Triggers initial build of the app repo via Gitea API
|
9. Triggers initial build of the app repo via Gitea API
|
||||||
10. Polls `https://www.hcsuzuki.net` until it responds (up to 3 minutes)
|
10. Polls `https://www.hcsuzuki.net` until it responds (up to 3 minutes)
|
||||||
|
|||||||
@@ -3,6 +3,34 @@ set -euo pipefail
|
|||||||
|
|
||||||
CMD="${SSH_ORIGINAL_COMMAND:-${1:-}}"
|
CMD="${SSH_ORIGINAL_COMMAND:-${1:-}}"
|
||||||
|
|
||||||
|
if [[ "$CMD" =~ ^cert-[a-zA-Z0-9._-]+$ ]]; then
|
||||||
|
APP="${CMD#cert-}"
|
||||||
|
|
||||||
|
# Write temporary HTTP-only config so nginx can serve ACME challenges
|
||||||
|
cat > "/opt/hantim/docker/nginx/conf.d/$APP.conf" <<NGINXCONF
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name $APP www.$APP;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINXCONF
|
||||||
|
|
||||||
|
docker exec nginx nginx -t && docker exec nginx nginx -s reload
|
||||||
|
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"
|
||||||
|
echo "Certificate issued for $APP"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
if ! [[ "$CMD" =~ ^deploy-[a-zA-Z0-9._-]+$ ]]; then
|
if ! [[ "$CMD" =~ ^deploy-[a-zA-Z0-9._-]+$ ]]; then
|
||||||
echo "Unknown command: $CMD"
|
echo "Unknown command: $CMD"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
+10
-24
@@ -9,8 +9,7 @@ if [ -z "${1:-}" ]; then
|
|||||||
echo "Usage: ./scripts/new-app.sh <domain>"
|
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 ""
|
||||||
echo "Requires environment variables:"
|
echo "Requires: bw, jq, dig"
|
||||||
echo " DEPLOY_USER - SSH username (default: \$USER)"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -23,8 +22,6 @@ if [ -z "$SERVER_IP" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SSH_TARGET="${DEPLOY_USER:-$USER}@$SERVER_IP"
|
|
||||||
|
|
||||||
if ! [[ "$APP" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
if ! [[ "$APP" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
||||||
echo "Error: app name must be alphanumeric (hyphens, dots, underscores allowed)."
|
echo "Error: app name must be alphanumeric (hyphens, dots, underscores allowed)."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -72,6 +69,12 @@ fi
|
|||||||
bw sync --session "${BW_SESSION:-}"
|
bw sync --session "${BW_SESSION:-}"
|
||||||
GITEA_TOKEN=$(bw get notes hantim-new-app-script --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:-}")
|
VULTR_API_KEY=$(bw get notes hantim-vultr-api-key --session "${BW_SESSION:-}")
|
||||||
|
DEPLOY_KEY=$(bw get item hantim-server-deploy --session "${BW_SESSION:-}" | jq -r '.sshKey.privateKey')
|
||||||
|
|
||||||
|
DEPLOY_KEY_FILE=$(mktemp)
|
||||||
|
echo "$DEPLOY_KEY" > "$DEPLOY_KEY_FILE"
|
||||||
|
chmod 600 "$DEPLOY_KEY_FILE"
|
||||||
|
trap "rm -f $DEPLOY_KEY_FILE" EXIT
|
||||||
|
|
||||||
# --- Set up DNS records on Vultr ---
|
# --- Set up DNS records on Vultr ---
|
||||||
|
|
||||||
@@ -259,25 +262,8 @@ NGINX
|
|||||||
|
|
||||||
# --- Issue SSL cert (zero downtime) ---
|
# --- Issue SSL cert (zero downtime) ---
|
||||||
|
|
||||||
echo "==> Writing temporary HTTP-only config on server..."
|
echo "==> Issuing SSL certificate via deploy user..."
|
||||||
TEMP_CONF="server {
|
ssh -o StrictHostKeyChecking=accept-new -i "$DEPLOY_KEY_FILE" deploy@"$SERVER_IP" "cert-$APP"
|
||||||
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 ---
|
# --- Commit and push ---
|
||||||
|
|
||||||
@@ -310,4 +296,4 @@ echo "WARNING: https://www.$APP is not responding after 3 minutes."
|
|||||||
echo " Check that:"
|
echo " Check that:"
|
||||||
echo " - DNS for $APP and www.$APP points to $SERVER_IP"
|
echo " - DNS for $APP and www.$APP points to $SERVER_IP"
|
||||||
echo " - The build completed: $GITEA_URL/$GITEA_ORG/$APP/actions"
|
echo " - The build completed: $GITEA_URL/$GITEA_ORG/$APP/actions"
|
||||||
echo " - The container is running: ssh $SSH_TARGET sudo docker ps"
|
echo " - The container is running on the server: docker ps"
|
||||||
|
|||||||
Reference in New Issue
Block a user