{ config, pkgs, lib, ... }: with lib; let cfg = config.services.forgejo; in { options.services.forgejo = { enable = mkEnableOption "Forgejo Git server"; domain = mkOption { type = types.str; example = "git.example.com"; description = "The domain name for Forgejo"; }; stateDir = mkOption { type = types.str; default = "/var/lib/forgejo"; description = "Directory for Forgejo data"; }; database = { createLocally = mkOption { type = types.bool; default = true; description = "Whether to create a local PostgreSQL database"; }; passwordFile = mkOption { type = types.nullOr types.path; default = null; example = "/run/keys/forgejo-db"; description = "Path to file containing database password"; }; }; mailer = { enable = mkOption { type = types.bool; default = false; description = "Enable email notifications"; }; host = mkOption { type = types.str; example = "smtp.gmail.com"; description = "SMTP server hostname"; }; port = mkOption { type = types.port; default = 587; description = "SMTP server port"; }; from = mkOption { type = types.str; example = "git@example.com"; description = "Email sender address"; }; userFile = mkOption { type = types.path; example = "/run/keys/forgejo-smtp-user"; description = "Path to file containing SMTP username"; }; passwordFile = mkOption { type = types.path; example = "/run/keys/forgejo-smtp-pass"; description = "Path to file containing SMTP password"; }; }; oauth2 = { enable = mkOption { type = types.bool; default = false; description = "Enable OAuth2 authentication"; }; github = { enable = mkOption { type = types.bool; default = false; description = "Enable GitHub OAuth2 provider"; }; clientId = mkOption { type = types.str; description = "GitHub OAuth client ID"; }; clientSecret = mkOption { type = types.path; description = "Path to GitHub OAuth client secret"; }; }; }; lfs = { enable = mkOption { type = types.bool; default = true; description = "Enable Git LFS support"; }; storage = mkOption { type = types.str; default = "/var/lib/forgejo/lfs"; description = "Path to LFS storage"; }; }; backup = { enable = mkOption { type = types.bool; default = true; description = "Enable automated backups"; }; interval = mkOption { type = types.str; default = "daily"; description = "Backup interval (systemd timer format)"; }; retentionDays = mkOption { type = types.int; default = 7; description = "Number of backups to keep"; }; backupDir = mkOption { type = types.str; default = "/var/backups/forgejo"; description = "Directory to store backups"; }; }; }; config = mkIf cfg.enable { # PostgreSQL database services.postgresql = mkIf cfg.database.createLocally { enable = true; enableTCPIP = false; ensureDatabases = [ "forgejo" ]; ensureUsers = [ { name = "forgejo"; ensureDBOwnership = true; } ]; }; # Forgejo service services.forgejo = { enable = true; settings = { server = { HTTP_PORT = 3000; DOMAIN = cfg.domain; ROOT_URL = "https://${cfg.domain}/"; DISABLE_SSH = false; SSH_PORT = 22; LFS_START_SERVER = cfg.lfs.enable; LFS_CONTENT_PATH = cfg.lfs.storage; }; database = { DB_TYPE = "postgres"; HOST = "/run/postgresql"; NAME = "forgejo"; USER = "forgejo"; PASSWD = mkIf (cfg.database.passwordFile != null) "#dbpass#"; }; service = { DISABLE_REGISTRATION = false; REQUIRE_SIGNIN_VIEW = false; ENABLE_NOTIFY_MAIL = cfg.mailer.enable; }; mailer = mkIf cfg.mailer.enable { ENABLED = true; FROM = cfg.mailer.from; SMTP_ADDR = cfg.mailer.host; SMTP_PORT = toString cfg.mailer.port; USER = "#smtpuser#"; PASSWD = "#smtppass#"; PROTOCOL = "smtps"; }; session = { COOKIE_SECURE = true; COOKIE_SAMESITE = "strict"; }; security = { INSTALL_LOCK = true; PASSWORD_CHECK_PWN = true; PASSWORD_COMPLEXITY = "lower,digit" }; }; }; # Set database password from file systemd.services.forgejo = { serviceConfig = mkMerge [ (mkIf (cfg.database.passwordFile != null) { EnvironmentFile = cfg.database.passwordFile; }) (mkIf cfg.mailer.enable { EnvironmentFile = [ cfg.mailer.userFile cfg.mailer.passwordFile ]; }) ]; }; # Nginx reverse proxy services.nginx = { enable = true; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; recommendedTlsSettings = true; virtualHosts.${cfg.domain} = { forceSSL = true; enableACME = true; locations."/".proxyPass = "http://localhost:3000"; # Proxy for WebSocket locations."/" = { proxyPass = "http://localhost:3000"; proxyWebsockets = true; extraConfig = '' proxy_buffering off; proxy_read_timeout 86400; ''; }; # Proxy for LFS locations."/.git/info/lfs" = { proxyPass = "http://localhost:3000"; extraConfig = '' client_max_body_size 0; ''; }; }; }; # Firewall networking.firewall = { allowedTCPPorts = [ 80 443 ]; allowedUDPPorts = [ 443 ]; }; # Backup service systemd.services.forgejo-backup = mkIf cfg.backup.enable { description = "Forgejo backup service"; serviceConfig = { Type = "oneshot"; User = "forgejo"; Group = "forgejo"; WorkingDirectory = cfg.stateDir; ExecStart = "${pkgs.forgejo}/bin/forgejo dump --type zip --file ${cfg.backup.backupDir}/forgejo-backup-%Y-%m-%d.zip"; }; }; systemd.timers.forgejo-backup = mkIf cfg.backup.enable { description = "Forgejo backup timer"; wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = cfg.backup.interval; Persistent = true; }; }; # Backup cleanup systemd.services.forgejo-backup-cleanup = mkIf cfg.backup.enable { description = "Clean up old Forgejo backups"; serviceConfig = { Type = "oneshot"; User = "root"; ExecStart = pkgs.writeShellScript "forgejo-backup-cleanup" '' find ${cfg.backup.backupDir} -name "forgejo-backup-*.zip" -mtime +${toString cfg.backup.retentionDays} -delete ''; }; }; systemd.timers.forgejo-backup-cleanup = mkIf cfg.backup.enable { description = "Forgejo backup cleanup timer"; wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "weekly"; Persistent = true; }; }; # Create backup directory system.activationScripts.forgejo-backup-dir = '' mkdir -p ${cfg.backup.backupDir} chown forgejo:forgejo ${cfg.backup.backupDir} chmod 750 ${cfg.backup.backupDir} ''; # Add Forgejo to known services environment.systemPackages = with pkgs; [ forgejo ]; }; }