From 2ed9e71e5ac70730587353ca789ec1371318c4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoran=20Peri=C4=8Di=C4=87?= Date: Sat, 1 Aug 2026 19:02:52 +0200 Subject: [PATCH] Add README --- README.md | 442 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c60b3c --- /dev/null +++ b/README.md @@ -0,0 +1,442 @@ +# Gitea RPM Package + +Self-hosted Git service packaged as an RPM for Fedora. Ships as a base package +with two mutually exclusive subpackages: `gitea-service` (native binary) and +`gitea-container` (Podman quadlet with rootless image). + +## Package structure + +``` +gitea Base: git user (UID 124), dirs, /etc/gitea/app.ini +gitea-service Native binary under systemd +gitea-container Podman quadlet (gitea + postgres containers in a pod) +``` + +## Path resolution and environment variables + +Gitea resolves its key paths through a priority chain. Understanding this is +essential because the native install, rootful Docker, and rootless Docker each +set these differently. Run `gitea --help` to see the computed defaults: + +``` +DEFAULT CONFIGURATION: + AppPath: /usr/libexec/gitea # absolute path to binary + WorkPath: /var/lib/gitea # resolved AppWorkPath + CustomPath: /var/lib/gitea/custom # {WorkPath}/custom + ConfigFile: /var/lib/gitea/custom/conf/app.ini # {CustomPath}/conf/app.ini +``` + +(When run without overrides, WorkPath defaults to the binary's directory.) + +### AppPath + +Absolute path to the Gitea binary itself. Not configurable at runtime — it is +always the real path of the running executable. All other paths that are +relative get resolved against the directory of AppPath. + +### AppWorkPath (WORK_PATH) + +The working directory that serves as the base for all relative paths. Resolved +in this order (first wins): + +1. `--work-path` / `-w` CLI flag +2. `GITEA_WORK_DIR` environment variable +3. `WORK_PATH` in `app.ini` +4. Built-in value (set at compile time via LDFLAGS) +5. Directory containing AppPath (the binary) + +If any of the above are relative, they become absolute against AppPath's +directory. + +| Deployment | How it's set | Effective value | +| -------------------- | --------------------------------------------------- | ---------------- | +| Gitea default | Falls through to rule 5 (binary dir) | _(binary dir)_ | +| Native install (RPM) | `Environment=GITEA_WORK_DIR=/var/lib/gitea` in unit | `/var/lib/gitea` | +| Docker rootful | Built into image (LDFLAGS at compile time) | `/data` | +| Docker rootless | Built into image (LDFLAGS at compile time) | `/data` | + +### CustomPath (GITEA_CUSTOM) + +Base directory for custom templates, public files, and (by default) `app.ini`. +Resolved in this order: + +1. `--custom-path` / `-C` CLI flag +2. `GITEA_CUSTOM` environment variable +3. Built-in value (compile time) +4. `{AppWorkPath}/custom` + +### CustomConf (app.ini location) + +1. `--config` / `-c` CLI flag +2. Built-in value (compile time) +3. `{CustomPath}/conf/app.ini` + +Note: this RPM uses `-c /etc/gitea/app.ini` in the systemd unit to override +the default `{CustomPath}/conf/app.ini` path. + +### APP_DATA_PATH + +Defined in `[server]`. Defaults to `{AppWorkPath}/data` (or `/data/gitea` on +rootful Docker). All relative paths in storage-related settings resolve against +this. Serves as root for: LFS, avatars, packages, attachments, queues, +indexers, etc. + +### HOME directory and RUN_USER + +`RUN_USER` in `app.ini` specifies which OS user Gitea expects to run as +(default: `git`). Gitea uses `~` / `HOME` for: + +- `SSH_ROOT_PATH` defaults to `~/.ssh` +- `SSH_SERVER_HOST_KEY` defaults to `~/.ssh/id_rsa` +- `authorized_keys` management (when `SSH_CREATE_AUTHORIZED_KEYS_FILE = true`) + +| Deployment | RUN_USER | HOME | +| -------------------- | -------- | ---------------- | +| Native install (RPM) | `git` | `/var/lib/gitea` | +| Docker rootful | `git` | `/data/gitea` | +| Docker rootless | `git` | `/var/lib/gitea` | + +### Docker-specific environment variables + +| Variable | Purpose | +| --------------------------- | ------------------------------------------------------------------- | +| `USER` | Username inside container (default: `git`) | +| `USER_UID` | Unix UID, must match volume ownership (default: `1000`) | +| `USER_GID` | Unix GID, must match volume ownership (default: `1000`) | +| `GITEA__section__KEY` | Override any `app.ini` value (e.g. `GITEA__database__HOST=db:5432`) | +| `GITEA__section__KEY__FILE` | Same but reads value from a file path (for secrets) | + +This RPM sets `USER_UID=124` / `USER_GID=124` in the quadlet to match the +system `git` user created by sysusers.d. + +## Directory layout + +### Gitea default (built-in, relative to WORK_PATH) + +When Gitea runs without any overrides, all paths are relative to the working +directory (typically where the binary lives or `GITEA_WORK_DIR`). + +``` +WORK_PATH/ + custom/ # custom templates, public files + data/ # APP_DATA_PATH + gitea-repositories/ # [repository] ROOT + lfs/ # [lfs] PATH + avatars/ # [picture] AVATAR_UPLOAD_PATH + repo-avatars/ # [picture] REPOSITORY_AVATAR_UPLOAD_PATH + packages/ # [packages] STORAGE_PATH + attachments/ # [attachment] PATH + sessions/ # [session] PROVIDER_CONFIG (file mode) + indexers/ # bleve search indexes + queues/ # async task queues + tmp/ # temporary archives, uploads + log/ # [log] ROOT_PATH (default: WORK_PATH/log) +``` + +### Native install from binary (this RPM: gitea-service) + +FHS-compliant layout using systemd, sysusers, tmpfiles. + +``` +/usr/libexec/gitea # binary +/etc/gitea/app.ini # config (root:git 0640) +/var/lib/gitea/ # WORK_PATH (git:git 0750) + custom/ + data/ # APP_DATA_PATH = data (relative) + git/ + repositories/ # [repository] ROOT = data/git/repositories + lfs/ # [lfs] PATH = data/git/lfs + avatars/ # default: data/avatars + repo-avatars/ # default: data/repo-avatars + packages/ # default: data/packages + attachments/ # default: data/attachments + sessions/ # [session] PROVIDER = file + indexers/ + queues/ + actions_artifacts/ + tmp/ + log/ # [log] ROOT_PATH = log (relative) +/run/gitea/ # tmpfiles.d, runtime +``` + +Systemd unit: `gitea.service` + +- `User=git`, `Group=git` (UID/GID 124 via sysusers.d) +- `WorkingDirectory=/var/lib/gitea` +- `Environment=GITEA_WORK_DIR=/var/lib/gitea` +- Database drop-in: `/usr/lib/systemd/system/gitea.service.d/database.conf` + (uncomment Wants/After for postgresql, redis, etc.) + +SSH passthrough: `/etc/ssh/sshd_config.d/50-gitea.conf` + +``` +Match User git + AuthorizedKeysCommand /usr/libexec/gitea keys -c /etc/gitea/app.ini -e git -u %u -t %t -k %k + AuthorizedKeysCommandUser git +``` + +### Docker / Podman rootful (`gitea/gitea:1.25`) + +The standard Docker image runs as root and remaps paths under `/data`: + +``` +/data/ + gitea/ + conf/app.ini # config + avatars/ # AVATAR_UPLOAD_PATH + packages/ # STORAGE_PATH + ... # APP_DATA_PATH = /data/gitea + git/ + repositories/ # [repository] ROOT + lfs/ # [lfs] PATH +``` + +Volume: `-v /host/path:/data` + +### Docker / Podman rootless (`gitea/gitea:1.25-rootless`) + +The rootless image flattens everything under `/var/lib/gitea` and `/data`: + +``` +/data/ # main volume + gitea/ + conf/app.ini # config (mounted from host or volume) + db/ # sqlite (if used) + git/ + repositories/ # [repository] ROOT + ... +/var/lib/gitea/ # APP_DATA_PATH (secondary volume or subpath) + avatars/ + repo-avatars/ + packages/ + attachments/ + indexers/ + queues/ + actions_artifacts/ + data/lfs/ # [lfs] PATH + tmp/ +``` + +This RPM's quadlet (`gitea.container`) uses the rootless image and maps: + +| Host path | Container path | Purpose | +| ---------------- | ------------------ | ------------------------------ | +| `/var/lib/gitea` | `/data` | Repositories, config, git data | +| `/etc/gitea` | `/data/gitea/conf` | app.ini (read-only intent) | + +### Elektronika Smodlaka (rootless container, custom app.ini) + +Uses the rootless image via quadlet with `APP_DATA_PATH = /var/lib/gitea` and +explicit absolute paths. Git data is separated under `git/` alongside `data/`. + +``` +/var/lib/gitea/ # WORK_PATH & APP_DATA_PATH + git/ + repositories/ # [repository] ROOT + lfs/ # [lfs] PATH + data/ + gitea.db # [database] PATH (sqlite fallback) + avatars/ # [picture] AVATAR_UPLOAD_PATH + repo-avatars/ # [picture] REPOSITORY_AVATAR_UPLOAD_PATH + attachments/ # [attachment] PATH + sessions/ # [session] PROVIDER_CONFIG (file) + log/ # [log] ROOT_PATH + custom/ # templates, public overrides + indexers/ + queues/ + actions_artifacts/ + packages/ + tmp/ +/tmp/gitea/ + local-repo/ # [repository.local] LOCAL_COPY_PATH + uploads/ # [repository.upload] TEMP_PATH +``` + +Key differences from defaults: + +- `APP_DATA_PATH` = `/var/lib/gitea` (not `data/` relative) — everything under one root +- `git/` directory sits alongside `data/` (repos and LFS separated from app data) +- `[log] ROOT_PATH` goes to `/var/lib/gitea/data/log` (inside data/, not alongside) +- Uses postgres (`HOST = 127.0.0.1`), but `[database] PATH` is set for sqlite fallback +- Built-in SSH server on port 30009 (`START_SSH_SERVER = true`) + +## Path comparison table + +| Setting | Gitea Default | RPM app.ini | Docker Rootful | Docker Rootless | Elektronika Smodlaka | +| ----------------------------------------- | ------------------------- | -------------------------------------- | -------------------------- | ----------------------------- | ---------------------------------- | +| `WORK_PATH` | _(binary dir)_ | `/var/lib/gitea` | `/data` | `/data` | `/var/lib/gitea` | +| `APP_DATA_PATH` | `data` (relative) | _(default)_ `data` → `.../gitea/data` | `/data/gitea` | `/var/lib/gitea` | `/var/lib/gitea` | +| `[repository] ROOT` | `data/gitea-repositories` | `data/git/repositories` | `/data/git/repositories` | `/data/git/repositories` | `/var/lib/gitea/git/repositories` | +| `[repository.local] LOCAL_COPY_PATH` | `/tmp/gitea/local-repo` | _(default)_ | _(default)_ | _(default)_ | `/tmp/gitea/local-repo` | +| `[repository.upload] TEMP_PATH` | `/tmp/gitea/uploads` | _(default)_ | _(default)_ | _(default)_ | `/tmp/gitea/uploads` | +| `[lfs] PATH` | `data/lfs` | `data/git/lfs` | `/data/git/lfs` | `/var/lib/gitea/data/lfs` | `/var/lib/gitea/git/lfs` | +| `[picture] AVATAR_UPLOAD_PATH` | `data/avatars` | _(default)_ `data/avatars` | `/data/gitea/avatars` | `/var/lib/gitea/avatars` | `/var/lib/gitea/data/avatars` | +| `[picture] REPOSITORY_AVATAR_UPLOAD_PATH` | `data/repo-avatars` | _(default)_ `data/repo-avatars` | `/data/gitea/repo-avatars` | `/var/lib/gitea/repo-avatars` | `/var/lib/gitea/data/repo-avatars` | +| `[attachment] PATH` | `data/attachments` | _(default)_ `data/attachments` | _(default)_ | _(default)_ | `/var/lib/gitea/data/attachments` | +| `[session] PROVIDER_CONFIG` | `data/sessions` | _(default)_ `data/sessions` | _(default)_ | _(default)_ | `/var/lib/gitea/data/sessions` | +| `[packages] STORAGE_PATH` | `data/packages` | _(default)_ `data/packages` | `/data/gitea/packages` | `/var/lib/gitea/packages` | _(default)_ | +| `[log] ROOT_PATH` | `WORK_PATH/log` | `log` → `/var/lib/gitea/log` | `/data/gitea/log` | `/var/lib/gitea/log` | `/var/lib/gitea/data/log` | +| `[database] PATH` (sqlite) | `data/gitea.db` | _(default)_ `data/gitea.db` | _(default)_ | _(default)_ | `/var/lib/gitea/data/gitea.db` | + +## Directories that grow + +| Directory | What it stores | Growth driver | +| --------------------- | ---------------------------------- | ----------------------------------------- | +| `gitea-repositories/` | Bare git repos | Number and size of repos | +| `lfs/` | Git LFS objects | Large file usage | +| `packages/` | Package registry artifacts | Package publishing | +| `indexers/` | Bleve/Elasticsearch search indexes | Number of repos (if code indexer enabled) | +| `actions_artifacts/` | CI/CD build artifacts | Gitea Actions usage | +| `queues/` | Async task data | Usually small, grows during backlogs | +| `tmp/` | Archive downloads, upload staging | Transient, reclaimed automatically | + +To move heavy directories to separate storage, use absolute paths in `app.ini`: + +```ini +[repository] +ROOT = /mnt/large_disk/repositories + +[packages] +STORAGE_PATH = /mnt/large_disk/packages +``` + +## Quadlet setup (gitea-container) + +The container subpackage installs Podman quadlet files to +`/usr/share/containers/systemd/`: + +| File | Purpose | +| -------------------- | ---------------------------------------- | +| `gitea.pod` | Pod definition, publishes port 1240:3000 | +| `gitea.container` | Gitea rootless container | +| `gitea-db.container` | PostgreSQL 18 container | + +Enabled via drop-in: `/etc/containers/systemd/gitea.pod.d/enable.conf` +(`WantedBy=multi-user.target`) + +Database credentials are passed as environment variables in the container +definition. For production, replace these with Podman secrets or a +`POSTGRES_PASSWORD_FILE` approach. + +SSH passthrough for containers uses a shim script: + +- `/usr/libexec/gitea-keys` - calls `podman exec` into the container +- `/usr/libexec/gitea-container` - shim so `gitea` CLI commands work from host + +``` +# /etc/ssh/sshd_config.d/50-gitea.conf (container variant) +Match User git + AuthorizedKeysCommand /usr/libexec/gitea-keys %u %t %k + AuthorizedKeysCommandUser root +``` + +Note: `AuthorizedKeysCommandUser` is `root` (not `git`) because podman exec +requires elevated privileges when run from sshd. + +## Quick start + +### Native binary + +```bash +sudo dnf install gitea gitea-service +# Edit /etc/gitea/app.ini (database, domain, etc.) +# Uncomment database dependency in /usr/lib/systemd/system/gitea.service.d/database.conf +sudo systemctl daemon-reload +sudo systemctl enable --now gitea +``` + +### Container (Podman quadlet) + +```bash +sudo dnf install gitea gitea-container +# Edit /etc/gitea/app.ini +# The pod auto-starts via the enable.conf drop-in +sudo systemctl daemon-reload +sudo systemctl start gitea-pod +``` + +Gitea will be available at `http://localhost:1240` (container) or +`http://localhost:3000` (native). + +## Migration: Elektronika Smodlaka → RPM native layout + +Copies data from the Elektronika Smodlaka container layout +(`/srv/storage/gitea-data/data`) to the RPM's native install structure +(`/var/lib/gitea`). The RPM app.ini groups repos and LFS under `data/git/`. + +| Source (Elektronika) | Destination (RPM) | Notes | +| ------------------------ | ------------------------ | ------------------------------ | +| `git/repositories/` | `data/git/repositories/` | Same name, different parent | +| `git/lfs/` | `data/git/lfs/` | Same name, different parent | +| `data/avatars/` | `data/avatars/` | Same relative path | +| `data/repo-avatars/` | `data/repo-avatars/` | Same relative path | +| `data/attachments/` | `data/attachments/` | Same relative path | +| `data/sessions/` | `data/sessions/` | Same relative path | +| `data/log/` | `log/` | RPM uses WORK_PATH/log | +| `indexers/` | `data/indexers/` | Moves under APP_DATA_PATH | +| `queues/` | `data/queues/` | Moves under APP_DATA_PATH | +| `actions_artifacts/` | `data/actions_artifacts/`| Moves under APP_DATA_PATH | +| `packages/` | `data/packages/` | Moves under APP_DATA_PATH | +| `custom/` | `custom/` | Same relative path | + +```bash +#!/bin/bash +set -euo pipefail + +SRC="/srv/storage/gitea-data/data" +DST="/var/lib/gitea" + +# Stop gitea before migration +# systemctl stop gitea + +# Git data (repositories + LFS) → data/git/ +rsync -av --progress "$SRC/git/repositories/" "$DST/data/git/repositories/" +rsync -av --progress "$SRC/git/lfs/" "$DST/data/git/lfs/" + +# App data dirs that keep the same relative path under data/ +for dir in avatars repo-avatars attachments sessions; do + if [ -d "$SRC/data/$dir" ]; then + rsync -av --progress "$SRC/data/$dir/" "$DST/data/$dir/" + fi +done + +# Logs (data/log → log — RPM default is WORK_PATH/log) +if [ -d "$SRC/data/log" ]; then + rsync -av --progress "$SRC/data/log/" "$DST/log/" +fi + +# Dirs that were at root in Elektronika, move under data/ for RPM +for dir in indexers queues actions_artifacts packages; do + if [ -d "$SRC/$dir" ]; then + rsync -av --progress "$SRC/$dir/" "$DST/data/$dir/" + fi +done + +# Custom templates/public files +if [ -d "$SRC/custom" ]; then + rsync -av --progress "$SRC/custom/" "$DST/custom/" +fi + +# SQLite database (only if present, RPM uses postgres by default) +if [ -f "$SRC/data/gitea.db" ]; then + rsync -av --progress "$SRC/data/gitea.db" "$DST/data/gitea.db" +fi + +# Fix ownership +chown -R git:git "$DST" + +echo "Migration complete. Verify /etc/gitea/app.ini paths match RPM app.ini." +``` + +The RPM app.ini only sets two paths explicitly (rest are defaults): + +```ini +[repository] +ROOT = data/git/repositories ;; was: /var/lib/gitea/git/repositories (absolute) + +[lfs] +PATH = data/git/lfs ;; was: /var/lib/gitea/git/lfs (absolute) + +;; [log] ROOT_PATH = log ;; RPM default, resolves to /var/lib/gitea/log +;; ;; Elektronika had: /var/lib/gitea/data/log +```