move some stuff into hosts/hostname/profiles

This commit is contained in:
xunuwu 2025-06-06 11:28:00 +02:00
parent 9c9a3d543c
commit e2946e4a1f
Signed by: xun
SSH key fingerprint: SHA256:Uot/1WoAjWAeqLOHA5vYy4phhVydsH7jCPmBjaPZfgI
33 changed files with 14 additions and 14 deletions

View file

@ -0,0 +1,25 @@
{
config,
vars,
...
}: let
inherit (vars) domain;
in {
security.acme = {
acceptTerms = true;
defaults.email = "xunuwu@gmail.com";
certs = {
"${domain}" = {
domain = "${domain}";
extraDomainNames = ["*.${domain}" "*.hopper.priv.${domain}"];
dnsProvider = "cloudflare";
reloadServices = ["caddy.service"];
credentialFiles = {
CF_DNS_API_TOKEN_FILE = config.sops.secrets.cloudflare.path;
};
};
};
};
environment.persistence."/persist".directories = ["/var/lib/acme"];
}

View file

@ -0,0 +1,11 @@
{config, ...}: {
services.audiobookshelf = {
enable = true;
host = "0.0.0.0";
};
environment.persistence."/persist".directories = ["/var/lib/${config.services.audiobookshelf.dataDir}"];
services.restic.backups.hopper.paths = [
"/var/lib/${config.services.audiobookshelf.dataDir}"
];
}

View file

@ -0,0 +1,73 @@
{
config,
vars,
inputs,
pkgs,
...
}: let
inherit (vars) domain;
bridge = config.vpnNamespaces."wg".bridgeAddress;
in {
systemd.services.caddy.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
systemd.services.caddy = {
environment.CADDY_ADMIN = "0.0.0.0:2019";
serviceConfig.RuntimeDirectory = "caddy";
};
services.caddy = {
enable = true;
globalConfig = "metrics";
virtualHosts = let
mkPublicEntry = name: destination: {
hostName = "${name}.${domain}:80";
extraConfig = ''
reverse_proxy {
to ${destination}
}
'';
};
mkPrivateEntry = name: destination: {
hostName = "${name}.hopper.priv.${domain}";
useACMEHost = domain;
extraConfig = ''
@blocked not remote_ip ${bridge}
respond @blocked "limited to intranet" 403
reverse_proxy ${destination}
'';
};
in {
navidrome = mkPublicEntry "navidrome" "${bridge}:${toString config.services.navidrome.settings.Port}";
vaultwarden = mkPublicEntry "vw" "${bridge}:${toString config.services.vaultwarden.config.ROCKET_PORT}";
abs = mkPublicEntry "abs" "${bridge}:${toString config.services.audiobookshelf.port}";
navidrome2 = mkPrivateEntry "navidrome" "${bridge}:${toString config.services.navidrome.settings.Port}";
slskd = mkPrivateEntry "slskd" "localhost:${toString config.services.slskd.settings.web.port}";
prometheus = mkPrivateEntry "prometheus" "${bridge}:${toString config.services.prometheus.port}";
transmission = mkPrivateEntry "transmission" "localhost:${toString config.services.transmission.settings.rpc-port}";
dash = mkPrivateEntry "dash" "${bridge}:${toString config.services.homepage-dashboard.listenPort}";
absPriv = mkPrivateEntry "abs" "${bridge}:${toString config.services.audiobookshelf.port}";
glances = mkPrivateEntry "glances" "${bridge}:${toString config.services.glances.port}";
base = {
hostName = "${domain}:80";
extraConfig = ''
root * ${inputs.own-website.packages.${pkgs.system}.default}
file_server
'';
};
other = {
hostName = "*.${domain}:80";
extraConfig = ''
respond 404 {
body "uhh that doesnt exist, i hope this isnt my fault.."
}
'';
};
};
};
}

View file

