Files
nixos/hosts/fw/modules/web/invidious.nix
2025-11-12 22:48:48 +01:00

232 lines
7.1 KiB
Nix

{ config, pkgs, lib, ... }:
with lib;
{
# Invidious - Privacy-focused YouTube frontend
# Replaces Piped with native NixOS service
# Secret for Invidious companion authentication
sops.secrets.invidious-companion-key = {
key = "invidious-companion-key";
};
# Main Invidious service
services.invidious = {
enable = true;
domain = "invidious.cloonar.com";
port = 3000;
# PostgreSQL database configuration
database = {
createLocally = true;
};
# Enable nginx reverse proxy with automatic TLS
nginx.enable = true;
# Enable http3-ytproxy for video/image proxying
# Handles /videoplayback, /vi/, /ggpht/, /sb/ paths
http3-ytproxy.enable = true;
# Signature helper - crashes with current YouTube player format
# sig-helper = {
# enable = true;
# };
# Service settings
settings = {
# Disable registration - admin user created via init script
registration_enabled = false;
# Disable CAPTCHA (not needed for private instance)
captcha_enabled = false;
# Database configuration
check_tables = true;
db = {
user = "invidious";
dbname = "invidious";
};
# Optional: Instance customization
default_home = "Popular";
feed_menu = [ "Popular" "Trending" "Subscriptions" ];
# HTTPS configuration for proper URL generation
external_port = mkForce 443;
https_only = mkForce true;
# YouTube compatibility settings
use_quic = true;
force_resolve = "ipv4";
};
};
# Use Podman for OCI containers
virtualisation.oci-containers.backend = "podman";
# Create Invidious network for container communication
systemd.services.init-invidious-network = {
description = "Create Podman network for Invidious companion";
wantedBy = [ "multi-user.target" ];
before = [ "podman-invidious-companion.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
${pkgs.podman}/bin/podman network exists invidious-net || \
${pkgs.podman}/bin/podman network create --interface-name=podman2 --subnet=10.90.0.0/24 invidious-net
'';
};
# Create systemd tmpfiles directory for Invidious config
systemd.tmpfiles.rules = [
"d /var/lib/invidious 0755 root root - -"
"d /run/invidious-companion 0700 root root - -"
];
# Generate companion environment file with secret key
systemd.services.invidious-companion-env-generate = {
description = "Generate Invidious companion environment file";
wantedBy = [ "multi-user.target" ];
before = [ "podman-invidious-companion.service" ];
after = [ "init-invidious-network.service" ];
requires = [ "init-invidious-network.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
COMPANION_KEY=$(cat ${config.sops.secrets.invidious-companion-key.path})
cat > /run/invidious-companion/env <<EOF
PORT=8282
HOST=0.0.0.0
SERVER_SECRET_KEY=$COMPANION_KEY
EOF
chmod 600 /run/invidious-companion/env
'';
};
# Invidious Companion container (handles PO token generation and video streams)
virtualisation.oci-containers.containers.invidious-companion = {
image = "quay.io/invidious/invidious-companion:latest";
ports = [ "127.0.0.1:8282:8282" ];
volumes = [
"invidious-companion-cache:/var/tmp:rw"
];
environmentFiles = [
"/run/invidious-companion/env"
];
extraOptions = [
"--pull=newer"
"--network=invidious-net"
"--cap-drop=ALL"
"--security-opt=no-new-privileges:true"
"--read-only"
];
};
# Ensure companion container depends on env file generation
systemd.services."podman-invidious-companion" = {
after = mkAfter [ "invidious-companion-env-generate.service" ];
requires = mkAfter [ "invidious-companion-env-generate.service" ];
};
# Generate Invidious companion config with actual secret key
systemd.services.invidious-companion-config-generate = {
description = "Generate Invidious companion configuration";
wantedBy = [ "multi-user.target" ];
before = [ "invidious.service" ];
after = [ "init-invidious-network.service" ];
requires = [ "init-invidious-network.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
mkdir -p /var/lib/invidious
COMPANION_KEY=$(cat ${config.sops.secrets.invidious-companion-key.path})
cat > /var/lib/invidious/companion-config.json <<EOF
{
"invidious_companion": [
{
"private_url": "http://127.0.0.1:8282/companion"
}
],
"invidious_companion_key": "$COMPANION_KEY"
}
EOF
chmod 644 /var/lib/invidious/companion-config.json
chown root:root /var/lib/invidious/companion-config.json
'';
};
# Configure Invidious to use companion via extraSettingsFile
services.invidious.extraSettingsFile = "/var/lib/invidious/companion-config.json";
# Ensure Invidious service depends on companion config generation
systemd.services.invidious = {
after = mkAfter [ "invidious-companion-config-generate.service" ];
requires = mkAfter [ "invidious-companion-config-generate.service" ];
};
# Override nginx vhost configuration
services.nginx.virtualHosts."invidious.cloonar.com" = {
acmeRoot = null;
# Complete http3-ytproxy configuration with proper headers and buffering
# This overrides the minimal config from the NixOS module
locations."~ (^/videoplayback|^/vi/|^/ggpht/|^/sb/)" = {
proxyPass = "http://unix:/run/http3-ytproxy/socket/http-proxy.sock";
extraConfig = ''
# Enable buffering for large video files
proxy_buffering on;
proxy_buffers 1024 16k;
proxy_buffer_size 128k;
proxy_busy_buffers_size 256k;
# Use HTTP/1.1 with keepalive for better performance
proxy_http_version 1.1;
proxy_set_header Connection "";
# Hide headers that might cause issues
proxy_hide_header Cache-Control;
proxy_hide_header etag;
proxy_hide_header "alt-svc";
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Expose-Headers;
# CORS headers for iOS clients like Yattee
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range, Content-Type" always;
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
# Handle preflight requests
if ($request_method = OPTIONS) {
return 204;
}
# Optimize for large file transfers
sendfile on;
sendfile_max_chunk 512k;
tcp_nopush on;
# Disable access logging for video traffic
access_log off;
'';
};
};
# Firewall configuration for Invidious
# (nginx handles external access on ports 80/443)
# PostgreSQL backup for Invidious database
services.postgresqlBackup = {
databases = [ "invidious" ];
};
}