Something went wrong. Try again.
Something went wrong. Try again.
knotserver-module module.nix
7.8 kB · 257 lines
Nix
at knot-fix
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258# modified from https://tangled.sh/@tangled.sh/core/blob/master/flake.nixtangledFlake:{ config, pkgs, lib, ...}:let inherit (lib) mkOption mkEnableOption types mkIf optional ; cfg = config.services.tangled-knotserver; tangledPkgs = tangledFlake.packages.${pkgs.system};
keyfetch-wrapped = pkgs.runCommandCC "tangled-packages-wrapped" { nativeBuildInputs = [ pkgs.makeBinaryWrapper ]; } '' mkdir -p $out/bin
makeBinaryWrapper ${lib.getExe' tangledPkgs.knot "knot"} $out/bin/keyfetch \ --add-flags "keys" \ --append-flags "-output authorized-keys" \ --append-flags "-internal-api=http://${cfg.server.internalListenAddr}" \ --append-flags "-git-dir=${cfg.repo.scanPath}" \ --append-flags "-log-path=/var/log/knotserver/repoguard.log" '';
in{ imports = [ (lib.mkRenamedOptionModule ["services" "tangled-knotserver" "gitUser"] ["services" "tangled-knotserver" "user"]) ];
options = { services.tangled-knotserver = { enable = mkEnableOption "a tangled knot server";
openFirewall = mkOption { type = types.bool; default = false; description = "Whether to automatically configure the firewall to open necessary ports."; };
appviewEndpoint = mkOption { type = types.str; default = "https://tangled.sh"; description = "Appview endpoint"; };
user = mkOption { type = types.str; default = "git"; description = "User that runs the server, hosts git repos and performs git operations"; };
git = { name = mkOption { type = types.str; default = "Tangled Knot daemon"; description = "Git username for git operations that requires one."; }; email = mkOption { type = types.str; default = "knot@example.invalid"; description = "Git email address for git operations that requires one."; }; };
# TODO: should a `stateDirectory` option be added?
repo = { scanPath = mkOption { type = types.path; default = "/var/lib/tangled-knot"; description = "Path where repositories are stored"; };
mainBranch = mkOption { type = types.str; default = "main"; description = "Default branch name for repositories"; }; };
server = { listenAddr = mkOption { type = types.str; default = "0.0.0.0:5555"; description = "Address to listen on"; };
internalListenAddr = mkOption { type = types.str; default = "127.0.0.1:5444"; description = "Internal address for inter-service communication"; };
dbPath = mkOption { type = types.path; default = "knotserver.db"; description = "Path to the database file"; };
hostname = mkOption { type = types.str; example = "knot.tangled.sh"; description = "Hostname for the server (required)"; };
dev = mkOption { type = types.bool; default = false; description = "Enable development mode (disables signature verification)"; internal = true; }; };
extraConfig = mkOption { type = types.attrsOf types.str; default = { }; example = lib.literalExpression '' { # this is only an example, do NOT do this! your secret will end up readable by *everyone*! KNOT_SERVER_SECRET = "verysecuresecret"; } ''; description = '' Additional environment variables. Use `environmentFile` for secrets.
`KNOT_SERVER_SECRET` must be set for the knotserver to work, and can be obtained from [this page](https://tangled.sh/knots). Please set this with environmentFile instead of setting it here directly. ''; };
extraSshdConfig = mkOption { type = types.lines; default = ""; example = '' Banner none PasswordAuthentication no KbdInteractiveAuthentication no ''; description = "Additional sshd_config options to set for the git user."; };
environmentFile = mkOption { type = types.nullOr types.path; default = null; example = "/etc/tangled/knotserver.env"; description = '' Environment file to set additional configuration and secrets for the knotserver.
`KNOT_SERVER_SECRET` must be set for the knotserver to work, and can be obtained from [this page](https://tangled.sh/knots). ''; }; }; };
config = mkIf cfg.enable { warnings = optional cfg.server.dev '' tangled-knotserver: development mode is enabled. This is not recommended in production as signature checks are disabled. '';
assertions = [ { assertion = tangledPkgs ? knot; message = "tangled-knotserver: your version of tangled flake is not compatible with this version of the knotserver module. please consider updating the pinned @tangled.sh/core version."; } ];
environment.systemPackages = with pkgs; [ git ];
users.users.${cfg.user} = { createHome = true; home = cfg.repo.scanPath; group = cfg.user; isSystemUser = true; useDefaultShell = true; };
users.groups.${cfg.user} = { };
systemd.services.knotserver = { description = "knotserver service"; path = [pkgs.git]; after = [ "network-online.target" "sshd.service" ]; wants = [ "network-online.target" "sshd.service" ]; wantedBy = [ "multi-user.target" ];
preStart = '' git config --global user.name "${cfg.git.name}" git config --global user.email "${cfg.git.email}" '';
serviceConfig = { User = cfg.user; WorkingDirectory = cfg.repo.scanPath; ExecStart = if (tangledPkgs ? knotserver) # compat then lib.getExe' tangledPkgs.knotserver "knotserver" else "${lib.getExe' tangledPkgs.knot "knot"} server"; Restart = "always";
StateDirectory = mkIf (lib.hasPrefix "/var/lib/tangled-knot" cfg.repo.scanPath) "tangled-knot"; EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; # TODO: hardening };
environment = { APPVIEW_ENDPOINT = cfg.appviewEndpoint; KNOT_REPO_SCAN_PATH = cfg.repo.scanPath; KNOT_SERVER_INTERNAL_LISTEN_ADDR = cfg.server.internalListenAddr; KNOT_SERVER_LISTEN_ADDR = cfg.server.listenAddr; KNOT_SERVER_HOSTNAME = cfg.server.hostname; } // cfg.extraConfig; };
systemd.tmpfiles.settings."knotserver-settings"."/var/log/knotserver"."d" = { mode = "0750"; user = config.users.users.${cfg.user}.name; group = config.users.groups.${cfg.user}.name; };
services.openssh = { enable = true; # required for the module to actually function extraConfig = '' Match User ${cfg.user} AuthorizedKeysCommand ${config.security.wrapperDir}/keyfetch AuthorizedKeysCommandUser nobody ${cfg.extraSshdConfig} ''; };
# get around openssh restrictions security.wrappers.keyfetch = { owner = "root"; group = config.users.groups.${cfg.user}.name; permissions = "u+rx,go+x"; source = lib.getExe' keyfetch-wrapped "keyfetch"; };
# open firewall ports if configured networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [22]; };}