{ config, pkgs, lib, ... }: let giteaPath = "/var/lib/gitea"; # Default service directory in { config = lib.mkIf config.services.gitea.enable { services.gitea = { database.type = "sqlite3"; settings = { actions.ENABLED = true; metrics.ENABLED = true; repository = { # Pushing to a repo that doesn't exist automatically creates one as # private. DEFAULT_PUSH_CREATE_PRIVATE = true; # Allow git over HTTP. DISABLE_HTTP_GIT = false; # Allow requests hitting the specified hostname. ACCESS_CONTROL_ALLOW_ORIGIN = config.hostnames.git; # Automatically create viable users/orgs on push. ENABLE_PUSH_CREATE_USER = true; ENABLE_PUSH_CREATE_ORG = true; # Default when creating new repos. DEFAULT_BRANCH = "main"; }; server = { HTTP_PORT = 3001; HTTP_ADDRESS = "127.0.0.1"; ROOT_URL = "https://${config.hostnames.git}/"; SSH_PORT = 22; START_SSH_SERVER = false; # Use sshd instead DISABLE_SSH = false; }; # Don't allow public users to register accounts. service.DISABLE_REGISTRATION = true; # Force using HTTPS for all session access. session.COOKIE_SECURE = true; # Hide users' emails. ui.SHOW_USER_EMAIL = false; }; extraConfig = null; }; users.users.${config.user}.extraGroups = [ "gitea" ]; caddy.routes = [ # Prevent public access to Prometheus metrics. { match = [ { host = [ config.hostnames.git ]; path = [ "/metrics*" ]; } ]; handle = [ { handler = "static_response"; status_code = "403"; } ]; } # Allow access to primary server. { match = [ { host = [ config.hostnames.git ]; } ]; handle = [ { handler = "reverse_proxy"; upstreams = [ { dial = "localhost:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}"; } ]; } ]; } ]; # Configure Cloudflare DNS to point to this machine services.cloudflare-dyndns.domains = [ config.hostnames.git ]; # Scrape the metrics endpoint for Prometheus. prometheus.scrapeTargets = [ "127.0.0.1:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}" ]; ## Backup config # Open to groups, allowing for backups systemd.services.gitea.serviceConfig.StateDirectoryMode = lib.mkForce "0770"; systemd.tmpfiles.rules = [ "f ${giteaPath}/data/gitea.db 0660 gitea gitea" ]; # Allow litestream and gitea to share a sqlite database users.users.litestream.extraGroups = [ "gitea" ]; users.users.gitea.extraGroups = [ "litestream" ]; # Backup sqlite database with litestream services.litestream = { settings = { dbs = [ { path = "${giteaPath}/data/gitea.db"; replicas = [ { url = "s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/gitea"; } ]; } ]; }; }; # Don't start litestream unless gitea is up systemd.services.litestream = { after = [ "gitea.service" ]; requires = [ "gitea.service" ]; }; # Run a repository file backup on a schedule systemd.timers.gitea-backup = lib.mkIf (config.backup.s3.endpoint != null) { timerConfig = { OnCalendar = "*-*-* 00:00:00"; # Once per day Unit = "gitea-backup.service"; }; wantedBy = [ "timers.target" ]; }; # Backup Gitea repos to object storage systemd.services.gitea-backup = lib.mkIf (config.backup.s3.endpoint != null) { description = "Backup Gitea data"; environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId; serviceConfig = { Type = "oneshot"; User = "gitea"; Group = "backup"; EnvironmentFile = config.secrets.backup.dest; }; script = '' ${pkgs.awscli2}/bin/aws s3 sync --exclude */gitea.db* \ ${giteaPath}/ \ s3://${config.backup.s3.bucket}/gitea-data/ \ --endpoint-url=https://${config.backup.s3.endpoint} ''; }; }; }