198 lines
8.3 KiB
Markdown
198 lines
8.3 KiB
Markdown
# Deployment
|
|
|
|
How a new version of the website ends up on Unraid. Current state:
|
|
**manual** — build the image locally, push it to the Gitea registry, pull it on
|
|
Unraid. Automation via Gitea Actions comes later.
|
|
|
|
The site runs as a **two-container stack** (project `eb-stack`): the Blazor app
|
|
(`eb-blazor`) plus a PocketBase container (`eb-pocketbase`) that holds the
|
|
content. The stack is described by `compose.yaml` (the production base) with
|
|
overlays layered on top.
|
|
|
|
> **Running it locally is a separate document.** For starting the stack on your
|
|
> own machine and for how the tests run, see `entwicklung.md`. This document is
|
|
> only about getting a built image onto Unraid.
|
|
|
|
---
|
|
|
|
## Prerequisites (one-time)
|
|
|
|
### Gitea registry token
|
|
|
|
The push to the registry needs a Gitea access token with **package write
|
|
permission** (`package: Read and Write`) — **not** the account password.
|
|
|
|
1. Gitea → top right profile picture → **Settings** → **Applications**.
|
|
2. **Manage Access Tokens** section: assign a name (e.g. `registry-push`).
|
|
3. Under **Select scopes**: set `package` to **Read and Write**.
|
|
4. Click **Generate Token**, **copy the string immediately** (shown only once).
|
|
|
|
> **The token is a secret.** Never store it in plain text in Git, chats,
|
|
> screenshots, or tickets. If one does become visible: **delete** it in Gitea and
|
|
> generate a new one. A `package` token allows uploading arbitrary images to the
|
|
> registry.
|
|
|
|
The Docker login then stores the token locally, so it only has to be entered once.
|
|
|
|
---
|
|
|
|
## Way of working: branches, never directly on main
|
|
|
|
`main` is the **published** state — only `:latest` is built from it, and only
|
|
`:latest` is pulled by Unraid. That is why we never commit directly to `main`:
|
|
|
|
1. Create a feature branch: `git switch -c <area>/<short>` (e.g.
|
|
`deployment/sha-tagging`, `content/pipeline`).
|
|
2. Commit there, push the branch: `git push -u origin <branch>`.
|
|
3. Open a **Pull Request** against `main` in Gitea and merge it there.
|
|
4. Only then build the release image from `main` (below).
|
|
|
|
**Dev builds stay local.** For trying things out on your own machine, run the
|
|
dev stack (`entwicklung.md`) — it builds the web image from source and pushes
|
|
nothing, so a development state can never accidentally land in the registry as
|
|
`:latest`.
|
|
|
|
## Rolling out a new version (manual)
|
|
|
|
Prerequisite: logged in to the registry once (see below). Then, **on main** and
|
|
with a clean working directory:
|
|
|
|
```bash
|
|
scripts/release.sh
|
|
```
|
|
|
|
The script builds the image, tags it with `:latest` **and** the short commit SHA
|
|
(for rollback), and pushes both. It **aborts** if you are not on `main` or have
|
|
uncommitted changes, and warns about unpushed commits.
|
|
|
|
> The image path `gitea.anticarnist.de/tom/elternbeirat` is **lowercase** —
|
|
> container registries require that in the path, even though the user (`Tom`) and
|
|
> the repo (`Elternbeirat`) are capitalized.
|
|
|
|
### Logging Unraid in to the registry once
|
|
|
|
The image is **private**, so Unraid has to log in once before it can pull. The
|
|
**Compose Manager Plus** plugin has no UI field for this — the login runs through
|
|
the Unraid terminal (`>_` symbol at the top right of the web interface, prompt
|
|
`root@Cube:~#`):
|
|
|
|
```bash
|
|
docker login gitea.anticarnist.de
|
|
# Username: Tom
|
|
# Password: <package token>
|
|
```
|
|
|
|
The login stays stored; it only has to be repeated when the token changes. Two
|
|
pitfalls learned from experience:
|
|
|
|
- **Don't confuse it with PowerShell/laptop.** The login has to happen in the
|
|
*Unraid* terminal (`root@Cube`), not in Windows PowerShell (`PS C:\`). The laptop
|
|
needs the login only to *push*, Unraid to *pull*.
|
|
- **A wrong username gets stuck.** If the login reports "Stored credentials
|
|
invalid or expired" and does *not* ask for the name, first `docker logout
|
|
gitea.anticarnist.de`, then log in again — otherwise a nonsense username gets
|
|
stored by accident.
|
|
- **Plain-text warning.** Docker stores the token unencrypted in
|
|
`/root/.docker/config.json`. On your own server, okay for a start.
|
|
*Cleaner later:* set up a credential helper (→ open item below).
|
|
|
|
### Pull and start again on Unraid
|
|
|
|
In the **Compose Manager Plus** plugin (Unraid web interface):
|
|
|
|
- **Docker** tab → **Compose** section → stack **eb-stack** →
|
|
**Compose Down**, then **Compose Up** (or "Pull" + "Up", depending on the plugin
|
|
version), so that the new `:latest` is pulled. Only the `eb-blazor` image
|
|
changes on a deploy; `eb-pocketbase` and its `pb_data` volume stay as they are.
|
|
|
|
> `docker compose up` does **not** automatically re-pull a `:latest` if an image of
|
|
> the same name is already present locally. When in doubt, explicitly "Pull" first.
|
|
|
|
---
|
|
|
|
## The compose files
|
|
|
|
`compose.yaml` is the **production** base and is the file Unraid uses. It defines
|
|
both services with no host port mappings — in production NPM is the only path to
|
|
the web app, and the editors reach PocketBase through an NPM subdomain (see #9).
|
|
`eb-pocketbase` keeps its data on the bind mount
|
|
`/mnt/user/appdata/pocketbase/pb_data`, which only exists on the server.
|
|
|
|
The two overlays (`compose.dev.yaml`, `compose.test.yaml`) are for local
|
|
development and the tests and are **not** used on Unraid — see `entwicklung.md`.
|
|
|
|
> **NPM network (still open).** The base does not yet attach `eb-blazor` to the
|
|
> external NPM Docker network; while the domain has not moved, add a temporary
|
|
> `ports:` mapping (e.g. `5000:8080`) to reach the app on the LAN, and swap it for
|
|
> the `npm` external network once NPM sits in front. Tracked with #9.
|
|
|
|
---
|
|
|
|
## Outage: PocketBase unreachable
|
|
|
|
If `eb-pocketbase` is down while `eb-blazor` runs, the app does not render
|
|
half-empty pages (the menu comes from PocketBase too). A gate in `Program.cs`
|
|
answers every **page** request with the static `wwwroot/maintenance.html`:
|
|
|
|
| Request | Answer while PocketBase is down |
|
|
|---|---|
|
|
| Any page (`/`, `/board`, `/posts/…`, unknown slugs) | **503**, `Retry-After: 60`, maintenance page |
|
|
| `/health` | **503** `PocketBase unreachable` (for Uptime Kuma) |
|
|
| `/events.ics` | 200 with an empty calendar, so subscriptions keep working |
|
|
| `/events/{id}.ics` | 503 |
|
|
| Static files (CSS, fonts, PDFs, `/maintenance.html`) | served as usual |
|
|
|
|
The gate caches the health check for **10 seconds** (`PocketBaseHealthCache`). So
|
|
after PocketBase stops, pages can render for up to 10 s more (the components
|
|
degrade on their own in that window: empty menu, a short note, 503), and after it
|
|
starts again the maintenance page can stay up for up to 10 s. No restart of the
|
|
web app is needed — the site comes back by itself.
|
|
|
|
`maintenance.html` carries its styles inline, as a copy of the tokens from
|
|
`app.css`. When colours change there, copy them over.
|
|
|
|
---
|
|
|
|
## Rollback
|
|
|
|
`scripts/release.sh` additionally tags each release with the short commit SHA,
|
|
which — unlike the moving `:latest` — never shifts. To roll back, replace
|
|
`:latest` in the Unraid `compose.yaml` with the `:<sha>` of the last working state
|
|
and bring it back up:
|
|
|
|
```yaml
|
|
image: gitea.anticarnist.de/tom/elternbeirat:99dd052 # instead of :latest
|
|
```
|
|
|
|
Which SHA tags are in the registry is shown by Gitea under
|
|
`Tom/-/packages` → `elternbeirat`.
|
|
|
|
---
|
|
|
|
## Automation
|
|
|
|
### Build and push (done)
|
|
|
|
`.gitea/workflows/deploy.yml` builds the image on every push to `main` and pushes
|
|
it to the registry, tagged `:latest` **and** `:<short-sha>` — the same scheme as
|
|
`scripts/release.sh`, just run by the `act_runner` instead of by hand. It does
|
|
**not** deploy; pulling and starting the new `:latest` on Unraid stays a manual,
|
|
deliberate step (see "Pull and start again on Unraid" above).
|
|
|
|
Requires two repository secrets (Gitea → repo → **Settings** → **Actions** →
|
|
**Secrets**):
|
|
|
|
- `REGISTRY_USER` — the registry username (e.g. `Tom`).
|
|
- `REGISTRY_TOKEN` — a Gitea access token with **`package: write`**, *not* the
|
|
login password.
|
|
|
|
The workflow currently only builds and pushes — it does **not** run the tests
|
|
yet, and it does **not** deploy. Those open steps (tests in the workflow,
|
|
auto-deploy with a health gate via Watchtower, encrypting the registry token on
|
|
Unraid) are tracked as Gitea issues under the "Elternbeirat-Website" milestone.
|
|
|
|
Two known pitfalls: the `act_runner` needs Docker socket access to build, and it
|
|
must offer the `ubuntu-latest` label the workflow asks for. To check the runner:
|
|
Gitea → repo (or site admin) → **Settings** → **Actions** → **Runners** — it
|
|
should be listed as **online** with a label set that includes `ubuntu-latest`.
|