Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ docker-compose*
README.md
LICENSE

# Local-only dirs that must never enter any image build context.
.worktrees
**/.terraform
**/*.tfstate*
coverage

*.swp

/aws
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ jobs:
with:
node-version: 24
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: pnpm install --frozen-lockfile
Comment thread
pstaylor-patrick marked this conversation as resolved.
- run: pnpm format

Expand All @@ -55,6 +58,9 @@ jobs:
with:
node-version: 24
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: pnpm install --frozen-lockfile
- run: pnpm lint

Expand All @@ -67,6 +73,9 @@ jobs:
with:
node-version: 24
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck

Expand All @@ -79,9 +88,41 @@ jobs:
with:
node-version: 24
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: pnpm install --frozen-lockfile
- run: pnpm build

test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: f3_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: pnpm install --frozen-lockfile
- run: pnpm test

test-coverage:
runs-on: ubuntu-latest
services:
Expand All @@ -105,5 +146,8 @@ jobs:
with:
node-version: 24
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: pnpm install --frozen-lockfile
- run: pnpm test:coverage
80 changes: 80 additions & 0 deletions .github/workflows/deploy-redirect-server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Deploy Redirect Server

# Redirect tier (Go `redirectd`): build the image, push to Artifact Registry,
# and roll the GCE VM so it pulls and runs the new image on boot.
#
# Deploy model: this app has a SINGLE production environment. Pushing to `dev`
# (the monorepo's integration/default branch) IS the prod deploy — there is no
# separate staging for redirect. Path-filtered so only changes that affect the
# redirect binary/container trigger a roll (not web-only or docs changes).
#
# ⚠️ SANDBOX (v1): targets Patrick's PERSONAL, self-funded GCP project
# `f3-redirects`. Long-term this pivots to an F3 Nation org-owned/funded project
# (provisioned by Tackle); the WIF provider, deployer SA, and project below are
# personal-sandbox identifiers that change at that cutover.
#
# Auth is keyless via Workload Identity Federation — no long-lived SA keys.

on:
push:
branches: [dev]
paths:
- "apps/redirect/server/**"
- "apps/redirect/shared/**"
- ".github/workflows/deploy-redirect-server.yml"
workflow_dispatch:
Comment thread
coderabbitai[bot] marked this conversation as resolved.

concurrency:
group: deploy-redirect-server
cancel-in-progress: false

permissions:
contents: read
id-token: write

env:
PROJECT: f3-redirects
REGION: us-central1
ZONE: us-central1-a
REPO: redirect
INSTANCE: redirect-vm
WIF_PROVIDER: projects/355149658273/locations/global/workloadIdentityPools/github-pool/providers/github
DEPLOYER_SA: redirect-deployer@f3-redirects.iam.gserviceaccount.com

jobs:
deploy:
runs-on: ubuntu-latest
# Production deploys come only from dev. workflow_dispatch can target any
# ref, so guard the job to block a manual deploy of unmerged code to prod.
if: github.ref == 'refs/heads/dev'
steps:
- uses: actions/checkout@v4

- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}

- uses: google-github-actions/setup-gcloud@v2
Comment thread
pstaylor-patrick marked this conversation as resolved.

- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet

- name: Build & push image
run: |
IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/redirectd"
# --provenance=false: emit a plain single-arch manifest. The buildx
# default OCI index (with an attestation manifest) is not reliably
# pullable by the Container-Optimized OS docker on the VM.
docker build --provenance=false --sbom=false \
--file apps/redirect/server/Dockerfile \
-t "${IMAGE}:${GITHUB_SHA}" -t "${IMAGE}:latest" \
apps/redirect/server
docker push "${IMAGE}:${GITHUB_SHA}"
docker push "${IMAGE}:latest"

- name: Roll the VM (re-runs startup script, pulls :latest)
run: |
gcloud compute instances reset "${INSTANCE}" --zone "${ZONE}" --project "${PROJECT}"
echo "Reset ${INSTANCE}; it will pull the new image on boot."
Comment thread
pstaylor-patrick marked this conversation as resolved.
104 changes: 104 additions & 0 deletions .github/workflows/deploy-redirect-web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Deploy Redirect Web

# Self-serve admin web (Next.js, `f3-redirect-web`): build the turbo-pruned
# container and deploy to Cloud Run. The Go redirect tier reverse-proxies the
# admin hostname to this service.
#
# Deploy model: SINGLE production environment. Pushing to `dev` (the monorepo's
# integration/default branch) IS the prod deploy — no separate staging for
# redirect. Path-filtered to web changes.
#
# ⚠️ SANDBOX (v1): targets Patrick's PERSONAL, self-funded GCP project
# `f3-redirects`. Long-term pivots to an F3 Nation org-owned/funded project
# (provisioned by Tackle), and the interim Cloud SQL database is replaced by an
# app-specific schema + service principal in the F3PROD data warehouse (the
# Codex/PaxVault pattern). Neon Postgres is off the table.
#
# Keyless auth via Workload Identity Federation.

on:
push:
branches: [dev]
paths:
- "apps/redirect/web/**"
- ".github/workflows/deploy-redirect-web.yml"
workflow_dispatch:

concurrency:
group: deploy-redirect-web
cancel-in-progress: false

permissions:
contents: read
id-token: write

env:
PROJECT: f3-redirects
REGION: us-central1
AR_REPO: redirect
# NOTE: the Artifact Registry image and the Cloud Run service are both named
# `f3redirect-web` (no hyphen) — matching the existing deployed resources so
# this workflow updates them in place. (The pnpm package is `f3-redirect-web`;
# the turbo --filter below uses that package name.)
IMAGE_NAME: f3redirect-web
SERVICE_NAME: f3redirect-web
WIF_PROVIDER: projects/355149658273/locations/global/workloadIdentityPools/github-pool/providers/github
DEPLOYER_SA: redirect-deployer@f3-redirects.iam.gserviceaccount.com

jobs:
build:
runs-on: ubuntu-latest
# Production deploys come only from dev. workflow_dispatch can target any
# ref, so guard the entry job (deploy needs: build) to block a manual
# deploy of unmerged code to prod.
if: github.ref == 'refs/heads/dev'
outputs:
image: ${{ steps.meta.outputs.image }}
steps:
- uses: actions/checkout@v4

- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}

- uses: google-github-actions/setup-gcloud@v2
Comment thread
pstaylor-patrick marked this conversation as resolved.

- name: Authorize Docker to Artifact Registry
run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet

- name: Resolve image reference (immutable by commit SHA)
id: meta
run: |
IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/${AR_REPO}/${IMAGE_NAME}:${GITHUB_SHA}"
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"

- name: Build and push Docker image
run: |
docker build \
--file apps/redirect/web/Dockerfile \
--tag "${{ steps.meta.outputs.image }}" \
.
Comment thread
pstaylor-patrick marked this conversation as resolved.
docker push "${{ steps.meta.outputs.image }}"

deploy:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOYER_SA }}

- name: Deploy to Cloud Run
uses: google-github-actions/deploy-cloudrun@v2
with:
service: ${{ env.SERVICE_NAME }}
image: ${{ needs.build.outputs.image }}
region: ${{ env.REGION }}
project_id: ${{ env.PROJECT }}
# Env vars (DATABASE_URL, BETTER_AUTH_SECRET, GCS bucket, etc.) are
# set on the Cloud Run service out-of-band, not in this workflow.
109 changes: 109 additions & 0 deletions .github/workflows/redirect-terraform-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: redirect Terraform Drift

# Enforces the zero-drift rule for the f3-redirects GCP project: the committed
# Terraform under apps/redirect/infra/terraform is the source of truth. This
# workflow runs `terraform plan` and fails if live infrastructure has drifted
# from the code — daily (catch console drift) and on every PR that touches the
# Terraform (catch drift before merge).
#
# Auth is keyless via the f3-redirects project's own Workload Identity pool
# (github-pool/github), whose attribute condition was widened in ci.tf to allow
# this repo (F3-Nation/f3-nation). It impersonates the read-only
# github-actions-ci@f3-redirects service account.
#
# NOTE: f3-redirects is currently Patrick's personal v1 sandbox. When the
# redirect app pivots to an F3 Nation org-owned project, update the project id,
# WIF provider, service account, and the TF_VAR_* values below.

on:
schedule:
- cron: "17 13 * * *" # daily ~7-8am Central (offset from region-pages' :00)
pull_request:
paths:
- "apps/redirect/infra/terraform/**"
- ".github/workflows/redirect-terraform-drift.yml"
workflow_dispatch:

concurrency:
group: redirect-tf-drift
cancel-in-progress: false

permissions:
contents: read
id-token: write
pull-requests: write

jobs:
drift:
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/redirect/infra/terraform
env:
# Non-secret values that must match the live VM startup-script metadata so
# `plan` reports no spurious drift. project has no variable default
# (removed to prevent unparameterized applies from targeting a live
# environment), so it is supplied explicitly here. image_tag, region,
# zone, machine_type, config_object, and cert_prefix defaults match live.
TF_VAR_project: "f3-redirects"
TF_VAR_acme_email: "patrick@pstaylor.net"
TF_VAR_admin_host: "admin.f3regions.com"
TF_VAR_admin_upstream: "https://f3redirect-web-355149658273.us-central1.run.app"
steps:
- uses: actions/checkout@v4

- name: Authenticate to GCP (Workload Identity Federation)
uses: google-github-actions/auth@v2
with:
workload_identity_provider: "projects/355149658273/locations/global/workloadIdentityPools/github-pool/providers/github"
service_account: "github-actions-ci@f3-redirects.iam.gserviceaccount.com"

- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2

- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.15.3"

- name: Terraform init
run: terraform init -input=false

- name: Terraform plan (detect drift)
id: plan
run: |
set +e
terraform plan -input=false -lock=false -no-color -detailed-exitcode
code=$?
set -e
echo "exitcode=$code" >> "$GITHUB_OUTPUT"
# 0 = in sync, 2 = drift detected, 1 = error
if [ "$code" = "1" ]; then
echo "::error::terraform plan failed."
exit 1
elif [ "$code" = "2" ]; then
echo "::error::Drift detected — f3-redirects infrastructure no longer matches Terraform. Reconcile by applying the committed config or importing the change."
exit 2
else
echo "No drift — f3-redirects matches Terraform."
fi

- name: Comment drift result on PR
# On fork PRs GitHub forces GITHUB_TOKEN to read-only, so createComment
# would 403 and fail the job. Only comment for same-repo PRs.
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && always()
uses: actions/github-script@v7
with:
script: |
const code = '${{ steps.plan.outputs.exitcode }}';
const body = code === '0'
? '✅ **redirect Terraform drift check:** in sync — live infrastructure matches the committed config.'
: code === '2'
? '⚠️ **redirect Terraform drift check:** drift detected — `terraform plan` shows changes. Reconcile before merge (apply the committed config or import the out-of-band change).'
: '❌ **redirect Terraform drift check:** `terraform plan` errored. See the job logs.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node-linker=hoisted
Loading
Loading