Minimal site for Pelorus Marine: warp, Askama templates, Bootstrap 5.3.3 (vendored), and a small TypeScript bundle for the animated pelorus dial on the home page.
The sighting-compass animation lives in ts/src/ (ported from temp/components/hero.jsx → PelorusDial). It is bundled to static/js/pelorus-dial.js with esbuild. Styles: static/css/pelorus-dial.css.
cd ts
npm ci # or: npm install
npm run check # tsc --noEmit
npm run build # writes ../static/js/pelorus-dial.jsCommit the bundle so cargo run works without Node; CI rebuilds it and fails if static/js/pelorus-dial.js is out of date (git diff --exit-code).
Requires a recent Rust toolchain.
cargo runThe server listens on http://0.0.0.0:8080. Routes: / and /pelorus (same page), plus /static/* for bundled assets.
cargo testUnit tests live in src/lib.rs; integration tests in tests/http.rs. GitHub Actions (.github/workflows/ci.yml) runs TypeScript npm ci / tsc / esbuild, cargo fmt --check, cargo clippy -D warnings, and cargo test.
- Rust — CI pins
1.90.0viadtolnay/rust-toolchain@master(same line asplatform/specifications) and runscargo fmt --all -- --check,cargo clippy … --all-targets -- -D warnings,cargo test/ coverage with--all-features/--lockedwhere configured. - Node.js — CI uses
22(same line asplatform/pelorus-inspectorfrontend jobs after alignment); usenvm/fnm/ system Node when matching CI locally insidets/. ts/— Relies ontsc(npm run check) plus esbuild; there is no ESLint configuration here on purpose (npm run lintis intentionally absent — keep correctness via strict TypeScript and CI). ESLint-heavy UI work lives underpelorus-inspector.
The Rust release binary is built on the host or in CI; the Dockerfile only packages that binary plus static/ into a Distroless runtime (no compile inside Docker).
cd ts && npm ci && npm run build && cd ..
cargo build --release --locked
./scripts/prepare-image-context.sh # writes build/image/{website,static/}
docker build -f Dockerfile -t pelorus-website:local build/image
docker run --rm -p 8080:8080 pelorus-website:localbuild/ is gitignored. The binary listens on 0.0.0.0 and PORT (default 8080) for Cloud Run.
A tag matching v* or a manual Actions → Release → Run workflow runs .github/workflows/release.yml: build once, push to GHCR and Artifact Registry, deploy Cloud Run from Artifact Registry.
Cloud Run only pulls from gcr.io, REGION-docker.pkg.dev, or docker.io — not ghcr.io unless you add an Artifact Registry remote repo. This workflow pushes the same image to GHCR and Artifact Registry.
Use .github/workflows/release.yml after this. From a clean project (billing on):
gcloud config set project YOUR_PROJECT_ID
PROJECT_ID=YOUR_PROJECT_ID \
GITHUB_REPO=your-org/your-repo \
./scripts/setup-gcp.shThis enables APIs, creates a Docker Artifact Registry repo (default id website in europe-west1), Workload Identity Federation for GitHub Actions, and a deployer service account. It does not create a Cloud Run service — the Release workflow (on tag v*) or a manual gcloud run deploy does that on first push. Override region only if you need another: GCP_REGION=… ./scripts/setup-gcp.sh.
Image URL shape:
europe-west1-docker.pkg.dev/PROJECT_ID/website/website:TAG
(website twice = Artifact Registry repository id vs image name in the workflow.)
Settings → Secrets and variables → Actions (not Dependabot/Codespaces). Organization variables must be allowed for this repository.
The job uses GitHub Environment gcp by default (create it under Settings → Environments, or set repository variable GCP_ACTIONS_ENVIRONMENT to another name). Put deploy variables/secrets and WIF secrets in the same scope (repository or that environment); environment-only values are not visible without a matching environment: on the job.
Variables (preferred) or Secrets with the same names — workflow accepts either:
| Name | Example |
|---|---|
GCP_PROJECT_ID |
your project id |
GCP_REGION |
europe-west1 (match Artifact Registry + Cloud Run; avoid CRLF pastes) |
GCP_ARTIFACT_REPOSITORY |
website (Artifact Registry repository id) |
CLOUD_RUN_SERVICE |
e.g. pelorus-website (must match workflow deploy name) |
Secrets (always):
| Name | Value |
|---|---|
GCP_WORKLOAD_IDENTITY_PROVIDER |
Full provider resource name (see below) |
GCP_SERVICE_ACCOUNT |
Deployer service account email |
Normally you’d put a GCP JSON key in GitHub so Actions can call gcloud. That key is a secret you must rotate and protect.
Workload Identity Federation (WIF) avoids keys: GitHub proves who it is with a short-lived OIDC token; Google trusts GitHub and lets that workflow act as one specific Google Cloud service account — a “robot user” in your project (email ends in @…iam.gserviceaccount.com). That robot account is what people mean by the “WIF service account” here: it’s just the GCP service account you bind to the GitHub pool/provider.
Scripted setup is ./scripts/setup-gcp.sh (see One-time: GCP from scratch above). GITHUB_REPO must be the full owner/repo OIDC claim (e.g. pelorus-marine/website). The script prints WIF secrets and the GitHub Variables to set; optional env is documented in the script header.
Manual setup: follow Authenticate to Google Cloud from GitHub Actions or the google-github-actions/auth README. In outline: pool → GitHub provider → service account → IAM binding so only your repo can impersonate that SA → paste the provider resource name and SA email into the secrets above.
No JSON key is stored in GitHub.
The deploy service account needs at least:
- Artifact Registry:
roles/artifactregistry.writeron the repository (or project), to push images. - Cloud Run: e.g. Cloud Run Admin and Service Account User on the runtime service account, to deploy and invoke the service.
Source code in this repository (for example Rust sources under src/, templates under templates/, and HTML/CSS authored for this site) is licensed under the MIT License or the Apache License, Version 2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.
The Pelorus word mark, Pelorus Marine name and branding, and image assets under static/ (including pelorus-favicon-32.png) are proprietary to Pelorus Marine. All rights reserved. Those assets are not licensed under MIT or Apache-2.0.
Third-party CSS bundled with the site (Bootstrap under static/vendor/bootstrap-5.3.3/) remains under its license (Bootstrap: MIT).
The display face Operation Napalm (Regular), file static/fonts/operation-napalm-regular.woff2, comes from the fonts-cc0 collection: folder Operation Napalm → Web Open Font Format (.woff) → OperationNapalm-Regular.woff2. That collection is under CC0 1.0 Universal (public domain dedication). No attribution is required; the font is provided as-is with no warranty.