audit fixes: error handling, docs consistency, bucket idempotency
Provision server / provision (push) Successful in 12s

This commit is contained in:
2026-03-20 11:28:36 -04:00
parent 6b1e78350f
commit be1ad6b456
8 changed files with 61 additions and 13 deletions
+8 -1
View File
@@ -31,6 +31,11 @@ The `bws` CLI fetches secrets using machine account access tokens.
- `hantim-deploy-ssh-public-key` -- deploy user SSH public key - `hantim-deploy-ssh-public-key` -- deploy user SSH public key
- `hantim-new-app-script` -- Gitea API token for creating repos - `hantim-new-app-script` -- Gitea API token for creating repos
- `hantim-vultr-api-key` -- Vultr API key for DNS management - `hantim-vultr-api-key` -- Vultr API key for DNS management
- `hantim-garage-rpc-secret` -- Garage cluster RPC secret
- `hantim-garage-argento-node-id` -- Argento's Garage node ID + address
- `hantim-garage-admin-token` -- Garage admin API token
- `hantim-garage-media-key-id` -- S3 access key ID for media uploads
- `hantim-garage-media-secret-key` -- S3 secret key for media uploads
Machine accounts: Machine accounts:
- `hantim-server` -- used by `bootstrap.sh` and `configure.sh` on the server - `hantim-server` -- used by `bootstrap.sh` and `configure.sh` on the server
@@ -69,7 +74,9 @@ docker/nginx/ # nginx reverse proxy
docker/<domain>/compose.yml # Per-app compose files docker/<domain>/compose.yml # Per-app compose files
docker/garage/ # Garage S3-compatible object storage docker/garage/ # Garage S3-compatible object storage
compose.yml compose.yml
garage.toml.template Dockerfile
garage.toml
.env.keys
.gitea/workflows/deploy-<app>.yml # Per-app deploy workflows .gitea/workflows/deploy-<app>.yml # Per-app deploy workflows
.gitea/workflows/provision.yml # Triggers configure.sh on setup changes .gitea/workflows/provision.yml # Triggers configure.sh on setup changes
``` ```
+5
View File
@@ -38,6 +38,11 @@ Bitwarden Secrets Manager (project: `hantim`, fetched via `bws` CLI):
- `hantim-deploy-ssh-public-key` -- deploy user SSH public key (used by bootstrap.sh) - `hantim-deploy-ssh-public-key` -- deploy user SSH public key (used by bootstrap.sh)
- `hantim-new-app-script` -- Gitea API token for creating repos (used by new-app.sh) - `hantim-new-app-script` -- Gitea API token for creating repos (used by new-app.sh)
- `hantim-vultr-api-key` -- Vultr API key for DNS management (used by new-app.sh) - `hantim-vultr-api-key` -- Vultr API key for DNS management (used by new-app.sh)
- `hantim-garage-rpc-secret` -- Garage cluster RPC secret (used by deploy.sh)
- `hantim-garage-argento-node-id` -- Argento's Garage node ID + address (used by deploy.sh)
- `hantim-garage-admin-token` -- Garage admin API token (used by deploy.sh, new-app.sh)
- `hantim-garage-media-key-id` -- S3 access key ID for media uploads (used by new-app.sh)
- `hantim-garage-media-secret-key` -- S3 secret key for media uploads (used by aws CLI)
Machine accounts: Machine accounts:
- `hantim-server` -- access token stored at `/etc/bws-token` on hantim - `hantim-server` -- access token stored at `/etc/bws-token` on hantim
+3 -2
View File
@@ -136,8 +136,9 @@ Media files (images, videos) are stored in Garage and served via nginx at
```bash ```bash
# Configure AWS CLI (one-time) # Configure AWS CLI (one-time)
aws configure # key ID, secret key from bws, region: garage aws configure # key ID, secret key from bws, region: garage, output: json
aws configure set default.endpoint_url https://s3.hantim.net # Then add to ~/.aws/config under [default]:
# endpoint_url = https://s3.hantim.net
# Upload files # Upload files
aws s3 cp photo.jpg s3://example.com/photo.jpg aws s3 cp photo.jpg s3://example.com/photo.jpg
+17 -2
View File
@@ -1,5 +1,19 @@
# Use Cases # Use Cases
## 0. Initial Bitwarden Secrets Manager setup
One-time setup before anything else works. Create the secrets project and
machine accounts in Bitwarden Secrets Manager.
1. Create a Bitwarden organization (free tier)
2. Enable Secrets Manager for the organization
3. Create a project named `hantim`
4. Create all required secrets (see README.md Secrets table for the full list)
5. Create machine accounts:
- `hantim-server` — grant access to the `hantim` project
- `hantim-ci` — reserved for future CI use
6. Generate access tokens for each machine account
## 1. Provision a new server ## 1. Provision a new server
Set up a fresh Rocky Linux 9 server from scratch. Set up a fresh Rocky Linux 9 server from scratch.
@@ -157,8 +171,9 @@ on each domain.
```bash ```bash
# One-time setup # One-time setup
aws configure # key ID + secret from bws, region: garage aws configure # key ID + secret from bws, region: garage, output: json
aws configure set default.endpoint_url https://s3.hantim.net # Then add to ~/.aws/config under [default]:
# endpoint_url = https://s3.hantim.net
# Upload a single file # Upload a single file
aws s3 cp photo.jpg s3://example.com/photo.jpg aws s3 cp photo.jpg s3://example.com/photo.jpg
+2 -2
View File
@@ -18,8 +18,8 @@ BWS_TOKEN_FILE="/etc/bws-token"
BWS_VERSION="2.0.0" BWS_VERSION="2.0.0"
# Secret IDs (Bitwarden Secrets Manager) # Secret IDs (Bitwarden Secrets Manager)
SECRET_REGISTRY_TOKEN="43f4a77d-7bb4-49ad-8044-b411016c5c6e" SECRET_REGISTRY_TOKEN="43f4a77d-7bb4-49ad-8044-b411016c5c6e" # hantim-ci-registry-push
SECRET_DEPLOY_PUBKEY="945760dd-90e2-46e5-98ad-b411016e3f3b" SECRET_DEPLOY_PUBKEY="945760dd-90e2-46e5-98ad-b411016e3f3b" # hantim-deploy-ssh-public-key
# --- Prerequisites --- # --- Prerequisites ---
+1 -1
View File
@@ -31,7 +31,7 @@ chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
if systemctl list-unit-files certbot-renew.timer 2>/dev/null | grep -q certbot-renew; then if systemctl list-unit-files certbot-renew.timer 2>/dev/null | grep -q certbot-renew; then
systemctl enable --now certbot-renew.timer systemctl enable --now certbot-renew.timer
else else
echo "0 3 * * * root certbot renew --quiet" > /etc/cron.d/certbot-renew echo "0 3 * * * root certbot renew --quiet --deploy-hook 'docker exec nginx nginx -s reload' 2>&1 | logger -t certbot" > /etc/cron.d/certbot-renew
fi fi
echo "==> Deploying services..." echo "==> Deploying services..."
+5 -2
View File
@@ -23,11 +23,13 @@ server {
} }
NGINXCONF NGINXCONF
trap 'rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf"' EXIT
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 --register-unsafely-without-email \ --non-interactive --agree-tos --register-unsafely-without-email \
--cert-name "$APP" -d "$APP" -d "www.$APP" --cert-name "$APP" -d "$APP" -d "www.$APP"
rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf" rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf"
trap - EXIT
echo "Certificate issued for $APP" echo "Certificate issued for $APP"
exit 0 exit 0
fi fi
@@ -59,8 +61,9 @@ if [ -f "docker/$APP/.env.keys" ] && [ -f /etc/bws-token ]; then
secret_key="${line#*=}" secret_key="${line#*=}"
value=$(bws secret list | jq -r --arg key "$secret_key" '.[] | select(.key == $key) | .value') value=$(bws secret list | jq -r --arg key "$secret_key" '.[] | select(.key == $key) | .value')
if [ -z "$value" ]; then if [ -z "$value" ]; then
echo "WARNING: Secret '$secret_key' not found in bws, skipping." echo "ERROR: Secret '$secret_key' not found in bws."
continue echo " Check that the secret exists and the machine account has access."
exit 1
fi fi
env_content="${env_content}${var_name}=${value}"$'\n' env_content="${env_content}${var_name}=${value}"$'\n'
done < "docker/$APP/.env.keys" done < "docker/$APP/.env.keys"
+18 -1
View File
@@ -204,6 +204,15 @@ done
# --- Create Gitea repo from template --- # --- Create Gitea repo from template ---
REPO_CHECK=$(curl -s -o /dev/null -w "%{http_code}" \
"$GITEA_URL/api/v1/repos/$GITEA_ORG/$APP" \
-H "Authorization: token $GITEA_TOKEN")
if [ "$REPO_CHECK" = "200" ]; then
echo "Error: Gitea repo $GITEA_ORG/$APP already exists."
echo " $GITEA_URL/$GITEA_ORG/$APP"
exit 1
fi
echo "Creating Gitea repo $GITEA_ORG/$APP from template $TEMPLATE_REPO..." echo "Creating Gitea repo $GITEA_ORG/$APP from template $TEMPLATE_REPO..."
HTTP_CODE=$(curl -s -o /tmp/new-app-response.json -w "%{http_code}" \ 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" \ -X POST "$GITEA_URL/api/v1/repos/$GITEA_ORG/$TEMPLATE_REPO/generate" \
@@ -343,9 +352,17 @@ BUCKET_RESP=$(curl -s -X POST "$GARAGE_API/v2/CreateBucket" \
BUCKET_ID=$(echo "$BUCKET_RESP" | jq -r '.id') BUCKET_ID=$(echo "$BUCKET_RESP" | jq -r '.id')
if [ -z "$BUCKET_ID" ] || [ "$BUCKET_ID" = "null" ]; then if [ -z "$BUCKET_ID" ] || [ "$BUCKET_ID" = "null" ]; then
echo "Error: Failed to create bucket" # Bucket may already exist — try to look it up
BUCKET_ID=$(curl -s -X POST "$GARAGE_API/v2/GetBucketInfo" \
-H "$GARAGE_AUTH" \
-H "Content-Type: application/json" \
-d "{\"globalAlias\": \"$APP\"}" | jq -r '.id')
if [ -z "$BUCKET_ID" ] || [ "$BUCKET_ID" = "null" ]; then
echo "Error: Failed to create or find bucket for $APP"
echo "$BUCKET_RESP" echo "$BUCKET_RESP"
exit 1 exit 1
fi
echo " Bucket already exists, continuing..."
fi fi
echo " Allowing media-key access..." echo " Allowing media-key access..."