diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml new file mode 100644 index 0000000..2f8fd57 --- /dev/null +++ b/.gitea/workflows/cd.yml @@ -0,0 +1,144 @@ +# J621 CD — manual release workflow (Actions tab -> "Run workflow"). +# +# One dispatch does everything: +# * builds and pushes the backend + frontend images (multi-arch, :latest +# and :, GIT_HASH baked in for the version pill), +# * builds the desktop packages and attaches them (plus the update +# metadata) to the Gitea release tagged `desktop-v`. +# +# The live update feed (deploy/data/desktop, served by the frontend nginx at +# /desktop/) is not touched here: it is runtime state on the deploy host and +# is still published with `deploy/push_desktop.sh --no-build` from a machine +# that can reach it. +# +# Registry login uses the REGISTRY_USER / REGISTRY_TOKEN repo secrets. +# Jobs run on the user-scoped nitro-ci runner (ubuntu-latest). + +name: CD + +on: + workflow_dispatch: + inputs: + platforms: + description: Image platforms (comma separated) + required: false + default: linux/amd64,linux/arm64 + windows: + description: Also cross-build the Windows installer (needs wine, slow) + required: false + default: "false" + +concurrency: + group: cd + cancel-in-progress: false + +jobs: + images: + name: Build & push images + runs-on: ubuntu-latest + env: + REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }} + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN || secrets.GITHUB_TOKEN }} + PLATFORMS: ${{ inputs.platforms || 'linux/amd64,linux/arm64' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Register binfmt (multi-arch builds) + run: docker run --privileged --rm tonistiigi/binfmt --install all + + - name: Build & push both images + run: | + set -euo pipefail + SHA="$(git rev-parse --short HEAD)" + echo "Publishing $SHA for $PLATFORMS" + PLATFORMS="$PLATFORMS" ./deploy/push_frontend.sh "$SHA" + PLATFORMS="$PLATFORMS" ./deploy/push_backend.sh "$SHA" + + desktop: + name: Desktop release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Install packaging tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends fakeroot libarchive-tools + if [ "${{ inputs.windows }}" = "true" ]; then + sudo apt-get install -y --no-install-recommends wine + fi + + - name: Install frontend + desktop dependencies + run: | + npm --prefix frontend ci --no-audit --no-fund + npm --prefix desktop ci --no-audit --no-fund + + - name: Build desktop packages + run: | + if [ "${{ inputs.windows }}" = "true" ]; then + ./deploy/build_desktop.sh --all + else + ./deploy/build_desktop.sh --linux + fi + + - name: Add the Gitea release + env: + GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + VERSION="$(node -p "require('./desktop/package.json').version")" + TAG="desktop-v$VERSION" + API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" + AUTH="Authorization: token $GITEA_TOKEN" + + NOTES="$(printf 'J621 desktop %s\n\n' "$VERSION" + cd desktop/release + sha256sum ./*.deb ./*.pkg.tar.zst ./*.exe 2>/dev/null || true)" + + RELEASE_ID="$(curl -sf -H "$AUTH" "$API/releases/tags/$TAG" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' \ + 2>/dev/null || true)" + if [ -z "$RELEASE_ID" ]; then + echo "Creating release $TAG" + PAYLOAD="$(python3 - "$TAG" "${{ github.sha }}" "$NOTES" <<'PY' + import json, sys + print(json.dumps({ + "tag_name": sys.argv[1], + "name": sys.argv[1], + "body": sys.argv[3], + "target_commitish": sys.argv[2], + })) + PY + )" + RELEASE_ID="$(curl -sf -X POST -H "$AUTH" \ + -H "Content-Type: application/json" -d "$PAYLOAD" "$API/releases" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" + else + echo "Release $TAG already exists (id $RELEASE_ID); attaching missing files." + fi + + EXISTING="$(curl -sf -H "$AUTH" "$API/releases/$RELEASE_ID/assets" \ + | python3 -c 'import json,sys; print("\n".join(a["name"] for a in json.load(sys.stdin)))' \ + || true)" + for FILE in desktop/release/*"$VERSION"*.deb \ + desktop/release/*"$VERSION"*.pkg.tar.zst \ + desktop/release/latest-linux.yml \ + desktop/release/latest.yml \ + desktop/release/*"$VERSION"*.exe \ + desktop/release/*"$VERSION"*.exe.blockmap; do + [ -e "$FILE" ] || continue + NAME="$(basename "$FILE")" + case "$EXISTING" in + *"$NAME"*) echo " already attached: $NAME"; continue ;; + esac + echo " attaching $NAME" + curl -sf -X POST -H "$AUTH" -H "Content-Type: application/octet-stream" \ + --data-binary @"$FILE" "$API/releases/$RELEASE_ID/assets?name=$NAME" >/dev/null + done + echo "Release: ${{ github.server_url }}/${{ github.repository }}/releases/tag/$TAG" diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml deleted file mode 100644 index 7b328f7..0000000 --- a/.gitea/workflows/publish.yml +++ /dev/null @@ -1,53 +0,0 @@ -# J621 image publishing — manual workflow. -# -# Builds the backend (gunicorn + whitenoise, ffmpeg) and frontend (static -# nginx) images for linux/amd64 + linux/arm64 and pushes them to the Gitea -# registry as :latest and :, with the commit baked in as GIT_HASH. -# -# Run it from the Actions tab ("Run workflow"), or: -# curl -X POST .../api/v1/repos/JakeBreath/J621/actions/workflows/publish.yml/dispatches \ -# -d '{"ref":"main"}' -# -# Registry login uses the automatic GITHUB_TOKEN (the repo needs package write -# access for the actor); no extra secrets are required. - -name: Publish images - -on: - workflow_dispatch: - inputs: - platforms: - description: Build platforms (comma separated) - required: false - default: linux/amd64,linux/arm64 - -concurrency: - group: publish - cancel-in-progress: false - -jobs: - publish: - name: Build & push images - runs-on: ubuntu-latest - env: - # Prefer dedicated registry secrets (as on other repos); fall back to - # the automatic Actions token. - REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }} - REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN || secrets.GITHUB_TOKEN }} - PLATFORMS: ${{ inputs.platforms || 'linux/amd64,linux/arm64' }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Register binfmt (multi-arch builds) - run: docker run --privileged --rm tonistiigi/binfmt --install all - - - name: Build & push both images - run: | - set -euo pipefail - SHA="$(git rev-parse --short HEAD)" - echo "Publishing $SHA for $PLATFORMS" - # Both scripts honour REGISTRY_USER/REGISTRY_TOKEN (see deploy/push_*.sh). - PLATFORMS="$PLATFORMS" ./deploy/push_frontend.sh "$SHA" - PLATFORMS="$PLATFORMS" ./deploy/push_backend.sh "$SHA" diff --git a/AGENTS.md b/AGENTS.md index b22bd8e..03a7749 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,12 +49,16 @@ Project constraints (do not regress): - Periodic commands (follow syncs, similarity cleanup, guest blacklist refresh) run in the composes' `scheduler` service — the backend image with the j621-scheduler entrypoint, intervals via J621_*_EVERY. No host cron. -- CI lives in .gitea/workflows: ci.yml runs on every push/PR (Django checks + - the full backend suite against MariaDB/Redis service containers, frontend - lint/type-check/build); publish.yml is manual and builds/pushes both images - multi-arch. Jobs run on the user-scoped msi-mortar-ci runner (labels - `desktop` + `ubuntu-latest`). Do not add actions/cache (`cache: pip`/`npm`) - to these workflows: Gitea's cache service hangs the job on restore/save. +- CI/CD lives in .gitea/workflows: ci.yml runs on every push/PR (Django checks + + the full backend suite against MariaDB/Redis service containers, frontend + lint/type-check/build); cd.yml is manual and builds/pushes both images + multi-arch plus the desktop packages (attached to the Gitea release + `desktop-v`). Jobs run on the user-scoped runners: `ubuntu-latest` + on nitro-ci, `desktop` on msi-mortar-ci. Do not add actions/cache + (`cache: pip`/`npm`) to these workflows: Gitea's cache service hangs the job + on restore/save. The live desktop update feed (deploy/data/desktop) is still + published with `deploy/push_desktop.sh --no-build` from a machine with SSH + to the deploy host — CI has no key for that. - Security/permission tests live in backend/apps/core/tests and need a one-time grant: GRANT ALL ON `test_j621`.* TO 'j621'@'%'; diff --git a/deploy/README.md b/deploy/README.md index fbcc710..67eea9b 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -164,6 +164,16 @@ does not. The desktop app's "Check for updates…" menu item reads `latest-linux.yml` / `latest.yml` from there (see `desktop/README.md`). Backend-only composes have no frontend, so no feed. +The manual **CD** workflow (Actions tab) builds the desktop packages on the +runner and attaches them plus the update metadata to the Gitea release +`desktop-v`; it does not touch the live feed, because that is +runtime state on the deploy host and CI has no SSH key for it. After a CD run, +publish the feed from a machine that can reach the deploy checkout: + +```bash +./push_desktop.sh --no-build --local # or without --local to also copy it +``` + ## Scheduled jobs Compose files with a backend also run a **`scheduler`** service — the same