# 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 ```