@ -0,0 +1,33 @@
## TODO look into sops-nix placeholders
## reference: https://github.com/javigomezo/nixos/blob/b3ebe8d570ea9b37aea8bb3a343f6e16e054e322/services/network/authelia/user_database.nix
{
imports = [
./acme.nix
./audiobookshelf.nix
./caddy.nix
./dnsmasq.nix
./glances.nix
./homepage.nix
./minecraft.nix
./navidrome
./prometheus.nix
./restic.nix
./samba.nix
./slskd.nix
./transmission.nix
./vaultwarden.nix
./vpn-namespace.nix
];
users.groups.media = {};
users.users.media = {
isSystemUser = true;
group = "media";
};
networking.firewall = {
allowedUDPPorts = [1900 7359]; # Jellyfin auto-discovery
};
boot.kernel.sysctl."fs.inotify.max_user_watches" = 99999999;
}

View file

@ -0,0 +1,16 @@
{
vars,
lib,
...
}: {
services.dnsmasq = {
enable = true;
resolveLocalQueries = false;
settings = {
server = ["1.1.1.1" "8.8.8.8"];
interface = ["tailscale0"];
bind-interfaces = true;
address = lib.mapAttrsToList (n: v: "/.${n}.priv.${vars.domain}/${v}") vars.tailnet;
};
};
}

View file

@ -0,0 +1,5 @@
{
services.glances = {
enable = true;
};
}

View file

@ -0,0 +1,86 @@
{
config,
vars,
...
}: let
inherit (vars) domain;
bridge = config.vpnNamespaces."wg".bridgeAddress;
in {
services.homepage-dashboard = {
enable = true;
allowedHosts = "dash.hopper.priv.${domain}";
widgets = [
{
resources = {
cpu = true;
disk = "/";
uptime = "";
units = "metric";
cputemp = true;
memory = true;
network = true;
};
}
];
services = [
{
"Downloading" = [
{
"transmission" = {
href = "https://transmission.hopper.priv.${domain}";
icon = "transmission";
widget = {
type = "transmission";
url = "http://${config.vpnNamespaces."wg".namespaceAddress}:${toString config.services.transmission.settings.rpc-port}";
};
};
}
{
"slskd" = {
href = "https://slskd.hopper.priv.${domain}";
icon = "slskd";
};
}
];
}
{
"Services" = [
{
"navidrome" = {
href = "https://navidrome.${domain}";
icon = "navidrome";
};
}
{
"audiobookshelf" = {
href = "https://abs.${domain}";
icon = "audiobookshelf";
};
}
{
"prometheus" = {
href = "https://prometheus.hopper.priv.${domain}";
icon = "prometheus";
widget = {
type = "prometheus";
url = "http://localhost:${toString config.services.prometheus.port}";
};
};
}
{
"glances" = {
href = "https://glances.hopper.priv.${domain}";
icon = "glances";
};
}
{
"vaultwarden" = {
href = "https://vw.${domain}";
icon = "vaultwarden";
};
}
];
}
];
};
}

View file

@ -0,0 +1,50 @@
{
inputs,
pkgs,
...
}: {
services.minecraft-servers = {
enable = false;
eula = true;
openFirewall = true;
servers.owo = {
enable = true;
package = inputs.nix-minecraft.legacyPackages.${pkgs.system}.fabricServers.fabric-1_21_5;
serverProperties = {
max-players = 5;
motd = "owo";
difficulty = "normal";
allow-flight = true;
view-distance = 16;
};
jvmOpts = "-Xms1024M -Xmx4096M";
symlinks.mods = pkgs.linkFarmFromDrvs "mods" (
builtins.attrValues {
Fabric-API = pkgs.fetchurl {
url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/hBmLTbVB/fabric-api-0.121.0%2B1.21.5.jar";
hash = "sha256-GbKETZqAN5vfXJF0yNgwTiogDAI434S3Rj9rZw6B53E=";
};
Lithium = pkgs.fetchurl {
url = "https://cdn.modrinth.com/data/gvQqBUqZ/versions/VWYoZjBF/lithium-fabric-0.16.2%2Bmc1.21.5.jar";
hash = "sha256-XqvnQxASa4M0l3JJxi5Ej6TMHUWgodOmMhwbzWuMYGg=";
};
FerriteCore = pkgs.fetchurl {
url = "https://cdn.modrinth.com/data/uXXizFIs/versions/CtMpt7Jr/ferritecore-8.0.0-fabric.jar";
hash = "sha256-K5C/AMKlgIw8U5cSpVaRGR+HFtW/pu76ujXpxMWijuo=";
};
C2ME = pkgs.fetchurl {
url = "https://cdn.modrinth.com/data/VSNURh3q/versions/VEjpHAOG/c2me-fabric-mc1.21.5-0.3.2%2Brc.1.0.jar";
hash = "sha256-D7Ho8N4vZwHeacmfNe8YMcxsQCSlyNWFsxOp2b+vujE=";
};
Krypton = pkgs.fetchurl {
url = "https://cdn.modrinth.com/data/fQEb0iXm/versions/neW85eWt/krypton-0.2.9.jar";
hash = "sha256-uGYia+H2DPawZQxBuxk77PMKfsN8GEUZo3F1zZ3MY6o=";
};
}
);
};
};
environment.persistence."/persist".directories = ["/srv/minecraft"];
services.restic.backups.hopper.paths = ["/srv/minecraft"];
}

View file

@ -0,0 +1,28 @@
{
config,
pkgs,
...
}: {
users.users.navidrome.extraGroups = ["media"];
services.navidrome = {
enable = true;
package = pkgs.navidrome.overrideAttrs {
patches = [./scrobbleAlbumArtist.patch];
doCheck = false;
};
settings = {
MusicFolder = "/media/library/music";
Address = config.vpnNamespaces."wg".bridgeAddress;
EnableSharing = true;
};
};
systemd.services.navidrome.serviceConfig.EnvironmentFile = config.sops.secrets.navidrome.path;
environment.persistence."/persist".directories = ["/var/lib/navidrome"];
services.restic.backups.hopper = {
paths = ["/var/lib/navidrome"];
exclude = ["/var/lib/navidrome/cache"];
};
}

View file

