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
12 changes: 4 additions & 8 deletions .github/actions/deploy/action.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 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.
# Both deploy/make-release.sh and checks/check-live-urls.sh read these same names directly.
# The bridge to Hugo's own HUGO_BASEURL convention happens inside make-release.sh, since only Hugo requires that name.
name: Deploy hook (Blog)
description: The build, prune, and verify modes deploy-site-task.yml calls.

Expand Down Expand Up @@ -65,9 +65,6 @@ runs:
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
Expand All @@ -76,7 +73,7 @@ runs:
MTIME_RESTORED: '1'
run: |
set -Eeuo pipefail
HUGO_BASEURL="$SITE_BASE_URL" deploy/make-release.sh "${{ inputs.bundle-path }}" "${{ inputs.release-id }}"
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.
Expand All @@ -97,5 +94,4 @@ runs:
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"
checks/check-live-urls.sh "$SITE_BASE_URL"
21 changes: 10 additions & 11 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,16 @@ __pycache__/

# Host-specific values: deploy roots, base URLs, container names, and uids.
# Each names one particular machine rather than the project.
# The whole directory is ignored so a value added later lands ignored by default.
# `example.env` is the committed template and sits at the repository root.
# The `*.env` pattern is the backstop for a real environment file written outside the
# directory, matching the `<server>.<environment>.env` shape those files are named for
# rather than a single literal name. It also matches the template, so the template is
# negated on the line after it, anchored so it only exempts the one at the root. Order
# matters: a negation placed before its pattern does nothing.
secrets/
**/secrets
*.env
!/example.env
# Real values live on the host in ~/.secrets/, never in this checkout, so nothing under this directory is ever a real value except the two exemptions below.
# Order matters: a negation placed before its pattern does nothing.
.secrets/*
!.secrets/example.env
!.secrets/README.md

# The retired convention this replaced kept real per-environment files in secrets/ at the repository root, ignored by a rule this migration otherwise removes.
# A checkout that predates the migration and still carries that directory would have those real files turn up as untracked instead, one git add -A away from being committed.
# Kept here as a permanent backstop against exactly that, even though the convention itself is gone and no current checkout carries the directory.
/secrets/

# The working copies of the host channel described in OPERATIONS.md.
# They carry server internals, and the host's own backup is what makes them durable.
Expand Down
27 changes: 27 additions & 0 deletions .secrets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# .secrets
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

Nothing under this directory holds a real value except [`example.env`](./example.env), the
tracked template, and this catalog. `.gitignore` un-ignores exactly those two paths and ignores
everything else here, so a fresh checkout documents its own required shape without ever
exposing one.

## Real values live on the host, never in the checkout

The documented local convention is `~/.secrets/`, not this directory. CI supplies the same
values directly from the GitHub Environment instead, reading no file here at all.
`ENV_FILE=<name> deploy/make-release.sh` and `ops/install.sh` both resolve a relative `ENV_FILE`
against `$HOME/.secrets`, refuse a traversing one, and default to
`~/.secrets/Blog.local.production.env`. An absolute `ENV_FILE` is honored as given rather than
resolved against `~/.secrets/`, an escape hatch rather than the documented shape.
`~/.secrets/` is shared across every repo on the host, so each of this repo's files carries the
`Blog.` prefix:

| File | Selects |
| --- | --- |
| `~/.secrets/Blog.local.production.env` | The default, read when `ENV_FILE` is unset. |
| `~/.secrets/Blog.local.staging.env` | The local staging mirror. |
| `~/.secrets/Blog.vps.production.env` | The VPS production environment. |
| `~/.secrets/Blog.vps.staging.env` | The VPS staging environment. |

Every value each file holds, and what reads it, is described once in
[`ENVIRONMENT.md`](../ENVIRONMENT.md). Start a new one from [`example.env`](./example.env).
32 changes: 17 additions & 15 deletions example.env → .secrets/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,38 @@
# It fills two destinations, marked below, because a value belongs to whichever machine
# holds it. Copy the section you need rather than the whole file.
#
# secrets/<server>.<environment>.env on a workstation, one file per environment
# /etc/vps-backup-pull.env on the backup host, or let ops/install.sh write it
# ~/.secrets/Blog.<server>.<environment>.env on a workstation, one file per environment
# /etc/vps-backup-pull.env on the backup host, or let ops/install.sh write it
#
# A value appearing in both sections is spelled the same way in both, deliberately. One
# name per thing means the side that writes and the side that reads cannot disagree, which
# is also why ops/install.sh copies values across rather than translating them.
#
# Naming convention: the prefix names whatever owns the value, not whatever reads it.
# HUGO_ is fixed by Hugo, DEPLOY_ is the release tooling, CADDY_ is the container,
# PANGOLIN_ is the proxy, VPS_ is the server, and a *_ROOT is a directory on this host.
# SITE_ is the generic deploy contract, DEPLOY_ is the release tooling, CADDY_ is the
# container, VPS_ is the server, and a *_ROOT is a directory on this host. deploy/make-release.sh
# and checks/check-live-urls.sh bridge SITE_ names to a tool's own native ones internally,
# HUGO_BASEURL for Hugo, only where that tool requires its own name.

# =============================================================================
# secrets/<server>.<environment>.env
# ~/.secrets/Blog.<server>.<environment>.env
# =============================================================================
# One file per environment, named for the server it describes and the environment on it,
# with both words spelled out, and selected by ENV_FILE:
# secrets/local.production.env the default, read when ENV_FILE is unset
# secrets/local.staging.env ENV_FILE=secrets/local.staging.env deploy/make-release.sh
# secrets/vps.production.env ENV_FILE=secrets/vps.production.env deploy/make-release.sh
# secrets/vps.staging.env ENV_FILE=secrets/vps.staging.env deploy/make-release.sh
# Blog.local.production.env the default, read when ENV_FILE is unset
# Blog.local.staging.env ENV_FILE=Blog.local.staging.env deploy/make-release.sh
# Blog.vps.production.env ENV_FILE=Blog.vps.production.env deploy/make-release.sh
# Blog.vps.staging.env ENV_FILE=Blog.vps.staging.env deploy/make-release.sh
#
# Sourced with `set -a`, which overwrites a variable the caller exported first. A named
# file that does not exist is a hard failure rather than a fall-through. The whole
# secrets/ directory is gitignored, so no value naming a machine reaches this history.
# file that does not exist is a hard failure rather than a fall-through. Real files live
# in ~/.secrets/, never in this checkout, so no value naming a machine reaches this history.

# Where a release is written. The first argument to make-release.sh wins over it.
DEPLOY_ROOT=/path/to/deploy/root

# The site base URL. Must be set for anything that is not production.
HUGO_BASEURL=https://blog.example.com/
SITE_BASE_URL=https://blog.example.com/

# The container's persistent state root, outside DEPLOY_ROOT. Nothing reads it.
CADDY_APPDATA=/path/to/container/appdata
Expand All @@ -48,8 +50,8 @@ CADDY_CONTAINER=blog-production
EXPECT_SITE_ENV=production

# Resource access token for an environment behind the auth gate. Set both or neither.
PANGOLIN_ACCESS_TOKEN_ID=
PANGOLIN_ACCESS_TOKEN=
SITE_AUTH_TOKEN_ID=
SITE_AUTH_TOKEN=

# Read by the deploy workflow, which resolves them from the GitHub Environment rather than
# a file. Named here so the local file and the environment describe the same shape.
Expand All @@ -66,7 +68,7 @@ PANGOLIN_ACCESS_TOKEN=
CAPTURE_ROOT=/path/to/blog-capture

# The old platform's base URL, the site the crawl and the URL verification ran against.
# NOT HUGO_BASEURL, which is where this site is served now. See ENVIRONMENT.md.
# NOT SITE_BASE_URL, which is where this site is served now. See ENVIRONMENT.md.
CAPTURE_SOURCE_URL=https://blog.example.com

# The old platform's REST API for that site, carrying its numeric site id.
Expand Down
Loading
Loading