66 lines
2.8 KiB
Nix
66 lines
2.8 KiB
Nix
|
|
# services/owncast.nix — Owncast relay for synced watch parties.
|
||
|
|
#
|
||
|
|
# Why relay instead of everyone just opening Twitch: embed-sync tools only sync
|
||
|
|
# play/pause/seek commands, not buffers. Each viewer's Twitch player picks its
|
||
|
|
# own live-edge offset, so a group drifts 5-15s apart and no amount of "seek to
|
||
|
|
# now" fixes it. One shared HLS playlist removes the drift by construction —
|
||
|
|
# everybody pulls the same segment numbers — at the cost of ~15-25s of extra
|
||
|
|
# absolute latency. That tradeoff is the whole point; without it this is just
|
||
|
|
# watching on the website.
|
||
|
|
#
|
||
|
|
# Private by design: Owncast has no viewer auth of its own (upstream #489/#4344
|
||
|
|
# are still open), so nginx does it with a shared password in services/nginx.nix.
|
||
|
|
{ config, lib, pkgs, ... }:
|
||
|
|
let
|
||
|
|
port = 8088;
|
||
|
|
|
||
|
|
# Party password, also the Owncast admin password and RTMP stream key. Not a
|
||
|
|
# secret in the /var/secrets sense — it gets read out loud in Discord and it
|
||
|
|
# only guards a living-room stream — so plain text in the store is fine here.
|
||
|
|
# Anything that actually matters still goes through Authelia or runtime
|
||
|
|
# injection.
|
||
|
|
password = "Blizzcon";
|
||
|
|
in
|
||
|
|
{
|
||
|
|
config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") {
|
||
|
|
|
||
|
|
services.owncast = {
|
||
|
|
enable = true;
|
||
|
|
inherit port;
|
||
|
|
listen = "127.0.0.1"; # nginx fronts it; RTMP is localhost-only too
|
||
|
|
};
|
||
|
|
|
||
|
|
# The module hardcodes its ExecStart with no escape hatch, so restate it.
|
||
|
|
# Without -adminpassword the admin panel sits on the documented default
|
||
|
|
# abc123, behind nothing but the same password the guests were given.
|
||
|
|
systemd.services.owncast.serviceConfig.ExecStart = lib.mkForce (
|
||
|
|
lib.concatStringsSep " " [
|
||
|
|
"${pkgs.owncast}/bin/owncast"
|
||
|
|
"-webserverport ${toString port}"
|
||
|
|
"-rtmpport 1935"
|
||
|
|
"-webserverip 127.0.0.1"
|
||
|
|
"-adminpassword ${password}"
|
||
|
|
"-streamkey ${password}"
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
# relay <url> [quality] — pull a live stream and push it into Owncast.
|
||
|
|
# Run it when the stream starts, Ctrl-C when it ends. Deliberately not a
|
||
|
|
# unit: there's nothing to supervise, the channel changes every time, and
|
||
|
|
# a systemd service would need an EnvironmentFile edit + restart per event.
|
||
|
|
environment.systemPackages = [
|
||
|
|
(pkgs.writeShellScriptBin "relay" ''
|
||
|
|
set -euo pipefail
|
||
|
|
url=''${1:?usage: relay <twitch or youtube url> [quality]}
|
||
|
|
# streamlink drops Twitch pre/mid-roll ads on its own (mandatory since
|
||
|
|
# 7.5.0), but that leaves a timestamp discontinuity where the ad was.
|
||
|
|
# -fflags +genpts rebuilds PTS across it so the copy doesn't stall on
|
||
|
|
# non-monotonic DTS.
|
||
|
|
${pkgs.streamlink}/bin/streamlink --stdout "$url" "''${2:-best}" \
|
||
|
|
| ${pkgs.ffmpeg}/bin/ffmpeg -hide_banner -fflags +genpts -i pipe:0 \
|
||
|
|
-c copy -f flv rtmp://127.0.0.1:1935/live/${password}
|
||
|
|
'')
|
||
|
|
];
|
||
|
|
};
|
||
|
|
}
|