Skip to content

Commit 76721ec

Browse files
Karim13014claude
andcommitted
Operating model: CI/CD governance, deploy-pages, site-health
Adapts the reusable GitHub deployment governance model (from the bridgeaxis-consulting reference PR) to this root user Pages repo (https://lglenz.github.io/). Site content unchanged. Workflows - pr-checks.yml (new): mandatory PR/push checks — install-validation, build-validation, lint, link-sanity, secrets-config. Names are intended to be marked Required in branch protection. - deploy-pages.yml (new): official actions/configure-pages@v5 + upload-pages-artifact@v3 + deploy-pages@v4 targeting a `production` environment so reviewers/wait timers can be required from the UI. Records a deployment status on every run, fixing the stale Deployments tab problem. - site-health.yml (new): runs after deploy-pages (workflow_run), on a 6h cron, and on demand. Per target, checks DNS, TLS handshake, HTTP status, and <title>. Opens an issue with label `site-health` on scheduled failure. No secrets. Scripts - scripts/site_health.sh (new): one-URL DNS/TLS/HTTP/title probe with required vs warn-only modes. Docs - docs/Operating-Model.md (new): end-to-end flow, branch-protection setup, Pages source switch, environment setup, and troubleshooting. - README.md: live URL, CI/CD section, project structure tree. - .gitignore (new): standard ignores. Safe rollout: until Settings → Pages → Source is switched to "GitHub Actions", the existing branch-source deploy continues to publish the site, so merging this PR causes no downtime. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent de0fb80 commit 76721ec

7 files changed

Lines changed: 796 additions & 0 deletions

File tree

.github/workflows/deploy-pages.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Deploy GitHub Pages (Production)
2+
3+
# Deploys the static site to GitHub Pages and records a GitHub deployment
4+
# status for the "production" environment.
5+
#
6+
# Triggers:
7+
# - push to main (auto-deploy)
8+
# - workflow_dispatch (manual deploy)
9+
#
10+
# Notes for this repo (LGLenz/LGLenz.github.io):
11+
# - This is a user/root Pages site served at https://lglenz.github.io/.
12+
# - To use this workflow as the deployment source, set
13+
# Settings → Pages → Build and deployment → Source = "GitHub Actions".
14+
# Until that is set, GitHub Pages will keep deploying from the default
15+
# branch and this workflow's "deploy" step will fail with a clear error;
16+
# no site downtime occurs in that case.
17+
#
18+
# Governance:
19+
# - The "production" environment should be configured in repository settings
20+
# with required reviewers and/or wait timers if human approval is desired.
21+
# - This workflow uses only official GitHub-maintained actions for Pages.
22+
23+
on:
24+
push:
25+
branches: ["main"]
26+
workflow_dispatch:
27+
28+
permissions:
29+
contents: read
30+
pages: write
31+
id-token: write
32+
deployments: write
33+
34+
concurrency:
35+
group: "pages"
36+
cancel-in-progress: false
37+
38+
jobs:
39+
build:
40+
name: Build Pages artifact
41+
runs-on: ubuntu-latest
42+
steps:
43+
- name: Checkout
44+
uses: actions/checkout@v4
45+
46+
- name: Setup Pages
47+
uses: actions/configure-pages@v5
48+
49+
- name: Upload artifact
50+
uses: actions/upload-pages-artifact@v3
51+
with:
52+
path: '.'
53+
54+
deploy:
55+
name: Deploy to GitHub Pages
56+
needs: build
57+
runs-on: ubuntu-latest
58+
environment:
59+
name: production
60+
url: ${{ steps.deployment.outputs.page_url }}
61+
steps:
62+
- name: Deploy to GitHub Pages
63+
id: deployment
64+
uses: actions/deploy-pages@v4
65+
66+
- name: Enforce HTTPS on GitHub Pages
67+
continue-on-error: true
68+
env:
69+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70+
run: |
71+
gh api --method PATCH /repos/${{ github.repository }}/pages \
72+
--field https_enforced=true \
73+
--silent \
74+
&& echo "HTTPS enforcement enabled." \
75+
|| echo "Warning: HTTPS enforcement API call failed (certificate may not yet be provisioned or already enabled)."
76+
77+
- name: Summarise deployment
78+
run: |
79+
{
80+
echo "## GitHub Pages deployment"
81+
echo ""
82+
echo "- Environment: production"
83+
echo "- URL: ${{ steps.deployment.outputs.page_url }}"
84+
echo "- Commit: ${{ github.sha }}"
85+
echo "- Triggered by: ${{ github.event_name }}"
86+
} >> "$GITHUB_STEP_SUMMARY"

.github/workflows/pr-checks.yml

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
name: PR Mandatory Checks
2+
3+
# Mandatory, fast checks run on every pull request and on every push to main.
4+
# These job names are intended to be referenced by GitHub branch protection
5+
# so that they become required status checks (see docs/Operating-Model.md).
6+
#
7+
# Design goals:
8+
# - No secrets required.
9+
# - All jobs are read-only.
10+
# - Each job is a small, independently-required check.
11+
# - Failure is informative, not noisy.
12+
13+
on:
14+
pull_request:
15+
branches: ["main"]
16+
push:
17+
branches: ["main"]
18+
workflow_dispatch:
19+
20+
permissions:
21+
contents: read
22+
23+
concurrency:
24+
group: pr-checks-${{ github.ref }}
25+
cancel-in-progress: true
26+
27+
jobs:
28+
install-validation:
29+
name: Install / tooling validation
30+
runs-on: ubuntu-latest
31+
steps:
32+
- name: Checkout
33+
uses: actions/checkout@v4
34+
35+
- name: Verify repository layout
36+
run: |
37+
set -euo pipefail
38+
required=(
39+
"index.html"
40+
"README.md"
41+
".github/workflows"
42+
)
43+
missing=0
44+
for path in "${required[@]}"; do
45+
if [ ! -e "$path" ]; then
46+
echo "::error file=$path::Required path missing"
47+
missing=$((missing + 1))
48+
else
49+
echo "ok: $path"
50+
fi
51+
done
52+
if [ "$missing" -gt 0 ]; then
53+
echo "Repository layout invalid — $missing required path(s) missing."
54+
exit 1
55+
fi
56+
57+
- name: Install lightweight validators
58+
run: |
59+
set -euo pipefail
60+
sudo apt-get update -qq
61+
sudo apt-get install -y --no-install-recommends \
62+
tidy yamllint shellcheck libxml2-utils
63+
64+
- name: Print tool versions
65+
run: |
66+
tidy -v || true
67+
yamllint --version || true
68+
shellcheck --version || true
69+
xmllint --version 2>&1 | head -2 || true
70+
71+
build-validation:
72+
name: Build / artifact validation
73+
runs-on: ubuntu-latest
74+
needs: install-validation
75+
steps:
76+
- name: Checkout
77+
uses: actions/checkout@v4
78+
79+
- name: Setup Pages (build artifact dry-run)
80+
uses: actions/configure-pages@v5
81+
with:
82+
# Don't fail the PR check if Pages source isn't yet set to
83+
# "GitHub Actions" — this job only validates that the artifact
84+
# can be built and uploaded, not that the repo is wired for
85+
# workflow-source deploys.
86+
enablement: false
87+
88+
- name: Upload artifact (validates Pages build will succeed)
89+
uses: actions/upload-pages-artifact@v3
90+
with:
91+
path: '.'
92+
93+
- name: Sanity-check index.html
94+
run: |
95+
set -euo pipefail
96+
if [ ! -s index.html ]; then
97+
echo "::error file=index.html::index.html is missing or empty"
98+
exit 1
99+
fi
100+
if ! grep -qiE '<title[^>]*>[^<]*</title>' index.html; then
101+
echo "::error file=index.html::No <title> element found"
102+
exit 1
103+
fi
104+
echo "index.html ok ($(wc -c < index.html) bytes)"
105+
106+
lint:
107+
name: Lint / static validation
108+
runs-on: ubuntu-latest
109+
steps:
110+
- name: Checkout
111+
uses: actions/checkout@v4
112+
113+
- name: Install linters
114+
run: |
115+
sudo apt-get update -qq
116+
sudo apt-get install -y --no-install-recommends \
117+
tidy yamllint shellcheck libxml2-utils
118+
119+
- name: Lint YAML (workflows + repo)
120+
run: |
121+
set -euo pipefail
122+
# Relaxed config: only fail on syntax-level issues, not stylistic ones.
123+
cat > /tmp/yamllint.yml <<'YAML'
124+
extends: relaxed
125+
rules:
126+
line-length: disable
127+
truthy: disable
128+
comments: disable
129+
comments-indentation: disable
130+
document-start: disable
131+
indentation: disable
132+
empty-lines: disable
133+
new-line-at-end-of-file: disable
134+
trailing-spaces: disable
135+
YAML
136+
yamllint -c /tmp/yamllint.yml .github/ || exit 1
137+
138+
- name: Shellcheck embedded scripts
139+
run: |
140+
set -euo pipefail
141+
found=$(git ls-files '*.sh' || true)
142+
if [ -z "$found" ]; then
143+
echo "No .sh files to lint."
144+
exit 0
145+
fi
146+
echo "$found" | xargs shellcheck -S warning
147+
148+
- name: Validate XML files (if any)
149+
run: |
150+
set -euo pipefail
151+
xml_files=$(git ls-files '*.xml' || true)
152+
if [ -z "$xml_files" ]; then
153+
echo "No XML files to validate."
154+
exit 0
155+
fi
156+
rc=0
157+
for f in $xml_files; do
158+
echo "xmllint --noout $f"
159+
xmllint --noout "$f" || rc=1
160+
done
161+
exit "$rc"
162+
163+
- name: HTML tidy (non-blocking warnings, blocking errors)
164+
run: |
165+
set -euo pipefail
166+
errors=0
167+
for f in $(git ls-files '*.html'); do
168+
echo "::group::tidy $f"
169+
# tidy exits: 0 ok, 1 warnings, 2 errors. We block on 2.
170+
set +e
171+
tidy -q -e --gnu-emacs yes "$f"
172+
rc=$?
173+
set -e
174+
echo "::endgroup::"
175+
if [ "$rc" -ge 2 ]; then
176+
echo "::error file=$f::tidy reported errors (exit $rc)"
177+
errors=$((errors + 1))
178+
fi
179+
done
180+
if [ "$errors" -gt 0 ]; then
181+
echo "$errors HTML file(s) had blocking errors."
182+
exit 1
183+
fi
184+
185+
link-sanity:
186+
name: Link / site artifact sanity
187+
runs-on: ubuntu-latest
188+
steps:
189+
- name: Checkout
190+
uses: actions/checkout@v4
191+
192+
- name: Verify internal links / asset references resolve on disk
193+
run: |
194+
set -euo pipefail
195+
# For each HTML file, extract local href/src targets (no http(s):,
196+
# no mailto:, no #fragment-only) and verify they exist in the repo.
197+
rc=0
198+
for f in $(git ls-files '*.html'); do
199+
grep -oE '(href|src)="[^"]+"' "$f" \
200+
| sed -E 's/(href|src)="([^"]*)"/\2/' \
201+
| while IFS= read -r target; do
202+
case "$target" in
203+
http://*|https://*|mailto:*|tel:*|data:*|"#"*|"") continue ;;
204+
esac
205+
path="${target%%\?*}"
206+
path="${path%%#*}"
207+
path="${path#/}"
208+
if [ -z "$path" ]; then continue; fi
209+
if [ ! -e "$path" ]; then
210+
echo "::error file=$f::Broken internal reference: $target (resolved to '$path')"
211+
rc=1
212+
fi
213+
done
214+
done
215+
exit "$rc"
216+
217+
secrets-config:
218+
name: No-secrets / config sanity
219+
runs-on: ubuntu-latest
220+
steps:
221+
- name: Checkout
222+
uses: actions/checkout@v4
223+
with:
224+
fetch-depth: 0
225+
226+
- name: Run gitleaks (best-effort, no auth)
227+
uses: gitleaks/gitleaks-action@v2
228+
continue-on-error: true
229+
env:
230+
GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "false"
231+
GITLEAKS_ENABLE_SUMMARY: "true"
232+
233+
- name: Heuristic secret pattern check (blocking)
234+
run: |
235+
set -euo pipefail
236+
# Patterns we never want committed in this static-site repo.
237+
# Kept narrow to avoid false positives.
238+
patterns=(
239+
'AKIA[0-9A-Z]{16}' # AWS access key id
240+
'-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'
241+
'ghp_[A-Za-z0-9]{36,}' # GitHub PAT
242+
'xox[baprs]-[A-Za-z0-9-]{10,}' # Slack
243+
'AIza[0-9A-Za-z_-]{35}' # Google API key
244+
)
245+
rc=0
246+
for p in "${patterns[@]}"; do
247+
hits=$(git grep -nE "$p" -- ':!.github/workflows/pr-checks.yml' ':!*.lock' || true)
248+
if [ -n "$hits" ]; then
249+
echo "::error::Secret-like pattern '$p' found:"
250+
echo "$hits"
251+
rc=1
252+
fi
253+
done
254+
exit "$rc"
255+
256+
- name: Validate workflow files parse
257+
run: |
258+
set -euo pipefail
259+
python3 - <<'PY'
260+
import sys, yaml, glob
261+
bad = 0
262+
for path in sorted(glob.glob(".github/workflows/*.yml")):
263+
try:
264+
with open(path, "r", encoding="utf-8") as f:
265+
yaml.safe_load(f)
266+
print(f"ok: {path}")
267+
except yaml.YAMLError as e:
268+
print(f"::error file={path}::YAML parse error: {e}")
269+
bad += 1
270+
sys.exit(1 if bad else 0)
271+
PY
272+
273+
- name: Verify expected config files
274+
run: |
275+
set -euo pipefail
276+
for f in README.md index.html; do
277+
if [ ! -f "$f" ]; then
278+
echo "::error file=$f::Required config file missing"
279+
exit 1
280+
fi
281+
done
282+
echo "All required config files present."

0 commit comments

Comments
 (0)