GitHub er utilgængeligt i ti dage. Koden ligger allerede begge steder; det, der manglede, var imaget og et sted at bygge det. **Imaget ligger i Gitea nu**, begge arkitekturer. Kopieret med `docker buildx imagetools create`, som flytter hele manifest-indekset mellem to registre uden at hente lagene ned — en `docker pull` på denne maskine ville have hentet arm64 og efterladt serveren uden en amd64 at køre. **Og det kan hentes uden legitimation, mens repoet bliver ved med at være privat.** Det var ikke givet: panelets `PullImage` sætter aldrig `RegistryAuth`, så et privat register er udelukket, og Gitea gør normalt en container-pakke privat sammen med det repo, den hører til. Men en pakke bindes kun til et repo, når den skubbes med et repo-link — og denne er ikke, så dens synlighed følger brugeren `kw`, som er offentlig. Bevist frem for antaget: `docker --config /tmp/tom pull` hentede den med samme digest som GHCR havde. En bar `curl` mod `/v2/…/manifests/…` svarer 401 også for et læsbart image, fordi registret uddeler et anonymt token først — så den er ikke prøven, og den kostede en runde at gennemskue. **Runen er bumpet til version 5.** Ikke fordi felterne ændrede sig, men fordi en rune ikke opdaterer sig selv: panelet skriver rune-rækken fra en UI-handling, så ændringen kræver **Runes → verdande → Update**, og katalog-listen caches ti minutter. **Et Actions-workflow til Gitea** ligger i `.gitea/workflows/`. Sin egen fil frem for en delt: `secrets.GITHUB_TOKEN` findes ikke der, registret er et andet, og der er ingen Releases-side at skrive til. En delt fil ville være fire betingede led, der hver kun gælder ét af de to steder. Den laver ingen release, fordi opdateringsnotitsen i appen læser GitHubs — skal den flyttes med, er det `internal/update`, der skal have en ny adresse. **Det virker ikke endnu:** der er ingen runner registreret på Gitea. Indtil der er, bygges og skubbes imaget i hånden herfra. Dokumentationen og landingssiden siger den nye adresse. Rune-siden er skrevet om, hvor den fortalte om GHCR-pakkeindstillinger, og siger nu, hvordan man prøver efter på den måde, der faktisk svarer på spørgsmålet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
3.5 KiB
Markdown
117 lines
3.5 KiB
Markdown
# Self-hosting
|
|
|
|
verdande is one static binary and one directory. There is no database server, no
|
|
cache, no queue and no second process.
|
|
|
|
## Docker
|
|
|
|
!!! note "If the pull is refused"
|
|
The image is published from a self-hosted Gitea and is readable without
|
|
credentials — the package is not linked to a repository, so its visibility
|
|
follows the account rather than the (private) source repo. If it is ever set
|
|
back to private, `docker login gitea.nolimit.dk` before pulling — and see
|
|
[the Rune page](rune.md#if-the-image-will-not-pull), because a private image
|
|
fails differently and much more confusingly under Yggdrasil.
|
|
|
|
```bash
|
|
docker run -d --name verdande \
|
|
--restart unless-stopped \
|
|
-p 8080:8080 \
|
|
-v verdande-data:/data \
|
|
-e VERDANDE_BASE_URL=https://todo.example.dk \
|
|
gitea.nolimit.dk/kw/verdande:latest
|
|
```
|
|
|
|
`VERDANDE_BASE_URL` has to be the address you actually reach it on. Invite links,
|
|
password resets and calendar feeds are all built from it, so getting it wrong
|
|
means the links in your email point somewhere that does not answer.
|
|
|
|
## Docker Compose
|
|
|
|
```yaml
|
|
services:
|
|
verdande:
|
|
image: gitea.nolimit.dk/kw/verdande:latest
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8080:8080"
|
|
volumes:
|
|
- ./data:/data
|
|
environment:
|
|
VERDANDE_BASE_URL: https://todo.example.dk
|
|
VERDANDE_SMTP_HOST: mail.example.dk
|
|
VERDANDE_SMTP_USER: verdande@example.dk
|
|
VERDANDE_SMTP_PASS: ${SMTP_PASSWORD}
|
|
VERDANDE_SMTP_FROM: verdande@example.dk
|
|
```
|
|
|
|
## Behind a reverse proxy
|
|
|
|
verdande expects to be terminated by something else. It reads `X-Forwarded-For`,
|
|
so rate limiting and the activity log see the real caller rather than the proxy.
|
|
|
|
=== "Caddy"
|
|
|
|
```
|
|
todo.example.dk {
|
|
reverse_proxy localhost:8080
|
|
}
|
|
```
|
|
|
|
=== "Cloudflare Tunnel"
|
|
|
|
```yaml
|
|
ingress:
|
|
- hostname: todo.example.dk
|
|
service: http://localhost:8080
|
|
- service: http_status:404
|
|
```
|
|
|
|
The WebSocket needs no special handling; Cloudflare proxies it as-is. A silent
|
|
socket is closed after 100 seconds, which is why verdande pings every 30.
|
|
|
|
!!! warning "Use HTTPS"
|
|
Over plain HTTP the session cookie cannot be `Secure`, and verdande will not
|
|
use the `__Host-` prefix that stops a subdomain overwriting it. Fine on a
|
|
laptop; not fine on anything reachable from outside.
|
|
|
|
## First run
|
|
|
|
Open the address and create an account. The first one is the administrator, and
|
|
the endpoint that creates it refuses once any account exists — so there is no
|
|
window in which somebody else can claim your instance.
|
|
|
|
Everybody after that joins by invite.
|
|
|
|
## What lives in `/data`
|
|
|
|
```
|
|
/data
|
|
├── verdande.db SQLite, WAL mode
|
|
├── files/ attachments, content-addressed
|
|
└── backups/ nightly snapshots, 14 kept
|
|
```
|
|
|
|
Back up the whole directory.
|
|
|
|
!!! danger "Do not copy verdande.db on its own"
|
|
It runs in WAL mode, so recent writes live in `verdande.db-wal` until they are
|
|
checkpointed. A copy of the `.db` file alone can be missing them. Copy the
|
|
whole directory, or take one of the nightly snapshots from `backups/` — those
|
|
are written with `VACUUM INTO` and are complete on their own.
|
|
|
|
## Backups
|
|
|
|
A snapshot is written once a day and the most recent fourteen are kept. They are
|
|
counted rather than aged out, so a container that was off for a month comes back
|
|
with its backups intact.
|
|
|
|
## Updating
|
|
|
|
```bash
|
|
docker compose pull && docker compose up -d
|
|
```
|
|
|
|
Migrations run at startup, each in its own transaction. Take a copy of `/data`
|
|
first anyway.
|