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
14 changes: 10 additions & 4 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
environment: docs-production
if: >
if: >-
${{
github.event_name == 'workflow_dispatch' ||
github.ref == 'refs/heads/main' ||
Expand Down Expand Up @@ -99,6 +99,8 @@ jobs:
env:
BUNNY_STORAGE_ZONE: ${{ vars.BUNNY_STORAGE_ZONE || secrets.BUNNY_STORAGE_ZONE }}
BUNNY_ACCESS_KEY: ${{ secrets.BUNNY_ACCESS_KEY }}
BUNNY_API_KEY: ${{ secrets.BUNNY_API_KEY }}
BUNNY_PULL_ZONE_ID: ${{ vars.BUNNY_PULL_ZONE_ID || secrets.BUNNY_PULL_ZONE_ID }}
BUNNY_STORAGE_ENDPOINT: ${{ vars.BUNNY_STORAGE_ENDPOINT || secrets.BUNNY_STORAGE_ENDPOINT }}
BUNNY_REMOTE_PREFIX: ${{ vars.BUNNY_REMOTE_PREFIX || secrets.BUNNY_REMOTE_PREFIX }}
BUNNY_PULL_ZONE_HOSTNAME: ${{ vars.BUNNY_PULL_ZONE_HOSTNAME || secrets.BUNNY_PULL_ZONE_HOSTNAME }}
Expand All @@ -107,10 +109,12 @@ jobs:
missing=()
[ -n "${BUNNY_STORAGE_ZONE:-}" ] || missing+=(BUNNY_STORAGE_ZONE)
[ -n "${BUNNY_ACCESS_KEY:-}" ] || missing+=(BUNNY_ACCESS_KEY)
[ -n "${BUNNY_API_KEY:-}" ] || missing+=(BUNNY_API_KEY)
[ -n "${BUNNY_PULL_ZONE_ID:-}" ] || missing+=(BUNNY_PULL_ZONE_ID)
if [ "${#missing[@]}" -gt 0 ]; then
echo "::error::Bunny.net deployment configuration is missing: ${missing[*]}"
echo "::error::Run the guarded cross-platform setup: pnpm setup:github -- --repo ${GITHUB_REPOSITORY} --scope deploy"
echo "::error::The prompts explain where every Bunny value is found and keep the access key hidden."
echo "::error::The prompts explain where every Bunny value is found and keep credentials hidden."
exit 1
fi
if [ -n "${BUNNY_PULL_ZONE_HOSTNAME:-}" ]; then
Expand All @@ -119,14 +123,16 @@ jobs:
echo "hostname=false" >>"$GITHUB_OUTPUT"
fi

- name: Upload files to Bunny.net storage
- name: Upload files and purge Bunny.net CDN cache
env:
BUNNY_STORAGE_ZONE: ${{ vars.BUNNY_STORAGE_ZONE || secrets.BUNNY_STORAGE_ZONE }}
BUNNY_ACCESS_KEY: ${{ secrets.BUNNY_ACCESS_KEY }}
BUNNY_API_KEY: ${{ secrets.BUNNY_API_KEY }}
BUNNY_PULL_ZONE_ID: ${{ vars.BUNNY_PULL_ZONE_ID || secrets.BUNNY_PULL_ZONE_ID }}
BUNNY_STORAGE_ENDPOINT: ${{ vars.BUNNY_STORAGE_ENDPOINT || secrets.BUNNY_STORAGE_ENDPOINT }}
BUNNY_REMOTE_PREFIX: ${{ vars.BUNNY_REMOTE_PREFIX || secrets.BUNNY_REMOTE_PREFIX }}
LOOPWIRE_DOCS_DEPLOYMENT_MANIFEST: dist/docs-deployment/deployment-manifest.json
run: bash scripts/deploy-docs-bunny.sh --dist dist/site
run: bash scripts/deploy-docs-bunny.sh --dist dist/site --purge-cache

- name: Verify docs deployment manifest
env:
Expand Down
20 changes: 20 additions & 0 deletions .planning/quick/260905-jjc-bunny-cache-purge/260905-jjc-PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
status: verified
---

# Purge the docs CDN after deployment

Tracking: https://github.com/sandwichfarm/loopwire/issues/43

1. Extend the existing uploader with an explicit --purge-cache option, enabled by Deploy Docs. Require a separate
Bunny account API key and numeric Pull Zone ID before uploads. Preserve storage-only and dry-run CLI behavior.
2. After all uploads and manifest generation succeed, POST to the fixed Bunny purge endpoint using a private stdin
header. Keep credentials out of arguments/logs, bound network waits/retries, and reject errors without fake success.
3. Update guided GitHub setup and documentation for BUNNY_API_KEY secret and BUNNY_PULL_ZONE_ID variable. The legacy
upload-only rehearsal helper remains separate; no actual credentials are requested through chat or changed here.
4. Add regression tests for ordering, dry-run/no-purge, failed uploads/manifests, malformed/missing config, HTTP and
network failures, and secret handling. Run relevant script/workflow/setup/docs checks and open a focused PR.

This branch starts from current master independently of pending CI-filter PR #42. Reuse deploy-docs-bunny.sh so #42's
existing deployment path filters cover the feature. No app/backend/package/dependency changes or actual cache purge.
The live API call remains dependent on the operator supplying the account key and zone ID.
55 changes: 55 additions & 0 deletions .planning/quick/260905-jjc-bunny-cache-purge/260905-jjc-SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
status: verified
issue: 43
---

# Purge the docs CDN after deployment

Tracking: https://github.com/sandwichfarm/loopwire/issues/43

## Result

The existing uploader accepts `--purge-cache`; Deploy Docs always enables it. Purge configuration is validated before
uploads. After all files and the local deployment manifest are written successfully, the uploader requests a full
CDN Pull Zone purge. It accepts only HTTP 2xx and hides response bodies and raw transport errors. The account key is
passed through stdin, curl config files and redirects are disabled, and connection/request/retry waits are bounded.

The guarded setup helper requires `BUNNY_API_KEY` and `BUNNY_PULL_ZONE_ID` in deploy and final scopes, with hidden
prompts and exact stdin transport. The signed-int64 ID check preserves leading zeros without integer overflow.
Existing storage-only CLI behavior, storage-password handling, and legacy Unix setup/config templates are unchanged.
No dependencies or audio/backend/UI behavior changed. Existing upload and setup machinery was reused.

Docs explain the new settings, environment precedence, repository-only setup checks, error recovery, full-zone purge
scope, and the browser-cache limitation. The docs contract no longer requires obsolete skip-on-missing-secret prose.

## Validation

- Existing setup baseline: 11 transport cases passed before edits. New required-name assertions first failed because
`BUNNY_PULL_ZONE_ID` was missing; the expanded 14-case suite now passes.
- `node scripts/test-docs-cache-purge.mjs`: first failed on unknown `--purge-cache`, then passed. Covers upload/manifest
ordering, dry run, legacy upload-only use, required config, control characters, int64 bounds and leading zeros,
prefix-independent full-zone purge, response errors, bounded curl flags, and account-key secrecy.
- Independent real-curl loopback checks passed for successful upload/purge, a 503-to-204 retry preserving the private
header, rejected redirects, and hidden authentication-error bodies. Only local fixture servers were contacted.
- `node scripts/test-setup-github-actions.mjs`: all 14 cases passed, including both configuration scopes,
stdin-only secret writes, control rejection, exact bytes, dry run, readback failure, and idempotent repeat setup.
- `bash scripts/verify-scripts.sh`: passed the full script regression suite.
- `bash scripts/verify-github-workflows.sh` and `bash scripts/verify-docs.sh`: passed.
- Node syntax checks, Bash syntax checks, actionlint on Deploy Docs, and ShellCheck on the uploader passed.
ShellCheck ran from an existing offline container image because no host binary was installed.
- `pnpm lint`: workspace typechecks and Svelte checks passed with no errors or warnings.
- `pnpm build:web && pnpm verify:site`: production Astro/VitePress builds and combined site checks passed.
- `git diff --check` and the 150-character limit for added lines passed.

## Operator configuration and limits

In repository Settings → Environments → docs-production, add `BUNNY_API_KEY` as an environment secret and
`BUNNY_PULL_ZONE_ID` as an environment variable. Obtain the account key from
https://dash.bunny.net/account/api-key and the numeric CDN ID from the Pull Zone dashboard. Keep the existing
`BUNNY_ACCESS_KEY` storage password. The guided helper writes/checks repository settings; environment values override
matching repository settings and are not inspected by that helper.

No production credentials were read or changed, no Bunny API request was made, and no deployment was triggered.
Production purge acceptance remains an operator validation after setting the new values. Purging the full zone
does not clear browser caches or prove immediate refresh at every edge. Native app/audio tests were not needed
for this deployment-only change.
35 changes: 31 additions & 4 deletions apps/docs/docs/developer/github-actions-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,36 @@ repository, variable, and secret preflight. Review the displayed names and type
| `BUNNY_PULL_ZONE_HOSTNAME` | Actions variable | Final; optional for deploy smoke | Bunny dashboard → CDN → Pull Zones → select the Loopwire zone → Hostnames. Copy only the hostname, without a scheme or path. Skipping it in deploy scope leaves existing configuration unchanged. |
| `BUNNY_REMOTE_PREFIX` | Actions variable | Optional | Choose a relative storage subdirectory only when the site should not deploy at the storage-zone root. Skipping it leaves existing configuration unchanged; an unset value means the root. |
| `BUNNY_ACCESS_KEY` | Actions secret | Deploy and final | Bunny dashboard → Storage → select the Loopwire zone → FTP & API Access → Password. Use the storage-zone password, not the account API key. |
| `BUNNY_PULL_ZONE_ID` | Actions variable | Deploy and final | Numeric CDN Pull Zone ID; see below. |
| `BUNNY_API_KEY` | Actions secret | Deploy and final | Bunny account API key for CDN purges; see below. |
| `LOOPWIRE_RELEASE_PRIVATE_KEY` | Actions secret | Final | Generate the local PEM with `pnpm release:prepare-key`. At the prompt, enter its file path; do not paste it into a shell argument. |

Public configuration uses the GitHub `vars` context. Credentials and signing material use the `secrets` context. During
migration, workflows still fall back to older repository secrets when the matching Actions variable is absent.

### CDN purge configuration

Get `BUNNY_API_KEY` from the [Bunny account API Keys page](https://dash.bunny.net/account/api-key).
This is the account API key used by Bunny's [CDN purge API](https://bunny.net/docs/cdn/purge-cache).
Keep `BUNNY_ACCESS_KEY` as the separate storage-zone password; it cannot authorize the CDN management request.

Find `BUNNY_PULL_ZONE_ID` under Bunny dashboard → CDN → Pull Zones → select the zone serving the site.
Copy the numeric Pull Zone ID, not the Storage Zone ID or hostname. The helper accepts decimal digits representing
an integer from 1 through 9223372036854775807 and preserves entered digits. API keys must be a single header line
without ASCII control characters, including tabs, carriage returns, and newlines.

The guided helper writes **repository-level** Actions variables and secrets; its `--check` checks that scope only.
The `Deploy Docs` job runs in the **docs-production** environment. To configure just the two new settings there, open
repository Settings → Environments → docs-production. Add `BUNNY_API_KEY` under **Environment secrets** and
`BUNNY_PULL_ZONE_ID` under **Environment variables**. Existing storage settings remain in their current scope.
Environment settings take precedence over matching repository settings, so update stale environment values there
instead of relying on a repository-level setup run to replace them.

Production deployment requests a full Pull Zone purge after all uploads and manifest generation succeed.
This includes every path in that CDN zone, even when `BUNNY_REMOTE_PREFIX` selects a storage subdirectory.
Acceptance by the purge API does not invalidate copies already cached in browsers or prove immediate refresh on
every CDN edge. Browser caches continue to follow the site's HTTP cache headers.

## AUR publication environment

The manually dispatched `Publish AUR` workflow uses a separate GitHub environment named `aur`. Configure required
Expand Down Expand Up @@ -109,10 +134,12 @@ pnpm setup:github -- --repo OWNER/REPO --scope final --check
The check does not and cannot read secret values. It verifies required variables through GitHub's variable API and
required secrets through the names-only secret list.

The production `Deploy Docs` workflow fails before upload when `BUNNY_STORAGE_ZONE` or `BUNNY_ACCESS_KEY` is absent.
This is intentional: a green workflow run means the static site was uploaded, not merely built. Configure
`BUNNY_PULL_ZONE_HOSTNAME` as well to make the workflow probe the public HTTPS site after upload; without it, the live
HTTP verification step is skipped.
The production `Deploy Docs` workflow fails before upload when `BUNNY_STORAGE_ZONE`, `BUNNY_ACCESS_KEY`,
`BUNNY_API_KEY`, or `BUNNY_PULL_ZONE_ID` is absent. Malformed purge settings also fail before any upload.
A successful deployment requires every upload and acceptance of the CDN purge request. Configure
`BUNNY_PULL_ZONE_HOSTNAME` as well to make the workflow probe the public HTTPS site afterward; without it, the live
HTTP verification step is skipped. A failed purge leaves uploaded storage objects in place; correct the API key,
zone ID, or connectivity problem and rerun the deployment.

## Recover from a failed write

Expand Down
35 changes: 27 additions & 8 deletions apps/docs/docs/developer/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -822,16 +822,22 @@ The docs deployment workflow builds the Astro homepage plus the VitePress docs t
artifact, and deploys to Bunny.net only on explicit workflow dispatch, `main`, `master`, or `v*` tags. The deploy job is assigned to the `docs-production` GitHub
environment so repository protection rules can require manual review or protected branches.

If Bunny.net secrets are missing, the deploy job emits a notice and skips upload instead of failing unrelated CI. The
notice prints the safe local recovery sequence: create `/secure/loopwire-release-secrets.env` with
`--write-env-template`, fill it locally, then run the same helper with the local env file:
The deploy job fails before upload when `BUNNY_STORAGE_ZONE`, `BUNNY_ACCESS_KEY`, `BUNNY_API_KEY`, or
`BUNNY_PULL_ZONE_ID` is missing. Use the guarded repository-level setup command:

```bash
bash scripts/setup-github-secrets.sh --repo <owner/repo> --scope deploy --env-file /secure/loopwire-release-secrets.env
pnpm setup:github -- --repo OWNER/REPO --scope deploy
```

For final release proof, include `BUNNY_PULL_ZONE_HOSTNAME` in that env file so the live-docs smoke can run against the
Bunny pull-zone URL after upload.
Alternatively, add `BUNNY_API_KEY` as an environment secret and `BUNNY_PULL_ZONE_ID` as an environment variable in
Settings → Environments → docs-production. The API key comes from the
[Bunny account API Keys page](https://dash.bunny.net/account/api-key); the ID is the numeric CDN Pull Zone ID,
not the Storage Zone ID. The guided helper's `--check` reads repository-level settings only, and environment values
override matching repository values. See [GitHub Actions setup](./github-actions-setup.md#cdn-purge-configuration).

The legacy `scripts/setup-github-secrets.sh` and `.env.example` still describe storage-upload and release-signing
configuration only. They do not configure or check the new CDN purge settings; add those separately or use the
guided helper. For final release proof, configure `BUNNY_PULL_ZONE_HOSTNAME` so live smoke can probe the public site.

Deployment uses `scripts/deploy-docs-bunny.sh`, which uploads raw files with Bunny Edge Storage's `PUT` endpoint and
the storage-zone password in the `AccessKey` header. The script defaults to `https://storage.bunnycdn.com`, and
Expand All @@ -840,7 +846,19 @@ zone is not in Bunny's default region. `BUNNY_REMOTE_PREFIX` can deploy the site
which is useful when one zone serves multiple preview or product directories. The helper rejects unsafe `.` or `..`
remote-prefix segments before upload planning.

Preview the upload plan without contacting Bunny.net:
The workflow passes `--purge-cache` to this uploader. After every file upload and deployment-manifest write succeeds,
the helper sends `POST https://api.bunny.net/pullzone/{id}/purgeCache` with the account API key in a private stdin
header. It disables curl configuration-file loading, follows no redirects, discards the response body, and bounds
connection time, request time, and retries. Only HTTP 2xx is accepted; authentication, HTTP, or transport failures
fail the deployment without printing the account key. Failed uploads or manifest writes never trigger a purge.

This purges the **entire CDN Pull Zone**, including other prefixes served by that zone. An accepted request does
not clear browser caches or prove immediate refresh at every edge. If purge fails after upload, the new storage
objects remain in place; correct the error and rerun deployment. The local CLI remains upload-only unless
`--purge-cache` is supplied. See Bunny's [purge API documentation](https://bunny.net/docs/cdn/purge-cache).

Preview the upload and purge plan without contacting Bunny.net or supplying either credential; use the real CDN
Pull Zone ID in `BUNNY_PULL_ZONE_ID` so the target can be validated:

```bash
pnpm build:web
Expand All @@ -850,6 +868,7 @@ bash scripts/deploy-docs-bunny.sh \
--storage-endpoint https://ny.storage.bunnycdn.com \
--remote-prefix loopwire \
--deployment-manifest dist/docs-deployment/deployment-manifest.json \
--purge-cache \
--dry-run
```

Expand All @@ -864,7 +883,7 @@ inventory, rejects checksum drift, checks the remote-prefix mapping, rejects sou
secret-like manifest keys.
When `BUNNY_PULL_ZONE_HOSTNAME` is configured, the deploy workflow also runs
`scripts/verify-docs-live.sh --hostname "$BUNNY_PULL_ZONE_HOSTNAME" --remote-prefix "$BUNNY_REMOTE_PREFIX"` after
upload. The smoke uses the same pull-zone prefix used for upload. It fetches the deployed homepage, `/docs/`, the
upload and purge acceptance. The smoke uses the same pull-zone prefix used for upload. It fetches the deployed homepage, `/docs/`, the
basic-usage guide, and `/install.sh`, then checks the installer parses as shell and matches the local public installer.

Final release proof must be tied to the same deployment run. Pass the deploy-docs workflow run id that uploaded
Expand Down
7 changes: 7 additions & 0 deletions apps/docs/docs/release-notes/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ These notes describe source-tree progress. They are not a public release announc
- A third `loopwire-git` AUR recipe follows the protected default branch, derives a monotonic VCS package version, and
remains explicitly separate from the stable source and binary package bases.

## Docs deployment

- Docs deployment now requests a full Bunny CDN Pull Zone purge after all uploads and manifest generation succeed.
Configure the separate `BUNNY_API_KEY` account secret and numeric `BUNNY_PULL_ZONE_ID` variable; the guided setup
checks both. Upload, manifest, and purge failures fail the deployment, while dry-run makes no requests.
Purges invalidate the CDN zone's paths; copies already cached in browsers continue to follow HTTP cache headers.

## Desktop UI Rebuild

- GitHub Actions setup now has a cross-platform guided command that separates public variables from secrets, explains
Expand Down
Loading
Loading