diff --git a/.gitea/ISSUE_TEMPLATE/bug.yml b/.gitea/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..5af85c9 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,46 @@ +name: Bug +# Gitea kalder feltet `about`, hvor GitHub kalder det `description`, og Gitea +# *kræver* det: en skabelon uden bliver afvist frem for at blive vist. Derfor +# ligger skabelonerne her for sig frem for at være delt med `.github/`. +about: Something does not work +labels: [bug] +body: + - type: textarea + id: what + attributes: + label: What happened + description: And what you expected instead. + validations: + required: true + - type: textarea + id: steps + attributes: + label: How to reproduce it + placeholder: | + 1. … + 2. … + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: The image tag, or the commit you built from. + validations: + required: true + - type: dropdown + id: how + attributes: + label: How are you running it + options: + - As a Rune in Yggdrasil Panel + - Docker directly + - From source + validations: + required: true + - type: textarea + id: logs + attributes: + label: Anything in the logs + description: Please remove tokens and email addresses first. + render: text diff --git a/.gitea/ISSUE_TEMPLATE/config.yml b/.gitea/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a038c9a --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security issue + url: https://gitea.nolimit.dk/kw/verdande/src/branch/main/SECURITY.md + about: Please report vulnerabilities by email, not as a public issue. diff --git a/.gitea/ISSUE_TEMPLATE/feature.yml b/.gitea/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000..ebf7f96 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,24 @@ +name: Feature +about: Suggest something +labels: [enhancement] +body: + - type: markdown + attributes: + # Absolut adresse, ikke GitHubs `../../blob/main/…`: Gitea lægger en fil + # under `/src/branch//`, så den relative form peger ingen steder her. + value: | + Please check the [non-goals](https://gitea.nolimit.dk/kw/verdande/src/branch/main/README.md#non-goals) + first — some things are deliberately not being built. + - type: textarea + id: problem + attributes: + label: What are you trying to do + description: The problem, not the solution. What does the current version make hard? + validations: + required: true + - type: textarea + id: idea + attributes: + label: What you had in mind + validations: + required: false diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..698bc08 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,196 @@ +name: CI + +# Gitea-tvillingen til .github/workflows/ci.yml. Den prøver at nå samme resultat +# ad en anden vej, og forskellen er hele pointen med filen: +# +# INGEN `uses:`. Gitea Actions kan læse GitHubs format, men `DEFAULT_ACTIONS_URL` +# står ikke i app.ini på gitea.nolimit.dk, og standarden er github.com — så et +# `uses: actions/checkout@v4` her ville blive hentet FRA GitHub, hver gang, også +# selvom både koden, runneren og registret er vores egne. Et workflow, der bruger +# markedspladsen, er derfor ikke uafhængigt af GitHub; det ser bare sådan ud. +# +# Prisen er, at værktøjskæden skal komme et sted fra. Den kommer fra jobbets +# container, hvilket er billigere end det lyder: `container: golang:1.26` er +# nøjagtig det, setup-go ellers ville bruge et trin på at installere. +# +# Alternativerne, hvis nogen senere vil skrive filen om: sæt DEFAULT_ACTIONS_URL +# til gitea.com, som spejler de gængse handlinger — men det flytter kun +# afhængigheden fra én andens server til en andens, og det er en indstilling for +# HELE instansen, altså også for alt andet, der bygger der. Denne fil rører kun +# dette repo. + +on: + push: + branches: [main] + pull_request: + +# Ingen `concurrency:`-blok. GitHub-udgaven aflyser en kørsel, når en nyere +# kommer, og det er stadig det rigtige at gøre — men understøttelsen kom sent til +# Gitea Actions, og et felt, runneren ikke kender, er en fejl ved oversættelsen +# frem for en ignoreret linje. Den kan sættes på, når instansen er en version, +# der kan. + +jobs: + go: + name: Go + runs-on: ubuntu-latest + # Containeren ER værktøjskæden. `-race` kræver en C-oversætter, og den er med + # i det officielle golang-image — et slankere ville koste et apt-trin. + container: + image: golang:1.26 + steps: + - name: Checkout + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -eu + # Håndrullet, fordi actions/checkout ville blive hentet fra GitHub. + # Det er de samme fire greb, den handling tager: init, remote, en + # fetch af præcis den commit, der udløste kørslen, og et checkout. + # + # Legitimationen sættes med -c på selve fetch-kommandoen frem for i + # remotens adresse. En adresse med token i havner i .git/config, i + # `git remote -v` og i enhver fejlbesked, der citerer den; en header + # gør ikke. + git config --global --add safe.directory '*' + git init -q . + git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + git -c http.extraheader="Authorization: Basic $(printf '%s:%s' "$GITHUB_ACTOR" "$TOKEN" | base64 | tr -d '\n')" \ + fetch -q --depth=1 origin "$GITHUB_SHA" + git checkout -q FETCH_HEAD + + - name: Formatting + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "These files are not gofmt'd:" + echo "$unformatted" + exit 1 + fi + + - name: Vet + run: go vet ./... + + # -race is the whole point of running tests in CI for a server with a + # WebSocket fan-out: a data race is exactly the bug that passes locally. + - name: Test + run: go test -race -count=1 ./... + + - name: Build + run: CGO_ENABLED=0 go build ./... + + spec: + name: OpenAPI is valid + runs-on: ubuntu-latest + container: + image: node:22 + steps: + - name: Checkout + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -eu + git config --global --add safe.directory '*' + git init -q . + git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + git -c http.extraheader="Authorization: Basic $(printf '%s:%s' "$GITHUB_ACTOR" "$TOKEN" | base64 | tr -d '\n')" \ + fetch -q --depth=1 origin "$GITHUB_SHA" + git checkout -q FETCH_HEAD + + # Structural validity only. That the spec matches the *router* is checked by + # a Go test, which is the half that actually goes stale. + - run: npx --yes @redocly/cli@latest lint docs/openapi.yaml + + e2e: + name: End-to-end + runs-on: ubuntu-latest + # node frem for golang her: Playwrights browsere trækker et lag + # Debian-pakker, som npm-imaget er vant til at have, mens Go til gengæld er + # fire linjer at hælde ind. Den anden vej rundt er den dyre. + container: + image: node:22 + steps: + - name: Checkout + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -eu + git config --global --add safe.directory '*' + git init -q . + git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + git -c http.extraheader="Authorization: Basic $(printf '%s:%s' "$GITHUB_ACTOR" "$TOKEN" | base64 | tr -d '\n')" \ + fetch -q --depth=1 origin "$GITHUB_SHA" + git checkout -q FETCH_HEAD + + - name: Go + run: | + set -eu + # Versionen læses ud af go.mod frem for at stå her. To steder at rette + # er ét for mange, og den slags glemmes lige indtil CI bygger med en + # anden Go end udviklerne. + version=$(awk '/^go /{print $2; exit}' go.mod) + arch=$(dpkg --print-architecture) + curl -fsSL "https://go.dev/dl/go${version}.linux-${arch}.tar.gz" | tar -xz -C /usr/local + echo /usr/local/go/bin >> "$GITHUB_PATH" + + - name: Install + working-directory: web + run: npm ci --no-audit --no-fund + + # Chromium for the suite, WebKit for one test. + # + # A smoke test exists to catch a build that does not work at all, and running + # the whole thing in three engines finds the same breakage three times for + # three times the cost. Focus is the exception, and it took a real report to + # learn it: Safari ignores `autofocus` on anything inserted after the page has + # loaded, so every inline form in the app opened with no cursor in it. WebKit + # here is the same engine Safari is, and `webkit-forms` in the Playwright + # config greps a single test out of the suite to run in it. + - name: Install browsers + working-directory: web + run: npx playwright install --with-deps chromium webkit + + # The suite builds the frontend, embeds it and starts the real binary — see + # playwright.config.js. There is nothing to build here first. + - name: Test + working-directory: web + run: npm run test:e2e + + # Ingen rapport at hente ned bagefter. GitHub-udgaven lægger + # web/playwright-report/ op som et artefakt, og det trin ER en handling fra + # markedspladsen — der er ikke en `run:`-form af det. Går suiten i stykker + # her, står grunden i loggen frem for i en zip. Skal rapporten tilbage, er + # det upload-artifact spejlet ind lokalt, ikke en linje mere i denne fil. + + docker: + name: Docker image builds + runs-on: ubuntu-latest + # Ingen `container:` her: jobbet skal bruge docker-klienten og buildx, og dem + # har runnerens eget ubuntu-image. En golang- eller node-container ville have + # sproget og mangle værktøjet. + steps: + - name: Checkout + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -eu + git config --global --add safe.directory '*' + git init -q . + git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + git -c http.extraheader="Authorization: Basic $(printf '%s:%s' "$GITHUB_ACTOR" "$TOKEN" | base64 | tr -d '\n')" \ + fetch -q --depth=1 origin "$GITHUB_SHA" + git checkout -q FETCH_HEAD + + # Build only — publishing happens on a tag, in release.yml. This job exists + # so a Dockerfile that no longer builds fails on the pull request rather + # than at release time. + # + # `type=cacheonly` frem for `--load`: resultatet skal ingen steder hen, og + # at hente et image ind i daemonen for straks at glemme det er et halvt + # minut hver kørsel. Én arkitektur er nok til at fange en Dockerfile, der + # ikke bygger; de to rigtige bygges på et mærkat. + - name: Build + run: | + set -eu + docker buildx create --use --name ci-verdande 2>/dev/null || docker buildx use ci-verdande + docker buildx build --platform linux/amd64 --output type=cacheonly . diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 7e612fe..d21fb36 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -2,11 +2,14 @@ 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. +# Gitea Actions læser samme format som GitHub Actions, men det er *ikke* det +# samme sted. Filen ligger derfor for sig frem for at være delt: der er intet +# `secrets.GITHUB_TOKEN`, 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. +# +# Og som i ci.yml: ingen `uses:`. Handlinger hentes fra github.com, så længe +# DEFAULT_ACTIONS_URL ikke er sat — se den lange forklaring der. on: push: tags: ["v[0-9]+.[0-9]+.[0-9]+*"] @@ -14,54 +17,92 @@ on: 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. + # `ubuntu-latest` er runnerens egen etiket (`ovh-1`, docker-mode). Ingen + # `container:`: jobbet skal bruge docker-klienten og buildx, og dem har + # runnerens image. runs-on: ubuntu-latest + env: + REGISTRY: gitea.nolimit.dk steps: - - uses: actions/checkout@v4 + - name: Checkout + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -eu + git config --global --add safe.directory '*' + git init -q . + git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + git -c http.extraheader="Authorization: Basic $(printf '%s:%s' "$GITHUB_ACTOR" "$TOKEN" | base64 | tr -d '\n')" \ + fetch -q --depth=1 origin "$GITHUB_SHA" + git checkout -q FETCH_HEAD - - uses: docker/setup-qemu-action@v3 - - uses: docker/setup-buildx-action@v3 + # setup-qemu-action i rå form. Uden den bygger buildx kun værtens egen + # arkitektur, og arm64 er ikke valgfrit: et hjemmelab er lige så tit en + # Raspberry Pi eller en Apple silicon-VM som en x86-kasse. + - name: Emulation for the other architecture + run: docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 + + - name: Builder + run: docker buildx create --use --name release-verdande 2>/dev/null || docker buildx use release-verdande # `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 }} + # --password-stdin frem for --password: det sidste ville stå i processens + # kommandolinje, som enhver på maskinen kan læse. + - name: Log in to the registry + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: printf '%s' "$TOKEN" | docker login "$REGISTRY" -u "$GITHUB_ACTOR" --password-stdin - - 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 + # metadata-action i rå form. + # + # v1.4.2 udgiver 1.4.2, 1.4, 1 og latest — så en rune, der er bundet til + # :1, får rettelser uden nogensinde at krydse et hovedversionsskifte. + # + # En forudgivelse (v1.4.2-rc1) får KUN sit eget nøjagtige mærkat. At lade + # en rc overskrive :latest eller :1 ville sende den ud til alle, der troede + # de fulgte det stabile spor — og mærkatmønsteret ovenfor lukker dem ind. + - name: Work out the tags + id: tags + run: | + set -eu + version=${GITHUB_REF_NAME#v} + image="$REGISTRY/$GITHUB_REPOSITORY" + args="--tag $image:$version" + case "$version" in + *-*) echo "Forudgivelse: kun $version." ;; + *) + minor=${version%.*} + major=${version%%.*} + args="$args --tag $image:$minor --tag $image:$major --tag $image:latest" + ;; + esac + echo "args=$args" >> "$GITHUB_OUTPUT" + echo "Mærkater: $args" + + - name: Build and push + run: | + set -eu + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --push \ + ${{ steps.tags.outputs.args }} \ + --build-arg "VERSION=$GITHUB_REF_NAME" \ + --label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" \ + --label "org.opencontainers.image.version=$GITHUB_REF_NAME" \ + --label "org.opencontainers.image.revision=$GITHUB_SHA" \ + --label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + . + # Ingen `cache-from: type=gha`. act_runner har en cache-tjeneste, der + # svarer på det navn, men den er ikke prøvet her, og en kold bygning, + # der tager nogle minutter længere, er billigere end en udgivelse, der + # fejler på noget, som ikke har med bygningen at gøre. - # 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. + # ændring for sig. (Bemærk også, at GitHub-udgavens softprops-handling taler + # mod api.github.com og ikke kan genbruges her.)