diff --git a/.dir-locals.el b/.dir-locals.el
deleted file mode 100644
index af235d5..0000000
--- a/.dir-locals.el
+++ /dev/null
@@ -1,7 +0,0 @@
-((rust-mode
- . ((eglot-workspace-configuration
- . (:rust-analyzer
- (:cargo (:features "all")
- :check (:command "clippy")
- :rustfmt (:overrideCommand ["leptosfmt" "--stdin" "--rustfmt"])
- :linkedProjects ["./pkgs/packages/webserver/Cargo.toml"]))))))
diff --git a/checks/default.nix b/checks/default.nix
index 03e5b6b..f3db4ce 100644
--- a/checks/default.nix
+++ b/checks/default.nix
@@ -1,82 +1,61 @@
-{ self, ... }:
{
- perSystem =
+ self,
+ nixpkgs,
+ deploy-rs,
+ system,
+ ...
+}:
+let
+ pkgs = nixpkgs.legacyPackages.${system};
+
+ statix' = pkgs.statix.overrideAttrs (old: {
+ patches = old.patches ++ [
+ (pkgs.fetchpatch {
+ url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
+ hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
+ })
+ ];
+ });
+
+ runNuCheck =
{
- inputs',
- lib,
- pkgs,
- ...
+ name,
+ packages,
+ check,
}:
- let
- mkLint =
- {
- name,
- fileset,
- checkInputs ? [ ],
- script,
- }:
- pkgs.stdenvNoCC.mkDerivation {
- inherit name;
+ pkgs.stdenvNoCC.mkDerivation {
+ inherit name;
- src = lib.fileset.toSource {
- root = ../.;
- fileset = lib.fileset.difference fileset (
- lib.fileset.fileFilter (
- file: file.type != "regular" || file.name == "hardware-configuration.nix"
- ) ../.
- );
- };
+ src = nixpkgs.lib.cleanSourceWith {
+ src = self;
+ filter = nixpkgs.lib.cleanSourceFilter;
+ };
- checkInputs = [ pkgs.nushell ] ++ checkInputs;
+ dontPatch = true;
+ dontConfigure = true;
+ dontBuild = true;
+ dontInstall = true;
+ dontFixup = true;
+ doCheck = true;
- checkPhase = ''
- nu -c '${script}' | tee $out
- '';
+ checkInputs = nixpkgs.lib.singleton pkgs.nushell ++ packages;
- dontPatch = true;
- dontConfigure = true;
- dontBuild = true;
- dontInstall = true;
- dontFixup = true;
- doCheck = true;
- };
- in
- {
- checks = {
- nix = mkLint {
- name = "nix-lints";
- fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.;
-
- checkInputs = lib.attrValues {
- inherit (pkgs) deadnix nixfmt-rfc-style;
-
- statix = pkgs.statix.overrideAttrs (old: {
- patches = old.patches ++ [
- (pkgs.fetchpatch {
- url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
- hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
- })
- ];
- });
- };
-
- script = /* bash */ ''
- statix check **/*.nix
- deadnix --fail **/*.nix
- nixfmt --check --strict **/*.nix
- '';
- };
-
- lockfile = mkLint {
- name = "nix-lockfile";
- fileset = ../flake.lock;
- checkInputs = lib.attrValues { inherit (inputs'.flint.packages) flint; };
-
- script = /* bash */ ''
- flint --fail-if-multiple-versions
- '';
- };
- }
- // self.nixosConfigurations.hetzner-1.config.serviceTests;
+ checkPhase = ''
+ nu ${check}
+ '';
};
-}
+in
+nixpkgs.lib.recursiveUpdate {
+ lints = runNuCheck {
+ name = "lints";
+
+ packages = [
+ pkgs.deadnix
+ pkgs.nixfmt-rfc-style
+ pkgs.shellcheck
+ statix'
+ ];
+
+ check = ./lints.nu;
+ };
+} (deploy-rs.lib.${system}.deployChecks self.deploy)
diff --git a/configuration/default.nix b/configuration/default.nix
index ef68727..041c885 100644
--- a/configuration/default.nix
+++ b/configuration/default.nix
@@ -1,6 +1,5 @@
{
- pkgs,
- lib,
+ config,
modulesPath,
flake-inputs,
...
@@ -9,13 +8,32 @@
imports = [
flake-inputs.disko.nixosModules.disko
flake-inputs.sops-nix.nixosModules.sops
- "${modulesPath}/profiles/minimal.nix"
+ flake-inputs.tlaternet-webserver.nixosModules.default
- ../modules
- ./nginx
- ./services
+ "${modulesPath}/profiles/minimal.nix"
+ (import ../modules)
+
+ ./services/auth
+ ./services/backups.nix
+ ./services/battery-manager.nix
+ ./services/conduit
+ ./services/crowdsec.nix
+ ./services/foundryvtt.nix
+ ./services/gitea.nix
+ ./services/immich.nix
+ ./services/metrics
+ ./services/minecraft.nix
+ ./services/nextcloud.nix
+ ./services/webserver.nix
+ ./services/wireguard.nix
+ # ./services/starbound.nix -- Not currently used
+ ./services/postgres.nix
+ ./nginx.nix
+ ./sops.nix
];
+ nixpkgs.overlays = [ (_: prev: { local = import ../pkgs { pkgs = prev; }; }) ];
+
nix = {
extraOptions = ''
experimental-features = nix-command flakes
@@ -25,9 +43,49 @@
settings.trusted-users = [ "@wheel" ];
};
+ # Optimization for minecraft servers, see:
+ # https://bugs.mojang.com/browse/MC-183518
+ boot.kernelParams = [
+ "highres=off"
+ "nohz=off"
+ ];
+
networking = {
usePredictableInterfaceNames = false;
useDHCP = false;
+
+ firewall = {
+ allowedTCPPorts = [
+ # http
+ 80
+ 443
+ # ssh
+ 2222
+ # matrix
+ 8448
+ # starbound
+ 21025
+
+ config.services.coturn.listening-port
+ config.services.coturn.tls-listening-port
+ config.services.coturn.alt-listening-port
+ config.services.coturn.alt-tls-listening-port
+ ];
+
+ allowedUDPPorts = [
+ config.services.coturn.listening-port
+ config.services.coturn.tls-listening-port
+ config.services.coturn.alt-listening-port
+ config.services.coturn.alt-tls-listening-port
+ ];
+
+ allowedUDPPortRanges = [
+ {
+ from = config.services.coturn.min-port;
+ to = config.services.coturn.max-port;
+ }
+ ];
+ };
};
systemd.network.enable = true;
@@ -54,19 +112,6 @@
};
logrotate.enable = true;
-
- postgresql = {
- package = pkgs.postgresql_14;
- enable = true;
-
- # Only enable connections via the unix socket, and check with the
- # OS to make sure the user matches the database name.
- #
- # See https://www.postgresql.org/docs/current/auth-pg-hba-conf.html
- authentication = ''
- local sameuser all peer
- '';
- };
};
security = {
@@ -80,10 +125,9 @@
services.sudo.rssh = true;
};
};
- sops.defaultSopsFile = ../keys/production.yaml;
# Remove some unneeded packages
- environment.defaultPackages = lib.mkForce [ ];
+ environment.defaultPackages = [ ];
system.stateVersion = "20.09";
}
diff --git a/configuration/hardware-specific/hetzner/default.nix b/configuration/hardware-specific/hetzner/default.nix
index 3b60011..4d0408c 100644
--- a/configuration/hardware-specific/hetzner/default.nix
+++ b/configuration/hardware-specific/hetzner/default.nix
@@ -2,7 +2,6 @@
imports = [
./hardware-configuration.nix
./disko.nix
- ./vm.nix
];
# Intel's special encrypted memory<->CPU feature. Hetzner's BIOS
diff --git a/configuration/hardware-specific/hetzner/disko.nix b/configuration/hardware-specific/hetzner/disko.nix
index 6b769d2..7e1acd7 100644
--- a/configuration/hardware-specific/hetzner/disko.nix
+++ b/configuration/hardware-specific/hetzner/disko.nix
@@ -80,7 +80,7 @@
inherit mountOptions;
mountpoint = "/var";
};
- "/volume/var/lib/private/continuwuity" = {
+ "/volume/var/lib/private/matrix-conduit" = {
mountOptions = [
# Explicitly don't compress here, since
# conduwuit's database does compression by
@@ -89,7 +89,7 @@
# if btrfs compresses it)
"noatime"
];
- mountpoint = "/var/lib/private/continuwuity";
+ mountpoint = "/var/lib/private/matrix-conduit";
};
"/volume/nix-store" = {
inherit mountOptions;
diff --git a/configuration/hardware-specific/hetzner/vm.nix b/configuration/hardware-specific/hetzner/vm.nix
deleted file mode 100644
index 7ecf8f8..0000000
--- a/configuration/hardware-specific/hetzner/vm.nix
+++ /dev/null
@@ -1,70 +0,0 @@
-{ lib, ... }:
-{
- virtualisation.vmVariant = {
- users.users.tlater.password = "insecure";
-
- # Disable graphical tty so -curses works
- boot.kernelParams = [ "nomodeset" ];
-
- networking.hostName = lib.mkForce "testvm";
-
- services = {
- # Sets the base domain for nginx to a local domain so that we can
- # easily test locally with the VM.
- nginx.domain = lib.mkForce "dev.local";
-
- # Don't run this
- batteryManager.enable = lib.mkForce false;
- btrfs.autoScrub.enable = lib.mkForce false;
-
- openssh.hostKeys = lib.mkForce [
- {
- type = "rsa";
- bits = 4096;
- path = "/etc/staging.key";
- }
- ];
- };
-
- # Use the staging secrets
- sops.defaultSopsFile = lib.mkOverride 99 ../../../keys/staging.yaml;
-
- systemd.network.networks."10-eth0" = {
- matchConfig.Name = "eth0";
- gateway = [ "192.168.9.1" ];
- networkConfig = {
- Address = "192.168.9.2/24";
- };
- };
-
- # Both so we have a predictable key for the staging env, as well as
- # to have a static key for decrypting the sops secrets for the
- # staging env.
- environment.etc."staging.key" = {
- mode = "0400";
- source = ../../../keys/hosts/staging.key;
- };
-
- # Pretend the acme renew succeeds.
- #
- # TODO(tlater): Set up pebble to retrieve certs "properly"
- # instead
- systemd.services."acme-order-renew-tlater.net".script = ''
- touch out/acme-success
- '';
-
- virtualisation = {
- memorySize = 3941;
- cores = 2;
- graphics = false;
- diskSize = 1024 * 20;
-
- qemu = {
- networkingOptions = lib.mkForce [
- "-device virtio-net,netdev=n1"
- "-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)"
- ];
- };
- };
- };
-}
diff --git a/configuration/hardware-specific/vm.nix b/configuration/hardware-specific/vm.nix
new file mode 100644
index 0000000..71870fb
--- /dev/null
+++ b/configuration/hardware-specific/vm.nix
@@ -0,0 +1,60 @@
+{ lib, ... }:
+{
+ users.users.tlater.password = "insecure";
+
+ # Disable graphical tty so -curses works
+ boot.kernelParams = [ "nomodeset" ];
+
+ networking.hostName = "testvm";
+
+ services = {
+ # Sets the base domain for nginx to a local domain so that we can
+ # easily test locally with the VM.
+ nginx.domain = "dev.local";
+
+ # Don't run this
+ batteryManager.enable = lib.mkForce false;
+
+ openssh.hostKeys = lib.mkForce [
+ {
+ type = "rsa";
+ bits = 4096;
+ path = "/etc/staging.key";
+ }
+ ];
+ };
+
+ # Use the staging secrets
+ sops.defaultSopsFile = lib.mkOverride 99 ../../keys/staging.yaml;
+
+ systemd.network.networks."10-eth0" = {
+ matchConfig.Name = "eth0";
+ gateway = [ "192.168.9.1" ];
+ networkConfig = {
+ Address = "192.168.9.2/24";
+ };
+ };
+
+ # Both so we have a predictable key for the staging env, as well as
+ # to have a static key for decrypting the sops secrets for the
+ # staging env.
+ environment.etc."staging.key" = {
+ mode = "0400";
+ source = ../../keys/hosts/staging.key;
+ };
+
+ virtualisation.vmVariant = {
+ virtualisation = {
+ memorySize = 3941;
+ cores = 2;
+ graphics = false;
+ };
+
+ virtualisation.qemu = {
+ networkingOptions = lib.mkForce [
+ "-device virtio-net,netdev=n1"
+ "-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)"
+ ];
+ };
+ };
+}
diff --git a/configuration/nginx.nix b/configuration/nginx.nix
new file mode 100644
index 0000000..935b5ac
--- /dev/null
+++ b/configuration/nginx.nix
@@ -0,0 +1,77 @@
+{ config, lib, ... }:
+{
+ services = {
+ nginx = {
+ enable = true;
+ recommendedTlsSettings = true;
+ recommendedOptimisation = true;
+ recommendedGzipSettings = true;
+ recommendedProxySettings = true;
+ clientMaxBodySize = "10G";
+
+ statusPage = true; # For metrics, should be accessible only from localhost
+
+ commonHttpConfig = ''
+ log_format upstream_time '$remote_addr - $remote_user [$time_local] '
+ '"$request" $status $body_bytes_sent '
+ '"$http_referer" "$http_user_agent" '
+ 'rt=$request_time uct="$upstream_connect_time" '
+ 'uht="$upstream_header_time" urt="$upstream_response_time"';
+ '';
+ };
+
+ logrotate.settings = {
+ # Override the default, just keep fewer logs
+ nginx.rotate = 6;
+ }
+ // lib.mapAttrs' (
+ virtualHost: _:
+ lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
+ frequency = "daily";
+ rotate = 2;
+ compress = true;
+ delaycompress = true;
+ su = "${config.services.nginx.user} ${config.services.nginx.group}";
+ postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
+ }
+ ) config.services.nginx.virtualHosts;
+
+ backups.acme = {
+ user = "acme";
+ paths = lib.mapAttrsToList (
+ virtualHost: _: "/var/lib/acme/${virtualHost}"
+ ) config.services.nginx.virtualHosts;
+ };
+ };
+
+ systemd.tmpfiles.rules = lib.mapAttrsToList (
+ virtualHost: _:
+ #
+ "d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
+ ) config.services.nginx.virtualHosts;
+
+ security.acme = {
+ defaults.email = "tm@tlater.net";
+ acceptTerms = true;
+
+ certs."tlater.net" = {
+ extraDomainNames = [
+ "*.tlater.net"
+ "tlater.com"
+ "*.tlater.com"
+ ];
+ dnsProvider = "porkbun";
+ group = "ssl-cert";
+ credentialFiles = {
+ PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
+ PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
+ };
+ };
+ };
+
+ users.groups.ssl-cert = { };
+
+ systemd.services.nginx.serviceConfig.SupplementaryGroups = [
+ config.security.acme.certs."tlater.net".group
+ ];
+}
diff --git a/configuration/nginx/default.nix b/configuration/nginx/default.nix
deleted file mode 100644
index dab0259..0000000
--- a/configuration/nginx/default.nix
+++ /dev/null
@@ -1,22 +0,0 @@
-{ lib, ... }:
-{
- imports = [
- ./logging.nix
- ./ssl.nix
- ];
-
- options.services.nginx.domain = lib.mkOption {
- type = lib.types.str;
- description = "The base domain name to append to virtual domain names";
- };
-
- config.services.nginx = {
- enable = true;
- recommendedTlsSettings = true;
- recommendedOptimisation = true;
- recommendedGzipSettings = true;
- recommendedProxySettings = true;
- clientMaxBodySize = "10G";
- statusPage = true; # For metrics, should be accessible only from localhost
- };
-}
diff --git a/configuration/nginx/logging.nix b/configuration/nginx/logging.nix
deleted file mode 100644
index e41bfae..0000000
--- a/configuration/nginx/logging.nix
+++ /dev/null
@@ -1,140 +0,0 @@
-{
- flake-inputs,
- pkgs,
- config,
- lib,
- ...
-}:
-let
- hostNames = lib.attrNames config.services.nginx.virtualHosts;
- logPath = name: "/var/log/nginx/${name}/access.log";
- logFormat = lib.concatStringsSep " " [
- "$remote_addr - $remote_user [$time_local]"
- ''"$request" $status $body_bytes_sent''
- ''"$http_referer" "$http_user_agent"''
- ''rt=$request_time uct="$upstream_connect_time"''
- ''uht="$upstream_header_time" urt="$upstream_response_time"''
- ];
-in
-{
- # Extend the default configuration for nginx virtual hosts; we'd
- # like to create log files for each of them, so that the prometheus
- # nginxlog exporter can process per-host logs.
- options.services.nginx.virtualHosts = lib.mkOption {
- type = lib.types.attrsOf (
- lib.types.submodule (
- { name, ... }:
- {
- config.extraConfig = ''
- access_log ${logPath name} upstream_time;
- '';
- }
- )
- );
- };
-
- config = {
- # Create directories for host-specific logs with systemd tmpfiles
- systemd.tmpfiles.settings."10-nginx-logs" = lib.listToAttrs (
- map (
- name:
- lib.nameValuePair "/var/log/nginx/${name}" {
- d = {
- inherit (config.services.nginx) user group;
- mode = "0750";
- };
- }
- ) hostNames
- );
-
- services = {
- # Set the nginx-wide log format
- nginx.commonHttpConfig = ''
- log_format upstream_time '${logFormat}';
- '';
-
- # Set up nginxlog to read the file and log format defined above
- # for each virtual host
- prometheus.exporters.nginxlog = {
- enable = true;
- listenAddress = "127.0.0.1";
- group = "nginx";
-
- settings.namespaces = map (name: {
- inherit name;
- metrics_override.prefix = "nginxlog";
- namespace_label = "vhost";
- format = logFormat;
- source.files = [ (logPath name) ];
- }) hostNames;
- };
-
- logrotate.settings = {
- # Override the nginx module default, just keep fewer logs
- nginx.rotate = 6;
-
- # Configure logrotate for host-specific logs
- nginxVirtualHosts = {
- files = map logPath hostNames;
-
- frequency = "daily";
- rotate = 2;
- compress = true;
- delaycompress = true;
- su = "${config.services.nginx.user} ${config.services.nginx.group}";
- postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
- };
- };
- };
-
- serviceTests =
- let
- testHostConfig =
- { config, ... }:
- {
- imports = [
- ./.
- ../../modules/serviceTests/mocks.nix
- ];
-
- networking.firewall.allowedTCPPorts = [ 80 ];
-
- services.nginx = {
- domain = "testHost";
- virtualHosts."${config.services.nginx.domain}".locations."/".return = "200 ok";
- };
- };
- in
- {
- nginxMetricsWork = pkgs.testers.runNixOSTest {
- name = "nginx-metrics-work";
- node.specialArgs = { inherit flake-inputs; };
-
- nodes = {
- testHost = testHostConfig;
-
- client =
- { pkgs, ... }:
- {
- environment.systemPackages = [ pkgs.curl ];
- };
- };
-
- testScript = ''
- import time
-
- start_all()
-
- testHost.wait_for_unit("nginx.service")
- client.succeed("curl --max-time 10 http://testHost")
-
- # Wait a bit for the prometheus exporter to scrape our logs
- time.sleep(5)
-
- res = testHost.succeed("curl localhost:${builtins.toString config.services.prometheus.exporters.nginxlog.port}/metrics")
- assert 'nginxlog_http_response_count_total{method="GET",status="200",vhost="testHost"} 1' in res, res
- '';
- };
- };
- };
-}
diff --git a/configuration/nginx/ssl.nix b/configuration/nginx/ssl.nix
deleted file mode 100644
index f3fac13..0000000
--- a/configuration/nginx/ssl.nix
+++ /dev/null
@@ -1,135 +0,0 @@
-{
- flake-inputs,
- pkgs,
- config,
- lib,
- ...
-}:
-{
- options = {
- # Add a custom per-host option to enable HSTS
- services.nginx.virtualHosts = lib.mkOption {
- type = lib.types.attrsOf (
- lib.types.submodule (
- { config, ... }:
- {
- options.enableHSTS = lib.mkEnableOption "HSTS";
- config.extraConfig = lib.mkIf config.enableHSTS ''
- add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
- '';
- }
- )
- );
- };
- };
-
- config = {
- # Certificate settings
- security.acme = {
- defaults.email = "tm@tlater.net";
- acceptTerms = true;
-
- certs."tlater.net" = {
- extraDomainNames = [
- "*.tlater.net"
- "tlater.com"
- "*.tlater.com"
- ];
- dnsProvider = "porkbun";
- group = config.users.groups.ssl-cert.name;
- credentialFiles = {
- PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
- PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
- };
- };
- };
- users.groups.ssl-cert = { };
-
- # Back up the SSL certificate, just in case
- services.backups.acme = {
- user = "acme";
- paths = [ "/var/lib/acme/tlater.net" ];
- };
-
- systemd.services.nginx.serviceConfig.SupplementaryGroups = [
- config.security.acme.certs."tlater.net".group
- ];
-
- sops.secrets = {
- "porkbun/api-key".owner = "acme";
- "porkbun/secret-api-key".owner = "acme";
- };
-
- serviceTests =
- let
- testHostConfig =
- { config, ... }:
- {
- imports = [
- ./.
- ../../modules/serviceTests/mocks.nix
- ];
-
- networking.firewall.allowedTCPPorts = [ 443 ];
-
- security.acme.certs."tlater.net".extraDomainNames = [ config.services.nginx.domain ];
-
- # Pretend the acme renew succeeds.
- #
- # TODO(tlater): Set up pebble to retrieve certs "properly"
- # instead
- systemd.services."acme-order-renew-tlater.net".script = ''
- touch out/acme-success
- '';
-
- services.nginx = {
- domain = "testHost.test";
-
- virtualHosts."${config.services.nginx.domain}.local" = {
- useACMEHost = "tlater.net";
- onlySSL = true;
- enableHSTS = true;
- locations."/".return = "200 ok";
- };
- };
- };
- in
- {
- testNginxSSL = pkgs.testers.runNixOSTest {
- name = "test-nginx-ssl";
-
- node.specialArgs = { inherit flake-inputs; };
- nodes = {
- testHost = testHostConfig;
-
- client =
- { pkgs, ... }:
- {
- environment.systemPackages = [ pkgs.curl ];
- networking.hosts."192.168.1.2" = [ "testHost.test" ];
- };
- };
-
- testScript = ''
- start_all()
-
- testHost.wait_for_unit("nginx.service")
- testHost.copy_from_vm("/var/lib/acme/tlater.net/", "certs")
- client.copy_from_host(f"{testHost.out_dir}/certs", "/certs")
-
- res = client.succeed(" ".join([
- "curl",
- "--show-error",
- "--silent",
- "--dump-header -",
- "--cacert /certs/tlater.net/fullchain.pem",
- "https://testHost.test",
- "-o /dev/null"
- ]))
-
- assert "strict-transport-security: max-age=15552000; includeSubDomains" in res
- '';
- };
- };
- };
-}
diff --git a/configuration/services/auth/authelia.nix b/configuration/services/auth/authelia.nix
new file mode 100644
index 0000000..f05e3f0
--- /dev/null
+++ b/configuration/services/auth/authelia.nix
@@ -0,0 +1,87 @@
+{ config, ... }:
+let
+ instanceName = config.services.authelia.instances.main.name;
+in
+{
+ services.authelia.instances.main = {
+ enable = true;
+
+ settings = {
+ theme = "auto";
+ default_2fa_method = "totp";
+
+ authentication_backend = {
+ password_reset.disable = true;
+ password_change.disable = true;
+
+ file = {
+ inherit (config.sops.secrets."authelia/users") path;
+
+ search = {
+ email = true;
+ case_insensitive = false;
+ };
+ };
+ };
+
+ storage.postgres = {
+ address = "unix:///run/postgresql";
+ database = "authelia";
+ username = "authelia";
+ };
+
+ session.cookies = [
+ {
+ domain = config.services.nginx.domain;
+ authelia_url = "https://auth.${config.services.nginx.domain}";
+ }
+ ];
+
+ notifier.filesystem.filename = ''{{ env "RUNTIME_DIRECTORY" }}/authelia-notifications'';
+
+ access_control = {
+
+ };
+
+ server = {
+ # Maybe a systemd socket can be used for this in the future,
+ # see:
+ # https://github.com/systemd/systemd/issues/23067#issuecomment-1212232155
+ address = "unix://${config.systemd.sockets."authelia-${instanceName}".socketConfig.ListenStream}";
+ };
+ };
+
+ secrets = {
+ jwtSecretFile = config.sops.secrets."authelia/jwt".path;
+ storageEncryptionKeyFile = config.sops.secrets."authelia/storage".path;
+ };
+ };
+
+ systemd.sockets."authelia-${instanceName}" = {
+ socketConfig = {
+ Accept = false;
+ ListenStream = "/var/run/authelia.sock";
+ SocketGroup = "authelia";
+ SocketMode = "0660";
+ };
+ };
+
+ systemd.services."authelia-${instanceName}" = {
+ requires = [ "authelia-${instanceName}.socket" ];
+
+ serviceConfig = {
+ RuntimeDirectory = "authelia-${instanceName}";
+ SupplementaryGroups = [ "authelia" ];
+ };
+ };
+
+ # TODO: Need to map these to systemd creds to pass them into the
+ # service because user permissions
+ sops.secrets = {
+ "authelia/users" = { };
+ "authelia/jwt" = { };
+ "authelia/storage" = { };
+ };
+
+ users.groups.authelia = { };
+}
diff --git a/configuration/services/auth/default.nix b/configuration/services/auth/default.nix
new file mode 100644
index 0000000..4b9c671
--- /dev/null
+++ b/configuration/services/auth/default.nix
@@ -0,0 +1,5 @@
+{
+ imports = [
+ ./authelia.nix
+ ];
+}
diff --git a/configuration/services/backups.nix b/configuration/services/backups.nix
index 0ae8abf..688f5f9 100644
--- a/configuration/services/backups.nix
+++ b/configuration/services/backups.nix
@@ -265,18 +265,5 @@ in
};
groups.backup = { };
};
-
- sops.secrets = {
- "restic/storagebox-backups" = {
- owner = "root";
- group = "backup";
- mode = "0440";
- };
- "restic/storagebox-ssh-key" = {
- owner = "backup";
- group = "backup";
- mode = "0040";
- };
- };
};
}
diff --git a/configuration/services/battery-manager.nix b/configuration/services/battery-manager.nix
index 0c58c7b..9da7e32 100644
--- a/configuration/services/battery-manager.nix
+++ b/configuration/services/battery-manager.nix
@@ -13,9 +13,4 @@
log_level = "DEBUG";
};
};
-
- sops.secrets = {
- "battery-manager/email" = { };
- "battery-manager/password" = { };
- };
}
diff --git a/configuration/services/conduit/default.nix b/configuration/services/conduit/default.nix
index e1a5f60..4ba5271 100644
--- a/configuration/services/conduit/default.nix
+++ b/configuration/services/conduit/default.nix
@@ -7,52 +7,25 @@
let
inherit (lib.strings) concatMapStringsSep;
- cfg = config.services.matrix-continuwuity;
+ cfg = config.services.matrix-conduit;
domain = "matrix.${config.services.nginx.domain}";
turn-realm = "turn.${config.services.nginx.domain}";
in
{
- imports = [ ./heisenbridge.nix ];
-
- networking.firewall = {
- allowedTCPPorts = [
- # These are for "normal" clients
- 80
- 443
-
- # Federation happens on 8448
- 8448
-
- config.services.coturn.listening-port
- config.services.coturn.tls-listening-port
- config.services.coturn.alt-listening-port
- config.services.coturn.alt-tls-listening-port
- ];
-
- allowedUDPPorts = [
- config.services.coturn.listening-port
- config.services.coturn.tls-listening-port
- config.services.coturn.alt-listening-port
- config.services.coturn.alt-tls-listening-port
- ];
-
- allowedUDPPortRanges = [
- {
- from = config.services.coturn.min-port;
- to = config.services.coturn.max-port;
- }
- ];
- };
+ imports = [
+ ./heisenbridge.nix
+ ./matrix-hookshot.nix
+ ];
services = {
- matrix-continuwuity = {
+ matrix-conduit = {
enable = true;
package = pkgs.matrix-continuwuity;
settings.global = {
- address = [ "127.0.0.1" ];
+ address = "127.0.0.1";
server_name = domain;
new_user_displayname_suffix = "🦆";
- turn_secret_file = "/run/credentials/continuwuity.service/turn-secret";
+ allow_check_for_updates = true;
# Set up delegation: https://docs.conduit.rs/delegation.html#automatic-recommended
# This is primarily to make sliding sync work
@@ -173,39 +146,37 @@ in
locations = {
"/_matrix" = {
- proxyPass = "http://${lib.head cfg.settings.global.address}:${toString cfg.settings.global.port}";
+ proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
# Recommended by conduit
extraConfig = ''
proxy_buffering off;
'';
};
"/.well-known/matrix" = {
- proxyPass = "http://${lib.head cfg.settings.global.address}:${toString cfg.settings.global.port}";
+ proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
};
};
};
backups.conduit = {
user = "root";
- paths = [ "/var/lib/private/matrix-continuwuity/" ];
+ paths = [ "/var/lib/private/matrix-conduit/" ];
# Other services store their data in conduit, so no other services
# need to be shut down currently.
- pauseServices = [ "continuwuity.service" ];
+ pauseServices = [ "conduit.service" ];
};
};
- systemd.services.continuwuity.serviceConfig.LoadCredential = "turn-secret:${
- config.sops.secrets."turn/env".path
- }";
+ systemd.services.conduit.serviceConfig = {
+ ExecStart = lib.mkForce "${config.services.matrix-conduit.package}/bin/conduwuit";
+ # Pass in the TURN secret via EnvironmentFile, not supported by
+ # upstream module currently.
+ #
+ # See also https://gitlab.com/famedly/conduit/-/issues/314
+ EnvironmentFile = config.sops.secrets."turn/env".path;
+ };
systemd.services.coturn.serviceConfig.SupplementaryGroups = [
config.security.acme.certs."tlater.net".group
];
-
- sops.secrets = {
- "turn/env" = { };
- "turn/secret" = {
- owner = "turnserver";
- };
- };
}
diff --git a/configuration/services/conduit/heisenbridge.nix b/configuration/services/conduit/heisenbridge.nix
index 0fe6bc5..f0f7e49 100644
--- a/configuration/services/conduit/heisenbridge.nix
+++ b/configuration/services/conduit/heisenbridge.nix
@@ -5,7 +5,7 @@
...
}:
let
- conduitCfg = config.services.matrix-continuwuity;
+ conduitCfg = config.services.matrix-conduit;
matrixLib = pkgs.callPackage ./lib.nix { };
in
{
@@ -36,7 +36,7 @@ in
{
description = "Matrix<->IRC bridge";
wantedBy = [ "multi-user.target" ];
- after = [ "continuwuity.service" ];
+ after = [ "conduit.service" ];
serviceConfig = {
Type = "exec";
@@ -75,10 +75,4 @@ in
# AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
};
};
-
- sops.secrets = {
- # Accessed via systemd cred through /run/secrets/heisebridge
- "heisenbridge/as-token" = { };
- "heisenbridge/hs-token" = { };
- };
}
diff --git a/configuration/services/conduit/matrix-hookshot.nix b/configuration/services/conduit/matrix-hookshot.nix
new file mode 100644
index 0000000..6b788b2
--- /dev/null
+++ b/configuration/services/conduit/matrix-hookshot.nix
@@ -0,0 +1,166 @@
+{
+ pkgs,
+ lib,
+ config,
+ ...
+}:
+let
+ matrixLib = pkgs.callPackage ./lib.nix { };
+
+ cfg = config.services.matrix-hookshot;
+ conduitCfg = config.services.matrix-conduit;
+
+ domain = conduitCfg.settings.global.server_name;
+
+ registration = matrixLib.writeRegistrationScript {
+ id = "matrix-hookshot";
+ url = "http://127.0.0.1:9993";
+ sender_localpart = "hookshot";
+
+ namespaces = {
+ aliases = [ ];
+ rooms = [ ];
+ users = [
+ {
+ regex = "@${cfg.settings.generic.userIdPrefix}.*:${domain}";
+ exclusive = true;
+ }
+ ];
+ };
+
+ # Encryption support
+ # TODO(tlater): Enable when
+ # https://github.com/matrix-org/matrix-hookshot/issues/1060 is
+ # fixed
+ # extraSettings = {
+ # "de.sorunome.msc2409.push_ephemeral" = true;
+ # push_ephemeral = true;
+ # "org.matrix.msc3202" = true;
+ # };
+
+ runtimeRegistration = "${cfg.registrationFile}";
+ };
+in
+{
+ # users = {
+ # users.matrix-hookshot = {
+ # home = "/run/matrix-hookshot";
+ # group = "matrix-hookshot";
+ # isSystemUser = true;
+ # };
+
+ # groups.matrix-hookshot = { };
+ # };
+
+ systemd.services.matrix-hookshot = {
+ serviceConfig = {
+ Type = lib.mkForce "exec";
+
+ LoadCredential = "matrix-hookshot:/run/secrets/matrix-hookshot";
+ inherit (registration) ExecStartPre;
+
+ # Some library in matrix-hookshot wants a home directory
+ Environment = [ "HOME=/run/matrix-hookshot" ];
+
+ # User = "matrix-hookshot";
+ DynamicUser = true;
+ StateDirectory = "matrix-hookshot";
+ RuntimeDirectory = "matrix-hookshot";
+ RuntimeDirectoryMode = "0700";
+
+ RestrictNamespaces = true;
+ PrivateUsers = true;
+ ProtectHostname = true;
+ ProtectClock = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectKernelLogs = true;
+ ProtectControlGroups = true;
+ RestrictAddressFamilies = [
+ # "AF_UNIX"
+ "AF_INET"
+ "AF_INET6"
+ ];
+ LockPersonality = true;
+ RestrictRealtime = true;
+ ProtectProc = "invisible";
+ ProcSubset = "pid";
+ UMask = 77;
+ };
+ };
+
+ # services.redis.servers.matrix-hookshot = {
+ # enable = true;
+ # user = "matrix-hookshot";
+ # };
+
+ services.matrix-hookshot = {
+ enable = true;
+
+ serviceDependencies = [ "conduit.service" ];
+
+ registrationFile = "/run/matrix-hookshot/registration.yaml";
+
+ settings = {
+ bridge = {
+ inherit domain;
+ url = "http://localhost:${toString conduitCfg.settings.global.port}";
+ mediaUrl = conduitCfg.settings.global.well_known.client;
+ port = 9993;
+ bindAddress = "127.0.0.1";
+ };
+
+ bot.displayname = "Hookshot";
+
+ # cache.redisUri = "redis://${config.services.redis.servers.matrix-hookshot.unixSocket}";
+
+ generic = {
+ enabled = true;
+ outbound = false;
+ # Only allow webhooks from localhost for the moment
+ urlPrefix = "http://127.0.0.1:9000/webhook";
+ userIdPrefix = "_webhooks_";
+ allowJsTransformationFunctions = true;
+ };
+
+ # TODO(tlater): Enable when
+ # https://github.com/matrix-org/matrix-hookshot/issues/1060 is
+ # fixed
+ # encryption.storagePath = "/var/lib/matrix-hookshot/cryptostore";
+
+ permissions = [
+ {
+ actor = "matrix.tlater.net";
+ services = [
+ {
+ service = "*";
+ level = "notifications";
+ }
+ ];
+ }
+ {
+ actor = "@tlater:matrix.tlater.net";
+ services = [
+ {
+ service = "*";
+ level = "admin";
+ }
+ ];
+ }
+ ];
+
+ listeners = [
+ {
+ port = 9000;
+ resources = [ "webhooks" ];
+ }
+ {
+ port = 9001;
+ resources = [ "metrics" ];
+ }
+ ];
+
+ metrics.enabled = true;
+ };
+ };
+}
diff --git a/configuration/services/configs/starbound.json b/configuration/services/configs/starbound.json
new file mode 100644
index 0000000..d995fdf
--- /dev/null
+++ b/configuration/services/configs/starbound.json
@@ -0,0 +1,50 @@
+{
+ "allowAdminCommands" : true,
+ "allowAdminCommandsFromAnyone" : false,
+ "allowAnonymousConnections" : true,
+ "allowAssetsMismatch" : true,
+ "anonymousConnectionsAreAdmin" : false,
+ "bannedIPs" : [],
+ "bannedUuids" : [],
+ "checkAssetsDigest" : false,
+ "clearPlayerFiles" : false,
+ "clearUniverseFiles" : false,
+ "clientIPJoinable" : false,
+ "clientP2PJoinable" : true,
+ "configurationVersion" : {
+ "basic" : 2,
+ "server" : 4
+ },
+ "crafting" : {
+ "filterHaveMaterials" : false
+ },
+ "gameServerBind" : "::",
+ "gameServerPort" : 21025,
+ "interactiveHighlight" : true,
+ "inventory" : {
+ "pickupToActionBar" : true
+ },
+ "maxPlayers" : 8,
+ "maxTeamSize" : 4,
+ "monochromeLighting" : false,
+ "playerBackupFileCount" : 3,
+ "queryServerBind" : "::",
+ "queryServerPort" : 21025,
+ "rconServerBind" : "::",
+ "rconServerPassword" : "",
+ "rconServerPort" : 21026,
+ "rconServerTimeout" : 1000,
+ "runQueryServer" : false,
+ "runRconServer" : false,
+ "safeScripts" : true,
+ "scriptInstructionLimit" : 10000000,
+ "scriptInstructionMeasureInterval" : 10000,
+ "scriptProfilingEnabled" : false,
+ "scriptRecursionLimit" : 100,
+ "serverFidelity" : "automatic",
+ "serverName" : "tlater.net",
+ "serverOverrideAssetsDigest" : null,
+ "serverUsers" : {
+ },
+ "tutorialMessages" : true
+}
diff --git a/configuration/services/crowdsec.nix b/configuration/services/crowdsec.nix
index 99eaa11..174115b 100644
--- a/configuration/services/crowdsec.nix
+++ b/configuration/services/crowdsec.nix
@@ -1,80 +1,45 @@
-{ config, lib, ... }:
{
- services = {
- crowdsec = {
- enable = true;
- autoUpdateService = true;
+ pkgs,
+ config,
+ lib,
+ ...
+}:
+{
+ security.crowdsec = {
+ enable = true;
- settings = {
- general.api.server = {
- enable = true;
- online_client.sharing = false;
- };
+ parserWhitelist = [ "10.45.249.2" ];
- lapi.credentialsFile = "/var/lib/crowdsec/state/local_credentials.yaml";
- };
+ extraGroups = [
+ "systemd-journal"
+ "nginx"
+ ];
- hub = {
- collections = [
- "crowdsecurity/base-http-scenarios"
- "crowdsecurity/http-cve"
- "crowdsecurity/linux"
- "crowdsecurity/nextcloud"
- "crowdsecurity/nginx"
- "crowdsecurity/sshd"
- ];
- };
+ acquisitions = [
+ {
+ source = "journalctl";
+ labels.type = "syslog";
+ journalctl_filter = [ "SYSLOG_IDENTIFIER=Nextcloud" ];
+ }
- localConfig = {
- acquisitions = [
- {
- labels.type = "syslog";
- journalctl_filter = [
- "SYSLOG_IDENTIFIER=Nextcloud"
- "SYSLOG_IDENTIFIER=sshd-session"
- ];
- source = "journalctl";
- }
+ {
+ source = "journalctl";
+ labels.type = "syslog";
+ journalctl_filter = [ "SYSLOG_IDENTIFIER=sshd-session" ];
+ }
- {
- labels.type = "nginx";
- filenames = [
- "/var/log/nginx/*.log"
- ]
- ++ lib.mapAttrsToList (
- vHost: _: "/var/log/nginx/${vHost}/access.log"
- ) config.services.nginx.virtualHosts;
- }
- ];
+ {
+ labels.type = "nginx";
+ filenames = [
+ "/var/log/nginx/*.log"
+ ]
+ ++ lib.mapAttrsToList (
+ vHost: _: "/var/log/nginx/${vHost}/access.log"
+ ) config.services.nginx.virtualHosts;
+ }
+ ];
- parsers.s02Enrich = [
- {
- name = "nixos/parser-whitelist";
- description = "Parser whitelist generated by the crowdsec NixOS module";
- whitelist = {
- reason = "Filtered by NixOS whitelist";
- ip = [ "10.45.249.2" ];
- };
- }
- ];
-
- postOverflows.s01Whitelist = [
- {
- description = "custom matrix whitelist";
- name = "tetsumaki/matrix";
- whitelist = {
- reason = "whitelist false positive for matrix";
- expression = [
- "evt.Overflow.Alert.Events[0].GetMeta('target_fqdn') == '${config.services.matrix-continuwuity.settings.global.server_name}'"
- "evt.Overflow.Alert.GetScenario() in ['crowdsecurity/http-probing', 'crowdsecurity/http-crawl-non_statics']"
- ];
- };
- }
- ];
- };
- };
-
- crowdsec-firewall-bouncer = {
+ remediationComponents.firewallBouncer = {
enable = true;
settings.prometheus = {
enabled = true;
@@ -82,23 +47,37 @@
listen_port = "60601";
};
};
-
- victoriametrics.scrapeConfigs = {
- crowdsec.targets =
- let
- cfg = config.services.crowdsec.settings.general;
- address = cfg.prometheus.listen_addr;
- port = cfg.prometheus.listen_port;
- in
- [ "${address}:${toString port}" ];
-
- csFirewallBouncer.targets =
- let
- cfg = config.services.crowdsec-firewall-bouncer.settings;
- address = cfg.prometheus.listen_addr;
- port = cfg.prometheus.listen_port;
- in
- [ "${address}:${toString port}" ];
- };
};
+
+ # Add whitelists for matrix
+ systemd.tmpfiles.settings."10-matrix" =
+ let
+ stateDir = config.security.crowdsec.stateDirectory;
+ in
+ {
+ "${stateDir}/config/postoverflows".d = {
+ user = "crowdsec";
+ group = "crowdsec";
+ mode = "0700";
+ };
+
+ "${stateDir}/config/postoverflows/s01-whitelist".d = {
+ user = "crowdsec";
+ group = "crowdsec";
+ mode = "0700";
+ };
+
+ "${stateDir}/config/postoverflows/s01-whitelist/matrix-whitelist.yaml"."L+".argument =
+ ((pkgs.formats.yaml { }).generate "crowdsec-matrix-whitelist.yaml" {
+ name = "tetsumaki/matrix";
+ description = "custom matrix whitelist";
+ whitelist = {
+ reason = "whitelist false positive for matrix";
+ expression = [
+ "evt.Overflow.Alert.Events[0].GetMeta('target_fqdn') == '${config.services.matrix-conduit.settings.global.server_name}'"
+ "evt.Overflow.Alert.GetScenario() in ['crowdsecurity/http-probing', 'crowdsecurity/http-crawl-non_statics']"
+ ];
+ };
+ }).outPath;
+ };
}
diff --git a/configuration/services/default.nix b/configuration/services/default.nix
deleted file mode 100644
index bee8f44..0000000
--- a/configuration/services/default.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- imports = [
- ./backups.nix
- ./battery-manager.nix
- ./conduit
- ./crowdsec.nix
- ./foundryvtt.nix
- ./gitea.nix
- ./immich.nix
- ./metrics
- ./ntfy-sh
- ./minecraft.nix
- ./nextcloud.nix
- ./webserver.nix
- ./wireguard.nix
- ];
-}
diff --git a/configuration/services/foundryvtt.nix b/configuration/services/foundryvtt.nix
index d7b9d02..6c475a3 100644
--- a/configuration/services/foundryvtt.nix
+++ b/configuration/services/foundryvtt.nix
@@ -11,11 +11,6 @@ in
{
imports = [ flake-inputs.foundryvtt.nixosModules.foundryvtt ];
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
services = {
foundryvtt = {
enable = true;
@@ -23,7 +18,7 @@ in
minifyStaticFiles = true;
proxySSL = true;
proxyPort = 443;
- package = flake-inputs.foundryvtt.packages.${pkgs.stdenv.hostPlatform.system}.foundryvtt_13;
+ package = flake-inputs.foundryvtt.packages.${pkgs.system}.foundryvtt_13;
};
nginx.virtualHosts."${domain}" =
diff --git a/configuration/services/gitea.nix b/configuration/services/gitea.nix
index b4dd719..613d30c 100644
--- a/configuration/services/gitea.nix
+++ b/configuration/services/gitea.nix
@@ -8,11 +8,6 @@ let
domain = "gitea.${config.services.nginx.domain}";
in
{
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
services = {
forgejo = {
enable = true;
diff --git a/configuration/services/immich.nix b/configuration/services/immich.nix
index 1255490..516ea3e 100644
--- a/configuration/services/immich.nix
+++ b/configuration/services/immich.nix
@@ -8,19 +8,11 @@ let
hostName = "immich.${config.services.nginx.domain}";
in
{
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
services = {
immich = {
enable = true;
settings.server.externalDomain = "https://${hostName}";
- # We're using vectorchord now
- database.enableVectors = false;
-
environment.IMMICH_TELEMETRY_INCLUDE = "all";
};
diff --git a/configuration/services/metrics/exporters.nix b/configuration/services/metrics/exporters.nix
index ea57c9b..52c2a46 100644
--- a/configuration/services/metrics/exporters.nix
+++ b/configuration/services/metrics/exporters.nix
@@ -1,4 +1,9 @@
-{ pkgs, ... }:
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}:
let
yaml = pkgs.formats.yaml { };
in
@@ -63,6 +68,28 @@ in
enable = true;
listenAddress = "127.0.0.1";
};
+
+ nginxlog = {
+ enable = true;
+ listenAddress = "127.0.0.1";
+ group = "nginx";
+
+ settings.namespaces = lib.mapAttrsToList (name: _: {
+ inherit name;
+ metrics_override.prefix = "nginxlog";
+ namespace_label = "vhost";
+
+ format = lib.concatStringsSep " " [
+ "$remote_addr - $remote_user [$time_local]"
+ ''"$request" $status $body_bytes_sent''
+ ''"$http_referer" "$http_user_agent"''
+ ''rt=$request_time uct="$upstream_connect_time"''
+ ''uht="$upstream_header_time" urt="$upstream_response_time"''
+ ];
+
+ source.files = [ "/var/log/nginx/${name}/access.log" ];
+ }) config.services.nginx.virtualHosts;
+ };
};
# TODO(tlater):
diff --git a/configuration/services/metrics/grafana.nix b/configuration/services/metrics/grafana.nix
index 3b757df..b30806c 100644
--- a/configuration/services/metrics/grafana.nix
+++ b/configuration/services/metrics/grafana.nix
@@ -3,110 +3,68 @@ let
domain = "metrics.${config.services.nginx.domain}";
in
{
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
- services = {
- grafana = {
- enable = true;
- settings = {
- server = {
- http_port = 3001; # Default overlaps with gitea
- root_url = "https://metrics.tlater.net";
- };
-
- security = {
- admin_user = "tlater";
- admin_password = "$__file{${config.sops.secrets."grafana/adminPassword".path}}";
- secret_key = "$__file{${config.sops.secrets."grafana/secretKey".path}}";
- cookie_secure = true;
- cookie_samesite = "strict";
- content_security_policy = true;
- };
-
- database = {
- user = "grafana";
- name = "grafana";
- type = "postgres";
- host = "/run/postgresql";
- };
+ services.grafana = {
+ enable = true;
+ settings = {
+ server = {
+ http_port = 3001; # Default overlaps with gitea
+ root_url = "https://metrics.tlater.net";
};
- declarativePlugins = [
- pkgs.grafanaPlugins.victoriametrics-metrics-datasource
- pkgs.grafanaPlugins.victoriametrics-logs-datasource
- ];
+ security = {
+ admin_user = "tlater";
+ admin_password = "$__file{${config.sops.secrets."grafana/adminPassword".path}}";
+ secret_key = "$__file{${config.sops.secrets."grafana/secretKey".path}}";
+ cookie_secure = true;
+ cookie_samesite = "strict";
+ content_security_policy = true;
+ };
- provision = {
- enable = true;
-
- datasources.settings.datasources = [
- {
- name = "Victoriametrics - tlater.net";
- url = "http://localhost:8428";
- type = "victoriametrics-metrics-datasource";
- access = "proxy";
- isDefault = true;
- }
-
- {
- name = "Victorialogs - tlater.net";
- url = "http://${config.services.victorialogs.bindAddress}";
- type = "victoriametrics-logs-datasource";
- access = "proxy";
- }
- ];
-
- alerting.contactPoints.settings.contactPoints = [
- {
- name = "ntfy";
- receivers = [
- {
- uid = "ntfy";
- type = "webhook";
- settings.url = "http://${config.services.ntfy-sh.settings.listen-http}/local-alerts?template=grafana";
- }
- ];
- }
- ];
+ database = {
+ user = "grafana";
+ name = "grafana";
+ type = "postgres";
+ host = "/run/postgresql";
};
};
- postgresql = {
- ensureUsers = [
+ declarativePlugins = [
+ pkgs.grafanaPlugins.victoriametrics-metrics-datasource
+ pkgs.grafanaPlugins.victoriametrics-logs-datasource
+ ];
+
+ provision = {
+ enable = true;
+
+ datasources.settings.datasources = [
{
- name = "grafana";
- ensureDBOwnership = true;
+ name = "Victoriametrics - tlater.net";
+ url = "http://localhost:8428";
+ type = "victoriametrics-metrics-datasource";
+ access = "proxy";
+ isDefault = true;
+ }
+
+ {
+ name = "Victorialogs - tlater.net";
+ url = "http://${config.services.victorialogs.bindAddress}";
+ type = "victoriametrics-logs-datasource";
+ access = "proxy";
}
];
-
- ensureDatabases = [ "grafana" ];
- };
-
- nginx.virtualHosts."${domain}" = {
- forceSSL = true;
- useACMEHost = "tlater.net";
- enableHSTS = true;
- locations = {
- "/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
- "/api/live" = {
- proxyWebsockets = true;
- proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
- };
- };
};
};
- sops.secrets = {
- "grafana/adminPassword" = {
- owner = "grafana";
- group = "grafana";
- };
- "grafana/secretKey" = {
- owner = "grafana";
- group = "grafana";
+ services.nginx.virtualHosts."${domain}" = {
+ forceSSL = true;
+ useACMEHost = "tlater.net";
+ enableHSTS = true;
+ locations = {
+ "/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
+ "/api/live" = {
+ proxyWebsockets = true;
+ proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
+ };
};
};
}
diff --git a/configuration/services/metrics/victoriametrics.nix b/configuration/services/metrics/victoriametrics.nix
index 3befec0..f37b8b0 100644
--- a/configuration/services/metrics/victoriametrics.nix
+++ b/configuration/services/metrics/victoriametrics.nix
@@ -4,7 +4,7 @@ let
blackbox_port = config.services.prometheus.exporters.blackbox.port;
in
{
- services.victoriametrics = {
+ config.services.victoriametrics = {
enable = true;
extraOptions = [ "-storage.minFreeDiskSpaceBytes=5GB" ];
@@ -68,18 +68,32 @@ in
coturn.targets = [ "127.0.0.1:9641" ];
+ crowdsec.targets =
+ let
+ address = config.security.crowdsec.settings.prometheus.listen_addr;
+ port = config.security.crowdsec.settings.prometheus.listen_port;
+ in
+ [ "${address}:${toString port}" ];
+
+ csFirewallBouncer.targets =
+ let
+ address =
+ config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_addr;
+ port =
+ config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_port;
+ in
+ [ "${address}:${toString port}" ];
+
immich.targets = [
"127.0.0.1:8081"
"127.0.0.1:8082"
];
+ # Configured in the hookshot listeners, but it's hard to filter
+ # the correct values out of that config.
+ matrixHookshot.targets = [ "127.0.0.1:9001" ];
+
victorialogs.targets = [ config.services.victorialogs.bindAddress ];
};
};
-
- sops.secrets."forgejo/metrics-token" = {
- owner = "forgejo";
- group = "metrics";
- mode = "0440";
- };
}
diff --git a/configuration/services/nextcloud.nix b/configuration/services/nextcloud.nix
index 6628d07..4af77a9 100644
--- a/configuration/services/nextcloud.nix
+++ b/configuration/services/nextcloud.nix
@@ -5,15 +5,10 @@
...
}:
let
- nextcloud = pkgs.nextcloud32;
+ nextcloud = pkgs.nextcloud31;
hostName = "nextcloud.${config.services.nginx.domain}";
in
{
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
services = {
nextcloud = {
inherit hostName;
@@ -103,22 +98,6 @@ in
};
};
- services.postgresql = {
- ensureUsers = [
- {
- name = "nextcloud";
- ensureDBOwnership = true;
- }
- ];
-
- ensureDatabases = [ "nextcloud" ];
- };
-
# Ensure that this service doesn't start before postgres is ready
- systemd.services.nextcloud-setup.after = [ "postgresql.target" ];
-
- sops.secrets."nextcloud/tlater" = {
- owner = "nextcloud";
- group = "nextcloud";
- };
+ systemd.services.nextcloud-setup.after = [ "postgresql.service" ];
}
diff --git a/configuration/services/ntfy-sh/default.nix b/configuration/services/ntfy-sh/default.nix
deleted file mode 100644
index 9cca567..0000000
--- a/configuration/services/ntfy-sh/default.nix
+++ /dev/null
@@ -1,188 +0,0 @@
-{
- pkgs,
- config,
- flake-inputs,
- ...
-}:
-let
- domain = "ntfy.${config.services.nginx.domain}";
-in
-{
- imports = [ ./downstream-module.nix ];
-
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
- services.ntfy-sh = {
- enable = true;
-
- environmentFile = config.sops.secrets."ntfy/users".path;
-
- settings = {
- base-url = "https://${domain}";
- listen-http = "127.0.0.1:2586";
- behind-proxy = true;
-
- # Paths
- attachment-cache-dir = "/var/lib/ntfy-sh/attachments";
- cache-file = "/var/lib/ntfy-sh/cache-file.db";
- auth-file = "/var/lib/ntfy-sh/user.db";
- auth-default-access = "deny-all";
- auth-access = [ "*:local-*:wo" ];
-
- # Don't want to host the front-end
- web-root = "disable";
- };
- };
-
- services.nginx.virtualHosts."ntfy.${config.services.nginx.domain}" = {
- forceSSL = true;
- useACMEHost = "tlater.net";
- enableHSTS = true;
-
- locations."/" = {
- proxyWebsockets = true;
- proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
- extraConfig = ''
- client_max_body_size 0; # Stream request body to backend
- '';
- };
-
- # Don't allow writing to topics with the local prefix, *including*
- # webhook writes, since they are set to write-only access from
- # anyone.
- locations."/local" = {
- proxyWebsockets = true;
- proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
- extraConfig = ''
- client_max_body_size 0; # Stream request body to backend
-
- limit_except GET OPTIONS {
- deny all;
- }
-
- location ~ /trigger$ {
- deny all;
- }
- '';
- };
- };
-
- sops.secrets."ntfy/users" = { };
-
- serviceTests = {
- testNtfyConfig = pkgs.testers.runNixOSTest {
- name = "test-ntfy-config";
-
- node.specialArgs = { inherit flake-inputs; };
- nodes = {
- testHost =
- { lib, ... }:
- {
- imports = [
- ./.
- ../../nginx
- ../../../modules/serviceTests/mocks.nix
- ];
-
- services.nginx.domain = "testHost";
-
- # Don't care for testing SSL here
- services.nginx.virtualHosts."ntfy.testHost" = {
- forceSSL = lib.mkForce false;
- enableHSTS = lib.mkForce false;
- };
- };
-
- client =
- { pkgs, ... }:
- {
- environment.systemPackages = [ pkgs.curl ];
- networking.hosts."192.168.1.2" = [ "ntfy.testHost" ];
- };
- };
-
- testScript = ''
- import json
- import time
- from contextlib import contextmanager
-
- def read_client_messages():
- client.wait_for_unit("messages.service")
- messages = [json.loads(line) for line in client.succeed("cat messages").split()]
- client.succeed("systemctl stop messages.service")
- client.succeed("rm messages")
-
- print(messages)
-
- return messages
-
- @contextmanager
- def client_subscribe(topic: str, timeout: int = 2):
- systemd_invocation = [
- "systemd-run",
- "--unit messages",
- "--property=Type=oneshot",
- "--property=SuccessExitStatus=28",
- "--remain-after-exit",
- "--setenv=PATH=$PATH",
- "--same-dir",
- "--no-block",
- "/bin/sh -c"
- ]
-
- curl = [
- "curl",
- "--silent",
- "--show-error",
- f"--max-time {2 + timeout}",
- "-u tlater:insecure",
- f"http://ntfy.testHost/{topic}/json",
- "-o messages"
- ]
-
- client.succeed(f'{" ".join(systemd_invocation)} "{" ".join(curl)}"')
-
- # Give some slack so the host doesn't send messages before
- # we're listening
- time.sleep(2)
-
- yield
-
-
- start_all()
- testHost.wait_for_unit("ntfy-sh.service")
- client.wait_until_succeeds("curl http://ntfy.testHost")
-
- with subtest("subscribing and writing to local topics works"):
- with client_subscribe("local-test"):
- testHost.succeed("curl --fail --silent --show-error --data test http://127.0.0.1:2586/local-test")
-
- messages = read_client_messages()
- t.assertEqual(len(messages), 2)
- t.assertEqual(messages[1].get("message"), "test")
-
- with subtest("writing to non-local topics without auth fails"):
- testHost.fail("curl --fail --silent --show-error --data test http://127.0.0.1:2586/test")
-
- with subtest("writing to *any* topics from outside localhost fails"):
- client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/test")
- client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/local-test")
- # GET requests work by default because websocket shenanigans
- client.fail("curl --fail --silent --show-error http://ntfy.testHost/local-test/trigger?message=test")
-
- with subtest("authenticated messaging works from outside localhost"):
- with client_subscribe("test", 10):
- client.succeed("curl -u tlater:insecure --fail --silent --show-error --data test http://ntfy.testHost/test")
- client.succeed("curl -u tlater:insecure --fail --silent --show-error http://ntfy.testHost/test/trigger?message=test2")
-
- messages = read_client_messages()
- t.assertEqual(len(messages), 3)
- t.assertEqual(messages[1].get("message"), "test")
- t.assertEqual(messages[2].get("message"), "test2")
- '';
- };
- };
-}
diff --git a/configuration/services/ntfy-sh/downstream-module.nix b/configuration/services/ntfy-sh/downstream-module.nix
deleted file mode 100644
index c3a9634..0000000
--- a/configuration/services/ntfy-sh/downstream-module.nix
+++ /dev/null
@@ -1,70 +0,0 @@
-{
- pkgs,
- lib,
- config,
- ...
-}:
-let
- cfg = config.services.ntfy-sh;
- settingsFormat = pkgs.formats.yaml { };
- configFile = settingsFormat.generate "server.yml" cfg.settings;
-in
-{
- # We don't use the upstream module because it's stupid; the author
- # doesn't seem to understand `DynamicUser` (or at least be unaware of
- # systemd credentials).
- disabledModules = [ "services/misc/ntfy-sh.nix" ];
-
- options.services.ntfy-sh = {
- enable = lib.mkEnableOption "[ntfy-sh](https://ntfy.sh), a push notification service";
- package = lib.mkPackageOption pkgs "ntfy-sh" { };
- environmentFile = lib.mkOption {
- type = lib.types.nullOr lib.types.path;
- default = null;
- description = ''
- Environment file; intended to be used for user provisioning.
- '';
- };
-
- settings = lib.mkOption {
- inherit (settingsFormat) type;
- default = { };
- description = ''
- Configuration for ntfy.sh, supported values are [here](https://ntfy.sh/docs/config/#config-options).
- '';
- };
- };
-
- config.systemd.services.ntfy-sh = {
- description = "Push notifications server";
-
- wantedBy = [ "multi-user.target" ];
- after = [ "network.target" ];
-
- serviceConfig = {
- Type = "exec";
- ExecReload = "kill --signal HUP $MAINPID";
- ExecStart = "${lib.getExe' cfg.package "ntfy"} serve -c ${configFile}";
-
- EnvironmentFile = cfg.environmentFile;
-
- StateDirectory = "ntfy-sh";
-
- DynamicUser = true;
- PrivateTmp = true;
- NoNewPrivileges = true;
- ProtectSystem = "full";
- ProtectKernelTunables = true;
- ProtectKernelModules = true;
- ProtectKernelLogs = true;
- ProtectControlGroups = true;
- PrivateDevices = true;
- RestrictSUIDSGID = true;
- RestrictNamespaces = true;
- RestrictRealtime = true;
- MemoryDenyWriteExecute = true;
- # Upstream Recommandation
- LimitNOFILE = 20500;
- };
- };
-}
diff --git a/configuration/services/postgres.nix b/configuration/services/postgres.nix
new file mode 100644
index 0000000..ddb2c82
--- /dev/null
+++ b/configuration/services/postgres.nix
@@ -0,0 +1,40 @@
+{ pkgs, ... }:
+{
+ services.postgresql = {
+ package = pkgs.postgresql_14;
+ enable = true;
+
+ # Only enable connections via the unix socket, and check with the
+ # OS to make sure the user matches the database name.
+ #
+ # See https://www.postgresql.org/docs/current/auth-pg-hba-conf.html
+ authentication = ''
+ local sameuser all peer
+ '';
+
+ # Note: The following options with ensure.* are set-only; i.e.,
+ # when permissions/users/databases are removed from these lists,
+ # that operation needs to be performed manually on the system as
+ # well.
+ ensureUsers = [
+ {
+ name = "authelia";
+ ensureDBOwnership = true;
+ }
+ {
+ name = "grafana";
+ ensureDBOwnership = true;
+ }
+ {
+ name = "nextcloud";
+ ensureDBOwnership = true;
+ }
+ ];
+
+ ensureDatabases = [
+ "authelia"
+ "grafana"
+ "nextcloud"
+ ];
+ };
+}
diff --git a/configuration/services/starbound.nix b/configuration/services/starbound.nix
new file mode 100644
index 0000000..f5b23c3
--- /dev/null
+++ b/configuration/services/starbound.nix
@@ -0,0 +1,117 @@
+{ pkgs, lib, ... }:
+let
+ inherit (lib) concatStringsSep;
+in
+{
+ # Sadly, steam-run requires some X libs
+ environment.noXlibs = false;
+
+ systemd.services.starbound = {
+ description = "Starbound";
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.local.starbound}/bin/launch-starbound ${./configs/starbound.json}";
+
+ Type = "simple";
+
+ # Credential loading for steam auth (if necessary; prefer
+ # anonymous login wherever possible).
+ LoadCredential = "steam:/run/secrets/steam/tlater";
+
+ # Security settings
+ DynamicUser = true;
+
+ # This is where the StateDirectory ends up
+ WorkingDirectory = "/var/lib/starbound";
+ # Creates /var/lib/starbound (or rather, a symlink there to
+ # /var/lib/private/starbound), and sets it up to be writeable to
+ # by the dynamic user.
+ StateDirectory = "starbound";
+
+ # Note some settings below are basically tautologous with
+ # `NoNewPrivileges`, but they all work slightly differently so
+ # add additional layers in case of bugs.
+
+ ## THESE SETTINGS ARE A GOOD IDEA BUT THE STEAM CLIENT IS
+ ## REALLY, REALLY BAD, AND FOR SOME REASON I NEED TO USE IT TO
+ ## DOWNLOAD GAME SERVERS AS WELL:
+ ##
+ # To guarantee the above (only permits 64-bit syscalls, 32-bit
+ # syscalls can circumvent the above restrictions).
+ #
+ # Obviously, if running a 32 bit game server, change this.
+ # SystemCallArchitectures = "native";
+ # Game servers shouldn't need to create new namespaces ever.
+ #
+ # TODO: Since steam uses namespaces for things *entirely
+ # unrelated* to installing game servers, we need to allow
+ # namespace access. Ideally I'd instead do this in an
+ # ExecStartPre, but alas, this isn't possible because of
+ # https://github.com/systemd/systemd/issues/19604.
+ #
+ # RestrictNamespaces = true;
+
+ # Don't need to let the game server see other user accounts
+ PrivateUsers = true;
+ # *Probably* not harmful for game servers, which probably don't update dynamically
+ ProtectHostname = true;
+ # Yeah, if a game server tries to edit the hardware clock something's fishy
+ ProtectClock = true;
+ # Don't let game servers modify kernel settings, duh
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectKernelLogs = true;
+ # Game servers shouldn't use cgroups themselves either
+ ProtectControlGroups = true;
+ # Most game servers will never need other socket types
+ RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+ # Also a no-brainer, no game server should ever need this
+ LockPersonality = true;
+ # Some game servers will probably try to set this, but they
+ # don't need it. It's only required for audio processing and
+ # such, which the server end doesn't need to do.
+ RestrictRealtime = true;
+ # Don't allow a variety of syscalls that gameservers have no
+ # business using anyway
+ SystemCallFilter =
+ "~"
+ + (concatStringsSep " " [
+ "@clock"
+ "@cpu-emulation"
+ "@debug"
+ "@keyring"
+ "@memlock"
+ "@module"
+ # "@mount" TODO: Consider adding when steamcmd is run in ExecStartPre
+ "@obsolete"
+ "@raw-io"
+ "@reboot"
+ # "@resources" TODO: Ditto
+ "@setuid"
+ "@swap"
+ ]);
+ # Normally only "read-only", but steamcmd will puke if there is
+ # no home directory to write to (though the nix package will
+ # implicitly symlink to the path that we set in its override, so
+ # no actual files are created, besides a symlink).
+ ProtectHome = "tmpfs";
+
+ # Implied by DynamicUser anyway, but it doesn't hurt to add
+ # these explicitly, at least for reference.
+ RemoveIPC = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ NoNewPrivileges = true;
+ RestrictSUIDSGID = true;
+ ProtectSystem = "strict";
+ # ProtectHome = "read-only"; # See further up
+ };
+ };
+
+ services.backups.starbound = {
+ user = "root";
+ paths = [ "/var/lib/private/starbound/storage/universe/" ];
+ pauseServices = [ "starbound.service" ];
+ };
+}
diff --git a/configuration/services/webserver.nix b/configuration/services/webserver.nix
index ffe7480..864f6c0 100644
--- a/configuration/services/webserver.nix
+++ b/configuration/services/webserver.nix
@@ -1,79 +1,28 @@
-{
- pkgs,
- config,
- lib,
- flake-inputs,
- ...
-}:
+{ config, ... }:
let
inherit (config.services.nginx) domain;
in
{
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
- systemd.services.tlaternet-webserver = {
- description = "tlater.net webserver";
- wantedBy = [ "multi-user.target" ];
- after = [ "network.target" ];
-
- script = ''
- ${lib.getExe flake-inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.webserver}
- '';
-
- environment = {
- TLATERNET_NTFY_INSTANCE = "http://${config.services.ntfy-sh.settings.listen-http}";
- LEPTOS_SITE_ADDR = "127.0.0.1:8000";
- };
-
- serviceConfig = {
- Type = "exec";
- LoadCredential = "ntfy-topic:/run/secrets/tlaternet/ntfy-topic";
-
- DynamicUser = true;
- ProtectHome = true; # Override the default (read-only)
- PrivateDevices = true;
- PrivateIPC = true;
- PrivateUsers = true;
- ProtectHostname = true;
- ProtectClock = true;
- ProtectKernelTunables = true;
- ProtectKernelModules = true;
- ProtectKernelLogs = true;
- ProtectControlGroups = true;
- RestrictAddressFamilies = [
- "AF_UNIX"
- "AF_INET"
- "AF_INET6"
- ];
- RestrictNamespaces = true;
- LockPersonality = true;
- MemoryDenyWriteExecute = true;
- RestrictRealtime = true;
- RestrictSUIDSGID = true;
- SystemCallArchitectures = "native";
- SystemCallFilter = [
- "@system-service"
- "~@privileged @resources @setuid @keyring"
- ];
+ services.tlaternet-webserver = {
+ enable = true;
+ listen = {
+ addr = "127.0.0.1";
+ port = 8000;
};
};
# Set up SSL
- services.nginx.virtualHosts."${domain}" = {
- serverAliases = [ "www.${domain}" ];
+ services.nginx.virtualHosts."${domain}" =
+ let
+ inherit (config.services.tlaternet-webserver.listen) addr port;
+ in
+ {
+ serverAliases = [ "www.${domain}" ];
- forceSSL = true;
- useACMEHost = "tlater.net";
- enableHSTS = true;
+ forceSSL = true;
+ useACMEHost = "tlater.net";
+ enableHSTS = true;
- locations."/".proxyPass =
- "http://${config.systemd.services.tlaternet-webserver.environment.LEPTOS_SITE_ADDR}";
- };
-
- sops.secrets = {
- "tlaternet/ntfy-topic" = { };
- };
+ locations."/".proxyPass = "http://${addr}:${toString port}";
+ };
}
diff --git a/configuration/services/wireguard.nix b/configuration/services/wireguard.nix
index d4ab090..a6b7763 100644
--- a/configuration/services/wireguard.nix
+++ b/configuration/services/wireguard.nix
@@ -62,10 +62,4 @@
};
};
};
-
- sops.secrets."wireguard/server-key" = {
- owner = "root";
- group = "systemd-network";
- mode = "0440";
- };
}
diff --git a/configuration/sops.nix b/configuration/sops.nix
new file mode 100644
index 0000000..0337438
--- /dev/null
+++ b/configuration/sops.nix
@@ -0,0 +1,89 @@
+{
+ sops = {
+ defaultSopsFile = ../keys/production.yaml;
+
+ secrets = {
+ "battery-manager/email" = { };
+
+ "battery-manager/password" = { };
+
+ # Gitea
+ "forgejo/metrics-token" = {
+ owner = "forgejo";
+ group = "metrics";
+ mode = "0440";
+ };
+
+ # Grafana
+ "grafana/adminPassword" = {
+ owner = "grafana";
+ group = "grafana";
+ };
+ "grafana/secretKey" = {
+ owner = "grafana";
+ group = "grafana";
+ };
+
+ # Heisenbridge
+ "heisenbridge/as-token" = { };
+ "heisenbridge/hs-token" = { };
+
+ # Matrix-hookshot
+ "matrix-hookshot/as-token" = { };
+ "matrix-hookshot/hs-token" = { };
+
+ # Nextcloud
+ "nextcloud/tlater" = {
+ owner = "nextcloud";
+ group = "nextcloud";
+ };
+
+ # Porkbub/ACME
+ "porkbun/api-key" = {
+ owner = "acme";
+ };
+ "porkbun/secret-api-key" = {
+ owner = "acme";
+ };
+
+ # Restic
+ "restic/local-backups" = {
+ owner = "root";
+ group = "backup";
+ mode = "0440";
+ };
+ "restic/storagebox-backups" = {
+ owner = "root";
+ group = "backup";
+ mode = "0440";
+ };
+ "restic/storagebox-ssh-key" = {
+ owner = "backup";
+ group = "backup";
+ mode = "0040";
+ };
+
+ # Steam
+ "steam/tlater" = { };
+
+ # Turn
+ "turn/env" = { };
+ "turn/secret" = {
+ owner = "turnserver";
+ };
+ "turn/ssl-key" = {
+ owner = "turnserver";
+ };
+ "turn/ssl-cert" = {
+ owner = "turnserver";
+ };
+
+ # Wireguard
+ "wireguard/server-key" = {
+ owner = "root";
+ group = "systemd-network";
+ mode = "0440";
+ };
+ };
+ };
+}
diff --git a/dev-utils.nix b/dev-utils.nix
deleted file mode 100644
index bfa0d17..0000000
--- a/dev-utils.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-{ self, ... }:
-{
- # Systems on which to make dev utilities runnable; anything
- # NixOS-related encodes its own system.
- systems = [ "x86_64-linux" ];
-
- perSystem =
- {
- inputs',
- self',
- pkgs,
- lib,
- ...
- }:
- {
- apps = {
- default = self'.apps.runVm;
-
- runVm = {
- type = "app";
- program = lib.getExe self.nixosConfigurations.hetzner-1.config.system.build.vm;
- meta.description = "Run the test VM";
- };
- };
-
- devShells = {
- default = pkgs.mkShell {
- sopsPGPKeyDirs = [
- "./keys/hosts/"
- "./keys/users/"
- ];
-
- packages = lib.attrValues {
- inherit (inputs'.sops-nix.packages) sops-import-keys-hook sops-init-gpg-key;
- inherit (pkgs) deploy-rs;
- };
- };
-
- minecraft = pkgs.mkShell { packages = lib.attrValues { inherit (pkgs) packwiz; }; };
- webserver = self'.packages.webserver.devShell;
- };
- };
-}
diff --git a/flake.lock b/flake.lock
index 3094d19..a62c357 100644
--- a/flake.lock
+++ b/flake.lock
@@ -10,10 +10,7 @@
"sonnenshift",
"crate2nix"
],
- "nixpkgs": [
- "sonnenshift",
- "nixpkgs"
- ],
+ "nixpkgs": "nixpkgs_3",
"pre-commit-hooks": [
"sonnenshift",
"crate2nix"
@@ -34,27 +31,90 @@
"type": "github"
}
},
+ "cachix_2": {
+ "inputs": {
+ "devenv": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable"
+ ],
+ "flake-compat": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable"
+ ],
+ "nixpkgs": "nixpkgs_4",
+ "pre-commit-hooks": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable"
+ ]
+ },
+ "locked": {
+ "lastModified": 1716549461,
+ "narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=",
+ "owner": "cachix",
+ "repo": "cachix",
+ "rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "latest",
+ "repo": "cachix",
+ "type": "github"
+ }
+ },
+ "cachix_3": {
+ "inputs": {
+ "devenv": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable"
+ ],
+ "flake-compat": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable"
+ ],
+ "nixpkgs": "nixpkgs_5",
+ "pre-commit-hooks": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable"
+ ]
+ },
+ "locked": {
+ "lastModified": 1716549461,
+ "narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=",
+ "owner": "cachix",
+ "repo": "cachix",
+ "rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "latest",
+ "repo": "cachix",
+ "type": "github"
+ }
+ },
"crate2nix": {
"inputs": {
"cachix": "cachix",
- "crate2nix_stable": [
- "sonnenshift",
- "crate2nix"
- ],
- "devshell": "devshell",
- "flake-compat": [
- "deploy-rs",
- "flake-compat"
- ],
- "flake-parts": [
- "flake-parts"
- ],
- "nix-test-runner": "nix-test-runner",
+ "crate2nix_stable": "crate2nix_stable",
+ "devshell": "devshell_3",
+ "flake-compat": "flake-compat_4",
+ "flake-parts": "flake-parts_3",
+ "nix-test-runner": "nix-test-runner_3",
"nixpkgs": [
"sonnenshift",
"nixpkgs"
],
- "pre-commit-hooks": "pre-commit-hooks"
+ "pre-commit-hooks": "pre-commit-hooks_3"
},
"locked": {
"lastModified": 1739473963,
@@ -70,20 +130,89 @@
"type": "github"
}
},
+ "crate2nix_stable": {
+ "inputs": {
+ "cachix": "cachix_2",
+ "crate2nix_stable": "crate2nix_stable_2",
+ "devshell": "devshell_2",
+ "flake-compat": "flake-compat_3",
+ "flake-parts": "flake-parts_2",
+ "nix-test-runner": "nix-test-runner_2",
+ "nixpkgs": "nixpkgs_7",
+ "pre-commit-hooks": "pre-commit-hooks_2"
+ },
+ "locked": {
+ "lastModified": 1719760004,
+ "narHash": "sha256-esWhRnt7FhiYq0CcIxw9pvH+ybOQmWBfHYMtleaMhBE=",
+ "owner": "nix-community",
+ "repo": "crate2nix",
+ "rev": "1dee214bb20855fa3e1e7bb98d28922ddaff8c57",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "ref": "0.14.1",
+ "repo": "crate2nix",
+ "type": "github"
+ }
+ },
+ "crate2nix_stable_2": {
+ "inputs": {
+ "cachix": "cachix_3",
+ "crate2nix_stable": "crate2nix_stable_3",
+ "devshell": "devshell",
+ "flake-compat": "flake-compat_2",
+ "flake-parts": "flake-parts",
+ "nix-test-runner": "nix-test-runner",
+ "nixpkgs": "nixpkgs_6",
+ "pre-commit-hooks": "pre-commit-hooks"
+ },
+ "locked": {
+ "lastModified": 1712821484,
+ "narHash": "sha256-rGT3CW64cJS9nlnWPFWSc1iEa3dNZecVVuPVGzcsHe8=",
+ "owner": "nix-community",
+ "repo": "crate2nix",
+ "rev": "42883afcad3823fa5811e967fb7bff54bc3c9d6d",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "ref": "0.14.0",
+ "repo": "crate2nix",
+ "type": "github"
+ }
+ },
+ "crate2nix_stable_3": {
+ "inputs": {
+ "flake-utils": "flake-utils"
+ },
+ "locked": {
+ "lastModified": 1702842982,
+ "narHash": "sha256-A9AowkHIjsy1a4LuiPiVP88FMxyCWK41flZEZOUuwQM=",
+ "owner": "nix-community",
+ "repo": "crate2nix",
+ "rev": "75ac2973affa6b9b4f661a7b592cba6e4f51d426",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "ref": "0.12.0",
+ "repo": "crate2nix",
+ "type": "github"
+ }
+ },
"deploy-rs": {
"inputs": {
"flake-compat": "flake-compat",
- "nixpkgs": [
- "nixpkgs"
- ],
+ "nixpkgs": "nixpkgs",
"utils": "utils"
},
"locked": {
- "lastModified": 1770019181,
- "narHash": "sha256-hwsYgDnby50JNVpTRYlF3UR/Rrpt01OrxVuryF40CFY=",
+ "lastModified": 1756719547,
+ "narHash": "sha256-N9gBKUmjwRKPxAafXEk1EGadfk2qDZPBQp4vXWPHINQ=",
"owner": "serokell",
"repo": "deploy-rs",
- "rev": "77c906c0ba56aabdbc72041bf9111b565cdd6171",
+ "rev": "125ae9e3ecf62fb2c0fd4f2d894eb971f1ecaed2",
"type": "github"
},
"original": {
@@ -94,10 +223,56 @@
},
"devshell": {
"inputs": {
- "flake-utils": [
- "deploy-rs",
- "utils"
- ],
+ "flake-utils": "flake-utils_2",
+ "nixpkgs": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1717408969,
+ "narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
+ "owner": "numtide",
+ "repo": "devshell",
+ "rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "devshell",
+ "type": "github"
+ }
+ },
+ "devshell_2": {
+ "inputs": {
+ "flake-utils": "flake-utils_3",
+ "nixpkgs": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1717408969,
+ "narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
+ "owner": "numtide",
+ "repo": "devshell",
+ "rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "devshell",
+ "type": "github"
+ }
+ },
+ "devshell_3": {
+ "inputs": {
+ "flake-utils": "flake-utils_4",
"nixpkgs": [
"sonnenshift",
"crate2nix",
@@ -125,11 +300,11 @@
]
},
"locked": {
- "lastModified": 1771355198,
- "narHash": "sha256-89m5VKxIs8QNiIvLsxHu5NpyhDsoXTtoN801IAurnW4=",
+ "lastModified": 1760701190,
+ "narHash": "sha256-y7UhnWlER8r776JsySqsbTUh2Txf7K30smfHlqdaIQw=",
"owner": "nix-community",
"repo": "disko",
- "rev": "92fceb111901a6f13e81199be4fab95fce86a5c9",
+ "rev": "3a9450b26e69dcb6f8de6e2b07b3fc1c288d85f5",
"type": "github"
},
"original": {
@@ -138,6 +313,48 @@
"type": "github"
}
},
+ "dream2nix": {
+ "inputs": {
+ "nixpkgs": "nixpkgs_8",
+ "purescript-overlay": "purescript-overlay",
+ "pyproject-nix": "pyproject-nix"
+ },
+ "locked": {
+ "lastModified": 1735160684,
+ "narHash": "sha256-n5CwhmqKxifuD4Sq4WuRP/h5LO6f23cGnSAuJemnd/4=",
+ "owner": "nix-community",
+ "repo": "dream2nix",
+ "rev": "8ce6284ff58208ed8961681276f82c2f8f978ef4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "dream2nix",
+ "type": "github"
+ }
+ },
+ "fenix": {
+ "inputs": {
+ "nixpkgs": [
+ "tlaternet-webserver",
+ "nixpkgs"
+ ],
+ "rust-analyzer-src": "rust-analyzer-src"
+ },
+ "locked": {
+ "lastModified": 1737181903,
+ "narHash": "sha256-lvp77MhGzSN+ICd0MugppCjQR6cmlM2iAC5cjy2ZsaA=",
+ "owner": "nix-community",
+ "repo": "fenix",
+ "rev": "ac79bb490b8c1af4bbc587b84c76f9527d6b14f7",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "fenix",
+ "type": "github"
+ }
+ },
"flake-compat": {
"flake": false,
"locked": {
@@ -154,16 +371,80 @@
"type": "github"
}
},
+ "flake-compat_2": {
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "revCount": 57,
+ "type": "tarball",
+ "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
+ }
+ },
+ "flake-compat_3": {
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "revCount": 57,
+ "type": "tarball",
+ "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
+ }
+ },
+ "flake-compat_4": {
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "revCount": 57,
+ "type": "tarball",
+ "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
+ }
+ },
+ "flake-compat_5": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
"flake-parts": {
"inputs": {
- "nixpkgs-lib": "nixpkgs-lib"
+ "nixpkgs-lib": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable",
+ "nixpkgs"
+ ]
},
"locked": {
- "lastModified": 1769996383,
- "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
+ "lastModified": 1719745305,
+ "narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=",
"owner": "hercules-ci",
"repo": "flake-parts",
- "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
+ "rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9",
"type": "github"
},
"original": {
@@ -172,23 +453,138 @@
"type": "github"
}
},
- "flint": {
+ "flake-parts_2": {
"inputs": {
- "nixpkgs": [
+ "nixpkgs-lib": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
"nixpkgs"
]
},
"locked": {
- "lastModified": 1767431140,
- "narHash": "sha256-ug37Jt6r8LP3161suTh6IW+fkx0a7kiSAhAPsVcPrkA=",
- "owner": "NotAShelf",
- "repo": "flint",
- "rev": "7832a5b5f5ef1243818f8f5e357ad1ee2d35d2b7",
+ "lastModified": 1719745305,
+ "narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9",
"type": "github"
},
"original": {
- "owner": "NotAShelf",
- "repo": "flint",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "flake-parts_3": {
+ "inputs": {
+ "nixpkgs-lib": [
+ "sonnenshift",
+ "crate2nix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1712014858,
+ "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems_2"
+ },
+ "locked": {
+ "lastModified": 1694529238,
+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_2": {
+ "inputs": {
+ "systems": "systems_3"
+ },
+ "locked": {
+ "lastModified": 1701680307,
+ "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_3": {
+ "inputs": {
+ "systems": "systems_4"
+ },
+ "locked": {
+ "lastModified": 1701680307,
+ "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_4": {
+ "inputs": {
+ "systems": "systems_5"
+ },
+ "locked": {
+ "lastModified": 1701680307,
+ "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_5": {
+ "inputs": {
+ "systems": "systems_6"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
"type": "github"
}
},
@@ -199,11 +595,11 @@
]
},
"locked": {
- "lastModified": 1767491610,
- "narHash": "sha256-/Nldo9ILD7T5aQKuyeUccNPXjhNBrovGXEoi5k7m9Bo=",
+ "lastModified": 1757786467,
+ "narHash": "sha256-gx3THVUlpycVrUFC9vGhAtYRLI7dJtHyo67Zdq5Hadc=",
"owner": "reckenrode",
"repo": "nix-foundryvtt",
- "rev": "35e789ba383fbfaa9039005b9b24669c5be6b8ab",
+ "rev": "1bbc26a28d320fb336d94e9f3cc6b92c035fab20",
"type": "github"
},
"original": {
@@ -213,6 +609,55 @@
}
},
"gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable",
+ "pre-commit-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "gitignore_2": {
+ "inputs": {
+ "nixpkgs": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "pre-commit-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "gitignore_3": {
"inputs": {
"nixpkgs": [
"sonnenshift",
@@ -251,31 +696,157 @@
"type": "github"
}
},
- "nixpkgs": {
+ "nix-test-runner_2": {
+ "flake": false,
"locked": {
- "lastModified": 1771208521,
- "narHash": "sha256-G2qGwj2t77kM0hZatRrTp2+50obn4ssSXoLCrJKZtgQ=",
- "rev": "fa56d7d6de78f5a7f997b0ea2bc6efd5868ad9e8",
- "type": "tarball",
- "url": "https://releases.nixos.org/nixos/25.11/nixos-25.11.6074.fa56d7d6de78/nixexprs.tar.xz"
- },
- "original": {
- "type": "tarball",
- "url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"
- }
- },
- "nixpkgs-lib": {
- "locked": {
- "lastModified": 1769909678,
- "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
- "owner": "nix-community",
- "repo": "nixpkgs.lib",
- "rev": "72716169fe93074c333e8d0173151350670b824c",
+ "lastModified": 1588761593,
+ "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
+ "owner": "stoeffel",
+ "repo": "nix-test-runner",
+ "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
"type": "github"
},
"original": {
- "owner": "nix-community",
- "repo": "nixpkgs.lib",
+ "owner": "stoeffel",
+ "repo": "nix-test-runner",
+ "type": "github"
+ }
+ },
+ "nix-test-runner_3": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1588761593,
+ "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
+ "owner": "stoeffel",
+ "repo": "nix-test-runner",
+ "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "stoeffel",
+ "repo": "nix-test-runner",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1743014863,
+ "narHash": "sha256-jAIUqsiN2r3hCuHji80U7NNEafpIMBXiwKlSrjWMlpg=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "bd3bac8bfb542dbde7ffffb6987a1a1f9d41699f",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1760782625,
+ "narHash": "sha256-qeSmHF66cMiHObiBhm8IngmqDBEcqNdBSSoAjuE6tTw=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "f2ee78c4eb601be36a277e1779a7a87655419dad",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-25.05-small",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_3": {
+ "locked": {
+ "lastModified": 1700612854,
+ "narHash": "sha256-yrQ8osMD+vDLGFX7pcwsY/Qr5PUd6OmDMYJZzZi0+zc=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "19cbff58383a4ae384dea4d1d0c823d72b49d614",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_4": {
+ "locked": {
+ "lastModified": 1715534503,
+ "narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_5": {
+ "locked": {
+ "lastModified": 1715534503,
+ "narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_6": {
+ "locked": {
+ "lastModified": 1719506693,
+ "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=",
+ "path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source",
+ "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a",
+ "type": "path"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "nixpkgs_7": {
+ "locked": {
+ "lastModified": 1719506693,
+ "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=",
+ "path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source",
+ "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a",
+ "type": "path"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "nixpkgs_8": {
+ "locked": {
+ "lastModified": 1729850857,
+ "narHash": "sha256-WvLXzNNnnw+qpFOmgaM3JUlNEH+T4s22b5i2oyyCpXE=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "41dea55321e5a999b17033296ac05fe8a8b5a257",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
"type": "github"
}
},
@@ -284,15 +855,85 @@
"flake-compat": [
"sonnenshift",
"crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable",
"flake-compat"
],
- "flake-utils": [
+ "gitignore": "gitignore",
+ "nixpkgs": [
"sonnenshift",
"crate2nix",
- "devshell",
- "flake-utils"
+ "crate2nix_stable",
+ "crate2nix_stable",
+ "nixpkgs"
],
- "gitignore": "gitignore",
+ "nixpkgs-stable": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "crate2nix_stable",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1719259945,
+ "narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=",
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "type": "github"
+ }
+ },
+ "pre-commit-hooks_2": {
+ "inputs": {
+ "flake-compat": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "flake-compat"
+ ],
+ "gitignore": "gitignore_2",
+ "nixpkgs": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "nixpkgs"
+ ],
+ "nixpkgs-stable": [
+ "sonnenshift",
+ "crate2nix",
+ "crate2nix_stable",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1719259945,
+ "narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=",
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "type": "github"
+ }
+ },
+ "pre-commit-hooks_3": {
+ "inputs": {
+ "flake-compat": [
+ "sonnenshift",
+ "crate2nix",
+ "flake-compat"
+ ],
+ "flake-utils": "flake-utils_5",
+ "gitignore": "gitignore_3",
"nixpkgs": [
"sonnenshift",
"crate2nix",
@@ -318,16 +959,96 @@
"type": "github"
}
},
+ "purescript-overlay": {
+ "inputs": {
+ "flake-compat": "flake-compat_5",
+ "nixpkgs": [
+ "tlaternet-webserver",
+ "dream2nix",
+ "nixpkgs"
+ ],
+ "slimlock": "slimlock"
+ },
+ "locked": {
+ "lastModified": 1728546539,
+ "narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=",
+ "owner": "thomashoneyman",
+ "repo": "purescript-overlay",
+ "rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "thomashoneyman",
+ "repo": "purescript-overlay",
+ "type": "github"
+ }
+ },
+ "pyproject-nix": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1702448246,
+ "narHash": "sha256-hFg5s/hoJFv7tDpiGvEvXP0UfFvFEDgTdyHIjDVHu1I=",
+ "owner": "davhau",
+ "repo": "pyproject.nix",
+ "rev": "5a06a2697b228c04dd2f35659b4b659ca74f7aeb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "davhau",
+ "ref": "dream2nix",
+ "repo": "pyproject.nix",
+ "type": "github"
+ }
+ },
"root": {
"inputs": {
"deploy-rs": "deploy-rs",
"disko": "disko",
- "flake-parts": "flake-parts",
- "flint": "flint",
"foundryvtt": "foundryvtt",
- "nixpkgs": "nixpkgs",
+ "nixpkgs": "nixpkgs_2",
"sonnenshift": "sonnenshift",
- "sops-nix": "sops-nix"
+ "sops-nix": "sops-nix",
+ "tlaternet-webserver": "tlaternet-webserver"
+ }
+ },
+ "rust-analyzer-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1737140097,
+ "narHash": "sha256-m4SN8DeKzsP10EQFS7+2zgGfCrMhTfTt1H0QRNesD08=",
+ "owner": "rust-lang",
+ "repo": "rust-analyzer",
+ "rev": "f61bfa4d7feb84d07538d361fe77d34a29e3b375",
+ "type": "github"
+ },
+ "original": {
+ "owner": "rust-lang",
+ "ref": "nightly",
+ "repo": "rust-analyzer",
+ "type": "github"
+ }
+ },
+ "slimlock": {
+ "inputs": {
+ "nixpkgs": [
+ "tlaternet-webserver",
+ "dream2nix",
+ "purescript-overlay",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1688756706,
+ "narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=",
+ "owner": "thomashoneyman",
+ "repo": "slimlock",
+ "rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "thomashoneyman",
+ "repo": "slimlock",
+ "type": "github"
}
},
"sonnenshift": {
@@ -338,11 +1059,11 @@
]
},
"locked": {
- "lastModified": 1764578400,
- "narHash": "sha256-8V0SpIcYyjpP+nAHfYJDof7CofLTwVVDo5QLZ0epjOQ=",
+ "lastModified": 1748144928,
+ "narHash": "sha256-OPdi6AjrWWwZlukNwDHDWAf1CCFkHsf8JbA3rJoibXM=",
"ref": "refs/heads/main",
- "rev": "bf17617899692c9c2bfebfce87320a4174e6dc28",
- "revCount": 27,
+ "rev": "429036aa8630d2325f8a9f067cc041d628279a8e",
+ "revCount": 24,
"type": "git",
"url": "ssh://git@github.com/sonnenshift/battery-manager"
},
@@ -358,11 +1079,11 @@
]
},
"locked": {
- "lastModified": 1771166946,
- "narHash": "sha256-UFc4lfGBr+wJmwgDGJDn1cVD6DTr0/8TdronNUiyXlU=",
+ "lastModified": 1760393368,
+ "narHash": "sha256-8mN3kqyqa2PKY0wwZ2UmMEYMcxvNTwLaOrrDsw6Qi4E=",
"owner": "Mic92",
"repo": "sops-nix",
- "rev": "2d0cf89b4404529778bc82de7e42b5754e0fe4fa",
+ "rev": "ab8d56e85b8be14cff9d93735951e30c3e86a437",
"type": "github"
},
"original": {
@@ -386,6 +1107,103 @@
"type": "github"
}
},
+ "systems_2": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_3": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_4": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_5": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_6": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "tlaternet-webserver": {
+ "inputs": {
+ "dream2nix": "dream2nix",
+ "fenix": "fenix",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1737271785,
+ "narHash": "sha256-yVdaaawYK1/q9V5btfGpxVCQBdyQx1WcFHYO0yX5bP8=",
+ "ref": "refs/heads/master",
+ "rev": "5d3d84836101ec9b9867a5f754c9ee1b9d4dc538",
+ "revCount": 76,
+ "type": "git",
+ "url": "https://gitea.tlater.net/tlaternet/tlaternet.git"
+ },
+ "original": {
+ "type": "git",
+ "url": "https://gitea.tlater.net/tlaternet/tlaternet.git"
+ }
+ },
"utils": {
"inputs": {
"systems": "systems"
diff --git a/flake.nix b/flake.nix
index 8680b59..76d612f 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,34 +1,21 @@
{
+ description = "tlater.net host configuration";
+
inputs = {
- nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz";
- flake-parts.url = "github:hercules-ci/flake-parts";
-
- ## Nix/OS utilities
-
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05-small";
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
-
- deploy-rs = {
- url = "github:serokell/deploy-rs";
- inputs.nixpkgs.follows = "nixpkgs";
- };
-
+ deploy-rs.url = "github:serokell/deploy-rs";
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
-
- ## Programs
-
- flint = {
- url = "github:NotAShelf/flint";
+ tlaternet-webserver = {
+ url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
inputs.nixpkgs.follows = "nixpkgs";
};
-
- ## Services
-
foundryvtt = {
url = "github:reckenrode/nix-foundryvtt";
inputs.nixpkgs.follows = "nixpkgs";
@@ -36,54 +23,118 @@
sonnenshift = {
url = "git+ssh://git@github.com/sonnenshift/battery-manager";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- crate2nix.inputs = {
- flake-compat.follows = "deploy-rs/flake-compat";
- devshell.inputs.flake-utils.follows = "deploy-rs/utils";
- flake-parts.follows = "flake-parts";
- };
- };
+ inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
- { flake-parts, ... }@inputs:
- flake-parts.lib.mkFlake { inherit inputs; } (
- { self, ... }@args:
- {
- imports = [
- (flake-parts.lib.importApply ./flakeModules/deploy-rs.nix args)
- ./checks
- ./dev-utils.nix
- ./pkgs
- ];
+ {
+ self,
+ nixpkgs,
+ sops-nix,
+ deploy-rs,
+ ...
+ }@inputs:
+ let
+ system = "x86_64-linux";
- flake.nixosConfigurations.hetzner-1 = inputs.nixpkgs.lib.nixosSystem {
+ vm = nixpkgs.lib.nixosSystem {
+ inherit system;
+ specialArgs.flake-inputs = inputs;
+
+ modules = [
+ ./configuration
+ ./configuration/hardware-specific/vm.nix
+ ];
+ };
+ in
+ {
+ ##################
+ # Configurations #
+ ##################
+ nixosConfigurations = {
+ # The actual system definition
+ hetzner-1 = nixpkgs.lib.nixosSystem {
+ inherit system;
specialArgs.flake-inputs = inputs;
+
modules = [
./configuration
./configuration/hardware-specific/hetzner
];
};
+ };
- deploy.nodes.hetzner-1 = {
+ ############################
+ # Deployment configuration #
+ ############################
+ deploy.nodes = {
+ hetzner-1 = {
hostname = "116.202.158.55";
profiles.system = {
user = "root";
- activation = "nixos";
- closure = self.nixosConfigurations.hetzner-1;
+ path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.hetzner-1;
+ };
- sshUser = "tlater";
- sshOpts = [
- "-p"
- "2222"
- "-o"
- "ForwardAgent=yes"
- ];
+ sshUser = "tlater";
+ sshOpts = [
+ "-p"
+ "2222"
+ "-o"
+ "ForwardAgent=yes"
+ ];
+ };
+ };
+
+ #########
+ # Tests #
+ #########
+ checks.${system} = import ./checks (inputs // { inherit system; });
+
+ ###########################
+ # Garbage collection root #
+ ###########################
+
+ packages.${system} = {
+ default = vm.config.system.build.vm;
+ }
+ // import ./pkgs { pkgs = nixpkgs.legacyPackages.${system}; };
+
+ ###################
+ # Utility scripts #
+ ###################
+ apps.${system} = {
+ default = self.apps.${system}.run-vm;
+
+ run-vm = {
+ type = "app";
+ program =
+ (nixpkgs.legacyPackages.${system}.writeShellScript "" ''
+ ${vm.config.system.build.vm.outPath}/bin/run-testvm-vm
+ '').outPath;
+ };
+ };
+
+ ###########################
+ # Development environment #
+ ###########################
+ devShells.${system} = {
+ default = nixpkgs.legacyPackages.${system}.mkShell {
+ sopsPGPKeyDirs = [
+ "./keys/hosts/"
+ "./keys/users/"
+ ];
+
+ packages = nixpkgs.lib.attrValues {
+ inherit (sops-nix.packages.${system}) sops-import-keys-hook sops-init-gpg-key;
+ inherit (deploy-rs.packages.${system}) default;
};
};
- }
- );
+
+ minecraft = nixpkgs.legacyPackages.${system}.mkShell {
+ packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; };
+ };
+ };
+ };
}
diff --git a/flakeModules/deploy-rs.nix b/flakeModules/deploy-rs.nix
deleted file mode 100644
index abee5a8..0000000
--- a/flakeModules/deploy-rs.nix
+++ /dev/null
@@ -1,136 +0,0 @@
-{ lib, ... }@exportingFlake:
-let
- inherit (lib) mkOption types;
-
- deploy-rs-for-system =
- system:
- (import exportingFlake.inputs.nixpkgs {
- inherit system;
- overlays = [
- exportingFlake.inputs.deploy-rs.overlays.default
- (_final: prev: {
- deploy-rs = {
- inherit (exportingFlake.inputs.nixpkgs.legacyPackages.${system}) deploy-rs;
- inherit (prev.deploy-rs) lib;
- };
- })
- ];
- }).deploy-rs;
-in
-{ config, ... }:
-let
- cfg = config.deploy;
-in
-{
- options.deploy =
- let
- genericOptions =
- let
- mkGenericOption =
- type:
- mkOption {
- type = types.nullOr type;
- default = null;
- };
- in
- {
- options = {
- sshUser = mkGenericOption types.str;
- user = mkGenericOption types.str;
- sshOpts = mkGenericOption (types.listOf types.str);
- fastConnection = mkGenericOption types.bool;
- autoRollback = mkGenericOption types.bool;
- magicRollback = mkGenericOption types.bool;
- confirmTimeout = mkGenericOption types.int;
- activationTimeout = mkGenericOption types.int;
- tempPath = mkGenericOption types.str;
- interactiveSudo = mkGenericOption types.bool;
- };
- };
-
- profileModule =
- { config, ... }:
- {
- imports = [ genericOptions ];
-
- options = {
- activation = mkOption {
- type = types.oneOf [
- (types.enum [
- "nixos"
- "home-manager"
- "darwin"
- "noop"
- ])
- ];
- };
-
- closure = mkOption { type = types.raw; };
-
- profilePath = mkOption {
- type = types.nullOr types.str;
- default = null;
- };
-
- path = mkOption {
- type = types.raw;
- internal = true;
- };
- };
-
- config =
- let
- inherit (config.closure.config.nixpkgs.hostPlatform) system;
- deploy-rs = deploy-rs-for-system system;
- in
- lib.mkMerge [
- (lib.mkIf (lib.elem config.activation [
- "nixos"
- "home-manager"
- "darwin"
- "noop"
- ]) { path = deploy-rs.lib.activate.${config.activation} config.closure; })
- ];
- };
-
- nodeModule = {
- imports = [ genericOptions ];
-
- options = {
- hostname = mkOption { type = types.str; };
-
- profilesOrder = mkOption {
- type = types.listOf types.str;
- default = [ ];
- };
-
- profiles = mkOption {
- type = types.attrsOf (types.submoduleWith { modules = [ profileModule ]; });
-
- apply = lib.mapAttrs (
- _: profile:
- lib.filterAttrs (
- name: val:
- !(lib.elem name [
- "activation"
- "closure"
- ])
- && val != null
- ) profile
- );
-
- default = { };
- };
- };
- };
- in
- {
- nodes = mkOption {
- default = { };
- type = types.attrsOf (types.submoduleWith { modules = [ nodeModule ]; });
- apply = lib.mapAttrs (_: node: lib.filterAttrs (_: val: val != null) node);
- };
- };
-
- config = lib.mkIf (cfg.nodes != { }) { flake.deploy.nodes = cfg.nodes; };
-}
diff --git a/keys/production.yaml b/keys/production.yaml
index 6a60c40..a5a4674 100644
--- a/keys/production.yaml
+++ b/keys/production.yaml
@@ -1,13 +1,9 @@
-tlaternet:
- ntfy-topic: ENC[AES256_GCM,data:1mlcrb4uOkwyB5pwTbIv,iv:Ba6k9HV4ze9cftbSE8ns2+j2ebi4cqmHa5UwM1YR6W8=,tag:Ksf840uCOQdoVasORjUdcQ==,type:str]
-ntfy:
- users: ENC[AES256_GCM,data:7vZx2jIXsEMT0LhPKis7oiaq8YGgGnW3M6L9Aq0qlaNzpF31sAAZHMYPMJDG0Z09gvQ60kRVxgggHYx8TlaD/ZxLhFVfKgUcQaiRRxyAFIKFVqVNRQaiykQcFzw=,iv:HefHN4rkVKg43Ery0QW2Mg74tIwsM0OvsluXFIWVCVM=,tag:WM8uW4bV9nHuwWYaH+mmUw==,type:str]
porkbun:
api-key: ENC[AES256_GCM,data:p3lqvGc8m2U/12rBPjoNR7hxQyD52CyEen/V8q59k5CSJZSqzZS8M5vEXFBsUMjz2lrmKM4pgtz4wa2fWK6Ty4LJCaI=,iv:OQC3FpwTtPmqHvDbA41mWF7LGYwC/jD2ZMBsE8ktNOg=,tag:kq5hUR7TBgczuGcXpsdu2A==,type:str]
secret-api-key: ENC[AES256_GCM,data:zV5PTKf45Zab8uW8mbuXmPNzciq6tV9OF0wUND7YnRk/DjZneYWItAsNBVoM+iHA+XsUPDoeKo6hoJiGkH/cCQ8WvuM=,iv:yr1M5DlgI8k6BgzNz3HRnqspHOrQuf2PmoZS1HGp0v8=,tag:JkNNziMMfKFZV2hnx5lXRg==,type:str]
battery-manager:
email: ENC[AES256_GCM,data:rYLUACXR/n+bLBmZ,iv:sUBEkh2+7qGjHZ5R23e/hoCiyTA7GTL4bJvXmxjZ5Sw=,tag:fdPMllaQQfRgX0WZKIre4g==,type:str]
- password: ENC[AES256_GCM,data:h12XeBosdVAJ9QwEYSyC,iv:m6A6EhL9BF4+c7P++A7qH2gXtRnxzAk3nCUvPH8E7e0=,tag:krZyGPuCSDLNTj+QQxN8vg==,type:str]
+ password: ENC[AES256_GCM,data:7cokZa6Q6ahSeiFPz+cV,iv:vz405P0IcG9FsAQXlY7mi78GuushQUKJm2irG6buGzc=,tag:JLHG2jTkJDGbinAq9dXRsQ==,type:str]
forgejo:
metrics-token: ENC[AES256_GCM,data:WVbD5JloJlHNjeEwe1uEd4Haj6L3ilj1Pnux6yrelUQP18ZPAh90aDO1OIZHaPJR7tTeyATr8BIzZL1zkNhCuA==,iv:eTYXN3hymIN3bTX1YxNGkAYE0KVDbdz2ds8UQAHlALE=,tag:A61loGdu0pfsiez96u2Qsg==,type:str]
grafana:
@@ -20,17 +16,24 @@ steam:
heisenbridge:
as-token: ENC[AES256_GCM,data:+2yo6T18j34622H8ZWblAFB2phLw1q0k0vUQEZ5sFj7dQaRnkEiAMi0R3p17Zq0pOtGEC0RRZuPLYkcZ1oKP0w==,iv:lGwrQYp//FufpmJocrLIVyy9RK7lEEVcpAi0wmkjr34=,tag:yV06UbhAYJQz36O2XdhY+A==,type:str]
hs-token: ENC[AES256_GCM,data:u52WpkQFd/J7JFoE/rfNluebyZQLOokvkVdL7+AEAvrhJhrkJli1ztkD79lbC+6tGUH4tT3T+nX9wvGKnrRUQg==,iv:as+9fVuvMg2IoE2WIKD9mHi+znhNcWRh5Zq+yr0xcDQ=,tag:mZ7fh7U0MfgI8hyq/28Bcg==,type:str]
+matrix-hookshot:
+ as-token: ENC[AES256_GCM,data:nXTanPhDyDF7R3AllLqpM5dzljBrHwlh1KJnTGIi5PhbDY2lPj4+uXkMEwvm1u+hQjPyM7vKZPfK+0/dms6Y7A==,iv:fSakJN+yai0gfOJKFxxaxgyUtk0pNmIeqVgrdq92/24=,tag:Qc7+SUnm5/Nq5+QIScR9kQ==,type:str]
+ hs-token: ENC[AES256_GCM,data:Bwyj0JTTN0NNnwOs1zA8CqbtZSNcvlINeT7QVc2eJiHda92J6vQk7bSxy6KuqCN9DxlUsK13ggYjNORY2vic5w==,iv:Npnp8arYQ3Yb6CXrnKgE03hD7ZjGINPa/DwFI8D+5tA=,tag:FqNE6yI0nF4puEUw9MGAjQ==,type:str]
wireguard:
server-key: ENC[AES256_GCM,data:mXb7ZznJHf5CgV8rI4uzPBATMRbmd7LimgtCkQM9kAjbIaGwUBqJZBN3fXs=,iv:3Po1Orinzov9rnEm9cLzgJY1PeD+5Jl9115MriABHh8=,tag:E/2CjDO1JCvJzxCnqKcNyw==,type:str]
restic:
+ local-backups: ENC[AES256_GCM,data:NLNVlR9G9bLSZOkMoPvkbBbAZlKkmiUbdWHOFDnaefuy9wNLH53ctOIyS0rSsQLaJCSBTpgPSWIIXUSuzoK/eA==,iv:DzuujmyJJP4GiE5z7KOOGUEzUgOwmtf/7UYhwkyLe9g=,tag:cElFhpVC7S6HYlB6UyN7PQ==,type:str]
storagebox-backups: ENC[AES256_GCM,data:UyT8jCkKlfYJXjWLI9MbYfeVhY5d89N3aj1Olj54/aBOP3gwcrx6gU56Pwa1xKZ3lR13AVs/b4wF9sbvP7Kqqg==,iv:0HM+DgH4iCiWpjRvAYCFQGEy4xIBQwAM+PkkzOsizw0=,tag:jbrqo1In2O4jVM5e7fjOzg==,type:str]
storagebox-ssh-key: ENC[AES256_GCM,data:7aYlKX7I8Bsur3nm4nV9eSW3lmIxBCeCUMbPX3qgcotPbyPYaUqD3MOCnFRepajYkFXAgMX4jknqLfoO9xYc4bavDFjOY8Ww/KmLay7ces6tDnkK6tTRxcNRPUBqEzaFiPNsZc2UwHnmHOF0rKvQusvhCOplYao3xxz5McTHC7IEriUApSNudWCg3qGbyAmxkGEw7tRfh6IiUXEOFeaXDZd78dWZlWIIeospmA1hcVhkLGrjMmoikt/YANHUpWPbd+B9E6x+s2eIzFdvztRjarBluWPZuX981b+hcOm1/+HY/tJ/jzgyVbX1rjmdgZ9jZqdKO/vkOkijHWXlwpQ0QJ2s8p5MURPGRsC7W5jfmGbVKrubxfQC2mSJRJgBaj1wX3yI4GbfCXNdbpseAMy7t8OmN/iMN57lGnD3uX8CWWD327PIWp3SgwMDIZtJRlMBu3vMUrBdNnrrlYoLgf821tX7JWW6L5g1EK/bcBZqZZ/6rE+Q9fLiJHTsj2lyTzQZRLKsn0YePlcIMOWHO2CG/aWrfycdSKKjaKGG,iv:OVnEIMFB4h/EQ8zV3XOpVXLDrV5t4roNYDFQz99m4sQ=,tag:mKWF12uD1TLla/MoJs2zNg==,type:str]
turn:
env: ENC[AES256_GCM,data:kt5nhVo9pb/ZbPUEcqSYXxN9YMgQKnFb5VRfFFS/qoIaJ73uD2fuJKqcxAyVRrdLqnSAWSQBgTgunBzdP7xqLAK2qt8DYAQWHkIe9uxFbSXZpdmw,iv:9lq6SFwTFN4GGm6gPiJpUMasMdnHVF6XLGYrsyG3kjU=,tag:428Qf9DOiiHt/Wjb188b8g==,type:str]
secret: ENC[AES256_GCM,data:si7ee6Xfhdgdyzbp6aQpF7pz3TmTBb7iQ82lRPVXNDg9JfHI+lbmgAsSnRLX5qMCA6P9R045sSMosqidL8QwRg==,iv:SrhpZKK8D45yxCEfDb9P3TwtA14+qEI+wcRqcN/a6pw=,tag:PiwV+mOL9xHJgJft6sc61g==,type:str]
+ ssl-key: ENC[AES256_GCM,data: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,iv:lpQ/utPri3QEIvB8V5MzHmgyeyGmlVGbkbNKKrb10lM=,tag:eWFSV+nRqDfYERXTXT8eAQ==,type:str]
+ #ENC[AES256_GCM,data:DkvLWzxK8Xp/TXnJNZ15Wa+ojB7qGxh44zvwdm50ENgTPaqIl2468q+HeOUIUE7OiXCQWj2PVg==,iv:J9EkotYGBY1FZo62wieUSFaTCn1NnBb6aOKnUOCELS8=,tag:vmxNYyjCYfi6n9nBZ8DwPg==,type:comment]
+ ssl-cert: ENC[AES256_GCM,data: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,iv:JvoC84+dJ+sKdDDNvzIRWJUKHftgW4hhT6fiJQXR4rs=,tag:wrFQF7ZqG0us3E98i/gkyA==,type:str]
sops:
- lastmodified: "2025-12-01T11:39:17Z"
- mac: ENC[AES256_GCM,data:TwhGOW/V9/IoBifzh1MSwy/ff7ONTnxEmwERD8Yl2E27WG/6dTVz0/nIlZ8KsEKLC6vB2m+sJT+14Q9KCj4Cn/bWV1PmhytktGPxLQpgF55+pZlSK1aLUPLq0hwE93b4MAeOvzoOXtCQguh1dsB2RkinabFoMeZ2xJ7Kc+jHlfA=,iv:Ri8aEA4tssGDv2UuKeza8vs94IovM9GARLIEapb9Ya0=,tag:MDgAffj7ndmMwpw7mBXNRg==,type:str]
+ lastmodified: "2025-02-07T19:44:49Z"
+ mac: ENC[AES256_GCM,data:+0hpd/E7GxK/27f2Itf0hDV+3Ga4gHb8xxLutJ32HLBWLZ5Y+dN03xgkz8jBTiM+BeHwS4gz70Cs9X3zLMHbosWVuIV9DLuRaHRq/IU9KiADwqmCySZALqCf3+T5QKZr3Qs4AZJHwaAXkRX9HbnRFriIAFDJW/BGdIHdoROquxY=,iv:TeXI8LGqHVa5wo61sGdNbZ2nJvSlPdgn9R3Lq5qUggU=,tag:TFort5wxVTdi9LMlMeT/DQ==,type:str]
pgp:
- created_at: "2025-10-03T21:38:48Z"
enc: |-
@@ -84,4 +87,4 @@ sops:
-----END PGP MESSAGE-----
fp: 0af7641adb8aa843136cf6d047f71da3e5ad79f9
unencrypted_suffix: _unencrypted
- version: 3.11.0
+ version: 3.9.2
diff --git a/keys/staging.yaml b/keys/staging.yaml
index b5c8533..caa5707 100644
--- a/keys/staging.yaml
+++ b/keys/staging.yaml
@@ -1,8 +1,7 @@
-tlaternet:
- ntfy-topic: ENC[AES256_GCM,data:BSNLP9hLQKufDf3STknQ,iv:4WeYARwDEg84fh8qMa4szssQeK2orBl72oiIRywXCi8=,tag:GtS7YAlQiuGAWaUn+JxaYg==,type:str]
-ntfy:
- #ENC[AES256_GCM,data:B5sUuCWTheo/Lz5kAN5/8NeiT7ohQ6IRYjD7k/c0,iv:E85yGjNcd9RGOgBKFPxRB6LqdqISNcjlvp/pqpR7VPg=,tag:1NrwtWyoVp4qDWiqAt/few==,type:comment]
- users: ENC[AES256_GCM,data:zW02xRlLvqJ0/rLJMX9/LSW7CQ26nIyifOLYba4AkstQcrVMOIV1Je4QwaKoy2TPhDcVcjLg6Ce2/0qxpO7Q3rqoXqACbPAUwcxzn14KOt7tEb3IE2S//0Y/law=,iv:sV5Dvplr6A5ivrI/+Cyl6mC+Zxo8NORxfuhEZ/75JbU=,tag:NzqxoN3Hr0YO2CXiVdEo+A==,type:str]
+authelia:
+ users: ""
+ jwt: ENC[AES256_GCM,data:oKA1B7zZAzTZL4nBdHvPENVx7M2BgbMBmNtetri0qCVB7qNFIgbnwVCJFiDvjKxxNdedqUKBZZL5QJbTlPNRxCVdFgBBMFiib3khxMP8kzqff2MgJZxumonlJt5Jmh8tVxwLRJwE/2fp/N9w2hRs0vhfmMyAA4y7RZv3R9/eaKM=,iv:2iTAwP6dipPBMskyygnBJHJ53E0nmHYcGyWDrODEs1Q=,tag:koSEZtQQzOzpbQBgUP5ZHw==,type:str]
+ storage: ENC[AES256_GCM,data:bO+bHu6jRvfbLU6xIDaE2JwXpNnMK916Upv43ycg9fCb+U5hqQfqBBwC2xVEVXtCBRq1VER+gc8rs8/XDT9vZkvMUqAHj4RqXHyzX0UjwsvccBJSLfoLUiT6obk3oVLo5CY7R2TukPuyFXPbMUOrBk9gnbk7z4IWzcwNnuOKBT4=,iv:RmKIS/cgZ0tUQDFF2yfaJnfTvPaeadjG0LPXKIzYFrA=,tag:XmqDhDf3Ja1BsyrYmzTKDg==,type:str]
porkbun:
api-key: ENC[AES256_GCM,data:A5J1sqwq6hs=,iv:77Mar3IX7mq7z7x6s9sSeGNVYc1Wv78HptJElEC7z3Q=,tag:eM/EF9TxKu+zcbJ1SYXiuA==,type:str]
secret-api-key: ENC[AES256_GCM,data:8Xv+jWYaWMI=,iv:li4tdY0pch5lksftMmfMVS729caAwfaacoztaQ49az0=,tag:KhfElBGzVH4ByFPfuQsdhw==,type:str]
@@ -21,17 +20,24 @@ steam:
heisenbridge:
as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str]
hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str]
+matrix-hookshot:
+ as-token: ENC[AES256_GCM,data:uSUOo4f2KqA=,iv:Xb9G8Ecv6m59m51kDw2bOfq3SMJt4g9/6/EdH74R+KM=,tag:K9MSfO2c2Y4rlf0eYrmTnw==,type:str]
+ hs-token: ENC[AES256_GCM,data:0KsyA06InL4=,iv:zAR0Y1fk8SyodcSLBHlQ8I+BAmttz9Hkd8Q3OREFqs4=,tag:t1Et8N/3seq95DeGoUd7Sw==,type:str]
wireguard:
server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str]
restic:
+ local-backups: ENC[AES256_GCM,data:3QjEv03t7wE=,iv:y/6Lv4eUbZZfGPwUONykz8VNL62cAJuWaJy9yk3aAmk=,tag:wMlGsepuG9JjwtUKGWSibw==,type:str]
storagebox-backups: ENC[AES256_GCM,data:NEHk57B3YtI=,iv:0/qnqMVK0662sgfDQoLxcW7L09SKF8E5liCnjaQ2+2k=,tag:RU0BPwGgvI9bgOPr8VItmA==,type:str]
storagebox-ssh-key: ENC[AES256_GCM,data:65+kbJPO90y+rRh3Q5cqLDtQa3VFfbaDPPo1nJLqxgAB7Wm3J7K4qUYAKPcYnkWV4/xFz63R2uCNaq5xv+vuZA==,iv:O7AeE/ujp5p1P7nff7PpghQfN2tQUYBSWL+EHRbE5yA=,tag:Pu/+bEAQuqwmD1Rc//t0cA==,type:str]
turn:
env: ENC[AES256_GCM,data:xjIz/AY109lyiL5N01p5T3HcYco/rM5CJSRTtg==,iv:16bW6OpyOK/QL0QPGQp/Baa9xyT8E3ZsYkwqmjuofk0=,tag:J5re3uKxIykw3YunvQWBgg==,type:str]
secret: ENC[AES256_GCM,data:eQ7dAocoZtg=,iv:fgzjTPv30WqTKlLy+yMn5MsKQgjhPnwlGFFwYEg3gWs=,tag:1ze33U1NBkgMX/9SiaBNQg==,type:str]
+ ssl-key: ENC[AES256_GCM,data:RYfwHjBvwFXgXxXIEuWUzaycTdrCvmPivsNvvUIwDRynS5G2Dl6RCVp1w9zuLvoNun5ncUPGGuLMmVqN2wkJlw==,iv:UKI3bVTY7iTDNvp5UqrZ3QlQkMZ5p2bjgODEc6DCBfQ=,tag:sz7VTyRWyZxAsP4nE48DnA==,type:str]
+ #ENC[AES256_GCM,data:bxhKzU5Tzezl749CDu8e8kxa7ahGuZFaPa9K3kxuD+4sg5Hi3apgDlC0n8oK0DeiK4Ks7+9Cyw==,iv:T/zVJUpNAv1rR0a9+6SDTG08ws2A1hFBs5Ia3TpT0uk=,tag:uGXb1VryM+lIJ8r0I5durA==,type:comment]
+ ssl-cert: ENC[AES256_GCM,data:xHUr14CjKslgbGh/n5jYSOuCw9JRxS6YXE4fxS+aJzFcNeSeGNqoipPeuJupZGBnQP/FCqohiHY=,iv:/OEsVqRshGL9NIvntMC42EPZSNL0u6EfhtUBqgV7qog=,tag:4pxtNjuvy/ibm6nDtKdSkw==,type:str]
sops:
- lastmodified: "2025-12-01T11:39:26Z"
- mac: ENC[AES256_GCM,data:11VQAYk8Am0k8OO6BtU17qpuEhcJ8ylRhJWQNHVAsmi5BCFjD1zU3NkWhtSstPrBcqHMenG+9XuEzpNnbccHI2ru0qlILsQvNj5OKo96FnvYtzApYlApoAzOetCx08Lfxa4RGLN/XCUSuccjBIU2PZRWEK+z+Cm1wHUFeqc1xPc=,iv:6y9j55Cld+GoOVGWAqsEgURRna6dHA2mGZwHVA+ZOE8=,tag:bSZi3nYmYrn3nFT2+RBPUQ==,type:str]
+ lastmodified: "2025-10-20T20:04:21Z"
+ mac: ENC[AES256_GCM,data:kRrmVm3PQooRA/MoHgDb9EaRnoKY9CJxAflus9Po8NBmyQxV6Ehjf8DlI6yf7ZpPlhV+VHZJamyPD+hsHp1hSr8krvr0o52ZQdKn4MJQzSQXa4K9i3i0+glj7cNGs2SzTJnKwN9lxBywZpbVDlkXmvRQYLE9tWPWoSBdurOibjw=,iv:2iBQ1cYT85mCc7jf2GTEOjNiHBlR/F76Dvjl/k5dyLA=,tag:Z7dY2i0KWmmoVp7VJjq1Sw==,type:str]
pgp:
- created_at: "2025-10-03T21:38:26Z"
enc: |-
diff --git a/modules/crowdsec/default.nix b/modules/crowdsec/default.nix
new file mode 100644
index 0000000..44e6bc5
--- /dev/null
+++ b/modules/crowdsec/default.nix
@@ -0,0 +1,383 @@
+{
+ pkgs,
+ lib,
+ config,
+ ...
+}:
+let
+ cfg = config.security.crowdsec;
+ settingsFormat = pkgs.formats.yaml { };
+
+ hub = pkgs.fetchFromGitHub {
+ owner = "crowdsecurity";
+ repo = "hub";
+ rev = "7a3b4753f4577257c0cbeb8f8f90c7f17d2ae008";
+ hash = "sha256-HB4jHyhiO8gjBkLmpo6bDbwhfm5m5nAtNlKhDkZjt2I=";
+ };
+
+ cscli = pkgs.writeShellScriptBin "cscli" ''
+ export PATH="$PATH:${cfg.package}/bin/"
+
+ sudo=exec
+ if [ "$USER" != "crowdsec" ]; then
+ sudo='exec /run/wrappers/bin/sudo -u crowdsec'
+ fi
+
+ $sudo ${cfg.package}/bin/cscli "$@"
+ '';
+
+ acquisitions = ''
+ ---
+ ${lib.concatMapStringsSep "\n---\n" builtins.toJSON cfg.acquisitions}
+ ---
+ '';
+in
+{
+ imports = [ ./remediations ];
+
+ options.security.crowdsec =
+ let
+ inherit (lib.types)
+ nullOr
+ listOf
+ package
+ path
+ str
+ ;
+ in
+ {
+ enable = lib.mkEnableOption "crowdsec";
+
+ package = lib.mkOption {
+ type = package;
+ default = pkgs.crowdsec;
+ };
+
+ stateDirectory = lib.mkOption {
+ type = path;
+ readOnly = true;
+
+ description = ''
+ The state directory of the crowdsec instance. Cannot be
+ changed, but is exposed for downstream use.
+ '';
+ };
+
+ settings = lib.mkOption {
+ inherit (settingsFormat) type;
+ default = { };
+
+ description = ''
+ The crowdsec configuration. Refer to
+