#!/usr/bin/env bash # # Local deploy — the fallback while Gitea Actions is unavailable. # # Gitea Actions needs `[actions] ENABLED = true` in app.ini and a registered # act_runner. The instance is jointly administered, so both depend on a second # administrator (AGENTS.md Q23). Until that lands, this script is how the site # ships. # # It matches .gitea/workflows/deploy.yml on everything that determines what gets # published: the same guard coverage, `npm run check` before the build, the same # three sync passes in the same order with the same cache headers, and the same # invalidation. Any change to one must be made to the other. # # Two deliberate differences: it does not run `npm ci` (your node_modules is # already installed, and CI starts empty), and it refuses to run as user/pouya, # which CI cannot do because CI has no such credential. # # Required environment (values are in AGENTS.md §7 — deliberately not restated # here; §7 is the single source of truth for operational facts): # # AWS_REGION S3_BUCKET CLOUDFRONT_DISTRIBUTION_ID INTAKE_ENDPOINT # # Credentials: use the scoped deploy user. AGENTS.md Q22 records that it does # NOT yet exist. NEVER run this as user/pouya — see AGENTS.md §10. set -euo pipefail # Same six values the workflow guards. Emptiness only — no value is echoed. missing='' [ -n "${AWS_REGION:-}" ] || missing="$missing AWS_REGION" [ -n "${S3_BUCKET:-}" ] || missing="$missing S3_BUCKET" [ -n "${CLOUDFRONT_DISTRIBUTION_ID:-}" ] || missing="$missing CLOUDFRONT_DISTRIBUTION_ID" [ -n "${INTAKE_ENDPOINT:-}" ] || missing="$missing INTAKE_ENDPOINT" [ -n "${AWS_ACCESS_KEY_ID:-}" ] || missing="$missing AWS_ACCESS_KEY_ID" [ -n "${AWS_SECRET_ACCESS_KEY:-}" ] || missing="$missing AWS_SECRET_ACCESS_KEY" if [ -n "$missing" ]; then echo "Not set:$missing" >&2 echo >&2 echo "Values are in AGENTS.md §7. An empty INTAKE_ENDPOINT does not fail the" >&2 echo "build — it ships a live contact form posting to nothing." >&2 exit 1 fi export AWS_DEFAULT_REGION="$AWS_REGION" echo "==> Identity check" caller=$(aws sts get-caller-identity --query Arn --output text) echo " $caller" case "$caller" in *:user/pouya) echo >&2 echo "REFUSING: that is the broadly-permissioned personal user." >&2 echo "AGENTS.md §10 — never use user/pouya to deploy. Use the scoped" >&2 echo "deploy user (Q22: not yet created)." >&2 exit 1 ;; esac echo "==> Type and template check" npm run check echo "==> Build" PUBLIC_SITE_URL="https://adr.smlcompany.ca" \ PUBLIC_INTAKE_ENDPOINT="$INTAKE_ENDPOINT" \ PUBLIC_BOOKING_URL="${BOOKING_URL:-}" \ npm run build echo "==> Pass 1/3 — hashed assets and fonts (immutable)" aws s3 sync ./dist "s3://${S3_BUCKET}" \ --exclude "*" \ --include "_astro/*" --include "fonts/*" \ --cache-control "public, max-age=31536000, immutable" \ --no-progress echo "==> Pass 2/3 — images" aws s3 sync ./dist "s3://${S3_BUCKET}" \ --exclude "*" \ --include "*.avif" --include "*.webp" --include "*.jpg" \ --include "*.png" --include "*.svg" \ --cache-control "public, max-age=604800" \ --no-progress echo "==> Pass 3/3 — HTML and the rest (must-revalidate, --delete)" aws s3 sync ./dist "s3://${S3_BUCKET}" \ --exclude "_astro/*" --exclude "fonts/*" \ --cache-control "public, max-age=0, must-revalidate" \ --delete --no-progress echo "==> Invalidate CloudFront" aws cloudfront create-invalidation \ --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" \ --paths "/*" >/dev/null echo "==> Deployed to https://adr.smlcompany.ca ($(git rev-parse --short HEAD))"