All checks were successful
Test / test (push) Successful in 6s
Project sites can pass a TOML file of edge rules (redirects, status codes, blocking, response headers, cache times) to the deploy action. The gateway validates them when a deployment starts and reconciles its own rules on the site's Pull Zones when it finalizes, deleting dropped rules and purging when a rule changed. Actions that could reach beyond the site, such as origin changes, are not available. The purge retry is limited to recorded purge failures, so a deployment whose rules failed to apply cannot be completed by a purge alone.
160 lines
5.7 KiB
Markdown
160 lines
5.7 KiB
Markdown
# Static Site Deploy
|
|
|
|
A small, dependency-free Forgejo Action that synchronizes a completed static
|
|
website build to a dedicated Bunny Storage Zone.
|
|
|
|
The runner needs Bash and Python 3.11 or newer. The action itself installs no
|
|
packages and executes no code from the Gandalf repository.
|
|
|
|
The deployer requires `index.html`, rejects symbolic links, compares files with
|
|
a checksum manifest, uploads changed assets before HTML, deletes stale files
|
|
only after successful uploads, and publishes the new manifest last.
|
|
|
|
## Forgejo Actions
|
|
|
|
Store the website-specific Storage Zone password as the repository secret
|
|
`BUNNY_STORAGE_PASSWORD`, then pin the action to an immutable commit from the
|
|
public action repository:
|
|
|
|
```yaml
|
|
---
|
|
name: Deploy website
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: docker
|
|
steps:
|
|
- uses: https://data.forgejo.org/actions/checkout@v6
|
|
- run: npm ci
|
|
- run: npm run build
|
|
- name: Deploy to Bunny
|
|
uses: https://forge.tionis.dev/actions/static-site-deploy@<commit-sha>
|
|
with:
|
|
directory: dist
|
|
storage-zone: example-org
|
|
purge-endpoint: https://gandalf.tionis.dev
|
|
env:
|
|
BUNNY_STORAGE_PASSWORD: ${{ secrets.BUNNY_STORAGE_PASSWORD }}
|
|
```
|
|
|
|
Only run deployments for trusted branches. A contributor able to change a
|
|
workflow that receives production secrets can attempt to expose those secrets.
|
|
|
|
`purge-endpoint` is the backward-compatible name of the optional Gandalf
|
|
deployment gateway input. Before uploading, the action acquires an expiring
|
|
per-zone lease. After publishing the manifest, Gandalf verifies its SHA-256
|
|
digest, records the revision and deployment outcome, and purges the mapped Pull
|
|
Zones only when the manifest changed from the previous successful deployment.
|
|
Interrupted uploads are aborted best-effort; abandoned leases expire after 30
|
|
minutes. The dedicated Storage Zone password authorizes this workflow without
|
|
exposing an account-wide API key to the project.
|
|
|
|
## Edge rules
|
|
|
|
Bunny Storage has no `_headers` or `_redirects` file and picks a file's
|
|
`Content-Type` from its extension. With the gateway, a site declares Bunny edge
|
|
rules in a TOML file kept in its repository, outside the build directory:
|
|
|
|
```yaml
|
|
with:
|
|
directory: dist
|
|
storage-zone: example-org
|
|
purge-endpoint: https://gandalf.tionis.dev
|
|
edge-rules: bunny.toml
|
|
```
|
|
|
|
```toml
|
|
[[edge_rules]]
|
|
name = "markdown"
|
|
url = "*.md"
|
|
response_headers = { "Content-Type" = "text/markdown; charset=utf-8" }
|
|
|
|
[[edge_rules]]
|
|
name = "old-blog"
|
|
url = ["*/blog/*", "*/news/*"]
|
|
redirect = { to = "https://example.org{{path}}", status = 301 }
|
|
|
|
[[edge_rules]]
|
|
name = "retired"
|
|
url = "*/alt/*"
|
|
status_code = 410
|
|
|
|
[[edge_rules]]
|
|
name = "hashed-assets"
|
|
url = "*/_astro/*"
|
|
cache_seconds = 31536000
|
|
browser_cache_seconds = 31536000
|
|
```
|
|
|
|
Each rule has a unique `name` (lowercase letters, digits, `-`), at least one
|
|
trigger and at least one action. Rules run in file order; there are at most 25.
|
|
|
|
Triggers take one pattern or a list of up to 10, any of which may match:
|
|
|
|
| Key | Matches |
|
|
| --- | --- |
|
|
| `url` | The full request URL, e.g. `*.md` or `*/docs/*` (`*` also spans `/`). |
|
|
| `method` | `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`. |
|
|
| `status` | The response status code, e.g. `[404]`. |
|
|
| `country` | Two-letter country codes such as `"DE"`. |
|
|
| `request_header` | `{ name = "X-Debug", values = ["1"] }`. |
|
|
|
|
With several triggers, `match` decides: `"all"` (default), `"any"` or `"none"`.
|
|
|
|
Actions:
|
|
|
|
| Key | Effect |
|
|
| --- | --- |
|
|
| `redirect` | `{ to = "https://…", status = 301 }`; `status` is 301, 302 (default), 307 or 308. `to` may use Bunny variables such as `{{path}}` (path with query string). |
|
|
| `status_code` | Replace the response status, 200-599. |
|
|
| `block` | `true`: refuse the request. |
|
|
| `response_headers` | Header names to values. Framing and hop-by-hop headers such as `Content-Length` are rejected. |
|
|
| `cache_seconds` | Edge cache time in seconds. |
|
|
| `browser_cache_seconds` | Browser cache time in seconds. |
|
|
| `ignore_query_string` | `true`: cache regardless of the query string. |
|
|
| `force_download` | `true`: serve as a download. |
|
|
|
|
`redirect`, `status_code` and `block` exclude each other. Bunny stops at the
|
|
first matching redirect or block; headers from all matching rules apply.
|
|
Actions that could reach beyond the site, such as changing the origin or
|
|
switching off security features, are not available.
|
|
|
|
The gateway checks the rules when the deployment starts, so a mistake fails
|
|
before anything is uploaded, and its error names the rule. A dry run only checks
|
|
the TOML structure. On finalization the gateway applies the rules to the site's
|
|
Pull Zones, removes rules dropped from the file, and purges the cache when a
|
|
rule changed. A deployment through the gateway without `edge-rules` removes the
|
|
rules of earlier deployments; rules set up by other means are never touched.
|
|
|
|
## Command line
|
|
|
|
The script can also be used directly with Python 3.11 or newer:
|
|
|
|
```bash
|
|
export BUNNY_STORAGE_ZONE=example-org
|
|
export BUNNY_STORAGE_PASSWORD='zone-specific password'
|
|
python3 static_site.py bunny dist --dry-run
|
|
python3 static_site.py bunny dist
|
|
```
|
|
|
|
Changed files are transferred with 16 workers by default while preserving the
|
|
asset-before-HTML and manifest-last publication order. Set
|
|
`BUNNY_TRANSFER_WORKERS` or pass `--workers` to tune the limit.
|
|
|
|
Set `BUNNY_STORAGE_ENDPOINT` when the Storage Zone is outside Frankfurt.
|
|
Pass edge rules with `--edge-rules bunny.toml` (or `STATIC_SITE_EDGE_RULES`);
|
|
`--dry-run` checks only the file's TOML structure.
|
|
|
|
## Development
|
|
|
|
```bash
|
|
python3 -m unittest discover -s tests
|
|
```
|
|
|
|
The canonical source lives in the private Gandalf infrastructure repository
|
|
and is published to this repository as a Git subtree.
|