-
Notifications
You must be signed in to change notification settings - Fork 0
153 lines (140 loc) · 7.12 KB
/
Copy pathdeploy-landing.yml
File metadata and controls
153 lines (140 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
name: deploy-landing
# Independent deploy of the driven.maxhogan.dev ROOT landing page (M12).
#
# `wrangler pages deploy site` publishes a WHOLE-SITE snapshot to the
# driven-updates CF Pages project (which serves driven.maxhogan.dev). So the
# landing page and the updater manifests share ONE site. The release.yml /
# dev-channel.yml pipelines already redeploy the whole site (and now copy the
# landing into it), but those only run on a tag / dev build. This workflow lets
# the MARKETING page ship on its own - whenever site-landing/ (or the assemble
# script / this workflow) changes on main - WITHOUT cutting a release.
#
# Critical no-wipe invariant: because the deploy is a whole-site snapshot, this
# workflow must reassemble the FULL site every time:
# site/ <- landing (index.html, styles.css, icon.svg, 404.html)
# site/updates/stable/ <- the currently-live stable manifests (overlaid)
# site/updates/dev/ <- the currently-live dev manifests (overlaid)
# It does this by copying the landing in AND pulling BOTH channels' live
# manifests via scripts/fetch-live-channel.sh (which tolerates a first-publish
# 404 and otherwise fails closed). If it deployed the landing alone it would wipe
# every updater manifest; this assembly preserves them.
on:
push:
branches: [main]
paths:
- "site-landing/**"
- "scripts/assemble-landing.sh"
- "scripts/fetch-live-channel.sh"
- ".github/workflows/deploy-landing.yml"
workflow_dispatch:
permissions:
contents: read
deployments: write
concurrency:
# Serialize with itself; do not cancel an in-flight deploy mid-snapshot.
group: deploy-landing
cancel-in-progress: false
jobs:
deploy:
name: assemble + deploy landing (whole-site, no-wipe)
runs-on: ubuntu-latest
env:
# The live updates site whose BOTH channels must be preserved across the
# whole-site `pages deploy` snapshot.
UPDATES_BASE: https://driven.maxhogan.dev/updates
SITE_URL: https://driven.maxhogan.dev/
# The tagline marker that must appear in the deployed landing body - this is
# the README one-liner, also the #tagline element in index.html.
TAGLINE_MARKER: "One-way, encrypted backup of your local folders to your own Google Drive."
steps:
- uses: actions/checkout@v7
# 1. Copy the committed landing page to the site root.
- name: Assemble landing into site/
run: bash scripts/assemble-landing.sh site site-landing
# 2. Overlay BOTH live channels' updater manifests so the whole-site deploy
# does NOT wipe them. fetch-live-channel.sh writes
# site/updates/<channel>/<plat>/update.json, tolerating a genuine
# first-publish 404 and failing closed on any other fetch error.
- name: Overlay live stable manifests (do not wipe stable)
run: bash scripts/fetch-live-channel.sh stable site/updates "$UPDATES_BASE"
- name: Overlay live dev manifests (do not wipe dev)
run: bash scripts/fetch-live-channel.sh dev site/updates "$UPDATES_BASE"
# dev-channel floor: this whole-site redeploy must not REPUBLISH a below-
# stable dev manifest (which would undo a release-time floor). Floor the
# overlaid live dev up to the overlaid live stable and assert dev>=stable
# before deploy. Self-healing regardless of deploy order
# (docs/superpowers/specs/2026-06-25-dev-channel-floor-design.md).
- name: Floor dev channel to stable (dev must never be below stable)
run: |
node scripts/floor-dev-channel.mjs \
--stable-dir site/updates/stable \
--dev-dir site/updates/dev
- name: Show assembled site tree
run: |
set -euo pipefail
echo "site/ root:"; ls -la site
echo "site/updates tree:"; find site/updates -type f 2>/dev/null | sort || true
# 3. Deploy the whole site (landing + both channels' updates) to CF Pages.
- name: Deploy site to Cloudflare Pages
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy site --project-name=driven-updates --branch=main
# 4. Post-deploy smoke: the root must serve 200 AND its body must contain
# the tagline marker. Cloudflare Pages propagation can lag the deploy and
# is not atomic across edges, so a fresh root can 404 briefly. -f makes a
# 404 a retryable error and curl's own bounded retry (8 attempts, 5s
# apart) rides out propagation - the SAME pattern the manifest smokes use,
# no bash sleep/poll loop.
- name: Smoke test deployed landing root
run: |
set -euo pipefail
body="$(mktemp)"
code="$(curl -fsSL --retry 8 --retry-delay 5 --retry-all-errors \
--connect-timeout 15 --max-time 120 \
-o "$body" -w '%{http_code}' "$SITE_URL" 2>/dev/null || true)"
code="${code:-000}"
if [ "$code" != "200" ]; then
echo "::error::landing smoke: ${SITE_URL} returned HTTP ${code} (expected 200)"
exit 1
fi
if ! grep -qF "$TAGLINE_MARKER" "$body"; then
echo "::error::landing smoke: ${SITE_URL} returned 200 but body is missing the tagline marker"
exit 1
fi
echo "landing smoke OK: ${SITE_URL} serves 200 and contains the tagline marker"
rm -f "$body"
# 5. Verify the deploy did NOT wipe the updater manifests: at least one
# channel must still serve a valid manifest. A first-ever deploy may have
# no live manifests to preserve (both 404), which is acceptable - the
# release/dev pipelines publish them. So this only FAILS if a manifest was
# present in the assembled tree but is unreachable after deploy.
- name: Verify updater manifests survived the deploy
run: |
set -euo pipefail
checked=0
fail=0
for chan in stable dev; do
for t in windows/x86_64 darwin/x86_64 darwin/aarch64 linux/x86_64; do
local_manifest="site/updates/${chan}/${t}/update.json"
[ -f "$local_manifest" ] || continue
checked=$((checked + 1))
url="${UPDATES_BASE}/${chan}/${t}/update.json"
code="$(curl -fsSL --retry 8 --retry-delay 5 --retry-all-errors \
--connect-timeout 15 --max-time 120 \
-o /dev/null -w '%{http_code}' "$url" 2>/dev/null || true)"
code="${code:-000}"
if [ "$code" != "200" ]; then
echo "::error::no-wipe check: ${url} returned HTTP ${code} (expected 200) - the landing deploy may have wiped a live manifest"
fail=1
else
echo "no-wipe OK: ${url} still serves 200"
fi
done
done
if [ "$fail" -ne 0 ]; then
echo "::error::landing deploy appears to have wiped one or more live updater manifests"
exit 1
fi
echo "no-wipe verified: ${checked} assembled manifest(s) still reachable (0 = first-ever deploy, nothing to preserve)"