Skip to content
Merged
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
26 changes: 9 additions & 17 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
name: deploy-staging

# After CI is green on main, deploy every surface to staging. Each
# surface runs through the shared deploy-surface reusable workflow, so
# staging and production share one deploy engine.
# After CI is green on main, deploy every surface to staging through
# the shared deploy-surface reusable workflow (surface=all), so staging
# and production share one deploy engine and the site builds ONCE per
# staging deploy instead of once per surface.
#
# Surfaces deploy independently (matrix, fail-fast disabled): one
# surface failing does not block the others, and any single surface can
# be re-run on its own from the Actions tab.
# Inside deploy-surface the per-surface rsync legs still run as an
# independent matrix (fail-fast disabled): one surface failing does not
# block the others, and a single leg can be re-run on its own from the
# Actions tab without rebuilding.

on:
workflow_run:
Expand All @@ -21,19 +23,9 @@ permissions:
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
strategy:
fail-fast: false
matrix:
surface:
- architecture
- main
- cultures
- plays
- misfits
- writing
uses: ./.github/workflows/deploy-surface.yml
with:
surface: ${{ matrix.surface }}
surface: all
env: staging
# Deploy exactly the commit CI just validated, not the tip of main.
ref: ${{ github.event.workflow_run.head_sha }}
Expand Down
78 changes: 68 additions & 10 deletions .github/workflows/deploy-surface.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
name: deploy-surface

# Reusable single-surface deploy. Builds the site and rsyncs ONE
# surface's dist/ subfolder to its document root for the chosen env.
# Reusable surface deploy: build ONCE, rsync each requested surface's
# dist/ subfolder to its document root for the chosen env.
#
# This is the shared engine behind both the auto-staging deploy (called
# once per surface in a matrix) and the per-surface production tag
# deploys, and it can be run by hand from the Actions tab via
# workflow_dispatch ("deploy my sites independently").
# This is the shared engine behind both the auto-staging deploy (one
# call with surface=all) and the per-surface production tag deploys
# (one call with that surface), and it can be run by hand from the
# Actions tab via workflow_dispatch ("deploy my sites independently",
# or surface=all for a whole release in one click).
#
# Astro has no partial build - every build emits every surface - so the
# expensive part (install, prebuild downloads, build, and on production
# the full baseline gates) runs in ONE job and the per-surface legs are
# artifact-download + rsync only. Beyond the compute saved, this means
# every surface in a deploy ships from literally the same build instead
# of N builds that could drift (e.g. a registry package publishing
# mid-release).
#
# The surface -> document-root mapping below is the single source of
# truth; it mirrors the host layout documented in docs/deploy-targets.md.
Expand All @@ -15,10 +24,12 @@ on:
workflow_dispatch:
inputs:
surface:
description: Which surface to deploy
description: Which surface to deploy (all = every surface, one build)
type: choice
required: true
default: all
options:
- all
- architecture
- main
- plays
Expand All @@ -36,6 +47,7 @@ on:
workflow_call:
inputs:
surface:
description: A surface name, or "all" for every surface
required: true
type: string
env:
Expand All @@ -52,7 +64,24 @@ permissions:
packages: read

jobs:
deploy:
resolve:
runs-on: ubuntu-latest
outputs:
surfaces: ${{ steps.s.outputs.surfaces }}
steps:
- id: s
run: |
surface="${{ inputs.surface }}"
case "$surface" in
all)
echo 'surfaces=["architecture","main","cultures","plays","misfits","writing"]' >> "$GITHUB_OUTPUT" ;;
architecture|main|cultures|plays|misfits|writing)
echo "surfaces=[\"$surface\"]" >> "$GITHUB_OUTPUT" ;;
*)
echo "::error::unknown surface '$surface' (expected a surface name or 'all')"; exit 1 ;;
esac

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -75,10 +104,39 @@ jobs:
# to staging.kaihacks.ai when DEPLOY_ENV=staging.
DEPLOY_ENV=staging npm run build
fi
- name: upload dist
uses: actions/upload-artifact@v4
with:
name: dist-${{ inputs.env }}
path: dist
# dist carries dot-paths the sites need (.well-known/security.txt,
# the apex .htaccess rewrites). upload-artifact excludes hidden
# files by default since v4.4; without this flag they would vanish
# from the artifact and the rsync --delete below would then REMOVE
# them from the live document roots.
include-hidden-files: true
retention-days: 1

deploy:
needs: [resolve, build]
runs-on: ubuntu-latest
strategy:
# Surfaces deploy independently: one failing leg does not block the
# others, and a single leg can be re-run on its own ("re-run failed
# jobs") without rebuilding - the artifact is the build.
fail-fast: false
matrix:
surface: ${{ fromJSON(needs.resolve.outputs.surfaces) }}
steps:
- name: download dist
uses: actions/download-artifact@v4
with:
name: dist-${{ inputs.env }}
path: dist
- name: resolve rsync target
id: target
run: |
surface="${{ inputs.surface }}"
surface="${{ matrix.surface }}"
env="${{ inputs.env }}"
base="/home/c216mkgp1lzk/public_html"
if [ "$env" = "production" ]; then
Expand Down Expand Up @@ -144,7 +202,7 @@ jobs:
run: |
rsync -avz --delete --exclude '/_assets/' \
-e "ssh -i ~/.ssh/kaihacksai" \
"./dist/${{ inputs.surface }}/" \
"./dist/${{ matrix.surface }}/" \
"c216mkgp1lzk@92.205.150.56:${{ steps.target.outputs.target }}"
- name: Deploy shared assets via rsync (NO --delete)
# Ship the hashed /_assets/ bundle the HTML references. No --delete:
Expand Down