2026-05-06 08:07:28 -05:00
|
|
|
# action/site-publish
|
|
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
Composite Gitea Action that publishes static, Hugo, and MkDocs output to Garage
|
|
|
|
|
and writes the matching Argo CD, Kubernetes, Traefik, and cert-manager resources
|
|
|
|
|
to `fritzlab/apps`.
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
Containerized applications use `action/image-build`, `action/image-push`, and
|
|
|
|
|
`action/image-deploy`. `type: docker` fails validation.
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
## Delivery contracts
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
There are two explicit internal modes:
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
- `single-surface-v1` is selected only when `schema`, `artifacts`, and `routes` are all
|
|
|
|
|
absent. It preserves the original one-repository/one-bucket/one-`/` route
|
|
|
|
|
behavior, existing action inputs, aliases, middleware, and revalidated cache.
|
|
|
|
|
- `v2` is selected by `schema: v2`. It requires named artifacts and routes and
|
|
|
|
|
validates the entire access/storage graph before any upload or manifest write.
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
Never mix the modes. A partial v2 declaration fails closed.
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
### Multi-surface schema
|
|
|
|
|
|
|
|
|
|
This example publishes immutable releases and revalidated channel pointers
|
|
|
|
|
anonymously while protecting the portal catch-all with Authentik:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
schema: v2
|
|
|
|
|
domain: baseline.fritzlab.net
|
|
|
|
|
type: static
|
|
|
|
|
content_dir: output
|
|
|
|
|
|
|
|
|
|
artifacts:
|
|
|
|
|
releases:
|
|
|
|
|
source: dist/releases
|
|
|
|
|
bucket: baseline-releases
|
|
|
|
|
credentials:
|
|
|
|
|
access_key_env: BASELINE_RELEASES_S3_ACCESS_KEY
|
|
|
|
|
secret_key_env: BASELINE_RELEASES_S3_SECRET_KEY
|
|
|
|
|
cache: immutable-release
|
|
|
|
|
channels:
|
|
|
|
|
source: dist/channels
|
|
|
|
|
bucket: baseline-channels
|
|
|
|
|
credentials:
|
|
|
|
|
access_key_env: BASELINE_CHANNELS_S3_ACCESS_KEY
|
|
|
|
|
secret_key_env: BASELINE_CHANNELS_S3_SECRET_KEY
|
|
|
|
|
cache: revalidated-channel
|
|
|
|
|
portal:
|
|
|
|
|
source: portal
|
|
|
|
|
bucket: baseline-portal
|
|
|
|
|
credentials:
|
|
|
|
|
access_key_env: BASELINE_PORTAL_S3_ACCESS_KEY
|
|
|
|
|
secret_key_env: BASELINE_PORTAL_S3_SECRET_KEY
|
|
|
|
|
cache: private
|
|
|
|
|
|
|
|
|
|
routes:
|
|
|
|
|
- path: /dist/releases
|
|
|
|
|
artifact: releases
|
|
|
|
|
access: public
|
|
|
|
|
- path: /dist/channels
|
|
|
|
|
artifact: channels
|
|
|
|
|
access: public
|
|
|
|
|
- path: /
|
|
|
|
|
artifact: portal
|
|
|
|
|
access: authenticated
|
|
|
|
|
middlewares: [authentik-forwardauth]
|
2026-05-06 08:07:28 -05:00
|
|
|
```
|
|
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
Artifact `source` is relative to `build/html` after the build phase. Credential
|
|
|
|
|
fields name inherited environment variables; secret values never belong in
|
|
|
|
|
`site.yaml`. Callers expose those variables to the composite action through the
|
|
|
|
|
workflow `env` contract.
|
|
|
|
|
|
|
|
|
|
Cache policies are intentionally closed:
|
|
|
|
|
|
|
|
|
|
| Policy | Header | Publication behavior |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `immutable-release` | `public, max-age=31536000, immutable` | write once; identical SHA-256 retry skips; changed key fails |
|
|
|
|
|
| `revalidated-channel` | `public, max-age=0, must-revalidate` | replace and delete stale keys |
|
|
|
|
|
| `private` | `private, no-store` | replace and delete stale keys |
|
|
|
|
|
|
|
|
|
|
Validation rejects unknown keys or references, unsafe paths, duplicate route
|
|
|
|
|
prefixes, public `/`, authenticated routes without middleware, private/public
|
|
|
|
|
cache mismatches, unrouted artifacts, an artifact routed twice, and bucket or
|
|
|
|
|
publication credential reuse between artifacts. V2 also requires an explicit
|
|
|
|
|
`/` access policy. Routes render deterministically by
|
|
|
|
|
longest prefix.
|
|
|
|
|
|
|
|
|
|
Each route receives a distinct ExternalName Service and Ingress. The Service
|
|
|
|
|
targets `<bucket>.web.sjc001.fritzlab.net` with Traefik host forwarding disabled,
|
|
|
|
|
so Garage selects that route's bucket. Objects are stored under their route
|
|
|
|
|
prefix, avoiding a path-rewrite middleware. Legacy Services directly target the
|
|
|
|
|
live `garage-s3.storage.svc.k8s.sjc001.fritzlab.net:80` endpoint. All routes share
|
|
|
|
|
one Certificate.
|
|
|
|
|
|
|
|
|
|
## Legacy usage
|
|
|
|
|
|
|
|
|
|
Existing callers remain valid:
|
2026-05-06 08:07:28 -05:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
domain: my-site.vino.network
|
2026-08-29 21:21:47 +00:00
|
|
|
type: static
|
|
|
|
|
content_dir: html
|
|
|
|
|
# aliases: [www.my-site.vino.network]
|
|
|
|
|
# tidy: true
|
|
|
|
|
# enabled: true
|
|
|
|
|
# excludes: [welcome/welcome.pdf]
|
|
|
|
|
# middlewares: [authentik-forwardauth]
|
2026-05-06 08:07:28 -05:00
|
|
|
```
|
|
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
Legacy bucket name is the repository name. Aliases are reconciled as Garage
|
|
|
|
|
global aliases and require `garage-admin-token`; reconciliation errors stop the
|
|
|
|
|
deploy. Disabling a site removes generated Apps manifests but never deletes its
|
|
|
|
|
bucket or objects.
|
2026-05-06 08:07:28 -05:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
name: Publish
|
|
|
|
|
on:
|
|
|
|
|
push:
|
|
|
|
|
branches: [main]
|
|
|
|
|
jobs:
|
|
|
|
|
publish:
|
|
|
|
|
runs-on: fritzlab
|
|
|
|
|
steps:
|
|
|
|
|
- uses: actions/checkout@v4
|
|
|
|
|
- uses: https://code.fritzlab.net/action/site-publish@v1
|
|
|
|
|
with:
|
|
|
|
|
token: ${{ secrets.CI_BOT_TOKEN }}
|
|
|
|
|
s3-access-key: ${{ secrets.GARAGE_S3_ACCESS_KEY }}
|
|
|
|
|
s3-secret-key: ${{ secrets.GARAGE_S3_SECRET_KEY }}
|
|
|
|
|
garage-admin-token: ${{ secrets.GARAGE_ADMIN_TOKEN }}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Inputs
|
|
|
|
|
|
|
|
|
|
| Input | Required | Default | Description |
|
|
|
|
|
|---|---|---|---|
|
2026-08-29 21:21:47 +00:00
|
|
|
| `token` | yes | | Gitea token used for the Apps repository |
|
|
|
|
|
| `s3-access-key` | single-surface-v1 | | Legacy Garage access key |
|
|
|
|
|
| `s3-secret-key` | single-surface-v1 | | Legacy Garage secret key |
|
|
|
|
|
| `s3-endpoint` | no | `http://garage-s3.storage.svc:3900` | Garage S3 API |
|
|
|
|
|
| `garage-admin-token` | with legacy aliases | | Garage admin token |
|
|
|
|
|
| `garage-admin-endpoint` | no | `http://garage.storage.svc:3903` | Garage admin API |
|
2026-05-06 08:07:28 -05:00
|
|
|
| `username` | no | `ci-bot` | Gitea username |
|
|
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
Git HTTPS authentication is supplied to Git through a short-lived inherited
|
|
|
|
|
file descriptor. The token isn't placed in argv, command output, or a clone URL.
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
## Generated topology
|
2026-05-06 08:07:28 -05:00
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
```text
|
|
|
|
|
push
|
|
|
|
|
-> build/html
|
|
|
|
|
-> validate complete site.yaml graph
|
|
|
|
|
-> publish each artifact with its cache/write policy and credential pair
|
|
|
|
|
-> one Certificate
|
|
|
|
|
-> N bucket-specific Services + longest-prefix Ingresses
|
|
|
|
|
-> commit changed manifests to fritzlab/apps
|
|
|
|
|
-> Argo CD reconciliation
|
2026-05-06 08:07:28 -05:00
|
|
|
```
|
|
|
|
|
|
2026-08-29 21:21:47 +00:00
|
|
|
Manual changes to generated Apps resources are overwritten. `new-site.sh`
|
|
|
|
|
continues to scaffold legacy one-surface sites.
|