diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a7bf4b6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,59 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Purpose + +This repo repackages [Claude Desktop](https://claude.ai) into RPMs for Fedora 43 & 44 / x86_64. It does **not** contain the app source — it downloads the upstream `.deb` from Anthropic's apt repository, repackages the prebuilt Electron bundle as an RPM (no recompile), signs it, and publishes to a self-hosted dnf repo at `rpm.lair.cafe`. Modelled on the sibling repo [`lair/mistralrs-package`](https://git.lair.cafe/lair/mistralrs-package). + +## Why this exists + +Claude Desktop's in-app updater uses Electron's native `autoUpdater`, which is **macOS/Windows only** — it is inert on Linux. Anthropic's only Linux update channel is an apt repo (Debian/Ubuntu). This repo is the dnf-equivalent so Fedora hosts stay current via `dnf upgrade`. + +## Architecture + +### Pipeline flow + +1. **poll-upstream** (`.gitea/workflows/poll-upstream.yml`) — cron every 6h. Reads the upstream apt `Packages` index for the latest version. If the corresponding RPMs don't exist (and aren't indexed) on `rpm.lair.cafe` for fc43 & fc44, and no build is in-flight, dispatches `build-release`. +2. **build-release** (`.gitea/workflows/build-release.yml`) — two-stage pipeline, matrix over Fedora 43 & 44: + - **package** (`runs-on: rpm`) — resolves the `.deb` URL + SHA256 from the apt `Packages` index, downloads and verifies it, then `rpmbuild -bb rpm/claude-desktop.spec` with `--define claude_desktop_version` and `--define "dist .fcNN"`. + - **publish** (`runs-on: rpm`) — OpenPGP-signs the RPMs (`rpm --addsign`), rsyncs to `rpm.lair.cafe`, runs `createrepo_c --update`, regenerates `packages.json`. `createrepo`/`packages.json` are wrapped in `flock /var/www/rpm/.publish.lock` to serialise against other package repos publishing into the same tree. + +### Upstream source of truth + +`https://downloads.claude.ai/claude-desktop/apt/stable/dists/stable/main/binary-amd64/Packages` — each stanza carries `Version`, `Filename`, and `SHA256`. The `.deb` download URL is `${APT_BASE}/${Filename}`. + +### Key files + +- `rpm/claude-desktop.spec` — RPM spec. Prebuilt Electron bundle; extracts the `.deb` with `ar` + `tar` in `%prep` (no `dpkg` dependency). Explicitly `chmod 4755` the setuid-root `chrome-sandbox` in `%install` (GNU tar drops setuid bits when not run as root); drops the Debian AppArmor/apt maintainer scripts (Fedora uses SELinux + dnf). +- `rpm/rpmmacros` — `%_openpgp_sign_id @GPG_NAME@`; `@GPG_NAME@` is sed-replaced with `RPM_SIGNING_KEY_ID` at publish time. +- `script/generate-packages-json.py` — regenerates the whole tree's `packages.json` from repodata (verbatim copy of the sibling repo's script; keep in sync). + +## Commands + +Build an RPM locally from the upstream `.deb`: +```bash +rpmdev-setuptree +VERSION=1.17377.2 +curl -fsSL -o ~/rpmbuild/SOURCES/claude-desktop_${VERSION}_amd64.deb \ + "https://downloads.claude.ai/claude-desktop/apt/stable/pool/main/c/claude-desktop/claude-desktop_${VERSION}_amd64.deb" +rpmbuild -bb rpm/claude-desktop.spec \ + --define "claude_desktop_version ${VERSION}" \ + --undefine dist --define "dist .fc44" +``` + +## Infrastructure + +- CI runs on Gitea Actions (self-hosted), not GitHub Actions. +- RPM repo hosted at `rpm.lair.cafe` on host `oolon.kosherinata.internal`. +- Stable repos: `rpm.lair.cafe/fedora/{43,44}/x86_64/` (shared with other `lair` packages). +- Publish uses rsync over SSH as the `gitea_ci` user. +- Required Actions secrets: `DISPATCH_TOKEN`, `RPM_SIGNING_KEY`, `RPM_SIGNING_KEY_ID`, `RSYNC_SSH_KEY` (org-level on `lair` if configured; otherwise set per-repo). + +## Consuming the repo + +On a Fedora host, drop a `.repo` file pointing at `https://rpm.lair.cafe/fedora/$releasever/x86_64/` (see `lair/mistralrs-package` for the existing client `.repo` convention), then `dnf install claude-desktop`. Subsequent `dnf upgrade` picks up new versions automatically. + +## Runtime notes + +- If Chromium's sandbox misbehaves on a host with restricted user namespaces: `claude-desktop --no-sandbox`, or `sysctl kernel.unprivileged_userns_clone=1`.