depot/ops/nixos/lib/hackyplayer.nix

270 lines
8.5 KiB
Nix

# SPDX-FileCopyrightText: 2024 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, pkgs, lib, config, ... }:
let
inherit (depot.third_party) hackyplayer;
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}
route {
${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
}
}
}
'';
})
];
}