add nginx and cert subcommands to service.sh
This commit is contained in:
+1
-1
@@ -92,7 +92,7 @@ parses it to determine the action:
|
|||||||
|
|
||||||
- `deploy-<app>` — deploy a specific app
|
- `deploy-<app>` — deploy a specific app
|
||||||
- `deploy-nginx` — deploy nginx (fails if certs are missing)
|
- `deploy-nginx` — deploy nginx (fails if certs are missing)
|
||||||
- `cert-<domain>` — issue a cert only (via `certbot --webroot`, zero downtime)
|
- `cert-<domain>` — issue a cert only (via `certbot --webroot`, zero downtime; subdomains like `garage.hantim.net` skip the `www.` variant)
|
||||||
- `provision` — run `configure.sh`
|
- `provision` — run `configure.sh`
|
||||||
|
|
||||||
## SSL certificate handling
|
## SSL certificate handling
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ for technical deep-dives, USECASES.md for expected behaviors.
|
|||||||
- `scripts/configure.sh` -- Idempotent server config (firewall, certbot, start services; CI-safe)
|
- `scripts/configure.sh` -- Idempotent server config (firewall, certbot, start services; CI-safe)
|
||||||
- `scripts/deploy.sh` -- SSH-triggered deploy + cert issuance + provision (deploy-*, cert-*, provision commands)
|
- `scripts/deploy.sh` -- SSH-triggered deploy + cert issuance + provision (deploy-*, cert-*, provision commands)
|
||||||
- `tools/app.sh` -- App provisioning by subcommand (dns, repo, files, cert, garage, build, verify, all)
|
- `tools/app.sh` -- App provisioning by subcommand (dns, repo, files, cert, garage, build, verify, all)
|
||||||
- `tools/service.sh` -- Manage Docker services by subcommand (dns, files, all)
|
- `tools/service.sh` -- Manage Docker services by subcommand (dns, files, nginx, cert, all)
|
||||||
- `tools/remove-app.sh` -- Remove a static site (local files + Gitea repo)
|
- `tools/remove-app.sh` -- Remove a static site (local files + Gitea repo)
|
||||||
- `docker/nginx/conf.d/` -- Per-app nginx server blocks
|
- `docker/nginx/conf.d/` -- Per-app nginx server blocks
|
||||||
- `docker/<domain>/compose.yml` -- Per-app compose files
|
- `docker/<domain>/compose.yml` -- Per-app compose files
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ scripts/
|
|||||||
deploy.sh # Deploy + provision (called via SSH)
|
deploy.sh # Deploy + provision (called via SSH)
|
||||||
tools/
|
tools/
|
||||||
app.sh # Add a new static site (run from dev machine)
|
app.sh # Add a new static site (run from dev machine)
|
||||||
service.sh # Manage Docker services by subcommand (dns, files, all)
|
service.sh # Manage Docker services by subcommand (dns, files, nginx, cert, all)
|
||||||
remove-app.sh # Remove a static site (run from dev machine)
|
remove-app.sh # Remove a static site (run from dev machine)
|
||||||
docker/
|
docker/
|
||||||
nginx/ # Reverse proxy (nginx:alpine) + SSL config
|
nginx/ # Reverse proxy (nginx:alpine) + SSL config
|
||||||
|
|||||||
+14
-1
@@ -89,7 +89,20 @@ for stateless static sites.
|
|||||||
- Existing certs skipped
|
- Existing certs skipped
|
||||||
- Services restarted
|
- Services restarted
|
||||||
|
|
||||||
## 8. Upload media files
|
## 8. Add a new Docker service
|
||||||
|
|
||||||
|
**Trigger:** `./tools/service.sh all <name> <port>` from dev machine.
|
||||||
|
|
||||||
|
**Prerequisites:** `bws`/`jq`/`dig` installed.
|
||||||
|
|
||||||
|
**Expected outcome:**
|
||||||
|
- DNS A record created for `<name>.hantim.net`
|
||||||
|
- Compose file and deploy workflow scaffolded
|
||||||
|
- Nginx conf created proxying `<name>.hantim.net` to port `<port>`
|
||||||
|
- SSL cert issued for `<name>.hantim.net` (no www variant)
|
||||||
|
- Service ready for customization (edit compose.yml image, add `.env.keys` if needed)
|
||||||
|
|
||||||
|
## 9. Upload media files
|
||||||
|
|
||||||
**Trigger:** `aws s3 cp` or `aws s3 sync` to `s3://example.com/`.
|
**Trigger:** `aws s3 cp` or `aws s3 sync` to `s3://example.com/`.
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -6,12 +6,21 @@ CMD="${SSH_ORIGINAL_COMMAND:-${1:-}}"
|
|||||||
if [[ "$CMD" =~ ^cert-[a-zA-Z0-9._-]+$ ]]; then
|
if [[ "$CMD" =~ ^cert-[a-zA-Z0-9._-]+$ ]]; then
|
||||||
APP="${CMD#cert-}"
|
APP="${CMD#cert-}"
|
||||||
|
|
||||||
|
# Subdomains (e.g., garage.hantim.net) don't have www variants
|
||||||
|
if [[ "$APP" == *.*.* ]]; then
|
||||||
|
CERT_DOMAINS="-d $APP"
|
||||||
|
SERVER_NAMES="$APP"
|
||||||
|
else
|
||||||
|
CERT_DOMAINS="-d $APP -d www.$APP"
|
||||||
|
SERVER_NAMES="$APP www.$APP"
|
||||||
|
fi
|
||||||
|
|
||||||
# Write temporary HTTP-only config so nginx can serve ACME challenges
|
# Write temporary HTTP-only config so nginx can serve ACME challenges
|
||||||
cat > "/opt/hantim/docker/nginx/conf.d/$APP.conf" <<NGINXCONF
|
cat > "/opt/hantim/docker/nginx/conf.d/$APP.conf" <<NGINXCONF
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name $APP www.$APP;
|
server_name $SERVER_NAMES;
|
||||||
|
|
||||||
location /.well-known/acme-challenge/ {
|
location /.well-known/acme-challenge/ {
|
||||||
root /var/www/certbot;
|
root /var/www/certbot;
|
||||||
@@ -27,7 +36,7 @@ NGINXCONF
|
|||||||
docker exec nginx nginx -t && docker exec nginx nginx -s reload
|
docker exec nginx nginx -t && docker exec nginx nginx -s reload
|
||||||
certbot certonly --webroot -w /opt/hantim/docker/nginx/certbot/www \
|
certbot certonly --webroot -w /opt/hantim/docker/nginx/certbot/www \
|
||||||
--non-interactive --agree-tos -m timothykim@fastmail.fm \
|
--non-interactive --agree-tos -m timothykim@fastmail.fm \
|
||||||
--cert-name "$APP" -d "$APP" -d "www.$APP"
|
--cert-name "$APP" $CERT_DOMAINS
|
||||||
git -C /opt/hantim restore "docker/nginx/conf.d/$APP.conf" 2>/dev/null || rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf"
|
git -C /opt/hantim restore "docker/nginx/conf.d/$APP.conf" 2>/dev/null || rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
docker exec nginx nginx -t && docker exec nginx nginx -s reload
|
docker exec nginx nginx -t && docker exec nginx nginx -s reload
|
||||||
|
|||||||
+107
-4
@@ -7,14 +7,18 @@ usage() {
|
|||||||
echo "Commands:"
|
echo "Commands:"
|
||||||
echo " dns <name> Create A record for <name>.hantim.net"
|
echo " dns <name> Create A record for <name>.hantim.net"
|
||||||
echo " files <name> Create compose.yml and deploy workflow"
|
echo " files <name> Create compose.yml and deploy workflow"
|
||||||
echo " all <name> Run dns + files"
|
echo " nginx <name> <port> Create nginx conf for <name>.hantim.net"
|
||||||
|
echo " cert <name> Issue SSL certificate for <name>.hantim.net"
|
||||||
|
echo " all <name> <port> Run dns + files + nginx + cert"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
echo " ./tools/service.sh files garage"
|
echo " ./tools/service.sh files garage"
|
||||||
echo " ./tools/service.sh dns garage # garage.hantim.net -> server IP"
|
echo " ./tools/service.sh dns garage # garage.hantim.net -> server IP"
|
||||||
echo " ./tools/service.sh all garage"
|
echo " ./tools/service.sh nginx garage 3903 # create nginx conf"
|
||||||
|
echo " ./tools/service.sh cert garage # issue SSL cert"
|
||||||
|
echo " ./tools/service.sh all garage 3903"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Requires: bws, jq, dig (for dns)"
|
echo "Requires: bws, jq, dig (for dns/cert)"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +84,25 @@ require_dig() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve_server_ip() {
|
||||||
|
require_dig
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_deploy_key() {
|
||||||
|
require_bws
|
||||||
|
echo "==> Fetching deploy key..."
|
||||||
|
DEPLOY_KEY=$(bws_get "hantim-deploy-ssh-private-key")
|
||||||
|
DEPLOY_KEY_FILE=$(mktemp)
|
||||||
|
echo "$DEPLOY_KEY" > "$DEPLOY_KEY_FILE"
|
||||||
|
chmod 600 "$DEPLOY_KEY_FILE"
|
||||||
|
trap "rm -f $DEPLOY_KEY_FILE" EXIT
|
||||||
|
}
|
||||||
|
|
||||||
validate_name() {
|
validate_name() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
if [ -z "$name" ]; then
|
if [ -z "$name" ]; then
|
||||||
@@ -228,11 +251,73 @@ OUTER
|
|||||||
echo " configure.sh will generate the .env file on the server automatically."
|
echo " configure.sh will generate the .env file on the server automatically."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd_nginx() {
|
||||||
|
local name="$1"
|
||||||
|
local port="$2"
|
||||||
|
local fqdn="$name.hantim.net"
|
||||||
|
|
||||||
|
if [ -e "$REPO_ROOT/docker/nginx/conf.d/$fqdn.conf" ]; then
|
||||||
|
echo " docker/nginx/conf.d/$fqdn.conf already exists, skipping."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Creating docker/nginx/conf.d/$fqdn.conf..."
|
||||||
|
cat > "$REPO_ROOT/docker/nginx/conf.d/$fqdn.conf" <<NGINX
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name $fqdn;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$fqdn\$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
http2 on;
|
||||||
|
server_name $fqdn;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/$fqdn/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/$fqdn/privkey.pem;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/security-headers.inc;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://$name:$port;
|
||||||
|
proxy_set_header Host \$host;
|
||||||
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINX
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_cert() {
|
||||||
|
local name="$1"
|
||||||
|
local fqdn="$name.hantim.net"
|
||||||
|
|
||||||
|
resolve_server_ip
|
||||||
|
setup_deploy_key
|
||||||
|
|
||||||
|
echo "==> Issuing SSL certificate for $fqdn via deploy user..."
|
||||||
|
ssh -o StrictHostKeyChecking=accept-new -i "$DEPLOY_KEY_FILE" deploy@"$SERVER_IP" "cert-$fqdn"
|
||||||
|
echo " Certificate issued."
|
||||||
|
}
|
||||||
|
|
||||||
cmd_all() {
|
cmd_all() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
|
local port="$2"
|
||||||
|
|
||||||
cmd_dns "$name"
|
cmd_dns "$name"
|
||||||
cmd_files "$name"
|
cmd_files "$name"
|
||||||
|
cmd_nginx "$name" "$port"
|
||||||
|
cmd_cert "$name"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Dispatch ---
|
# --- Dispatch ---
|
||||||
@@ -246,9 +331,27 @@ case "$COMMAND" in
|
|||||||
validate_name "${2:-}"
|
validate_name "${2:-}"
|
||||||
cmd_files "${2:-}"
|
cmd_files "${2:-}"
|
||||||
;;
|
;;
|
||||||
|
nginx)
|
||||||
|
validate_name "${2:-}"
|
||||||
|
if [ -z "${3:-}" ]; then
|
||||||
|
echo "Error: port is required for nginx command."
|
||||||
|
echo " Usage: ./tools/service.sh nginx <name> <port>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmd_nginx "${2:-}" "${3:-}"
|
||||||
|
;;
|
||||||
|
cert)
|
||||||
|
validate_name "${2:-}"
|
||||||
|
cmd_cert "${2:-}"
|
||||||
|
;;
|
||||||
all)
|
all)
|
||||||
validate_name "${2:-}"
|
validate_name "${2:-}"
|
||||||
cmd_all "${2:-}"
|
if [ -z "${3:-}" ]; then
|
||||||
|
echo "Error: port is required for all command."
|
||||||
|
echo " Usage: ./tools/service.sh all <name> <port>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmd_all "${2:-}" "${3:-}"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown command: $COMMAND"
|
echo "Unknown command: $COMMAND"
|
||||||
|
|||||||
Reference in New Issue
Block a user