diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index db136ea..ac22520 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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-new-app-script` -- Gitea API token for creating repos - `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: - `hantim-server` -- used by `bootstrap.sh` and `configure.sh` on the server @@ -69,7 +74,9 @@ docker/nginx/ # nginx reverse proxy docker//compose.yml # Per-app compose files docker/garage/ # Garage S3-compatible object storage compose.yml - garage.toml.template + Dockerfile + garage.toml + .env.keys .gitea/workflows/deploy-.yml # Per-app deploy workflows .gitea/workflows/provision.yml # Triggers configure.sh on setup changes ``` diff --git a/CLAUDE.md b/CLAUDE.md index 0f1a391..a125105 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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-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-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: - `hantim-server` -- access token stored at `/etc/bws-token` on hantim diff --git a/README.md b/README.md index 146f92b..6a4a551 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,9 @@ Media files (images, videos) are stored in Garage and served via nginx at ```bash # Configure AWS CLI (one-time) -aws configure # key ID, secret key from bws, region: garage -aws configure set default.endpoint_url https://s3.hantim.net +aws configure # key ID, secret key from bws, region: garage, output: json +# Then add to ~/.aws/config under [default]: +# endpoint_url = https://s3.hantim.net # Upload files aws s3 cp photo.jpg s3://example.com/photo.jpg diff --git a/USECASES.md b/USECASES.md index d2de64d..125bfb9 100644 --- a/USECASES.md +++ b/USECASES.md @@ -1,5 +1,19 @@ # 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 Set up a fresh Rocky Linux 9 server from scratch. @@ -157,8 +171,9 @@ on each domain. ```bash # One-time setup -aws configure # key ID + secret from bws, region: garage -aws configure set default.endpoint_url https://s3.hantim.net +aws configure # key ID + secret from bws, region: garage, output: json +# Then add to ~/.aws/config under [default]: +# endpoint_url = https://s3.hantim.net # Upload a single file aws s3 cp photo.jpg s3://example.com/photo.jpg diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 4ef654f..1eb8917 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -18,8 +18,8 @@ BWS_TOKEN_FILE="/etc/bws-token" BWS_VERSION="2.0.0" # Secret IDs (Bitwarden Secrets Manager) -SECRET_REGISTRY_TOKEN="43f4a77d-7bb4-49ad-8044-b411016c5c6e" -SECRET_DEPLOY_PUBKEY="945760dd-90e2-46e5-98ad-b411016e3f3b" +SECRET_REGISTRY_TOKEN="43f4a77d-7bb4-49ad-8044-b411016c5c6e" # hantim-ci-registry-push +SECRET_DEPLOY_PUBKEY="945760dd-90e2-46e5-98ad-b411016e3f3b" # hantim-deploy-ssh-public-key # --- Prerequisites --- diff --git a/scripts/configure.sh b/scripts/configure.sh index 464ae35..a068afe 100755 --- a/scripts/configure.sh +++ b/scripts/configure.sh @@ -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 systemctl enable --now certbot-renew.timer 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 echo "==> Deploying services..." diff --git a/scripts/deploy.sh b/scripts/deploy.sh index f6ef89b..c96889a 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -23,11 +23,13 @@ server { } NGINXCONF + trap 'rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf"' EXIT 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" rm -f "/opt/hantim/docker/nginx/conf.d/$APP.conf" + trap - EXIT echo "Certificate issued for $APP" exit 0 fi @@ -59,8 +61,9 @@ if [ -f "docker/$APP/.env.keys" ] && [ -f /etc/bws-token ]; then secret_key="${line#*=}" value=$(bws secret list | jq -r --arg key "$secret_key" '.[] | select(.key == $key) | .value') if [ -z "$value" ]; then - echo "WARNING: Secret '$secret_key' not found in bws, skipping." - continue + echo "ERROR: Secret '$secret_key' not found in bws." + echo " Check that the secret exists and the machine account has access." + exit 1 fi env_content="${env_content}${var_name}=${value}"$'\n' done < "docker/$APP/.env.keys" diff --git a/tools/new-app.sh b/tools/new-app.sh index fe1ac05..97362b9 100755 --- a/tools/new-app.sh +++ b/tools/new-app.sh @@ -204,6 +204,15 @@ done # --- 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..." 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" \ @@ -343,9 +352,17 @@ BUCKET_RESP=$(curl -s -X POST "$GARAGE_API/v2/CreateBucket" \ BUCKET_ID=$(echo "$BUCKET_RESP" | jq -r '.id') if [ -z "$BUCKET_ID" ] || [ "$BUCKET_ID" = "null" ]; then - echo "Error: Failed to create bucket" - echo "$BUCKET_RESP" - exit 1 + # 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" + exit 1 + fi + echo " Bucket already exists, continuing..." fi echo " Allowing media-key access..."