{ pkgs, lib, config, ... }: with lib; let cfg = config.services.tubesync; in { options.services.tubesync = { enable = mkEnableOption "tubesync stack"; debug = mkEnableOption "debug logging"; package = mkOption { type = with types; package; default = pkgs.tubesync; description = "tubesync launcher package"; }; workers = mkOption { type = with types; int; default = 1; description = "maximum amount of concurrent workers"; }; user = mkOption { type = with types; str; default = "tubesync"; description = "user under which tubesync runs"; }; group = mkOption { type = with types; str; default = "tubesync"; description = "group under which tubesync runs"; }; listen = { host = mkOption { type = with types; str; default = "127.0.0.1"; description = "host to listen on"; }; port = mkOption { type = with types; port; default = 8080; description = "port to listen on"; }; }; stateDir = mkOption { type = with types; str; default = "/var/lib/tubesync"; description = "path to tubesync state storage directory"; }; dataDir = mkOption { type = with types; str; default = "${cfg.stateDir}/downloads"; description = "path to tubesync video downloads"; }; database = mkOption { type = with types; str; default = "postgresql://tubesync:@localhost:5432/tubesync"; description = "database connection string"; }; }; config = mkIf cfg.enable { systemd.services = let env = { GUNICORN_PID_FILE = "${cfg.stateDir}/run/gunicorn.pid"; GUNICORN_USER = cfg.user; GUNICORN_GROUP = cfg.group; DATABASE_CONNECTION = cfg.database; CONFIG_BASE_DIR = cfg.stateDir; DOWNLOADS_BASE_DIR = cfg.dataDir; TUBESYNC_DEBUG = mkIf cfg.debug "True"; TUBESYNC_WORKERS = toString cfg.workers; PYTHONPATH = cfg.package.pythonPath; REDIS_CONNECTION = "redis+socket://" + "${cfg.stateDir}/run/redis.sock"; }; base = description: { description = "tubesync: ${description}"; wantedBy = [ "multi-user.target" ]; environment = env; path = [ cfg.package ]; serviceConfig = { WorkingDirectory = cfg.stateDir; User = cfg.user; Group = cfg.group; LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateTmp = true; PrivateDevices = true; PrivateUsers = false; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProcSubset = "all"; ProtectSystem = "strict"; RemoveIPC = true; ReadWritePaths = with cfg; [ stateDir dataDir ]; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ]; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ]; #UMask = "0077"; }; }; base' = description: (base description) // { after = [ "tubesync.service" ]; partOf = [ "tubesync.service" ]; }; in { tubesync = recursiveUpdate (base "gunicorn") ({ after = [ "network.target" ]; serviceConfig = { PIDFile = env.GUNICORN_PID_FILE; ExecStartPre = pkgs.writeShellScript "tubesync-setup" '' set -xe tubesync-migrate mkdir -p "${cfg.stateDir}/run" mkdir -p "/tmp/tubesync" cp -r "${cfg.package.app}/static/." "/tmp/tubesync/static" chmod +w -R "/tmp/tubesync/static" ''; ExecStart = "${cfg.package}/bin/tubesync-gunicorn"; ExecReload = "/usr/bin/env kill -s HUP $MAINPID"; ExecStop = "/usr/bin/env kill -s TERM $MAINPID"; ExecStopPost = pkgs.writeShellScript "tubesync-cleanup" '' rm -f "$GUNICORN_PID_FILE" rm -rf "/tmp/tubesync" ''; }; }); tubesync-worker = recursiveUpdate (base' "worker") ({ serviceConfig.ExecStart = "${cfg.package}/bin/tubesync-worker"; }); # allow binding to unix socket redis-tubesync-celery.serviceConfig.ReadWritePaths = [ "${cfg.stateDir}/run" ]; }; services.redis.servers.tubesync-celery = { enable = true; inherit (cfg) user; unixSocket = "${cfg.stateDir}/run/redis.sock"; save = [ ]; }; users.users = mkIf (cfg.user == "tubesync") { tubesync = { description = "tubesync service account"; group = cfg.group; uid = config.ids.uids.tubesync; }; }; users.groups = mkIf (cfg.group == "tubesync") { tubesync.gid = config.ids.gids.tubesync; }; ids.uids.tubesync = 101; ids.gids.tubesync = 101; # package is local-only, will allow this for now nixpkgs.config.permittedInsecurePackages = [ "python3.12-django-3.2.25" ]; }; }