gitea-selfhost/README.md

180 lines
7.8 KiB
Markdown

# gitea-selfhost
Run your own [Gitea](https://about.gitea.com/) code host, set up the way the new internet's
hosted Gitea is: public repos browsable without an account, open sign-up behind a captcha,
`main` protected so every change lands through a pull request, and one Gitea user plus one
scoped token per agent.
Two ways to run it, same config:
| | Docker (default) | No Docker |
|---|---|---|
| What runs | official image `gitea/gitea:28.1.0-rootless` | official binary 28.1.0 (sha256-verified download) |
| Start | `./kit.sh up` | `KIT_MODE=binary` in `.env`, then `./kit.sh up` (or a systemd service) |
| Data | `./data` | `GITEA_HOME` (default `/var/lib/gitea`) |
| Config | `./config/app.ini` | `GITEA_CONF` (default `/etc/gitea/app.ini`) |
## One-command start
```sh
git clone <this repo> gitea-selfhost && cd gitea-selfhost
./kit.sh up
```
The first run creates `.env` from `.env.example`. Edit it (at least `DOMAIN` and `ROOT_URL`), then
run `./kit.sh up` again. It will:
1. render `config/app.ini` from `config/app.ini.template` with fresh random secrets (generated on
your machine, never committed);
2. start Gitea (Docker, or the downloaded binary) and wait for `/api/healthz`;
3. create the admin user (password generated into `secrets/admin.password` unless you set
`ADMIN_PASSWORD`) and an admin API token in `secrets/admin.token` (mode 0600).
Then open `ROOT_URL`. Needs: `bash`, `curl`, `python3` (for `protect-all`), and Docker with the
compose plugin (Docker mode).
### No Docker: binary + systemd
```sh
sed -i 's/^KIT_MODE=.*/KIT_MODE=binary/' .env
sudo ./kit.sh install-service
```
As root, that downloads and verifies the binary, creates a `git` system user, installs
`/usr/local/bin/gitea`, renders the config into `GITEA_CONF`, installs and starts
`systemd/gitea.service`, and creates the admin + token. Run later kit commands with `sudo`.
Without systemd, `./kit.sh up` in binary mode runs Gitea in the background with a pid file under
`GITEA_HOME` (set `GITEA_HOME` / `GITEA_CONF` in `.env` to paths you can write).
## Commands
```
./kit.sh up init + start + admin setup
./kit.sh protect OWNER/REPO [BRANCH] block direct pushes to BRANCH (default main); PRs only
./kit.sh protect-all protect the default branch of every non-empty repo
./kit.sh agent NAME one Gitea user + one scoped token for an agent
./kit.sh backup gitea dump into ./backups
./kit.sh status | down
```
## What the config does (`config/app.ini.template`)
| Setting | Value | Why |
|---|---|---|
| `DISABLE_REGISTRATION` | `false` | anyone can sign up (set `true` in `.env` for invite-only) |
| `ENABLE_CAPTCHA`, `CAPTCHA_TYPE` | `true`, `image` | built-in image captcha on sign-up, no third party |
| `REGISTER_EMAIL_CONFIRM` | `false` | turn on only after `[mailer]` really delivers mail |
| `REQUIRE_SIGNIN_VIEW` | `false` | public repos are browsable without an account |
| `DEFAULT_ALLOW_CREATE_ORGANIZATION` | `false` | new users cannot create organizations |
| `MAX_CREATION_LIMIT` | `10` | repos per regular user (admins are exempt) |
| `DISABLE_MIGRATIONS`, `[mirror] DISABLE_NEW_PULL` | `true` | no importing or mirroring from arbitrary URLs |
| `[security] EGRESS_MODE` | `lax` | webhooks may call public hosts only, not your LAN or loopback |
| `DEFAULT_KEEP_EMAIL_PRIVATE` | `true` | new users' emails hidden by default |
| `DEFAULT_BRANCH` | `main` | |
| `DISABLE_SSH` | `true` | Git over HTTPS with a token; nothing to expose but one HTTP port |
| `SSH_CREATE_AUTHORIZED_KEYS_FILE` | `false` | Gitea never touches the host user's `~/.ssh` |
Gitea has no instance-wide "protect main in every new repo" switch, so the kit applies the rule
per repo: `./kit.sh protect OWNER/REPO`, or `./kit.sh protect-all` from a timer
(`systemd/gitea-protect.timer` runs it every 5 minutes; with cron:
`*/5 * * * * cd /opt/gitea-selfhost && ./kit.sh protect-all`). The rule blocks direct pushes for
everyone, admins included; changes land through a PR. `REQUIRED_APPROVALS` in `.env` sets how many
approvals a PR needs (0 lets a solo admin merge their own PR; Gitea never lets you approve your own).
## Agents and tokens
- One Gitea user per agent: `./kit.sh agent my-agent`. The token (in `secrets/agents/my-agent.token`)
has `write:repository`, `write:issue` and `write:user`: it can create its own repos, push branches and
open PRs, but has no admin scope.
- Git remote: `https://<your host>/<owner>/<repo>.git`, username = agent name, password = token.
Keep it out of URLs and shell history: `git config credential.helper store` with a 0600 file, or
`GIT_ASKPASS`.
- API: `Authorization: token <token>`.
- Give an agent access to someone else's repo by adding it as a collaborator (write), not by sharing
tokens. Never hand out the admin token. Rotate a token by generating a new one and deleting the old
one in the user's settings (Applications).
## Put it behind a tunnel or reverse proxy
Keep Gitea on loopback (`BIND_ADDR=127.0.0.1`) and publish it through a proxy that terminates TLS.
Set `ROOT_URL` to the public URL and `COOKIE_SECURE=true` once it is https, then restart.
**Caddy** (automatic HTTPS):
```
git.example.com {
reverse_proxy 127.0.0.1:3000
}
```
**nginx**:
```nginx
server {
server_name git.example.com;
client_max_body_size 512m;
location / {
proxy_pass /gitea;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
Under a sub-path (`https://example.com/git/`), set `ROOT_URL = https://example.com/git/` and strip
the prefix in the proxy (`location /git/ { proxy_pass /gitea/; ... }`).
**Cloudflare Tunnel** (no open ports, no public IP needed):
```sh
# quick, temporary URL (changes when cloudflared restarts; update ROOT_URL each time)
cloudflared tunnel --url /gitea
# stable hostname on your own domain
cloudflared tunnel login
cloudflared tunnel create gitea
cloudflared tunnel route dns gitea git.example.com
cloudflared tunnel run --url /gitea gitea
```
## Backups
```sh
./kit.sh backup # backups/gitea-dump-YYYYmmdd-HHMMSS.zip: repos, database, config
```
The dump contains your `app.ini` secrets and every repo: store it encrypted and off the machine.
Also keep a copy of `.env` and `secrets/`. Restore = stop Gitea, unzip the dump, put the repos,
`gitea.db` and `app.ini` back in place (see the upstream
[backup and restore guide](https://docs.gitea.com/administration/backup-and-restore)), start Gitea.
## Upgrades
1. `./kit.sh backup`.
2. Read the [release notes](https://blog.gitea.com/) for breaking config changes.
3. Docker: change the image tag in `docker-compose.yml`, then `docker compose pull && docker compose up -d`.
Binary: change `GITEA_VERSION` in `kit.sh`, `rm bin/gitea && ./kit.sh up`
(systemd: `sudo install -m 0755 bin/gitea /usr/local/bin/gitea && sudo systemctl restart gitea`).
4. Gitea migrates the database on start. Check `./kit.sh status` and the log.
## Test the kit
```sh
tests/e2e-binary.sh [PORT]
```
Runs the whole flow in a throwaway directory on a spare port (default 3990): download and verify the
binary, start, create the admin, create a repo, protect `main`, check that a direct push to `main` is
rejected (even for the admin) while a PR merges, create an agent token (own repo yes, admin API no),
check the new-user defaults (no org creation, 10-repo limit), migrations off, captcha on sign-up,
backup. Then it stops Gitea and deletes everything.
## Licenses
- **Gitea** is free software under the MIT License, copyright the Gitea authors
(<https://github.com/go-gitea/gitea>). This kit does not include or modify Gitea: it downloads the
official binary or image.
- **This kit** (scripts, templates, docs) is MIT-licensed too; see [LICENSE](LICENSE).