@ -0,0 +1,23 @@
diff --git a/core/agents/lastfm/client.go b/core/agents/lastfm/client.go
index 6a24ac80..bbc0aebb 100644
--- a/core/agents/lastfm/client.go
+++ b/core/agents/lastfm/client.go
@@ -131,7 +131,7 @@ type ScrobbleInfo struct {
func (c *client) updateNowPlaying(ctx context.Context, sessionKey string, info ScrobbleInfo) error {
params := url.Values{}
params.Add("method", "track.updateNowPlaying")
- params.Add("artist", info.artist)
+ params.Add("artist", info.albumArtist)
params.Add("track", info.track)
params.Add("album", info.album)
params.Add("trackNumber", strconv.Itoa(info.trackNumber))
@@ -154,7 +154,7 @@ func (c *client) scrobble(ctx context.Context, sessionKey string, info ScrobbleI
params := url.Values{}
params.Add("method", "track.scrobble")
params.Add("timestamp", strconv.FormatInt(info.timestamp.Unix(), 10))
- params.Add("artist", info.artist)
+ params.Add("artist", info.albumArtist)
params.Add("track", info.track)
params.Add("album", info.album)
params.Add("trackNumber", strconv.Itoa(info.trackNumber))

View file

@ -0,0 +1,53 @@
{
lib,
config,
...
}: {
services.prometheus = {
enable = true;
port = 9001;
extraFlags = ["--storage.tsdb.retention.time=30d"];
scrapeConfigs = [
{
job_name = "node";
static_configs = lib.singleton {
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.node.port}"];
};
}
{
job_name = "tailscale_client";
static_configs = lib.singleton {
targets = ["100.100.100.100"];
};
}
{
job_name = "caddy";
static_configs = lib.singleton {
targets = ["${config.vpnNamespaces."wg".namespaceAddress}:2019"];
};
}
{
job_name = "slskd";
static_configs = lib.singleton {
targets = ["${config.vpnNamespaces."wg".namespaceAddress}:${toString config.services.slskd.settings.web.port}"];
};
metric_relabel_configs = lib.singleton {
source_labels = ["__name__"];
regex = "node_.*";
action = "drop";
};
}
];
};
services.prometheus.exporters = {
node = {
enable = true;
enabledCollectors = ["systemd"];
};
systemd.enable = true;
};
environment.persistence."/persist".directories = ["/var/lib/prometheus2"];
services.restic.backups.hopper.paths = ["/var/lib/prometheus2"];
}

View file

@ -0,0 +1,21 @@
{config, ...}: {
services.restic.backups.hopper = {
initialize = true;
inhibitsSleep = true;
repository = "rest:http://nixdesk:8000/hopper";
passwordFile = config.sops.secrets.restic-password.path;
timerConfig = {
OnCalendar = "18:00";
Persistent = true;
RandomizedDelaySec = "1h";
};
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 5"
"--keep-monthly 6"
];
paths = [
"/media/library/music"
];
};
}

View file

@ -0,0 +1,80 @@
{
config,
lib,
pkgs,
...
}: {
# only used for samba
users.groups.xun = {};
users.users.xun = {
isSystemUser = true;
group = "xun";
extraGroups = ["transmission" "vault" "media"];
};
users.users.media = {
isSystemUser = true;
group = "media";
};
users.groups.vault = {};
systemd.tmpfiles.rules = [
"d /srv/vault 0770 root vault -"
"d /media/library 0770 media media -"
];
services.samba = {
enable = true;
openFirewall = true;
settings = {
global = {
"log level" = "3 passdb:5 auth:5";
"log file" = "/var/log/samba/samba.log";
"server string" = config.networking.hostName;
"hosts allow" = "192.168.50.0/24";
"map to guest" = "bad user";
"passdb backend" = "smbpasswd:${config.sops.secrets.samba-pass.path}";
};
transmission = {
path = "/var/lib/transmission";
browseable = "yes";
"read only" = "yes";
"guest ok" = "no";
"create mask" = "0660";
"directory mask" = "0770";
};
vault = {
path = "/srv/vault";
browseable = "yes";
"read only" = "no";
"guest ok" = "no";
"create mask" = "0660";
"directory mask" = "0770";
"force user" = "xun";
"force group" = "xun";
};
slskd = {
path = "/var/lib/slskd";
browseable = "yes";
"read only" = "no";
"guest ok" = "no";
"create mask" = "0660";
"directory mask" = "0770";
"force user" = "slskd";
"force group" = "slskd";
};
library = {
path = "media/library";
browseable = "yes";
"read only" = "no";
"guest ok" = "no";
"create mask" = "0660";
"directory mask" = "0770";
"force user" = "media";
"force group" = "media";
};
};
};
environment.persistence."/persist".directories = ["/srv/vault"];
services.restic.backups.hopper.paths = ["/srv/vault"];
}

View file

@ -0,0 +1,43 @@
{
config,
pkgs,
...
}: {
systemd.services.slskd.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
users.users.slskd.extraGroups = ["media"];
services.slskd = {
enable = true;
environmentFile = config.sops.secrets.slskd.path;
domain = null; # why isnt this the default?
settings = {
metrics = {
enabled = true;
authentication.disabled = true;
};
remote_file_management = true;
shares.directories = ["/media/library/music"];
soulseek = {
listen_port = 24001;
picture = pkgs.fetchurl {
url = "https://cdn.donmai.us/original/57/65/__kasane_teto_utau_drawn_by_nonounno__576558c9a54c63a268f9b584f1e84c9f.png";
hash = "sha256-7WOClBi4QgOfmcMaMorK/t8FGGO7dNUwxg3AVEjRemw=";
};
};
web.authentication.disabled = true;
global = {
upload = {
slots = 50;
speed_limit = 10000;
};
download.speed_limit = 10000;
};
};
};
environment.persistence."/persist".directories = ["/var/lib/slskd"];
}

View file

@ -0,0 +1,34 @@
{
pkgs,
config,
vars,
...
}: {
systemd.services.transmission.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.transmission = {
enable = true;
package = pkgs.transmission_4;
performanceNetParameters = true;
settings = let
mbit = 125;
in {
speed-limit-up-enabled = true;
speed-limit-up = 50 * mbit;
speed-limit-down-enabled = true;
speed-limit-down = 150 * mbit;
peer-port = 24003;
rpc-authentication-required = false;
rpc-bind-address = "0.0.0.0";
rpc-host-whitelist = "transmission.hopper.priv.${vars.domain}";
rpc-whitelist-enabled = true;
rpc-whitelist = "127.0.0.1,192.168.\*.\*,100.\*.\*.\*";
};
# credentialsFile = config.sops.secrets.transmission.path;
};
environment.persistence."/persist".directories = ["/var/lib/transmission"];
}

View file

@ -0,0 +1,20 @@
{config, ...}: {
systemd.services.vaultwarden = {
serviceConfig.EnvironmentFile = config.sops.secrets.vaultwarden-env.path;
};
# NOTE send doesnt work, probably due to my cloudflare port rewriting rules
services.vaultwarden = {
enable = true;
config = {
DOMAIN = "https://${config.services.caddy.virtualHosts.vaultwarden.hostName}";
ROCKET_ADDRESS = "0.0.0.0";
ROCKET_PORT = 35381;
ROCKET_LOG = "critical";
SIGNUPS_ALLOWED = false;
IP_HEADER = "X-Forwarded-For";
};
};
environment.persistence."/persist".directories = ["/var/lib/bitwarden_rs"];
services.restic.backups.hopper.paths = ["/var/lib/bitwarden_rs"];
}

View file

@ -0,0 +1,75 @@
{
config,
lib,
...
}: {
networking.firewall = let
allowTcpFromVPNToDefaultPorts = [
config.services.prometheus.port
config.services.vaultwarden.config.ROCKET_PORT
config.services.homepage-dashboard.listenPort
config.services.audiobookshelf.port
config.services.navidrome.settings.Port
config.services.glances.port
];
in {
extraCommands = builtins.concatStringsSep "\n" (map
(port: "iptables -A nixos-fw -p tcp -s ${config.vpnNamespaces."wg".namespaceAddress} --dport ${toString port} -j nixos-fw-accept")
allowTcpFromVPNToDefaultPorts);
extraStopCommands = builtins.concatStringsSep "\n" (
map
(port: "iptables -D nixos-fw -p tcp -s ${config.vpnNamespaces."wg".namespaceAddress} --dport ${toString port} -j nixos-fw-accept || true")
allowTcpFromVPNToDefaultPorts
);
};
vpnNamespaces."wg" = {
enable = true;
wireguardConfigFile = config.sops.secrets.wireguard.path;
accessibleFrom = [
"192.168.0.0/24"
# "127.0.0.1"
];
# Forwarded to my vpn, for making things accessible from outside
openVPNPorts = [
{
port = 443; # caddy
protocol = "tcp";
}
{
port = 80; # caddy
protocol = "tcp";
}
{
port = 24001; # slskd
protocol = "both";
}
{
port = 24002; # slskd
protocol = "both";
}
{
port = 24003; # transmission
protocol = "both";
}
];
# From inside of the vpn namespace to outside of it, for making things inside accessible to LAN
portMappings = let
passthrough = [
8336 # caddy
80 # caddy
443 # caddy
2019 # caddy admin, for prometheus metrics
config.services.transmission.settings.rpc-port
config.services.slskd.settings.web.port
];
in (lib.map (x: {
from = x;
to = x;
})
passthrough);
};
}