#!/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 performs EXACTLY what .gitea/workflows/deploy.yml performs: the same guard, # the same three sync passes in the same order with the same cache headers, and # the same invalidation. At this scale the pipeline changes only how a deploy is # TRIGGERED, not what it does — so this is a fallback, not a lesser path. Any # change to one must be made to the other. # # 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 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" 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 "==> 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))"