{ config, lib, pkgs, homeyConfig, ... }: # Gitea — self-hosted Git service. # # Auth model: LDAP authentication is configured through Gitea's admin UI # (or CLI) after first start. Reverse proxy auth headers from Caddy/Authelia # handle transparent login. # # Volume layout: # /gitea/data/ → /data (repos, sqlite db, avatars, lfs, etc.) # # The app.ini is rendered by Nix and bind-mounted read-only. # # Secrets consumed from sops: # gitea/admin_password # gitea/lfs_jwt_secret # gitea/oauth2_jwt_secret # gitea/internal_token let cfg = config.homey.gitea; dataDir = config.homey.storage.mountPoint; domain = homeyConfig.domain; # Gitea app.ini — generated at build time. # Secrets that Gitea reads from env vars are referenced as env var names here. # The actual values are injected by the ExecStartPre wrapper below. giteaAppIni = '' APP_NAME = ${homeyConfig.organization} RUN_MODE = prod RUN_USER = git WORK_PATH = /data/gitea [repository] ROOT = /data/git/repositories [repository.local] LOCAL_COPY_PATH = /data/gitea/tmp/local-repo [repository.upload] TEMP_PATH = /data/gitea/uploads [server] APP_DATA_PATH = /data/gitea DOMAIN = git.${domain} HTTP_PORT = 3000 ROOT_URL = https://git.${domain}/ DISABLE_SSH = true LFS_START_SERVER = true ; LFS_JWT_SECRET injected at container start via env var / startup script LFS_JWT_SECRET = __GITEA_LFS_JWT_SECRET__ OFFLINE_MODE = false [lfs] PATH = /data/git/lfs [database] DB_TYPE = sqlite3 PATH = /data/gitea/gitea.db LOG_SQL = false [indexer] ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve [session] PROVIDER_CONFIG = /data/gitea/sessions PROVIDER = file [picture] AVATAR_UPLOAD_PATH = /data/gitea/avatars REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars DISABLE_GRAVATAR = false [attachment] PATH = /data/gitea/attachments [log] MODE = console LEVEL = info ROUTER = console ROOT_PATH = /data/gitea/log [security] INSTALL_LOCK = true REVERSE_PROXY_LIMIT = 1 REVERSE_PROXY_TRUSTED_PROXIES = * ; INTERNAL_TOKEN injected at container start INTERNAL_TOKEN = __GITEA_INTERNAL_TOKEN__ [service] DISABLE_REGISTRATION = true REQUIRE_SIGNIN_VIEW = false REGISTER_EMAIL_CONFIRM = false ENABLE_NOTIFY_MAIL = false ALLOW_ONLY_EXTERNAL_REGISTRATION = true ENABLE_CAPTCHA = false DEFAULT_ALLOW_CREATE_ORGANIZATION = true DEFAULT_ENABLE_TIMETRACKING = true NO_REPLY_ADDRESS = noreply.localhost ENABLE_REVERSE_PROXY_AUTHENTICATION = true ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = true [mailer] ENABLED = false [openid] ENABLE_OPENID_SIGNIN = false ENABLE_OPENID_SIGNUP = false [oauth2] ENABLE = false ; JWT_SECRET injected at container start JWT_SECRET = __GITEA_OAUTH2_JWT_SECRET__ ''; in { options.homey.gitea = { enable = lib.mkEnableOption "Gitea Git server"; image = lib.mkOption { type = lib.types.str; default = "docker.io/gitea/gitea:latest"; }; port = lib.mkOption { type = lib.types.port; default = 3000; description = "Host port Gitea listens on (bound to 127.0.0.1)."; }; }; config = lib.mkIf cfg.enable { # ----------------------------------------------------------------------- # Secrets # ----------------------------------------------------------------------- sops.secrets."gitea/admin_password" = { owner = "root"; }; sops.secrets."gitea/lfs_jwt_secret" = { owner = "root"; }; sops.secrets."gitea/oauth2_jwt_secret" = { owner = "root"; }; sops.secrets."gitea/internal_token" = { owner = "root"; }; # ----------------------------------------------------------------------- # Write the app.ini template to /etc (will be processed by ExecStartPre) # ----------------------------------------------------------------------- environment.etc."gitea/app.ini.tpl" = { text = giteaAppIni; mode = "0444"; }; # ----------------------------------------------------------------------- # Container # ----------------------------------------------------------------------- virtualisation.oci-containers.containers.gitea = { image = cfg.image; ports = [ "127.0.0.1:${toString cfg.port}:3000" ]; environment = { USER_UID = "1000"; USER_GID = "1000"; # Tell gitea where to look for the config GITEA_CUSTOM = "/data/gitea"; }; volumes = [ "${dataDir}/gitea/data:/data" # The processed app.ini is written by ExecStartPre into /run/gitea-conf/ "/run/gitea-conf/app.ini:/data/gitea/conf/app.ini:ro" ]; extraOptions = [ "--network=host" ]; }; # ----------------------------------------------------------------------- # ExecStartPre: substitute secret placeholders into the ini template # ----------------------------------------------------------------------- systemd.services."podman-gitea" = { serviceConfig = { ExecStartPre = [ (pkgs.writeShellScript "gitea-build-config" '' set -euo pipefail install -d -m 700 /run/gitea-conf LFS=$(cat ${config.sops.secrets."gitea/lfs_jwt_secret".path}) OAUTH=$(cat ${config.sops.secrets."gitea/oauth2_jwt_secret".path}) TOKEN=$(cat ${config.sops.secrets."gitea/internal_token".path}) sed \ -e "s|__GITEA_LFS_JWT_SECRET__|$LFS|g" \ -e "s|__GITEA_OAUTH2_JWT_SECRET__|$OAUTH|g" \ -e "s|__GITEA_INTERNAL_TOKEN__|$TOKEN|g" \ /etc/gitea/app.ini.tpl > /run/gitea-conf/app.ini chmod 444 /run/gitea-conf/app.ini '') ]; }; after = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" ]; requires = lib.mkAfter [ "mnt-data.mount" ]; }; }; }