nixos-config/config/service/gitea-container.nix
2024-03-24 13:15:45 -07:00

279 lines
8.2 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.fudo.services.gitea-container;
hostname = config.instance.hostname;
domainName = config.fudo.hosts."${hostname}".domain;
zoneName = config.fudo.domains."${domainName}".zone;
siteName = config.fudo.hosts."${hostname}".site;
getSiteGatewayV4 = pkgs.lib.getSiteGatewayV4;
keyPaths = {
ed25519 = "/state/ssh/ed25519.key";
ecdsa = "/state/ssh/ecdsa.key";
};
in {
options.fudo.services.gitea-container = with types; {
enable = mkEnableOption "Enable Gitea running in a container.";
site-name = mkOption {
type = str;
description = "Name of this Gitea instance.";
};
hostname = mkOption {
type = str;
description = "Hostname at which the server is reachable.";
};
state-directory = mkOption {
type = str;
description = "Path at which to store Gitea state.";
};
trusted-networks = mkOption {
type = listOf str;
description =
"List of networks to be considered trusted (for metrics access).";
default = [ ];
};
networking = {
interface = mkOption {
type = str;
description = "Parent host interface on which to listen.";
};
ipv4 = mkOption {
type = nullOr (submodule {
options = {
address = mkOption {
type = str;
description = "IP address.";
};
prefixLength = mkOption {
type = int;
description = "Significant bits in the address.";
};
};
});
default = null;
};
ipv6 = mkOption {
type = nullOr (submodule {
options = {
address = mkOption {
type = str;
description = "IP address.";
};
prefixLength = mkOption {
type = int;
description = "Significant bits in the address.";
};
};
});
default = null;
};
};
};
config = mkIf cfg.enable {
systemd.tmpfiles.rules = [
"d ${cfg.state-directory}/gitea 755 root root - -"
"d ${cfg.state-directory}/acme 755 root root - -"
];
containers.gitea = {
autoStart = true;
additionalCapabilities = [ "CAP_NET_ADMIN" ];
macvlans = [ cfg.networking.interface ];
bindMounts = {
"/state" = {
hostPath = "${cfg.state-directory}/gitea";
isReadOnly = false;
};
"/var/lib/acme" = {
hostPath = "${cfg.state-directory}/acme";
isReadOnly = false;
};
};
config = {
nixpkgs.pkgs = pkgs;
systemd = {
tmpfiles.rules = [ "d /state 0755 root root - -" ];
services = {
gitea-chown = {
requiredBy = [ "gitea.service" ];
before = [ "gitea.service" ];
serviceConfig.Type = "oneshot";
script = "chown -R gitea:gitea /state";
};
gitea-keygen = {
requiredBy = [ "gitea.service" ];
before = [ "gitea.service" ];
serviceConfig.Type = "oneshot";
script = let
keygenScripts = mapAttrsToList (type: path:
let dir = dirOf path;
in ''
if [ ! -f ${path} ]; then
mkdir -p ${dir}
${pkgs.openssh}/bin/ssh-keygen -q -N "" -t ${type} -f ${path}
chown -R gitea:gitea ${dir}
chmod 0750 ${dir}
chmod 0440 ${path}
fi
'') keyPaths;
in concatStringsSep "\n" keygenScripts;
};
};
};
environment.systemPackages = let
giteaCli = pkgs.writeShellApplication {
name = "gitea-cli";
runtimeInputs = with pkgs; [ gitea ];
text = ''gitea --config /state/gitea/custom/conf/app.ini "$@"'';
};
in [ giteaCli ];
networking = {
defaultGateway = {
address = getSiteGatewayV4 siteName;
interface = "mv-${cfg.networking.interface}";
};
enableIPv6 = !isNull cfg.networking.ipv6;
nameservers = config.networking.nameservers;
firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 ];
};
interfaces."mv-${cfg.networking.interface}" = {
ipv4.addresses = optional (!isNull cfg.networking.ipv4) {
address = cfg.networking.ipv4.address;
prefixLength = cfg.networking.ipv4.prefixLength;
};
ipv6.addresses = optional (!isNull cfg.networking.ipv6) {
address = cfg.networking.ipv6.address;
prefixLength = cfg.networking.ipv6.prefixLength;
};
};
};
security.acme = {
acceptTerms = true;
defaults.email = "admin@${cfg.hostname}";
};
services = {
gitea = {
enable = true;
appName = cfg.site-name;
database = {
createDatabase = true;
type = "sqlite3";
};
repositoryRoot = "/state/repositories";
stateDir = "/state/gitea";
settings = {
service = {
#DISABLE_REGISTRATION = true;
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
};
security = {
INSTALL_LOCK = true;
LOGIN_REMEMBER_DAYS = 30;
};
metrics.ENABLED = cfg.trusted-networks != [ ];
server = {
START_SSH_SERVER = true;
# Host & port to display in the clone URL
SSH_DOMAIN = cfg.hostname;
# SSH_LISTEN_HOST = "0.0.0.0";
SSH_PORT = 22;
SSH_LISTEN_PORT = 2222;
DOMAIN = cfg.hostname;
ROOT_URL = "https://${cfg.hostname}";
SSH_SERVER_HOST_KEYS =
concatStringsSep "," (attrValues keyPaths);
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 8080;
};
oauth2_client = {
REGISTER_EMAIL_CONFIRM = false;
OPENID_CONNECT_SCOPES =
concatStringsSep "," [ "email" "profile" ];
#ENABLE_AUTO_REGISTRATION = true;
USERNAME = "email";
UPDATE_AVATAR = true;
ACCOUNT_LINKING = "login";
};
"git.timeout" = {
DEFAULT = 720;
MIGRATE = 3600;
MIRROR = 3600;
CLONE = 1200;
PULL = 1200;
GC = 600;
};
};
};
xinetd = {
enable = true;
services = [{
name = "ssh";
port = 22;
protocol = "tcp";
extraConfig = ''
redirect = localhost 2222
wait = no
socket_type = stream
'';
user = "nobody";
# Must be defined, but not used
server = "/usr/bin/env";
# unlisted = true;
}];
};
nginx = {
enable = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
recommendedGzipSettings = true;
virtualHosts."${cfg.hostname}" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
proxyWebsockets = true;
recommendedProxySettings = true;
};
locations."/metrics" = mkIf (cfg.trusted-networks != [ ]) {
proxyPass = "http://127.0.0.1:8080/metrics";
extraConfig = let
networkAllowClauses =
map (net: "allow ${net};") cfg.trusted-networks;
in concatStringsSep "\n"
(networkAllowClauses ++ [ "deny all;" ]);
};
};
};
};
};
};
};
}