# action/site-publish Composite Gitea Action that publishes a **static-content** website to the fritzlab k8s cluster. Supports `static`, `hugo`, and `mkdocs`. Content goes to a Garage S3 bucket; Traefik fronts the bucket via an `ExternalName` Service with cert-manager TLS. > **Containerized web apps (Dockerfile-based) are NOT handled here.** Use the > standard image-producer chain instead: > [`action/image-build`](https://code.fritzlab.net/action/image-build) + > [`action/image-push`](https://code.fritzlab.net/action/image-push) + > [`action/image-deploy`](https://code.fritzlab.net/action/image-deploy). > Hand-author the apps-repo manifests once (Deployment, Service, Ingress, > Certificate, kustomization with `images:` block) and let `image-deploy` > pin the tag on every push. See `sjc001/websites/rainsounds.vino.network/` > for the canonical example. site-publish errors out explicitly if > `site.yaml` has `type: docker`. ## Convention Bucket name = repo name = canonical domain. Sibling hostnames (e.g. `www.`, `ipv6.`) are declared as `aliases:` in `site.yaml` — the action registers each as a Garage `globalAlias` on the bucket and adds it to the Ingress + Certificate on every deploy. Manual edits to manifests in the apps repo are clobbered; edit `site.yaml` instead. ## Usage Scaffold a new site (handles repo creation + Garage bucket): ```sh ./new-site.sh --name my-site.vino.network --domain my-site.vino.network --type static ``` Or do it manually. `site.yaml`: ```yaml domain: my-site.vino.network type: static # static | hugo | mkdocs # content_dir: html # subdirectory containing content (default: repo root) # aliases: # additional hostnames (each gets a globalAlias on the bucket) # - www.my-site.vino.network # tidy: true # set false to skip HTML tidy # enabled: true # set false to decommission # excludes: # paths/patterns to skip during sync (relative to bucket root). # - welcome/welcome.pdf # # These are passed verbatim to `aws s3 sync --exclude`, # # so they're both un-uploaded AND un-deleted. Use this # # for large assets managed out-of-band via aws-cli # # (e.g. media files updated more often than the site code). ``` `.gitea/workflows/publish.yaml`: ```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 }} ``` DNS: subdomains of `vino.network` are covered by the wildcard CNAME to `traefik.edge.svc…`. For other zones, add an explicit CNAME: ``` my-site.fritzlab.net 300 IN CNAME traefik.edge.svc.k8s.sjc001.fritzlab.net. ``` ## Inputs | Input | Required | Default | Description | |---|---|---|---| | `token` | yes | | Gitea token for apps repo push | | `s3-access-key` | yes | | Garage `ci-deploy-key` access key id | | `s3-secret-key` | yes | | Garage `ci-deploy-key` secret key | | `s3-endpoint` | no | `http://garage.storage.svc:3900` | Garage S3 endpoint | | `garage-admin-token` | only if site has `aliases` | | Garage admin API token (`admin-token` from `garage-rpc-secret` in `storage` ns) | | `garage-admin-endpoint` | no | `http://garage.storage.svc:3903` | Garage admin API endpoint | | `username` | no | `ci-bot` | Gitea username | Org secrets in `websites`: `CI_BOT_TOKEN`, `GARAGE_S3_ACCESS_KEY`, `GARAGE_S3_SECRET_KEY`, `GARAGE_ADMIN_TOKEN`. ## Tools - **`new-site.sh`** — create a new site: Gitea repo, Garage bucket, web hosting enabled. - **`scripts/publish.py decommission `** — remove a site's manifests from apps repo. Bucket purge is manual. ## Architecture ``` push to websites/ → CI runs site-publish action → reads site.yaml, builds content (static copy / hugo / mkdocs), runs tidy → aws s3 sync → Garage bucket named after the repo → admin API: ensures every alias from site.yaml is a globalAlias on the bucket → renders manifests in fritzlab/apps from templates: ExternalName Service → garage.storage.svc, Traefik Ingress (canonical + aliases), cert-manager Certificate (canonical + aliases as SANs), kustomization → commits + pushes apps repo only if diff is non-empty → ArgoCD syncs → site live with TLS ``` The Ingress + Certificate are re-rendered on every deploy from `site.yaml`. There is no "first-deploy vs. update" branching — every deploy is idempotent. No nginx pods, no per-site Docker images. Garage matches `Host:` header to bucket name (or any of its globalAliases), so every site shares a single ExternalName target. ## History - 2026-05-06: removed `type: docker` support. The single docker site (`rainsounds.vino.network`) migrated to the `image-*` chain. site-publish is now scoped strictly to static-content sites. - 2026-05-06: renamed from `fritzlab/publish-site` → `action/site-publish`.