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
101 changes: 101 additions & 0 deletions .github/actions/deploy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Blog's own deploy hook, run by the hub's deploy-site-task.yml in build, prune, and verify modes.
# The hub task forwards SITE_BASE_URL, SITE_AUTH_TOKEN_ID, and SITE_AUTH_TOKEN as plain process env vars, not through this composite action's own expression context.
# This hook therefore reads them as $SITE_BASE_URL and so on in its run: steps, never as ${{ env.* }}.
# Hugo's own HUGO_BASEURL convention is bridged from SITE_BASE_URL inside build mode only.
# Every other script in this repo keeps reading HUGO_BASEURL exactly as OPERATIONS.md documents.
name: Deploy hook (Blog)
description: The build, prune, and verify modes deploy-site-task.yml calls.

inputs:
mode:
description: build, prune, or verify.
required: true
bundle-path:
description: Scratch path for the assembled release bundle, used by build mode.
required: false
default: ''
release-id:
description: The release id this run is building or verifying.
required: false
default: ''
environment:
description: The GitHub Environment name, used by verify mode.
required: false
default: ''

runs:
using: composite
steps:

# One update for the job, since each is a network round trip that can fail on its own.
# REQUIRE_BROTLI later makes a missing brotli fatal.
- name: Install build tools step
if: inputs.mode == 'build'
shell: bash
run: |
set -Eeuo pipefail
sudo apt-get update
sudo apt-get install --yes --no-install-recommends brotli

# A file links under --link-dest only when size and mtime both match, and git stores no mtimes.
# This restores static/ only, since generated pages are written fresh by every build and can never match.
- name: Restore file mtimes step
if: inputs.mode == 'build'
uses: chetan/git-restore-mtime-action@d186aca54f8760da4dec55313195e51ed3ebb0b3 # v2.3
with:
args: static

# The outcome is asserted because a failing restore reports success and does nothing.
# A restored file cannot be newer than the commit it was dated from.
- name: Assert mtimes were restored step
if: inputs.mode == 'build'
shell: bash
run: |
set -Eeuo pipefail
bound=$(git log -1 --format=%ct)
newest=$(find static -type f -printf '%T@\n' | sort -n | tail -1 | cut -d. -f1)
echo "static/ newest mtime $newest, HEAD committed $bound, margin $((bound - newest))s"
if [ "$newest" -gt "$bound" ]; then
echo "::error::mtime restore did nothing: static/ carries files newer than HEAD's commit, so they still hold their checkout time and --link-dest will link nothing."
exit 1
fi

# The pin lives in the action, so this hook and the validate hook install the same generator.
- name: Install Hugo step
if: inputs.mode == 'build'
uses: ./.github/actions/install-hugo

# HUGO_BASEURL is set here, once, from the hub's SITE_BASE_URL.
# Everything downstream, Hugo itself and the verify step below, reads whichever name it already expects.
# No other script in this repo touches SITE_BASE_URL directly.
- name: Assemble release bundle step
if: inputs.mode == 'build'
shell: bash
env:
REQUIRE_BROTLI: '1'
MTIME_RESTORED: '1'
run: |
set -Eeuo pipefail
HUGO_BASEURL="$SITE_BASE_URL" deploy/make-release.sh "${{ inputs.bundle-path }}" "${{ inputs.release-id }}"

# Deliberately a no-op.
# OPERATIONS.md "Who Owns What" states it directly: the host's blog-prune-releases.timer owns retention, and nothing in this repo does.
# The deploy key needs no delete capability at all.
- name: No-op prune step
if: inputs.mode == 'prune'
shell: bash
run: |
echo "no-op: the host's blog-prune-releases.timer owns retention, not this deploy key"

# The only step that observes the running site.
# The token pair is set on staging alone, since production answers unauthenticated.
- name: Verify URL contract step
if: inputs.mode == 'verify'
shell: bash
env:
EXPECT_SITE_ENV: ${{ inputs.environment }}
EXPECT_RELEASE: ${{ inputs.release-id }}
run: |
set -Eeuo pipefail
PANGOLIN_ACCESS_TOKEN_ID="${SITE_AUTH_TOKEN_ID:-}" PANGOLIN_ACCESS_TOKEN="${SITE_AUTH_TOKEN:-}" \
checks/check-live-urls.sh "$SITE_BASE_URL"
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
191 changes: 0 additions & 191 deletions .github/workflows/deploy-site-task.yml

This file was deleted.

9 changes: 7 additions & 2 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@ jobs:
deploy:
name: Deploy site job
needs: [ validate ]
uses: ./.github/workflows/deploy-site-task.yml
uses: ptr727/ProjectTemplate/.github/workflows/deploy-site-task.yml@cd7bae9ea830d2e42d1f7bb6e45b3e7f631c0668 # 2.0.448
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
with:
environment: ${{ inputs.environment }}
permissions:
contents: read
secrets: inherit
# Cross-repo, so secrets: inherit does not apply.
# Production maps both auth-token secrets to empty, since neither is set there, which the hub task's own assert step treats as a public site.
secrets:
DEPLOY_SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
SITE_AUTH_TOKEN_ID: ${{ secrets.SITE_AUTH_TOKEN_ID }}
SITE_AUTH_TOKEN: ${{ secrets.SITE_AUTH_TOKEN }}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
3 changes: 1 addition & 2 deletions AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ The ground truth is the hub's committed `repo-config/` payloads, which this repo

