From 913d3d1238f6bda60753e94df164a92cf8e690ca Mon Sep 17 00:00:00 2001 From: Joakim Repomaa Date: Sat, 8 Feb 2025 19:44:52 +0200 Subject: [PATCH] refactor --- hosts/apu/configuration.nix | 2 +- hosts/freun.dev/configuration.nix | 1 + hosts/freun.dev/default.nix | 2 +- hosts/freun.dev/services.nix | 107 +++++++++ hosts/freun.dev/services/default.nix | 39 ---- hosts/freun.dev/services/gotosocial.nix | 47 ---- hosts/freun.dev/services/grafana.nix | 55 ----- hosts/freun.dev/services/gtrackmap.nix | 13 -- hosts/freun.dev/services/immich.nix | 185 ---------------- hosts/freun.dev/services/invidious.nix | 23 -- hosts/freun.dev/services/owncast.nix | 14 -- hosts/freun.dev/services/syncthing.nix | 34 --- hosts/freun.dev/services/tailscale.nix | 7 - hosts/freun.dev/services/vaultwarden.nix | 38 ---- hosts/freun.dev/services/workout-sync.nix | 26 --- hosts/freun.dev/services/workout-tracker.nix | 35 --- modules/default.nix | 3 +- {hosts/freun.dev => modules}/services/bin.nix | 62 ++++-- modules/services/default.nix | 18 ++ modules/services/gotosocial.nix | 61 +++++ modules/services/grafana.nix | 72 ++++++ modules/services/gtrackmap.nix | 26 +++ modules/services/immich.nix | 208 ++++++++++++++++++ modules/services/invidious.nix | 41 ++++ modules/services/owncast.nix | 26 +++ modules/services/syncthing.nix | 35 +++ modules/services/tailscale.nix | 18 ++ modules/services/vaultwarden.nix | 52 +++++ modules/{ => services}/webserver.nix | 4 +- modules/services/workout-sync.nix | 45 ++++ modules/services/workout-tracker.nix | 34 +++ modules/storage-box-mounts.nix | 55 +++++ 32 files changed, 844 insertions(+), 544 deletions(-) create mode 100644 hosts/freun.dev/services.nix delete mode 100644 hosts/freun.dev/services/default.nix delete mode 100644 hosts/freun.dev/services/gotosocial.nix delete mode 100644 hosts/freun.dev/services/grafana.nix delete mode 100644 hosts/freun.dev/services/gtrackmap.nix delete mode 100644 hosts/freun.dev/services/immich.nix delete mode 100644 hosts/freun.dev/services/invidious.nix delete mode 100644 hosts/freun.dev/services/owncast.nix delete mode 100644 hosts/freun.dev/services/syncthing.nix delete mode 100644 hosts/freun.dev/services/tailscale.nix delete mode 100644 hosts/freun.dev/services/vaultwarden.nix delete mode 100644 hosts/freun.dev/services/workout-sync.nix delete mode 100644 hosts/freun.dev/services/workout-tracker.nix rename {hosts/freun.dev => modules}/services/bin.nix (56%) create mode 100644 modules/services/default.nix create mode 100644 modules/services/gotosocial.nix create mode 100644 modules/services/grafana.nix create mode 100644 modules/services/gtrackmap.nix create mode 100644 modules/services/immich.nix create mode 100644 modules/services/invidious.nix create mode 100644 modules/services/owncast.nix create mode 100644 modules/services/syncthing.nix create mode 100644 modules/services/tailscale.nix create mode 100644 modules/services/vaultwarden.nix rename modules/{ => services}/webserver.nix (96%) create mode 100644 modules/services/workout-sync.nix create mode 100644 modules/services/workout-tracker.nix create mode 100644 modules/storage-box-mounts.nix diff --git a/hosts/apu/configuration.nix b/hosts/apu/configuration.nix index 841d690..970f1f2 100644 --- a/hosts/apu/configuration.nix +++ b/hosts/apu/configuration.nix @@ -218,7 +218,7 @@ }; }; - modules.webserver = { + modules.services.webserver = { enable = true; acme.dnsChallenge = true; vHosts."koti.repomaa.com" = { diff --git a/hosts/freun.dev/configuration.nix b/hosts/freun.dev/configuration.nix index 7956bad..0d530b3 100644 --- a/hosts/freun.dev/configuration.nix +++ b/hosts/freun.dev/configuration.nix @@ -21,6 +21,7 @@ boot.loader.efi.canTouchEfiVariables = true; networking.hostName = "freun-dev"; # Define your hostname. + networking.domain = "freun.dev"; networking.useDHCP = false; networking.nftables.enable = true; diff --git a/hosts/freun.dev/default.nix b/hosts/freun.dev/default.nix index dce393d..1311e22 100644 --- a/hosts/freun.dev/default.nix +++ b/hosts/freun.dev/default.nix @@ -6,7 +6,7 @@ in imports = [ ./hardware-configuration.nix ./configuration.nix - ./services + ./services.nix gtrackmap.nixosModules.default ]; } diff --git a/hosts/freun.dev/services.nix b/hosts/freun.dev/services.nix new file mode 100644 index 0000000..188696b --- /dev/null +++ b/hosts/freun.dev/services.nix @@ -0,0 +1,107 @@ +{ pkgs, config, ... }: +let + immichDataDir = "/mnt/storage/immich"; + syncthingDataDir = "/mnt/storage/syncthing"; + smtp = { + host = "horologium.uberspace.de"; + port = 587; + username = "noreply@${config.networking.domain}"; + from = "noreply@${config.networking.domain}"; + heloName = config.networking.domain; + }; +in +{ + services.postgresql.package = pkgs.postgresql_17; + + modules.storageBoxMounts = { + ${immichDataDir} = { + path = "/backup/immich"; + user = "u407959"; + }; + + ${syncthingDataDir} = { + path = "/backup/syncthing"; + user = "u407959"; + uid = config.users.users.syncthing.uid; + gid = config.users.groups.syncthing.gid; + }; + }; + + modules.services = { + vaultwarden = { + enable = true; + subdomain = "pw"; + config = { + YUBICO_CLIENT_ID = 86799; + SMTP_HOST = smtp.host; + SMTP_FROM = smtp.from; + SMTP_FROM_NAME = "Vaultwarden"; + SMTP_USERNAME = smtp.username; + SMTP_PORT = smtp.port; + HELO_NAME = smtp.heloName; + }; + }; + + immich = { + enable = true; + subdomain = "img"; + dataDir = immichDataDir; + storageDirs = [ syncthingDataDir ]; + version = "v1.125.7"; + }; + + syncthing = { + enable = true; + subdomain = "sync"; + dataDir = syncthingDataDir; + }; + + gotosocial = { + enable = true; + subdomain = "social"; + settings = { + smtp-host = smtp.host; + smtp-port = smtp.port; + smtp-username = smtp.username; + smtp-from = smtp.from; + }; + }; + + bin = { + enable = true; + subdomain = "bin"; + }; + + workout-tracker = { + enable = true; + subdomain = "fit"; + }; + + workout-sync = { + enable = true; + subdomain = "ws"; + }; + + invidious = { + enable = true; + subdomain = "vid"; + }; + + grafana = { + enable = true; + subdomain = "graph"; + }; + + gtrackmap = { + enable = true; + subdomain = "trackmap"; + }; + + owncast = { + enable = true; + subdomain = "stream"; + }; + + tailscale.enable = true; + }; +} diff --git a/hosts/freun.dev/services/default.nix b/hosts/freun.dev/services/default.nix deleted file mode 100644 index f3e4d9f..0000000 --- a/hosts/freun.dev/services/default.nix +++ /dev/null @@ -1,39 +0,0 @@ -{ pkgs, ... }: -{ - services.postgresql = { - enable = true; - package = pkgs.postgresql_17; - }; - - virtualisation.podman = { - enable = true; - autoPrune.enable = true; - dockerCompat = true; - defaultNetwork.settings = { - # Required for container networking to be able to use names. - dns_enabled = true; - }; - }; - - virtualisation.oci-containers.backend = "podman"; - - networking.firewall.trustedInterfaces = [ "podman1" ]; - modules.firewall.interfaces.podman1 = [ "dns" ]; - - modules.webserver.enable = true; - - imports = [ - ./vaultwarden.nix - ./immich.nix - ./syncthing.nix - ./invidious.nix - ./grafana.nix - ./gtrackmap.nix - ./owncast.nix - ./tailscale.nix - ./workout-tracker.nix - ./gotosocial.nix - ./bin.nix - ./workout-sync.nix - ]; -} diff --git a/hosts/freun.dev/services/gotosocial.nix b/hosts/freun.dev/services/gotosocial.nix deleted file mode 100644 index 358ce4a..0000000 --- a/hosts/freun.dev/services/gotosocial.nix +++ /dev/null @@ -1,47 +0,0 @@ -{ config, lib, ... }: -let - domain = "freun.dev"; - fqdn = "social.${domain}"; - port = 3500; -in -{ - services.gotosocial = { - enable = true; - environmentFile = "/var/secrets/gotosocial.env"; - settings = { - host = "social.freun.dev"; - account-domain = "freun.dev"; - protocol = "https"; - bind-address = "localhost"; - instance-languages = [ "de" "fi" "en" ]; - instance-inject-mastodon-version = true; - accounts-registration-open = true; - instance-expose-public-timeline = true; - letsencrypt-enabled = false; - smtp-host = "horologium.uberspace.de"; - smtp-port = 587; - smtp-username = "noreply@freun.dev"; - smtp-from = "noreply@freun.dev"; - inherit port; - }; - setupPostgresqlDB = true; - }; - - modules.webserver.vHosts = { - ${domain}.locations = lib.listToAttrs ( - lib.map - (path: { - name = "/.well-known/${path}"; - value.extraConfig = '' - rewrite ^.*$ https://${fqdn}/.well-known/${path} permanent; - ''; - }) [ - "host-meta" - "webfinger" - "nodeinfo" - ] - ); - - "${fqdn}".locations."/".proxyPort = port; - }; -} diff --git a/hosts/freun.dev/services/grafana.nix b/hosts/freun.dev/services/grafana.nix deleted file mode 100644 index 9f7d15c..0000000 --- a/hosts/freun.dev/services/grafana.nix +++ /dev/null @@ -1,55 +0,0 @@ -{ ... }: -let - fqdn = "graph.freun.dev"; - port = 3300; -in -{ - services.grafana = { - enable = true; - - settings = { - server = { - root_url = "https://${fqdn}"; - http_port = port; - }; - - database = { - host = "/var/run/postgresql"; - type = "postgres"; - user = "grafana"; - }; - - smtp = { - enabled = true; - host = "horologium.uberspace.de"; - from_address = "noreply@freun.dev"; - from_name = "Vaultwarden"; - user = "noreply@freun.dev"; - password = "$__file{/var/secrets/smtp-password}"; - }; - }; - }; - - services.prometheus = { - enable = true; - exporters.node.enable = true; - scrapeConfigs = [ - { - job_name = "node"; - static_configs = [ - { targets = [ "localhost:9100" ]; } - ]; - } - ]; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; - - services.postgresql = { - ensureDatabases = [ "grafana" ]; - ensureUsers = [{ - name = "grafana"; - ensureDBOwnership = true; - }]; - }; -} diff --git a/hosts/freun.dev/services/gtrackmap.nix b/hosts/freun.dev/services/gtrackmap.nix deleted file mode 100644 index a3f4f0e..0000000 --- a/hosts/freun.dev/services/gtrackmap.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ ... }: -let - fqdn = "trackmap.freun.dev"; - port = 3200; -in -{ - services.gtrackmap = { - enable = true; - inherit port; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; -} diff --git a/hosts/freun.dev/services/immich.nix b/hosts/freun.dev/services/immich.nix deleted file mode 100644 index ce3bf61..0000000 --- a/hosts/freun.dev/services/immich.nix +++ /dev/null @@ -1,185 +0,0 @@ -{ pkgs, lib, ... }: - -let - fqdn = "img.freun.dev"; - port = 2283; - - immich_version = "v1.125.7"; - storage_dir = "/mnt/storage/syncthing"; - immich_data_dir = "/mnt/storage/immich"; - - volumeServices = names: ( - lib.lists.foldl - (services: name: - services // { - "podman-volume-immich_${name}" = { - path = [ pkgs.podman ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = '' - podman volume inspect immich_${name} || podman volume create immich_${name} - ''; - partOf = [ "podman-compose-immich-root.target" ]; - wantedBy = [ "podman-compose-immich-root.target" ]; - }; - } - ) - { } - names - ); - - containerServices = services: ( - lib.lists.foldl - (acc: { name, volumes ? [ ], ... }: - let - volume_services = map (volume: "podman-volume-immich_${volume}.service") volumes; - dependent_services = [ "podman-network-immich_default.service" ] ++ volume_services; - in - - acc // { - "podman-immich_${name}" = { - serviceConfig = { - Restart = lib.mkOverride 500 "always"; - }; - after = dependent_services; - requires = dependent_services; - partOf = [ - "podman-compose-immich-root.target" - ]; - wantedBy = [ - "podman-compose-immich-root.target" - ]; - }; - } - ) - { } - services - ); -in -{ - # Containers - virtualisation.oci-containers.containers = { - "immich_machine_learning" = { - image = "ghcr.io/immich-app/immich-machine-learning:${immich_version}"; - environmentFiles = [ - "/var/secrets/immich.env" - ]; - volumes = [ - "immich_model_cache:/cache:rw" - ]; - log-driver = "journald"; - extraOptions = [ - "--network-alias=immich-machine-learning" - "--network=immich_default" - ]; - }; - - "immich_postgres" = { - image = "registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0"; - environmentFiles = [ - "/var/secrets/immich.env" - ]; - environment = { - POSTGRES_INITDB_ARGS = "--data-checksums"; - }; - volumes = [ - "immich_db_data:/var/lib/postgresql/data:rw" - ]; - cmd = [ "postgres" "-c" "shared_preload_libraries=vectors.so" "-c" "search_path=\"$user\", public, vectors" "-c" "logging_collector=on" "-c" "max_wal_size=2GB" "-c" "shared_buffers=512MB" "-c" "wal_compression=on" ]; - log-driver = "journald"; - extraOptions = [ - "--network-alias=database" - "--network=immich_default" - ]; - }; - - "immich_redis" = { - image = "registry.hub.docker.com/library/redis:6.2-alpine"; - environmentFiles = [ - "/var/secrets/immich.env" - ]; - log-driver = "journald"; - extraOptions = [ - "--network-alias=redis" - "--network=immich_default" - ]; - }; - - "immich_server" = { - image = "ghcr.io/immich-app/immich-server:${immich_version}"; - environmentFiles = [ - "/var/secrets/immich.env" - ]; - volumes = [ - "/etc/localtime:/etc/localtime:ro" - "${immich_data_dir}:/usr/src/app/upload:rw" - "${storage_dir}:${storage_dir}:ro" - ]; - ports = [ - "${toString port}:2283/tcp" - ]; - dependsOn = [ - "immich_postgres" - "immich_redis" - ]; - log-driver = "journald"; - extraOptions = [ - "--network-alias=immich-server" - "--network=immich_default" - ]; - }; - }; - - systemd.services = (containerServices [ - { name = "machine_learning"; volumes = [ "model_cache" ]; } - { name = "postgres"; volumes = [ "db_data" ]; } - { name = "redis"; } - { name = "server"; } - ]) // { - # Networks - "podman-network-immich_default" = { - path = [ pkgs.podman ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStop = "${pkgs.podman}/bin/podman network rm -f immich_default"; - }; - script = '' - podman network inspect immich_default || podman network create immich_default - ''; - partOf = [ "podman-compose-immich-root.target" ]; - wantedBy = [ "podman-compose-immich-root.target" ]; - }; - } // (volumeServices [ - "db_data" - "model_cache" - ]); - - # Root service - # When started, this will automatically create all resources and start - # the containers. When stopped, this will teardown all resources. - systemd.targets."podman-compose-immich-root" = { - unitConfig = { - Description = "Root target generated by compose2nix."; - }; - wantedBy = [ "multi-user.target" ]; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; - - fileSystems."${immich_data_dir}" = { - device = "//u407959.your-storagebox.de/backup/immich"; - fsType = "cifs"; - options = - let - # this line prevents hanging on network split - automount_opts = "x-systemd.automount,auto,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s"; - - in - [ "${automount_opts},credentials=/var/secrets/smb-storage" ]; - }; - - environment.systemPackages = [ pkgs.cifs-utils ]; -} diff --git a/hosts/freun.dev/services/invidious.nix b/hosts/freun.dev/services/invidious.nix deleted file mode 100644 index cf0d2d1..0000000 --- a/hosts/freun.dev/services/invidious.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ ... }: -let - fqdn = "vid.freun.dev"; - port = 3000; -in -{ - services.invidious = { - enable = true; - domain = fqdn; - address = "127.0.0.1"; - inherit port; - - settings = { - external_port = 443; - db = { - dbname = "invidious"; - user = "invidious"; - }; - }; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; -} diff --git a/hosts/freun.dev/services/owncast.nix b/hosts/freun.dev/services/owncast.nix deleted file mode 100644 index fec2521..0000000 --- a/hosts/freun.dev/services/owncast.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ ... }: -let - fqdn = "stream.freun.dev"; - port = 8080; -in -{ - services.owncast = { - enable = true; - openFirewall = true; - inherit port; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; -} diff --git a/hosts/freun.dev/services/syncthing.nix b/hosts/freun.dev/services/syncthing.nix deleted file mode 100644 index 06b7e9e..0000000 --- a/hosts/freun.dev/services/syncthing.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ pkgs, config, lib, ... }: -let - fqdn = "sync.freun.dev"; - port = 8384; - - storage_dir = "/mnt/storage/syncthing"; -in -{ - services.syncthing = { - enable = true; - dataDir = "/mnt/storage/syncthing"; - openDefaultPorts = true; - guiAddress = "[::1]:${toString port}"; - settings.gui.insecureSkipHostCheck = true; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; - - fileSystems."${storage_dir}" = { - device = "//u407959.your-storagebox.de/backup/syncthing"; - fsType = "cifs"; - options = - let - # this line prevents hanging on network split - automount_opts = "x-systemd.automount,auto,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s"; - uid = builtins.toString config.users.users.syncthing.uid; - gid = builtins.toString config.users.groups.syncthing.gid; - in - [ "${automount_opts},credentials=/var/secrets/smb-storage,uid=${uid},gid=${gid}" ]; - }; - - - environment.systemPackages = [ pkgs.cifs-utils ]; -} diff --git a/hosts/freun.dev/services/tailscale.nix b/hosts/freun.dev/services/tailscale.nix deleted file mode 100644 index 31f2a3e..0000000 --- a/hosts/freun.dev/services/tailscale.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ ... }: -{ - services.tailscale = { - enable = true; - useRoutingFeatures = "server"; - }; -} diff --git a/hosts/freun.dev/services/vaultwarden.nix b/hosts/freun.dev/services/vaultwarden.nix deleted file mode 100644 index 461bb25..0000000 --- a/hosts/freun.dev/services/vaultwarden.nix +++ /dev/null @@ -1,38 +0,0 @@ -{ lib, ... }: -let - fqdn = "pw.freun.dev"; - port = 8000; -in -{ - services.vaultwarden = { - enable = true; - dbBackend = "postgresql"; - environmentFile = "/var/secrets/vaultwarden.env"; - config = { - DOMAIN = "https://${fqdn}"; - DATABASE_URL = "postgres://%2Fvar%2Frun%2Fpostgresql/vaultwarden"; - WEBSOCKET_ENABLED = true; - SIGNUPS_VERIFY = true; - PASSWORD_ITERATIONS = 600000; - YUBICO_CLIENT_ID = 86799; - SMTP_HOST = "horologium.uberspace.de"; - SMTP_FROM = "noreply@freun.dev"; - SMTP_FROM_NAME = "Vaultwarden"; - SMTP_USERNAME = "noreply@freun.dev"; - SMTP_PORT = 587; - HELO_NAME = "freun.dev"; - ROCKET_LIMITS = "{json=10485760}"; - ROCKET_PORT = port; - }; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; - - services.postgresql = { - ensureDatabases = [ "vaultwarden" ]; - ensureUsers = [{ - name = "vaultwarden"; - ensureDBOwnership = true; - }]; - }; -} diff --git a/hosts/freun.dev/services/workout-sync.nix b/hosts/freun.dev/services/workout-sync.nix deleted file mode 100644 index fa2eeaa..0000000 --- a/hosts/freun.dev/services/workout-sync.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ pkgs, inputs, config, ... }: -let - fqdn = "ws.freun.dev"; - port = 3344; - workout-sync = inputs.workout-sync.packages.${pkgs.system}.default; -in -{ - systemd.services.workout-sync = { - enable = true; - description = "Workout sync service"; - environment = { - PORT = toString port; - WORKOUT_TRACKER_URL = "http://localhost:${toString config.services.workout-tracker.port}"; - }; - serviceConfig = { - ExecStart = "${workout-sync}/bin/workout-sync"; - Restart = "always"; - DynamicUser = true; - EnvironmentFile = "/var/secrets/workout-sync.env"; - }; - wantedBy = [ "multi-user.target" ]; - confinement.enable = true; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; -} diff --git a/hosts/freun.dev/services/workout-tracker.nix b/hosts/freun.dev/services/workout-tracker.nix deleted file mode 100644 index 07b3fb9..0000000 --- a/hosts/freun.dev/services/workout-tracker.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ pkgs, ... }: -let - fqdn = "fit.freun.dev"; - port = 3322; - version = "2.0.3"; -in -{ - services.workout-tracker = { - enable = true; - inherit port; - package = pkgs.stdenv.mkDerivation { - name = "workout-tracker"; - src = pkgs.fetchurl { - url = "https://github.com/jovandeginste/workout-tracker/releases/download/v${version}/workout-tracker-v${version}-linux-arm64.tar.gz"; - hash = "sha256-k6Fq9emrUgGr29d5AaWJtyjGoftGN9IpTgdTOdvOE1o="; - }; - nativeBuildInputs = [ - pkgs.autoPatchelfHook - ]; - sourceRoot = "."; - installPhase = '' - runHook preInstall - install -m755 -D workout-tracker $out/bin/workout-tracker - runHook postInstall - ''; - meta = with pkgs.lib; { - description = "A simple workout tracker"; - license = licenses.mit; - mainProgram = "workout-tracker"; - }; - }; - }; - - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; -} diff --git a/modules/default.nix b/modules/default.nix index 315dfc8..0b6b0f2 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -3,6 +3,7 @@ imports = [ ./vlans.nix ./firewall.nix - ./webserver.nix + ./storage-box-mounts.nix + ./services ]; } diff --git a/hosts/freun.dev/services/bin.nix b/modules/services/bin.nix similarity index 56% rename from hosts/freun.dev/services/bin.nix rename to modules/services/bin.nix index 6de5379..0b436af 100644 --- a/hosts/freun.dev/services/bin.nix +++ b/modules/services/bin.nix @@ -1,10 +1,10 @@ -{ pkgs, ... }: +{ lib, pkgs, config, ... }: let - fqdn = "bin.freun.dev"; - port = 3600; - config = (pkgs.formats.toml { }).generate "rustypaste-config.toml" { + cfg = config.modules.services.bin; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; + rustypasteConfig = (pkgs.formats.toml { }).generate "rustypaste-config.toml" { server = { - address = "[::1]:${toString port}"; + address = "[::1]:${toString cfg.port}"; max_content_length = "1GB"; upload_path = "/var/lib/rustypaste/uploads"; timeout = "5m"; @@ -48,26 +48,42 @@ let }; in { - systemd.services.rustypaste = { - enable = true; - description = "Rustypaste pastebin"; - environment = { - CONFIG = config; - AUTH_TOKENS_FILE = "/var/secrets/rustypaste-tokens"; + options.modules.services.bin = { + enable = lib.mkEnableOption "Enable Rustypaste"; + subdomain = lib.mkOption { + type = lib.types.str; }; - serviceConfig = { - ExecStart = "${pkgs.rustypaste}/bin/rustypaste"; - WorkingDirectory = "/var/lib/rustypaste"; - StateDirectory = "rustypaste"; - DynamicUser = true; - BindReadOnlyPaths = [ "/var/secrets/rustypaste-tokens" ]; - }; - wantedBy = [ "multi-user.target" ]; - confinement = { - enable = true; - packages = [ config ]; + port = lib.mkOption { + type = lib.types.int; + default = 3600; }; }; - modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port; + config = lib.mkIf cfg.enable { + systemd.services.rustypaste = { + enable = true; + description = "Rustypaste pastebin"; + environment = { + CONFIG = rustypasteConfig; + AUTH_TOKENS_FILE = "/var/secrets/rustypaste-tokens"; + }; + serviceConfig = { + ExecStart = "${pkgs.rustypaste}/bin/rustypaste"; + WorkingDirectory = "/var/lib/rustypaste"; + StateDirectory = "rustypaste"; + DynamicUser = true; + BindReadOnlyPaths = [ "/var/secrets/rustypaste-tokens" ]; + }; + wantedBy = [ "multi-user.target" ]; + confinement = { + enable = true; + packages = [ rustypasteConfig ]; + }; + }; + + modules.services.webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; + }; } diff --git a/modules/services/default.nix b/modules/services/default.nix new file mode 100644 index 0000000..c6f380e --- /dev/null +++ b/modules/services/default.nix @@ -0,0 +1,18 @@ +{ ... }: +{ + imports = [ + ./webserver.nix + ./vaultwarden.nix + ./immich.nix + ./syncthing.nix + ./invidious.nix + ./grafana.nix + ./gtrackmap.nix + ./owncast.nix + ./tailscale.nix + ./workout-tracker.nix + ./gotosocial.nix + ./bin.nix + ./workout-sync.nix + ]; +} diff --git a/modules/services/gotosocial.nix b/modules/services/gotosocial.nix new file mode 100644 index 0000000..5836536 --- /dev/null +++ b/modules/services/gotosocial.nix @@ -0,0 +1,61 @@ +{ config, lib, ... }: +let + cfg = config.modules.services.gotosocial; + domain = config.networking.domain; + fqdn = "${cfg.subdomain}.${domain}"; + port = cfg.port; +in +{ + options.modules.services.gotosocial = { + enable = lib.mkEnableOption "Enable Gotosocial"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + port = lib.mkOption { + type = lib.types.int; + default = 3500; + }; + settings = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + services.gotosocial = { + enable = true; + environmentFile = "/var/secrets/gotosocial.env"; + settings = { + host = fqdn; + account-domain = domain; + protocol = "https"; + bind-address = "localhost"; + instance-languages = [ "de" "fi" "en" ]; + instance-inject-mastodon-version = true; + accounts-registration-open = true; + instance-expose-public-timeline = true; + letsencrypt-enabled = false; + inherit port; + } // cfg.settings; + setupPostgresqlDB = true; + }; + + modules.services.webserver.vHosts = { + ${domain}.locations = lib.listToAttrs ( + lib.map + (path: { + name = "/.well-known/${path}"; + value.extraConfig = '' + rewrite ^.*$ https://${fqdn}/.well-known/${path} permanent; + ''; + }) [ + "host-meta" + "webfinger" + "nodeinfo" + ] + ); + + "${fqdn}".locations."/".proxyPort = port; + }; + }; +} diff --git a/modules/services/grafana.nix b/modules/services/grafana.nix new file mode 100644 index 0000000..1031873 --- /dev/null +++ b/modules/services/grafana.nix @@ -0,0 +1,72 @@ +{ lib, config, ... }: +let + cfg = config.modules.services.grafana; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; +in +{ + options.modules.services.grafana = { + enable = lib.mkEnableOption "Enable Grafana"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + config = lib.mkOption { + type = lib.types.attrs; + }; + port = lib.mkOption { + type = lib.types.int; + default = 3300; + }; + }; + + config = lib.mkIf cfg.enable { + services.grafana = { + enable = true; + + settings = { + server = { + root_url = "https://${fqdn}"; + http_port = cfg.port; + }; + + database = { + host = "/var/run/postgresql"; + type = "postgres"; + user = "grafana"; + }; + + smtp = { + enabled = true; + host = "horologium.uberspace.de"; + from_address = "noreply@freun.dev"; + from_name = "Vaultwarden"; + user = "noreply@freun.dev"; + password = "$__file{/var/secrets/smtp-password}"; + }; + }; + }; + + services.prometheus = { + enable = true; + exporters.node.enable = true; + scrapeConfigs = [ + { + job_name = "node"; + static_configs = [ + { targets = [ "localhost:9100" ]; } + ]; + } + ]; + }; + + modules.services.webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + + services.postgresql = { + enable = lib.mkDefault true; + ensureDatabases = [ "grafana" ]; + ensureUsers = [{ + name = "grafana"; + ensureDBOwnership = true; + }]; + }; + }; +} diff --git a/modules/services/gtrackmap.nix b/modules/services/gtrackmap.nix new file mode 100644 index 0000000..a3ecebf --- /dev/null +++ b/modules/services/gtrackmap.nix @@ -0,0 +1,26 @@ +{ lib, config, ... }: +let + cfg = config.modules.services.gtrackmap; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; +in +{ + options.modules.services.gtrackmap = { + enable = lib.mkEnableOption "Enable GTrackmap"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + port = lib.mkOption { + type = lib.types.int; + default = 3200; + }; + }; + + config = { + services.gtrackmap = { + enable = true; + inherit (cfg) port; + }; + + modules.services.webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; +} diff --git a/modules/services/immich.nix b/modules/services/immich.nix new file mode 100644 index 0000000..09322cd --- /dev/null +++ b/modules/services/immich.nix @@ -0,0 +1,208 @@ +{ pkgs, lib, config, ... }: +let + cfg = config.modules.services.immich; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; + + volumeServices = names: ( + lib.lists.foldl + (services: name: + services // { + "podman-volume-immich_${name}" = { + path = [ pkgs.podman ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + podman volume inspect immich_${name} || podman volume create immich_${name} + ''; + partOf = [ "podman-compose-immich-root.target" ]; + wantedBy = [ "podman-compose-immich-root.target" ]; + }; + } + ) + { } + names + ); + + containerServices = services: ( + lib.lists.foldl + (acc: { name, volumes ? [ ], ... }: + let + volume_services = map (volume: "podman-volume-immich_${volume}.service") volumes; + dependent_services = [ "podman-network-immich_default.service" ] ++ volume_services; + in + + acc // { + "podman-immich_${name}" = { + serviceConfig = { + Restart = lib.mkOverride 500 "always"; + }; + after = dependent_services; + requires = dependent_services; + partOf = [ + "podman-compose-immich-root.target" + ]; + wantedBy = [ + "podman-compose-immich-root.target" + ]; + }; + } + ) + { } + services + ); +in +{ + options.modules.services.immich = { + enable = lib.mkEnableOption "Enable Immich"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + version = lib.mkOption { + type = lib.types.str; + default = "latest"; + }; + port = lib.mkOption { + type = lib.types.int; + default = 2283; + }; + dataDir = lib.mkOption { + type = lib.types.str; + }; + storageDirs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + }; + + config = lib.mkIf cfg.enable + { + virtualisation.podman = { + enable = true; + autoPrune.enable = true; + dockerCompat = true; + defaultNetwork.settings = { + # Required for container networking to be able to use names. + dns_enabled = true; + }; + }; + + virtualisation.oci-containers.backend = "podman"; + + networking.firewall.trustedInterfaces = [ "podman1" ]; + modules.firewall.interfaces.podman1 = [ "dns" ]; + + # Containers + virtualisation.oci-containers.containers = { + "immich_machine_learning" = { + image = "ghcr.io/immich-app/immich-machine-learning:${cfg.version}"; + environmentFiles = [ + "/var/secrets/immich.env" + ]; + volumes = [ + "immich_model_cache:/cache:rw" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=immich-machine-learning" + "--network=immich_default" + ]; + }; + + "immich_postgres" = { + image = "registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0"; + environmentFiles = [ + "/var/secrets/immich.env" + ]; + environment = { + POSTGRES_INITDB_ARGS = "--data-checksums"; + }; + volumes = [ + "immich_db_data:/var/lib/postgresql/data:rw" + ]; + cmd = [ "postgres" "-c" "shared_preload_libraries=vectors.so" "-c" "search_path=\"$user\", public, vectors" "-c" "logging_collector=on" "-c" "max_wal_size=2GB" "-c" "shared_buffers=512MB" "-c" "wal_compression=on" ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=database" + "--network=immich_default" + ]; + }; + + "immich_redis" = { + image = "registry.hub.docker.com/library/redis:6.2-alpine"; + environmentFiles = [ + "/var/secrets/immich.env" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=redis" + "--network=immich_default" + ]; + }; + + "immich_server" = { + image = "ghcr.io/immich-app/immich-server:${cfg.version}"; + environmentFiles = [ + "/var/secrets/immich.env" + ]; + volumes = [ + "/etc/localtime:/etc/localtime:ro" + "${cfg.dataDir}:/usr/src/app/upload:rw" + ] ++ map (dir: "${dir}:${dir}:ro") cfg.storageDirs; + ports = [ + "${toString cfg.port}:2283/tcp" + ]; + dependsOn = [ + "immich_postgres" + "immich_redis" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=immich-server" + "--network=immich_default" + ]; + }; + }; + + systemd.services = (containerServices [ + { name = "machine_learning"; volumes = [ "model_cache" ]; } + { name = "postgres"; volumes = [ "db_data" ]; } + { name = "redis"; } + { name = "server"; } + ]) // { + # Networks + "podman-network-immich_default" = { + path = [ pkgs.podman ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "${pkgs.podman}/bin/podman network rm -f immich_default"; + }; + script = '' + podman network inspect immich_default || podman network create immich_default + ''; + partOf = [ "podman-compose-immich-root.target" ]; + wantedBy = [ "podman-compose-immich-root.target" ]; + }; + } // (volumeServices [ + "db_data" + "model_cache" + ]); + + # Root service + # When started, this will automatically create all resources and start + # the containers. When stopped, this will teardown all resources. + systemd.targets."podman-compose-immich-root" = { + unitConfig = { + Description = "Root target generated by compose2nix."; + }; + wantedBy = [ "multi-user.target" ]; + }; + + modules.services.webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; + }; +} diff --git a/modules/services/invidious.nix b/modules/services/invidious.nix new file mode 100644 index 0000000..a64aa2f --- /dev/null +++ b/modules/services/invidious.nix @@ -0,0 +1,41 @@ +{ config, lib, ... }: +let + cfg = config.modules.services.invidious; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; +in +{ + options.modules.services.invidious = { + enable = lib.mkEnableOption "Enable Invidious"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + port = lib.mkOption { + type = lib.types.int; + default = 3000; + }; + }; + + config = lib.mkIf cfg.enable { + services.invidious = { + enable = true; + domain = fqdn; + address = "127.0.0.1"; + inherit (cfg) port; + + settings = { + external_port = 443; + db = { + dbname = "invidious"; + user = "invidious"; + }; + }; + }; + + services.postgresql.enable = lib.mkDefault true; + + modules.services.webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; + }; +} diff --git a/modules/services/owncast.nix b/modules/services/owncast.nix new file mode 100644 index 0000000..2ac1a0b --- /dev/null +++ b/modules/services/owncast.nix @@ -0,0 +1,26 @@ +{ lib, config, ... }: +let + cfg = config.modules.services.owncast; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; +in +{ + options.modules.services.owncast = { + enable = lib.mkEnableOption "Enable Owncast"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + port = lib.mkOption { + type = lib.types.int; + default = 8080; + }; + }; + + config = lib.mkIf cfg.enable { + services.owncast = { + enable = true; + openFirewall = true; + inherit (cfg) port; + }; + modules.services.webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; +} diff --git a/modules/services/syncthing.nix b/modules/services/syncthing.nix new file mode 100644 index 0000000..ae04b15 --- /dev/null +++ b/modules/services/syncthing.nix @@ -0,0 +1,35 @@ +{ config, lib, ... }: +let + cfg = config.modules.services.syncthing; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; +in +{ + options.modules.services.syncthing = { + enable = lib.mkEnableOption "Enable Syncthing"; + dataDir = lib.mkOption { + type = lib.types.str; + }; + subdomain = lib.mkOption { + type = lib.types.str; + }; + port = lib.mkOption { + type = lib.types.int; + default = 8384; + }; + }; + + config = lib.mkIf cfg.enable { + services.syncthing = { + enable = true; + dataDir = cfg.dataDir; + openDefaultPorts = true; + guiAddress = "[::1]:${toString cfg.port}"; + settings.gui.insecureSkipHostCheck = true; + }; + + modules.services.webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; + }; +} diff --git a/modules/services/tailscale.nix b/modules/services/tailscale.nix new file mode 100644 index 0000000..713f5d6 --- /dev/null +++ b/modules/services/tailscale.nix @@ -0,0 +1,18 @@ +{ lib, config, ... }: +let + cfg = config.modules.services.tailscale; +in +{ + options = { + modules.services.tailscale = { + enable = lib.mkEnableOption "Enable Tailscale"; + }; + }; + + config = lib.mkIf cfg.enable { + services.tailscale = { + enable = true; + useRoutingFeatures = "server"; + }; + }; +} diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix new file mode 100644 index 0000000..f0ffa5a --- /dev/null +++ b/modules/services/vaultwarden.nix @@ -0,0 +1,52 @@ +{ lib, config, ... }: +let + cfg = config.modules.services.vaultwarden; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; + port = config.services.vaultwarden.config.ROCKET_PORT; +in +{ + options.modules.services.vaultwarden = { + enable = lib.mkEnableOption "Enable Vaultwarden"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + secrets = lib.mkOption { + type = lib.types.str; + }; + config = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + services.vaultwarden = { + enable = true; + dbBackend = "postgresql"; + environmentFile = "/var/secrets/vaultwarden.env"; + config = { + DOMAIN = "https://${fqdn}"; + DATABASE_URL = "postgres://%2Fvar%2Frun%2Fpostgresql/vaultwarden"; + WEBSOCKET_ENABLED = true; + SIGNUPS_VERIFY = true; + PASSWORD_ITERATIONS = 600000; + ROCKET_LIMITS = "{json=10485760}"; + ROCKET_PORT = 8000; + } // cfg.config; + }; + + modules.services.webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = port; + }; + + services.postgresql = { + enable = lib.mkDefault true; + ensureDatabases = [ "vaultwarden" ]; + ensureUsers = [{ + name = "vaultwarden"; + ensureDBOwnership = true; + }]; + }; + }; +} diff --git a/modules/webserver.nix b/modules/services/webserver.nix similarity index 96% rename from modules/webserver.nix rename to modules/services/webserver.nix index b4ffcc8..d8dbea0 100644 --- a/modules/webserver.nix +++ b/modules/services/webserver.nix @@ -1,6 +1,6 @@ { lib, config, ... }: let - cfg = config.modules.webserver; + cfg = config.modules.services.webserver; types = { location = lib.types.submodule { @@ -30,7 +30,7 @@ let }; in { - options.modules.webserver = { + options.modules.services.webserver = { enable = lib.mkEnableOption "Enable nginx"; acme = { dnsChallenge = lib.mkEnableOption "Enable DNS challenge"; diff --git a/modules/services/workout-sync.nix b/modules/services/workout-sync.nix new file mode 100644 index 0000000..7337814 --- /dev/null +++ b/modules/services/workout-sync.nix @@ -0,0 +1,45 @@ +{ pkgs, inputs, config, lib, ... }: +let + cfg = config.modules.services.workout-sync; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; + port = cfg.port; + workout-sync = inputs.workout-sync.packages.${pkgs.system}.default; +in +{ + options.modules.services.workout-sync = { + enable = lib.mkEnableOption "Enable Workout Sync"; + subdomain = lib.mkOption { + type = lib.types.str; + }; + port = lib.mkOption { + type = lib.types.int; + default = 3344; + }; + }; + + config = lib.mkIf cfg.enable { + modules.services.workout-tracker.enable = lib.mkDefault true; + + systemd.services.workout-sync = { + enable = true; + description = "Workout sync service"; + environment = { + PORT = toString port; + WORKOUT_TRACKER_URL = "http://localhost:${toString config.services.workout-tracker.port}"; + }; + serviceConfig = { + ExecStart = "${workout-sync}/bin/workout-sync"; + Restart = "always"; + DynamicUser = true; + EnvironmentFile = "/var/secrets/workout-sync.env"; + }; + wantedBy = [ "multi-user.target" ]; + confinement.enable = true; + }; + + modules.services.webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = port; + }; + }; +} diff --git a/modules/services/workout-tracker.nix b/modules/services/workout-tracker.nix new file mode 100644 index 0000000..31a5c7e --- /dev/null +++ b/modules/services/workout-tracker.nix @@ -0,0 +1,34 @@ +{ pkgs, config, lib, inputs, ... }: +let + cfg = config.modules.services.workout-tracker; + fqdn = "${cfg.subdomain}.${config.networking.domain}"; + port = cfg.port; +in +{ + options.modules.services.workout-tracker = { + enable = lib.mkEnableOption "Enable Workout Tracker"; + version = lib.mkOption { + type = lib.types.str; + }; + subdomain = lib.mkOption { + type = lib.types.str; + }; + port = lib.mkOption { + type = lib.types.int; + default = 3322; + }; + }; + + config = lib.mkIf cfg.enable { + services.workout-tracker = { + enable = true; + inherit port; + package = inputs.nixpkgs-unstable.legacyPackages.${pkgs.system}.workout-tracker; + }; + + modules.services.webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = port; + }; + }; +} diff --git a/modules/storage-box-mounts.nix b/modules/storage-box-mounts.nix new file mode 100644 index 0000000..0f7bf2d --- /dev/null +++ b/modules/storage-box-mounts.nix @@ -0,0 +1,55 @@ +{ lib, config, pkgs, ... }: +let + types = { + mount = lib.types.submodule { + options = { + path = lib.mkOption { + type = lib.types.str; + default = "/"; + }; + user = lib.mkOption { + type = lib.types.str; + }; + uid = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + }; + gid = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + }; + }; + }; + }; + + cfg = config.modules.storageBoxMounts; + mountOptions = { uid, gid, ... }: [ + "x-systemd.automount" + "auto" + "x-systemd.device-timeout=5s" + "x-systemd.mount-timeout=5s" + "credentials=/var/secrets/storage-box-credentials" + ] ++ ( + if (uid != null) then [ "uid=${toString uid}" ] else [ ] + ) ++ ( + if (gid != null) then [ "gid=${toString gid}" ] else [ ] + ); +in +{ + options.modules.storageBoxMounts = lib.mkOption { + type = lib.types.attrsOf types.mount; + default = { }; + }; + + config = { + fileSystems = lib.mapAttrs + (_: { path, user, ... }@options: { + device = "//${user}.your-storagebox.de${path}"; + fsType = "cifs"; + options = mountOptions options; + }) + cfg; + + environment.systemPackages = lib.mkIf ((lib.length (lib.attrNames cfg)) > 0) [ pkgs.cifs-utils ]; + }; +}