From 2e930e279d81abc2a5ca6d993e09df3d4d58bf29 Mon Sep 17 00:00:00 2001 From: Adam0 Date: Tue, 9 Jun 2026 15:08:59 +0200 Subject: [PATCH] push --- flake.lock | 23 +++- flake.nix | 1 + modules/pkgs/crowdsec-blocklist-import.nix | 43 ++++++++ modules/pkgs/default.nix | 2 + modules/pkgs/gotify-optimize-images.nix | 31 ++++++ modules/services/cloudbeaver.nix | 7 +- modules/services/crowdsec.nix | 120 +++++++++++++++++---- modules/services/dockhand.nix | 7 +- modules/services/gotify.nix | 70 +++++++++++- secrets.yaml | 8 +- 10 files changed, 280 insertions(+), 32 deletions(-) create mode 100644 modules/pkgs/crowdsec-blocklist-import.nix create mode 100644 modules/pkgs/gotify-optimize-images.nix diff --git a/flake.lock b/flake.lock index edf8764..ce72dde 100644 --- a/flake.lock +++ b/flake.lock @@ -1367,6 +1367,22 @@ "type": "github" } }, + "nixpkgs-crowdsec-blocklist-import": { + "locked": { + "lastModified": 1778009350, + "narHash": "sha256-TxHxJ0Gg7iGpPDXGYUBrGa2eDa+ZiyuAooQ3BuGbNPI=", + "owner": "gaelj", + "repo": "nixpkgs", + "rev": "c018e9c39209205cf02bc60bb59da29fc05c1c37", + "type": "github" + }, + "original": { + "owner": "gaelj", + "ref": "init-crowdsec-blocklist-import", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-lib": { "locked": { "lastModified": 1777168982, @@ -1559,11 +1575,11 @@ ] }, "locked": { - "lastModified": 1780982947, - "narHash": "sha256-XeZqQEa8wztuEQMASi46o4ZPsJZQkKZGbg5OaFON/Ig=", + "lastModified": 1781003294, + "narHash": "sha256-LlCAtY0iTj/QAmZH2pEtEVFgoN2W10mUnWQ9kd8zmxE=", "owner": "nix-community", "repo": "NUR", - "rev": "0e8d2ff58607457a3634aeeca850b3cfbde54462", + "rev": "b9af271942a328837d4dde3d2f336798dce0c53c", "type": "github" }, "original": { @@ -1746,6 +1762,7 @@ "nixcord": "nixcord", "nixpkgs": "nixpkgs_7", "nixpkgs-crowdsec": "nixpkgs-crowdsec", + "nixpkgs-crowdsec-blocklist-import": "nixpkgs-crowdsec-blocklist-import", "noctalia": "noctalia", "noctalia-qs": "noctalia-qs", "nur": "nur", diff --git a/flake.nix b/flake.nix index 1ecc1a6..23578e4 100644 --- a/flake.nix +++ b/flake.nix @@ -62,6 +62,7 @@ }; nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"; nixpkgs-crowdsec.url = "github:TornaxO7/nixpkgs/crowdsec"; + nixpkgs-crowdsec-blocklist-import.url = "github:gaelj/nixpkgs/init-crowdsec-blocklist-import"; noctalia = { url = "github:noctalia-dev/noctalia?ref=legacy-v4"; inputs = { diff --git a/modules/pkgs/crowdsec-blocklist-import.nix b/modules/pkgs/crowdsec-blocklist-import.nix new file mode 100644 index 0000000..9caaceb --- /dev/null +++ b/modules/pkgs/crowdsec-blocklist-import.nix @@ -0,0 +1,43 @@ +{inputs, ...}: { + perSystem = {pkgs, ...}: let + inherit (pkgs.stdenv.hostPlatform) system; + src = inputs.nixpkgs-crowdsec-blocklist-import.legacyPackages.${system}.crowdsec-blocklist-import.src; + in { + packages.crowdsec-blocklist-import = pkgs.python3Packages.buildPythonApplication { + pname = "crowdsec-blocklist-import"; + version = "3.7.1"; + inherit src; + pyproject = true; + + build-system = [pkgs.python3Packages.setuptools]; + dependencies = with pkgs.python3Packages; [ + # keep-sorted start + prometheus-client + python-dotenv + requests + # keep-sorted end + ]; + + nativeCheckInputs = with pkgs.python3Packages; [pytestCheckHook]; + pytestFlags = [ + "-v" + "test_blocklist_import.py" + ]; + doCheck = true; + + installPhase = '' + runHook preInstall + install -Dm755 blocklist_import.py $out/bin/crowdsec-blocklist-import + install -Dm644 grafana-dashboard.json $out/share/grafana/dashboards/crowdsec-blocklist-import.json + runHook postInstall + ''; + + meta = { + description = "Import threat intelligence from 30+ public blocklists into CrowdSec"; + homepage = "https://github.com/wolffcatskyy/crowdsec-blocklist-import"; + license = pkgs.lib.licenses.mit; + mainProgram = "crowdsec-blocklist-import"; + }; + }; + }; +} diff --git a/modules/pkgs/default.nix b/modules/pkgs/default.nix index f88a55b..ed1895f 100644 --- a/modules/pkgs/default.nix +++ b/modules/pkgs/default.nix @@ -6,7 +6,9 @@ inherit (packages) # keep-sorted start + crowdsec-blocklist-import djvutorga-adapter + gotify-optimize-images man-preview nocheatsheet-nvim os-age diff --git a/modules/pkgs/gotify-optimize-images.nix b/modules/pkgs/gotify-optimize-images.nix new file mode 100644 index 0000000..aa0b495 --- /dev/null +++ b/modules/pkgs/gotify-optimize-images.nix @@ -0,0 +1,31 @@ +{ + perSystem = {pkgs, ...}: let + inherit (pkgs) writeShellApplication; + in { + packages.gotify-optimize-images = writeShellApplication { + name = "gotify-optimize-images"; + runtimeInputs = with pkgs; [ + # keep-sorted start + imagemagick + optipng + # keep-sorted end + ]; + text = '' + set -e + DATA=/var/lib/gotify/data + for FILE in "$DATA"/images/*; do + if [ "$FILE" -nt "$DATA"/images-optimized ]; then + EXT=$(echo "''${FILE##*.}" | tr '[:upper:]' '[:lower:]') + if [ "$EXT" = png ] || [ "$EXT" = jpg ] || [ "$EXT" = jpeg ] || [ "$EXT" = gif ]; then + convert "$FILE" -resize "512>" "$FILE" + fi + if [ "$EXT" = png ]; then + optipng "$FILE" + fi + fi + done + touch "$DATA"/images-optimized + ''; + }; + }; +} diff --git a/modules/services/cloudbeaver.nix b/modules/services/cloudbeaver.nix index 649c194..1498593 100644 --- a/modules/services/cloudbeaver.nix +++ b/modules/services/cloudbeaver.nix @@ -10,6 +10,9 @@ }: let inherit (lib) mkForce; + secrets = config.sops.secrets; + templates = config.sops.templates; + inherit (vars) groundDomain; in { sops = { @@ -51,7 +54,7 @@ CLOUDBEAVER_QM_DB_USER = "cloudbeaver"; }; - environmentFiles = [config.sops.templates."cloudbeaver.env".path]; + environmentFiles = [templates."cloudbeaver.env".path]; volumes = ["/var/lib/cloudbeaver:/opt/cloudbeaver/workspace"]; }; @@ -90,7 +93,7 @@ # keep-sorted end # keep-sorted start - LoadCredential = ["database_password:${config.sops.secrets."cloudbeaver/database_password".path}"]; + LoadCredential = ["database_password:${secrets."cloudbeaver/database_password".path}"]; RemainAfterExit = true; # keep-sorted end }; diff --git a/modules/services/crowdsec.nix b/modules/services/crowdsec.nix index 72b0e50..baac936 100644 --- a/modules/services/crowdsec.nix +++ b/modules/services/crowdsec.nix @@ -3,6 +3,10 @@ inputs.nixpkgs-crowdsec = { url = "github:TornaxO7/nixpkgs/crowdsec"; }; + + inputs.nixpkgs-crowdsec-blocklist-import = { + url = "github:gaelj/nixpkgs/init-crowdsec-blocklist-import"; + }; }; flake.overlays.crowdsec = final: _prev: let @@ -30,6 +34,7 @@ imports = [ # keep-sorted start + "${inputs.nixpkgs-crowdsec-blocklist-import}/nixos/modules/services/security/crowdsec-blocklist-import.nix" "${inputs.nixpkgs-crowdsec}/nixos/modules/services/security/crowdsec-firewall-bouncer.nix" "${inputs.nixpkgs-crowdsec}/nixos/modules/services/security/crowdsec.nix" # keep-sorted end @@ -107,7 +112,10 @@ }: let inherit (lib) mkForce; + secrets = config.sops.secrets; + dataDir = "/var/lib/crowdsec/data"; + gotifyUrl = "https://127.0.0.1:4407/message"; setupDeps = [ "postgresql.service" @@ -121,32 +129,102 @@ in { imports = [self.modules.nixos.crowdsec-base]; - sops.secrets = { - # keep-sorted start - "crowdsec/console_enroll_key" = {}; - "traefik/crowdsec_bouncer_key" = {}; - # keep-sorted end - }; + sops = { + secrets = { + # keep-sorted start + "crowdsec/console_enroll_key" = {}; + "crowdsec/gotify_api_key" = {}; + "traefik/crowdsec_bouncer_key" = {}; + # keep-sorted end + }; - services.crowdsec.settings = { - config = { - api.server.online_client.credentials_path = "${dataDir}/online_api_credentials.yaml"; + templates."crowdsec-gotify-notification" = { + path = "/etc/crowdsec/notifications/gotify-alerts.yaml"; + content = '' + type: http + name: gotify_alerts + log_level: info + url: ${gotifyUrl} + method: POST + headers: + X-Gotify-Key: ${config.sops.placeholder."crowdsec/gotify_api_key"} + Content-Type: application/json + skip_tls_verification: true + format: | + {{ range . -}} + {{ $alert := . -}} + { + "extras": { + "client::display": { + "contentType": "text/markdown" + } + }, + "priority": 3, + {{range .Decisions -}} + "title": "{{.Type }} {{ .Value }} for {{.Duration}}", + "message": "{{.Scenario}}\n\n[crowdsec cti](https://app.crowdsec.net/cti/{{.Value -}})\n\n[shodan](https://www.shodan.io/host/{{.Value -}})" + {{end -}} + } + {{ end -}} + ''; + }; + }; - db_config = { - db_name = "crowdsec"; - db_path = "/run/postgresql"; - type = "pgx"; - user = "crowdsec"; + services = { + crowdsec.settings = { + config = { + api.server.online_client.credentials_path = "${dataDir}/online_api_credentials.yaml"; + + db_config = { + db_name = "crowdsec"; + db_path = "/run/postgresql"; + type = "pgx"; + user = "crowdsec"; + }; }; + + console.enrollKeyFile = secrets."crowdsec/console_enroll_key".path; + + profiles = mkForce [ + # keep-sorted start + { + decisions = [ + { + duration = "4h"; + type = "ban"; + } + ]; + filters = [''Alert.Remediation == true && Alert.GetScope() == "Ip"'']; + name = "default_ip_remediation"; + notifications = ["gotify_alerts"]; + on_success = "break"; + } + { + decisions = [ + { + duration = "4h"; + type = "ban"; + } + ]; + filters = [''Alert.Remediation == true && Alert.GetScope() == "Range"'']; + name = "default_range_remediation"; + notifications = ["gotify_alerts"]; + on_success = "break"; + } + # keep-sorted end + ]; }; - console.enrollKeyFile = config.sops.secrets."crowdsec/console_enroll_key".path; - }; + crowdsec-firewall-bouncer = { + enable = true; + registerBouncer.enable = true; + createRulesets = true; + }; - services.crowdsec-firewall-bouncer = { - enable = true; - registerBouncer.enable = true; - createRulesets = true; + crowdsec-blocklist-import = { + enable = true; + allowListGithub = true; + }; }; systemd.services = { @@ -206,7 +284,7 @@ # keep-sorted end # keep-sorted start - LoadCredential = ["traefik_bouncer_key:${config.sops.secrets."traefik/crowdsec_bouncer_key".path}"]; + LoadCredential = ["traefik_bouncer_key:${secrets."traefik/crowdsec_bouncer_key".path}"]; RemainAfterExit = true; Type = "oneshot"; # keep-sorted end diff --git a/modules/services/dockhand.nix b/modules/services/dockhand.nix index d28ea08..0cd6d5e 100644 --- a/modules/services/dockhand.nix +++ b/modules/services/dockhand.nix @@ -8,6 +8,9 @@ ... }: let inherit (lib) mkForce; + + secrets = config.sops.secrets; + templates = config.sops.templates; in { sops = { secrets."dockhand/database_password" = {}; @@ -32,7 +35,7 @@ "--health-timeout=5s" ]; - environmentFiles = [config.sops.templates."dockhand.env".path]; + environmentFiles = [templates."dockhand.env".path]; volumes = ["/var/lib/dockhand:/app/data"]; }; @@ -71,7 +74,7 @@ # keep-sorted end # keep-sorted start - LoadCredential = ["database_password:${config.sops.secrets."dockhand/database_password".path}"]; + LoadCredential = ["database_password:${secrets."dockhand/database_password".path}"]; RemainAfterExit = true; # keep-sorted end }; diff --git a/modules/services/gotify.nix b/modules/services/gotify.nix index be6ea38..24d2f72 100644 --- a/modules/services/gotify.nix +++ b/modules/services/gotify.nix @@ -1,13 +1,30 @@ { flake.modules.nixos.gotify = { # keep-sorted start + config, lib, pkgs, + vars, # keep-sorted end ... }: let inherit (lib) mkForce; + + templates = config.sops.templates; + inherit (vars) groundDomain; in { + sops = { + secrets = { + "gotify/client_id" = {}; + "gotify/client_secret" = {}; + }; + + templates."gotify.env".content = '' + GOTIFY_OIDC_CLIENTID = ${config.sops.placeholder."gotify/client_id"} + GOTIFY_OIDC_CLIENTSECRET=${config.sops.placeholder."gotify/client_secret"} + ''; + }; + services.gotify = { enable = true; package = pkgs.nur.repos.adam0.gotify-server; @@ -15,10 +32,17 @@ environment = { GOTIFY_SERVER_PORT = 44407; + GOTIFY_SERVER_SECURECOOKIE = "true"; + + GOTIFY_OIDC_ENABLED = "true"; + GOTIFY_OIDC_ISSUER = "https://authentik.${groundDomain}/application/o/gotify/"; + GOTIFY_OIDC_REDIRECTURL = "https://gotify.${groundDomain}/auth/oidc/callback"; GOTIFY_DATABASE_DIALECT = "postgres"; GOTIFY_DATABASE_CONNECTION = "host=/run/postgresql user=gotify dbname=gotify sslmode=disable"; }; + + environmentFiles = [templates."gotify.env".path]; }; users = { @@ -31,8 +55,25 @@ }; systemd.services.gotify-server = { - after = ["postgresql.service"]; - requires = ["postgresql.service"]; + after = [ + "authentik-worker.service" + "authentik.service" + "postgresql.service" + ]; + requires = [ + "authentik-worker.service" + "authentik.service" + "postgresql.service" + ]; + + preStart = let + inherit (pkgs.nur.repos.adam0) gotifyPlugins; + inherit (config.services.gotify) stateDirectoryName; + in '' + install -Dm755 ${gotifyPlugins.gotify-authentik-plugin}/authentik-plugin.so /var/lib/${stateDirectoryName}/data/plugins/authentik-plugin.so + install -Dm755 ${gotifyPlugins.gotify-webhooks-plugin}/webhooks-plugin.so /var/lib/${stateDirectoryName}/data/plugins/webhooks-plugin.so + chown gotify:gotify /var/lib/gotify/data/plugins/*.so + ''; serviceConfig = { DynamicUser = mkForce false; @@ -40,5 +81,30 @@ Group = "gotify"; }; }; + + systemd.services.gotify-optimize-images = { + description = "Optimize Gotify uploaded images"; + + after = ["gotify-server.service"]; + requires = ["gotify-server.service"]; + + serviceConfig = { + Type = "oneshot"; + User = "gotify"; + Group = "gotify"; + ExecStart = "${pkgs.gotify-optimize-images}/bin/gotify-optimize-images"; + }; + }; + + systemd.timers.gotify-optimize-images = { + description = "Daily Gotify image optimization"; + + wantedBy = ["timers.target"]; + + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + }; + }; }; } diff --git a/secrets.yaml b/secrets.yaml index fd5b009..f5cee90 100644 --- a/secrets.yaml +++ b/secrets.yaml @@ -45,6 +45,7 @@ godns: password: ENC[AES256_GCM,data:8977GbHBvlCsOrUcFg0Wo9X2+lXyWdvK4med7owm+7U/HiqY7FLX0RQ9JBUo0IXRqqVFOYG77fWI17Diw3jQ6dfEuO4=,iv:RdP28iz7Oc8iL3HbOIqzek8K92JEKAtEXVqn7sxIioQ=,tag:znY+3NqNn1yUsTRq0xFEGw==,type:str] crowdsec: console_enroll_key: ENC[AES256_GCM,data:IfKTndWJCj1EtYCH9u6UF2IPUjjmH6bN4Q==,iv:74ZDRo8pFDGwsJIdrRvx7M+Hzq/9z42fC/T3/tXx25c=,tag:f/U8PgnjxfOE0QTY+gjSVg==,type:str] + gotify_api_key: ENC[AES256_GCM,data:QKJbe1rdm64vpRbF2R8ccodBeUY43wE=,iv:YFUOhJHCDVeJuQnyHRGpYxtSStm5CGix9C+j3KOvUh4=,tag:UHTH1iw5x7c/Fw9DBMixwg==,type:str] traefik: mail: ENC[AES256_GCM,data:kU+b4zXyyVmT85TYvV3yaOwsy+DEbw==,iv:aE96ngF6yC+ayp/X0jPcDyr9wHPL0ItJjq4x/9IVhnA=,tag:UqeJHyHVwCMgltCzxFfAaQ==,type:str] crowdsec_bouncer_key: ENC[AES256_GCM,data:NogMDioHUe+mKwtpCwF5xhABv9asVg0bbTx5yIVucb5g5OHWD2BO2dykWsg=,iv:cmnFSJXvNStbARyBKNeUdoV/OG04wqQBLYRfn/zXiFA=,tag:nk9+Fr69pl6ciROtBe/Vvg==,type:str] @@ -64,6 +65,9 @@ mysql: wireguard: euclid: private_key: ENC[AES256_GCM,data:dGh0qAiYgloX6EoV0Q72UP/faOpeoPqXUaxkN8lKq6KQbxycRqBHapPo+5s=,iv:jb/sliGbNK2r7yWoaL7u6RTUnbMTH6+wFJgq/LCxucE=,tag:mdfz5/aRkozRB8lRRPtd2Q==,type:str] +gotify: + client_id: ENC[AES256_GCM,data:sWHLVejwqjtjFhjAyT0dK+s4bdyXUUC8jh+u4uWu+kwgU5tDS21qUw==,iv:atw/QxQYZ38j30IdA4Osrba1wDb5YVomrPojoWd/82g=,tag:7d600/i1rzp4y7WO6M/YRw==,type:str] + client_secret: ENC[AES256_GCM,data:6PhFHPRcrUD1tPySDiB4tAtTng4/lYfNZ7XGYvIxxkbDobH9q3pjtuvXkz4MvGyjtC+M4sxc8qH1or7AYFtb/u71aptIwt+u821jz9XiFrQJiM7RC9yTZt0RQLtpPkTejRg4jfBXhDt+xOVkOZG7Z7cMrsMIS6eYDUJhpOXUO/c=,iv:6IseU28YQFyTmTh3dEUeK2pfvVq5CFIpXXYDkuY4HVY=,tag:NRZIDm07RhgDsEXT8IGl4Q==,type:str] sops: age: - enc: | @@ -102,8 +106,8 @@ sops: w2ToACYVigKMSlqPNG9bKcK75XI/iAONG48DQm1Nse3yB9/q3jwd+Q== -----END AGE ENCRYPTED FILE----- recipient: age1yl52c6aemw9anmfuqayamvsnkvlu6fmy0kk3gzvrraexxd9pr90qvs8pe5 - lastmodified: "2026-06-09T04:35:13Z" - mac: ENC[AES256_GCM,data:M2JOuRcG4ZSl/W8SSF6L04lGPH9La9e6V/5vrnTLYUWwQTB10Rd2ijq9BBpw+A5+k6NlLtZjD1cb3rTwRqpmGbfol0+4BQzd8aV2NsRxE2JdzdpfFsKPbmtmbN1c7sFRHPdXGlQqpwEH4GMTv9vVN7NRrnKDIhqLkuNA8EHW+Fo=,iv:F2OZl4TlRYlZ9H1xtvlgVdPDoC8qS2aOcXIQ6L036v4=,tag:Wtt5glZXZXdpAmBbzlQBtw==,type:str] + lastmodified: "2026-06-09T12:53:30Z" + mac: ENC[AES256_GCM,data:URruaFY0DBqsvW1O1rIu5cOgfXoTPl7awIX57VeFWWJQsrL8gK0PWeFq5KW9UzAXch9FvDjXl/EtVLFvtALn8fNX7dCOGvBA6pnamZhohykjBjk64Jzz171KntrtBUUxCXO0LgYj6PQsUc33wxBigsW9YdhI4X5WL4rejckWqps=,iv:yCZNTZTTuPzJyiSnrzJnWvWLCsR2WtIm+A88GK9/mxc=,tag:EZaAGDbbRPd3wNDERkTCIg==,type:str] pgp: - created_at: "2026-06-02T01:50:38Z" enc: |- -- 2.51.2