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
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ trim_trailing_whitespace = true
[*.sh]
indent_style = tab

# The carried tree is byte-identical to the hub's copy, which indents with four spaces.
[lib/fleet/**.sh]
indent_style = space
indent_size = 4

# Markdown files
# Two trailing spaces are a hard line break in Markdown, so trimming them silently rewrites content.
[*.md]
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
validate:
name: Validate sources job
needs: [ assert-ref ]
uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@63cf1c9436876d55889260ce43d3900da2fd9b47 # 2.0.1166
uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@d7041d8ce15356dfd9223e54d74c64d464f28335 # 2.0.1192
permissions:
contents: read
with:
Expand All @@ -58,7 +58,7 @@ jobs:
deploy:
name: Deploy site job
needs: [ validate ]
uses: ptr727/ProjectTemplate/.github/workflows/deploy-site-task.yml@63cf1c9436876d55889260ce43d3900da2fd9b47 # 2.0.1166
uses: ptr727/ProjectTemplate/.github/workflows/deploy-site-task.yml@d7041d8ce15356dfd9223e54d74c64d464f28335 # 2.0.1192
with:
environment: ${{ inputs.environment }}
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/merge-bot-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

merge-bot:
name: Merge bot pull request job
uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@63cf1c9436876d55889260ce43d3900da2fd9b47 # 2.0.1166
uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@d7041d8ce15356dfd9223e54d74c64d464f28335 # 2.0.1192
secrets:
CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }}
CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
# Also replaces this repo's own dispatch-ref assertion: the hub task errors identically on a ref other than main/develop.
plan:
name: Plan release job
uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@63cf1c9436876d55889260ce43d3900da2fd9b47 # 2.0.1166
uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@d7041d8ce15356dfd9223e54d74c64d464f28335 # 2.0.1192
with:
event_name: ${{ github.event_name }}
actor: ${{ github.actor }}
Expand All @@ -30,7 +30,7 @@ jobs:
name: Validate sources job
needs: [plan]
if: ${{ needs.plan.outputs.publish == 'true' }}
uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@63cf1c9436876d55889260ce43d3900da2fd9b47 # 2.0.1166
uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@d7041d8ce15356dfd9223e54d74c64d464f28335 # 2.0.1192
permissions:
contents: read
with:
Expand All @@ -54,7 +54,7 @@ jobs:
# An if: on a job replaces the implicit success() over its needs.
# Without the validate arm a failed gate would publish anyway.
if: ${{ needs.plan.outputs.publish == 'true' && needs.validate.result == 'success' }}
uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@63cf1c9436876d55889260ce43d3900da2fd9b47 # 2.0.1166
uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@d7041d8ce15356dfd9223e54d74c64d464f28335 # 2.0.1192
permissions:
contents: write
actions: write
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

validate:
name: Validate sources job
uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@63cf1c9436876d55889260ce43d3900da2fd9b47 # 2.0.1166
uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@d7041d8ce15356dfd9223e54d74c64d464f28335 # 2.0.1192
permissions:
contents: read
with:
Expand Down
2 changes: 1 addition & 1 deletion .secrets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ than the documented shape.
| `~/.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).
[`ENVIRONMENT.md`](../ENVIRONMENT.md). Start a new one from [`example.env`](./example.env), then `chmod 600` it, since `make-release.sh` refuses a file that group or other can read.
2 changes: 1 addition & 1 deletion .secrets/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
# 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
# make-release.sh reads DEPLOY_ROOT, DEPLOY_SSH_HOST, and SITE_BASE_URL from it over the caller's, and refuses a file that group or other can read. A named
# 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.

Expand Down
6 changes: 4 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"command": "shellcheck",
"args": [
"deploy/make-release.sh",
"checks/check-live-urls.sh"
"checks/check-live-urls.sh",
"lib/fleet/sh/secrets.sh"
],
"problemMatcher": []
},
Expand All @@ -38,7 +39,8 @@
"args": [
"-d",
"deploy/make-release.sh",
"checks/check-live-urls.sh"
"checks/check-live-urls.sh",
"lib/fleet/sh/secrets.sh"
],
"problemMatcher": []
},
Expand Down
6 changes: 3 additions & 3 deletions ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Every configuration value this repository reads or writes, described once. [`OPE

## The mechanism

A local run reads one file. `~/.secrets/blog_<server>_<environment>.env` is sourced with `set -a`, selected by `ENV_FILE`, and defaults to `~/.secrets/blog_local_production.env`. The real files live on the host, never in this checkout, and [`.secrets/example.env`](./.secrets/example.env) is the tracked template that documents the shape.
A local run reads one file. `~/.secrets/blog_<server>_<environment>.env` is selected by `ENV_FILE`, and defaults to `~/.secrets/blog_local_production.env`. `make-release.sh` checks it with the carried `lib/fleet/sh/secrets.sh` readers. It reads only the values it needs, each in a child shell, rather than sourcing the whole file. A file must give group and other no access and use LF line endings, or the script refuses it and says why. The real files live on the host, never in this checkout, and [`.secrets/example.env`](./.secrets/example.env) is the tracked template that documents the shape.

Two consequences of `set -a` are worth stating because both have surprised someone. Sourcing overwrites a variable the caller exported first, so exporting `DEPLOY_ROOT` by hand does not switch environments and only `ENV_FILE` does. And a named file that does not exist is a hard failure rather than a fall-through, because on a host serving two sites the ambient value is the other site's root.
Two consequences are worth stating because both have surprised someone. Once a file is read, its `DEPLOY_ROOT`, `DEPLOY_SSH_HOST`, and `SITE_BASE_URL` replace any value the caller exported, even where the file leaves one unset. So exporting `DEPLOY_ROOT` by hand does not switch environments, and only `ENV_FILE` does. And a named file that does not exist is a hard failure rather than a fall-through, because on a host serving two sites the ambient value is the other site's root.

CI reads no file. The deploy workflow resolves the same values from the GitHub Environment, which is why the shapes have to match even though the sources do not.

Expand Down Expand Up @@ -74,7 +74,7 @@ Set on the command line for one run rather than stored anywhere.

| Value | Effect |
| --- | --- |
| `ENV_FILE` | which environment file to source. Defaults to `~/.secrets/blog_local_production.env` |
| `ENV_FILE` | which environment file to read. Defaults to `~/.secrets/blog_local_production.env` |
| `REQUIRE_BROTLI=1` | fail rather than shipping gzip-only. CI sets it |
| `NO_LINK_DEST=1` | full copy instead of hard-linking from the previous release |
| `KEEP_RELEASES` | how many releases `make-release.sh` leaves behind |
Expand Down
14 changes: 7 additions & 7 deletions OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ ENV_FILE=~/.secrets/blog_local_production.env deploy/make-release.sh "" "$RELEAS
EXPECT_RELEASE="$RELEASE" checks/check-live-urls.sh "$SITE_BASE_URL"
```

Name the file in both places, even when it is the default, since `make-release.sh` sources `ENV_FILE` independently of the shell above and a value already exported earlier in the same session would otherwise win silently over the sourced one. The empty first argument leaves the deploy root at the sourced `DEPLOY_ROOT`, and `RELEASE` is reused so `EXPECT_RELEASE` verifies the release the command just built rather than skipping the release-stamp guard. `set -e` matters here too: a failed `git rev-parse` would otherwise leave `RELEASE` empty, which silently skips the check's own release-stamp verification instead of failing loud:
Name the file in both places, even when it is the default, since `make-release.sh` reads `ENV_FILE` independently of the shell above and a value already exported earlier in the same session would otherwise win silently over the sourced one. The empty first argument leaves the deploy root at the `DEPLOY_ROOT` the script reads from that file. `RELEASE` is reused so `EXPECT_RELEASE` verifies the release the command just built rather than skipping the release-stamp guard. `set -e` matters here too: a failed `git rev-parse` would otherwise leave `RELEASE` empty, which silently skips the check's own release-stamp verification instead of failing loud:

```sh
set -e
Expand All @@ -65,9 +65,9 @@ So the bundle stamps its own version as `X-Blog-Release`, and `check-live-urls.s
EXPECT_RELEASE=<version> checks/check-live-urls.sh "$SITE_BASE_URL"
```

Sourcing the environment file first puts the deploy root and the base URL in the environment, so no literal value is typed. `make-release.sh` then needs no arguments, because its deploy root falls back to `$DEPLOY_ROOT` and its version falls back to a timestamp. It still accepts both arguments, and the deploy workflow passes a bundle path and a version, as [Deploying](#deploying) below shows. Either form works locally, and the argument wins over the environment.
Sourcing the environment file first puts the base URL, and the other values `check-live-urls.sh` reads, in the shell, so no literal value is typed. `make-release.sh` needs no arguments. Its deploy root falls back to the `DEPLOY_ROOT` it reads from its own file, and its version falls back to a timestamp. It still accepts both arguments, and the deploy workflow passes a bundle path and a version, as [Deploying](#deploying) below shows. Either form works locally, and the argument wins over the file's `DEPLOY_ROOT`.

`ENV_FILE` is set as well as sourced, and the redundancy is deliberate. The script sources its own file regardless, so leaving `ENV_FILE` off would build and install against `~/.secrets/blog_local_production.env` while the shell's `$SITE_BASE_URL` still named staging, and the run would check the staging site after publishing to the production root. The script prints the file it read, on every build, for that reason.
The file is both sourced in the shell and named in `ENV_FILE`, and the redundancy is deliberate. The script reads its own file regardless, so leaving `ENV_FILE` off would build and install against `~/.secrets/blog_local_production.env` while the shell's `$SITE_BASE_URL` still named staging, and the run would check the staging site after publishing to the production root. The script prints the file it read, on every build, for that reason.

It refuses to install a release that fails the build gate. `check-live-urls.sh` does take a base URL, which is where the sourced `$SITE_BASE_URL` goes. It follows every URL in the contract against the running mirror, checking each redirect's destination rather than trusting its status code.

Expand Down Expand Up @@ -119,9 +119,9 @@ SITE_BASE_URL=<base-url> deploy/make-release.sh <bundle-path> <release-id>
EXPECT_RELEASE=<release-id> checks/check-live-urls.sh <base-url>
```

The deploy root and the base URL are the only host-specific values. A local run reads them from a file under `~/.secrets/`, one per environment, copied from [`.secrets/example.env`](./.secrets/example.env). CI passes the base URL and a scratch bundle path, then uploads the bundle to the host's deploy root. The real files live on the host, never in this checkout.
The deploy root and the base URL are the only host-specific values. A local run reads them from a file under `~/.secrets/`, one per environment, copied from [`.secrets/example.env`](./.secrets/example.env). Make each one mode 600, since `make-release.sh` refuses a file that group or other can read. CI passes the base URL and a scratch bundle path, then uploads the bundle to the host's deploy root. The real files live on the host, never in this checkout.

**The command-prefix form above is CI-only.** A local run whose default environment file exists sources it after the command-prefix assignment and overwrites it, since `set -a` overwrites a value the caller exported first. Locally, select the environment through `ENV_FILE` instead, as the two examples earlier in this section do.
**The command-prefix form above is CI-only.** A local run whose default environment file exists reads it after the command-prefix assignment and overwrites it. A value read from the file replaces one the caller exported. Locally, select the environment through `ENV_FILE` instead, as the two examples earlier in this section do.

**A VPS release is named by its deploy run, never by a commit.** The hub's deploy task names each release `<UTC yyyymmdd-HHMMSS>-<run id>-<run attempt>` and verifies the site against that id itself. To check a VPS site by hand afterwards, pass that id as `EXPECT_RELEASE`. Read it from the **Deploy site job**'s log, where `make-release.sh` prints `==> installing release <id>` with the id ending in the run's own id and attempt. The Validate sources job prints the same line earlier in the run, for a scratch bundle stamped with a bare timestamp, so the first match of a log search is the wrong id. Never copy it from the site's `X-Blog-Release` header, since a stale config still serves the previous id and the check would then pass against it. A short SHA never matches, so the check waits out its timeout and fails.

Expand All @@ -139,7 +139,7 @@ The deploy root and the base URL are the only host-specific values. A local run

| Variable | Effect |
| --- | --- |
| `ENV_FILE` | Which environment file to source. Defaults to `~/.secrets/blog_local_production.env`. |
| `ENV_FILE` | Which environment file to read. Defaults to `~/.secrets/blog_local_production.env`. |
| `DEPLOY_ROOT` | Fallback deploy root. The first argument wins. |
| `SITE_BASE_URL` | Overrides the site base URL. |
| `REQUIRE_BROTLI=1` | Fails rather than shipping gzip-only. CI sets this. |
Expand Down Expand Up @@ -497,7 +497,7 @@ The local mirrors are not staging. They run the same bundle against the same web

**The two words are `production` and `staging`, spelled out, in every position.** No `prod`, no `stage`. The same two name the container, the deploy root, the environment file, the `X-Blog-Env` value, and the GitHub Environment. This is not tidiness: the environment name is a value that gets **compared**, by `EXPECT_SITE_ENV` and by the deploy, so a spelling that differs in one position fails a deploy for a reason that reads like an outage. The local mirrors prefix the same words, `mirror-production` and `mirror-staging`, so a header names exactly one of the four environments in the fleet.

Each environment is one file under `~/.secrets/`, named `blog_<server>_<environment>.env`, selected with `ENV_FILE`, and holding the deploy root, the base URL, and the container name. The name carries both halves because the two pairs differ in server as well as environment, so a file says which machine it describes rather than leaving that to the value inside it, and the four in the table above are the four files. `~/.secrets/blog_local_production.env` is the one read when `ENV_FILE` is unset. Selecting the file is how an environment is chosen: the file is sourced with `set -a`, so it overwrites a `DEPLOY_ROOT` the caller exported and setting that variable by hand does not switch anything. A named file that does not exist is a hard failure rather than a fall-through, because on a host serving two sites the ambient value is the other site's root.
Each environment is one file under `~/.secrets/`, named `blog_<server>_<environment>.env`, selected with `ENV_FILE`, and holding the deploy root, the base URL, and the container name. The name carries both halves because the two pairs differ in server as well as environment, so a file says which machine it describes rather than leaving that to the value inside it, and the four in the table above are the four files. `~/.secrets/blog_local_production.env` is the one read when `ENV_FILE` is unset. Selecting the file is how an environment is chosen. The script reads `DEPLOY_ROOT` from the file and overwrites one the caller exported. Setting that variable by hand therefore switches nothing. A named file that does not exist is a hard failure rather than a fall-through, because on a host serving two sites the ambient value is the other site's root.

**The staging FQDN sits under the VPS wildcard deliberately.** `blog.vps.insanegenius.net` needs no new certificate and no new DNS record, and it keeps the staging name off the production domain.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ ENV_FILE=~/.secrets/blog_local_production.env deploy/make-release.sh "" "$RELEAS
EXPECT_RELEASE="$RELEASE" checks/check-live-urls.sh "$SITE_BASE_URL"
```

The deploy root and the base URL come from a file per environment in `~/.secrets/`, named `blog_<server>_<environment>.env`, copied from [example.env][env-example] and selected with `ENV_FILE`. `~/.secrets/blog_local_production.env` is the one read when `ENV_FILE` is unset. The real files live on the host, never in this checkout.
The deploy root and the base URL come from a file per environment in `~/.secrets/`, named `blog_<server>_<environment>.env`, copied from [example.env][env-example] and selected with `ENV_FILE`. Make each one mode 600, since `make-release.sh` refuses a file that group or other can read. `~/.secrets/blog_local_production.env` is the one read when `ENV_FILE` is unset. The real files live on the host, never in this checkout.

## 3rd Party Tools <!-- omit from toc -->

Expand Down
Loading
Loading