self: { pkgs, lib, config, ... }: let cfg = config.services.thulani; in { options.services.thulani = with lib; with lib.types; { enable = mkEnableOption "thulani"; package = mkOption { description = "thulani derivation to use"; type = package; default = pkgs.thulani; }; environment = mkOption { description = "literal environment to include"; type = attrs; }; envFiles = mkOption { description = "environment files to include"; type = listOf path; default = []; }; user = mkOption { description = "user to run service as"; type = str; default = "thulani"; }; group = mkOption { description = "group to run service as"; type = str; default = "thulani"; }; userIdMappingFile = mkOption { description = "user id mapping file"; type = nullOr path; default = null; }; restrictFile = mkOption { description = "restrict file"; type = nullOr path; default = null; }; restartTimer = mkOption { description = "restart on timer"; type = nullOr str; default = null; }; postgres = mkOption { description = "local postgres server with automatic setup"; type = submodule { options = { enable = mkEnableOption "postgres"; db = mkOption { description = "db name"; type = str; default = "memes"; }; }; }; }; }; config = lib.mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; services.thulani.environment = { RUST_BACKTRACE = lib.mkDefault "1"; MAX_HIST = lib.mkDefault "30"; DEFAULT_HIST = lib.mkDefault "5"; MAX_SHEET_COLUMN = lib.mkDefault "ZZZ"; YTDL = lib.mkDefault "${pkgs.yt-dlp}/bin/yt-dlp"; FFMPEG = lib.mkDefault "${pkgs.ffmpeg_4}/bin/ffmpeg"; RESTRICT = lib.mkIf (cfg.restrictFile != null) "${cfg.restrictFile}"; USER_ID_MAPPING = lib.mkIf (cfg.userIdMappingFile != null) "${cfg.userIdMappingFile}"; DATABASE_URL = lib.mkIf cfg.postgres.enable "postgres:///${cfg.postgres.db}?user=${cfg.user}&host=/var/run/postgresql"; }; systemd.services.thulani = { description = "thulani bot"; wantedBy = [ "multi-user.target" ]; bindsTo = [ "network-online.target" ] ++ lib.optional cfg.postgres.enable "postgresql.service"; after = [ "network-online.target" ] ++ lib.optional cfg.postgres.enable "postgresql.service"; inherit (cfg) environment; unitConfig = { StartLimitBurst = 3; StartLimitIntervalSec = "1m"; }; serviceConfig = { Type = "exec"; ExecStart = "${cfg.package}/bin/thulani"; ExecStartPre = let inherit (self.packages.${pkgs.system}) dbInit; in lib.mkIf cfg.postgres.enable "+${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} -- ${dbInit} ${config.services.postgresql.package}/bin/psql ${cfg.postgres.db} ${cfg.user}"; EnvironmentFile = cfg.envFiles; DynamicUser = true; User = cfg.user; Group = cfg.group; Restart = "always"; RestartSec = "10s"; TimeoutStopSec = "10s"; MemoryHigh = "200M"; MemoryMax = "300M"; ProtectSystem = "strict"; ProtectProc = "noaccess"; ProtectHome = true; ProtectHostname = true; ProtectClock = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectKernelLogs = true; ProtectControlGroups = true; PrivateDevices = true; PrivateUsers = true; PrivateMounts = true; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; MemoryDenyWriteExecute = true; LockPersonality = true; NoNewPrivileges = true; KeyringMode = "private"; SystemCallFilter = "@system-service"; SystemCallErrorNumber = "EPERM"; }; }; systemd.timers.thulani-restart = lib.mkIf (cfg.restartTimer != null) { wantedBy = ["timers.target"]; timerConfig = { OnCalendar = cfg.restartTimer; Unit = "thulani-restart.service"; }; }; systemd.services.thulani-restart = lib.mkIf (cfg.restartTimer != null) { description = "restart thulani bot"; serviceConfig = { Type = "oneshot"; }; script = '' set -euo pipefail if systemctl is-active thulani.service; then exec systemctl restart --no-block thulani.service fi ''; }; services.postgresql = lib.mkIf cfg.postgres.enable { enable = true; authentication = '' local ${cfg.postgres.db} ${cfg.user} ident ''; }; }; }