This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

2configs/services/invidious: youtube frontend on neoprism (yt.lassul.us)

invidious plus invidious-companion, with every request to YouTube going
out through the residential exits on styx (primary) and coaxmetal
(backup). An nginx stream block fans 127.0.0.1:8118 into the two
tinyproxies; failover rather than round-robin, because YouTube binds
po_tokens and videoplayback URLs to the requesting address.

The companion key is generated once into a clan var and handed to
invidious as a systemd credential (the config script runs as the
DynamicUser, which cannot read /run/secrets) and to the container as an
EnvironmentFile.

default_user_preferences.local is forced on: upstream defaults it off,
which makes invidious hand the browser raw googlevideo URLs that are
minted for the exit's address, so they 403 for the client and leak the
exit IP. Playback paths additionally require a real session via
auth_request, since the bytes come off a residential line.

invidious is pinned one release ahead of our nixpkgs for the comments
parser fix; drop the override once the pin catches up.

Livestreams do not play: YouTube serves them SABR-only or with HLS
segments that 403 without a po_token, and invidious' HLS path bypasses
companion. Upstream gap, tracked in iv-org/invidious#3811 and
iv-org/invidious-companion#7.

+187 -1
+2 -1
2configs/dns/lassul.us.zone
··· 1 - @ 3600 IN SOA lassul.us. ns1.lassul.us. 2026072100 7200 3600 86400 3600 1 + @ 3600 IN SOA lassul.us. ns1.lassul.us. 2026080500 7200 3600 86400 3600 2 2 3 3 ;;@ 3600 IN NS ns1 4 4 @ 3600 IN NS ns1 ··· 57 57 n2n IN CNAME neoprism 58 58 id IN CNAME neoprism 59 59 covibe IN CNAME neoprism 60 + yt IN CNAME neoprism 60 61 61 62 ;; Mail 62 63 @ IN MX 3 mail
+182
2configs/services/invidious.nix
··· 1 + { config, pkgs, ... }: 2 + 3 + let 4 + domain = "yt.lassul.us"; 5 + 6 + # local entry point of the residential exit pool (see 2configs/residential-exit.nix) 7 + proxyPort = 8118; 8 + companionPort = 8282; 9 + 10 + exit = host: config.krebs.hosts.${host}.nets.retiolum.ip4.addr; 11 + in 12 + { 13 + # invidious talks to companion over this key; it must not end up in the 14 + # world readable nix store, so it is generated once and handed to invidious 15 + # as a systemd credential and to companion as an EnvironmentFile. 16 + clan.core.vars.generators.invidious = { 17 + files."companion.json" = { }; 18 + files."companion.env" = { }; 19 + runtimeInputs = [ 20 + pkgs.pwgen 21 + pkgs.jq 22 + ]; 23 + script = '' 24 + key=$(pwgen -s 16 1) 25 + jq -n --arg key "$key" '{ invidious_companion_key: $key }' > "$out"/companion.json 26 + echo "SERVER_SECRET_KEY=$key" > "$out"/companion.env 27 + ''; 28 + }; 29 + 30 + services.invidious = { 31 + enable = true; 32 + inherit domain; 33 + nginx.enable = true; 34 + 35 + # Our nixpkgs pin is two invidious releases behind. 2.20260804.1 carries 36 + # the comments parser fixes (#5862, #5870): YouTube started emitting a 37 + # commentFilterContextViewModel node, which the packaged version feeds 38 + # straight into a Hash lookup -> "Expected Hash for #[]?(key : String), 39 + # not String" and a 500 on every comment request. Overriding just 40 + # versions.json is safe here: shard.lock is untouched between the two 41 + # tags, so the packaged shards.nix still applies, and the videojs hash is 42 + # unchanged. Drop this once the nixpkgs pin catches up (master has it). 43 + package = pkgs.invidious.override { 44 + versions = { 45 + invidious = { 46 + version = "2.20260804.1"; 47 + date = "2026.08.04"; 48 + commit = "48c6110a"; 49 + hash = "sha256-YO+aJXphVfplAOjLtpTcWcqMcWgv4SHgoWNWhPOnz2M="; 50 + }; 51 + videojs.hash = "sha256-jED3zsDkPN8i6GhBBJwnsHujbuwlHdsVpVqa1/pzSH4="; 52 + }; 53 + }; 54 + # credentials land at a fixed path, and the config script reads the file 55 + # as the (dynamic) invidious user, which plain vars files are not readable by 56 + extraSettingsFile = "/run/credentials/invidious.service/companion.json"; 57 + settings = { 58 + # single user instance; accounts exist only for subscriptions/prefs. 59 + # The admin panel can flip this at runtime, but the unit is restarted 60 + # hourly (RuntimeMaxSec), so the config value always wins. 61 + registration_enabled = false; 62 + admins = [ "lassulus" ]; 63 + 64 + # metadata, search and thumbnails leave through the residential pool too: 65 + # a datacenter IP gets rate limited on the innertube API within minutes 66 + http_proxy = { 67 + host = "127.0.0.1"; 68 + port = proxyPort; 69 + user = ""; 70 + password = ""; 71 + }; 72 + 73 + # "Proxy videos" must be on by default. Upstream defaults it off, which 74 + # makes invidious hand the browser raw googlevideo/youtube.com URLs on 75 + # the HLS, listen and download paths. Those URLs are minted for styx's 76 + # address (they carry ip=<exit>), so the client gets 403 -- and they 77 + # leak the exit IP. Everything has to come back through us. 78 + default_user_preferences.local = true; 79 + # public_url omitted on purpose: invidious then proxies /companion itself 80 + # instead of us maintaining a second nginx route 81 + invidious_companion = [ 82 + { private_url = "http://127.0.0.1:${toString companionPort}/companion"; } 83 + ]; 84 + }; 85 + }; 86 + 87 + systemd.services.invidious = { 88 + after = [ "podman-invidious-companion.service" ]; 89 + wants = [ "podman-invidious-companion.service" ]; 90 + serviceConfig.LoadCredential = [ 91 + "companion.json:${config.clan.core.vars.generators.invidious.files."companion.json".path}" 92 + ]; 93 + }; 94 + 95 + # No anonymous playback: every stream byte comes off a residential line, so 96 + # the paths that move video require a real invidious session. auth_request 97 + # subrequests invidious' own authenticated API, which resolves the SID 98 + # cookie against the session_ids table -- a forged cookie gets 403 there, so 99 + # this is genuine authentication rather than a cookie-presence check. 100 + # Browsing/search stay open so the login page and UI still work. 101 + services.nginx.virtualHosts.${domain}.locations = 102 + let 103 + upstream = "http://127.0.0.1:${toString config.services.invidious.port}"; 104 + gated = { 105 + proxyPass = upstream; 106 + extraConfig = '' 107 + auth_request /_session_check; 108 + proxy_buffering off; 109 + ''; 110 + }; 111 + in 112 + { 113 + "= /_session_check".extraConfig = '' 114 + internal; 115 + proxy_pass ${upstream}/api/v1/auth/preferences; 116 + proxy_pass_request_body off; 117 + proxy_set_header Content-Length ""; 118 + ''; 119 + 120 + # companion serves all VOD audio/video; the rest carry HLS and the 121 + # non-companion fallbacks 122 + "/companion/" = gated; 123 + "/videoplayback" = gated; 124 + "/latest_version" = gated; 125 + "/api/manifest/" = gated; 126 + }; 127 + 128 + # video streams are fetched by companion, not by invidious, so the proxy has 129 + # to be configured here as well or playback comes from the Hetzner address 130 + virtualisation.oci-containers = { 131 + backend = "podman"; 132 + containers.invidious-companion = { 133 + image = "quay.io/invidious/invidious-companion:latest"; 134 + environment = { 135 + HOST = "127.0.0.1"; 136 + PORT = toString companionPort; 137 + PROXY = "http://127.0.0.1:${toString proxyPort}"; 138 + }; 139 + environmentFiles = [ 140 + config.clan.core.vars.generators.invidious.files."companion.env".path 141 + ]; 142 + volumes = [ "invidious-companion-cache:/var/tmp/youtubei.js" ]; 143 + extraOptions = [ 144 + # host netns so 127.0.0.1 means the same thing for both the listen 145 + # address and the proxy pool 146 + "--network=host" 147 + "--cap-drop=ALL" 148 + "--read-only" 149 + "--security-opt=no-new-privileges:true" 150 + ]; 151 + }; 152 + }; 153 + 154 + # upstream only publishes a rolling tag, so pull it on a schedule 155 + systemd.services.invidious-companion-update = { 156 + startAt = "daily"; 157 + script = '' 158 + ${pkgs.podman}/bin/podman pull ${config.virtualisation.oci-containers.containers.invidious-companion.image} 159 + systemctl restart podman-invidious-companion.service 160 + ${pkgs.podman}/bin/podman image prune -f 161 + ''; 162 + }; 163 + 164 + # TCP load balancer in front of the residential tinyproxies. styx is the 165 + # permanently-on box and takes all traffic; coaxmetal is a laptop and only 166 + # takes over while styx is unreachable. Deliberately not round-robin: 167 + # YouTube binds po_tokens and videoplayback URLs to the requesting address, 168 + # so alternating exits mid-session breaks playback. 169 + services.nginx.streamConfig = '' 170 + upstream residential_exits { 171 + server ${exit "styx"}:${toString proxyPort} max_fails=2 fail_timeout=60s; 172 + server ${exit "coaxmetal"}:${toString proxyPort} backup; 173 + } 174 + 175 + server { 176 + listen 127.0.0.1:${toString proxyPort}; 177 + proxy_pass residential_exits; 178 + proxy_connect_timeout 10s; 179 + proxy_timeout 1h; 180 + } 181 + ''; 182 + }
+3
machines/neoprism/config.nix
··· 110 110 111 111 # self-hosted iroh relay (relay.lassul.us) 112 112 ../../2configs/iroh-relay 113 + 114 + # invidious youtube frontend (yt.lassul.us), exiting via styx/coaxmetal 115 + ../../2configs/services/invidious.nix 113 116 ]; 114 117 115 118 # lassul.us shouldn't be the default vhost here (nginx.nix already sets one)