# SPDX-FileCopyrightText: 2024 Luke Granger-Brown # # SPDX-License-Identifier: Apache-2.0 { depot, pkgs, lib, config, ... }: let hackyplayerSrc = /home/lukegb/Projects/hackyplayer; hackyplayer = import hackyplayerSrc { inherit pkgs; }; yamlFormat = pkgs.formats.yaml {}; cfg = config.my.hackyplayer; processedConfig = lib.mapAttrs' (name: value: lib.nameValuePair "FLASK_${name}" (builtins.toJSON value)) cfg.config; readWritePaths = [ cfg.config.VIDEO_OUTPUT cfg.config.VIDEO_TEMP cfg.config.LOG_DIR ] ++ (map (value: value.OUTPUT_DIR) cfg.config.WATCHFOLDERS) ++ (map (value: value.FULLPATH) cfg.config.WATCHFOLDERS); in { options.my.hackyplayer = { enable = lib.mkEnableOption "hackyplayer"; hostname = lib.mkOption { type = lib.types.str; }; listenAddresses = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); default = null; }; config = lib.mkOption { type = lib.types.anything; default = let paths = { input = "/store/emf/2024/video/input"; source = "/store/emf/2024/video/source"; output = "/store/emf/2024/video/output"; temp = "/store/emf/2024/video/tmp"; logs = "/store/emf/2024/video/logs"; }; in { VIDEO_SOURCES = [{ DISKDIR = paths.source; WEBDIR = "source"; EXT = [".mp4"]; NAME = "Source"; } { DISKDIR = paths.output; WEBDIR = "output"; EXT = [".mp4"]; NAME = "Output"; }]; VIDEO_OUTPUT = paths.output; VIDEO_TEMP = paths.temp; WATCHFOLDERS = [{ NAME = "input"; FULLPATH = paths.input; OUTPUT_DIR = paths.source; }]; LOG_DIR = paths.logs; }; }; caddyPrefixExtraHandles = lib.mkOption { type = lib.types.lines; default = ""; }; caddyPrefixExtraConfig = lib.mkOption { type = lib.types.lines; default = ""; }; gunicornConcurrency = lib.mkOption { type = lib.types.int; default = 4; }; workerConcurrency = lib.mkOption { type = lib.types.int; default = 4; }; vouch = { enable = lib.mkEnableOption "hackyplayer-vouch"; config = lib.mkOption { type = lib.types.submodule { freeformType = yamlFormat.type; }; default = {}; }; }; }; config = lib.mkMerge [ (lib.mkIf cfg.enable { users.users.hackyplayer = { isSystemUser = true; group = "hackyplayer"; }; users.groups.hackyplayer = {}; systemd.services.hackyplayer = { serviceConfig = { User = "hackyplayer"; ProtectSystem = "strict"; ProtectHome = "read-only"; PrivateTmp = true; RemoveIPC = true; RuntimeDirectory = "hackyplayer"; ExecStart = "${hackyplayer}/bin/gunicorn -w ${toString cfg.gunicornConcurrency} --bind unix:/run/hackyplayer/hackyplayer.sock hackyplayer.app:app"; EnvironmentFile = config.my.vault.secrets.hackyplayer-environment.path; }; wantedBy = [ "hackyplayer.target" ]; environment = processedConfig; }; systemd.services.hackyplayer-celery = { serviceConfig = { User = "hackyplayer"; ProtectSystem = "strict"; ProtectHome = "read-only"; PrivateTmp = true; RemoveIPC = true; RuntimeDirectory = "hackyplayer-celery"; ExecStart = "${hackyplayer}/bin/celery -A hackyplayer.tasks worker --concurrency ${toString cfg.workerConcurrency} --loglevel INFO --statedb /run/hackyplayer-celery/state.db"; ReadWritePaths = readWritePaths; EnvironmentFile = config.my.vault.secrets.hackyplayer-environment.path; }; wantedBy = [ "hackyplayer.target" ]; environment = processedConfig; }; systemd.targets.hackyplayer = { wantedBy = [ "multi-user.target" ]; }; my.vault.secrets.hackyplayer-environment = { reloadOrRestartUnits = ["hackyplayer.service"]; group = "root"; template = '' {{ with secret "kv/apps/hackyplayer" }} {{ .Data.data.environment }} {{ end }} ''; }; environment.systemPackages = let envPrelude = lib.concatStringsSep "\n" ( lib.mapAttrsToList (var: val: "export ${var}=${lib.escapeShellArg val}") processedConfig); in [ (pkgs.writeShellApplication { name = "hackyplayer-celery"; text = '' ${envPrelude} exec ${hackyplayer}/bin/celery -A hackyplayer.tasks "$@" ''; }) (pkgs.writeShellApplication { name = "hackyplayer-flask"; text = '' ${envPrelude} exec ${hackyplayer}/bin/flask -A hackyplayer.app:app "$@" ''; }) ]; services.caddy = { enable = true; virtualHosts."${cfg.hostname}" = { listenAddresses = lib.mkIf (cfg.listenAddresses != null) cfg.listenAddresses; extraConfig = '' ${cfg.caddyPrefixExtraHandles} handle { ${cfg.caddyPrefixExtraConfig} ${lib.concatMapStrings (src: '' route /static/video/${src.WEBDIR}/* { uri strip_prefix /static/video/${src.WEBDIR} root * ${src.DISKDIR} file_server } '') cfg.config.VIDEO_SOURCES} route /static/* { uri strip_prefix /static root * ${hackyplayer}/${hackyplayer.python.sitePackages}/hackyplayer/static file_server } reverse_proxy unix//run/hackyplayer/hackyplayer.sock } ''; }; }; systemd.services.caddy.serviceConfig.SupplementaryGroups = lib.mkAfter [ "hackyplayer" ]; services.redis.servers."".enable = true; my.hackyplayer.vouch.enable = lib.mkDefault true; my.hackyplayer.vouch.config = lib.mkDefault { vouch = { allowAllUsers = true; cookie.domain = "voc.emf.camp"; listen = "unix:/run/hacky-vouchproxy/sock"; socket_mode = "0770"; socket_group = "hackyplayer"; document_root = "/_vouch"; }; oauth = let base = "https://identity.emfcamp.org"; in { provider = "oidc"; auth_url = "${base}/oauth2/authorize"; token_url = "${base}/oauth2/token"; user_info_url = "${base}/oauth2/userinfo"; scopes = [ "profile" ]; callback_url = "https://hackyplayer.voc.emf.camp/auth"; }; }; }) (lib.mkIf cfg.vouch.enable { environment.etc."hacky-vouchproxy/config.yaml".source = yamlFormat.generate "hackyplayer-vouch-config.yaml" cfg.vouch.config; systemd.services.hacky-vouchproxy = { serviceConfig = { User = "hacky-vouchproxy"; SupplementaryGroups = [ "hackyplayer" ]; DynamicUser = true; RuntimeDirectory = "hacky-vouchproxy"; ExecStart = "${pkgs.vouch-proxy}/bin/vouch-proxy -config /etc/hacky-vouchproxy/config.yaml"; EnvironmentFile = config.my.vault.secrets.hacky-vouchproxy-environment.path; }; wantedBy = [ "multi-user.target" ]; }; my.vault.secrets.hacky-vouchproxy-environment = { reloadOrRestartUnits = ["hacky-vouchproxy.service"]; group = "root"; template = '' {{ with secret "kv/apps/hacky-vouchproxy" }} {{ .Data.data.environment }} {{ end }} ''; }; my.hackyplayer.caddyPrefixExtraHandles = lib.mkBefore '' handle /auth { reverse_proxy unix//run/hacky-vouchproxy/sock { rewrite /_vouch/auth } } handle /_vouch/* { reverse_proxy unix//run/hacky-vouchproxy/sock } ''; my.hackyplayer.caddyPrefixExtraConfig = lib.mkBefore '' reverse_proxy unix//run/hacky-vouchproxy/sock { method GET rewrite /_vouch/validate header_up Host {host} @good status 2xx handle_response @good { request_header X-Vouch-User {rp.header.X-Vouch-User} } @bad status 4xx handle_response @bad { header Location /_vouch/login?url=https://{host}{uri}&vouch-failcount={rp.header.X-Vouch-Failcount}&X-Vouch-Token={rp.header.X-Vouch-JWT}&error={rp.header.X-Vouch-Err} respond 303 { close } } } ''; }) ]; }