ec-node: add relay CAS archiver and nix auto-archive service
This commit is contained in:
parent
f70d4a02fd
commit
656ec11c73
4 changed files with 823 additions and 6 deletions
|
|
@ -191,6 +191,61 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
archive = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Run relay archival workers that subscribe and persist streams into CAS storage.";
|
||||
};
|
||||
|
||||
directoryUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://every.channel";
|
||||
description = "Directory base URL used to discover public streams (`/api/public-streams`).";
|
||||
};
|
||||
|
||||
outputDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel/archive";
|
||||
description = "CAS object root passed to `ec-node wt-archive --output-dir`.";
|
||||
};
|
||||
|
||||
manifestDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel/manifests";
|
||||
description = "Manifest/index root passed to `ec-node wt-archive --manifest-dir`.";
|
||||
};
|
||||
|
||||
pollIntervalMs = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 15000;
|
||||
description = "Discovery poll interval for public streams.";
|
||||
};
|
||||
|
||||
streamPrefix = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional broadcast-name prefix filter for archival workers.";
|
||||
};
|
||||
|
||||
tracks = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [
|
||||
"catalog.json"
|
||||
"init.mp4"
|
||||
"video0.m4s"
|
||||
"audio0.m4s"
|
||||
];
|
||||
description = "Tracks passed to each `wt-archive` worker.";
|
||||
};
|
||||
|
||||
tlsDisableVerify = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Danger: disable TLS verification for relay archive subscribers.";
|
||||
};
|
||||
};
|
||||
|
||||
broadcasts = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
options = {
|
||||
|
|
@ -218,8 +273,8 @@ in
|
|||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.broadcasts != [ ];
|
||||
message = "services.every-channel.ec-node.broadcasts must be non-empty when enabled";
|
||||
assertion = (cfg.broadcasts != [ ]) || cfg.archive.enable;
|
||||
message = "services.every-channel.ec-node.broadcasts must be non-empty unless archive.enable=true";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
|
|
@ -239,9 +294,14 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /run/every-channel 1777 root root - -"
|
||||
];
|
||||
systemd.tmpfiles.rules =
|
||||
[
|
||||
"d /run/every-channel 1777 root root - -"
|
||||
]
|
||||
++ lib.optionals cfg.archive.enable [
|
||||
"d ${cfg.archive.outputDir} 0750 root root - -"
|
||||
"d ${cfg.archive.manifestDir} 0750 root root - -"
|
||||
];
|
||||
|
||||
systemd.services =
|
||||
lib.listToAttrs (map
|
||||
|
|
@ -571,6 +631,146 @@ in
|
|||
SystemCallArchitectures = "native";
|
||||
};
|
||||
|
||||
environment = cfg.environment;
|
||||
};
|
||||
})
|
||||
// lib.optionalAttrs cfg.archive.enable
|
||||
(let
|
||||
archiveUnit = "every-channel-wt-archive-auto";
|
||||
archivePrefix = if cfg.archive.streamPrefix == null then "" else cfg.archive.streamPrefix;
|
||||
archiveTrackLines = lib.concatMapStrings (track: " cmd+=(--track ${lib.escapeShellArg track})\n") cfg.archive.tracks;
|
||||
archiveRunner = pkgs.writeShellApplication {
|
||||
name = archiveUnit;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.curl
|
||||
pkgs.gawk
|
||||
pkgs.jq
|
||||
cfg.package
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
|
||||
directory_url=${lib.escapeShellArg (cfg.archive.directoryUrl + "/api/public-streams")}
|
||||
output_dir=${lib.escapeShellArg cfg.archive.outputDir}
|
||||
manifest_dir=${lib.escapeShellArg cfg.archive.manifestDir}
|
||||
relay_fallback=${lib.escapeShellArg cfg.relayUrl}
|
||||
stream_prefix=${lib.escapeShellArg archivePrefix}
|
||||
state_dir="/run/every-channel/archive"
|
||||
pids_dir="$state_dir/pids"
|
||||
logs_dir="$state_dir/logs"
|
||||
mkdir -p "$pids_dir" "$logs_dir"
|
||||
poll_secs="$(awk 'BEGIN { printf "%.3f", ${toString cfg.archive.pollIntervalMs} / 1000.0 }')"
|
||||
|
||||
cleanup_children() {
|
||||
for pid_file in "$pids_dir"/*.pid; do
|
||||
[[ -e "$pid_file" ]] || continue
|
||||
pid="$(cat "$pid_file" 2>/dev/null || true)"
|
||||
if [[ -n "$pid" ]]; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$pid_file"
|
||||
done
|
||||
}
|
||||
|
||||
trap cleanup_children INT TERM EXIT
|
||||
|
||||
while true; do
|
||||
entries_json="$(curl -fsS "$directory_url" || true)"
|
||||
if [[ -z "$entries_json" ]]; then
|
||||
sleep "$poll_secs"
|
||||
continue
|
||||
fi
|
||||
|
||||
while IFS= read -r entry; do
|
||||
name="$(printf '%s\n' "$entry" | jq -r '.broadcast_name // empty')"
|
||||
relay="$(printf '%s\n' "$entry" | jq -r '.relay_url // empty')"
|
||||
if [[ -z "$name" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ -n "$stream_prefix" && "$name" != "$stream_prefix"* ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ -z "$relay" ]]; then
|
||||
relay="$relay_fallback"
|
||||
fi
|
||||
|
||||
key="$(printf '%s' "$name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
pid_file="$pids_dir/$key.pid"
|
||||
if [[ -s "$pid_file" ]]; then
|
||||
pid="$(cat "$pid_file" 2>/dev/null || true)"
|
||||
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
cmd=(
|
||||
${lib.escapeShellArg "${cfg.package}/bin/ec-node"}
|
||||
wt-archive
|
||||
--url "$relay"
|
||||
--name "$name"
|
||||
--output-dir "$output_dir"
|
||||
--manifest-dir "$manifest_dir"
|
||||
)
|
||||
${lib.optionalString cfg.archive.tlsDisableVerify "cmd+=(--tls-disable-verify)"}
|
||||
${archiveTrackLines}
|
||||
|
||||
log_file="$logs_dir/$key.log"
|
||||
(
|
||||
exec "''${cmd[@]}"
|
||||
) >>"$log_file" 2>&1 &
|
||||
echo "$!" > "$pid_file"
|
||||
done < <(printf '%s\n' "$entries_json" | jq -rc '.entries[]?')
|
||||
|
||||
for pid_file in "$pids_dir"/*.pid; do
|
||||
[[ -e "$pid_file" ]] || continue
|
||||
pid="$(cat "$pid_file" 2>/dev/null || true)"
|
||||
if [[ -z "$pid" ]] || ! kill -0 "$pid" 2>/dev/null; then
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
done
|
||||
|
||||
sleep "$poll_secs"
|
||||
done
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
"${archiveUnit}" = {
|
||||
description = "every.channel relay archival auto-worker";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
unitConfig = {
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${archiveRunner}/bin/${archiveUnit}";
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
ReadWritePaths = [
|
||||
"/run/every-channel"
|
||||
cfg.archive.outputDir
|
||||
cfg.archive.manifestDir
|
||||
];
|
||||
};
|
||||
|
||||
environment = cfg.environment;
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue