apu local dhcp lease dns and invidious
This commit is contained in:
@@ -424,7 +424,6 @@
|
||||
};
|
||||
};
|
||||
"apu" = {
|
||||
hostname = "apu.tempel-vibes.ts.net";
|
||||
user = "root";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -258,61 +258,25 @@
|
||||
openFirewall = true;
|
||||
};
|
||||
|
||||
services.home-assistant = {
|
||||
services.invidious-companion = {
|
||||
enable = true;
|
||||
extraComponents = [
|
||||
# Components required to complete the onboarding
|
||||
"esphome"
|
||||
"met"
|
||||
"radio_browser"
|
||||
|
||||
"yeelight"
|
||||
"xiaomi_aqara"
|
||||
"shelly"
|
||||
];
|
||||
subdomain = "home";
|
||||
extraPackages =
|
||||
python3Packages: with python3Packages; [
|
||||
gtts
|
||||
numpy
|
||||
];
|
||||
config = {
|
||||
homeassistant = {
|
||||
name = "Koti";
|
||||
unit_system = "metric";
|
||||
time_zone = "Europe/Helsinki";
|
||||
};
|
||||
http = {
|
||||
use_x_forwarded_for = true;
|
||||
trusted_proxies = "127.0.0.1";
|
||||
};
|
||||
default_config = { };
|
||||
};
|
||||
host = "0.0.0.0";
|
||||
port = 8282;
|
||||
secretKeyFile = config.age.secrets.invidious-companion.path;
|
||||
binaryHash = "sha256-nZXKpExKCc2zgSdVT3qo05NyFdpM9H9NJB5UWo+MVWI=";
|
||||
};
|
||||
|
||||
services = {
|
||||
webserver = {
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
acme.dnsChallenge = true;
|
||||
vHosts."koti.repomaa.com" = {
|
||||
proxyBuffering = false;
|
||||
locations."/".proxyPort = 8123;
|
||||
};
|
||||
};
|
||||
|
||||
invidious = {
|
||||
enable = true;
|
||||
subdomain = "vid";
|
||||
};
|
||||
interfaces.tailscale0.allowedTCPPorts = [ 8282 ];
|
||||
};
|
||||
|
||||
security.acme.defaults.environmentFile = config.age.secrets.hetzner.path;
|
||||
|
||||
networking = {
|
||||
nftables.enable = true;
|
||||
firewall.enable = true;
|
||||
useDHCP = false;
|
||||
domain = "repomaa.com";
|
||||
domain = "apu.home.arpa";
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
})
|
||||
[
|
||||
"hetzner"
|
||||
"invidious-companion"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,5 +32,6 @@
|
||||
./voidauth.nix
|
||||
./gitea.nix
|
||||
./dhcp-dns-sync
|
||||
./invidious-companion.nix
|
||||
];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
}:
|
||||
let
|
||||
cfg = config.modules.services.dhcp-dns-sync;
|
||||
ownAddress = (
|
||||
lib.elemAt (lib.splitString "/"
|
||||
config.systemd.network.networks."30-${cfg.interface}".networkConfig.Address
|
||||
) 0
|
||||
);
|
||||
|
||||
dhcp-leases-to-unbound =
|
||||
pkgs.runCommand "dhcp-leases-to-unbound"
|
||||
@@ -59,9 +64,10 @@ in
|
||||
users.groups.dhcp-dns-sync = { };
|
||||
|
||||
# Ensure directories and files exist with proper permissions
|
||||
# Directory needs to be group-writable for unbound group
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/unbound 0755 unbound unbound -"
|
||||
"f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync dhcp-dns-sync -"
|
||||
"d /var/lib/unbound 0775 unbound unbound -"
|
||||
"f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync unbound -"
|
||||
];
|
||||
|
||||
# Extend Unbound configuration to include generated file
|
||||
@@ -69,6 +75,8 @@ in
|
||||
server = {
|
||||
local-zone = [ "${cfg.domain}. static" ];
|
||||
include = cfg.unboundConfigPath;
|
||||
local-data = [ ''"apu.home.arpa. IN A ${ownAddress}"'' ];
|
||||
local-data-ptr = [ ''"${ownAddress} apu.home.arpa."'' ];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -88,7 +96,7 @@ in
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "dhcp-dns-sync";
|
||||
Group = "dhcp-dns-sync";
|
||||
Group = "unbound";
|
||||
# Allow access to networkctl via D-Bus
|
||||
SupplementaryGroups = [ "systemd-network" ];
|
||||
# Read/write paths
|
||||
|
||||
@@ -58,13 +58,6 @@ def sanitize_hostname(hostname : String) : String?
|
||||
sanitized
|
||||
end
|
||||
|
||||
def reverse_ptr(ip : String) : String?
|
||||
parts = ip.split('.')
|
||||
return nil unless parts.size == 4
|
||||
|
||||
"#{parts[3]}.#{parts[2]}.#{parts[1]}.#{parts[0]}.in-addr.arpa."
|
||||
end
|
||||
|
||||
def generate_unbound_config(leases : Array(Lease), domain : String) : String
|
||||
lines = [] of String
|
||||
|
||||
@@ -82,23 +75,30 @@ def generate_unbound_config(leases : Array(Lease), domain : String) : String
|
||||
# A record
|
||||
lines << %{local-data: "#{fqdn} IN A #{lease.address}"}
|
||||
|
||||
# PTR record
|
||||
if ptr = reverse_ptr(lease.address)
|
||||
lines << %{local-data-ptr: "#{ptr} #{fqdn}"}
|
||||
end
|
||||
# PTR record - local-data-ptr expects IP in normal form, unbound reverses it
|
||||
lines << %{local-data-ptr: "#{lease.address} #{fqdn}"}
|
||||
end
|
||||
|
||||
lines.join("\n") + "\n"
|
||||
end
|
||||
|
||||
def get_leases(interface : String, networkctl_path : String? = nil) : Array(Lease)
|
||||
cmd = networkctl_path ? "#{networkctl_path} status #{interface} --json=short" : "networkctl status #{interface} --json=short"
|
||||
output = `#{cmd}`
|
||||
raise "networkctl failed (exit code #{$?.exit_status}): #{output}" unless $?.success?
|
||||
cmd = networkctl_path ? "#{networkctl_path}" : "networkctl"
|
||||
args = ["status", interface, "--json=short"]
|
||||
|
||||
Process.run(cmd, args, output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
|
||||
result = process.wait
|
||||
output = process.output.to_s
|
||||
|
||||
unless result.success?
|
||||
error = process.error.to_s
|
||||
raise "networkctl failed (exit code #{result.exit_code}): #{error.empty? ? output : error}"
|
||||
end
|
||||
|
||||
status = NetworkStatus.from_json(output)
|
||||
status.dhcp_server.try(&.leases) || [] of Lease
|
||||
end
|
||||
end
|
||||
|
||||
def write_if_changed(content : String, path : String) : Bool
|
||||
# Check if content is the same
|
||||
@@ -151,15 +151,20 @@ OptionParser.parse do |parser|
|
||||
end
|
||||
|
||||
def reload_unbound(unbound_control_path : String?)
|
||||
cmd = unbound_control_path ? "#{unbound_control_path} reload" : "unbound-control reload"
|
||||
cmd = unbound_control_path ? "#{unbound_control_path}" : "unbound-control"
|
||||
puts "Reloading Unbound..."
|
||||
result = system(cmd)
|
||||
unless result
|
||||
# Fallback to systemctl
|
||||
system("systemctl reload unbound")
|
||||
|
||||
Process.run(cmd, ["reload"], output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
|
||||
result = process.wait
|
||||
|
||||
unless result.success?
|
||||
raise "unbound reload failed (exit code #{result.exit_code}): #{process.error}"
|
||||
end
|
||||
end
|
||||
|
||||
puts "Unbound reloaded successfully."
|
||||
end
|
||||
|
||||
begin
|
||||
# Get leases from networkd
|
||||
leases = get_leases(interface, networkctl_path)
|
||||
|
||||
99
modules/services/invidious-companion.nix
Normal file
99
modules/services/invidious-companion.nix
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.invidious-companion;
|
||||
companionRelease = "release-master";
|
||||
hostPlatform = pkgs.stdenv.hostPlatform.system;
|
||||
|
||||
# Invidious Companion package - fetches binary release and patches for NixOS
|
||||
unwrappedCompanion = pkgs.stdenv.mkDerivation {
|
||||
pname = "unwrapped-invidious-companion";
|
||||
version = companionRelease;
|
||||
|
||||
src =
|
||||
let
|
||||
archMap = {
|
||||
x86_64-linux = "x86_64-unknown-linux-gnu";
|
||||
aarch64-linux = "aarch64-unknown-linux-gnu";
|
||||
};
|
||||
platform = archMap.${hostPlatform} or (throw "Unsupported platform: ${hostPlatform}");
|
||||
in
|
||||
pkgs.fetchzip {
|
||||
url = "https://github.com/iv-org/invidious-companion/releases/download/${companionRelease}/invidious_companion-${platform}.tar.gz";
|
||||
sha256 = cfg.binaryHash;
|
||||
};
|
||||
|
||||
dontStrip = true;
|
||||
dontPatchELF = true;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp invidious_companion $out/bin/invidious_companion
|
||||
chmod +x $out/bin/invidious_companion
|
||||
'';
|
||||
};
|
||||
|
||||
invidiousCompanion = pkgs.buildFHSEnv {
|
||||
name = "invidious-companion";
|
||||
targetPkgs = pkgs: [ unwrappedCompanion ];
|
||||
runScript = "invidious_companion";
|
||||
meta = {
|
||||
description = "Invidious companion for handling video streams";
|
||||
homepage = "https://github.com/iv-org/invidious-companion";
|
||||
license = lib.licenses.agpl3Only;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.invidious-companion = {
|
||||
enable = lib.mkEnableOption "Enable Invidious Companion service";
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "localhost";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8282;
|
||||
description = "Port for Invidious Companion to listen on";
|
||||
};
|
||||
secretKeyFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Path to file containing the companion secret key";
|
||||
};
|
||||
binaryHash = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "SHA256 hash of the invidious companion binary release";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.invidious-companion = {
|
||||
description = "Invidious Companion - video stream handler";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "invidious";
|
||||
Group = "invidious";
|
||||
DynamicUser = true;
|
||||
ExecStart = lib.getExe invidiousCompanion;
|
||||
Environment = [
|
||||
"HOST=${cfg.host}"
|
||||
"PORT=${toString cfg.port}"
|
||||
"TMPDIR=/var/cache/invidious-companion"
|
||||
];
|
||||
EnvironmentFile = [ cfg.secretKeyFile ];
|
||||
CacheDirectory = "invidious-companion";
|
||||
WorkingDirectory = "%C/invidious-companion";
|
||||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.invidious;
|
||||
fqdn = "${cfg.subdomain}.${config.networking.domain}";
|
||||
@@ -32,5 +36,14 @@ in
|
||||
vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.invidious.serviceConfig.DynamicUser = lib.mkForce false;
|
||||
|
||||
users.groups.invidious = { };
|
||||
users.users.invidious = {
|
||||
isSystemUser = true;
|
||||
group = "invidious";
|
||||
description = "Invidious user";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
12
secrets/invidious-companion.age
Normal file
12
secrets/invidious-companion.age
Normal file
@@ -0,0 +1,12 @@
|
||||
age-encryption.org/v1
|
||||
-> ssh-ed25519 osOCZA 9wZ3G4vjwJhYungj/utZ/jgnQRD7qGHsMXM51gNFLyY
|
||||
SvdeK7R1AxveXXFJng21JK1fy+y7lh6OINB4CtUdS1Q
|
||||
-> ssh-ed25519 DFiohQ 1NIsoZWR4fY+bcROkw7iq+X0cYIE9g5IiWOqO0FvymQ
|
||||
igfAuxzfUSlhE3jaTMjqCYeF8ccKVyuUW+uD8JdH75c
|
||||
-> ssh-ed25519 wU682A g5y8TFpeJ0myejb8r7gL96JBk/q21KlDOBE6ZpCqv2A
|
||||
I/3aFKq2ne3gVeg+/1LmlKoDyg723yyjUdVdzgFzhV4
|
||||
--- JsRdNjJ285V+RGktIxJv29Alef95kpB2TOnYH66Wr4Q
|
||||
z¿
|
||||
n
|
||||
–´²xÇ‘
|
||||
Û¸"‹KA›x)ñÑ8 é…
|
||||
BIN
secrets/invidious.age
Normal file
BIN
secrets/invidious.age
Normal file
Binary file not shown.
Reference in New Issue
Block a user