diff --git a/machines/solis/modules.nix b/machines/solis/modules.nix index 0c9e32e..2eef00c 100644 --- a/machines/solis/modules.nix +++ b/machines/solis/modules.nix @@ -1,4 +1,6 @@ -{...}: { +{config, ...}: let + username = config.horseman.username; +in { imports = [ ../../modules ]; @@ -6,6 +8,15 @@ config.horseman = { users.default.enable = true; + containers = { + enable = true; + backupDir = "/home/${username}/backups"; + + nginx.enable = true; + vaultwarden.enable = true; + forgejo.enable = true; + }; + base = { nix.enable = true; locale.enable = true; diff --git a/modules/base/secrets.nix b/modules/base/secrets.nix index 6699d79..429d96e 100644 --- a/modules/base/secrets.nix +++ b/modules/base/secrets.nix @@ -6,7 +6,7 @@ }: let inherit (lib) mkEnableOption mkIf; cfg = config.horseman.base.secrets; - secretFile = path: ../../secrets/${path}; + secretFile = path: ../../secrets/${path}.age; username = config.horseman.username; in { options = { @@ -19,38 +19,45 @@ in { environment.systemPackages = [pkgs.ragenix]; age.secrets = { - wifi.file = secretFile "wifi.age"; + wifi.file = secretFile "wifi"; personalSSHpub = { - file = secretFile "ssh/id_personal.pub.age"; + file = secretFile "ssh/id_personal.pub"; owner = username; group = "users"; - path = "/home/horseman/.ssh/id_ed25519.pub"; + path = "/home/${username}/.ssh/id_ed25519.pub"; }; personalSSH = { - file = secretFile "ssh/id_personal.age"; + file = secretFile "ssh/id_personal"; owner = username; group = "users"; - path = "/home/horseman/.ssh/id_ed25519"; + path = "/home/${username}/.ssh/id_ed25519"; }; githubSSHpub = { - file = secretFile "ssh/id_github.pub.age"; + file = secretFile "ssh/id_github.pub"; owner = username; group = "users"; - path = "/home/horseman/.ssh/id_github.pub"; + path = "/home/${username}/.ssh/id_github.pub"; }; githubSSH = { - file = secretFile "ssh/id_github.age"; + file = secretFile "ssh/id_github"; owner = username; group = "users"; - path = "/home/horseman/.ssh/id_github"; + path = "/home/${username}/.ssh/id_github"; }; sshConfig = { - file = secretFile "ssh/config.age"; + file = secretFile "ssh/config"; owner = username; group = "users"; - path = "/home/horseman/.ssh/config"; + path = "/home/${username}/.ssh/config"; + }; + + forgejo-secret = { + file = secretFile "containers/forgejo-secret"; + path = "/run/forgejo-secrets/secret"; + symlink = false; + mode = "444"; }; }; }; diff --git a/modules/containers/default.nix b/modules/containers/default.nix new file mode 100644 index 0000000..4bc2f02 --- /dev/null +++ b/modules/containers/default.nix @@ -0,0 +1,34 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf mkEnableOption mkOption types; + cfg = config.horseman.containers; +in { + imports = [ + ./nginx.nix + ./forgejo.nix + ./vaultwarden.nix + ]; + + options = { + horseman.containers = { + enable = mkEnableOption "Containers"; + backupDir = mkOption { + type = types.str; + }; + }; + }; + + config = mkIf cfg.enable { + networking.nat = { + enable = true; + # Use "ve-*" when using nftables instead of iptables + internalInterfaces = ["ve-+"]; + externalInterface = "enp2s0"; + # Lazy IPv6 connectivity for the container + enableIPv6 = true; + }; + }; +} diff --git a/modules/containers/forgejo.nix b/modules/containers/forgejo.nix new file mode 100644 index 0000000..3401e38 --- /dev/null +++ b/modules/containers/forgejo.nix @@ -0,0 +1,185 @@ +{ + inputs, + outputs, + lib, + config, + pkgs, + ... +}: let + inherit (lib) types mkOption mkEnableOption mkIf; + cfg = config.horseman.containers.forgejo; + username = config.horseman.username; + + BACKUP_DIR = config.horseman.containers.backupDir; + DATA_DIR = "/home/${username}/backups/volumes/forgejo"; +in { + options = { + horseman.containers.forgejo = { + enable = mkEnableOption "forgejo containers"; + + port = mkOption { + default = 3000; + type = types.int; + }; + + sshPort = mkOption { + default = 34916; + type = types.int; + }; + + url = mkOption { + default = "https://git.koendev.nl"; + type = types.str; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.timers."backup-forgejo" = { + wantedBy = ["timers.target"]; + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + }; + }; + + environment.systemPackages = [pkgs.gnutar]; + systemd.services."backup-forgejo" = { + script = '' + cd ${BACKUP_DIR} + ${pkgs.gnutar}/bin/tar -cf forgejo-$(date +'%Y-%m-%d').tar ${DATA_DIR} + ''; + serviceConfig = { + User = "root"; + }; + }; + + containers.forgejoRunner = { + autoStart = true; + privateNetwork = true; + hostAddress = "192.168.100.2"; + localAddress = "192.168.100.102"; + + bindMounts = { + "/var/lib/secrets" = { + hostPath = "/run/forgejo-secrets"; + isReadOnly = true; + }; + }; + + config = { + config, + pkgs, + ... + }: let + configFile = pkgs.writeText "runner.yml" '' + runner: + labels: + - "self-hosted:host" + ''; + in { + environment.systemPackages = with pkgs; [ + forgejo-runner + ]; + + users.groups.runner = {}; + users.users.runner = { + isNormalUser = true; + group = "runner"; + }; + + systemd.services.startup = { + script = '' + cd ${config.users.users.runner.home} + ${pkgs.forgejo-runner}/bin/forgejo-runner create-runner-file --instance ${cfg.url} --secret $(cat /var/lib/secrets/secret) --name runner + sleep 10 + ${pkgs.forgejo-runner}/bin/forgejo-runner daemon --config ${configFile} + ''; + serviceConfig.User = "runner"; + wantedBy = ["multi-user.target"]; + }; + + system.stateVersion = "23.11"; + }; + }; + + containers.forgejo = { + autoStart = true; + privateNetwork = true; + hostAddress = "192.168.100.3"; + localAddress = "192.168.100.103"; + + bindMounts = { + "/var/lib/forgejo" = { + hostPath = DATA_DIR; + isReadOnly = false; + }; + + "/var/lib/secrets" = { + hostPath = "/run/forgejo-secrets"; + isReadOnly = true; + }; + }; + + config = { + config, + pkgs, + ... + }: { + environment.systemPackages = [pkgs.forgejo]; + + services.openssh = { + enable = true; + ports = [cfg.sshPort]; + settings = { + PermitRootLogin = "no"; + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + # AllowUsers = ["git"]; + }; + }; + + services.forgejo = { + enable = true; + + stateDir = "/var/lib/forgejo"; + + settings = { + server = { + HTTP_PORT = cfg.port; + SSH_PORT = cfg.sshPort; + ROOT_URL = cfg.url; + }; + session = { + COOKIE_SECURE = true; + }; + service = { + DISABLE_REGISTRATION = true; + }; + }; + }; + + systemd.services.startup = { + script = '' + cd ${config.users.users.forgejo.home} + ${pkgs.forgejo}/bin/forgejo forgejo-cli actions register --name runner --secret $(cat /var/lib/secrets/secret) --config ${config.services.forgejo.stateDir}/custom/conf/app.ini + ''; + serviceConfig.User = "forgejo"; + wantedBy = ["multi-user.target"]; + }; + + networking = { + firewall = { + enable = true; + allowedTCPPorts = [cfg.port cfg.sshPort]; + }; + # Use systemd-resolved inside the container + # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 + useHostResolvConf = lib.mkForce false; + }; + + system.stateVersion = "23.11"; + }; + }; + }; +} diff --git a/modules/containers/nginx.nix b/modules/containers/nginx.nix new file mode 100644 index 0000000..4a2d81b --- /dev/null +++ b/modules/containers/nginx.nix @@ -0,0 +1,83 @@ +{ + lib, + config, + ... +}: let + inherit (lib) mkEnableOption mkIf mkOption types; + cfg = config.horseman.containers.nginx; +in { + options = { + horseman.containers.nginx = { + enable = mkEnableOption "nginx container"; + + domain = mkOption { + type = types.str; + default = "koendev.nl"; + }; + }; + }; + + config = mkIf cfg.enable { + security.acme = { + acceptTerms = true; + defaults.email = "koen.de.ruiter@hotmail.com"; + }; + + services.fail2ban.enable = true; + services.nginx = { + enable = true; + + streamConfig = '' + server { + listen ${toString config.horseman.containers.forgejo.sshPort}; + proxy_pass ${config.containers.forgejo.localAddress}:${toString config.horseman.containers.forgejo.sshPort}; + } + ''; + + virtualHosts = { + "${cfg.domain}" = { + forceSSL = true; + enableACME = true; + + root = "/var/www/portfolio"; + default = true; + extraConfig = '' + error_page 404 /404.html; + ''; + }; + + "public.${cfg.domain}" = { + forceSSL = true; + enableACME = true; + + root = "/var/www/public"; + }; + + "git.${cfg.domain}" = { + forceSSL = true; + enableACME = true; + + locations."/" = { + proxyPass = "http://${config.containers.forgejo.localAddress}:${toString config.horseman.containers.forgejo.port}"; + }; + }; + + "vault.${cfg.domain}" = { + forceSSL = true; + enableACME = true; + + locations."/" = { + proxyPass = "http://${config.containers.vaultwarden.localAddress}:${toString config.horseman.containers.vaultwarden.port}"; + }; + }; + }; + }; + + networking = { + firewall = { + enable = true; + allowedTCPPorts = [80 443 config.horseman.containers.forgejo.sshPort]; + }; + }; + }; +} diff --git a/modules/containers/vaultwarden.nix b/modules/containers/vaultwarden.nix new file mode 100644 index 0000000..02707c3 --- /dev/null +++ b/modules/containers/vaultwarden.nix @@ -0,0 +1,95 @@ +{ + inputs, + outputs, + lib, + config, + pkgs, + ... +}: let + inherit (lib) types mkOption mkEnableOption mkIf; + cfg = config.horseman.containers.vaultwarden; + username = config.horseman.username; + + BACKUP_DIR = config.horseman.containers.backupDir; + DATA_DIR = "/home/${username}/backups/volumes/vaultwarden"; +in { + options = { + horseman.containers.vaultwarden = { + enable = mkEnableOption "Password manager"; + + port = mkOption { + default = 3000; + type = types.int; + }; + + url = mkOption { + default = "https://vault.koendev.nl"; + type = types.str; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.timers."backup-vaultwarden" = { + wantedBy = ["timers.target"]; + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + }; + }; + + environment.systemPackages = [pkgs.gnutar]; + systemd.services."backup-vaultwarden" = { + script = '' + cd ${BACKUP_DIR} + ${pkgs.gnutar}/bin/tar -cf vaultwarden-$(date +'%Y-%m-%d').tar ${DATA_DIR} + ''; + serviceConfig = { + User = "root"; + }; + }; + + containers.vaultwarden = { + autoStart = true; + privateNetwork = true; + hostAddress = "192.168.100.4"; + localAddress = "192.168.100.104"; + + bindMounts = { + "/var/lib/bitwarden_rs" = { + hostPath = DATA_DIR; + isReadOnly = false; + }; + }; + + config = { + config, + pkgs, + ... + }: { + environment.systemPackages = with pkgs; [ + vaultwarden.webvault + ]; + + services.vaultwarden = { + enable = true; + config = { + ROCKET_PORT = cfg.port; + ROCKET_ADDRESS = "0.0.0.0"; + WEB_VAULT_FOLDER = "${pkgs.vaultwarden.webvault}/share/vaultwarden/vault"; + }; + }; + + networking = { + firewall = { + enable = true; + allowedTCPPorts = [cfg.port]; + }; + useHostResolvConf = lib.mkForce false; + }; + + system.stateVersion = "23.11"; + }; + }; + }; +} diff --git a/modules/default.nix b/modules/default.nix index 7b85bd5..2c57013 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -12,6 +12,7 @@ in { ./base ./boot ./catppuccin + ./containers ./hardware ./network ./timers diff --git a/secrets.nix b/secrets.nix index 52a7f5c..edb111b 100644 --- a/secrets.nix +++ b/secrets.nix @@ -17,6 +17,7 @@ let "ssh/id_github" "ssh/id_github.pub" "ssh/config" + "containers/forgejo-secret" ]; attrs = map (secret: {"secrets/${secret}.age".publicKeys = all;}) secrets; in diff --git a/secrets/containers/forgejo-secret.age b/secrets/containers/forgejo-secret.age new file mode 100644 index 0000000..81b3f0a --- /dev/null +++ b/secrets/containers/forgejo-secret.age @@ -0,0 +1,18 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IC9tczZkdyAvWjRI +VkpQNWpZNHdiZm9jVlZvcTYxS04wUk1hTWtmdWRXL2dydGwxc3pVCklSOWZYNThu +aVBSeDBwRldwTjVzT0VOaG9kNnV5SWJnT2M1YXVRSjUwUFkKLT4gc3NoLWVkMjU1 +MTkgZ1BJZFpBIExmTUxudEFaR21CQzg5ejlUaXRoQW5ORmVqcUlRd1o2L0pZbTd5 +UVBIeWcKZWVSV3k4bHZqV3pDaDMxYzBnZW9FL085TG14bkl0UlZoQ25CbW9iVENv +RQotPiBzc2gtZWQyNTUxOSBXeUlGekEgY2Zkd0VaZmxRaVV4cXBlRnNHeDMyUFFV +YVVzZlF2SXFjak5LREZINFRTMAp6dmFKOTd5eUMrczZ0WUU2T3hER3lkSHlTTGZj +U1k4ZVV1THJMK0RSd21zCi0+IHNzaC1lZDI1NTE5IGRiT2VoQSB0WkJpSkZHZ1BM +QitLdG9SdDdjckxVa0dxQ2dmQlNGekVITlllN2R3OWxZCmt2Zk5VSjNTdnB4Z2ln +TnExNytuZ2FtVE83NWZWcm1Fd25MS1NEL3JsbzgKLT4gc3NoLWVkMjU1MTkgdHYv +Q3pnIFVDMU9Kbld0aHFia3FmTEs2aGF2THdaSk1Ec2h2WWlMVjZvUkNHTFpCR1EK +NkR6R2JaZkZtbitBMmk1UjRFS3FFZ293bFhDUWxmR2M0ZHFoSVRGV2E1ZwotPiAn +eCFtQ3htLWdyZWFzZQp4VFZWeTlDNWlsNDI5WWlPTzNGbQotLS0gRmZIdXk3UUVw +ZEJjOGdnVVdlWWN5Y0VxaUJORG0ycTBQdVFGVGw1RVpZdwqD8VD14PUaG2u0/h9o +6VeX+m3nJkJgsXUkGPskTHHEc+1NaZ9MQM5dXzrmjfVHBT7N27bDcYlGG2RIfehC +Xz5jRZwSTG58wzt9 +-----END AGE ENCRYPTED FILE-----