{ config, lib, pkgs, domain, vhosts ? { }, ... }: with lib; let cfg = config.infra.gitea; vhost = vhosts.${cfg.hostname} or { }; httpPort = vhost.ports.http or 3000; sshPort = vhost.ports.ssh or 2222; giteaBin = "${config.services.gitea.package}/bin/gitea"; discoveryUrl = "${cfg.oidc.issuerUrl}/.well-known/openid-configuration"; in { options.infra.gitea = { enable = mkEnableOption "Gitea with Keycloak OIDC"; hostname = mkOption { type = types.str; default = "git.${domain}"; description = "Public hostname for Gitea"; }; oidc = { enable = mkOption { type = types.bool; default = true; description = "Register Keycloak as an OIDC authentication source"; }; name = mkOption { type = types.str; default = "Keycloak"; description = "Display name for the OIDC auth source"; }; clientId = mkOption { type = types.str; description = "Keycloak OIDC client id"; }; clientSecretFile = mkOption { type = types.path; description = '' Path to a file (readable by the gitea user) containing the Keycloak OIDC client secret. ''; }; issuerUrl = mkOption { type = types.str; default = "https://auth.${domain}/realms/master"; description = "Keycloak realm issuer URL"; }; }; }; config = mkIf cfg.enable { services.gitea = { enable = true; settings = { server = { DOMAIN = cfg.hostname; ROOT_URL = "https://${cfg.hostname}/"; PROTOCOL = "http"; HTTP_ADDR = "127.0.0.1"; HTTP_PORT = httpPort; SSH_DOMAIN = cfg.hostname; SSH_PORT = sshPort; START_SSH_SERVER = true; SSH_LISTEN_PORT = sshPort; LANDING_PAGE = "explore"; }; service = { DISABLE_REGISTRATION = false; SHOW_REGISTRATION_BUTTON = false; }; openid = { ENABLE_OPENID_SIGNIN = true; ENABLE_OPENID_SIGNUP = true; }; oauth2_client = { ENABLE_AUTO_REGISTRATION = true; ACCOUNT_LINKING = "login"; UPDATE_AVATAR = true; }; }; }; networking.firewall.allowedTCPPorts = [ sshPort ]; systemd.services.gitea-oidc = mkIf cfg.oidc.enable { description = "Register Keycloak OIDC authentication source in Gitea"; after = [ "gitea.service" ]; bindsTo = [ "gitea.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; User = "gitea"; Group = "gitea"; RemainAfterExit = true; }; environment = { GITEA_CUSTOM = "/var/lib/gitea/custom"; GITEA_WORK_DIR = "/var/lib/gitea"; }; script = '' secret="$(cat ${cfg.oidc.clientSecretFile})" # Wait until Gitea has finished initialising its database for i in $(seq 1 60); do if ${giteaBin} admin auth list > /tmp/gitea-auth-list 2>/dev/null; then break fi sleep 2 done ${giteaBin} admin auth list > /tmp/gitea-auth-list || true existing=$(grep -w '${cfg.oidc.name}' /tmp/gitea-auth-list | awk '{print $1}' | head -n1) if [ -n "$existing" ]; then ${giteaBin} admin auth update-oauth --id "$existing" \ --name '${cfg.oidc.name}' \ --provider openidConnect \ --key '${cfg.oidc.clientId}' \ --secret "$secret" \ --auto-discover-url '${discoveryUrl}' else ${giteaBin} admin auth add-oauth \ --name '${cfg.oidc.name}' \ --provider openidConnect \ --key '${cfg.oidc.clientId}' \ --secret "$secret" \ --auto-discover-url '${discoveryUrl}' fi ''; }; }; }