# Release pipeline (maintainers — INTERNAL) > This file is `export-ignore` (see `.gitattributes`) — it never reaches the public repo. It may > name the private repositories. Clusev is promoted across three repositories, dev → staging → public: | Repo | URL | Role | |---|---|---| | Gitea (dev) | `https://git.bave.dev/boban/clusev` | Sole development source. Commit + tag here. | | GitHub private (staging) | `https://github.com/boksbc/clusev-staging` | Push-mirror of Gitea. Runs CI; betas tested on staging. | | GitHub public (release) | `https://github.com/clusev/clusev` | Receives only released versions — one clean commit per release, beta + stable tags. Public users install/update from here. | Channels follow semver tags: **beta** = `vX.Y.Z-betaN`, **stable** = `vX.Y.Z`. No private repo URL is committed: `config/clusev.php` defaults `repository` to the public URL, `install.sh` derives `CLUSEV_REPOSITORY` from the clone origin into the (gitignored) `.env`, so dev points at Gitea, staging at GitHub-private, and public users at GitHub-public — automatically. ## One-time wiring 1. **Gitea push-mirror** (Gitea → repo → Settings → Mirror → Push Mirror): - Remote: `https://github.com/boksbc/clusev-staging.git` - Auth: GitHub username + a GitHub PAT (scope `repo`) as the password. - Enable "sync when new commits are pushed". 2. **`boksbc/clusev-staging` → Settings:** - Variable `PUBLIC_REPO_SLUG = clusev/clusev` (the only variable the promotion workflows read). - Secret `PUBLIC_REPO_TOKEN` = a PAT/deploy token with push to `clusev/clusev`. - Environment `production` with a required reviewer (gates the promotion workflows). - (Optional staging deploy) Variable `STAGING_ENABLED=true`, `STAGING_PROJECT_DIR`, and a self-hosted runner labelled `clusev-staging` on the internal network — GitHub-hosted runners cannot reach the internal staging host (`.165`). Until then, update staging from its Versions page. ## Promoting - **To staging:** push a beta tag `vX.Y.Z-betaN` to Gitea → mirror → CI tests it; staging runs the beta. - **To public (beta):** on `boksbc/clusev-staging` → Actions → run **Promote to public (beta channel)** with the beta tag → publishes it to `clusev/clusev` (beta channel). - **To stable:** run **Promote beta to stable** with the beta tag + the stable tag → publishes `vX.Y.Z` to `clusev/clusev` (stable channel). There are no custom webhooks: Gitea's push-mirror + GitHub Actions (`on: push tags` / `workflow_dispatch`) replace them. `promote.sh` copies the release tree with `git archive`, which honours `export-ignore`, so internal docs (`CLAUDE.md`, `rules.md`, `handoff.md`, `docs/`) never reach the public repo. Phase B will drive the `workflow_dispatch` promotions from the dev dashboard via the GitHub API with a deploy token in the encrypted vault — see `docs/superpowers/specs/2026-06-22-3-repo-release-promotion-design.md`.