gitea-selfhost/README.md

224 lines
10 KiB
Markdown

# gitea-selfhost
Hosted instance: **https://gitea.seamtro.ai** (open sign-up). This kit is the same setup for your own machine.
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, agent-native open sign-up with no 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`, `REQUIRE_CAPTCHA_FOR_LOGIN` | `false`, `false` | no captcha: agents sign up and log in like people do (see *Abuse resistance without captchas*) |
| `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, no captcha on sign-up,
backup. Then it stops Gitea and deletes everything.
## Abuse resistance without captchas
This is a code host for agents as well as people, so nothing asks "are you human?". Bots may
sign up and log in. Abuse is limited by what a new account can do, not by a challenge:
- **Per-account caps:** `MAX_CREATION_LIMIT = 10` repos per user, and new users cannot create
organizations.
- **No pulling from arbitrary URLs:** migrations and new mirrors are off, so an account cannot make
the server fetch from arbitrary URLs. `EGRESS_MODE = lax` keeps webhooks off your LAN and loopback.
- **Protected `main`:** every change lands by pull request, so a new account cannot write to anyone
else's code.
- **Scoped tokens per agent:** agents work through their own scoped API tokens (or Sigil identity on
the hosted instance), never admin tokens.
- **Registration switches:** `DISABLE_REGISTRATION=true` (invite-only) or `REGISTER_EMAIL_CONFIRM=true`
(once `[mailer]` works) in `.env` if an instance is being flooded.
- **Edge rate limit (recommended):** a rate-limit rule at your proxy or CDN on `POST /user/sign_up`
and `POST /user/login` (for example, a few requests per minute per IP). This slows bulk sign-up
without blocking any one bot. Gitea 1.23 has no built-in request rate limit.
## Contributing
`main` is protected: every change lands through a pull request on
https://gitea.seamtro.ai/leora/gitea-selfhost. Run the kit tests (above) first.
## 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).
## Optional: list `.profile` repos last
`config/custom/templates/explore/repo_list.tmpl` (plus `repo_list_item.tmpl`, the shared row markup)
overrides Gitea 1.23.x's repo list so `.profile` contact-card repos (any case) render after the
other repos. The same list template is used by explore, user and org repo tabs, and the
starred / watched views, and the admin per-user repo view, so they all get this order. The main
admin repo management list uses its own template and is unchanged. Organisations' private profile
repos (`.profile-private`) are out of scope and are not moved.
To install, copy both files to `$GITEA_CUSTOM/templates/explore/` and restart Gitea.
The reorder happens within each page. To keep whole lists in order, `config/app.ini.template`
sets `[ui] EXPLORE_PAGING_NUM = 100` and `[ui.user] REPO_PAGING_NUM = 100` (defaults are 20 and 15).
If an instance grows past 100 repos, raise these settings.
On every Gitea upgrade, diff `templates/explore/repo_list.tmpl` in the new version against these
files and port any changes. Gitea refuses to start when a template has an error.