apu local dhcp lease dns and invidious
This commit is contained in:
@@ -424,7 +424,6 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
"apu" = {
|
"apu" = {
|
||||||
hostname = "apu.tempel-vibes.ts.net";
|
|
||||||
user = "root";
|
user = "root";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -258,61 +258,25 @@
|
|||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
services.home-assistant = {
|
services.invidious-companion = {
|
||||||
enable = true;
|
enable = true;
|
||||||
extraComponents = [
|
host = "0.0.0.0";
|
||||||
# Components required to complete the onboarding
|
port = 8282;
|
||||||
"esphome"
|
secretKeyFile = config.age.secrets.invidious-companion.path;
|
||||||
"met"
|
binaryHash = "sha256-nZXKpExKCc2zgSdVT3qo05NyFdpM9H9NJB5UWo+MVWI=";
|
||||||
"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 = { };
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services = {
|
networking.firewall = {
|
||||||
webserver = {
|
enable = true;
|
||||||
enable = true;
|
interfaces.tailscale0.allowedTCPPorts = [ 8282 ];
|
||||||
acme.dnsChallenge = true;
|
|
||||||
vHosts."koti.repomaa.com" = {
|
|
||||||
proxyBuffering = false;
|
|
||||||
locations."/".proxyPort = 8123;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
invidious = {
|
|
||||||
enable = true;
|
|
||||||
subdomain = "vid";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
security.acme.defaults.environmentFile = config.age.secrets.hetzner.path;
|
security.acme.defaults.environmentFile = config.age.secrets.hetzner.path;
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
nftables.enable = true;
|
nftables.enable = true;
|
||||||
firewall.enable = true;
|
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
domain = "repomaa.com";
|
domain = "apu.home.arpa";
|
||||||
};
|
};
|
||||||
|
|
||||||
system.stateVersion = "24.05";
|
system.stateVersion = "24.05";
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
})
|
})
|
||||||
[
|
[
|
||||||
"hetzner"
|
"hetzner"
|
||||||
|
"invidious-companion"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,5 +32,6 @@
|
|||||||
./voidauth.nix
|
./voidauth.nix
|
||||||
./gitea.nix
|
./gitea.nix
|
||||||
./dhcp-dns-sync
|
./dhcp-dns-sync
|
||||||
|
./invidious-companion.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.modules.services.dhcp-dns-sync;
|
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 =
|
dhcp-leases-to-unbound =
|
||||||
pkgs.runCommand "dhcp-leases-to-unbound"
|
pkgs.runCommand "dhcp-leases-to-unbound"
|
||||||
@@ -59,9 +64,10 @@ in
|
|||||||
users.groups.dhcp-dns-sync = { };
|
users.groups.dhcp-dns-sync = { };
|
||||||
|
|
||||||
# Ensure directories and files exist with proper permissions
|
# Ensure directories and files exist with proper permissions
|
||||||
|
# Directory needs to be group-writable for unbound group
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /var/lib/unbound 0755 unbound unbound -"
|
"d /var/lib/unbound 0775 unbound unbound -"
|
||||||
"f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync dhcp-dns-sync -"
|
"f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync unbound -"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Extend Unbound configuration to include generated file
|
# Extend Unbound configuration to include generated file
|
||||||
@@ -69,6 +75,8 @@ in
|
|||||||
server = {
|
server = {
|
||||||
local-zone = [ "${cfg.domain}. static" ];
|
local-zone = [ "${cfg.domain}. static" ];
|
||||||
include = cfg.unboundConfigPath;
|
include = cfg.unboundConfigPath;
|
||||||
|
local-data = [ ''"apu.home.arpa. IN A ${ownAddress}"'' ];
|
||||||
|
local-data-ptr = [ ''"${ownAddress} apu.home.arpa."'' ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,7 +96,7 @@ in
|
|||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
User = "dhcp-dns-sync";
|
User = "dhcp-dns-sync";
|
||||||
Group = "dhcp-dns-sync";
|
Group = "unbound";
|
||||||
# Allow access to networkctl via D-Bus
|
# Allow access to networkctl via D-Bus
|
||||||
SupplementaryGroups = [ "systemd-network" ];
|
SupplementaryGroups = [ "systemd-network" ];
|
||||||
# Read/write paths
|
# Read/write paths
|
||||||
|
|||||||
@@ -58,13 +58,6 @@ def sanitize_hostname(hostname : String) : String?
|
|||||||
sanitized
|
sanitized
|
||||||
end
|
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
|
def generate_unbound_config(leases : Array(Lease), domain : String) : String
|
||||||
lines = [] of String
|
lines = [] of String
|
||||||
|
|
||||||
@@ -82,22 +75,29 @@ def generate_unbound_config(leases : Array(Lease), domain : String) : String
|
|||||||
# A record
|
# A record
|
||||||
lines << %{local-data: "#{fqdn} IN A #{lease.address}"}
|
lines << %{local-data: "#{fqdn} IN A #{lease.address}"}
|
||||||
|
|
||||||
# PTR record
|
# PTR record - local-data-ptr expects IP in normal form, unbound reverses it
|
||||||
if ptr = reverse_ptr(lease.address)
|
lines << %{local-data-ptr: "#{lease.address} #{fqdn}"}
|
||||||
lines << %{local-data-ptr: "#{ptr} #{fqdn}"}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
lines.join("\n") + "\n"
|
lines.join("\n") + "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_leases(interface : String, networkctl_path : String? = nil) : Array(Lease)
|
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"
|
cmd = networkctl_path ? "#{networkctl_path}" : "networkctl"
|
||||||
output = `#{cmd}`
|
args = ["status", interface, "--json=short"]
|
||||||
raise "networkctl failed (exit code #{$?.exit_status}): #{output}" unless $?.success?
|
|
||||||
|
|
||||||
status = NetworkStatus.from_json(output)
|
Process.run(cmd, args, output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
|
||||||
status.dhcp_server.try(&.leases) || [] of Lease
|
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
|
end
|
||||||
|
|
||||||
def write_if_changed(content : String, path : String) : Bool
|
def write_if_changed(content : String, path : String) : Bool
|
||||||
@@ -151,13 +151,18 @@ OptionParser.parse do |parser|
|
|||||||
end
|
end
|
||||||
|
|
||||||
def reload_unbound(unbound_control_path : String?)
|
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..."
|
puts "Reloading Unbound..."
|
||||||
result = system(cmd)
|
|
||||||
unless result
|
Process.run(cmd, ["reload"], output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
|
||||||
# Fallback to systemctl
|
result = process.wait
|
||||||
system("systemctl reload unbound")
|
|
||||||
|
unless result.success?
|
||||||
|
raise "unbound reload failed (exit code #{result.exit_code}): #{process.error}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
puts "Unbound reloaded successfully."
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
|||||||
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
|
let
|
||||||
cfg = config.services.invidious;
|
cfg = config.services.invidious;
|
||||||
fqdn = "${cfg.subdomain}.${config.networking.domain}";
|
fqdn = "${cfg.subdomain}.${config.networking.domain}";
|
||||||
@@ -32,5 +36,14 @@ in
|
|||||||
vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
|
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