build: imaget hentes fra vores egen Gitea, og runen peger derind

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>
This commit is contained in:
Kristian Wind
2026-08-21 16:53:18 +02:00
co-authored by Claude Opus 5
parent 8d5dd1f1c3
commit 20b8c12b4a
7 changed files with 113 additions and 18 deletions
+67
View File
@@ -0,0 +1,67 @@
name: Release
# Samme udløser som GitHub-workflowet: et semver-tag, og ikke andet.
#
# Gitea Actions læser samme format som GitHub Actions og kan bruge handlinger
# derfra, men det er *ikke* det samme sted. Filen ligger derfor for sig i
# `.gitea/workflows/` frem for at være delt: `secrets.GITHUB_TOKEN` findes ikke
# her, 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.
on:
push:
tags: ["v[0-9]+.[0-9]+.[0-9]+*"]
jobs:
image:
name: Build and publish image
# `ubuntu-latest` betyder her det, runneren er sat op til at kalde det.
# Registreres runneren med en anden etiket, skal denne linje følge med.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
# `GITEA_TOKEN` er automatisk til stede i et Actions-job og har lov at
# skrive pakker for den, der ejer repoet. Ingen hemmelighed at sætte op.
- uses: docker/login-action@v3
with:
registry: gitea.nolimit.dk
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: gitea.nolimit.dk/${{ gitea.repository }}
# v1.4.2 udgiver 1.4.2, 1.4 og 1 — så en rune, der er bundet til :1, får
# rettelser uden nogensinde at krydse et hovedversionsskifte.
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
# arm64 er ikke valgfrit: et hjemmelab er lige så tit en Raspberry Pi eller
# en Apple silicon-VM som en x86-kasse.
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ gitea.ref_name }}
# Ingen `cache-from: type=gha`. Den cache er GitHubs tjeneste, og den
# findes ikke her — bedt om den, fejler trinnet på noget, der ikke har
# med bygningen at gøre. Uden cache tager en kold bygning nogle minutter
# længere, og det er prisen, indtil runneren har en cache af sin egen.
# Ingen Release oprettes.
#
# GitHub-workflowet laver en Release, fordi opdateringsnotitsen i appen læser
# `releases/latest` derfra. Gitea har sit eget Releases-begreb, men notitsen
# peger på GitHub — så en release her ville ingen læse. Skal notitsen flyttes
# med, er det `internal/update`, der skal have en ny adresse, og det er en
# ændring for sig.
+1 -1
View File
@@ -171,7 +171,7 @@ docker run -d --name verdande \
-p 8080:8080 \ -p 8080:8080 \
-v verdande-data:/data \ -v verdande-data:/data \
-e VERDANDE_BASE_URL=https://todo.example.dk \ -e VERDANDE_BASE_URL=https://todo.example.dk \
ghcr.io/kristianwind/verdande:latest gitea.nolimit.dk/kw/verdande:latest
``` ```
### From source ### From source
+1 -1
View File
@@ -72,7 +72,7 @@ docker run -d --name verdande \
-p 8080:8080 \ -p 8080:8080 \
-v verdande-data:/data \ -v verdande-data:/data \
-e VERDANDE_BASE_URL=https://todo.example.dk \ -e VERDANDE_BASE_URL=https://todo.example.dk \
ghcr.io/kristianwind/verdande:latest gitea.nolimit.dk/kw/verdande:latest
``` ```
Open the address, create the first account — that account is the administrator. Open the address, create the first account — that account is the administrator.
+19 -8
View File
@@ -7,14 +7,14 @@ verdande ships with a Rune manifest for
``` ```
create container: Error response from daemon: create container: Error response from daemon:
No such image: ghcr.io/kristianwind/verdande:latest No such image: gitea.nolimit.dk/kw/verdande:latest
``` ```
This message is a liar. It does not mean the tag is missing from the registry — This message is a liar. It does not mean the tag is missing from the registry —
it means the image is not on the host, and the panel's attempt to fetch it failed it means the image is not on the host, and the panel's attempt to fetch it failed
without saying so. without saying so.
The usual cause is a **private** GHCR package. Yggdrasil pulls anonymously: its The usual cause is a **private** package. Yggdrasil pulls anonymously: its
`PullImage` calls the Docker SDK with no `RegistryAuth`, so a private image comes `PullImage` calls the Docker SDK with no `RegistryAuth`, so a private image comes
back 401. The pull error is discarded, and `ContainerCreate` — which only ever back 401. The pull error is discarded, and `ContainerCreate` — which only ever
looks locally — then reports the image as missing. looks locally — then reports the image as missing.
@@ -24,10 +24,21 @@ looks locally — then reports the image as missing.
sends them itself with each request. Yggdrasil is a different client and sends them itself with each request. Yggdrasil is a different client and
sends none, so logging in changes nothing for the panel. sends none, so logging in changes nothing for the panel.
**Make the package public.** Under **The package has to be readable without credentials.** On Gitea that does not
[Packages → verdande → Package settings](https://github.com/users/kristianwind/packages/container/package/verdande) mean making the source repository public: a container package is only tied to a
→ *Change visibility*. The repository stays private; only the built image becomes repository when it is pushed with a repository link, and this one is not — so its
readable, which is what the manifest and these pages have always assumed. visibility follows the account, which is public, while `kw/verdande` stays
private.
Check it the way a puller does, with no credentials at all:
```bash
docker --config /tmp/empty pull gitea.nolimit.dk/kw/verdande:latest
```
If that works, the panel can pull it. A plain `curl` against `/v2/…/manifests/…`
answers 401 even for a readable image — the registry hands out an anonymous token
first — so a bare curl is not the test.
Once it is public, press **Start** on the server you already have. There is no Once it is public, press **Start** on the server you already have. There is no
need to delete it and make another: the image reference is read from the rune at need to delete it and make another: the image reference is read from the rune at
@@ -37,8 +48,8 @@ To keep the package private you have to put the image on the host yourself,
before creating the server, and again after every release: before creating the server, and again after every release:
```bash ```bash
sudo docker login ghcr.io -u kristianwind sudo docker login gitea.nolimit.dk -u kw
sudo docker pull ghcr.io/kristianwind/verdande:latest sudo docker pull gitea.nolimit.dk/kw/verdande:latest
``` ```
Run it as the user the Docker daemon runs as — `sudo` if the panel does. The Run it as the user the Docker daemon runs as — `sudo` if the panel does. The
+6 -5
View File
@@ -6,9 +6,10 @@ cache, no queue and no second process.
## Docker ## Docker
!!! note "If the pull is refused" !!! note "If the pull is refused"
The GHCR package is public, so this needs no credentials. If it is ever set The image is published from a self-hosted Gitea and is readable without
back to private, `docker login ghcr.io` with a token carrying the credentials — the package is not linked to a repository, so its visibility
`read:packages` scope before pulling — and see 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 [the Rune page](rune.md#if-the-image-will-not-pull), because a private image
fails differently and much more confusingly under Yggdrasil. fails differently and much more confusingly under Yggdrasil.
@@ -18,7 +19,7 @@ docker run -d --name verdande \
-p 8080:8080 \ -p 8080:8080 \
-v verdande-data:/data \ -v verdande-data:/data \
-e VERDANDE_BASE_URL=https://todo.example.dk \ -e VERDANDE_BASE_URL=https://todo.example.dk \
ghcr.io/kristianwind/verdande:latest gitea.nolimit.dk/kw/verdande:latest
``` ```
`VERDANDE_BASE_URL` has to be the address you actually reach it on. Invite links, `VERDANDE_BASE_URL` has to be the address you actually reach it on. Invite links,
@@ -30,7 +31,7 @@ means the links in your email point somewhere that does not answer.
```yaml ```yaml
services: services:
verdande: verdande:
image: ghcr.io/kristianwind/verdande:latest image: gitea.nolimit.dk/kw/verdande:latest
restart: unless-stopped restart: unless-stopped
ports: ports:
- "8080:8080" - "8080:8080"
+18 -2
View File
@@ -15,14 +15,30 @@ gameskill:
# this: a manifest with new fields and an unchanged version is a manifest the # this: a manifest with new fields and an unchanged version is a manifest the
# panel has no reason to re-read, so the fields never appear and the failure is # panel has no reason to re-read, so the fields never appear and the failure is
# silent — the server's settings page simply does not offer them. # silent — the server's settings page simply does not offer them.
version: 4 # Bumpet til 5, fordi imagets adresse flyttede. En rune opdaterer ikke sig selv:
# panelet skriver rune-rækken fra en UI-handling, så ændringen her kræver
# **Runes → verdande → Update** — og katalog-listen caches ti minutter, så to
# forsøg lige efter hinanden kan begge give den gamle version.
version: 5
icon: "app" icon: "app"
# The image is distroless — no shell — so keep_entrypoint runs the binary the # The image is distroless — no shell — so keep_entrypoint runs the binary the
# image already declares as its ENTRYPOINT, and startup.exec passes it argv # image already declares as its ENTRYPOINT, and startup.exec passes it argv
# directly rather than through /bin/sh, which does not exist in there. # directly rather than through /bin/sh, which does not exist in there.
# Hentes fra vores egen Gitea frem for fra GHCR.
#
# Panelets `PullImage` sætter aldrig `RegistryAuth`, så registret skal kunne
# læses anonymt. Det kan det her: pakken er *ikke* knyttet til repoet — Gitea
# binder kun en container-pakke til et repo, når den bliver skubbet med et
# repo-link — så dens synlighed følger brugeren `kw`, som er offentlig, mens
# `kw/verdande` bliver ved med at være privat. Bevist med en `docker pull` uden
# nogen legitimation, som hentede samme digest som GHCR havde.
#
# Skubbes den nogensinde på en måde, der knytter den til repoet, bliver den
# privat med det samme, og panelet får `No such image` — lydløst, fordi
# `pullImageRetry` kasserer fejlen og `ContainerCreate` først slår op lokalt.
docker: docker:
image: "ghcr.io/kristianwind/verdande:latest" image: "gitea.nolimit.dk/kw/verdande:latest"
data_path: /data data_path: /data
keep_entrypoint: true keep_entrypoint: true
+1 -1
View File
@@ -1238,7 +1238,7 @@
-p 8080:8080 \ -p 8080:8080 \
-v verdande-data:/data \ -v verdande-data:/data \
-e VERDANDE_BASE_URL=https://todo.example.dk \ -e VERDANDE_BASE_URL=https://todo.example.dk \
ghcr.io/kristianwind/verdande:latest</code></pre> gitea.nolimit.dk/kw/verdande:latest</code></pre>
</div> </div>
</div> </div>