From b4ffe6215b6ea0e96c618fa87e0e923290b1c0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Barnouin?= Date: Wed, 14 May 2025 10:55:34 +0200 Subject: [PATCH] First try at docker VM --- secrets.nix | 3 + secrets/docker-lapi-key.age | 14 +++ services/docker/default.nix | 208 ++++++++++++++++++++++++++++++++++ services/jellyfin/default.nix | 7 ++ 4 files changed, 232 insertions(+) create mode 100644 secrets/docker-lapi-key.age create mode 100644 services/docker/default.nix diff --git a/secrets.nix b/secrets.nix index 4fcf4be..a7d41c7 100644 --- a/secrets.nix +++ b/secrets.nix @@ -9,6 +9,7 @@ let forgejo = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILMf3Cc/S0p/LFcW+RLMEqpxOOv8q/HrKO4I9joHmRxl root@forgejo"; nginx = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKX2wkS9bpMy1+ITPtQclRkthOwksWBZOLa3bT9oLAe1 root@nixos-nginx"; jellyfin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBiJb+U6LQ3KglTJqdUzwCVkKWqYoBuJXZ8BXXgCMqN5 root@jellyfin"; + docker = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBiW/4iymJAZzG7wyUXVavcivjVZY9D2lNqGm4W2iDlD root@docker"; systems = [grafana onlyoffice postgresql forgejo nginx jellyfin]; in { @@ -39,4 +40,6 @@ in { "secrets/jellyfin-lapi-key.age".publicKeys = [tbarnouin jellyfin]; "secrets/redis-lapi-key.age".publicKeys = [tbarnouin redis]; + + "secrets/docker-lapi-key.age".publicKeys = [tbarnouin docker]; } diff --git a/secrets/docker-lapi-key.age b/secrets/docker-lapi-key.age new file mode 100644 index 0000000..114cdc5 --- /dev/null +++ b/secrets/docker-lapi-key.age @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE9Xa1ZYdyBsa0Nk +MHQ3cHdNWXE5b1k3aE0xUGtYUDBDNDRlSVVwN3lNNnlDK0hicEZzClc5N0Y1N1lT +dUtRa0ZDd2pnR2tudHoxNlV1b2tIdkhKRFY2NHdBN0R6MTQKLT4gc3NoLWVkMjU1 +MTkgOTNTUHhnIEx1cHJhcHhuNy93MXVSSWZraE5vN3FkNXVvbFdaZ1pWOUZZazhI +Q2Q2Q3MKOFdDd01MaU5INyswVXh4TGJrRENZMkpwMTB4NjlNMDM4Yi9DY0RqaFRU +OAotPiBYa3gtZ3JlYXNlIGYnTzM6OHAgWDIpLHcjICRTLS1sKytMCi9XZFJVOGt0 +Q1BlbUo3cEppY2hrRVo0TDh2bUJlREpiT1BheUZkcjN3YVdmNHovK1RwckZ6ZzZa +MEpLVC83cwotLS0gME9yd1FFbFVWT29PUTA1VnFZbzVxUFVIMjkrMGdhOWJicG8v +a2FvaGVNRQq0Sqh7ZG3QPYnV6XHPoS/1Egrz3co6h0vItVfzN+9HwXgIe8GPPfCH +WtEJnUJUqhhEXwHfwXtiflpR5z2I7P7z1F3oJQQXOEysyE0mgogDAf3Rz0pat+ZO +xHmdLjPSjmEfqSztFXVFNd08OkvQmb5ZsH+AvxN8OasAuV57ANGHgBkuOnXnq6x8 +2fXOGlDOKQnBuUmdJx8E +-----END AGE ENCRYPTED FILE----- diff --git a/services/docker/default.nix b/services/docker/default.nix new file mode 100644 index 0000000..2559bb7 --- /dev/null +++ b/services/docker/default.nix @@ -0,0 +1,208 @@ +{ + config, + pkgs, + lib, + ... +}: let + cfg = config.services.vm_docker; +in { + options.services.vm_docker = { + enable = lib.mkEnableOption "Enable minimal config"; + pgsql_ip = lib.mkOption { + type = lib.types.str; + description = "docker database IP address"; + }; + }; + config = lib.mkIf cfg.enable { + age.secrets.docker-lapi-key = { + file = ../../secrets/docker-lapi-key.age; + owner = "crowdsec"; + }; + }; + fileSystems."/mnt/docker-data" = { + device = "/dev/disk/by-uuid/39fb44a4-5c01-4337-894f-a6a6f4212b10"; + fsType = "ext4"; + }; + users.users.tbarnouin.extraGroups = [ "docker" ]; + virtualisation = { + oci-containers.backend = "docker"; + docker = { + enable = true; + autoPrune.enable = true; + daemon.settings = { + userland-proxy = false; + metrics-addr = "0.0.0.0:9323"; + data-root = "/mnt/docker-data"; + }; + }; + + virtualisation.oci-containers.containers = { + "gluetun" = { + image = "ghcr.io/qdm12/gluetun:latest"; + environment = { + "QBT_WEBUI_ENABLED" = "true"; + "SERVER_CITIES" = "Paris"; + "SERVER_COUNTRIES" = "France"; + "TZ" = "Europe/Paris"; + "VPN_PORT_FORWARDING" = "on"; + "VPN_SERVICE_PROVIDER" = "protonvpn"; + "VPN_TYPE" = "wireguard"; + "WIREGUARD_PRIVATE_KEY" = "IJqSQQC2heTOqo0YvqNHq+ZzPmBuKk9vrdo5pZtU2GE="; + }; + volumes = [ + "gluetun_gluetun-config:/gluetun:rw" + ]; + ports = [ + "8080:8080/tcp" + ]; + log-driver = "journald"; + extraOptions = [ + "--cap-add=NET_ADMIN" + "--device=/dev/net/tun:/dev/net/tun:rwm" + "--health-cmd=[\"wget\", \"--spider\", \"-q\", \"http://google.com\"]" + "--health-interval=30s" + "--health-retries=3" + "--health-timeout=10s" + "--network-alias=gluetun" + "--network=gluetun_default" + "--sysctl=net.ipv6.conf.all.disable_ipv6=1" + ]; + }; + "qbittorrent" = { + image = "lscr.io/linuxserver/qbittorrent:latest"; + environment = { + "DOCKER_MODS" = "ghcr.io/t-anc/gsp-qbittorent-gluetun-sync-port-mod:main"; + "GSP_GTN_API_KEY" = "1egJpY4lciGGs2CkpESR9RR480O4QyLqzKwQ792X7R4plzh5hri0pDsotWqYF1GM"; + "GSP_MINIMAL_LOGS" = "false"; + "GSP_QBITTORRENT_PORT" = "53764"; + "PGID" = "1000"; + "PUID" = "1000"; + "QBITTORRENT_INTERFACE" = "tun0"; + "TZ" = "Europe/Paris"; + "WEBUI_PORT" = "8080"; + }; + volumes = [ + "/mnt/DATA/:/downloads:rw" + "/mnt/docker-data/gluetun/qbittorrent/webui:/webui:rw" + "gluetun_qbittorrent-config:/config:rw" + ]; + dependsOn = [ + "gluetun" + ]; + log-driver = "journald"; + extraOptions = [ + "--network=container:gluetun" + ]; + }; + }; + systemd.services = { + "docker-gluetun" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + after = [ + "docker-network-gluetun_default.service" + "docker-volume-gluetun_gluetun-config.service" + ]; + requires = [ + "docker-network-gluetun_default.service" + "docker-volume-gluetun_gluetun-config.service" + ]; + partOf = [ + "docker-compose-gluetun-root.target" + ]; + wantedBy = [ + "docker-compose-gluetun-root.target" + ]; + }; + "docker-qbittorrent" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + after = [ + "docker-volume-gluetun_qbittorrent-config.service" + ]; + requires = [ + "docker-volume-gluetun_qbittorrent-config.service" + ]; + partOf = [ + "docker-compose-gluetun-root.target" + ]; + wantedBy = [ + "docker-compose-gluetun-root.target" + ]; + }; + # Networks + "docker-network-gluetun_default" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "docker network rm -f gluetun_default"; + }; + script = '' + docker network inspect gluetun_default || docker network create gluetun_default + ''; + partOf = [ "docker-compose-gluetun-root.target" ]; + wantedBy = [ "docker-compose-gluetun-root.target" ]; + }; + # Volumes + "docker-volume-gluetun_gluetun-config" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + docker volume inspect gluetun_gluetun-config || docker volume create gluetun_gluetun-config + ''; + partOf = [ "docker-compose-gluetun-root.target" ]; + wantedBy = [ "docker-compose-gluetun-root.target" ]; + }; + "docker-volume-gluetun_qbittorrent-config" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + docker volume inspect gluetun_qbittorrent-config || docker volume create gluetun_qbittorrent-config + ''; + partOf = [ "docker-compose-gluetun-root.target" ]; + wantedBy = [ "docker-compose-gluetun-root.target" ]; + }; + # Root service + # When started, this will automatically create all resources and start + # the containers. When stopped, this will teardown all resources. + "docker-compose-gluetun-root" = { + unitConfig = { + Description = "Root target generated by compose2nix."; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; + + services = { + crowdsec = { + settings.lapi.credentialsFile = "${config.age.secrets.docker-lapi-key.path}"; + localConfig = { + acquisitions = [ + { + source = "journalctl"; + journalctl_filter = [ "_SYSTEMD_UNIT=docker.service" ]; + labels = { + type = "syslog"; + }; + } + ]; + }; + }; + }; + }; +} diff --git a/services/jellyfin/default.nix b/services/jellyfin/default.nix index 6a36b94..8af5929 100644 --- a/services/jellyfin/default.nix +++ b/services/jellyfin/default.nix @@ -14,15 +14,22 @@ in { file = ../../secrets/jellyfin-lapi-key.age; owner = "crowdsec"; }; + boot = { + kernelPackages = pkgs.linuxPackages_latest; + kernelParams = ["intel_pstate=enable"]; + kernelModules = ["kvm-intel"]; + }; systemd.services.jellyfin.environment.LIBVA_DRIVER_NAME = "iHD"; environment = { sessionVariables = { LIBVA_DRIVER_NAME = "iHD"; }; systemPackages = with pkgs; [ cifs-utils + s-tui ]; }; # Intel Hardware Acceleration config hardware = { + cpu.intel.updateMicrocode = true; enableAllFirmware = true; intel-gpu-tools.enable = true; graphics = {