Compare commits
21 Commits
78f0afe517
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d6785f27e | ||
|
|
f446706a9c | ||
|
|
6ea5db6d37 | ||
|
|
e46b5689a2 | ||
|
|
8fa60a1b06 | ||
|
|
4f5de3b4cc | ||
|
|
65569f6b57 | ||
|
|
f2130be7e4 | ||
|
|
efaa92d4a1 | ||
|
|
5ab63a0880 | ||
|
|
d614186ef9 | ||
|
|
aefe89f648 | ||
|
|
2a1c8038ba | ||
|
|
9fd7c65b44 | ||
|
|
e78f603717 | ||
|
|
e5c9fcea33 | ||
|
|
1dbcb4439a | ||
|
|
b26fb44824 | ||
|
|
48e352f015 | ||
|
|
e575a147cd | ||
|
|
7653bfce22 |
78
flake.lock
generated
78
flake.lock
generated
@@ -573,6 +573,48 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"komga-bookmanager": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1772572455,
|
||||||
|
"narHash": "sha256-B4KOdV2GQogU1XL9903qFkOFf/6H7UGa+dprNGctgMY=",
|
||||||
|
"ref": "main",
|
||||||
|
"rev": "002732d4ebc5b80c2b8522896427c9f6bee9e1d1",
|
||||||
|
"revCount": 6,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.ppp.pm:1122/alex/komga-bookmanager.git"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.ppp.pm:1122/alex/komga-bookmanager.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"komga-comictracker": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1772884418,
|
||||||
|
"narHash": "sha256-2V/JnGsIhvQoAiH7iIQkOPX5OTZVwGTOEv+nFetd4I8=",
|
||||||
|
"ref": "main",
|
||||||
|
"rev": "0b91385e8906ca40cc89381bf8394ed24a116c6b",
|
||||||
|
"revCount": 49,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.ppp.pm:1122/alex/komga-comictracker.git"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.ppp.pm:1122/alex/komga-comictracker.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
"naviterm": {
|
"naviterm": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
@@ -596,9 +638,7 @@
|
|||||||
},
|
},
|
||||||
"nh": {
|
"nh": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": "nixpkgs"
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770930949,
|
"lastModified": 1770930949,
|
||||||
@@ -669,16 +709,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1771008912,
|
"lastModified": 1770770419,
|
||||||
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
"narHash": "sha256-iKZMkr6Cm9JzWlRYW/VPoL0A9jVKtZYiU4zSrVeetIs=",
|
||||||
"owner": "nixos",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
"rev": "6c5e707c6b5339359a9a9e215c5e66d6d802fd7a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-25.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -699,6 +739,22 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1771008912,
|
||||||
|
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"pppdotpm-site": {
|
"pppdotpm-site": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -751,12 +807,14 @@
|
|||||||
"home-manager": "home-manager_2",
|
"home-manager": "home-manager_2",
|
||||||
"hyprland": "hyprland",
|
"hyprland": "hyprland",
|
||||||
"hyprland-contrib": "hyprland-contrib",
|
"hyprland-contrib": "hyprland-contrib",
|
||||||
|
"komga-bookmanager": "komga-bookmanager",
|
||||||
|
"komga-comictracker": "komga-comictracker",
|
||||||
"naviterm": "naviterm",
|
"naviterm": "naviterm",
|
||||||
"nh": "nh",
|
"nh": "nh",
|
||||||
"nix-gc-env": "nix-gc-env",
|
"nix-gc-env": "nix-gc-env",
|
||||||
"nix-jetbrains-plugins": "nix-jetbrains-plugins",
|
"nix-jetbrains-plugins": "nix-jetbrains-plugins",
|
||||||
"nixos-hardware": "nixos-hardware",
|
"nixos-hardware": "nixos-hardware",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"pppdotpm-site": "pppdotpm-site",
|
"pppdotpm-site": "pppdotpm-site",
|
||||||
"whib-backend": "whib-backend",
|
"whib-backend": "whib-backend",
|
||||||
"whib-frontend": "whib-frontend"
|
"whib-frontend": "whib-frontend"
|
||||||
|
|||||||
13
flake.nix
13
flake.nix
@@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
nh = {
|
nh = {
|
||||||
url = "github:viperML/nh";
|
url = "github:viperML/nh";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
nix-gc-env.url = "github:Julow/nix-gc-env";
|
nix-gc-env.url = "github:Julow/nix-gc-env";
|
||||||
@@ -69,6 +68,18 @@
|
|||||||
# url = "path:/home/alex/code/own/whib-react";
|
# url = "path:/home/alex/code/own/whib-react";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
komga-comictracker = {
|
||||||
|
url = "git+ssh://gitea@git.ppp.pm:1122/alex/komga-comictracker.git?ref=main";
|
||||||
|
# url = "path:/home/alex/code/own/komga-comictracker";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
komga-bookmanager = {
|
||||||
|
url = "git+ssh://gitea@git.ppp.pm:1122/alex/komga-bookmanager.git?ref=main";
|
||||||
|
# url = "path:/home/alex/code/own/komga-comictracker";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|||||||
@@ -22,6 +22,13 @@
|
|||||||
alsa.enable = true;
|
alsa.enable = true;
|
||||||
alsa.support32Bit = true;
|
alsa.support32Bit = true;
|
||||||
pulse.enable = true;
|
pulse.enable = true;
|
||||||
|
|
||||||
|
extraConfig.pipewire."90-hdmi-fix" = {
|
||||||
|
"context.properties" = {
|
||||||
|
"default.clock.rate" = 48000;
|
||||||
|
"default.clock.allowed-rates" = [ 48000 ];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
hardware = {
|
hardware = {
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ in
|
|||||||
|
|
||||||
efi.canTouchEfiVariables = true;
|
efi.canTouchEfiVariables = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extraModprobeConfig = ''
|
||||||
|
options snd-intel-dspcfg dsp_driver=1
|
||||||
|
options snd_hda_intel power_save=0 power_save_controller=N
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,33 +3,40 @@
|
|||||||
networking = {
|
networking = {
|
||||||
hostName = "backwards";
|
hostName = "backwards";
|
||||||
|
|
||||||
networkmanager.enable = false;
|
wireless.enable = false;
|
||||||
|
|
||||||
#wireless.networks are defined in the secret `wpa_supplicant.conf`
|
networkmanager = {
|
||||||
wireless = {
|
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
secretsFile = config.age.secrets.wireless-network-secrets.path;
|
wifi.backend = "iwd";
|
||||||
|
|
||||||
networks = {
|
ensureProfiles = {
|
||||||
"w1-f1_5G" = {
|
environmentFiles = [
|
||||||
pskRaw = "ext:w1-f1_psk";
|
config.age.secrets.wireless-network-secrets.path
|
||||||
};
|
];
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
defaultGateway = "192.168.50.1";
|
profiles = {
|
||||||
nameservers = [ "1.1.1.1" ];
|
w1-f1_5G = {
|
||||||
interfaces = {
|
connection = {
|
||||||
wlp1s0 = {
|
id = "w1-f1_5G";
|
||||||
useDHCP = false;
|
type = "wifi";
|
||||||
ipv4 = {
|
interface-name = "wlp1s0";
|
||||||
addresses = [
|
};
|
||||||
{
|
wifi = {
|
||||||
address = "192.168.50.202";
|
ssid = "w1-f1_5G";
|
||||||
prefixLength = 24;
|
mode = "infrastructure";
|
||||||
}
|
};
|
||||||
];
|
wifi-security = {
|
||||||
|
key-mgmt = "wpa-psk";
|
||||||
|
psk = "$w1_f1_psk";
|
||||||
|
};
|
||||||
|
ipv4 = {
|
||||||
|
method = "manual";
|
||||||
|
addresses = "192.168.50.202/24";
|
||||||
|
gateway = "192.168.50.1";
|
||||||
|
dns = "1.1.1.1";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf enabled {
|
config = lib.mkIf enabled {
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Audiobookshelf";
|
||||||
|
port = 8000;
|
||||||
|
description = "Audiobooks & podcasts";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
users.users.audiobookshelf = {
|
users.users.audiobookshelf = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
description = "audiobookshelf";
|
description = "audiobookshelf";
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
let
|
|
||||||
enabled = config.mod.calibre-web.enable;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
mod.calibre-web = {
|
|
||||||
enable = lib.mkEnableOption "add calibre-web module";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf enabled {
|
|
||||||
services = {
|
|
||||||
calibre-web = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
user = "storage";
|
|
||||||
group = "storage";
|
|
||||||
|
|
||||||
listen = {
|
|
||||||
ip = "0.0.0.0";
|
|
||||||
port = 8083;
|
|
||||||
};
|
|
||||||
|
|
||||||
dataDir = "/mnt/media/public/books";
|
|
||||||
|
|
||||||
options = {
|
|
||||||
calibreLibrary = "/mnt/media/public/books";
|
|
||||||
enableBookUploading = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,19 @@
|
|||||||
"--http-timeout=60"
|
"--http-timeout=60"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"komga.ppp.pm" = {
|
||||||
|
dnsProvider = "hetzner";
|
||||||
|
environmentFile = config.age.secrets.hetzner-dns.path;
|
||||||
|
group = "nginx";
|
||||||
|
|
||||||
|
extraLegoFlags = [
|
||||||
|
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
|
||||||
|
"--dns.propagation-wait=60s"
|
||||||
|
"--dns-timeout=60"
|
||||||
|
"--http-timeout=60"
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ in
|
|||||||
nginx.enable = true;
|
nginx.enable = true;
|
||||||
syncthing.enable = true;
|
syncthing.enable = true;
|
||||||
transmission.enable = true;
|
transmission.enable = true;
|
||||||
calibre-web.enable = true;
|
|
||||||
audiobookshelf.enable = true;
|
audiobookshelf.enable = true;
|
||||||
jellyfin.enable = true;
|
jellyfin.enable = true;
|
||||||
immich.enable = true;
|
immich.enable = true;
|
||||||
navidrome.enable = true;
|
navidrome.enable = true;
|
||||||
komga.enable = true;
|
komga.enable = true;
|
||||||
|
homepage.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,70 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
nginxEnabled = config.mod.nginx.enable;
|
nginxEnabled = config.mod.nginx.enable;
|
||||||
|
|
||||||
|
script = pkgs.writeShellScript "bt-reset" ''
|
||||||
|
set -euo pipefail
|
||||||
|
export PATH="${
|
||||||
|
lib.makeBinPath [
|
||||||
|
pkgs.bluez
|
||||||
|
pkgs.util-linux
|
||||||
|
pkgs.kmod
|
||||||
|
pkgs.gnugrep
|
||||||
|
pkgs.coreutils
|
||||||
|
]
|
||||||
|
}"
|
||||||
|
|
||||||
|
logger -t bt-reset "Starting Bluetooth adapter reset..."
|
||||||
|
|
||||||
|
# Exit early if the adapter is already present and running
|
||||||
|
if hciconfig hci0 2>/dev/null | grep -q "UP RUNNING"; then
|
||||||
|
logger -t bt-reset "hci0 is already UP RUNNING — nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If hci0 exists but isn't UP, try bringing it up
|
||||||
|
if hciconfig hci0 2>/dev/null; then
|
||||||
|
logger -t bt-reset "hci0 exists but not running — bringing it up"
|
||||||
|
hciconfig hci0 up || true
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if hciconfig hci0 2>/dev/null | grep -q "UP RUNNING"; then
|
||||||
|
logger -t bt-reset "hci0 is UP now"
|
||||||
|
systemctl restart bluetooth.service
|
||||||
|
logger -t bt-reset "bluetooth.service restarted — done"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Hard reset: reload the btusb kernel module (works for USB adapters)
|
||||||
|
logger -t bt-reset "hci0 missing — reloading btusb module..."
|
||||||
|
modprobe -r btusb 2>/dev/null || true
|
||||||
|
sleep 3
|
||||||
|
modprobe btusb
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
if hciconfig hci0 2>/dev/null; then
|
||||||
|
hciconfig hci0 up
|
||||||
|
logger -t bt-reset "hci0 restored after module reload"
|
||||||
|
else
|
||||||
|
logger -t bt-reset "ERROR: hci0 not found after module reload"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restart the bluetooth systemd service so bluetoothd picks up the adapter
|
||||||
|
systemctl restart bluetooth.service
|
||||||
|
logger -t bt-reset "bluetooth.service restarted — done"
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Home Assistant";
|
||||||
|
port = 8123;
|
||||||
|
description = "Home automation";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
hardware.bluetooth.enable = true;
|
hardware.bluetooth.enable = true;
|
||||||
|
|
||||||
virtualisation.oci-containers = {
|
virtualisation.oci-containers = {
|
||||||
@@ -57,71 +119,114 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Trigger reset via udev when hci0 disappears
|
||||||
|
udev.extraRules = ''
|
||||||
|
ACTION=="remove", SUBSYSTEM=="bluetooth", KERNEL=="hci0", \
|
||||||
|
TAG+="systemd", ENV{SYSTEMD_WANTS}+="bt-reset.service"
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.user = {
|
systemd = {
|
||||||
timers = {
|
services = {
|
||||||
"update-hetzner-ha-dns" = {
|
# Trigger reset on bluetoothd failure
|
||||||
unitConfig = {
|
bluetooth = {
|
||||||
Description = "updates Hetzner DNS for home-assistant";
|
unitConfig.OnFailure = [ "bt-reset.service" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
timerConfig = {
|
bt-reset = {
|
||||||
Unit = "update-hetzner-ha-dns.service";
|
description = "Reset Bluetooth adapter";
|
||||||
OnCalendar = "*-*-* *:00/30:00";
|
after = [ "bluetooth.service" ];
|
||||||
Persistent = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
wantedBy = [ "timers.target" ];
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = script;
|
||||||
|
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "10s";
|
||||||
|
StartLimitIntervalSec = "120";
|
||||||
|
StartLimitBurst = 3;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services = {
|
timers.bt-reset = {
|
||||||
"update-hetzner-ha-dns" = {
|
description = "Periodically reset Bluetooth adapter";
|
||||||
unitConfig = {
|
wantedBy = [ "timers.target" ];
|
||||||
Description = "updates Hetzner DNS for home-assistant";
|
timerConfig = {
|
||||||
|
OnBootSec = "5min"; # first run 5 min after boot
|
||||||
|
OnUnitActiveSec = "4h"; # then every 4 hours
|
||||||
|
RandomizedDelaySec = "5min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
user = {
|
||||||
|
timers = {
|
||||||
|
"update-hetzner-dns" = {
|
||||||
|
unitConfig = {
|
||||||
|
Description = "updates Hetzner DNS records";
|
||||||
|
};
|
||||||
|
|
||||||
|
timerConfig = {
|
||||||
|
Unit = "update-hetzner-dns.service";
|
||||||
|
OnCalendar = "*-*-* *:00/30:00";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
serviceConfig = {
|
services = {
|
||||||
Type = "exec";
|
"update-hetzner-dns" = {
|
||||||
EnvironmentFile = config.age.secrets.hetzner-dns.path;
|
unitConfig = {
|
||||||
|
Description = "updates Hetzner DNS records";
|
||||||
|
};
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "exec";
|
||||||
|
EnvironmentFile = config.age.secrets.hetzner-dns.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
path = [
|
||||||
|
pkgs.curl
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.jq
|
||||||
|
];
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
SUBDOMAINS="ha komga"
|
||||||
|
INTERFACE="enp3s0"
|
||||||
|
|
||||||
|
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
|
||||||
|
|
||||||
|
for SUBDOMAIN in $SUBDOMAINS; do
|
||||||
|
LAST_IP_FILE="/tmp/hetzner-dns-''${SUBDOMAIN}-ip"
|
||||||
|
|
||||||
|
LAST_IP=""
|
||||||
|
if [[ -f "$LAST_IP_FILE" ]]; then
|
||||||
|
LAST_IP=$(cat "$LAST_IP_FILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$CURRENT_IP" == "$LAST_IP" ]]; then
|
||||||
|
echo "$SUBDOMAIN: IP unchanged, NOOP update."
|
||||||
|
else
|
||||||
|
echo "$SUBDOMAIN: Updating IP"
|
||||||
|
|
||||||
|
JSON_BODY=$(jq -n --arg ip "$CURRENT_IP" '{records: [{value: $ip}]}')
|
||||||
|
|
||||||
|
curl \
|
||||||
|
--fail \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$JSON_BODY" \
|
||||||
|
"https://api.hetzner.cloud/v1/zones/ppp.pm/rrsets/''${SUBDOMAIN}/A/actions/set_records" \
|
||||||
|
&& echo $CURRENT_IP > $LAST_IP_FILE
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
path = [
|
|
||||||
pkgs.curl
|
|
||||||
pkgs.coreutils # For `cat`
|
|
||||||
pkgs.jq
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
LAST_IP_FILE="/tmp/hetzner-dns-ha-ip"
|
|
||||||
INTERFACE="enp3s0"
|
|
||||||
|
|
||||||
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
|
|
||||||
|
|
||||||
LAST_IP=""
|
|
||||||
if [[ -f "$LAST_IP_FILE" ]]; then
|
|
||||||
LAST_IP=$(cat "$LAST_IP_FILE")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$CURRENT_IP" == "$LAST_IP" ]]; then
|
|
||||||
echo "IP unchanged, NOOP update."
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo "Updating IP"
|
|
||||||
|
|
||||||
JSON_BODY=$(jq -n --arg ip "$CURRENT_IP" '{records: [{value: $ip}]}')
|
|
||||||
|
|
||||||
curl \
|
|
||||||
--fail \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$JSON_BODY" \
|
|
||||||
"https://api.hetzner.cloud/v1/zones/ppp.pm/rrsets/ha/A/actions/set_records" \
|
|
||||||
&& echo $CURRENT_IP > $LAST_IP_FILE
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
111
hosts/manatee/modules/homepage/default.nix
Normal file
111
hosts/manatee/modules/homepage/default.nix
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
enabled = config.mod.homepage.enable;
|
||||||
|
nginxEnabled = config.mod.nginx.enable;
|
||||||
|
services = config.mod.homepage.services;
|
||||||
|
|
||||||
|
serviceToCard = svc: ''
|
||||||
|
<a class="card" href="http://manatee:${toString svc.port}">
|
||||||
|
<div class="name">${svc.name}</div>
|
||||||
|
<div class="desc">${svc.description}</div>
|
||||||
|
<div class="port">:${toString svc.port}</div>
|
||||||
|
</a>
|
||||||
|
'';
|
||||||
|
|
||||||
|
page = pkgs.writeTextDir "index.html" ''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>manatee</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
background: #1a1b26;
|
||||||
|
color: #c0caf5;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #7aa2f7;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
display: block;
|
||||||
|
background: #24283b;
|
||||||
|
border: 1px solid #414868;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.card:hover { border-color: #7aa2f7; }
|
||||||
|
.name { font-size: 1.1rem; font-weight: 600; color: #c0caf5; }
|
||||||
|
.desc { font-size: 0.85rem; color: #565f89; margin-top: 0.35rem; }
|
||||||
|
.port { font-size: 0.8rem; color: #414868; margin-top: 0.5rem; font-family: monospace; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>manatee</h1>
|
||||||
|
<div class="grid">
|
||||||
|
${lib.concatMapStrings serviceToCard services}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
mod.homepage = {
|
||||||
|
enable = lib.mkEnableOption "Enable homepage module";
|
||||||
|
services = lib.mkOption {
|
||||||
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption { type = lib.types.str; };
|
||||||
|
port = lib.mkOption { type = lib.types.port; };
|
||||||
|
description = lib.mkOption { type = lib.types.str; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = [ ];
|
||||||
|
description = "Services to display on the homepage";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf (enabled && nginxEnabled) {
|
||||||
|
services.nginx.virtualHosts."homepage" = {
|
||||||
|
listen = [
|
||||||
|
{
|
||||||
|
addr = "0.0.0.0";
|
||||||
|
port = 9999;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
root = page;
|
||||||
|
locations."/" = {
|
||||||
|
index = "index.html";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 9999 ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,6 +10,14 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf enabled {
|
config = lib.mkIf enabled {
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Immich";
|
||||||
|
port = 2283;
|
||||||
|
description = "Photo library";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
users.users.immich = {
|
users.users.immich = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = "storage";
|
group = "storage";
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Jellyfin";
|
||||||
|
port = 8096;
|
||||||
|
description = "Media streaming";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
firewall.allowedTCPPorts = [ 8096 ];
|
firewall.allowedTCPPorts = [ 8096 ];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
{ lib, config, ... }:
|
{
|
||||||
|
inputs,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
enabled = config.mod.komga.enable;
|
enabled = config.mod.komga.enable;
|
||||||
|
nginxEnabled = config.mod.nginx.enable;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
@@ -9,7 +16,30 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
inputs.komga-comictracker.nixosModules.default
|
||||||
|
inputs.komga-bookmanager.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
config = lib.mkIf enabled {
|
config = lib.mkIf enabled {
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Komga";
|
||||||
|
port = 8002;
|
||||||
|
description = "Comic library";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "Komga Reader";
|
||||||
|
port = 8888;
|
||||||
|
description = "Comic reader";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "Komga Book Manager";
|
||||||
|
port = 8686;
|
||||||
|
description = "Book manager";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
users.users.komga = {
|
users.users.komga = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = "storage";
|
group = "storage";
|
||||||
@@ -21,8 +51,75 @@ in
|
|||||||
user = "komga";
|
user = "komga";
|
||||||
group = "storage";
|
group = "storage";
|
||||||
|
|
||||||
settings.server.port = 8002;
|
settings = {
|
||||||
|
server.port = 8002;
|
||||||
|
komga."cors.allowed-origins" = [
|
||||||
|
"http://manatee:8888"
|
||||||
|
"https://komga.ppp.pm"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.nginx = lib.mkIf nginxEnabled {
|
||||||
|
virtualHosts."komga-reader" = {
|
||||||
|
listen = [
|
||||||
|
{
|
||||||
|
addr = "0.0.0.0";
|
||||||
|
port = 8888;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
root = (pkgs.writeTextDir "komga-reader.html" (builtins.readFile ./komga-reader.html));
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
index = "komga-reader.html";
|
||||||
|
tryFiles = "$uri $uri/ /komga-reader.html";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualHosts."komga.ppp.pm" = {
|
||||||
|
forceSSL = true;
|
||||||
|
useACMEHost = "komga.ppp.pm";
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8002";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 8888 ];
|
||||||
|
|
||||||
|
programs.comictracker = {
|
||||||
|
enable = true;
|
||||||
|
komgaUrl = "http://127.0.0.1:8002";
|
||||||
|
komgaLibraryId = "0NVZH5AK3RPE1";
|
||||||
|
secretsFile = config.age.secrets.komga-comicbooktracker-credentials.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.komga-book-manager = {
|
||||||
|
enable = true;
|
||||||
|
port = 8686;
|
||||||
|
group = "storage";
|
||||||
|
komgaUrl = "http://127.0.0.1:8002";
|
||||||
|
credentialsFile = config.age.secrets.komga-bookmanager-credentials.path;
|
||||||
|
libraryRoot = "/mnt/media/public/books";
|
||||||
|
libraryId = "0PNE1NEPY6995";
|
||||||
|
};
|
||||||
|
|
||||||
|
age.secrets = {
|
||||||
|
"komga-comicbooktracker-credentials" = {
|
||||||
|
file = ../../../../secrets/manatee/komga-comicbooktracker-credentials.age;
|
||||||
|
owner = "alex";
|
||||||
|
group = "users";
|
||||||
|
};
|
||||||
|
|
||||||
|
"komga-bookmanager-credentials" = {
|
||||||
|
file = ../../../../secrets/manatee/komga-bookmanager-credentials.age;
|
||||||
|
owner = "alex";
|
||||||
|
group = "users";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
1553
hosts/manatee/modules/komga/komga-reader.html
Normal file
1553
hosts/manatee/modules/komga/komga-reader.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,14 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
|
mod.homepage.services = lib.mkIf navidromeEnabled [
|
||||||
|
{
|
||||||
|
name = "Navidrome";
|
||||||
|
port = 4533;
|
||||||
|
description = "Music streaming";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
services = lib.mkIf navidromeEnabled {
|
services = lib.mkIf navidromeEnabled {
|
||||||
navidrome = {
|
navidrome = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf enabled {
|
config = lib.mkIf enabled {
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Syncthing";
|
||||||
|
port = 8384;
|
||||||
|
description = "File sync";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
services.syncthing = {
|
services.syncthing = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf enabled {
|
config = lib.mkIf enabled {
|
||||||
|
mod.homepage.services = [
|
||||||
|
{
|
||||||
|
name = "Transmission";
|
||||||
|
port = 9091;
|
||||||
|
description = "Torrent client";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
transmission = {
|
transmission = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -555,6 +555,9 @@ Setup prefix for keybindings.
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
:custom
|
||||||
|
(eglot-code-action-indications nil)
|
||||||
|
|
||||||
:hook (
|
:hook (
|
||||||
(eglot-managed-mode . mp-eglot-eldoc)
|
(eglot-managed-mode . mp-eglot-eldoc)
|
||||||
(go-mode . eglot-ensure)
|
(go-mode . eglot-ensure)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
home-manager.users.alex = {
|
home-manager.users.alex = {
|
||||||
programs.ssh = {
|
programs.ssh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
enableDefaultConfig = false;
|
||||||
|
|
||||||
matchBlocks = {
|
matchBlocks = {
|
||||||
"manatee" = {
|
"manatee" = {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
system,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
@@ -11,7 +10,7 @@ let
|
|||||||
match =
|
match =
|
||||||
v: l: builtins.elemAt (lib.lists.findFirst (x: (if_let v (builtins.elemAt x 0)) != null) null l) 1;
|
v: l: builtins.elemAt (lib.lists.findFirst (x: (if_let v (builtins.elemAt x 0)) != null) null l) 1;
|
||||||
|
|
||||||
package = match { platform = system; } [
|
package = match { platform = pkgs.stdenv.hostPlatform.system; } [
|
||||||
[
|
[
|
||||||
{ platform = "aarch64-linux"; }
|
{ platform = "aarch64-linux"; }
|
||||||
{
|
{
|
||||||
|
|||||||
7
secrets/manatee/komga-bookmanager-credentials.age
Normal file
7
secrets/manatee/komga-bookmanager-credentials.age
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 wkRvNA GW8mvnaXpspxr78xV0fKhXwHVvReyjvDc0v7uPwhuBI
|
||||||
|
Rne8JZYVhrTTesSFpRQ/IOZlFIMoX9Wmv5n1Ed7Ehv8
|
||||||
|
-> ssh-ed25519 +oNaHQ d7utzodGQ7LsD2Uht1rbT8Qq9BZp3PkJS9EDhajCjnk
|
||||||
|
qd2Vj+1TQrjEKkSVAf0cXcCdkgeN/Jbp4UrBSp3cKYQ
|
||||||
|
--- JQr5UQlutONqnTeoT/mIVZL8ME7ipUDK8zDfNcN3uhU
|
||||||
|
ø5-VŸÌ²ÂòÀ-®ªÁÅ'^žô5kú.t(d1‡)É'<u%
|
||||||
BIN
secrets/manatee/komga-comicbooktracker-credentials.age
Normal file
BIN
secrets/manatee/komga-comicbooktracker-credentials.age
Normal file
Binary file not shown.
@@ -33,6 +33,8 @@ in {
|
|||||||
"manatee/syncthing-cert.age".publicKeys = [ manatee alex ];
|
"manatee/syncthing-cert.age".publicKeys = [ manatee alex ];
|
||||||
"manatee/syncthing-key.age".publicKeys = [ manatee alex ];
|
"manatee/syncthing-key.age".publicKeys = [ manatee alex ];
|
||||||
"manatee/hetzner-dns.age".publicKeys = [ manatee alex ];
|
"manatee/hetzner-dns.age".publicKeys = [ manatee alex ];
|
||||||
|
"manatee/komga-comicbooktracker-credentials.age".publicKeys = [ manatee alex];
|
||||||
|
"manatee/komga-bookmanager-credentials.age".publicKeys = [ manatee alex];
|
||||||
|
|
||||||
"backwards/root.backwards.age".publicKeys = [ backwards alex ];
|
"backwards/root.backwards.age".publicKeys = [ backwards alex ];
|
||||||
"backwards/root.backwards.pub.age".publicKeys = [ backwards alex ];
|
"backwards/root.backwards.pub.age".publicKeys = [ backwards alex ];
|
||||||
|
|||||||
Reference in New Issue
Block a user