428 lines
14 KiB
Nix
428 lines
14 KiB
Nix
{
|
||
pkgs,
|
||
lib,
|
||
config,
|
||
inputs,
|
||
...
|
||
}: let
|
||
sopsPath = key: config.sops.secrets.${key}.path;
|
||
|
||
mkVirtHost = certificateName:
|
||
lib.attrsets.recursiveUpdate {
|
||
addSSL = true;
|
||
listenAddresses = [vpnIPv4 "[${vpnIPv6}]"];
|
||
sslTrustedCertificate = pkgs.writeText "ca.crt" (builtins.readFile ../secrets/ca.crt);
|
||
sslCertificateKey = sopsPath "certificate-key-${certificateName}";
|
||
sslCertificate = pkgs.writeText "${certificateName}.crt" (builtins.readFile ../secrets/pub/${certificateName}.crt);
|
||
};
|
||
|
||
vpnIPv4 = config.state.vpn.ipv4;
|
||
vpnIPv6 = config.state.vpn.ipv6;
|
||
in {
|
||
imports = [
|
||
inputs.nixos-hardware.nixosModules.common-cpu-intel #-cpu-only
|
||
../modules/nginx-reverse-proxy.nix
|
||
../hardware/asrock-z370-i3-black-box.nix
|
||
];
|
||
config = {
|
||
networking.hostName = "faunus-ater";
|
||
networking.hostId = "a4d7bec4";
|
||
networking.interfaces.eno1.useDHCP = true;
|
||
|
||
# === Make sure ZFS works ===
|
||
# TODO: Update and think of some automatic way of keeping this up to date.
|
||
boot.kernelPackages = pkgs.linuxPackages_5_15;
|
||
|
||
# === Can't handle this ===
|
||
systemd.enableEmergencyMode = false;
|
||
|
||
# === Settings ===
|
||
settings.ssh.openOutsideVPN = true;
|
||
|
||
# === ZFS services ===
|
||
services.zfs.trim.enable = true;
|
||
services.zfs.autoScrub.enable = true;
|
||
services.zfs.autoScrub.pools = ["rpool"];
|
||
|
||
# === Additional services ===
|
||
services.fwupd.enable = true;
|
||
powerManagement = {
|
||
enable = true;
|
||
powertop.enable = true;
|
||
cpuFreqGovernor = "powersave";
|
||
};
|
||
|
||
# === Git.home, because everything else sucks ===
|
||
services.gogs = {
|
||
enable = true;
|
||
stateDir = "/data/dirty/gogs";
|
||
appName = "Malte's Secret Git Stash";
|
||
cookieSecure = true;
|
||
database.passwordFile = sopsPath "gogs-database-password";
|
||
httpPort = config.state.services.git.port;
|
||
rootUrl = "https://git.tammena.me/";
|
||
domain = "git.tammena.me";
|
||
# FIXME: Remove after upstream fix of database type
|
||
extraConfig = ''
|
||
[database]
|
||
TYPE = sqlite3
|
||
|
||
[auth]
|
||
DISABLE_REGISTRATION = true
|
||
SHOW_REGISTRATION_BUTTON = false
|
||
|
||
[server]
|
||
SSH_PORT = 22222
|
||
'';
|
||
};
|
||
services.nginx.virtualHosts."git.home" = mkVirtHost "git-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://${config.services.gogs.httpAddress}:${builtins.toString config.services.gogs.httpPort}";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
|
||
virtualisation.oci-containers.backend = "podman";
|
||
virtualisation.podman = {
|
||
enable = true;
|
||
dockerCompat = true;
|
||
extraPackages = with pkgs; [zfs];
|
||
};
|
||
# Override storage driver
|
||
virtualisation.containers.storage.settings = {
|
||
storage = {
|
||
driver = "zfs";
|
||
graphroot = "/var/lib/containers/storage";
|
||
runroot = "/run/containers/storage";
|
||
};
|
||
};
|
||
|
||
virtualisation.oci-containers.containers."timetagger" = {
|
||
image = "ghcr.io/almarklein/timetagger:v23.2.1";
|
||
ports = ["5873:5873"];
|
||
environment = {
|
||
TIMETAGGER_BIND = "0.0.0.0:5873";
|
||
TIMETAGGER_DATADIR = "/root/_timetagger";
|
||
TIMETAGGER_LOG_LEVEL = "info";
|
||
TIMETAGGER_CREDENTIALS = "malte:$2a$08$P.e3SD0cnPK0P4mFYShELuoa37.1e1dEqE8MWa6LJ/kSJfje1BdBi,marie:$2a$08$ubOZWO510y5bgwIl0O4Ne.dKZdWoHqEMzvs56L6esqvLfBJ/6OgYm";
|
||
};
|
||
volumes = [
|
||
"/data/dirty/timetagger:/root/_timetagger"
|
||
];
|
||
};
|
||
services.nginx.virtualHosts."time.home" = mkVirtHost "time-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://127.0.0.1:5873";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
|
||
services.nginx.virtualHosts."todo.home" = mkVirtHost "todo-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://127.0.0.1:7372";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
|
||
services.nginx.virtualHosts."support.home" = mkVirtHost "support-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://127.0.0.1:9999";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
|
||
services.nginx.virtualHosts."config.home" = mkVirtHost "config-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://127.0.0.1:8123";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
virtualisation.oci-containers.containers.home-assistant = {
|
||
volumes = ["/data/dirty/home-assistant:/config"];
|
||
environment.TZ = "Europe/Berlin";
|
||
image = "ghcr.io/home-assistant/home-assistant:2023.9";
|
||
ports = [
|
||
"8123:8123"
|
||
"1400:1400/tcp"
|
||
];
|
||
extraOptions = [
|
||
# TODO: Fix the path of the zigbee controller using udev
|
||
"--device=/dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0001-if00-port0"
|
||
"--device=/dev/ttyUSB0"
|
||
"--cap-add=CAP_NET_RAW,CAP_NET_BIND_SERVICE"
|
||
];
|
||
};
|
||
# For SONOS
|
||
networking.firewall.allowedTCPPorts = [1400];
|
||
|
||
# === HYDRA & Friends. ===
|
||
services.hydra = {
|
||
enable = true;
|
||
package = pkgs.hydra;
|
||
notificationSender = "hydra@home";
|
||
hydraURL = "http://faunus-ater:${builtins.toString config.services.hydra.port}";
|
||
minimumDiskFree = 10;
|
||
useSubstitutes = true;
|
||
};
|
||
services.nix-serve = {
|
||
enable = true;
|
||
secretKeyFile = sopsPath "nix-store-signing-key";
|
||
# FIXME: Remove once fixed upstream
|
||
package = pkgs.nix-serve.override {
|
||
nix = pkgs.nixVersions.nix_2_12;
|
||
};
|
||
};
|
||
# Build on other machines aswell if possible
|
||
nix.buildMachines = [
|
||
{
|
||
hostName = "localhost";
|
||
maxJobs = 4;
|
||
speedFactor = 1;
|
||
sshKey = sopsPath "hydra-overseer-key";
|
||
sshUser = "hydra-minion";
|
||
systems = ["x86_64-linux" "i686-linux"];
|
||
}
|
||
{
|
||
hostName = "helix-texta";
|
||
maxJobs = 4;
|
||
speedFactor = 2;
|
||
sshKey = sopsPath "hydra-overseer-key";
|
||
sshUser = "hydra-minion";
|
||
supportedFeatures = ["kvm" "big-parallel"];
|
||
systems = ["x86_64-linux" "i686-linux"];
|
||
}
|
||
{
|
||
hostName = "murex-pecten";
|
||
maxJobs = 4;
|
||
speedFactor = 4;
|
||
sshKey = sopsPath "hydra-overseer-key";
|
||
sshUser = "hydra-minion";
|
||
supportedFeatures = ["kvm" "big-parallel"];
|
||
systems = ["x86_64-linux" "i686-linux"];
|
||
}
|
||
];
|
||
# TODO: This doesn't seem to work
|
||
programs.ssh.extraConfig = ''
|
||
Host *
|
||
StrictHostKeyChecking accept-new
|
||
'';
|
||
nix.extraOptions = ''
|
||
allowed-uris = http:// https://
|
||
'';
|
||
systemd.services."hydra-initial-setup" = {
|
||
description = "Setup hydra admin password once";
|
||
serviceConfig = {
|
||
Type = "oneshot";
|
||
RemainAfterExit = true;
|
||
LoadCredential = "USER_PW:${sopsPath "hydra-admin-password"}";
|
||
};
|
||
wantedBy = lib.singleton "multi-user.target";
|
||
requires = lib.singleton "hydra-init.service";
|
||
after = lib.singleton "hydra-init.service";
|
||
environment = {
|
||
inherit (config.systemd.services.hydra-init.environment) HYDRA_DBI;
|
||
};
|
||
script = let
|
||
hydra-create-user = "${pkgs.hydra}/bin/hydra-create-user";
|
||
in ''
|
||
if [ ! -e ~hydra/.setup-is-complete ]; then
|
||
# create admin user
|
||
${hydra-create-user} admin --full-name 'Admin Mc. Admining' --email-address 'admin@faunus-ater' --password "$USER_PW" --role admin || exit 1
|
||
# done
|
||
touch ~hydra/.setup-is-complete
|
||
fi
|
||
'';
|
||
};
|
||
services.nginx.virtualHosts = {
|
||
"hydra.home" = mkVirtHost "hydra-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://localhost:${builtins.toString config.services.hydra.port}";
|
||
};
|
||
};
|
||
"cache.home" = mkVirtHost "cache-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://localhost:${builtins.toString config.services.nix-serve.port}";
|
||
};
|
||
};
|
||
};
|
||
|
||
# === PAPERLESS service, save me! ===
|
||
services.paperless = {
|
||
enable = true;
|
||
address = "[::1]";
|
||
passwordFile = sopsPath "paperless-admin-password";
|
||
dataDir = "/data/dirty/paperless";
|
||
extraConfig = {
|
||
PAPERLESS_CONSUMER_DELETE_DUPLICATES = true;
|
||
PAPERLESS_CONSUMER_RECURSIVE = true;
|
||
PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS = true;
|
||
PAPERLESS_FILENAME_FORMAT = "{created_year}/{correspondent}/{created_year}-{created_month}-{created_day}-{document_type}-{title}-{tag_list}";
|
||
PAPERLESS_OCR_LANGUAGE = "deu";
|
||
PAPERLESS_URL = "https://doc.home";
|
||
};
|
||
};
|
||
services.nginx.virtualHosts."doc.home" = mkVirtHost "doc-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://[::1]:${builtins.toString config.services.paperless.port}";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
|
||
# === Komga, for my reading needs ===
|
||
services.komga = {
|
||
enable = true;
|
||
port = config.state.services.read.port;
|
||
stateDir = "/data/dirty/komga";
|
||
};
|
||
services.nginx.virtualHosts."read.home" = mkVirtHost "read-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://[::1]:${builtins.toString config.services.komga.port}";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
|
||
# === Trilium ===
|
||
services.trilium-server = {
|
||
enable = true;
|
||
port = 10302;
|
||
dataDir = "/data/dirty/trilium";
|
||
};
|
||
services.nginx.virtualHosts."note.home" = mkVirtHost "note-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://${config.services.trilium-server.host}:${builtins.toString config.services.trilium-server.port}";
|
||
proxyWebsockets = true;
|
||
};
|
||
};
|
||
|
||
# === Photoprism ===
|
||
services.photoprism = {
|
||
enable = true;
|
||
port = 2342;
|
||
storagePath = "/data/dirty/photoprism/storage";
|
||
originalsPath = "/data/dirty/photoprism/originals";
|
||
importPath = "/data/dirty/photoprism/import";
|
||
passwordFile = sopsPath "photoprism-admin-password";
|
||
settings = {
|
||
PHOTOPRISM_SESSION_MAXAGE = "31536000";
|
||
PHOTOPRISM_SESSION_TIMEOUT = "31536000";
|
||
PHOTOPRISM_UPLOAD_NSFW = "true";
|
||
PHOTOPRISM_DETECT_NSFW = "true";
|
||
PHOTOPRISM_SITE_URL = "https://foto.home";
|
||
PHOTOPRISM_SITE_TITLE = "PhotoPrism";
|
||
PHOTOPRISM_SITE_CAPTION = "All the pictures!";
|
||
PHOTOPRISM_SITE_DESCRIPTION = "";
|
||
PHOTOPRISM_SITE_AUTHOR = "";
|
||
};
|
||
};
|
||
# TODO: Why does it not work without these? :/
|
||
systemd.services.photoprism.serviceConfig.User = lib.mkForce null;
|
||
systemd.services.photoprism.serviceConfig.Group = lib.mkForce null;
|
||
systemd.services.photoprism.serviceConfig.DynamicUser = lib.mkForce false;
|
||
systemd.services.photoprism.serviceConfig.SystemCallFilter = lib.mkForce [];
|
||
services.nginx.virtualHosts."foto.home" = mkVirtHost "foto-home" {
|
||
locations."/" = {
|
||
proxyPass = "http://localhost:${builtins.toString config.services.photoprism.port}";
|
||
proxyWebsockets = true;
|
||
};
|
||
extraConfig = ''
|
||
client_max_body_size 500M;
|
||
'';
|
||
};
|
||
|
||
# === Restic User Backup ===
|
||
services.resticConfigured = {
|
||
enable = true;
|
||
rootDir = "/data/dirty/restic";
|
||
openFirewall = true;
|
||
};
|
||
|
||
users.users.sftp = {
|
||
description = "User used for all sftp stuff";
|
||
isNormalUser = true;
|
||
group = "sftp";
|
||
openssh.authorizedKeys.keyFiles = [
|
||
../secrets/users/malte/sftp-key.pub
|
||
../secrets/users/marie/sftp-key.pub
|
||
];
|
||
};
|
||
users.groups.sftp = {};
|
||
|
||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||
|
||
# === BACKUPS ===
|
||
services.restic.backups = {
|
||
# Make sure my 'active IO' disk get's saved once a day
|
||
zdirty = {
|
||
initialize = true;
|
||
repository = "/data/archive/dirty.bak";
|
||
timerConfig.OnCalendar = "daily";
|
||
paths = lib.singleton "/data/dirty";
|
||
pruneOpts = [
|
||
"--keep-daily 1"
|
||
"--keep-weekly 1"
|
||
"--keep-monthly 1"
|
||
"--keep-yearly 5"
|
||
];
|
||
passwordFile = sopsPath "internal-restic-password";
|
||
};
|
||
};
|
||
|
||
# === RUNTIME SECRETS ===
|
||
sops.defaultSopsFile = ../secrets/hosts/faunus-ater/secrets.yaml;
|
||
sops.age.sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"];
|
||
sops.secrets = let
|
||
nginxSecret = {
|
||
owner = config.users.users.nginx.name;
|
||
mode = "0400";
|
||
};
|
||
in {
|
||
"certificate-key-config-home" = nginxSecret;
|
||
"certificate-key-todo-home" = nginxSecret;
|
||
"certificate-key-time-home" = nginxSecret;
|
||
"certificate-key-support-home" = nginxSecret;
|
||
"certificate-key-hydra-home" = nginxSecret;
|
||
"certificate-key-cache-home" = nginxSecret;
|
||
"certificate-key-doc-home" = nginxSecret;
|
||
"certificate-key-read-home" = nginxSecret;
|
||
"certificate-key-note-home" = nginxSecret;
|
||
"certificate-key-foto-home" = nginxSecret;
|
||
"certificate-key-listen-home" = nginxSecret;
|
||
"certificate-key-git-home" = nginxSecret;
|
||
"paperless-admin-password" = {};
|
||
"photoprism-admin-password" = {};
|
||
"nginx-cert-key" = nginxSecret;
|
||
"nginx-cert-crt" = nginxSecret;
|
||
"fritzbox-exporter-env" = {};
|
||
"internal-restic-password" = {};
|
||
"nix-store-signing-key" = {};
|
||
"hydra-admin-password" = {
|
||
owner = config.users.users.hydra.name;
|
||
mode = "0400";
|
||
};
|
||
"hydra-overseer-key" = {
|
||
owner = config.users.users.hydra.name;
|
||
mode = "0440";
|
||
};
|
||
"gogs-database-password" = {
|
||
owner = config.users.users.gogs.name;
|
||
mode = "0400";
|
||
};
|
||
};
|
||
|
||
# All services that run here, that should be exposed need to be exposed on the VPN
|
||
networking.firewall.interfaces.${config.services.tailscale.interfaceName}.allowedTCPPorts = let
|
||
selectPort = _: config: config.port;
|
||
filterRunningHereAndExposed = lib.attrsets.filterAttrs (_: conf: conf.host == config.networking.hostName && conf ? external && conf.external);
|
||
in
|
||
lib.attrsets.mapAttrsToList selectPort (filterRunningHereAndExposed config.state.services);
|
||
|
||
# This value determines the NixOS release from which the default
|
||
# settings for stateful data, like file locations and database versions
|
||
# on your system were taken. It‘s perfectly fine and recommended to leave
|
||
# this value at the release version of the first install of this system.
|
||
# Before changing this value read the documentation for this option
|
||
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
|
||
system.stateVersion = "22.05"; # Did you read the comment?
|
||
};
|
||
}
|