From b29b017341b9d9099df207821f0e2166796cff3f Mon Sep 17 00:00:00 2001 From: maro Date: Wed, 16 Sep 2026 20:31:07 +0200 Subject: [PATCH] ci: use GitHub Container Registry --- .env.example | 14 ++-- .gitea/workflows/ci.yml | 118 --------------------------------- .gitea/workflows/release.yml | 120 ---------------------------------- .github/workflows/ci.yml | 9 +-- .github/workflows/release.yml | 9 +-- DEPLOYMENT.md | 59 ++++++----------- README.md | 10 +-- deploy.sh | 6 +- docker-compose.yml | 4 +- setup.sh | 36 +++++----- 10 files changed, 64 insertions(+), 321 deletions(-) delete mode 100644 .gitea/workflows/ci.yml delete mode 100644 .gitea/workflows/release.yml diff --git a/.env.example b/.env.example index ae4944f..80d1356 100644 --- a/.env.example +++ b/.env.example @@ -80,19 +80,19 @@ NPM_NETWORK= BACKUP_RETENTION_DAYS=14 # ── Container registry & deployment (used by deploy.sh) ──────────────── -# Gitea's built-in container registry. deploy.sh builds nothing — it only -# ever pulls this image (see .gitea/workflows/release.yml, which is what -# actually builds and pushes it). -REGISTRY=git.maro.run -REGISTRY_REPO=maro/anouma +# GitHub Container Registry. deploy.sh builds nothing — it only ever pulls +# the image that GitHub Actions built and pushed. +REGISTRY=ghcr.io +REGISTRY_REPO=whitehai11/anouma # Only needed if the registry/repository requires authentication to pull. -# Use a Gitea access token as the password, not your account password. +# Use a GitHub token with at least read:packages as the password, not your +# account password. REGISTRY_USERNAME= REGISTRY_PASSWORD= # Set automatically by deploy.sh on every successful deploy — do not edit # by hand. Empty until the first release has been deployed. ANOUMA_IMAGE= # Informational only (shown in the setup summary) — actual automatic -# deployment is controlled by whether release.yml + its Gitea secrets are +# deployment is controlled by whether release.yml + its GitHub secrets are # configured, not by this flag. AUTO_DEPLOY=true diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml deleted file mode 100644 index 7fa7faf..0000000 --- a/.gitea/workflows/ci.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: CI - -on: - push: - branches: ['**'] - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - container: - image: node:22-alpine - # No live database is needed here — every Payload-backed route in this - # app is force-dynamic, so `next build` never touches Postgres. These - # are dummy values only so a real config error (a missing secret) is - # never silently masked by CI happening to run without one. - # - # NODE_ENV is deliberately NOT set here: `npm ci` treats a production - # NODE_ENV as `--omit=dev`, which would skip eslint/typescript/etc. and - # break Lint/Typecheck/Test. It's set below, scoped to the Build step. - env: - NEXT_TELEMETRY_DISABLED: "1" - DATABASE_URI: postgresql://ci:ci@localhost:5432/ci - PAYLOAD_SECRET: ci-only-not-a-real-secret - MEETING_SESSION_SECRET: ci-only-not-a-real-secret - ANALYTICS_SALT_SECRET: ci-only-not-a-real-secret - CRON_SECRET: ci-only-not-a-real-secret - NEXT_PUBLIC_SERVER_URL: http://localhost:3000 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install build dependencies (sharp needs libc6-compat on alpine) - run: apk add --no-cache libc6-compat - - - name: Install dependencies - run: npm ci - - - name: Lint - run: npm run lint - - - name: Typecheck - run: npx next typegen && npx tsc --noEmit - - - name: Test - run: npm test - - - name: Build - env: - NODE_ENV: production - run: npm run build - - # Builds once per commit that lands on main and has passed the checks - # above, and pushes it to the registry tagged by commit SHA. release.yml - # (triggered by a version tag) never rebuilds — it only promotes this - # exact, already-tested image to the release tag + `latest` and deploys - # it. This guarantees byte-for-byte that what gets deployed is what CI - # verified, and makes tagging a commit that never passed CI on main fail - # loudly (no image to promote) instead of silently rebuilding from - # source and skipping the checks. - build-image: - needs: test - if: github.ref == 'refs/heads/main' && github.event_name == 'push' - runs-on: ubuntu-latest - # The anouma-runner runs as a Docker container itself (gitea/runner:3), - # with the host's docker.sock bind-mounted in but no `docker` CLI binary - # of its own — a job with no `container:` here runs directly inside that - # minimal runner image and gets "docker: command not found". - # - # A plain `docker:27-cli` image (Alpine, no Node.js) doesn't work either: - # actions/checkout@v4 is a JS action and needs a Node runtime in the job - # container, which that image doesn't have — it fails at Checkout with - # exit 127. Using a Node image as the base guarantees Node is already - # there (same reason the `test` job's node:22-alpine works), and the - # Docker CLI is added via apt. The runner auto-mounts the same host - # docker.sock into this container (sibling containers — see - # container.docker_host in the runner's config.yaml), so `docker - # build`/`push` operate on the host's real Docker daemon. release.yml's - # promote-and-deploy job uses the same setup, for the same reasons. - container: - image: node:22-bookworm-slim - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install Docker CLI - run: | - apt-get update - apt-get install -y --no-install-recommends docker.io - rm -rf /var/lib/apt/lists/* - - - name: Determine image name - id: image - run: | - echo "registry=${REGISTRY:-git.maro.run}" >> "$GITHUB_OUTPUT" - echo "repo=${REGISTRY_REPO:-maro/anouma}" >> "$GITHUB_OUTPUT" - env: - REGISTRY: git.maro.run - REGISTRY_REPO: maro/anouma - - - name: Log in to registry - env: - REGISTRY_IMAGE: ${{ steps.image.outputs.registry }} - REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} - run: echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_IMAGE" -u "$REGISTRY_USERNAME" --password-stdin - - - name: Build image - env: - IMAGE: ${{ steps.image.outputs.registry }}/${{ steps.image.outputs.repo }} - # Tagged by commit SHA only — `latest` is reserved for whatever was - # last actually released (see release.yml), not the newest main build. - run: docker build -t "$IMAGE:$GITHUB_SHA" . - - - name: Push image - env: - IMAGE: ${{ steps.image.outputs.registry }}/${{ steps.image.outputs.repo }} - run: docker push "$IMAGE:$GITHUB_SHA" diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml deleted file mode 100644 index b79e8ba..0000000 --- a/.gitea/workflows/release.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*' - -jobs: - # Deliberately does NOT rebuild or re-run lint/typecheck/test/build: the - # image for this exact commit was already built and pushed by ci.yml's - # build-image job when it landed on main (which itself only runs after - # the `test` job passes). This job just promotes that already-verified - # image to the release tag + `latest` and deploys it — if the tagged - # commit was never pushed to main (or CI hasn't finished yet), the - # "Pull the tested image" step below fails loudly instead of silently - # rebuilding from source and skipping the checks. - promote-and-deploy: - runs-on: ubuntu-latest - # Node-based image (not the bare runner, not a Docker-only image) for - # the same reasons as ci.yml's build-image job: the runner needs a - # Docker CLI added via apt (it has none of its own), and actions/checkout - # needs a Node runtime that a plain docker:*-cli image doesn't have. - container: - image: node:22-bookworm-slim - steps: - - name: Install Docker CLI and ssh client - run: | - apt-get update - apt-get install -y --no-install-recommends docker.io openssh-client - rm -rf /var/lib/apt/lists/* - - - name: Determine image tags - id: image - # Gitea Actions' runner is protocol-compatible with GitHub Actions, - # so step commands use the same $GITHUB_OUTPUT / $GITHUB_ENV - # mechanism (the `gitea.*` vs `github.*` expression contexts are - # just aliases of each other for `${{ }}` templating). - # - # The tag name (github.ref_name / $GITHUB_REF_NAME) is - # attacker-influenced: anyone with tag-push rights controls it, and - # git allows shell metacharacters in tag names. It's read here via - # the automatic $GITHUB_REF_NAME env var rather than `${{ github.ref_name }}` - # so its value reaches bash as data (argv/envp), never spliced into - # the script text — `${{ }}` expressions are substituted into the - # YAML before bash ever sees the script, which is what makes - # interpolating attacker-controlled values that way a script-injection - # vector (arbitrary code execution in a job that holds the registry - # and production deploy credentials). - run: | - echo "registry=${REGISTRY:-git.maro.run}" >> "$GITHUB_OUTPUT" - echo "repo=${REGISTRY_REPO:-maro/anouma}" >> "$GITHUB_OUTPUT" - echo "version=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" - env: - REGISTRY: git.maro.run - REGISTRY_REPO: maro/anouma - - - name: Log in to registry - env: - REGISTRY_IMAGE: ${{ steps.image.outputs.registry }} - REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} - run: echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_IMAGE" -u "$REGISTRY_USERNAME" --password-stdin - - - name: Pull the tested image for this commit - env: - IMAGE: ${{ steps.image.outputs.registry }}/${{ steps.image.outputs.repo }} - run: | - docker pull "$IMAGE:$GITHUB_SHA" || { - echo "::error::No image found for commit $GITHUB_SHA. This tag must point at a commit that was already pushed to main and built by ci.yml's build-image job — push to main first (and wait for CI to finish) before tagging a release." >&2 - exit 1 - } - - - name: Promote image to the release tag and latest - env: - IMAGE: ${{ steps.image.outputs.registry }}/${{ steps.image.outputs.repo }} - VERSION: ${{ steps.image.outputs.version }} - run: | - docker tag "$IMAGE:$GITHUB_SHA" "$IMAGE:$VERSION" - docker tag "$IMAGE:$GITHUB_SHA" "$IMAGE:latest" - - - name: Push promoted tags - env: - IMAGE: ${{ steps.image.outputs.registry }}/${{ steps.image.outputs.repo }} - VERSION: ${{ steps.image.outputs.version }} - run: | - docker push "$IMAGE:$VERSION" - docker push "$IMAGE:latest" - - - name: Deploy to production - env: - DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} - DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }} - DEPLOY_USER: ${{ secrets.DEPLOY_USER }} - DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} - # Optional: pin the exact host key instead of trusting it on - # first connect (see DEPLOYMENT.md). Safe to leave unset. - DEPLOY_HOST_KEY: ${{ secrets.DEPLOY_HOST_KEY }} - VERSION: ${{ steps.image.outputs.version }} - shell: bash - run: | - set -euo pipefail - umask 077 - key_file="$(mktemp)" - known_hosts_file="$(mktemp)" - trap 'rm -f "$key_file" "$known_hosts_file"' EXIT - - printf '%s\n' "$DEPLOY_SSH_KEY" > "$key_file" - chmod 600 "$key_file" - - if [ -n "${DEPLOY_HOST_KEY:-}" ]; then - printf '%s\n' "$DEPLOY_HOST_KEY" > "$known_hosts_file" - else - ssh-keyscan -p "${DEPLOY_PORT:-22}" "$DEPLOY_HOST" > "$known_hosts_file" 2>/dev/null - fi - - # The forced command in the server's authorized_keys (see - # setup.sh) ignores this and always runs deploy.sh itself — the - # string below only ever reaches it via $SSH_ORIGINAL_COMMAND. - ssh -i "$key_file" -o UserKnownHostsFile="$known_hosts_file" -p "${DEPLOY_PORT:-22}" \ - "${DEPLOY_USER}@${DEPLOY_HOST}" "deploy ${VERSION}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bb7ed7..a8f16f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,17 +46,18 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + packages: write env: - IMAGE: git.maro.run/maro/anouma + IMAGE: ghcr.io/whitehai11/anouma steps: - name: Checkout uses: actions/checkout@v5 - name: Log in to registry uses: docker/login-action@v3 with: - registry: git.maro.run - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build image run: docker build -t "$IMAGE:$GITHUB_SHA" . - name: Push image diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 943b5c1..7125a71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,19 +6,20 @@ on: permissions: contents: read + packages: write jobs: promote-and-deploy: runs-on: ubuntu-latest env: - IMAGE: git.maro.run/maro/anouma + IMAGE: ghcr.io/whitehai11/anouma steps: - name: Log in to registry uses: docker/login-action@v3 with: - registry: git.maro.run - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Pull tested image run: docker pull "$IMAGE:$GITHUB_SHA" - name: Promote release image diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 8f55d27..f520793 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,10 +1,10 @@ # ANOUMA — Deployment -Production deployment is fully CI/CD-driven via Gitea or GitHub Actions. There is no `update.sh` and the production server never builds anything — it only ever pulls a tested image from the registry. +Production deployment is fully CI/CD-driven via GitHub Actions. There is no `update.sh` and the production server never builds anything — it only ever pulls a tested image from GitHub Container Registry. ```text -Developer → git push main → Gitea Actions (CI) - → Tests → Docker Build → Registry Push (tagged by commit SHA) +Developer → git push main → GitHub Actions (CI) + → Tests → Docker Build → GHCR Push (tagged by commit SHA) → Release (git tag vX.Y.Z) → promote that exact image to vX.Y.Z + latest → SSH → deploy.sh → docker compose pull → migrate → up -d → Healthcheck → LIVE ``` @@ -18,7 +18,7 @@ building from source and skipping the checks. ## One-time server setup ```bash -git clone https://git.maro.run/maro/anouma.git +git clone https://github.com/whitehai11/anouma.git cd anouma chmod +x setup.sh ./setup.sh @@ -34,15 +34,15 @@ chmod +x setup.sh 6. Creates a restricted `anouma-deploy` system user, generates a dedicated SSH keypair for it, and installs the public key with a **forced command** so that key can only ever run `deploy.sh` — never an interactive shell. 7. Copies `docker-compose.yml` and `deploy.sh` into `/opt/anouma`. 8. Starts Postgres (so it's ready for the first deploy). -9. Prints the exact values to paste into **Gitea → maro/anouma → Settings → Secrets → Actions**, including the deploy private key — shown only once, the first time the key is generated. +9. Prints the exact values to paste into **GitHub → whitehai11/anouma → Settings → Secrets and variables → Actions**, including the deploy private key — shown only once, the first time the key is generated. Run `./setup.sh --non-interactive` for a scripted install; any value that has no safe default and wasn't exported as an environment variable beforehand aborts with a clear message naming the missing variable, instead of silently guessing. Re-running `./setup.sh` later (e.g. after pulling script updates) is always safe: existing secrets, the SSH key, the database and all volumes are left exactly as they are — only `docker-compose.yml` and `deploy.sh` themselves get refreshed, since those are generated artifacts, not data. -### After setup: add the Gitea secrets +### After setup: add the GitHub secrets -Copy the block `setup.sh` printed into **Gitea → maro/anouma → Settings → Secrets → Actions**: +Copy the deployment values `setup.sh` printed into **GitHub → whitehai11/anouma → Settings → Secrets and variables → Actions**: | Secret | Value | | --- | --- | @@ -51,7 +51,7 @@ Copy the block `setup.sh` printed into **Gitea → maro/anouma → Settings → | `DEPLOY_USER` | `anouma-deploy` | | `DEPLOY_SSH_KEY` | the private key setup.sh printed | | `DEPLOY_HOST_KEY` | optional — pin the server's SSH host public key instead of trusting it on first connect (`ssh-keyscan -p ` on a machine you already trust) | -| `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` | registry push credentials (a Gitea access token as the password, not your account password) | +| `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` | **not needed in GitHub Actions**; these belong in `/opt/anouma/.env` on the server for pulling the private GHCR image | ### First deploy @@ -60,28 +60,9 @@ Nothing runs on the server yet at this point — no release has been built. Push ```bash git tag v1.0.0 git push github v1.0.0 # GitHub Actions -# alternativ: git push origin v1.0.0 # Gitea Actions ``` -The selected CI provider takes it from there (see below). Once it's live, seed the original content once: `ssh anouma-deploy@` won't work (the key is restricted — see Security), so run it directly on the server instead: `cd /opt/anouma && docker compose run --rm app npm run seed`. - -## Gitea Actions - -### CI (`.gitea/workflows/ci.yml`) - -Runs on every push and PR: `npm ci` → lint → typecheck → test → build, on the `ubuntu-latest`-labelled runner. No database is required — every Payload-backed route in this app is `force-dynamic`, so a production build never touches Postgres. (There's no automated test suite yet — `npm test` is an honest placeholder; add a real one, e.g. vitest, whenever the project needs it, and it'll run here automatically.) - -On top of that, every push to `main` that passes the checks above also builds the Docker image and pushes it to the registry tagged by commit SHA (`git.maro.run/maro/anouma:`) — this is the one and only build of that commit; nothing later rebuilds it. - -### Release (`.gitea/workflows/release.yml`) - -Triggered by pushing a tag matching `v*` (e.g. `v1.2.0`): - -1. Pulls the image CI already built and pushed for that commit's SHA — fails immediately with a clear error if it isn't there (the tagged commit was never on `main`, or CI for it hasn't finished yet). -2. Retags that same image as `git.maro.run/maro/anouma:vX.Y.Z` **and** `:latest`, and pushes both. -3. SSHes into the production server as `anouma-deploy` and runs `deploy.sh deploy vX.Y.Z`. - -Production always runs a specific version tag — `latest` is pushed for convenience/reference only (and always points at the most recently *released* build, not just the newest commit on `main`), `deploy.sh` never uses it. +The GitHub workflow takes it from there. Once it's live, seed the original content once: `ssh anouma-deploy@` won't work (the key is restricted — see Security), so run it directly on the server instead: `cd /opt/anouma && docker compose run --rm app npm run seed`. ## GitHub Actions @@ -91,28 +72,26 @@ The GitHub repository is `whitehai11/anouma`; the workflows live in | Secret | Required | Value | | --- | --- | --- | -| `REGISTRY_USERNAME` | yes | Gitea Container-Registry username, normally `maro` | -| `REGISTRY_PASSWORD` | yes | Gitea access token/password with permission to push/pull `git.maro.run/maro/anouma` | | `DEPLOY_HOST` | yes | Public IP or hostname of the production server | | `DEPLOY_PORT` | yes | SSH port, normally `22` | | `DEPLOY_USER` | yes | `anouma-deploy` | | `DEPLOY_SSH_KEY` | yes | Private key generated by `setup.sh` for the restricted deploy user | | `DEPLOY_HOST_KEY` | no | Complete SSH host-key line from a trusted `ssh-keyscan`; safer than first-connect trust | -Es werden keine zusätzlichen GitHub-Variables benötigt; Registry und Image -sind im Workflow fest auf `git.maro.run/maro/anouma` gesetzt. Runtime-Secrets +GitHub Actions authentifiziert sich mit dem automatisch bereitgestellten +`GITHUB_TOKEN`; dafür musst du keine Registry-Secrets anlegen. Registry und +Image sind im Workflow fest auf `ghcr.io/whitehai11/anouma` gesetzt. Runtime-Secrets wie `PAYLOAD_SECRET`, `MEETING_SESSION_SECRET`, `LIVEKIT_API_SECRET` und `DATABASE_URI` gehören ausschließlich in `/opt/anouma/.env` auf dem Server und nicht in GitHub Actions. -Wichtig: Für Releases nur einen CI-Anbieter verwenden, damit nicht beide -Systeme dasselbe Image parallel bauen oder deployen. Für dieses GitHub-Setup -ist GitHub Actions die empfohlene Quelle; Gitea kann als Spiegel bestehen -bleiben. +Wichtig: Für Releases nur GitHub Actions verwenden. Gitea kann als reiner +Repository-Spiegel bestehen bleiben, darf aber keine eigenen Build- oder +Release-Workflows ausführen. ## `deploy.sh` (server-side, `/opt/anouma/deploy.sh`) -Not a manual tool — it's invoked exclusively by Gitea Actions over SSH. What it does for `deploy.sh deploy vX.Y.Z`: +Not a manual tool — it's invoked exclusively by GitHub Actions over SSH. What it does for `deploy.sh deploy vX.Y.Z`: ```text Lock (refuse a second concurrent deploy) @@ -126,7 +105,7 @@ Lock (refuse a second concurrent deploy) → success, or: automatic rollback to the previous version ``` -If the post-deploy healthcheck fails, `deploy.sh` automatically redeploys the **previous** version (recorded from `ANOUMA_IMAGE` before the attempt) and reports the failed deploy as exit code 1 — Gitea Actions shows the release as failed even though production recovered. Database migrations from the failed release are **not** reverted (write migrations to be forward-compatible); restore the pre-deploy backup manually if a migration truly needs undoing. +If the post-deploy healthcheck fails, `deploy.sh` automatically redeploys the **previous** version (recorded from `ANOUMA_IMAGE` before the attempt) and reports the failed deploy as exit code 1 — GitHub Actions shows the release as failed even though production recovered. Database migrations from the failed release are **not** reverted (write migrations to be forward-compatible); restore the pre-deploy backup manually if a migration truly needs undoing. A lock file (`/opt/anouma/.deploy.lock`) prevents two deploys from running at once; a stale lock from a killed process is detected and ignored automatically. @@ -242,7 +221,7 @@ Neither `setup.sh` nor `deploy.sh` ever runs `docker system prune`, `docker volu - `anouma-deploy` **is** a member of the `docker` group, because running `docker compose` requires it — and Docker-group membership is well known to be effectively root-equivalent (a container can bind-mount the host filesystem). This is a deliberate, documented trade-off: the blast radius is contained by the two points above (no interactive access, and the only reachable code path is the fixed `deploy.sh` logic), not eliminated. For a stricter setup, consider rootless Docker or a dedicated deployment tool with finer-grained Docker API ACLs — out of scope here. - `.env` and the deploy SSH private key are `chmod 600`. - Secrets are never committed, never logged, and never printed more than once (`setup.sh` shows the private key only the first time it's generated; on a later run it prints "Existing deployment key found." instead). -- `REGISTRY_PASSWORD`/`SMTP_PASSWORD`/etc. are masked (`********`) in `setup.sh`'s configuration summary — only the one-time Gitea-secrets block shows real values, since that block exists specifically for you to copy them into Gitea. +- `REGISTRY_PASSWORD`/`SMTP_PASSWORD`/etc. are masked (`********`) in `setup.sh`'s configuration summary. The GHCR token is needed only on the production server for pulling the private image; GitHub Actions uses its automatic `GITHUB_TOKEN`. ## Troubleshooting @@ -250,7 +229,7 @@ Neither `setup.sh` nor `deploy.sh` ever runs `docker system prune`, `docker volu | --- | --- | | `setup.sh` fails at "Datenbank wurde nicht rechtzeitig healthy" | `docker compose logs postgres` (from `/opt/anouma`) — usually a bad `POSTGRES_PASSWORD`/`DATABASE_URI` mismatch if you hand-edited `.env` | | Release workflow fails at "Pull the tested image" | The tagged commit was never pushed to `main`, or `main`'s CI/build-image run for it hasn't finished yet — push to `main`, wait for CI to go green, then tag | -| Release workflow fails at "Deploy to production" | Check the Gitea secrets are all set correctly, and that the server's SSH port is reachable from the runner | +| Release workflow fails at "Deploy to production" | Check the GitHub repository secrets are all set correctly, and that the server's SSH port is reachable from the runner | | App container unhealthy after a deploy | `ssh` directly to the server (not via the restricted key) and run `docker compose logs app` from `/opt/anouma` | | Video calls connect but no audio/video | Check LiveKit logs, the UDP 3478/50000–60000 firewall rules and the public `LIVEKIT_URL` | | Browser cannot connect to LiveKit | Check the LiveKit proxy host, trusted TLS certificate and that Websockets Support is enabled | diff --git a/README.md b/README.md index 1ab93f6..9befe59 100644 --- a/README.md +++ b/README.md @@ -40,16 +40,16 @@ Der Versand ist idempotent (`reminder60Sent`/`reminder30Sent` je Anmeldung, `hos ## Setup (Produktion / Deployment) -Produktion läuft komplett über Gitea Actions CI/CD — der Server baut nichts selbst, er zieht ein fertiges, getestetes Image aus der Registry. Einmalig auf dem Server: +Produktion läuft komplett über GitHub Actions CI/CD — der Server baut nichts selbst, er zieht ein fertiges, getestetes Image aus GitHub Container Registry. Einmalig auf dem Server: ```bash -git clone https://git.maro.run/maro/anouma.git +git clone https://github.com/whitehai11/anouma.git cd anouma chmod +x setup.sh ./setup.sh ``` -`setup.sh` installiert bei Bedarf Docker, legt `/opt/anouma` an, generiert alle Secrets automatisch (nie erneut, wenn schon vorhanden), richtet einen eingeschränkten Deployment-User samt SSH-Key für Gitea Actions ein und zeigt am Ende genau die Werte an, die als Gitea Secrets hinterlegt werden müssen. `./setup.sh --non-interactive` läuft ohne Rückfragen. +`setup.sh` installiert bei Bedarf Docker, legt `/opt/anouma` an, generiert alle Secrets automatisch (nie erneut, wenn schon vorhanden), richtet einen eingeschränkten Deployment-User samt SSH-Key für GitHub Actions ein und zeigt am Ende genau die Werte an, die als GitHub Repository Secrets hinterlegt werden müssen. Das Docker-Image landet unter `ghcr.io/whitehai11/anouma`. `./setup.sh --non-interactive` läuft ohne Rückfragen. Jedes weitere Deployment läuft danach ausschließlich über einen Git-Tag: @@ -57,9 +57,9 @@ Jedes weitere Deployment läuft danach ausschließlich über einen Git-Tag: git tag v1.2.0 && git push origin v1.2.0 ``` -Gitea Actions testet, baut das Image, pusht es in die Registry und deployt es per SSH auf den Server — inklusive Backup, Healthcheck und automatischem Rollback bei Fehlschlag. Es gibt kein separates Update-Script. +GitHub Actions testet, baut das Image, pusht es nach GHCR und deployt es per SSH auf den Server — inklusive Backup, Healthcheck und automatischem Rollback bei Fehlschlag. Es gibt kein separates Update-Script. -Für alle Details (Architektur, Gitea Secrets, CI/Release-Workflows, Reverse-Proxy- und LiveKit-Einrichtung, Environment-Variablen, Backups, Rollback, Security) siehe **[DEPLOYMENT.md](./DEPLOYMENT.md)**. +Für alle Details (Architektur, GitHub Secrets, CI/Release-Workflows, Reverse-Proxy- und LiveKit-Einrichtung, Environment-Variablen, Backups, Rollback, Security) siehe **[DEPLOYMENT.md](./DEPLOYMENT.md)**. ## Lokale Entwicklung (ohne Docker für die App) diff --git a/deploy.sh b/deploy.sh index 926a8cf..ccc8702 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # ANOUMA — server-side deployment script. # -# This is NOT a manual update tool. It is invoked exclusively by Gitea +# This is NOT a manual update tool. It is invoked exclusively by GitHub # Actions over SSH as the restricted `anouma-deploy` user (see setup.sh, # which installs a forced-command SSH key that can only ever run this # script). The production server never builds an image — it only ever @@ -106,7 +106,7 @@ backup_database() { registry_login() { if [ -n "${REGISTRY_USERNAME:-}" ] && [ -n "${REGISTRY_PASSWORD:-}" ]; then - echo "$REGISTRY_PASSWORD" | docker login "${REGISTRY:-git.maro.run}" -u "$REGISTRY_USERNAME" --password-stdin >/dev/null 2>&1 \ + echo "$REGISTRY_PASSWORD" | docker login "${REGISTRY:-ghcr.io}" -u "$REGISTRY_USERNAME" --password-stdin >/dev/null 2>&1 \ && log_success "Registry login OK." \ || fail "Registry login failed — check REGISTRY_USERNAME/REGISTRY_PASSWORD in .env." fi @@ -116,7 +116,7 @@ registry_login() { # the rest of this script (and `docker compose`, via env_file) picks it up. set_image_version() { local version="$1" - env_set ANOUMA_IMAGE "${REGISTRY:-git.maro.run}/${REGISTRY_REPO:-maro/anouma}:${version}" + env_set ANOUMA_IMAGE "${REGISTRY:-ghcr.io}/${REGISTRY_REPO:-whitehai11/anouma}:${version}" set -a; source .env; set +a } diff --git a/docker-compose.yml b/docker-compose.yml index 9a09f6b..ba09fd0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,10 +30,10 @@ services: app: # Production never builds from source — the image is built once by - # Gitea Actions on release and pushed to the registry; this host only + # GitHub Actions on release and pushed to GHCR; this host only # ever pulls a specific, already-tested tag (see deploy.sh, which is the # only thing that ever changes ANOUMA_IMAGE in .env). - image: ${ANOUMA_IMAGE:-git.maro.run/maro/anouma:latest} + image: ${ANOUMA_IMAGE:-ghcr.io/whitehai11/anouma:latest} restart: unless-stopped networks: - anouma-network diff --git a/setup.sh b/setup.sh index 4833941..ab06069 100755 --- a/setup.sh +++ b/setup.sh @@ -1,16 +1,16 @@ #!/usr/bin/env bash # ANOUMA — production server installer. # -# git clone https://git.maro.run/maro/anouma.git && cd anouma +# git clone https://github.com/whitehai11/anouma.git && cd anouma # chmod +x setup.sh && ./setup.sh # # Prepares a fresh Ubuntu/Debian server for ANOUMA: installs Docker if # needed, creates /opt/anouma, writes a production .env with generated -# secrets, creates a restricted `anouma-deploy` SSH user for Gitea Actions, +# secrets, creates a restricted `anouma-deploy` SSH user for GitHub Actions, # and installs the server-side deploy.sh + docker-compose.yml. # -# From then on, Gitea Actions owns all deployments (see -# .gitea/workflows/release.yml) — there is no update.sh. Pushing a release +# From then on, GitHub Actions owns all deployments (see +# .github/workflows/release.yml) — there is no update.sh. Pushing a release # tag (vX.Y.Z) is the only way production gets updated. # # Safe to re-run: never regenerates an existing secret or SSH key, never @@ -151,7 +151,7 @@ command -v wget >/dev/null 2>&1 || log_info "wget nicht gefunden — wird hier n if systemctl is-active --quiet ssh 2>/dev/null || systemctl is-active --quiet sshd 2>/dev/null; then log_success "SSH-Server läuft." else - log_warn "Kein laufender SSH-Server erkannt (ssh/sshd) — Gitea Actions kann ohne einen laufenden SSH-Server nicht deployen. Installiere/starte z. B. 'openssh-server'." + log_warn "Kein laufender SSH-Server erkannt (ssh/sshd) — GitHub Actions kann ohne einen laufenden SSH-Server nicht deployen. Installiere/starte z. B. 'openssh-server'." fi # --------------------------------------------------------------------------- @@ -317,8 +317,8 @@ GOOGLE_SITE_VERIFICATION= DOCKER_NETWORK=anouma-network NPM_NETWORK= BACKUP_RETENTION_DAYS=14 -REGISTRY=git.maro.run -REGISTRY_REPO=maro/anouma +REGISTRY=ghcr.io +REGISTRY_REPO=whitehai11/anouma REGISTRY_USERNAME= REGISTRY_PASSWORD= ANOUMA_IMAGE= @@ -380,7 +380,7 @@ if [ -z "$(env_get NPM_NETWORK)" ] && [ "$NPM_DETECTED" = true ]; then fi # --- registry -------------------------------------------------------------- -REGISTRY_HOST="$(env_get REGISTRY)"; REGISTRY_HOST="${REGISTRY_HOST:-git.maro.run}" +REGISTRY_HOST="$(env_get REGISTRY)"; REGISTRY_HOST="${REGISTRY_HOST:-ghcr.io}" log_info "Prüfe Registry-Erreichbarkeit ($REGISTRY_HOST) …" REGISTRY_REACHABLE=false REGISTRY_HTTP_CODE="$(curl -fsS -o /dev/null -w '%{http_code}' --max-time 5 "https://${REGISTRY_HOST}/v2/" 2>/dev/null || echo "000")" @@ -405,7 +405,7 @@ if [ -z "$(env_get REGISTRY_USERNAME)" ]; then NEED_REGISTRY_AUTH="$(ask_yn "Werden Zugangsdaten für die Container Registry benötigt (privates Repository)?" "Y")" if [ "$NEED_REGISTRY_AUTH" = true ]; then REG_USER="$(ask "Registry Username" "" "REGISTRY_USERNAME")" - REG_PASS="$(ask_secret "Registry Password/Token (Gitea Personal Access Token empfohlen)" "REGISTRY_PASSWORD")" + REG_PASS="$(ask_secret "Registry Password/Token (GitHub Token mit read:packages)" "REGISTRY_PASSWORD")" if [ -z "$REG_USER" ] || [ -z "$REG_PASS" ]; then if [ "$NON_INTERACTIVE" = true ]; then fail "Registry-Zugangsdaten fehlen." "Setze die Umgebungsvariablen REGISTRY_USERNAME und REGISTRY_PASSWORD und starte setup.sh erneut, oder führe es interaktiv aus." @@ -421,7 +421,7 @@ else fi if [ -z "$(env_get AUTO_DEPLOY)" ] || [ "$FRESH_INSTALL" = true ]; then - AUTO_DEPLOY_ANSWER="$(ask_yn "Enable automatic deployment (Gitea Actions deployt automatisch bei neuen Releases)?" "Y")" + AUTO_DEPLOY_ANSWER="$(ask_yn "Enable automatic deployment (GitHub Actions deployt automatisch bei neuen Releases)?" "Y")" env_set AUTO_DEPLOY "$AUTO_DEPLOY_ANSWER" fi @@ -590,7 +590,7 @@ if [ -n "$EXISTING_IMAGE" ]; then log_warn "Anwendung konnte nicht (neu) gestartet werden — prüfe: $COMPOSE logs app" fi else - log_info "Noch kein Release deployt — die Anwendung wird beim ersten Git-Tag automatisch über Gitea Actions gestartet." + log_info "Noch kein Release deployt — die Anwendung wird beim ersten Git-Tag automatisch über GitHub Actions gestartet." if [ "$SKIP_SEED" = false ]; then log_info "Hinweis: Inhalte können nach dem ersten Deploy mit 'docker compose run --rm app npm run seed' eingespielt werden." fi @@ -635,7 +635,7 @@ echo "" echo "Registry:" if [ "$REGISTRY_REACHABLE" = true ]; then echo " ✓ ($(env_get REGISTRY))"; else echo " ⚠ nicht erreichbar geprüft ($(env_get REGISTRY))"; fi echo "" -echo "Gitea Actions:" +echo "GitHub Actions:" echo " Ready" echo "" echo "Automatic Deployment:" @@ -643,11 +643,11 @@ if [ "$(env_get AUTO_DEPLOY)" = "true" ]; then echo " Enabled"; else echo " Di echo "" echo "========================================" -echo "GITEA SECRETS" +echo "GITHUB DEPLOY SECRETS" echo "========================================" echo "" if [ "$SSH_KEY_FRESH" = true ]; then - echo "Trage folgende Werte EINMALIG unter Gitea → maro/anouma → Settings → Secrets → Actions ein." + echo "Trage folgende Werte EINMALIG unter GitHub → whitehai11/anouma → Settings → Secrets and variables → Actions ein." echo "Der private Key wird nach diesem Lauf nicht erneut angezeigt." echo "" echo "DEPLOY_HOST:" @@ -664,13 +664,13 @@ if [ "$SSH_KEY_FRESH" = true ]; then $SUDO cat "$KEY_PATH" echo "----------------------------------------" echo "" - echo "REGISTRY_USERNAME:" + echo "GHCR SERVER USERNAME (not a GitHub Secret):" echo " $(env_get REGISTRY_USERNAME)" echo "" - echo "REGISTRY_PASSWORD:" + echo "GHCR SERVER TOKEN (not a GitHub Secret):" echo " $(env_get REGISTRY_PASSWORD)" else - echo "Existing deployment key found — Gitea Secrets wurden bereits bei der ersten Einrichtung ausgegeben." + echo "Existing deployment key found — GitHub Secrets wurden bereits bei der ersten Einrichtung ausgegeben." echo "Falls sie nicht mehr vorliegen: DEPLOY_SSH_KEY kann sicher eingesehen werden mit:" echo " sudo cat $KEY_PATH" fi @@ -681,7 +681,7 @@ echo "NEXT STEP" echo "========================================" echo "" echo "Add these values to:" -echo " Gitea → maro/anouma → Settings → Secrets → Actions" +echo " GitHub → whitehai11/anouma → Settings → Secrets and variables → Actions" echo "" echo "Danach reicht für jedes zukünftige Deployment:" echo " git tag v1.0.0 && git push origin v1.0.0"