diff --git a/.secrets/hetzner/nextcloud-claude-bot.yaml b/.secrets/hetzner/nextcloud-claude-bot.yaml deleted file mode 100644 index 4d638f0..0000000 --- a/.secrets/hetzner/nextcloud-claude-bot.yaml +++ /dev/null @@ -1,35 +0,0 @@ -nextcloud-claude-bot: - secret: ENC[AES256_GCM,data:I0YxTjU89dDFnpF/TwZYBliLDyre0kNZbWvJD5Jdleihe1LGEptcLuTN0lkO9I8z9U7GDGxoAprb8W+5d2MQrA==,iv:m/q82cfbFID0aW3KfXCZSIa7FhtGx/3TCxv5x8GXVk0=,tag:+IuHUKVqdGrU0RS18NUlPg==,type:str] -sops: - age: - - recipient: age1qur4kh3gay9ryk3jh2snvjp6x9eq94zdrmgkrfcv4fzsu7l6lumq4tr3uy - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwUkZhbzhZbHZQRDJvZW1q - QjlIS2IxL1NaSnZ6L3JDU3hENEh4SFBBLzN3CnRvZkFmOUIzWUgybGdPblp4UmRH - U1JmUCt5WkNUc09EdktUdFBHY0lKUFkKLS0tIDZHRXRtZTBROGJJcFhMVDM4ZDJt - OGZOUElSNGJmaEtPalQ5MXBxQUFaRFkKu2EIbPsNMkejgc2rVC/nL5G2Hfp1IkiA - 3CV36NHFXKRlo8Fxj+hl1Fi063TRlNW0TK5fc15u4En7tdMnCdfJ+A== - -----END AGE ENCRYPTED FILE----- - - recipient: age1dwcz3fmp29ju4svy0t0wz4ylhpwlqa8xpw4l7t4gmgqr0ev37qrsfn840l - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPQTd4U0ZVOHJKMUgyLzF5 - RkpiVmMzYTRZS21ZUnNERTB3K2pDSXpFMlVjClkxNDl6WlcyN0xBT3MzYWVOWnNL - UldRZER4YVFuSHZ0S3BMSVZLQm5pRWcKLS0tIEpZVlA2RFZGbElUQWVWb3c5OSt3 - WlpSVGx4OEJGYU52L2xkdmNteWdGUE0KS0Xa9GmwTiAURgC72OhNLHW1/XgHyHFZ - 4yQ2qri2m14E5oheB8ELzMMY9K/yQUs90UqdZIS8UoSeaG4GqjEuQA== - -----END AGE ENCRYPTED FILE----- - - recipient: age1cpm9xhgue7sjvq7zyeeaxwr96c93sfzxxxj76sxsq7s7kgnygvcq5jxren - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4YjNVSE9mdjhKY2hWVGJ3 - d3VnVDBJODRyMkRyMUJUREwvT0ZUUkRtVUU0CjFOWCtFK05saHNTWGRoazQ2aVgw - bnlPMUNmdVVSUEFoVEtkaXcwVklETm8KLS0tIFBWMERoR0ZiMDJ1bW5May9RSWlv - VktQbU9STjNRVTh6TndIRVBLdFVFUVkKz0dBpDQ9+/Pp3FKsBpcmzuEROsZ65jkw - 9LRQTMGF6kSrbLjRkBs21t5t2kunKgCriAmd8Nv+S/sG/NKqpQMJ6A== - -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-03T14:36:03Z" - mac: ENC[AES256_GCM,data:y0gOLMHjzv2ER+Bvo0glkY2EC28K2uWtPPhv0EDzb4PczGDNgQWGHhdyFsN07+JJIf2LMpKV1u7BMp4e/dF1wDgZsR6wErZLxuLrXfZ6B7mTDOPGUR1rGo5PhbNIO90LL5uQ/aRLl38efqxgU8fHCkuXJkUtM38UQ9+7JN4PVic=,iv:2AKgYujqxeGiiVMhqC8FGFiYbTcogxZx/uUgh+8XowQ=,tag:3RwH2AboBU9T25fWjecsMQ==,type:str] - unencrypted_suffix: _unencrypted - version: 3.11.0 diff --git a/flake.lock b/flake.lock index 703e220..da101c7 100644 --- a/flake.lock +++ b/flake.lock @@ -19,16 +19,16 @@ "brew-src": { "flake": false, "locked": { - "lastModified": 1769363988, - "narHash": "sha256-BiGPeulrDVetXP+tjxhMcGLUROZAtZIhU5m4MqawCfM=", + "lastModified": 1763638478, + "narHash": "sha256-n/IMowE9S23ovmTkKX7KhxXC2Yq41EAVFR2FBIXPcT8=", "owner": "Homebrew", "repo": "brew", - "rev": "d01011cac6d72032c75fd2cd9489909e95d9faf2", + "rev": "fbfdbaba008189499958a7aeb1e2c36ab10c067d", "type": "github" }, "original": { "owner": "Homebrew", - "ref": "5.0.12", + "ref": "5.0.3", "repo": "brew", "type": "github" } @@ -134,11 +134,11 @@ ] }, "locked": { - "lastModified": 1769524058, - "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=", + "lastModified": 1768923567, + "narHash": "sha256-GVJ0jKsyXLuBzRMXCDY6D5J8wVdwP1DuQmmvYL/Vw/Q=", "owner": "nix-community", "repo": "disko", - "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d", + "rev": "00395d188e3594a1507f214a2f15d4ce5c07cb28", "type": "github" }, "original": { @@ -421,11 +421,11 @@ ] }, "locked": { - "lastModified": 1769580047, - "narHash": "sha256-tNqCP/+2+peAXXQ2V8RwsBkenlfWMERb+Uy6xmevyhM=", + "lastModified": 1768949235, + "narHash": "sha256-TtjKgXyg1lMfh374w5uxutd6Vx2P/hU81aEhTxrO2cg=", "owner": "nix-community", "repo": "home-manager", - "rev": "366d78c2856de6ab3411c15c1cb4fb4c2bf5c826", + "rev": "75ed713570ca17427119e7e204ab3590cc3bf2a5", "type": "github" }, "original": { @@ -454,11 +454,11 @@ "homebrew-cask": { "flake": false, "locked": { - "lastModified": 1770127519, - "narHash": "sha256-wIpVsLhx1gaB2JYfpVipt9ZLAReKFO0kmVIOhieHfqs=", + "lastModified": 1769077283, + "narHash": "sha256-alvFQmhX8POHxBP3/jResx6AJ06X+k6SF4/CiNndpPA=", "owner": "homebrew", "repo": "homebrew-cask", - "rev": "76e6c1bda247fe48dc30683203cce2b28b5d6eee", + "rev": "4a8185e145fa4fc8326705c666d608c3ee761612", "type": "github" }, "original": { @@ -470,11 +470,11 @@ "homebrew-core": { "flake": false, "locked": { - "lastModified": 1770130704, - "narHash": "sha256-95Jwssj3WbBwHO4nNB5uVIgIym/fuSDBb5vs6eKdgp0=", + "lastModified": 1769077518, + "narHash": "sha256-QtWC5CcY9xzfjcThSwZgise9RXbM2vZmw+Tot67RiJo=", "owner": "homebrew", "repo": "homebrew-core", - "rev": "5369d45006ea107dead79ef8ef4b29b7c972f276", + "rev": "2ac083c750fa2a6999ad05a7352e8edbd7abd969", "type": "github" }, "original": { @@ -515,11 +515,11 @@ }, "mnw": { "locked": { - "lastModified": 1769981889, - "narHash": "sha256-ndI7AxL/6auelkLHngdUGVImBiHkG8w2N2fOTKZKn4k=", + "lastModified": 1768701608, + "narHash": "sha256-kSvWF3Xt2HW9hmV5V7i8PqeWJIBUKmuKoHhOgj3Znzs=", "owner": "Gerg-L", "repo": "mnw", - "rev": "332fed8f43b77149c582f1782683d6aeee1f07cf", + "rev": "20d63a8a1ae400557c770052a46a9840e768926b", "type": "github" }, "original": { @@ -562,11 +562,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1770130359, - "narHash": "sha256-IfoT9oaeIE6XjXprMORG2qZFzGGZ0v6wJcOlQRdlpvY=", + "lastModified": 1768906339, + "narHash": "sha256-iwkHIz2IYRcELkBoKXQUHlP0bFGmrHIz/roJUVYsyx8=", "owner": "NotAShelf", "repo": "nvf", - "rev": "92854bd0eaaa06914afba345741c372439b8e335", + "rev": "18c55d3bebf2c704970b4ea6fd0261808bec8d94", "type": "github" }, "original": { @@ -580,11 +580,11 @@ "brew-src": "brew-src" }, "locked": { - "lastModified": 1769437432, - "narHash": "sha256-8d7KnCpT2LweRvSzZYEGd9IM3eFX+A78opcnDM0+ndk=", + "lastModified": 1764473698, + "narHash": "sha256-C91gPgv6udN5WuIZWNehp8qdLqlrzX6iF/YyboOj6XI=", "owner": "zhaofengli-wip", "repo": "nix-homebrew", - "rev": "a5409abd0d5013d79775d3419bcac10eacb9d8c5", + "rev": "6a8ab60bfd66154feeaa1021fc3b32684814a62a", "type": "github" }, "original": { @@ -595,11 +595,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1769302137, - "narHash": "sha256-QEDtctEkOsbx8nlFh4yqPEOtr4tif6KTqWwJ37IM2ds=", + "lastModified": 1768736227, + "narHash": "sha256-qgGq7CfrYKc3IBYQ7qp0Z/ZXndQVC5Bj0N8HW9mS2rM=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "a351494b0e35fd7c0b7a1aae82f0afddf4907aa8", + "rev": "d447553bcbc6a178618d37e61648b19e744370df", "type": "github" }, "original": { @@ -642,11 +642,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1770056022, - "narHash": "sha256-yvCz+Qmci1bVucXEyac3TdoSPMtjqVJmVy5wro6j/70=", + "lastModified": 1768940263, + "narHash": "sha256-sJERJIYTKPFXkoz/gBaBtRKke82h4DkX3BBSsKbfbvI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d04d8548aed39902419f14a8537006426dc1e4fa", + "rev": "3ceaaa8bc963ced4d830e06ea2d0863b6490ff03", "type": "github" }, "original": { @@ -748,11 +748,11 @@ ] }, "locked": { - "lastModified": 1770088046, - "narHash": "sha256-4hfYDnUTvL1qSSZEA4CEThxfz+KlwSFQ30Z9jgDguO0=", + "lastModified": 1769050281, + "narHash": "sha256-1H8DN4UZgEUqPUA5ecHOufLZMscJ4IlcGaEftaPtpBY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "71f9daa4e05e49c434d08627e755495ae222bc34", + "rev": "6deef0585c52d9e70f96b6121207e1496d4b0c49", "type": "github" }, "original": { @@ -835,11 +835,11 @@ ] }, "locked": { - "lastModified": 1770110318, - "narHash": "sha256-NUVGVtYBTC96WhPh4Y3SVM7vf0o1z5W4uqRBn9v1pfo=", + "lastModified": 1768863606, + "narHash": "sha256-1IHAeS8WtBiEo5XiyJBHOXMzECD6aaIOJmpQKzRRl64=", "owner": "Mic92", "repo": "sops-nix", - "rev": "f990b0a334e96d3ef9ca09d4bd92778b42fd84f9", + "rev": "c7067be8db2c09ab1884de67ef6c4f693973f4a2", "type": "github" }, "original": { @@ -857,11 +857,11 @@ "rust-overlay": "rust-overlay_3" }, "locked": { - "lastModified": 1769829418, - "narHash": "sha256-ALZKPUa0eHP6HwETAJ9PsAnYQjNLF6eEpo1W2fmYqwA=", + "lastModified": 1768997903, + "narHash": "sha256-UpBfh3I4PhykVHqV74rrxufF3X1Z8z8sx/lFgMFfIP8=", "owner": "haraldh", "repo": "ssh-tresor", - "rev": "2e1bfa29bd5ad5a60c3e0effd69851a67d455781", + "rev": "dd45aed45f8d9b8729b7698ef43e7cc32fab97b6", "type": "github" }, "original": { @@ -932,11 +932,11 @@ }, "unstable": { "locked": { - "lastModified": 1770115704, - "narHash": "sha256-KHFT9UWOF2yRPlAnSXQJh6uVcgNcWlFqqiAZ7OVlHNc=", + "lastModified": 1768886240, + "narHash": "sha256-C2TjvwYZ2VDxYWeqvvJ5XPPp6U7H66zeJlRaErJKoEM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e6eae2ee2110f3d31110d5c222cd395303343b08", + "rev": "80e4adbcf8992d3fd27ad4964fbb84907f9478b0", "type": "github" }, "original": { @@ -949,16 +949,16 @@ "xremap": { "flake": false, "locked": { - "lastModified": 1769021727, - "narHash": "sha256-2wylBk3+Zu1pHa41dhKwvUtxOVyHSMRDfOD9fIp8x2I=", + "lastModified": 1766606475, + "narHash": "sha256-FPZ4iQA/vVZGzbO8i8lTK8i9A3zs9BLqMvTMeAVv9rQ=", "owner": "k0kubun", "repo": "xremap", - "rev": "890e0a6ca92e90f3bcbd1e235abcf2192e233a46", + "rev": "cdc744d873c19899ef21f329c4305b4b5e53d459", "type": "github" }, "original": { "owner": "k0kubun", - "ref": "v0.14.10", + "ref": "v0.14.8", "repo": "xremap", "type": "github" } @@ -971,11 +971,11 @@ "xremap": "xremap" }, "locked": { - "lastModified": 1769636170, - "narHash": "sha256-X000Dgg053Dv9NIzm1b9QYSAHYtW2jHMVALQezui7L0=", + "lastModified": 1767318478, + "narHash": "sha256-h3oE50RedA8DRGrFU+Hv2kirt4rmzdaC9oSD+MSg9Ms=", "owner": "xremap", "repo": "nix-flake", - "rev": "00bc6dd4275d4b003a17ef7f5f271ba87f73d698", + "rev": "9a2224aa01a3c86e94b398c33329c8ff6496dc5d", "type": "github" }, "original": { diff --git a/modules/nixos/hardware/wooting/default.nix b/modules/nixos/hardware/wooting/default.nix deleted file mode 100644 index e5cc8f5..0000000 --- a/modules/nixos/hardware/wooting/default.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ - config, - lib, - ... -}: -with lib; -with lib.metacfg; -let - cfg = config.metacfg.hardware.wooting; -in -{ - options.metacfg.hardware.wooting = with types; { - enable = mkBoolOpt false "Whether or not to enable Wooting keyboard support."; - enableBluetoothCompat = mkBoolOpt true "Disable ClassicBondedOnly for Bluetooth compatibility."; - }; - - config = mkIf cfg.enable { - hardware.bluetooth.input.General.ClassicBondedOnly = mkIf cfg.enableBluetoothCompat false; - - services.udev.extraRules = '' - KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="342d", ATTRS{idProduct}=="e4c5", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl" - KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="342d", ATTRS{idProduct}=="e489", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl" - ''; - }; -} diff --git a/modules/nixos/services/acme-base/default.nix b/modules/nixos/services/acme-base/default.nix deleted file mode 100644 index d572848..0000000 --- a/modules/nixos/services/acme-base/default.nix +++ /dev/null @@ -1,41 +0,0 @@ -{ - config, - lib, - ... -}: -with lib; -with lib.metacfg; -let - cfg = config.metacfg.services.acmeBase; -in -{ - options.metacfg.services.acmeBase = with types; { - enable = mkBoolOpt false "Whether or not to enable ACME with common settings."; - email = mkOption { - type = types.str; - default = "harald@hoyer.xyz"; - description = "Registration email for ACME."; - }; - dnsProvider = mkOption { - type = types.str; - default = "cloudflare"; - description = "DNS provider for ACME DNS-01 challenge."; - }; - credentialsFile = mkOption { - type = types.nullOr types.path; - default = null; - description = "Path to the credentials file for the DNS provider."; - }; - }; - - config = mkIf cfg.enable { - security.acme = { - acceptTerms = true; - defaults = { - email = cfg.email; - dnsProvider = cfg.dnsProvider; - credentialsFile = mkIf (cfg.credentialsFile != null) cfg.credentialsFile; - }; - }; - }; -} diff --git a/modules/nixos/services/nginx-base/default.nix b/modules/nixos/services/nginx-base/default.nix deleted file mode 100644 index 6b2dd52..0000000 --- a/modules/nixos/services/nginx-base/default.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - config, - lib, - ... -}: -with lib; -with lib.metacfg; -let - cfg = config.metacfg.services.nginxBase; -in -{ - options.metacfg.services.nginxBase = with types; { - enable = mkBoolOpt false "Whether or not to enable nginx with common settings."; - clientMaxBodySize = mkOption { - type = types.str; - default = "1000M"; - description = "Maximum allowed size of the client request body."; - }; - enableAcmeGroup = mkBoolOpt true "Add nginx user to acme group."; - enableVcombinedLog = mkBoolOpt true "Enable vcombined log format."; - }; - - config = mkIf cfg.enable { - users.users.nginx.extraGroups = mkIf cfg.enableAcmeGroup [ "acme" ]; - - services.nginx = { - enable = true; - clientMaxBodySize = cfg.clientMaxBodySize; - recommendedGzipSettings = true; - recommendedOptimisation = true; - recommendedProxySettings = true; - recommendedTlsSettings = true; - appendHttpConfig = mkIf cfg.enableVcombinedLog '' - log_format vcombined '$host:$server_port ' - '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent"'; - access_log /var/log/nginx/access.log vcombined; - ''; - }; - }; -} diff --git a/modules/nixos/services/xremap/default.nix b/modules/nixos/services/xremap/default.nix deleted file mode 100644 index 6f22f38..0000000 --- a/modules/nixos/services/xremap/default.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ - config, - lib, - ... -}: -with lib; -with lib.metacfg; -let - cfg = config.metacfg.services.xremap; -in -{ - options.metacfg.services.xremap = with types; { - enable = mkBoolOpt false "Whether or not to enable xremap key remapping."; - userName = mkOption { - type = types.str; - default = "harald"; - description = "User to run xremap as."; - }; - withGnome = mkBoolOpt true "Enable GNOME support."; - deviceNames = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of device names to remap."; - }; - config = mkOption { - type = types.attrs; - default = { }; - description = "Xremap configuration."; - }; - }; - - config = { - services.xremap = { - enable = cfg.enable; - userName = mkIf cfg.enable cfg.userName; - serviceMode = mkIf cfg.enable "user"; - withGnome = mkIf cfg.enable cfg.withGnome; - deviceNames = mkIf cfg.enable cfg.deviceNames; - config = mkIf cfg.enable cfg.config; - }; - - users.users.${cfg.userName}.extraGroups = mkIf cfg.enable [ "input" ]; - }; -} diff --git a/modules/nixos/system/kernel-tweaks/default.nix b/modules/nixos/system/kernel-tweaks/default.nix deleted file mode 100644 index 0443809..0000000 --- a/modules/nixos/system/kernel-tweaks/default.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ - config, - lib, - ... -}: -with lib; -with lib.metacfg; -let - cfg = config.metacfg.system.kernelTweaks; -in -{ - options.metacfg.system.kernelTweaks = with types; { - enable = mkBoolOpt false "Whether or not to enable desktop kernel optimizations."; - pmFreezeTimeout = mkOption { - type = types.int; - default = 30000; - description = "PM freeze timeout in milliseconds."; - }; - enableZram = mkBoolOpt true "Enable zram swap."; - }; - - config = mkIf cfg.enable { - boot.kernel.sysctl = { - "power.pm_freeze_timeout" = cfg.pmFreezeTimeout; - }; - - zramSwap.enable = cfg.enableZram; - }; -} diff --git a/modules/nixos/system/no-sleep/default.nix b/modules/nixos/system/no-sleep/default.nix deleted file mode 100644 index 9e12659..0000000 --- a/modules/nixos/system/no-sleep/default.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ - config, - lib, - ... -}: -with lib; -with lib.metacfg; -let - cfg = config.metacfg.system.noSleep; -in -{ - options.metacfg.system.noSleep = with types; { - enable = mkBoolOpt false "Whether or not to disable all sleep targets."; - disableGdmAutoSuspend = mkBoolOpt false "Disable GDM auto-suspend."; - ignoreLidSwitch = mkBoolOpt false "Ignore lid switch events."; - }; - - config = mkIf cfg.enable { - systemd.targets.sleep.enable = false; - systemd.targets.suspend.enable = false; - systemd.targets.hibernate.enable = false; - systemd.targets.hybrid-sleep.enable = false; - - services.displayManager.gdm.autoSuspend = mkIf cfg.disableGdmAutoSuspend false; - - services.logind.settings.Login.HandleLidSwitch = mkIf cfg.ignoreLidSwitch "ignore"; - }; -} diff --git a/systems/aarch64-linux/m4nix/default.nix b/systems/aarch64-linux/m4nix/default.nix index f8a6934..d8cd570 100644 --- a/systems/aarch64-linux/m4nix/default.nix +++ b/systems/aarch64-linux/m4nix/default.nix @@ -9,13 +9,7 @@ with lib.metacfg; services.spice-autorandr.enable = true; services.spice-vdagentd.enable = true; - services.resolved.enable = true; - services.resolved.extraConfig = '' - ResolveUnicastSingleLabel=yes - ''; - metacfg = { - system.noSleep.enable = true; base.enable = true; gui.enable = true; nix-ld.enable = true; @@ -40,6 +34,13 @@ with lib.metacfg; ]; }; + # Disable the GNOME3/GDM auto-suspend feature that cannot be disabled in GUI! + # If no user is logged in, the machine will power down after 20 minutes. + systemd.targets.sleep.enable = false; + systemd.targets.suspend.enable = false; + systemd.targets.hibernate.enable = false; + systemd.targets.hybrid-sleep.enable = false; + environment.systemPackages = with pkgs; [ azure-cli desktop-file-utils @@ -59,11 +60,16 @@ with lib.metacfg; services.ratbagd.enable = true; + services.resolved.enable = true; + #services.resolved.dnssec = "allow-downgrade"; + services.resolved.extraConfig = '' + ResolveUnicastSingleLabel=yes + ''; + virtualisation = { docker.enable = true; podman.dockerCompat = false; libvirtd.enable = false; - rosetta.enable = true; }; system.autoUpgrade = { @@ -72,5 +78,7 @@ with lib.metacfg; allowReboot = false; }; + virtualisation.rosetta.enable = true; + system.stateVersion = "25.05"; } diff --git a/systems/aarch64-linux/rnix/default.nix b/systems/aarch64-linux/rnix/default.nix index f8a6934..d8cd570 100644 --- a/systems/aarch64-linux/rnix/default.nix +++ b/systems/aarch64-linux/rnix/default.nix @@ -9,13 +9,7 @@ with lib.metacfg; services.spice-autorandr.enable = true; services.spice-vdagentd.enable = true; - services.resolved.enable = true; - services.resolved.extraConfig = '' - ResolveUnicastSingleLabel=yes - ''; - metacfg = { - system.noSleep.enable = true; base.enable = true; gui.enable = true; nix-ld.enable = true; @@ -40,6 +34,13 @@ with lib.metacfg; ]; }; + # Disable the GNOME3/GDM auto-suspend feature that cannot be disabled in GUI! + # If no user is logged in, the machine will power down after 20 minutes. + systemd.targets.sleep.enable = false; + systemd.targets.suspend.enable = false; + systemd.targets.hibernate.enable = false; + systemd.targets.hybrid-sleep.enable = false; + environment.systemPackages = with pkgs; [ azure-cli desktop-file-utils @@ -59,11 +60,16 @@ with lib.metacfg; services.ratbagd.enable = true; + services.resolved.enable = true; + #services.resolved.dnssec = "allow-downgrade"; + services.resolved.extraConfig = '' + ResolveUnicastSingleLabel=yes + ''; + virtualisation = { docker.enable = true; podman.dockerCompat = false; libvirtd.enable = false; - rosetta.enable = true; }; system.autoUpgrade = { @@ -72,5 +78,7 @@ with lib.metacfg; allowReboot = false; }; + virtualisation.rosetta.enable = true; + system.stateVersion = "25.05"; } diff --git a/systems/x86_64-linux/amd/default.nix b/systems/x86_64-linux/amd/default.nix index 795fe5d..08b0b84 100644 --- a/systems/x86_64-linux/amd/default.nix +++ b/systems/x86_64-linux/amd/default.nix @@ -18,17 +18,21 @@ with lib.metacfg; 22000 ]; + services.tailscale.enable = true; + services.cratedocs-mcp.enable = true; services.openssh = { enable = true; }; - services.tailscale.enable = true; - services.resolved.enable = true; + hardware.bluetooth.input.General.ClassicBondedOnly = false; + services.udev.extraRules = '' + KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="342d", ATTRS{idProduct}=="e4c5", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl" + KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="342d", ATTRS{idProduct}=="e489", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl" + ''; metacfg = { - hardware.wooting.enable = true; base.enable = true; gui.enable = true; nix-ld.enable = true; @@ -55,21 +59,15 @@ with lib.metacfg; "dialout" "tss" ]; - system.kernelTweaks.enable = true; - }; - - system.autoUpgrade = { - enable = true; - operation = "boot"; - allowReboot = false; }; nixpkgs.config.permittedInsecurePackages = [ "electron-27.3.11" ]; - # Additional kernel tuning beyond the module defaults + # Kernel tuning boot.kernel.sysctl = { + "power.pm_freeze_timeout" = 30000; # Reduce swap usage (you have zram) "vm.swappiness" = 10; # Prefer keeping directory/inode caches @@ -103,7 +101,6 @@ with lib.metacfg; kubectl kubectx logseq - nvtopPackages.amd obsidian piper-tts tipp10 @@ -114,18 +111,32 @@ with lib.metacfg; # zram swap with zstd compression for better performance zramSwap = { + enable = true; algorithm = "zstd"; memoryPercent = 50; }; services.ratbagd.enable = true; + services.resolved.enable = true; + + #services.resolved.dnssec = "allow-downgrade"; + #services.resolved.extraConfig = '' + # ResolveUnicastSingleLabel=yes + #''; + virtualisation = { libvirtd.enable = true; docker.enable = true; podman.dockerCompat = false; }; + system.autoUpgrade = { + enable = true; + operation = "boot"; + allowReboot = false; + }; + services.trezord.enable = true; services.ollama = { diff --git a/systems/x86_64-linux/amd/xremap.nix b/systems/x86_64-linux/amd/xremap.nix index c28dd0a..64a45c0 100644 --- a/systems/x86_64-linux/amd/xremap.nix +++ b/systems/x86_64-linux/amd/xremap.nix @@ -1,21 +1,33 @@ +# In /etc/nixos/configuration.nix { ... }: { - metacfg.services.xremap = { - enable = true; - deviceNames = [ - "Hangsheng MonsGeek Keyboard" - "HS Galaxy100 Keyboard" + users.users.harald.extraGroups = [ "input" ]; + + # Enable the xremap service + services.xremap.enable = true; + services.xremap.userName = "harald"; # Replace with your username + services.xremap.serviceMode = "user"; # Run as user service, not system-wide + services.xremap.withGnome = true; + + # Add a specific configuration block to select your keyboard(s) by name + services.xremap.deviceNames = [ + # Use the name found in the log output: "Hangsheng MonsGeek Keyboard System Control" + "Hangsheng MonsGeek Keyboard" + "HS Galaxy100 Keyboard" + # You can usually shorten the name slightly to match the device you want + ]; + + # Define your remapping configuration using Nix's attribute set format + services.xremap.config = { + keymap = [ + { + remap = { + # Map Alt+C (LeftAlt-C) to Ctrl+C (LeftControl-C) + LeftAlt-C = "COPY"; + LeftAlt-V = "PASTE"; + LeftAlt-X = "CUT"; + }; + } ]; - config = { - keymap = [ - { - remap = { - LeftAlt-C = "COPY"; - LeftAlt-V = "PASTE"; - LeftAlt-X = "CUT"; - }; - } - ]; - }; }; } diff --git a/systems/x86_64-linux/mx/acme.nix b/systems/x86_64-linux/mx/acme.nix index ee338c4..069bb2d 100644 --- a/systems/x86_64-linux/mx/acme.nix +++ b/systems/x86_64-linux/mx/acme.nix @@ -1,4 +1,6 @@ { + pkgs, + lib, config, ... }: @@ -7,9 +9,14 @@ sopsFile = ../../../.secrets/hetzner/internetbs.yaml; # bring your own password file }; - metacfg.services.acmeBase.credentialsFile = config.sops.secrets.internetbs.path; - - security.acme.certs = { + security.acme = { + acceptTerms = true; + defaults = { + email = "harald@hoyer.xyz"; + dnsProvider = "cloudflare"; + credentialsFile = config.sops.secrets.internetbs.path; + }; + certs = { "surfsite.org" = { extraDomainNames = [ "*.surfsite.org" ]; }; @@ -64,4 +71,5 @@ extraDomainNames = [ "*.harald-hoyer.de" ]; }; }; + }; } diff --git a/systems/x86_64-linux/mx/default.nix b/systems/x86_64-linux/mx/default.nix index 3efb22c..e8ce185 100644 --- a/systems/x86_64-linux/mx/default.nix +++ b/systems/x86_64-linux/mx/default.nix @@ -12,7 +12,6 @@ ./mailserver.nix ./network.nix ./nextcloud.nix - ./nextcloud-claude-bot ./nginx.nix ./postgresql.nix ./rspamd.nix @@ -23,8 +22,6 @@ services.tailscale.enable = true; metacfg = { - services.nginxBase.enable = true; - services.acmeBase.enable = true; emailOnFailure.enable = true; base.enable = true; nix.enable = true; @@ -45,6 +42,7 @@ dates = "04:00"; operation = "switch"; allowReboot = true; + # flake = lib.mkForce "git+file:///var/lib/gitea/repositories/harald/nixcfg.git#mx"; flake = lib.mkForce "/root/nixcfg/.#mx"; }; diff --git a/systems/x86_64-linux/mx/nextcloud-claude-bot/README.md b/systems/x86_64-linux/mx/nextcloud-claude-bot/README.md deleted file mode 100644 index a16b3f8..0000000 --- a/systems/x86_64-linux/mx/nextcloud-claude-bot/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# Nextcloud Claude Bot Setup - -## Voraussetzungen - -- NixOS Server mit Nextcloud (Talk App aktiviert) -- Claude Code CLI installiert und authentifiziert -- Nextcloud Talk Version 17+ (Nextcloud 26+) - -## 1. Bot Secret generieren - -```bash -openssl rand -hex 32 > /var/secrets/nextcloud-claude-bot -chmod 600 /var/secrets/nextcloud-claude-bot -``` - -## 2. NixOS Konfiguration - -Kopiere die Dateien nach `/etc/nixos/nextcloud-claude-bot/` oder in dein Flake: - -``` -/etc/nixos/ -├── configuration.nix -└── nextcloud-claude-bot/ - ├── module.nix - └── bot.py -``` - -Füge das Modul zu deiner `configuration.nix` hinzu (siehe `example-config.nix`). - -## 3. System rebuilden - -```bash -nixos-rebuild switch -``` - -## 4. Bot bei Nextcloud registrieren - -```bash -# Als root oder mit sudo -cd /var/www/nextcloud # oder wo dein Nextcloud liegt - -# Bot secret auslesen -BOT_SECRET=$(cat /var/secrets/nextcloud-claude-bot) - -# Bot installieren -sudo -u nextcloud php occ talk:bot:install \ - "Claude" \ - "Claude AI Assistant" \ - "http://127.0.0.1:8085/webhook" \ - "$BOT_SECRET" -``` - -Falls der Bot extern erreichbar sein muss: -```bash -sudo -u nextcloud php occ talk:bot:install \ - "Claude" \ - "Claude AI Assistant" \ - "https://cloud.example.com/_claude-bot/webhook" \ - "$BOT_SECRET" -``` - -## 5. Bot aktivieren - -Nach der Installation musst du den Bot für Konversationen aktivieren: - -```bash -# Liste alle Bots -sudo -u nextcloud php occ talk:bot:list - -# Bot für alle User verfügbar machen (optional) -sudo -u nextcloud php occ talk:bot:state 1 -``` - -## 6. Testen - -1. Öffne Nextcloud Talk -2. Starte einen neuen Chat mit dem Bot (suche nach "Claude") -3. Schreibe eine Nachricht - -### Health Check - -```bash -curl http://127.0.0.1:8085/health -``` - -### Logs prüfen - -```bash -journalctl -u nextcloud-claude-bot -f -``` - -## Troubleshooting - -### Bot antwortet nicht - -1. Prüfe ob der Service läuft: - ```bash - systemctl status nextcloud-claude-bot - ``` - -2. Prüfe die Logs: - ```bash - journalctl -u nextcloud-claude-bot -n 50 - ``` - -3. Teste den Webhook manuell: - ```bash - curl -X POST http://127.0.0.1:8085/webhook \ - -H "Content-Type: application/json" \ - -d '{"actor":{"type":"users","id":"harald"},"message":{"message":"test","id":1},"conversation":{"token":"abc123","type":1}}' - ``` - -### Claude CLI Fehler - -Stelle sicher, dass Claude CLI als der Service-User funktioniert: - -```bash -# Teste als der User -sudo -u nextcloud-claude-bot claude --print "Hello" -``` - -Die Claude CLI Config liegt in `/var/lib/nextcloud-claude-bot/.config/claude/`. - -### Signature Fehler - -Prüfe ob das Bot Secret in Nextcloud und im Service übereinstimmt: - -```bash -# Secret im Service -cat /var/secrets/nextcloud-claude-bot - -# Secret in Nextcloud (verschlüsselt gespeichert) -sudo -u nextcloud php occ talk:bot:list -``` - -## Befehle im Chat - -- `/help` oder `/hilfe` – Hilfe anzeigen -- `/clear` oder `/reset` – Konversation zurücksetzen - -## Sicherheitshinweise - -- Der Bot läuft nur auf localhost und ist nicht direkt erreichbar -- Nur in `allowedUsers` gelistete Nutzer können den Bot verwenden -- Webhook-Signaturen werden verifiziert -- DynamicUser isoliert den Service diff --git a/systems/x86_64-linux/mx/nextcloud-claude-bot/bot.py b/systems/x86_64-linux/mx/nextcloud-claude-bot/bot.py deleted file mode 100644 index 3788d2a..0000000 --- a/systems/x86_64-linux/mx/nextcloud-claude-bot/bot.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env python3 -""" -Nextcloud Talk Claude Bot - -Receives webhooks from Nextcloud Talk and responds using Claude CLI. -""" - -import asyncio -import hashlib -import hmac -import json -import logging -import os -import re -import secrets -from datetime import datetime -from typing import Optional - -import httpx -from fastapi import FastAPI, Request, HTTPException, Header -from fastapi.responses import JSONResponse - -# Configuration from environment -NEXTCLOUD_URL = os.environ.get("NEXTCLOUD_URL", "").rstrip("/") -CLAUDE_PATH = os.environ.get("CLAUDE_PATH", "claude") -ALLOWED_USERS = [u.strip() for u in os.environ.get("ALLOWED_USERS", "").split(",") if u.strip()] -TIMEOUT = int(os.environ.get("TIMEOUT", "120")) -SYSTEM_PROMPT = os.environ.get("SYSTEM_PROMPT", "") - -# Bot secret from systemd credential -def get_bot_secret() -> str: - cred_path = os.environ.get("CREDENTIALS_DIRECTORY", "") - if cred_path: - secret_file = os.path.join(cred_path, "bot-secret") - if os.path.exists(secret_file): - with open(secret_file) as f: - return f.read().strip() - # Fallback for development - return os.environ.get("BOT_SECRET", "") - -BOT_SECRET = get_bot_secret() - -# Logging -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s [%(levelname)s] %(message)s" -) -log = logging.getLogger(__name__) - -app = FastAPI(title="Nextcloud Claude Bot") - -# In-memory conversation history per conversation token -# Format: {token: [(user, message), ...]} -conversations: dict[str, list[tuple[str, str]]] = {} -MAX_HISTORY = int(os.environ.get("CONTEXT_MESSAGES", "6")) - - -def generate_bot_auth_headers(body: str = "") -> dict: - """Generate authentication headers for bot requests to Nextcloud.""" - random = secrets.token_hex(32) - digest = hmac.new( - BOT_SECRET.encode(), - (random + body).encode(), - hashlib.sha256 - ).hexdigest() - return { - "X-Nextcloud-Talk-Bot-Random": random, - "X-Nextcloud-Talk-Bot-Signature": digest, - "OCS-APIRequest": "true", - } - - - - -def verify_signature(body: bytes, signature: str, random: Optional[str] = None) -> bool: - """Verify Nextcloud webhook signature.""" - if not BOT_SECRET: - log.warning("No bot secret configured, skipping signature verification") - return True - - # Nextcloud sends: sha256= - if signature.startswith("sha256="): - signature = signature[7:] - - # Try different signature computation methods - # Method 1: Just body - expected1 = hmac.new(BOT_SECRET.encode(), body, hashlib.sha256).hexdigest() - - # Method 2: random + body (if random header present) - if random: - expected2 = hmac.new(BOT_SECRET.encode(), (random.encode() + body), hashlib.sha256).hexdigest() - else: - expected2 = None - - - if hmac.compare_digest(expected1, signature): - return True - if expected2 and hmac.compare_digest(expected2, signature): - return True - - return False - - -BOT_SYSTEM_PROMPT = """\ -Du bist ein KI-Assistent im Nextcloud Talk Chat. -Deine Antworten werden direkt in den Chatraum gepostet. -Halte deine Antworten kurz und prägnant, da es ein Chat ist. -Nutze Markdown für Formatierung wenn sinnvoll. - -Du erhältst: -- : Die letzten Nachrichten im Chatraum (User und deine Antworten) -- : Die aktuelle Nachricht, auf die du antworten sollst""" - - -def build_system_prompt() -> str: - """Build the full system prompt from hardcoded + optional custom parts.""" - if SYSTEM_PROMPT: - return f"{BOT_SYSTEM_PROMPT}\n\n{SYSTEM_PROMPT.strip()}" - return BOT_SYSTEM_PROMPT - - -def build_prompt(conversation_token: str, current_message: str, current_user: str) -> str: - """Build user prompt with in-memory conversation history using XML structure.""" - parts = [] - - # Add chat history if available - history = conversations.get(conversation_token, []) - if history: - parts.append("") - for role, msg in history[-MAX_HISTORY:]: - parts.append(f"{role}: {msg}") - parts.append("") - parts.append("") - - # Add current message - parts.append(f"") - parts.append(current_message) - parts.append("") - - return "\n".join(parts) - - -async def call_claude(prompt: str) -> str: - """Call Claude CLI and return response.""" - cmd = [ - CLAUDE_PATH, "--print", - "--tools", "WebSearch,WebFetch", - "--allowedTools", "WebSearch,WebFetch", - "--append-system-prompt", build_system_prompt(), - ] - - log.info(f"Calling Claude: {cmd[0]} --print --append-system-prompt ...") - - try: - proc = await asyncio.create_subprocess_exec( - *cmd, - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - - stdout, stderr = await asyncio.wait_for( - proc.communicate(prompt.encode()), - timeout=TIMEOUT - ) - - if proc.returncode != 0: - log.error(f"Claude CLI error: {stderr.decode()}") - return f"❌ Fehler beim Aufruf von Claude: {stderr.decode()[:200]}" - - return stdout.decode().strip() - - except asyncio.TimeoutError: - log.error(f"Claude CLI timeout after {TIMEOUT}s") - return f"⏱️ Timeout: Claude hat nicht innerhalb von {TIMEOUT}s geantwortet." - except Exception as e: - log.exception("Error calling Claude") - return f"❌ Fehler: {str(e)}" - - -async def send_reply(conversation_token: str, message: str, reply_to: int = None): - """Send reply back to Nextcloud Talk.""" - if not NEXTCLOUD_URL: - log.error("NEXTCLOUD_URL not configured") - return - - url = f"{NEXTCLOUD_URL}/ocs/v2.php/apps/spreed/api/v1/bot/{conversation_token}/message" - - # Bot authentication - signature is over the message being sent - headers = generate_bot_auth_headers(message) - headers["Content-Type"] = "application/json" - - payload = { - "message": message, - "referenceId": hashlib.sha256(f"{conversation_token}-{datetime.now().isoformat()}".encode()).hexdigest()[:32], - } - - if reply_to: - payload["replyTo"] = reply_to - - async with httpx.AsyncClient() as client: - try: - resp = await client.post(url, json=payload, headers=headers) - if resp.status_code not in (200, 201): - log.error(f"Failed to send reply: {resp.status_code} {resp.text}") - else: - log.info(f"Reply sent to conversation {conversation_token}") - except Exception as e: - log.exception("Error sending reply to Nextcloud") - - -@app.post("/webhook") -async def handle_webhook( - request: Request, - x_nextcloud_talk_signature: Optional[str] = Header(None, alias="X-Nextcloud-Talk-Signature"), - x_nextcloud_talk_random: Optional[str] = Header(None, alias="X-Nextcloud-Talk-Random"), -): - """Handle incoming webhook from Nextcloud Talk.""" - body = await request.body() - - # Verify signature - if x_nextcloud_talk_signature and not verify_signature(body, x_nextcloud_talk_signature, x_nextcloud_talk_random): - log.warning("Invalid webhook signature") - raise HTTPException(status_code=401, detail="Invalid signature") - - try: - data = json.loads(body) - except json.JSONDecodeError: - raise HTTPException(status_code=400, detail="Invalid JSON") - - log.info(f"Received webhook: {json.dumps(data, indent=2)[:500]}") - - # Extract message info - Nextcloud Talk Bot webhook format - actor = data.get("actor", {}) - actor_type = actor.get("type", "") - actor_id_full = actor.get("id", "") # e.g., "users/harald" - - # Extract username from "users/username" format - if "/" in actor_id_full: - actor_id = actor_id_full.split("/", 1)[1] - else: - actor_id = actor_id_full - - # Message is in object.content as JSON string - obj = data.get("object", {}) - message_id = obj.get("id") - content_str = obj.get("content", "{}") - try: - content = json.loads(content_str) - message_text = content.get("message", "") - except json.JSONDecodeError: - message_text = content_str - - # Conversation info is in target - target = data.get("target", {}) - conversation_token = target.get("id", "") - - # Only respond to user/person messages - if actor_type not in ("users", "Person"): - log.info(f"Ignoring non-user actor: {actor_type}") - return JSONResponse({"status": "ignored", "reason": "not a user message"}) - - # For now, treat all conversations the same (respond to mentions) - is_direct_message = False # We can't easily determine this from the webhook - - # Check for bot mention in message (Nextcloud uses @"Bot Name" format) - bot_mentioned = False - clean_message = message_text - - # Look for mention patterns: @Claude or @"Claude" - mention_patterns = [ - r'@"?Claude"?\s*', - r'@"?claude"?\s*', - ] - - for pattern in mention_patterns: - if re.search(pattern, message_text, re.IGNORECASE): - bot_mentioned = True - clean_message = re.sub(pattern, '', message_text, flags=re.IGNORECASE).strip() - break - - # In group chats, only respond if mentioned - if not is_direct_message and not bot_mentioned: - log.info(f"Ignoring message in group chat without mention") - return JSONResponse({"status": "ignored", "reason": "not mentioned in group chat"}) - - # Use clean message (without mention) for processing - if bot_mentioned: - message_text = clean_message - - # Check allowed users - if ALLOWED_USERS and actor_id not in ALLOWED_USERS: - log.warning(f"User {actor_id} not in allowed list") - await send_reply( - conversation_token, - "🚫 Du bist nicht berechtigt, diesen Bot zu nutzen.", - reply_to=message_id - ) - return JSONResponse({"status": "rejected", "reason": "user not allowed"}) - - if not message_text.strip(): - return JSONResponse({"status": "ignored", "reason": "empty message"}) - - log.info(f"Processing message from {actor_id}: {message_text[:100]}") - - if message_text.strip().lower() in ("hilfe", "help", "?"): - help_text = """🤖 **Claude Bot Hilfe** - -Schreib mir einfach eine Nachricht und ich antworte dir. - -**Nutzung:** -• In Gruppenchats: @Claude gefolgt von deiner Frage - -**Befehle:** -• `hilfe` oder `?` – Diese Hilfe anzeigen - -Der Bot merkt sich die letzten Nachrichten pro Raum (bis zum Neustart).""" - await send_reply(conversation_token, help_text, reply_to=message_id) - return JSONResponse({"status": "ok", "action": "help"}) - - # Build prompt with chat history and call Claude - prompt = build_prompt(conversation_token, message_text, actor_id) - response = await call_claude(prompt) - - # Store in history - if conversation_token not in conversations: - conversations[conversation_token] = [] - conversations[conversation_token].append((f"User ({actor_id})", message_text)) - conversations[conversation_token].append(("Assistant", response)) - - # Trim history - if len(conversations[conversation_token]) > MAX_HISTORY * 2: - conversations[conversation_token] = conversations[conversation_token][-MAX_HISTORY * 2:] - - # Send response - await send_reply(conversation_token, response, reply_to=message_id) - - return JSONResponse({"status": "ok"}) - - -@app.get("/health") -async def health(): - """Health check endpoint.""" - return { - "status": "ok", - "nextcloud_url": NEXTCLOUD_URL, - "claude_path": CLAUDE_PATH, - "allowed_users": ALLOWED_USERS if ALLOWED_USERS else "all", - "max_history": MAX_HISTORY, - } - - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="127.0.0.1", port=8085) diff --git a/systems/x86_64-linux/mx/nextcloud-claude-bot/default.nix b/systems/x86_64-linux/mx/nextcloud-claude-bot/default.nix deleted file mode 100644 index 9bda0c7..0000000 --- a/systems/x86_64-linux/mx/nextcloud-claude-bot/default.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ config, ... }: -{ - imports = [ ./module.nix ]; - - services.nextcloud-claude-bot = { - enable = true; - nextcloudUrl = "https://nc.hoyer.xyz"; - botSecretFile = config.sops.secrets."nextcloud-claude-bot/secret".path; - allowedUsers = []; # Allow all registered users - # Optional extra instructions (base prompt is hardcoded in bot.py) - # systemPrompt = "Additional custom instructions here"; - }; - - sops.secrets."nextcloud-claude-bot/secret" = { - sopsFile = ../../../../.secrets/hetzner/nextcloud-claude-bot.yaml; - restartUnits = [ "nextcloud-claude-bot.service" ]; - owner = "claude-bot"; - }; - - # Nginx location for Nextcloud to send webhooks to the bot - services.nginx.virtualHosts."nc.hoyer.xyz".locations."/_claude-bot/" = { - proxyPass = "http://127.0.0.1:8085/"; - extraConfig = '' - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Only allow from localhost (Nextcloud on same server) - allow 127.0.0.1; - deny all; - ''; - }; -} diff --git a/systems/x86_64-linux/mx/nextcloud-claude-bot/example-config.nix b/systems/x86_64-linux/mx/nextcloud-claude-bot/example-config.nix deleted file mode 100644 index 9560ff3..0000000 --- a/systems/x86_64-linux/mx/nextcloud-claude-bot/example-config.nix +++ /dev/null @@ -1,80 +0,0 @@ -# Example NixOS configuration for the Nextcloud Claude Bot -# Add this to your configuration.nix or a separate module - -{ config, pkgs, ... }: - -{ - imports = [ - ./nextcloud-claude-bot/module.nix - ]; - - # Install Claude Code CLI - # Note: You'll need to either: - # 1. Use the official package if available in nixpkgs - # 2. Package it yourself - # 3. Use a binary wrapper - - # Option 1: If claude-code is in nixpkgs (check latest state) - # environment.systemPackages = [ pkgs.claude-code ]; - - # Option 2: Manual binary installation wrapper - nixpkgs.overlays = [ - (final: prev: { - claude-code = final.writeShellScriptBin "claude" '' - # Assumes claude is installed via npm globally or similar - exec ${final.nodejs}/bin/node /opt/claude-code/cli.js "$@" - ''; - }) - ]; - - # Create bot secret - # Generate with: openssl rand -hex 32 - # Store in a file, e.g., /var/secrets/nextcloud-claude-bot - - services.nextcloud-claude-bot = { - enable = true; - port = 8085; - host = "127.0.0.1"; - - nextcloudUrl = "https://cloud.example.com"; - botSecretFile = "/var/secrets/nextcloud-claude-bot"; - - # Only allow specific users - allowedUsers = [ "harald" ]; - - # Claude settings - maxTokens = 4096; - timeout = 120; - - # Optional system prompt - systemPrompt = '' - Du bist ein hilfreicher Assistent. Antworte auf Deutsch, - es sei denn der Nutzer schreibt auf Englisch. - ''; - }; - - # Ensure secrets directory exists with proper permissions - systemd.tmpfiles.rules = [ - "d /var/secrets 0750 root root -" - ]; - - # If Nextcloud runs locally, bot can stay on localhost. - # If you need external access (e.g., Nextcloud on different server): - services.nginx.virtualHosts."cloud.example.com" = { - # ... your existing Nextcloud config ... - - locations."/_claude-bot/" = { - proxyPass = "http://127.0.0.1:8085/"; - extraConfig = '' - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Only allow from Nextcloud itself - allow 127.0.0.1; - deny all; - ''; - }; - }; -} diff --git a/systems/x86_64-linux/mx/nextcloud-claude-bot/module.nix b/systems/x86_64-linux/mx/nextcloud-claude-bot/module.nix deleted file mode 100644 index 6edcd71..0000000 --- a/systems/x86_64-linux/mx/nextcloud-claude-bot/module.nix +++ /dev/null @@ -1,143 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.services.nextcloud-claude-bot; - - pythonEnv = pkgs.python3.withPackages (ps: with ps; [ - fastapi - uvicorn - httpx - ]); - - botModule = pkgs.runCommand "nextcloud-claude-bot-module" {} '' - mkdir -p $out - cp ${./bot.py} $out/nextcloud_claude_bot.py - ''; - -in { - options.services.nextcloud-claude-bot = { - enable = mkEnableOption "Nextcloud Talk Claude Bot"; - - port = mkOption { - type = types.port; - default = 8085; - description = "Port for the webhook listener"; - }; - - host = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Host to bind to"; - }; - - nextcloudUrl = mkOption { - type = types.str; - example = "https://cloud.example.com"; - description = "Base URL of your Nextcloud instance"; - }; - - botSecretFile = mkOption { - type = types.path; - description = "Path to file containing the bot secret (shared with Nextcloud)"; - }; - - claudePath = mkOption { - type = types.path; - default = "${pkgs.claude-code}/bin/claude"; - description = "Path to claude CLI binary"; - }; - - allowedUsers = mkOption { - type = types.listOf types.str; - default = []; - example = [ "harald" "admin" ]; - description = "Nextcloud usernames allowed to talk to the bot (empty = all)"; - }; - - contextMessages = mkOption { - type = types.int; - default = 6; - description = "Number of recent messages to fetch from chat for context"; - }; - - timeout = mkOption { - type = types.int; - default = 120; - description = "Timeout in seconds for Claude CLI"; - }; - - systemPrompt = mkOption { - type = types.nullOr types.str; - default = null; - example = "Du bist ein hilfreicher Assistent."; - description = "Optional system prompt for Claude"; - }; - }; - - config = mkIf cfg.enable { - systemd.services.nextcloud-claude-bot = { - description = "Nextcloud Talk Claude Bot"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - - environment = { - HOME = "/var/lib/nextcloud-claude-bot"; - BOT_HOST = cfg.host; - BOT_PORT = toString cfg.port; - NEXTCLOUD_URL = cfg.nextcloudUrl; - CLAUDE_PATH = cfg.claudePath; - ALLOWED_USERS = concatStringsSep "," cfg.allowedUsers; - CONTEXT_MESSAGES = toString cfg.contextMessages; - TIMEOUT = toString cfg.timeout; - SYSTEM_PROMPT = cfg.systemPrompt or ""; - PYTHONPATH = botModule; - }; - - serviceConfig = { - Type = "simple"; - ExecStart = "${pythonEnv}/bin/uvicorn nextcloud_claude_bot:app --host ${cfg.host} --port ${toString cfg.port}"; - Restart = "always"; - RestartSec = 5; - - User = "claude-bot"; - Group = "claude-bot"; - - # Security hardening - NoNewPrivileges = true; - ProtectSystem = "strict"; - ProtectHome = "read-only"; - PrivateTmp = true; - PrivateDevices = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - MemoryDenyWriteExecute = false; # Python needs this - LockPersonality = true; - - # Bot secret - LoadCredential = "bot-secret:${cfg.botSecretFile}"; - - # Claude CLI needs home for config - StateDirectory = "nextcloud-claude-bot"; - }; - }; - - users.users.claude-bot = { - isSystemUser = true; - group = "claude-bot"; - home = "/var/lib/nextcloud-claude-bot"; - }; - - users.groups.claude-bot = {}; - - # Nginx reverse proxy config (optional, if you want external access) - # services.nginx.virtualHosts."cloud.example.com".locations."/claude-bot/" = { - # proxyPass = "http://${cfg.host}:${toString cfg.port}/"; - # }; - }; -} diff --git a/systems/x86_64-linux/mx/nginx.nix b/systems/x86_64-linux/mx/nginx.nix index e71eb46..26556bf 100644 --- a/systems/x86_64-linux/mx/nginx.nix +++ b/systems/x86_64-linux/mx/nginx.nix @@ -1,6 +1,21 @@ { ... }: { - services.nginx.virtualHosts = { + users.users.nginx.extraGroups = [ "acme" ]; + services.nginx = { + enable = true; + clientMaxBodySize = "1000M"; + appendHttpConfig = '' + log_format vcombined '$host:$server_port ' + '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + access_log /var/log/nginx/access.log vcombined; + ''; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + virtualHosts = { "00000" = { useACMEHost = "hoyer.xyz"; serverName = "_"; @@ -142,4 +157,5 @@ forceSSL = true; }; }; + }; } diff --git a/systems/x86_64-linux/nixtee1/default.nix b/systems/x86_64-linux/nixtee1/default.nix index 6c879ae..4ca3282 100644 --- a/systems/x86_64-linux/nixtee1/default.nix +++ b/systems/x86_64-linux/nixtee1/default.nix @@ -6,6 +6,8 @@ { imports = [ ./hardware-configuration.nix ]; + services.tailscale.enable = true; + boot.kernelPackages = lib.mkOverride 0 pkgs.linuxPackages_latest; boot.loader.systemd-boot.enable = false; # Bootloader. @@ -16,8 +18,6 @@ security.tpm2.enable = false; security.tpm2.abrmd.enable = false; - services.tailscale.enable = true; - metacfg = { base.enable = true; nix-ld.enable = true; @@ -37,6 +37,12 @@ podman.dockerCompat = false; }; + system.autoUpgrade = { + enable = true; + operation = "switch"; + allowReboot = true; + }; + networking.wireless.enable = false; # Enables wireless support via wpa_supplicant. networking.firewall.allowPing = true; @@ -60,11 +66,5 @@ } ]; - system.autoUpgrade = { - enable = true; - operation = "switch"; - allowReboot = true; - }; - system.stateVersion = "25.05"; } diff --git a/systems/x86_64-linux/sgx-attic/default.nix b/systems/x86_64-linux/sgx-attic/default.nix index 896c57c..5cd7e1d 100644 --- a/systems/x86_64-linux/sgx-attic/default.nix +++ b/systems/x86_64-linux/sgx-attic/default.nix @@ -1,5 +1,7 @@ { + pkgs, lib, + config, ... }: with lib; @@ -15,17 +17,17 @@ with lib.metacfg; nix.enable = true; }; + virtualisation = { + docker.enable = true; + podman.dockerCompat = false; + }; + system.autoUpgrade = { enable = true; operation = "switch"; allowReboot = true; }; - virtualisation = { - docker.enable = true; - podman.dockerCompat = false; - }; - security.tpm2.enable = false; security.tpm2.abrmd.enable = false; diff --git a/systems/x86_64-linux/sgx/acme.nix b/systems/x86_64-linux/sgx/acme.nix index 76df2ef..b3e1272 100644 --- a/systems/x86_64-linux/sgx/acme.nix +++ b/systems/x86_64-linux/sgx/acme.nix @@ -7,9 +7,14 @@ sopsFile = ../../../.secrets/sgx/internetbs.yaml; # bring your own password file }; - metacfg.services.acmeBase.credentialsFile = config.sops.secrets.internetbs.path; - - security.acme.certs = { + security.acme = { + acceptTerms = true; + defaults = { + email = "harald@hoyer.xyz"; + dnsProvider = "cloudflare"; + credentialsFile = config.sops.secrets.internetbs.path; + }; + certs = { "internal.hoyer.world" = { extraDomainNames = [ "openwebui.hoyer.world" @@ -18,4 +23,5 @@ ]; }; }; + }; } diff --git a/systems/x86_64-linux/sgx/default.nix b/systems/x86_64-linux/sgx/default.nix index e4ecaa8..0a95eef 100644 --- a/systems/x86_64-linux/sgx/default.nix +++ b/systems/x86_64-linux/sgx/default.nix @@ -12,6 +12,8 @@ ./wyoming.nix ]; + services.tailscale.enable = true; + boot.tmp.useTmpfs = false; sops.secrets.pccs.sopsFile = ../../../.secrets/sgx/pccs.yaml; @@ -21,16 +23,7 @@ claude-code ]; - services.tailscale.enable = true; - metacfg = { - services.nginxBase.enable = true; - services.acmeBase.enable = true; - system.noSleep = { - enable = true; - disableGdmAutoSuspend = true; - ignoreLidSwitch = true; - }; emailOnFailure.enable = true; base.enable = true; gui.enable = true; @@ -65,5 +58,13 @@ allowReboot = true; }; + systemd.targets.sleep.enable = false; + systemd.targets.suspend.enable = false; + systemd.targets.hibernate.enable = false; + systemd.targets.hybrid-sleep.enable = false; + services.displayManager.gdm.autoSuspend = false; + + services.logind.settings.Login.HandleLidSwitch = "ignore"; + system.stateVersion = "23.11"; } diff --git a/systems/x86_64-linux/sgx/nginx.nix b/systems/x86_64-linux/sgx/nginx.nix index 52f1cdc..0c685c0 100644 --- a/systems/x86_64-linux/sgx/nginx.nix +++ b/systems/x86_64-linux/sgx/nginx.nix @@ -3,7 +3,22 @@ ... }: { - services.nginx.virtualHosts = { + users.users.nginx.extraGroups = [ "acme" ]; + services.nginx = { + enable = true; + clientMaxBodySize = "1000M"; + appendHttpConfig = '' + log_format vcombined '$host:$server_port ' + '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + access_log /var/log/nginx/access.log vcombined; + ''; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + virtualHosts = { "openwebui.hoyer.world" = { enableACME = false; useACMEHost = "internal.hoyer.world"; @@ -33,4 +48,5 @@ }; }; }; + }; } diff --git a/systems/x86_64-linux/t15/default.nix b/systems/x86_64-linux/t15/default.nix index 9e39ddc..cce5666 100644 --- a/systems/x86_64-linux/t15/default.nix +++ b/systems/x86_64-linux/t15/default.nix @@ -2,8 +2,6 @@ { imports = [ ./hardware-configuration.nix ]; - services.resolved.enable = true; - metacfg = { base.enable = true; gui.enable = true; @@ -29,6 +27,9 @@ system.stateVersion = "23.11"; + services.resolved.enable = true; + #services.resolved.dnssec = "allow-downgrade"; + sops.age.sshKeyPaths = [ "/persist/ssh/ssh_host_ed25519_key" ]; sops.secrets.backup-s3.sopsFile = ../../../.secrets/t15/backup-s3.yaml; sops.secrets.backup-pw.sopsFile = ../../../.secrets/t15/backup-s3.yaml; diff --git a/systems/x86_64-linux/x1/default.nix b/systems/x86_64-linux/x1/default.nix index 84fbaea..96122d9 100644 --- a/systems/x86_64-linux/x1/default.nix +++ b/systems/x86_64-linux/x1/default.nix @@ -20,6 +20,8 @@ with lib.metacfg; programs.ccache.enable = true; nix.settings.extra-sandbox-paths = [ config.programs.ccache.cacheDir ]; + services.tailscale.enable = true; + services.cratedocs-mcp.enable = true; sops.age.sshKeyPaths = [ "/var/lib/secrets/ssh_host_ed25519_key" ]; @@ -43,11 +45,13 @@ with lib.metacfg; ]; }; - services.tailscale.enable = true; - services.resolved.enable = true; + hardware.bluetooth.input.General.ClassicBondedOnly = false; + services.udev.extraRules = '' + KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="342d", ATTRS{idProduct}=="e4c5", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl" + KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="342d", ATTRS{idProduct}=="e489", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl" + ''; metacfg = { - hardware.wooting.enable = true; base.enable = true; gui.enable = true; nix-ld.enable = true; @@ -73,19 +77,17 @@ with lib.metacfg; "dialout" "tss" ]; - system.kernelTweaks.enable = true; - }; - - system.autoUpgrade = { - enable = true; - operation = "boot"; - allowReboot = false; }; nixpkgs.config.permittedInsecurePackages = [ "electron-27.3.11" ]; + # increase freezing timeout + boot.kernel.sysctl = { + "power.pm_freeze_timeout" = 30000; + }; + environment.systemPackages = with pkgs; [ attic-client azure-cli @@ -110,12 +112,26 @@ with lib.metacfg; vscode ]; + zramSwap.enable = true; + services.ratbagd.enable = true; + services.resolved.enable = true; + #services.resolved.dnssec = "allow-downgrade"; + #services.resolved.extraConfig = '' + # ResolveUnicastSingleLabel=yes + #''; + virtualisation = { libvirtd.enable = true; }; + system.autoUpgrade = { + enable = true; + operation = "boot"; + allowReboot = false; + }; + services.trezord.enable = true; services.ollama = { diff --git a/systems/x86_64-linux/x1/xremap.nix b/systems/x86_64-linux/x1/xremap.nix index c28dd0a..64a45c0 100644 --- a/systems/x86_64-linux/x1/xremap.nix +++ b/systems/x86_64-linux/x1/xremap.nix @@ -1,21 +1,33 @@ +# In /etc/nixos/configuration.nix { ... }: { - metacfg.services.xremap = { - enable = true; - deviceNames = [ - "Hangsheng MonsGeek Keyboard" - "HS Galaxy100 Keyboard" + users.users.harald.extraGroups = [ "input" ]; + + # Enable the xremap service + services.xremap.enable = true; + services.xremap.userName = "harald"; # Replace with your username + services.xremap.serviceMode = "user"; # Run as user service, not system-wide + services.xremap.withGnome = true; + + # Add a specific configuration block to select your keyboard(s) by name + services.xremap.deviceNames = [ + # Use the name found in the log output: "Hangsheng MonsGeek Keyboard System Control" + "Hangsheng MonsGeek Keyboard" + "HS Galaxy100 Keyboard" + # You can usually shorten the name slightly to match the device you want + ]; + + # Define your remapping configuration using Nix's attribute set format + services.xremap.config = { + keymap = [ + { + remap = { + # Map Alt+C (LeftAlt-C) to Ctrl+C (LeftControl-C) + LeftAlt-C = "COPY"; + LeftAlt-V = "PASTE"; + LeftAlt-X = "CUT"; + }; + } ]; - config = { - keymap = [ - { - remap = { - LeftAlt-C = "COPY"; - LeftAlt-V = "PASTE"; - LeftAlt-X = "CUT"; - }; - } - ]; - }; }; }