nixos-config/hosts/plover/modules/services/gitea.nix

248 lines
8.4 KiB
Nix

# My code forge service of choice. I'm pretty excited for the federation
# feature in particular to see how this plays out. It might not be toppling
# over the popular services but it is interesting to see new spaces for this
# one.
{ config, lib, pkgs, ... }:
let
hostCfg = config.hosts.plover;
cfg = hostCfg.services.gitea;
codeForgeDomain = "code.${config.networking.domain}";
giteaUser = config.users.users."${config.services.gitea.user}".name;
giteaDatabaseUser = config.services.gitea.user;
in
{
options.hosts.plover.services.gitea.enable =
lib.mkEnableOption "Gitea server for ${config.networking.domain}";
config = lib.mkIf cfg.enable (lib.mkMerge [
{
sops.secrets = lib.getSecrets ../../secrets/secrets.yaml {
"gitea/db/password".owner = giteaUser;
"gitea/smtp/password".owner = giteaUser;
};
services.gitea = {
enable = true;
appName = "foodogsquared's code forge";
database = {
type = "postgres";
passwordFile = config.sops.secrets."gitea/db/password".path;
};
# Allow Gitea to take a dump.
dump = {
enable = true;
interval = "weekly";
};
# There are a lot of services in port 3000 so we'll change it.
lfs.enable = true;
mailerPasswordFile = config.sops.secrets."gitea/smtp/password".path;
# You can see the available configuration options at
# https://docs.gitea.io/en-us/config-cheat-sheet/.
settings = {
server = {
ROOT_URL = "https://${codeForgeDomain}";
HTTP_PORT = 8432;
DOMAIN = codeForgeDomain;
};
"repository.pull_request" = {
WORK_IN_PROGRESS_PREFIXES = "WIP:,[WIP],DRAFT,[DRAFT]";
ADD_CO_COMMITTERS_TRAILERS = true;
};
ui = {
DEFAULT_THEME = "auto";
EXPLORE_PAGING_SUM = 15;
GRAPH_MAX_COMMIT_NUM = 200;
};
"ui.meta" = {
AUTHOR = "foodogsquared's code forge";
DESCRIPTION = "foodogsquared's personal projects and some archived and mirrored codebases.";
KEYWORDS = "foodogsquared,gitea,self-hosted";
};
# It's a personal instance so nah...
service.DISABLE_REGISTRATION = true;
repository = {
ENABLE_PUSH_CREATE_USER = true;
DEFAULT_PRIVATE = "public";
DEFAULT_PRIVATE_PUSH_CREATE = true;
};
"markup.asciidoc" = {
ENABLED = true;
NEED_POSTPROCESS = true;
FILE_EXTENSIONS = ".adoc,.asciidoc";
RENDER_COMMAND = "${pkgs.asciidoctor}/bin/asciidoctor --embedded --out-file=- -";
IS_INPUT_FILE = false;
};
# Mailer service.
mailer = {
ENABLED = true;
PROTOCOL = "smtp+starttls";
SMTP_ADDRESS = "smtp.sendgrid.net";
SMTP_PORT = 587;
USER = "apikey";
FROM = "bot+gitea@foodogsquared.one";
SEND_AS_PLAIN_TEXT = true;
SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
};
# Reduce the logs to be filled with. You also have to keep in mind this
# to be configured with fail2ban.
log.LEVEL = "Warn";
# Well, collaboration between forges is nice...
federation.ENABLED = true;
# Enable mirroring feature...
mirror.ENABLED = true;
# Session configuration.
session.COOKIE_SECURE = true;
# Some more database configuration.
database.SCHEMA = config.services.gitea.user;
# Run various periodic services.
"cron.update_mirrors".SCHEDULE = "@every 3h";
other = {
SHOW_FOOTER_VERSION = true;
ENABLE_SITEMAP = true;
ENABLE_FEED = true;
};
};
};
# Disk space is always assumed to be limited so we're really only limited
# with 2 dumps.
systemd.services.gitea-dump.preStart = lib.mkAfter ''
${pkgs.findutils}/bin/find ${lib.escapeShellArg config.services.gitea.dump.backupDir} \
-maxdepth 1 -type f -iname '*.${config.services.gitea.dump.type}' -ctime 21 \
| tail -n -3 | xargs rm
'';
# Customizing Gitea which you can see more details at
# https://docs.gitea.io/en-us/customizing-gitea/. We're just using
# systemd-tmpfiles to make this work which is pretty convenient.
systemd.tmpfiles.rules =
let
# To be used similarly to $GITEA_CUSTOM variable.
giteaCustomDir = config.services.gitea.customDir;
in
[
"L+ ${giteaCustomDir}/templates/home.tmpl - - - - ${../../files/gitea/home.tmpl}"
"L+ ${giteaCustomDir}/public/img/logo.svg - - - - ${../../files/gitea/logo.svg}"
"L+ ${giteaCustomDir}/public/img/logo.png - - - - ${../../files/gitea/logo.png}"
];
}
(lib.mkIf hostCfg.services.database.enable {
# Making sure this plays nicely with the database service of choice. Take
# note, we're mainly using secure schema usage pattern here as described from
# the PostgreSQL documentation at
# https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS.
services.postgresql = {
ensureUsers = [{
name = config.services.gitea.user;
ensurePermissions = {
"SCHEMA ${config.services.gitea.user}" = "ALL PRIVILEGES";
};
}];
};
# Setting up Gitea for PostgreSQL secure schema usage.
systemd.services.gitea = {
# Gitea service module will have to set up certain things first which is
# why we have to go first.
preStart =
let
gitea = lib.getExe' config.services.gitea.package "gitea";
giteaAdminUsername = lib.escapeShellArg "foodogsquared";
psql = lib.getExe' config.services.postgresql.package "psql";
in
lib.mkMerge [
(lib.mkBefore ''
# Setting up the appropriate schema for PostgreSQL secure schema usage.
${psql} -tAc "CREATE SCHEMA IF NOT EXISTS AUTHORIZATION ${giteaDatabaseUser};"
'')
(lib.mkAfter ''
# Setting up the administrator account automated.
${gitea} admin user list --admin | grep -q ${giteaAdminUsername} \
|| ${gitea} admin user create \
--username ${giteaAdminUsername} --email foodogsquared@${config.networking.domain} \
--random-password --random-password-length 76 --admin
'')
];
};
})
(lib.mkIf hostCfg.services.reverse-proxy.enable {
# Attaching it altogether with the reverse proxy of choice.
services.nginx.virtualHosts."${codeForgeDomain}" = {
forceSSL = true;
enableACME = true;
acmeRoot = null;
kTLS = true;
locations."/" = {
proxyPass = "http://gitea";
};
extraConfig = ''
proxy_cache ${config.services.nginx.proxyCachePath.apps.keysZoneName};
'';
};
services.nginx.upstreams."gitea" = {
extraConfig = ''
zone services;
'';
servers = {
"localhost:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}" = { };
};
};
})
(lib.mkIf hostCfg.services.fail2ban.enable {
# Configuring fail2ban for this service which thankfully has a dedicated page
# at https://docs.gitea.io/en-us/fail2ban-setup/.
services.fail2ban.jails = {
gitea.settings = {
enabled = true;
backend = "systemd";
filter = "gitea[journalmatch='_SYSTEMD_UNIT=gitea.service + _COMM=gitea']";
maxretry = 8;
};
};
environment.etc = {
"fail2ban/filter.d/gitea.conf".text = ''
[Includes]
before = common.conf
# Thankfully, Gitea also has a dedicated page for configuring fail2ban
# for the service at https://docs.gitea.io/en-us/fail2ban-setup/
[Definition]
failregex = ^.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
ignoreregex =
'';
};
})
(lib.mkIf hostCfg.services.backup.enable {
# Add the following files to be backed up.
services.borgbackup.jobs.services-backup.paths = [ config.services.gitea.dump.backupDir ];
})
]);
}