270 lines
8.5 KiB
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
|
|
}
|
|
}
|
|
}
|
|
'';
|
|
})
|
|
];
|
|
}
|