This repo declares `types: ["source-only"]` and `workflowModel: release` with `lineEndings: "lf"`.

Two of those three are deliberate deviations from what the fleet spec would predict, recorded here rather than left to be rediscovered as drift. A third gap is not a deviation from the spec but from this repo's own progress adopting it, recorded the same way:
Both are deliberate deviations from what the fleet spec would predict, recorded here rather than left to be rediscovered as drift:

- **`deploy-site.yml` still calls this repo's own local `deploy-site-task.yml`, not the hub-hosted one [WORKFLOW.md](./WORKFLOW.md) describes.** Splitting `make-release.sh`'s hard-link and assertion logic into the documented build/prune/verify hook shape is deferred: this repo's script layout (pruning lives inside `make-release.sh` rather than as its own script) doesn't match what the adoption guide assumes, and untangling that on the live SSH deploy path needs more care than a quick fix gives it. `WORKFLOW.md` describes the fleet's target shape, not yet this repo's actual one, for this one guarantee.
- **`lineEndings: "lf"` on a `release` repo.** [`GOVERNANCE.md` "Line Endings"](./GOVERNANCE.md#line-endings) grants the native-platform default to operational repos only and holds `release` repos to the CRLF fleet default. Every consumer here is Linux: Hugo builds in CI, Caddy and OpenSSH read their config on Ubuntu, and the deploy scripts run there. Taking CRLF would mean an LF override for the shell scripts, the workflow YAML, the Caddyfile, the generated Caddy maps, and the content tree, which is the over-normalization that rule exists to prevent. The rule ties the ending to the workflow model when the thing that actually determines it is the consuming platform.
- **`types: ["source-only"]` rather than `docs`.** `docs` detects a "governance-only repo" and asserts that CI runs linting only with no build. Both are false here, since this repo builds a site with Hugo and gates it on a URL contract. `source-only` detects "no `build-*-task.yml`", which is true, and its checks describe the release shape this repo actually has. Both selectors resolve to the same 24 baseline files, so the choice costs nothing and only one of them is honest.

Expand Down
8 changes: 4 additions & 4 deletions ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ Held on the `production` and `staging` environments. The deploy workflow reads n

| Value | Kind | Names |
| --- | --- | --- |
| `HUGO_BASEURL` | variable | the base URL, used twice: the site is built with it and `check-live-urls.sh` is pointed at it |
| `SITE_BASE_URL` | variable | the base URL, used twice: `.github/actions/deploy/action.yml` builds the site with it (as `HUGO_BASEURL`) and points `check-live-urls.sh` at it |
| `DEPLOY_SSH_HOST` | variable | the deploy endpoint |
| `DEPLOY_SSH_USER` | variable | the confined deploy account |
| `DEPLOY_SSH_KNOWN_HOSTS` | variable | the pinned host key. A variable rather than a secret, deliberately, since it is public by nature |
| `DEPLOY_SSH_PRIVATE_KEY` | secret | the deploy key, held behind an `rrsync` forced command |
| `PANGOLIN_ACCESS_TOKEN_ID` | secret | as above, for an environment behind the gate |
| `PANGOLIN_ACCESS_TOKEN` | secret | as above |
| `SITE_AUTH_TOKEN_ID` | secret | as above, for an environment behind the gate. `.github/actions/deploy/action.yml` reads it as `PANGOLIN_ACCESS_TOKEN_ID` for `check-live-urls.sh` |
| `SITE_AUTH_TOKEN` | secret | as above, bridged to `PANGOLIN_ACCESS_TOKEN` the same way |

**`HUGO_BASEURL` being read twice is the trap worth knowing.** A wrong value bakes the wrong address into every canonical tag and then runs the full URL contract against that same wrong address, so the deploy verifies itself and passes.
**`SITE_BASE_URL` being read twice is the trap worth knowing.** A wrong value bakes the wrong address into every canonical tag and then runs the full URL contract against that same wrong address, so the deploy verifies itself and passes. Its generic name is the hub's own `deploy-site-task.yml` interface, since that task is not Hugo-specific. Blog's own scripts and `OPERATIONS.md` keep reading `HUGO_BASEURL`, which the deploy hook bridges from `SITE_BASE_URL` in one place.

**A host rebuild regenerates the SSH host keys and the pinned value stops matching**, which fails every deploy closed and blocks the rollback path at the same moment a rebuild makes both matter. Replace `DEPLOY_SSH_KNOWN_HOSTS` on **both** environments before the first deploy after a rebuild.

Expand Down
Loading