Compare commits

...

28 Commits

Author SHA1 Message Date
450d9d6457 fix: claude.md update 2025-12-07 12:59:43 +01:00
e08bf42eaa fix: nas leds and disks 2025-12-07 12:59:27 +01:00
8e0e5c0d16 feat: add disks to monitoring 2025-12-05 21:57:58 +01:00
ada9db7942 feat: add disks to nas 2025-12-04 15:01:47 +01:00
5995612407 fix: amzebs add mysql port 2025-12-04 12:46:28 +01:00
5762916970 feat: add mcp server 2025-12-04 11:39:17 +01:00
dd456eab69 feat: update pyload 2025-12-04 11:39:08 +01:00
18a8fde66e feat: add uv for mcp 2025-12-04 11:38:22 +01:00
f97c9185c1 docs: update claude 2025-12-02 11:28:05 +01:00
8bf4b185a1 feat(nas): update to 25.11, add software, add storage plan 2025-12-02 11:27:52 +01:00
8424d771f6 feat(fw): update to 25.11 2025-12-02 08:34:57 +01:00
840f99a7e9 feat(amzebs-01): update to 25.11 2025-12-01 23:02:35 +01:00
1b27bafd41 feat(web-arm): update to 25.11
- Migrate logind.extraConfig to logind.settings.Login
- Update dovecot alert for service rename (dovecot2 → dovecot)
- Fix sa-core buildGoModule env attribute for CGO_ENABLED
2025-12-01 22:48:02 +01:00
4770d671c0 docs: add commit footer convention to CLAUDE.md 2025-12-01 22:25:08 +01:00
28a7bed3b9 feat(mail): update to 25.11 with TLS hardening
- Upgrade NixOS channel from 25.05 to 25.11
- Fix dovecot systemd service rename (dovecot2 -> dovecot)
- Convert postfix numeric settings to integers (25.11 requirement)
- Remove insecure 512-bit DH params, fix 2048-bit DH params
- Update postfix ciphers to modern ECDHE/DHE+AESGCM/CHACHA20
- Require TLS 1.2 minimum for OpenLDAP
- Remove weak ciphers (3DES, RC4, aNULL) from OpenLDAP
2025-12-01 22:24:57 +01:00
170becceb0 fix: nvim 2025-12-01 22:05:24 +01:00
6e8f530537 feat: amz add cron job 2025-12-01 16:17:45 +01:00
209bafd70f feat: test-configuration script get real errors 2025-12-01 16:17:28 +01:00
1d182437db feat: nb update to 25.11 2025-12-01 16:17:10 +01:00
6c046a549e feat: change pushover emergency on alerts 2025-12-01 13:29:37 +01:00
0a30a2ac23 fix: ai-mailer restart on secret change 2025-12-01 13:29:26 +01:00
82c15e8d26 feat: pyload config change, cyberghost change 2025-11-30 19:53:13 +01:00
f277d089bd feat: add cyberghost module 2025-11-30 19:29:33 +01:00
7ed345b8e8 feat: add pyload extraction passwords 2025-11-30 19:29:24 +01:00
bd6b15b617 changes 2025-11-29 22:42:09 +01:00
3282b7d634 fix: monitoring 2025-11-29 22:42:00 +01:00
21ed381d18 fix: pyload 2025-11-29 22:41:48 +01:00
4500f41983 feat: add ugreen nas leds 2025-11-29 13:12:18 +01:00
57 changed files with 1515 additions and 473 deletions

8
.mcp.json Normal file
View File

@@ -0,0 +1,8 @@
{
"mcpServers": {
"nixos": {
"command": "uvx",
"args": ["mcp-nixos"]
}
}
}

View File

@@ -138,6 +138,7 @@ creation_rules:
- *netboot - *netboot
- *fw - *fw
- *fw-new - *fw-new
- *nas
- *amzebs-01 - *amzebs-01
- path_regex: utils/modules/victoriametrics/[^/]+\.yaml$ - path_regex: utils/modules/victoriametrics/[^/]+\.yaml$
key_groups: key_groups:

View File

@@ -40,7 +40,7 @@ Each host in `hosts/<hostname>/` contains:
- `fleet.nix` → symlink to root `fleet.nix` (SFTP user provisioning) - `fleet.nix` → symlink to root `fleet.nix` (SFTP user provisioning)
- `utils/` → symlink to root `utils/` (shared modules) - `utils/` → symlink to root `utils/` (shared modules)
Current hosts: `fw` (firewall/router), `nb` (notebook), `web-arm`, `mail`, `amzebs-01` Current hosts: `fw` (firewall/router), `nb` (notebook), `web-arm`, `mail`, `amzebs-01`, `nas`
### Shared Components (`utils/`) ### Shared Components (`utils/`)
- `modules/` - Reusable NixOS modules (nginx, sops, borgbackup, lego, promtail, etc.) - `modules/` - Reusable NixOS modules (nginx, sops, borgbackup, lego, promtail, etc.)
@@ -80,6 +80,13 @@ utils/pkgs/<package-name>/
└── (other files like patches, lock files) └── (other files like patches, lock files)
``` ```
**IMPORTANT: When modifying a custom package** (patches, version updates, etc.), always test by building the package directly, not just running `test-configuration`. The configuration test only checks that the Nix expression evaluates, but doesn't verify the package actually builds:
```bash
# Build a custom package directly to verify it works
nix-build -E 'with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; config.allowUnfree = true; }; <package-name>'
```
## Workflow ## Workflow
**IMPORTANT: Always run `./scripts/test-configuration <hostname>` after making any changes** to verify the NixOS configuration builds successfully. This is required before committing. **IMPORTANT: Always run `./scripts/test-configuration <hostname>` after making any changes** to verify the NixOS configuration builds successfully. This is required before committing.
@@ -87,6 +94,7 @@ utils/pkgs/<package-name>/
## Conventions ## Conventions
- Nix files: two-space indentation, lower kebab-case naming - Nix files: two-space indentation, lower kebab-case naming
- Commits: Conventional Commits format (`fix:`, `feat:`, `chore:`), scope by host when relevant (`fix(mail):`) - Commits: Conventional Commits format (`fix:`, `feat:`, `chore:`), scope by host when relevant (`fix(mail):`). Do not add "Generated with Claude Code" or "Co-Authored-By: Claude" footers.
- Modules import via explicit paths, not wildcards - Modules import via explicit paths, not wildcards
- Comments explain non-obvious decisions (open ports, unusual service options) - Comments explain non-obvious decisions (open ports, unusual service options)
- **Never update `system.stateVersion`** - it should remain at the original installation version. To upgrade NixOS, update the `channel` file instead.

1
hosts/amzebs-01/channel Normal file
View File

@@ -0,0 +1 @@
https://channels.nixos.org/nixos-25.11

View File

@@ -3,10 +3,12 @@
./utils/bento.nix ./utils/bento.nix
./utils/modules/sops.nix ./utils/modules/sops.nix
./utils/modules/nginx.nix ./utils/modules/nginx.nix
./utils/modules/set-nix-channel.nix
./modules/mysql.nix ./modules/mysql.nix
./modules/web/stack.nix ./modules/web/stack.nix
./modules/laravel-storage.nix ./modules/laravel-storage.nix
./modules/laravel-scheduler.nix
./modules/blackbox-exporter.nix ./modules/blackbox-exporter.nix
./modules/postfix.nix ./modules/postfix.nix
./modules/rspamd.nix ./modules/rspamd.nix
@@ -67,7 +69,7 @@
networking.firewall = { networking.firewall = {
enable = true; enable = true;
allowedTCPPorts = [ 22 80 443 ]; allowedTCPPorts = [ 22 80 443 3306 ];
# Allow MariaDB access only from specific IP # Allow MariaDB access only from specific IP
extraCommands = '' extraCommands = ''
@@ -75,5 +77,5 @@
''; '';
}; };
system.stateVersion = "23.11"; system.stateVersion = "25.11";
} }

View File

@@ -0,0 +1,51 @@
{ config, lib, pkgs, ... }:
# Daily scheduled Laravel artisan jobs
# Runs artisan finish:reports at 01:00 for production and staging APIs
let
php = pkgs.php82;
sites = [
{
domain = "api.ebs.amz.at";
user = "api_ebs_amz_at";
}
{
domain = "api.stage.ebs.amz.at";
user = "api_stage_ebs_amz_at";
}
];
mkArtisanService = site: {
name = "artisan-finish-reports-${site.domain}";
value = {
description = "Laravel artisan finish:reports for ${site.domain}";
after = [ "network.target" "mysql.service" "phpfpm-${site.domain}.service" ];
serviceConfig = {
Type = "oneshot";
User = site.user;
Group = "nginx";
WorkingDirectory = "/var/www/${site.domain}";
ExecStart = "${php}/bin/php artisan finish:reports";
};
};
};
mkArtisanTimer = site: {
name = "artisan-finish-reports-${site.domain}";
value = {
description = "Daily timer for artisan finish:reports on ${site.domain}";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*-*-* 01:00:00";
Persistent = true;
};
};
};
in
{
systemd.services = builtins.listToAttrs (map mkArtisanService sites);
systemd.timers = builtins.listToAttrs (map mkArtisanTimer sites);
}

View File

@@ -3,15 +3,16 @@
, config , config
, ... , ...
}: }:
{ let
# Header checks file for validating email headers headerChecksFile = pkgs.writeText "header_checks" ''
environment.etc."postfix/header_checks".text = ''
# Warn about missing critical headers (but don't reject from localhost) # Warn about missing critical headers (but don't reject from localhost)
# These help identify misconfigured applications # These help identify misconfigured applications
/^$/ WARN Missing headers detected /^$/ WARN Missing headers detected
''; '';
in
{
services.postfix = { services.postfix = {
mapFiles."header_checks" = headerChecksFile;
enable = true; enable = true;
hostname = "amzebs-01.amz.at"; hostname = "amzebs-01.amz.at";
domain = "amz.at"; domain = "amz.at";
@@ -34,20 +35,20 @@
compatibility_level = "2"; compatibility_level = "2";
# Only accept mail from localhost # Only accept mail from localhost
mynetworks = "127.0.0.0/8 [::1]/128"; mynetworks = [ "127.0.0.0/8" "[::1]/128" ];
# Larger message size limits for attachments # Larger message size limits for attachments
mailbox_size_limit = "202400000"; # ~200MB mailbox_size_limit = 202400000; # ~200MB
message_size_limit = "51200000"; # ~50MB message_size_limit = 51200000; # ~50MB
# Ensure proper header handling # Ensure proper header handling
# Reject mail that's missing critical headers # Reject mail that's missing critical headers
header_checks = "regexp:/etc/postfix/header_checks"; header_checks = "regexp:/var/lib/postfix/conf/header_checks";
# Rate limiting to prevent spam-like behavior # Rate limiting to prevent spam-like behavior
# Allow reasonable sending rates for applications # Allow reasonable sending rates for applications
smtpd_client_message_rate_limit = "100"; smtpd_client_message_rate_limit = 100;
smtpd_client_recipient_rate_limit = "200"; smtpd_client_recipient_rate_limit = 200;
# Milter configuration is handled automatically by rspamd.postfix.enable # Milter configuration is handled automatically by rspamd.postfix.enable
}; };

View File

@@ -1 +1 @@
https://channels.nixos.org/nixos-25.05 https://channels.nixos.org/nixos-25.11

View File

@@ -7,8 +7,10 @@
./utils/modules/nginx.nix ./utils/modules/nginx.nix
./utils/modules/autoupgrade.nix ./utils/modules/autoupgrade.nix
./utils/modules/victoriametrics
./utils/modules/promtail ./utils/modules/promtail
./utils/modules/borgbackup.nix ./utils/modules/borgbackup.nix
./utils/modules/set-nix-channel.nix
# fw # fw
./modules/network-prefix.nix ./modules/network-prefix.nix
@@ -25,7 +27,6 @@
./modules/podman.nix ./modules/podman.nix
./modules/omada.nix ./modules/omada.nix
./modules/ddclient.nix ./modules/ddclient.nix
./utils/modules/victoriametrics
# ./modules/wol.nix # ./modules/wol.nix
@@ -103,7 +104,7 @@
time.timeZone = "Europe/Vienna"; time.timeZone = "Europe/Vienna";
services.logind.extraConfig = "RuntimeDirectorySize=2G"; services.logind.settings.Login.RuntimeDirectorySize = "2G";
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
sops.defaultSopsFile = ./secrets.yaml; sops.defaultSopsFile = ./secrets.yaml;

View File

@@ -104,6 +104,8 @@
restartTriggers = [ restartTriggers = [
"/etc/ai-mailer/config.yaml" "/etc/ai-mailer/config.yaml"
config.sops.secrets.ai-mailer-imap-password.path
config.sops.secrets.ai-mailer-openrouter-key.path
]; ];
}; };
} }

View File

@@ -19,21 +19,19 @@
}; };
# n8n service configuration # n8n service configuration
services.n8n = { services.n8n.enable = true;
enable = true;
settings = {
database.type = "postgresdb";
database.postgresdb.host = "/run/postgresql";
database.postgresdb.database = "n8n";
database.postgresdb.user = "n8n";
executions.pruneData = true;
executions.pruneDataMaxAge = 168; # 7 days
};
};
# Configure git integration via environment variables # Configure n8n via environment variables
systemd.services.n8n = { systemd.services.n8n = {
environment = lib.mkForce { environment = lib.mkForce {
# Database configuration (migrated from services.n8n.settings)
DB_TYPE = "postgresdb";
DB_POSTGRESDB_HOST = "/run/postgresql";
DB_POSTGRESDB_DATABASE = "n8n";
DB_POSTGRESDB_USER = "n8n";
EXECUTIONS_DATA_PRUNE = "true";
EXECUTIONS_DATA_MAX_AGE = "168"; # 7 days
# Other settings
N8N_ENCRYPTION_KEY = ""; # Will be set via environmentFile N8N_ENCRYPTION_KEY = ""; # Will be set via environmentFile
N8N_VERSION_NOTIFICATIONS_ENABLED = "false"; N8N_VERSION_NOTIFICATIONS_ENABLED = "false";
N8N_DIAGNOSTICS_ENABLED = "false"; N8N_DIAGNOSTICS_ENABLED = "false";

View File

@@ -1,69 +1,69 @@
ai-mailer-imap-password: ENC[AES256_GCM,data:mZrq13pJLpscWxNRAm5B/nU60w==,iv:Y6DV+5upNLg5csxqCWKPV8dHXhl+eGJNBI5tar7OV3o=,tag:enTXX8Y2ZM30UmGA6h+8rA==,type:str] ai-mailer-imap-password: ENC[AES256_GCM,data:q9eJ9Tom+X6KxQJhWQTUB61k5A==,iv:FH+IUWi2yZBBgMiL/kNW470GEVHEG3fImf0bel9og/c=,tag:RSlcpXwmNyLB8Oc/K2Epvw==,type:str]
ai-mailer-openrouter-key: ENC[AES256_GCM,data:6zeI8NlawmLppsaGY9V1tY8ExfAXNMRHDcthKpSSyaKcaGGh6Mrb2ZxB4IWNnyzziI1VPLE7MAW/rrszIi2y6bFayENZXMlJoA==,iv:bVNQGwN7c7KxuH3D70ScB9SR+82iFxAZyUPD6qCDjkE=,tag:gO0Vbb7Ac79DiGdjeNPFQw==,type:str] ai-mailer-openrouter-key: ENC[AES256_GCM,data:EvI0BuCBA1uYOderjAVcB8RSk7un7tiKmgsSe70KQcmfu3CxmQerP/2kQsRTJ0/6pWf4QqNpaes691O3nf+UG1qgG2CUcIaYRQ==,iv:OYEy0xMs+vkGa0qMtY4UP/iol5JPQ0eFVyPpPXLAmUE=,tag:5PeXZcI8TRSUOyuKs0STWg==,type:str]
borg-passphrase: ENC[AES256_GCM,data:M3dTj9TYbpB89gJSAGy0Q2mfb6OijFGN/lQsSsws4Ggy1AzLlMyP/l1YTADOS/j7enBYyoatQ2x/Bxm7inM5OK7LCO8=,iv:CMKjg7Oa9GlpeqSg1WsYfB26Fy2C2n8FOzpx3ryfn+A=,tag:OdENe09TTLOdMXbw0tzU+Q==,type:str] borg-passphrase: ENC[AES256_GCM,data:GGmf09zX5wQ8Fih1EyP1p3up9ckFjVKsktU6ZFwvuZnG/O2OyOod66qXc/IXx8GQordubZ3TgisOeMLNnSowp2qylh8=,iv:fFgw/x8Ww9cInkNlPIoE3stUfISbfk46PBj7aimuXNA=,tag:hnNYrkLgt1qJc+gN5s9L2Q==,type:str]
borg-ssh-key: ENC[AES256_GCM,data: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,iv:j9VYSEdwLNYw2vWFOvCldUQPxyoDP4LmTd6hh1gvOKs=,tag:ITNYqqAqVRIjd/tK3PBquw==,type:str] borg-ssh-key: ENC[AES256_GCM,data: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,iv:f7rBK8aNqX8dGyzjoeRX6yl20XsnLU8b4gitaw9+O+0=,tag:WvfUw1JgFBAtS3vsVIvM6Q==,type:str]
ddclient: ENC[AES256_GCM,data:FlO5vdUYS4AZPxnoebpjd5SqfDA35DPPc7+idiFFhTQ=,iv:WqoNjfQ6OPJ4GVYI3/o0LoYjZIW5kqbf2rVGQyBtxxQ=,tag:yvKAakq2+a4Wl6H1ZT/qhA==,type:str] ddclient: ENC[AES256_GCM,data:dS6TVVNb6R7EE1JVMDfSnRYCZyHHqEPvwaYpkTSj+VA=,iv:9uMo+9X7dFdVW4wuSgrqIAaQelXuA4cek2oif0GRHow=,tag:ncQq4UeUzWtjPNxEUOlqNA==,type:str]
filebot-license: ENC[AES256_GCM,data:sV2yaCnSja67UqHVFQJlUHXDvxqv2FeJzXvslinr8ucFvUsoxI07gtFjlk+R0cw2BMtlKaqNK5XKuCtCF/MdGIPHymhpZGgxF3OUfv0f9OXPhIOxuI5o5WBSbgY+WngrLz3E3gMcJ4CKhwkg4CpAgdSRcQqTbYEWCA8/MDbsA5fbOuZJvzZnWNgaq0qNLUntOUzRB3Ae2C/nMaxL0PEMw5NriuauHohvqeKcZ/XMZXgEewHhKlSFSXWRVXnqCcd/kOrYVKfD85N6XL67SAkijlyIoFDRUnb1bBiUlAnVEAVO/hoeKg2IANdcnRtuyP1BY4/fCsdOVMCQcO8SaqbkJke/fiEXu9vr52dqzeJYwrHDIs7G7aRkKmoq7wF4dhg8NPoWEh/UULbGhhArXpgJOON+9xSQm0aMbtj/7pAPoT6Na1gFK1zO0yg6mG32mkogy//hCqtdCZL47JrbvIhWzlDz6z3hW1mHDJ74uf12buvv9bAdhjwFFjSNr5QCI6fhpA//mwD8JIfdQfylwcMmH0t0fCWOyG1vGvhFCscC/MFMzuH6EyZx8nJfzPGutdbuaMmzGBD57u9G25VDpu/G91AWGsXTXFUG/CdZg+k1JsCDKpoE7CeQoGTsJidcg+WmLRfmQz+v5oG5fslOTHMWYj9sSAE9m4d4dYxscLvpUGwsyL+p9MQWqc5p18iMW7TtJFJZa3dPYswmBkumYgAxFQaCBEAjsr5llFMnt+2NBcz+u7lidh2AergETf5jSrZ4DDI+UhVrgBEF/kmsiVV3i2Yiz+DZ/CqqrKUPKSSmfVJ1lAteUPUhSZJuqJdRPA06Z9aTx7J4JFEPGg+tgB0NQCUoz6iHemeTYsHEw22g7ObGsg/kiFGzxopJtw9eoJM=,iv:tTaw+Hy0HPuOSf7Gl2S+fR/YrQtUrQo8hAhc9S1bOL8=,tag:jG8skiISKFNenuut8aCFkw==,type:str] filebot-license: ENC[AES256_GCM,data:jY7E29fFJ/h9NIgIjuX++WBhnLk6Mm4iRfMh4P0pUDdqH231gXDsTZ6pJ1rpFXdEHSuNN4LfznDTKgZ2azKid4WprDUzGkN0uJD6CfSR8gTIx5Rq0M8vkRah51LC36bop4hTMzECYQd1YA47hOBV/gfyg3RIw95coWamV9FebnQjIBgWYxE+wTvO5iRvWpiCHd6VZQfkiiR0KF1DrkYkuxlX0piGEKmIgyYCiKMFZ4nrrIe58x5lEQA9uPVjE7vmq3c3ge6tJzjVVaaNocbJhxhA18GLMqTSHfnBsOLRlA8qSQ3xX/VRzKQmaYQHIM77Ylb9ZQsvFt6EDlzQMl5NqT7OJZUW/0jwNaEXHURjeTOC3Hr1HugiDGm+uLXEraaJ6Na2AbFDn28o+3J22p9xNg6vWL0FElzKuaz5TFDzdZLZsD9HOPQm95/ZM8JymDjN4qxkkd2o9rEKY6to1MVDarj+lDxIHhf4pL23YhZsn3esNlEbFswzHQiH7nMsu9Jg6a0rPu7IYylDnH/soBjxSKmf2dhH1LLsDm8It9K/7NnXwmvncFXaqNBqm/e7JzCDBCCyVUf/BXbBc3xwLwZf5MiirZ/iYiYnRtUssveh7BV7ICigRj5Ewtr+n97+IGI+FyonkvOgM0bn8nHf79ZzJCKMuntcw3FlGd1nIkmcehkC79PlKIS95oV/wypl1OmU0CVel+D8hsMuONmF9NPHgFk/ztp4GF+XXRO4ExNotX1XrUlvLOccoHDsl1TedUOISzgAK71edxfI8y110shIe9OfsCEUAbmWMmjGVWH2fKu/IrYYQTry6pYFOjG2bIEUXMaiIP0lALbq/QNgleqMNPY8wGzFbP+/jaYzTbw9KXH4bwYQCl1hSI8THfV7lLE=,iv:4ik/aQqi/hIqH8ix3ejgUiXGY7ycw0ymdVrV+CEQe1o=,tag:7ymc4QZEezJVPlYTlU4H/g==,type:str]
gitea-mailer-password: ENC[AES256_GCM,data:We58w80t0CfxdGXe2eGjLop/Eg/46u5jNAtBvnJznTJ9CB4U9+0A+KvsimNowqrhKzCEaLsXzy9wMtEmfj79tZTR,iv:f1QE2i6NrCWs1qcmbO2j5WjuH4p8324DsPR206nK8cE=,tag:ccX/W5bsfsi0CHa4lPUuxQ==,type:str] gitea-mailer-password: ENC[AES256_GCM,data:lEv5euTCHG6pyNqrVtKK7oE8wLvk+q8ABXOzFSizQ2TVFi35lyGPzOTel/dCCC0Je5GAHE1KQQ4Y4/iHghZgb5Ft,iv:gt/mCzLbDrHFNqW+Lkd2dy9nRIBKO+rqsVuXM45zJ8k=,tag:gCxTSzY7GZ+jQP9SCsdUtw==,type:str]
gitea-runner: ENC[AES256_GCM,data:9v3IWNyf8JxX4GbXPSCKHInhnaBqjbs0CpIHtu97+TtKTCZQ/1tU6SmX3KzNLtc8LtQnOiHo5Zq8M9yI32b32/yrL9460CwRmUVfhSiFb+JoLFWpSIlsDfHjSY6ciPWyBvuf41Iy3BuvJpV/r/8YdrqkcO5ceevk3qfRhgbWI05ODS4+786OlGFXZnDJ7i2u4eilmb4S5uye5fgHMGS2yTZgNWWThz8K2W9XSwWggdvQ2g/pgKPqEndLrPMmKtmCa6wXqECArKhkvM2MTH/AHz8=,iv:TMEL9bD/Uir8B2k+ASyWPaDwlCi1CvN3hCkvTzh+eNE=,tag:6pjHIYTUEVffAVR9yBXufQ==,type:str] gitea-runner: ENC[AES256_GCM,data:HLjSETmu2C2ROf6kqUuIzQl/t4Fe5EOVkMqdTeLNnb6AJ95l6M/WUk//dnPMrWVvEq7rV07awUiyvyJcYQzMgPNddCrfcn2Xr0dYK4XFenz/sdhknVex9uS/RhK8fOqdYJ6djpynikMKddZMQr9AOVfpF5mea//87+Az9rOrlzLdgNtf5HyBEAFKaOFbkZboAsP+jlxyyYurGHPr8LxxikewDVxnpB+XzMc6RAnesrZPOTDQlkMiPZ2t2o0klhD/4VomgiHEklULxCCmIAHaqDo=,iv:1FwTespqVTnKFbyf9Unbbod08D36MKsVbDhIBNGBkHg=,tag:rgVvyxUCwzYB2CqWm2fwgg==,type:str]
gitea-runner-token: ENC[AES256_GCM,data:tC9YVqv2lBvPdem2j++HvNtqYYaFLWgy+nNgrjNvrubbenRjcB77EXT2g2k6CvI=,iv:8bhDPdsF72QAiacS3MuZBTv75mMWtxTAxTiENFxHo9M=,tag:meY+r5AMpdotR06l9IMIBA==,type:str] gitea-runner-token: ENC[AES256_GCM,data:pzJp7j1Ktz+27oU+qtESk7D32w7+BSEUkPSX4xuFml0i10z12Gzu0QHXL9s3734=,iv:U77b5515H1URfz5BCdzuY03zVkhSRsL9d+HdHUJFx9U=,tag:QvooaT4TS/X5R5KGdaVpVQ==,type:str]
home-assistant-ldap: ENC[AES256_GCM,data:X5rz/mJG9A9wfaHkZy7cDoNRD9eR3mRX5+ROCdWJrA8ka81+gdofn9oSha2IjwdPoxikAAhYR73n5hZIEdZKbQ==,iv:Ol31hI+XKRjbZVS4GPiMyzdKvJVe9aQDjnWYQl7uJrw=,tag:IMt5ZCZnDdBz9kcG8Khi7A==,type:str] home-assistant-ldap: ENC[AES256_GCM,data:4kofJzPbiLXILxjuAZWiTb9hu2Gver/IHBCXDnrmrKuCSII6SJ9FrSi67nl7SHdoA6xe22GSMfmPrKzy5sGiow==,iv:F8mIHhWHpaI6kzRV9du6uW/Fj07PbEIU1goSDmeSD5E=,tag:6NIC6sN8OclinribZhrLLw==,type:str]
home-assistant-secrets.yaml: ENC[AES256_GCM,data:AQ8gs3glsTWw/UyiBu2eNzP7dFy/SHPP7T0e1gyevx5JQTYbKwbOMjTqv55hM50Qoiy1RKoYyyvTkc9B9BcRpX82S+joEZx8K3eV7yrXpVAk4/JbFk++mDT8Ty38sb+/dszi+D9Tv0p7m6HfrtZCup7ojCOaF6TwMIdYjG9MJ7IyT+4m0lTZv2/1Sr24gaIYRyZ+vrlJ8mSpe1ZWWtKeGZys1aE6OwzR0TrCqg0wEf0m9VUNeVr9uZYZseWQtE/RiDOpN112vv5c1A==,iv:sJjWPZUFvFxAgTmKsOclW1x3tvef9SfTUrDvqgYh/hE=,tag:1prKFB8iNgoBhr4C7gFAwA==,type:str] home-assistant-secrets.yaml: ENC[AES256_GCM,data:rns9heAmVMxB6WWlGMXvF/ianFUnja3FObiLTEKJmodePNsJ8ah3OhuCAX5jON+/7NZ+3JN/hIJjXsORC5WYhr01DvO9meykf0aMpbmAnYI+cmPEPvcunF4NNInl96rpcI519nMiHDSh5J7pD74CxHZcXSV4c9ZR5UBymchrwmHyZMF6dVrD9Jbr9yph1r7iq6S5wlI2ZImWRjaoGDZ1x+ZU8XnsUmYcP4pa1Yt8JBxSnyUw5gxgBkVCh4eSZBsUCt0cd9P0i7qWVg==,iv:YXQsawXZsQb9ZUt1/lkpfTa4tfKIQrLkkyShFtBRaIQ=,tag:/vSnipGiMntdMqHLePSEQw==,type:str]
piped-db-password: ENC[AES256_GCM,data:rF9jfxZtfyEPG1W2k6SgcS58Z7Z/wEhZtPnjpmB/ngbYVynUXuYmL5bOVMc=,iv:mF6tCzyMEj4ZOIuODi9v+Z8aUICfWxZYRzdD5lVbx04=,tag:FDlMlXvhvfmXa3t6jMqX2A==,type:str] piped-db-password: ENC[AES256_GCM,data:5atQccdHYDEf638bpiON9VO14jqNDtzZ8nnXVW0/cqtWkZJc8RYn9N7QhAw=,iv:Gwyf1R+mpmX+TFuoYLPHjXwSDwzJhSEpnj5ZsJgmrtk=,tag:zm4zNkzbqbCyTN6o3lQQfg==,type:str]
pushover-api-token: ENC[AES256_GCM,data:nni7mP/oQ3qvyBfbQF141/FkOZispnon/iaw9LU2,iv:FXpPAPv0hms0dfusil2Plk2BJYslqv0OwmJltkUUDY0=,tag:GYMw2k6Mz93cBoWGkZOVHw==,type:str] pushover-api-token: ENC[AES256_GCM,data:cMBDdySEBQ7vS7FUC2DsCcSvEMpapWvMFmnuCsY6,iv:SVDrrDm2pcAfwUVAC5j47YwF4s/FWNARlZdIZ1Wgwgw=,tag:w7ZeNMPXWc9j+zVaSxq1cQ==,type:str]
pushover-user-key: ENC[AES256_GCM,data:q/Cr+Y104tZADBgoTSnocaiRaNPox2Z4zMgQwpYE,iv:MmoX3zCs60Uwc2Sijv/weny6bBVVUFTpphXzWtMo4ww=,tag:bG2BdC+CQXa9slLEcFQ6hw==,type:str] pushover-user-key: ENC[AES256_GCM,data:fjoA2YQxmeWEbSKWWE5iyi+CUh1vtW9usVCm5EGk,iv:p4YwYIhpgn/bY9t61//CDrDmZrsj9B/naZit62lCpwo=,tag:pqEw3pDlX7i87tE0Nsy0/Q==,type:str]
wrwks_vpn_key: ENC[AES256_GCM,data:oiRzOI1qXesCVG9X4c+sNVpCdfa9rTahJVQaLsi7+bsfc2Kc50tioBJ4YJrggGYI2KSjBe7Q4Sy1zhUKvuTmbA==,iv:KVa3tJJ0NeR4MM+LcL0IwysH/NvH8HvlbVoLOUdkp3w=,tag:lMHRTA9wsMbpfAJdJaBc+g==,type:str] wrwks_vpn_key: ENC[AES256_GCM,data:VEHqnr/bDtmyLzs0wnmZ0jCWS0BGJWu6Wjq0ZHJuEz8PH3j/E54S9NUe6WRIo+BJCsh1PlRqw/PD9xSqlW5uPg==,iv:OMP0s8Lc2CmFgwRuwB3UWJVuQFqvpy+BiyhnIKbVIb8=,tag:x1LvSf6i8khd8jKgv/284g==,type:str]
wg_cloonar_key: ENC[AES256_GCM,data:tg+dVsUfK/XodSs0rcUGjg+J/uNmhdWcTFImFKsPDprkCy6JldVF4opsYb0=,iv:kWXpVp3xn80S2JaRnbW/qp75tTI47Mvrn5fHYkjuNEE=,tag:8748Qu8hG4/dmdFC1pvlYA==,type:str] wg_cloonar_key: ENC[AES256_GCM,data:1OfHD8yX+pgCXqqxn7cddnnCA9HBjGra4eht7uLxdcbdG9vDvxUoE1x6aWg=,iv:/NBEbmA3wP/zwrqCeBKDzaoSMqz3f4ZeMlWbu81R5Pg=,tag:Apt8x/j0qiJAKR4UEVSkrA==,type:str]
wg_epicenter_works_key: ENC[AES256_GCM,data:izKZBqbLUrziALVK88LOkDDU2oVfClpCc7t8kJmQHaq+gKmUJzep1Eqic4M=,iv:KBQcHarvWLONLgyEm7O/4uiShNYAKWFx0Z41G1zMyXk=,tag:Mg4QgntbapqdDOYagoXjTw==,type:str] wg_epicenter_works_key: ENC[AES256_GCM,data:CTZkVGEVRlCdt6W0BGPmX0SZbuBBH5IIlUsi44SGXi7gdmrZNwv2zDv6zjA=,iv:4ZDDKqR6pBq8cjX763tBxOvWFaS2IiGaBxJu6L2JYig=,tag:H8p63BvXSx1SKPFw5gnptw==,type:str]
wg_epicenter_works_psk: ENC[AES256_GCM,data:kVXJk/lPHV8RM72BSmursA2HDLc2DsSzutDBy/LIY9AXwa8hqFUdY1/h6aE=,iv:9fwIGtOefdAUFpvARLhhmycbwd5wf0SeynLZtFhNm+A=,tag:vuHXT7HPnZqNI0FQxDAf3A==,type:str] wg_epicenter_works_psk: ENC[AES256_GCM,data:K0SDlDWfUk9vIGP5U1j8p6TJ9GsydJTuKPb4kMgde1CILOia0S9/+4AkMWY=,iv:ITwLoWZXR6NxRFF3eBvOogiWHLmXnf7S1e2FW0ofr/M=,tag:2OVi3OBFYT0nlCx8gf2AdA==,type:str]
wg_ghetto_at_key: ENC[AES256_GCM,data:KKYAWcW0xcBLvZedJdQlifE0lSkachW2H5dwEQmpsDF9ViBayYOkkgL36jc=,iv:6NgbeaHoptvsLTA81IyF9THPvu0BwFrTOzhhRI4/PRo=,tag:BvjfBVgxdfG1f13jgghC3w==,type:str] wg_ghetto_at_key: ENC[AES256_GCM,data:+bonpVjV1hxwaqtR7ywshmoDxCnFPD11q0OiNLzxUJIaYrDeS1srpyo6rlE=,iv:Djn16kuXTWqJZy/AT77GpH8RcNtUMZ6zcIdKIMHv+PM=,tag:LP2JCaPKpzeOKvBc2bMr4w==,type:str]
matrix-shared-secret: ENC[AES256_GCM,data:FK0PAhskPL7fjwTrjHTjnr/lpIS4bxETRMrtM0SeVEcw6CRtLCAiSgXvZqdn58CoFZvpd9iRvpFAe4FZdA==,iv:I793hCj1FkluSy/EYISde5h+b3Tsm7FJL9SIXjBxLkM=,tag:2J1vAEjuUIIG74gh/916ng==,type:str] matrix-shared-secret: ENC[AES256_GCM,data:nVSHwPa8xYUaDCxL+5neFtzc11DDNzJtoDCSHYXZ+bZXVAAbp6/Pjx6UkTdAA8B2GOM09nFAsBuLnQfJ3w==,iv:WU3hnRlWVwx7Qin3ejw7V4VhAmYLf6oXzVk6xQgZPgA=,tag:O2hJ2q8XDxYF+rHPNgATgA==,type:str]
phpldapadmin: ENC[AES256_GCM,data:IpZCNla1mejWKF5o1bomeh92Pji6HBc7OJSoVS7vZFn/GVreJmLzFceML1cMGHCT3veFdI+epJ/R4dtY/R4Efyb3zkpNnEeLnwOBnOq3KC9oYB7btyTQAs+zIwZXo9Kot2rVe84OtwkccdwaeA51/LZqvM4MnCTP76SeBhqk7Z4bHahbqTznJ7hkcBg3608RJmI7n3TcXJhrwDO7AiaQrm18LKMLhmpycyljyD2lvlF6FnB9kdXI2DRt/uYHHO02/XlgFt9e3fDT7mj8N9Cu5YO1Y3rjq0+oc0W2VLsYS5eNS786p2JM1JgNLae9WmulVRpT5enwg39JzL/S9bWqVvkuzLyliMNA,iv:vjtMwMRu9xODXJmiVnq7/Zj0kD8m7p6wJxjfpLTWRFc=,tag:RED3fQqc2jRMqYDIU9fJ8w==,type:str] phpldapadmin: ENC[AES256_GCM,data:94jCcgGJ89Er5ENLqhFZ1qY44Qp709SuUhBUuED6v/a7mPPjrJGDmi0Gm3r1Hb4CDPGkWf+x4NStY7LSQ2bHEzjyMPMS23wvSLTmC5b2TVca1UI8vZRTD1R7OvdWo8d1oNweSpYEnAXGv3USYF0NZo8DrPLM5G8lG5Tk/rKS/mxU5ZRhPyA60rbmIiy3Mk4yNcs1tvTEckxU/zMVl7zUPAsOOlmYGuwJrHmmh9p7YIWHGIgZNiLs3U0BvSKzN7WktmlwqjfWpeLn4dusqgov4SSQ2otAkxLHIH8mGhyotd1wgXJDZc6tilMe+WPHQDz9db7FT0VdeKggQ94FD+8rP0OsIjR4AdjZ,iv:C8X10wtA9jPgS41pxasaZJTO/XFcRymOyTDZCWJlhmg=,tag:xkMJsGubny+Di+GucAqypQ==,type:str]
palworld: ENC[AES256_GCM,data:J+HADBl7JmGxWiDf5HjYHt+mFrimm8yNxFG1AGol+R7FFimwqJYfq4ObxUUPDdaJacqwjalQVDylz/NDuxQrFtkeeQOZ0gExpKkkgPVFQjnKPFIBc8yafC1F4SNCegDpMQOdDSaQLWWS2Q0UNMl0W8+Zj1ofuKAsZrpIbojeSWKAfFrdu1vGV+uJm7ftf23BK/moKBl+AUQhDEGVJiynXUAlA2i1YxqBoxeweKu1f6hMJc6nu4iXEDPpHF9cHPPPPkfIJypwbWR3aIqSJAvfhKy3QyJBeHdtuMKrAxZxC/N6T0H/SijEIvkUSt5JzAU6V5gTPRWMjs4BdaMI+EcdYMQzTc++pNjYwvDG0hOl5Fbny8klHDXTTMuWzMoCgI4Rg8QbvlhATd3ueCc4e8JtIC+4ANL93UHh7uBhc/Ya0QbRVCGL17Vt/mZCuuelwSAh3dkm58aafIvQrRJz4sA4FVNeoC5JGPUzL3e82AbIrZFELvWRQqmcnk9ehqHcLCICXsIukAYU5dlO9oogobsLXR/Dft2uVJeq9NLpu2H5ZGUNg0EegxwchS4xhNjCWQga5DBDmhpvrJYSeaH/py+5+er+s+tPzZcm6qA8AgpduqJVDJcbNDE5maFanncU0p2ELphVTaIkcp9ED9qeLL4Agi9K+oMndtPZzqzAlPluZFr/5Ni+t2rcryvNiXpXnmDT+ChxXAO9Qv0dnF35VQ==,iv:cYXRFxXGjXKLbmokEn8MhkoyT52wI/mpW/FgpLjyu34=,tag:O53Q8pnAtv/2EC2ubuXgfw==,type:str] palworld: ENC[AES256_GCM,data:iR9nceVotLKrFHnPIVskCYVLev9OzGLLlmfCGQq5hqB1HveXjhjkfm/NMmqnSi9o776+Ezy7l3kkS0R+0cFJ2B9kaWGsdJtdYDwQevmf6Nq5eaBYmvu8kTnaatqZ5e/1BQzcF3to6MA061XL54YGqsAV5FpnDVLhyyzIvaR3gMvMqJ748NL7K+hbBqMFuWcSH3hKXwxtDK7SLtcgx93W5ZgXkZMMumtlH9hSSlZL4yxuQDAQUwHrEBL+rdphA0m27dyS87DA3Av5ZL1MZ+Vlm4uAHM68T+rtVYXTakNImDTc0WrhIP8FZD/UKTAhVYAbA9oz6cbeC574vchuEY1z19SY9+2HshZZBOiPDMqdvrqyszMQCo5I9dUzAJCemQQTlYG8ekREQ0wxARnBYi3iy5PbmgDQWdM3+ff4yhMmGiHtiMQLHzrquKy8nvS9lDp9uT7njkaI0QAt3eNWa2DAQRqXQAtmuRVob5+GS2Nt6XMTWRkbeEb1phwbTqZD5mH4p2TiyMKCn6KOXsgQTxqGr35Izbe+bfptCmUeyscTKq01IZg77w/dvg3AX4iHAcMNgJ9LIHDLibGHQzu9fGN6alpeyy788GDwRY4glYyKxPhCasKkBSj/uhcDAtdg+c63vDTBhqRjNr6+v1NeRW6lVBzrgq+f9QvO5RVKrvdsVHnTA3CMGQzUPAaluNZMpzV5KqxqIrpAAPXnN0ktig==,iv:kkcm/alLHwC84IKK//OJpa36ec9ddOARTIM+KJlOHHs=,tag:jV1DjfNzRgNaCGgJTKIy5g==,type:str]
ark: ENC[AES256_GCM,data:MIoUBI3iImXLozkgcVszqfjlofNAWp7BRnLmQvM+gXAyJ/OUaQiQETeMH48FVL9Y2AzPcILQ1ocIeXUClXU1XxYo5ItqRMQY8XWTApF4MKedyen+eVPtB7L14zlCZR3HwhA8Mb8AWhg+vhpDyD3ANTP4bETVjcxFlxqQ5n3YaCL6jGWSWAkwiuzQU2uQsFQzrJia8ybx/wrKGUhX4+SKDbupSzvkRzLkOsa7PT6chL8/REO7WYlFbwj0Wex6BdtoVAYb7NMTCQR8VqrU,iv:h/qId8ZHbpT0Bkql871Sv9yZfw5DKbsp94vAh0HMxQ0=,tag:sZYfTi9kiFATGT7trNreDQ==,type:str] ark: ENC[AES256_GCM,data:TRTwxqkeUGbtgrWuj1YEFr73+nxCXmt/fR5vVnYR+k4FpNBB2FoY/gXl0kqeFKPDcajwn8nYBs8YE9vmYtAX/Qs4g5OyU9qC/pkmSV7/gbGfqLLqcbIlbWrZzeM8gRW0fp6h1TMPsGO8/iYdF4bmInfuZW+fKr0i7ZRgrtOpPiRCOI/ztPGkFaduuwGIy+yVoS64b9r7ZLRnOZT7ghVv80GKorJuuOQIipNAJMzEqtSA2IqaxWeb13v8wdQoKuMNcD6dCYVJnvgwf4R+,iv:+F9+yJUZBzPSSIt4uLHxjjXAjzRojLxKAyrd8grMXkk=,tag:VrIr4FFbIGTq9RBJMz8/Ig==,type:str]
firefox-sync: ENC[AES256_GCM,data:8zD3QcZmex4F2EBSyUc7Lw0teWpz0o/R6mK8utuvgVjWKW6JkdsbtI5+IvH5TQtJHmwwq1Mh2Q2VZk5HsVA38dST4XM/XVlA15tIveQCprTaQrOxw8jHqJ/DLxw2xGz+2OlFagof46eu9lcsrYg4uLdfmLVcuhjhTHM5z72GxFiE/QIcoZLllhdTb7/1dqs=,iv:KBHX4iSlMASmEKjJNdH7gX6QBTvMmHUtubNErK2PsVY=,tag:G1CJkdLe3a9mnSG4PFByGA==,type:str] firefox-sync: ENC[AES256_GCM,data:guNgEVi9n8uJuLkkX2Z3tMY/NVqzQ2tdIutZAqleah9qBri0/3dzVHF2xvztLeAgm/59tN7TtAlAH2SMK6gcfAZDasAWOJ/rGEASxLi6VRjqCe25glDMp2YrA0/mcqZVYMCg+QZ5OPA56b55WDqPHPoBJkPDuTm9axwm6AOxdNi5BkDzMw12fVBxlJL/Rm8=,iv:yD+MkZK5vvZ85vYGd9X2Dv6KkSvMUsMGLrwlJ1pRqlk=,tag:YA379QupHh7aJZKcQxB7bA==,type:str]
knot-tsig-key: ENC[AES256_GCM,data:MXA1qLfUJ9O3Fvd0WGi2wInuVjgYkfH2cVRLogLQ+B0TRG1YbnVqVEp7P+s=,iv:zxBCr/hSHH1zqxBGzQ53nArcUfBcq/LIpqgWllTi6ks=,tag:6G5eL4i9Cjrw0CF5JJotCg==,type:str] knot-tsig-key: ENC[AES256_GCM,data:CBFaRKPr+HRVM01fA9/OLWeD1O33axQKEKJuqDRfcGmuDeP3oXf+ccEJhQE=,iv:2O5y24YenpiMc9txPx8kz8x0aO37LpLjIcwlNywPEak=,tag:J4bVZ7RNSR9fiOBQ2HKpnQ==,type:str]
mopidy-spotify: ENC[AES256_GCM,data:ow7WgSwgbGqxRFWkDXsgKBvxMx2uF7lH2DHUDXhGVz1kLORGqp7wQl04SiYxnYpWJoFl+Suxia7hZ0riQF0bgMDi3SqQQrDimQZI6U2m04QAGJyBoaxratnwyFNe246GWiYgXxvtjPi10N6skPQBym6sJquBF/gE,iv:70hs89+g2dr/dmI3YPyIL+3B49ol7ltByk3kKCz3ASg=,tag:jy9Sn9dAYfs6wk3KoX3ylg==,type:str] mopidy-spotify: ENC[AES256_GCM,data:irBeIh2FieNkdf6Hls/Oj+qYxj1U7R7/Ffq6dx+JCS0PdOiFWIHXtccY+PXPKP7RhhaQOgZtIcgPyqTiML52P0c8AwN6UHMl7kgUcKnk60AI0IUZNWorCBZluHhEpf2e2OISlFzDGjSHk+zAzh2eDS1lJ9lCRYEC,iv:r6aZmlVHdRsA9DxkelcIVVpwwm32jaOgP429h61NL/U=,tag:FvPIr0HX/V7+G9kal4nO8w==,type:str]
lms-spotify: ENC[AES256_GCM,data:RcoVtWO6kt1Lx6sK15tLsr8GOL1j304UmLE4CCzNMTLFyN29dKvpC/Sn5CsxWf9TCclYlNJsuz9kZ/ZiL2qVCGYWyoH9vIs+o1iINkYJzhZT8wwsNpN7Y41OuB2tm+mCjm3hKuDnciI+bX9W8TC/szJ+W9pbgYdK1ggIPOyl99NiLKeDNz4ELpc8eCqtBjung1hPbM7OkJcDMwC9txEBIXL+dDjuoi5MMZoQp8NnbM6BaKgalMzrxUpkUBf/1+G05C1NWzIBXA==,iv:sIgS6I0Vto1v564wbJU6HFoSo4/aoRxmJjfJ7w/ZZQ8=,tag:BFGn+eGm6Gc9cEua+auUjw==,type:str] lms-spotify: ENC[AES256_GCM,data:E53aUSNxE30SSrG6Y6SWKVzmsv0lu8aZvjk1RBgSj3q4m65dPLwGM9HcagN3BPoVTc0tKJaccrjoL2k5FOMnwcTXIz3qgiZGbnB6hVCoOhMrrkoFRN2JzSIA5WxKOT8VuMoC4/a6WaWbY8SWAdhgRQb9uq1hUxdkMCoNRLNJnPqR/0w07lCDVHvkj8XuBV4rGl93VVT3rCzjVTL+Vigv38WZ2il2aANkCz3joNeN8Uod3K/HA5uXLw3cLFmD7eI7LBDSTHpMEg==,iv:iRKrij3TRaufB5BXy7Xhiu3asClZ6hpkbMV14aod7jk=,tag:hpUwP/OHygqfgI6j6q2sKQ==,type:str]
sops: sops:
age: age:
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7 - recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkT0thSi85UWJlREkyNVVC YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrTDFvM2l3Tm5lU0paWXpF
NWtzSU9DWXRqbWhYSW82aUtyb29LZGswV1ZVCjFPZXIvcDBIVXFzOSt2UUxiM2kv cjVBSFhENW5mNG9DSFM1NXh3UHdaKzlKMGlZCnRmNFBFVWY4N0FqLzF1bUMyUDdL
MXFoY2NWSE9lVnRzYlNsYndLYVZnOFkKLS0tIFlLMExqYkt5ZlpUK1lJOCtQZW9t U091VENiVFhYeEJ5K0xodXlHVkhHKzgKLS0tIGxta3A2TjJiMUtiR2RzcU02Rys5
MVZSV0JwUW9GWVIzekV3a1RibGF1bEkKBoNNseldjYfL8QPSFnOa3f5IlQ+0Tntm U1c0SjRKK2UwbTVIQUMrT1pOOVFmOVkKY3UyGNIPZJLE8GG124y0pLgqGub9SMCq
kOTf0oH380WM5856TQchND26wX0rHAMllx40yWBzjlkJMX5wvXBYWg== plK5H+kASOB1X6pK+3PBFuDYT1AbsRxXvWgAEMvVI7eBcxQlSrrB4Q==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz - recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTYW9NeGo1a09oVjVBTGFr YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVaXBqMGl1UytNL3BkZEhQ
V3JWc3NHRDcvYXVDMFVMNUl1Q0h5ZXJ5dzFnClZGbGhyME1Id0RETEdtMkN3RWZt S3RFL3lZRVZKTGVRTGFMNlFlWFRCNDNvRTM4CnpWZWovSDZaclQvN2Vwa0dWZGgz
VVFVOEphZEpJQ29YRjd2WGltWkdmYUkKLS0tIFBOOVdhM2RMSTdWM1lKT2hGTHYz Q1ZLM0sveXBxOVpvNHkycWJWWXdmVE0KLS0tIHl2bFk3RE03N01IdDJPWk5HT1Np
c1FGaE1KZkhxbmRmVEFCRGUzR3gyZXcKMvtkOvb35ocdQHfq/cN0wuu59d8e6GM2 Qm82Sit3Q0haaDdnbzFjendMUm04Wk0KYp09dxXjzvC4IlH6Ilip8YjTz0mFeu/0
9AqDummh/D0NQ69X2Hoojgmt9tl5TKnhYwvfWU6gwAd5RKYIAcRghQ== 5IDMYjT1BuW5YiKgIJVd+UgOd6ysZLFFwk+Us2AcV7z110xk/askqQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch - recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkRTJvZkNoYjVMaCtkZEN2 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Wkd0YnBQRnExeVdUTGFu
OEpMRUZ3TE9SSHplZjRMaFhESXJNZjk4MUM4CmVBVWNrakxvdEtGVFZmMiszVjBz N3o3MnF2aTY2NlBmdDJYT01zRytWZ2w1dFg0ClAzcnJ0NFYrVWlBM2JQU1B0SEJi
aTNJK1pHNXVpcnZNbjg2QWYwcXFZQlUKLS0tIDdIK01VVDRSZlB4ZFB4WHpaZG1N MGE5aVh6KzNmaEoxaHFOTW90K0VmMGsKLS0tIDNkOGZyVmMzME80TlBWMzI5UVR2
Z1ZGVUROeXZGMm0xclF4N1A0S3BIeTgKVYwnDmneB4dljyvmxgXxTu/ftk0b1Z9I djB3Y2FIRDFKWlEwTnRBUnRIT3M2OXcK+SIt/7DRdQi6H1AZooJN2Pt2g1EwVTZe
ENyOiKIgN0YPOq1a1GOr9OCUOoNM5onTB0fkrUFzkChSB/3xDpUHlA== Q14cEt0sLyVYzLJugfz2JWRHDZX6wPueYcTSEs7w3wAPVwvJWju8bg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df - recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKRVlHQ1h3eDlKd3FuN2Q4 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzQXhNSFBnNUtMdkpwR0th
Q2RuUVpKeHJqYldWRlNlWDFPQ2U2TzQ4MG5ZClFLVlBwYy9xU1lzbGMvNE9kdVI3 M1NmOVorcUdlZTFDM3dVRHZlYWpJcDZiakVnCit6eTFOeW92SzhPYzJxR0VTem9r
VHpxeEtwOHgvS3NhZWdKaVp3RHh6aUUKLS0tIHUrOWRibGhJckFycVJwalJsSFA3 MSs4cWxRbzVBQmlWaHIwMjB5RUlJMXcKLS0tIHNSVTloOEVVVndDWkVrWmQrYXlD
UXEzSHMxVVc4b0FOU0NycElaV3VMOWMKpq9KFfdu0kEWhlT7Of5aOrAhM8FmOuzo NTd1WGFJWHVLTnFNT3hYbDdtSnMzTTAKBmJOayZLbjmBejwVzVtUSYPki+qPkYwG
1RRxsDAtpfjw3dYbvS8ISrFP+nEr84qi4Hdrr1Y35ye5fR30gvQycw== xdO3L7n0Z8Cv/kVYZpkuG5GqOUL+nCJuYDjF0g4PaLb6WWd0W8ZGFA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-11-25T19:16:56Z" lastmodified: "2025-12-01T11:01:54Z"
mac: ENC[AES256_GCM,data:kaeDCxG+z6NqzcaZTBsEkPhlXts47iEhQp7MGsRrYMP9X4vYsLBkzKgeHugcN2VnaZI+Uw27Tn1Fgt4Eaqty7j07iZKa33B/SOnFu3fKacP3Ae3vGAgzda/5HyOPsPxEgiHUEbJqTnhahh9sYckwdaMPg/PdSEAJyJN1b2WYy+0=,iv:E65Baxjw2mV6Tm59Ka4l1Q640PoS7IDLggNLmO9ij0k=,tag:+5Rx9nUEWsLIsLfEg/TcfA==,type:str] mac: ENC[AES256_GCM,data:taGX5HHZCL7Zo4taS2Jz/5WxhvpBNNKZ13ZCtS3x/P17tC1Nrk2UDcxbOZ1pPVbVvvaAHJtDb3owFvBOM4nr2Eve0M9zT4HbXh3hke7AviQ6U7CT1ru6LjY7W8lBjbQ6uCt+Ldxd1PRPPGiyKdK5GAUPKg6avFjpJbhEikh8Gww=,iv:NNs5usVJ5izYvHKnNm1IgjSt4dg0QFQ7cClJ6zh+3wM=,tag:sYYbEWIUgOWthEItdy5PFg==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0

View File

@@ -1 +1 @@
https://channels.nixos.org/nixos-25.05 https://channels.nixos.org/nixos-25.11

View File

@@ -240,11 +240,11 @@ in
sops.secrets.dovecot-ldap-password = { }; sops.secrets.dovecot-ldap-password = { };
systemd.services.dovecot2.preStart = '' systemd.services.dovecot.preStart = ''
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${ldapConfig} > /run/dovecot2/ldap.conf sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${ldapConfig} > /run/dovecot2/ldap.conf
''; '';
systemd.services.dovecot2 = { systemd.services.dovecot = {
wants = [ "acme-imap.${domain}.service" ]; wants = [ "acme-imap.${domain}.service" ];
after = [ "acme-imap.${domain}.service" ]; after = [ "acme-imap.${domain}.service" ];
}; };
@@ -257,7 +257,7 @@ in
"imap-test.${domain}" "imap-test.${domain}"
"imap-02.${domain}" "imap-02.${domain}"
]; ];
postRun = "systemctl --no-block restart dovecot2.service"; postRun = "systemctl --no-block restart dovecot.service";
}; };
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [

View File

@@ -17,10 +17,10 @@ in {
olcTLSCACertificateFile = "/var/lib/acme/ldap.${domain}/full.pem"; olcTLSCACertificateFile = "/var/lib/acme/ldap.${domain}/full.pem";
olcTLSCertificateFile = "/var/lib/acme/ldap.${domain}/cert.pem"; olcTLSCertificateFile = "/var/lib/acme/ldap.${domain}/cert.pem";
olcTLSCertificateKeyFile = "/var/lib/acme/ldap.${domain}/key.pem"; olcTLSCertificateKeyFile = "/var/lib/acme/ldap.${domain}/key.pem";
olcTLSCipherSuite = "HIGH:MEDIUM:+3DES:+RC4:+aNULL"; olcTLSCipherSuite = "HIGH:!aNULL:!MD5:!3DES:!RC4";
olcTLSCRLCheck = "none"; olcTLSCRLCheck = "none";
olcTLSVerifyClient = "never"; olcTLSVerifyClient = "never";
olcTLSProtocolMin = "3.1"; olcTLSProtocolMin = "3.3";
olcSecurity = "tls=1"; olcSecurity = "tls=1";
}; };

View File

@@ -128,16 +128,16 @@ in
compatibility_level = "2"; compatibility_level = "2";
# bigger attachement size # bigger attachement size
mailbox_size_limit = "202400000"; mailbox_size_limit = 202400000;
message_size_limit = "51200000"; message_size_limit = 51200000;
smtpd_helo_required = "yes"; smtpd_helo_required = "yes";
smtpd_delay_reject = "yes"; smtpd_delay_reject = "yes";
strict_rfc821_envelopes = "yes"; strict_rfc821_envelopes = "yes";
# send Limit # send Limit
smtpd_error_sleep_time = "1s"; smtpd_error_sleep_time = "1s";
smtpd_soft_error_limit = "10"; smtpd_soft_error_limit = 10;
smtpd_hard_error_limit = "20"; smtpd_hard_error_limit = 20;
smtpd_use_tls = "yes"; smtpd_use_tls = "yes";
smtp_tls_note_starttls_offer = "yes"; smtp_tls_note_starttls_offer = "yes";
@@ -151,14 +151,13 @@ in
smtpd_tls_key_file = "/var/lib/acme/mail.cloonar.com/key.pem"; smtpd_tls_key_file = "/var/lib/acme/mail.cloonar.com/key.pem";
smtpd_tls_CAfile = "/var/lib/acme/mail.cloonar.com/fullchain.pem"; smtpd_tls_CAfile = "/var/lib/acme/mail.cloonar.com/fullchain.pem";
smtpd_tls_dh512_param_file = config.security.dhparams.params.postfix512.path;
smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix2048.path; smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix2048.path;
smtpd_tls_session_cache_database = ''btree:''${data_directory}/smtpd_scache''; smtpd_tls_session_cache_database = ''btree:''${data_directory}/smtpd_scache'';
smtpd_tls_mandatory_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1"; smtpd_tls_mandatory_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
smtpd_tls_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1"; smtpd_tls_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
smtpd_tls_mandatory_ciphers = "medium"; smtpd_tls_mandatory_ciphers = "medium";
tls_medium_cipherlist = "AES128+EECDH:AES128+EDH"; tls_medium_cipherlist = "ECDHE+AESGCM:DHE+AESGCM:ECDHE+CHACHA20:DHE+CHACHA20";
# authentication # authentication
smtpd_sasl_auth_enable = "yes"; smtpd_sasl_auth_enable = "yes";
@@ -225,8 +224,7 @@ in
security.dhparams = { security.dhparams = {
enable = true; enable = true;
params.postfix512.bits = 512; params.postfix2048.bits = 2048;
params.postfix2048.bits = 1024;
}; };
security.acme.certs."mail.${domain}" = { security.acme.certs."mail.${domain}" = {

View File

@@ -119,7 +119,7 @@ in
# systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "redis-rspamd" ]; # systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "redis-rspamd" ];
systemd.services.dovecot2.preStart = '' systemd.services.dovecot.preStart = ''
mkdir -p /var/lib/dovecot/sieve/ mkdir -p /var/lib/dovecot/sieve/
for i in ${sieve-spam-filter}/share/sieve-rspamd-filter/*.sieve; do for i in ${sieve-spam-filter}/share/sieve-rspamd-filter/*.sieve; do
dest="/var/lib/dovecot/sieve/$(basename $i)" dest="/var/lib/dovecot/sieve/$(basename $i)"

60
hosts/nas/STORAGE.md Normal file
View File

@@ -0,0 +1,60 @@
# NAS Storage Notes
## Current Issue: XFS Metadata Overhead
The XFS filesystem on `/var/lib/multimedia` uses ~100GB more than the actual file data due to metadata overhead.
### Root Cause
The filesystem was created with advanced features enabled:
```
rmapbt=1 # Reverse mapping btree - tracks block ownership
reflink=1 # Copy-on-write support
```
These features add metadata that scales with **filesystem size**, not file count. On a 5TB filesystem with 700GB of data, this results in ~100GB (~2%) overhead.
### Diagnostic Commands
```bash
# Compare file data vs filesystem usage
du -sh /var/lib/multimedia # Actual file data
df -h /var/lib/multimedia # Filesystem reports
# Check XFS features
xfs_info /var/lib/multimedia
# Verify block allocation
xfs_db -r -c "freesp -s" /dev/mapper/vg--data-lv--multimedia
```
## Recommendation: LVM + ext4
For media storage (write-once, read-many), ext4 with minimal reserved space offers the lowest overhead:
```bash
# Create filesystem with 0% reserved blocks
mkfs.ext4 -m 0 /dev/vg/lv
# Or adjust existing ext4
tune2fs -m 0 /dev/vg/lv
```
### Why ext4 over XFS for this use case
| Consideration | ext4 | XFS (current) |
|---------------|------|---------------|
| Reserved space | 0% with `-m 0` | N/A |
| Metadata overhead | ~0.5% | ~2% (with rmapbt) |
| Shrink support | Yes | No |
| Performance for 4K stream | Identical | Identical |
A single 4K remux stream requires ~12 MB/s. Any filesystem handles this trivially.
## Migration Path
1. Backup data from XFS volumes
2. Recreate LVs with ext4 (`mkfs.ext4 -m 0`)
3. Restore data
4. Update `/etc/fstab` or NixOS `fileSystems` config

1
hosts/nas/channel Normal file
View File

@@ -0,0 +1 @@
https://channels.nixos.org/nixos-25.11

View File

@@ -9,12 +9,16 @@ in {
"${impermanence}/nixos.nix" "${impermanence}/nixos.nix"
./utils/bento.nix ./utils/bento.nix
./utils/modules/sops.nix ./utils/modules/sops.nix
./utils/modules/victoriametrics/default.nix ./utils/modules/set-nix-channel.nix
./utils/modules/victoriametrics
./utils/modules/promtail
# ./modules/cyberghost.nix
./modules/pyload.nix ./modules/pyload.nix
./modules/jellyfin.nix ./modules/jellyfin.nix
./modules/power-management.nix ./modules/power-management.nix
./modules/disk-monitoring.nix ./modules/disk-monitoring.nix
./modules/ugreen-leds.nix
./hardware-configuration.nix ./hardware-configuration.nix
]; ];
@@ -73,6 +77,12 @@ in {
]; ];
}; };
# System packages
environment.systemPackages = with pkgs; [
vim
screen
];
# Nix settings # Nix settings
nix = { nix = {
settings = { settings = {

View File

@@ -24,12 +24,14 @@
"i915.enable_fbc=1" # Frame buffer compression "i915.enable_fbc=1" # Frame buffer compression
]; ];
# RAID 1 array for data storage # RAID 1 arrays for data storage
boot.swraid = { boot.swraid = {
enable = true; enable = true;
mdadmConf = '' mdadmConf = ''
DEVICE /dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52TBSB-part1 DEVICE /dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1MAZ0E7-part1
DEVICE /dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52V9QX-part1 DEVICE /dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1M9Z0E7-part1
DEVICE /dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_8582A01SF4MJ-part1
DEVICE /dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_75V2A0H3F4MJ-part1
''; '';
}; };
@@ -84,14 +86,13 @@
# LVM volumes on RAID array # LVM volumes on RAID array
fileSystems."/var/lib/downloads" = { fileSystems."/var/lib/downloads" = {
device = "/dev/vg-data/lv-downloads"; device = "/dev/vg-data-fast/downloads";
fsType = "xfs"; fsType = "ext4";
options = [ "noatime" ];
}; };
fileSystems."/var/lib/multimedia" = { fileSystems."/var/lib/multimedia" = {
device = "/dev/vg-data/lv-multimedia"; device = "/dev/vg-data-slow/multimedia";
fsType = "xfs"; fsType = "ext4";
options = [ "noatime" ]; options = [ "noatime" ];
}; };

View File

@@ -0,0 +1,103 @@
{ config, pkgs, ... }:
let
localNetwork = "10.42.96.0/20";
in
{
# SOPS secrets for CyberGhost credentials
sops.secrets.cyberghost-auth = {
mode = "0400";
owner = "root";
};
sops.secrets.cyberghost-ca = {
mode = "0400";
owner = "root";
};
sops.secrets.cyberghost-cert = {
mode = "0400";
owner = "root";
};
sops.secrets.cyberghost-key = {
mode = "0400";
owner = "root";
};
environment.systemPackages = [ pkgs.openvpn ];
# OpenVPN client service
services.openvpn.servers.cyberghost = {
autoStart = true;
updateResolvConf = true;
config = ''
client
dev tun
proto udp
remote 87-1-hu.cg-dialup.net 443
resolv-retry infinite
nobind
persist-key
persist-tun
# Authentication
auth-user-pass ${config.sops.secrets.cyberghost-auth.path}
ca ${config.sops.secrets.cyberghost-ca.path}
cert ${config.sops.secrets.cyberghost-cert.path}
key ${config.sops.secrets.cyberghost-key.path}
# Security
data-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC
data-ciphers-fallback AES-256-CBC
auth SHA256
remote-cert-tls server
script-security 2
# Connection
ping 5
explicit-exit-notify 2
route-delay 5
# Split tunnel: Don't pull routes from server, we'll set our own
route-nopull
# Route all traffic through VPN except local network
route 0.0.0.0 128.0.0.0 vpn_gateway
route 128.0.0.0 128.0.0.0 vpn_gateway
# Keep local network route direct
route ${localNetwork} net_gateway
verb 4
'';
};
# Kill switch: Block outgoing traffic if VPN is down
networking.firewall = {
extraCommands = ''
# Allow traffic to local network
iptables -A OUTPUT -d ${localNetwork} -j ACCEPT
# Allow traffic through VPN tunnel
iptables -A OUTPUT -o tun+ -j ACCEPT
# Allow loopback
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established connections (for responses)
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow OpenVPN to establish connection (UDP 443)
iptables -A OUTPUT -p udp --dport 443 -j ACCEPT
# Drop all other outgoing internet traffic (kill switch)
iptables -A OUTPUT ! -d ${localNetwork} -j DROP
'';
extraStopCommands = ''
iptables -D OUTPUT -d ${localNetwork} -j ACCEPT 2>/dev/null || true
iptables -D OUTPUT -o tun+ -j ACCEPT 2>/dev/null || true
iptables -D OUTPUT -o lo -j ACCEPT 2>/dev/null || true
iptables -D OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
iptables -D OUTPUT -p udp --dport 443 -j ACCEPT 2>/dev/null || true
iptables -D OUTPUT ! -d ${localNetwork} -j DROP 2>/dev/null || true
'';
};
}

View File

@@ -9,6 +9,10 @@ let
disks = [ disks = [
"/dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52TBSB" "/dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52TBSB"
"/dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52V9QX" "/dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52V9QX"
"/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_8582A01SF4MJ"
"/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_75V2A0H3F4MJ"
"/dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1MAZ0E7"
"/dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1M9Z0E7"
]; ];
textfileDir = "/var/lib/prometheus-node-exporter"; textfileDir = "/var/lib/prometheus-node-exporter";
@@ -40,12 +44,16 @@ let
continue continue
fi fi
# Resolve symlink to get actual device # Resolve symlink to get actual device (needed for hdparm/smartctl)
device=$(readlink -f "$disk") device=$(readlink -f "$disk")
short_name=$(basename "$device")
# Extract serial from disk ID for labels # Extract model+serial from disk-by-id path for stable labeling
serial=$(basename "$disk" | sed 's/ata-ST18000NM000J-2TV103_//') # ata-ST18000NM000J-2TV103_ZR52TBSB ST18000NM000J-2TV103-ZR52TBSB
# nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1MAZ0E7 KIOXIA-EXCERIA_PLUS_G3_SSD-7FJKS1MAZ0E7
disk_id=$(basename "$disk")
serial=$(echo "$disk_id" | sed 's/.*_//')
model=$(echo "$disk_id" | sed 's/^[^-]*-//; s/_[^_]*$//')
short_name="$model-$serial"
# Check power state without waking disk # Check power state without waking disk
power_state=$(${pkgs.hdparm}/bin/hdparm -C "$device" 2>/dev/null | grep -oP '(standby|active/idle|active|idle)' | head -1 || echo "unknown") power_state=$(${pkgs.hdparm}/bin/hdparm -C "$device" 2>/dev/null | grep -oP '(standby|active/idle|active|idle)' | head -1 || echo "unknown")
@@ -104,7 +112,7 @@ EOF
# mdadm RAID array status (doesn't access disks) # mdadm RAID array status (doesn't access disks)
echo "" >> "$TEMP_FILE" echo "" >> "$TEMP_FILE"
echo "# HELP mdadm_array_state RAID array state (1=clean, 0=degraded/other)" >> "$TEMP_FILE" echo "# HELP mdadm_array_state RAID array state (1=clean/active/resyncing, 0=degraded/other)" >> "$TEMP_FILE"
echo "# TYPE mdadm_array_state gauge" >> "$TEMP_FILE" echo "# TYPE mdadm_array_state gauge" >> "$TEMP_FILE"
echo "# HELP mdadm_array_devices_total Total devices in RAID array" >> "$TEMP_FILE" echo "# HELP mdadm_array_devices_total Total devices in RAID array" >> "$TEMP_FILE"
echo "# TYPE mdadm_array_devices_total gauge" >> "$TEMP_FILE" echo "# TYPE mdadm_array_devices_total gauge" >> "$TEMP_FILE"
@@ -122,7 +130,7 @@ EOF
# Parse state # Parse state
state=$(echo "$mdadm_output" | grep "State :" | sed 's/.*State : //' | tr -d ' ') state=$(echo "$mdadm_output" | grep "State :" | sed 's/.*State : //' | tr -d ' ')
if [[ "$state" == "clean" ]] || [[ "$state" == "active" ]]; then if [[ "$state" == *clean* ]] || [[ "$state" == *active* ]]; then
state_value=1 state_value=1
else else
state_value=0 state_value=0
@@ -156,7 +164,6 @@ in
enable = true; enable = true;
enabledCollectors = [ enabledCollectors = [
"textfile" "textfile"
"systemd"
]; ];
extraFlags = [ extraFlags = [
"--collector.textfile.directory=${textfileDir}" "--collector.textfile.directory=${textfileDir}"

View File

@@ -15,5 +15,10 @@
ACTION=="add", KERNEL=="sd[a-z]", SUBSYSTEM=="block", \ ACTION=="add", KERNEL=="sd[a-z]", SUBSYSTEM=="block", \
ATTRS{model}=="ST18000NM000J*", \ ATTRS{model}=="ST18000NM000J*", \
RUN+="${pkgs.hdparm}/bin/hdparm -B 127 -S 180 /dev/%k" RUN+="${pkgs.hdparm}/bin/hdparm -B 127 -S 180 /dev/%k"
# Toshiba 20TB NAS drives - same settings
ACTION=="add", KERNEL=="sd[a-z]", SUBSYSTEM=="block", \
ATTRS{model}=="TOSHIBA MG10ACA2*", \
RUN+="${pkgs.hdparm}/bin/hdparm -B 127 -S 180 /dev/%k"
''; '';
} }

View File

@@ -36,6 +36,14 @@ in
path = "/var/lib/pyload/filebot-license.psm"; path = "/var/lib/pyload/filebot-license.psm";
}; };
# Extraction passwords for pyload (one password per line)
sops.secrets.pyload-extraction-passwords = {
mode = "0440";
owner = "pyload";
group = "pyload";
path = "/var/lib/pyload/extraction-passwords.txt";
};
# PyLoad user with jellyfin group membership for multimedia access # PyLoad user with jellyfin group membership for multimedia access
users.users.pyload = { users.users.pyload = {
isSystemUser = true; isSystemUser = true;
@@ -76,13 +84,26 @@ in
PYLOAD__EXTRACTARCHIVE__REPAIR = "1"; PYLOAD__EXTRACTARCHIVE__REPAIR = "1";
PYLOAD__EXTRACTARCHIVE__RECURSIVE = "1"; PYLOAD__EXTRACTARCHIVE__RECURSIVE = "1";
PYLOAD__EXTRACTARCHIVE__FULLPATH = "1"; PYLOAD__EXTRACTARCHIVE__FULLPATH = "1";
PYLOAD__EXTRACTARCHIVE__PASSWORDFILE = "/var/lib/pyload/extraction-passwords.txt";
# Enable ExternalScripts plugin for hooks # Enable ExternalScripts plugin for hooks
PYLOAD__EXTERNALSCRIPTS__ENABLED = "1"; PYLOAD__EXTERNALSCRIPTS__ENABLED = "1";
PYLOAD__EXTERNALSCRIPTS__UNLOCK = "1"; # Run hooks asynchronously PYLOAD__EXTERNALSCRIPTS__UNLOCK = "1"; # Run hooks asynchronously
# DdownloadCom plugin: don't fall back to free if premium fails
PYLOAD__DDOWNLOADCOM__FALLBACK = "0";
}; };
serviceConfig = { serviceConfig = {
# Bind-mount DNS configuration files into the sandboxed service
BindReadOnlyPaths = [
"/etc/resolv.conf"
"/etc/nsswitch.conf"
"/etc/hosts"
"/etc/ssl"
"/etc/static/ssl"
"/run/secrets" # SOPS secrets access for FileBot license
];
# Bind mount multimedia directory as writable for FileBot hook scripts # Bind mount multimedia directory as writable for FileBot hook scripts
BindPaths = [ "/var/lib/multimedia" ]; BindPaths = [ "/var/lib/multimedia" ];

View File

@@ -0,0 +1,285 @@
# UGREEN DXP4800 LED control
# Based on https://github.com/miskcoo/ugreen_leds_controller
{ config, lib, pkgs, ... }:
let
# Disk mapping: ata port -> LED name
# DXP4800 has bays 1-4, all populated
diskMapping = {
"1" = "disk1";
"2" = "disk2";
"3" = "disk3";
"4" = "disk4";
};
# LED colors (R G B)
colors = {
healthy = "0 255 0"; # Green
activity = "0 255 0"; # Green blink
standby = "0 0 255"; # Dim blue when sleeping
fail = "255 0 0"; # Red
network = "0 255 0"; # Green
power = "255 255 255"; # White
};
brightness = 255;
standbyBrightness = 30; # Dim when in standby
refreshInterval = "0.1"; # Seconds between activity checks
powerCheckInterval = 30; # Seconds between power state checks
# Script to initialize LEDs on boot
initLedsScript = pkgs.writeShellScript "ugreen-leds-init" ''
set -euo pipefail
PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli ]}:$PATH"
# Wait for i2c device to be available
for i in $(seq 1 30); do
if [ -e /dev/i2c-0 ]; then
break
fi
sleep 1
done
# Initialize power LED - solid white
ugreen_leds_cli power -on -color ${colors.power} -brightness ${toString brightness} || true
# Initialize network LED - will be controlled by netdevmon
ugreen_leds_cli netdev -off || true
# Initialize disk LEDs based on mapping
${lib.concatStringsSep "\n" (lib.mapAttrsToList (ata: led: ''
ugreen_leds_cli ${led} -off || true
'') diskMapping)}
echo "UGREEN LEDs initialized"
'';
# Disk activity monitoring script
diskMonitorScript = pkgs.writeShellScript "ugreen-diskiomon" ''
set -euo pipefail
PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli pkgs.coreutils pkgs.gnugrep pkgs.gawk pkgs.smartmontools pkgs.hdparm ]}:$PATH"
# Build device -> LED mapping by checking ata ports
declare -A devices
declare -A diskio_data
declare -A disk_healthy
declare -A disk_standby
# Discover disks based on ata port mapping
for path in /dev/disk/by-path/pci-*-ata-*; do
[ -e "$path" ] || continue
# Skip partitions
[[ "$path" == *-part* ]] && continue
# Extract ata port number (e.g., ata-2 -> 2)
ata_port=$(echo "$path" | grep -oP 'ata-\K[0-9]+' | head -1)
case "$ata_port" in
${lib.concatStringsSep "\n " (lib.mapAttrsToList (ata: led: ''
${ata})
device=$(readlink -f "$path")
short_name=$(basename "$device")
devices["${led}"]="$short_name"
echo "Mapped $short_name (ata-${ata}) -> ${led}"
;;'') diskMapping)}
esac
done
if [ ''${#devices[@]} -eq 0 ]; then
echo "No disks found matching ATA ports, exiting"
exit 1
fi
# Set initial LED state for discovered disks
for led in "''${!devices[@]}"; do
device="''${devices[$led]}"
# Check SMART health (this will wake the disk at boot, which is acceptable)
if smartctl -H "/dev/$device" 2>/dev/null | grep -q "PASSED"; then
disk_healthy["$led"]=1
ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true
else
disk_healthy["$led"]=0
ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true
fi
# Initialize tracking
diskio_data["$led"]=""
disk_standby["$led"]=0
done
echo "Starting disk activity monitoring for ''${#devices[@]} disk(s)"
# Function to update LED based on current state
update_led() {
local led="$1"
local device="''${devices[$led]}"
# Check power state without waking disk
local power_state
power_state=$(hdparm -C "/dev/$device" 2>/dev/null | grep -oP '(standby|active/idle|active|idle)' | head -1 || echo "unknown")
if [[ "$power_state" == "standby" ]]; then
if [[ "''${disk_standby[$led]}" != "1" ]]; then
# Disk just went to standby - dim the LED
disk_standby["$led"]=1
ugreen_leds_cli "$led" -on -color ${colors.standby} -brightness ${toString standbyBrightness} || true
echo "Disk $device entered standby, dimming LED"
fi
else
if [[ "''${disk_standby[$led]}" == "1" ]]; then
# Disk woke up - restore health-based color
disk_standby["$led"]=0
if [[ "''${disk_healthy[$led]}" == "1" ]]; then
ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true
else
ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true
fi
echo "Disk $device woke up, restoring LED"
fi
fi
}
# Background power state checker
check_power_states() {
while true; do
sleep ${toString powerCheckInterval}
for led in "''${!devices[@]}"; do
update_led "$led"
done
done
}
# Start power state checker in background
check_power_states &
POWER_CHECK_PID=$!
trap "kill $POWER_CHECK_PID 2>/dev/null || true" EXIT
# Main activity monitoring loop
while true; do
for led in "''${!devices[@]}"; do
device="''${devices[$led]}"
stat_file="/sys/block/$device/stat"
if [ -f "$stat_file" ]; then
new_stat=$(cat "$stat_file" 2>/dev/null || echo "")
if [ -n "$new_stat" ] && [ "''${diskio_data[$led]}" != "$new_stat" ]; then
# Activity detected - disk must be awake now
if [[ "''${disk_standby[$led]}" == "1" ]]; then
disk_standby["$led"]=0
if [[ "''${disk_healthy[$led]}" == "1" ]]; then
ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true
else
ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true
fi
fi
# Trigger LED blink for activity
if [ -e "/sys/class/leds/$led/shot" ]; then
echo 1 > "/sys/class/leds/$led/shot" 2>/dev/null || true
else
ugreen_leds_cli "$led" -blink 100 100 2>/dev/null || true
sleep 0.05
ugreen_leds_cli "$led" -on 2>/dev/null || true
fi
fi
diskio_data["$led"]="$new_stat"
fi
done
sleep ${refreshInterval}
done
'';
# Network activity monitoring script
netMonitorScript = pkgs.writeShellScript "ugreen-netdevmon" ''
set -euo pipefail
PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli pkgs.coreutils pkgs.iproute2 ]}:$PATH"
INTERFACE="$1"
CHECK_INTERVAL=60
echo "Starting network monitoring on $INTERFACE"
# Configure LED to trigger on network activity
led_path="/sys/class/leds/netdev"
while true; do
# Check if interface is up
if ip link show "$INTERFACE" 2>/dev/null | grep -q "state UP"; then
# Link is up - set green
ugreen_leds_cli netdev -on -color ${colors.network} -brightness ${toString brightness} || true
# Try to enable hardware trigger for activity indication
if [ -e "$led_path/device_name" ]; then
echo "$INTERFACE" > "$led_path/device_name" 2>/dev/null || true
fi
if [ -e "$led_path/rx" ]; then
echo 1 > "$led_path/rx" 2>/dev/null || true
fi
if [ -e "$led_path/tx" ]; then
echo 1 > "$led_path/tx" 2>/dev/null || true
fi
else
# Link is down - turn off
ugreen_leds_cli netdev -off || true
fi
sleep $CHECK_INTERVAL
done
'';
in
{
# Load i2c-dev kernel module for LED controller communication
boot.kernelModules = [ "i2c-dev" ];
# Install CLI tool
environment.systemPackages = [ pkgs.ugreen-leds-cli ];
# LED initialization service - runs once at boot
systemd.services.ugreen-leds-init = {
description = "Initialize UGREEN NAS LEDs";
wantedBy = [ "multi-user.target" ];
after = [ "local-fs.target" "systemd-modules-load.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${initLedsScript}";
};
};
# Disk activity monitoring service
systemd.services.ugreen-diskiomon = {
description = "UGREEN disk activity LED monitor";
wantedBy = [ "multi-user.target" ];
after = [ "ugreen-leds-init.service" "local-fs.target" ];
requires = [ "ugreen-leds-init.service" ];
serviceConfig = {
Type = "simple";
ExecStart = "${diskMonitorScript}";
Restart = "always";
RestartSec = "5s";
};
};
# Network activity monitoring service (template for interface)
systemd.services."ugreen-netdevmon@" = {
description = "UGREEN network LED monitor for %i";
after = [ "ugreen-leds-init.service" "network-online.target" ];
requires = [ "ugreen-leds-init.service" ];
serviceConfig = {
Type = "simple";
ExecStart = "${netMonitorScript} %i";
Restart = "always";
RestartSec = "10s";
};
};
# Enable network monitoring for primary interface
systemd.services."ugreen-netdevmon@enp2s0" = {
wantedBy = [ "multi-user.target" ];
};
}

View File

@@ -1,43 +1,48 @@
filebot-license: ENC[AES256_GCM,data:7fmczgcLOp/tkdoMVGfwESam9NwhZrNJXNcnebMKfilEKc81V4fdzS3/hxlueun2At6047UAdDi+6jAO3FvTmzmpmOFnrQxhojUKX2kj9SMSuDlskqxNt04O8wpGy69T8uOeV3ZUcRASzmfzQmuVU+zPKoIV2nq34mhXeIl17V22ZvoTDtFLs2w3IzxtKMZqLtDH4GZ4dkRcQBFcGY96T9R4CLcBqyZeBYb15ZaVLN9dHpQfExtpsBMVB3NikdWgUK9IAbjff1Xtkn+RT9gpQggCyQUl6UbtGqienbCq8ATORe1f89s+q6x/KneC5bdVZKQ4IqZrzDcuQ5aEJUEslc3aVITI3THEBWLOhYANy/4mMyw7R2KRIiHLrXAiiuxi3JumBvev27S6ilKdNo2By8OsbAB/Oj2YvDc5EBTePLCU0OloHIMSxWv5VkLQ2rM94bYtqXN269Lv6w4zmpOF0QblrvtoXnhhf4BQyf9owRDuNMrbp3Iu9Xnj87GoQVnulpwsMSifFxIibd94sRxS/N/5JTTih3Twj/Y8ZpmvoR8t9vCJ14vwBbMXheBpIYzxTk1phZKK1jLfi49DpWudop6iKNOCZdMeXxVRyAfTpOZSGqmdmsqOEDZESCnOAcNYlTxXmLmTdeqJfvTzsnWUi6kbbMYzaSaUlG7UB+RYnt97mcT5NtmbwCRDuFEOdqKGiR/vi7natH8VXe6nlY7xZxei0kkKAhIwyXV+mj/xurbQP3SuyYZriO2luCfuazxmVR0FpBqv+UZ6LwldmQ5e+O7hveYd+o1IjqjT67pVgLyFo1XY95Phq4/vFNvcZKhP7dqKlddkO5RCqjxHCmLowPSUfO2G4k7NV9/UioudATZnWGBpMfSp0pSuD9GXrPg=,iv:5BP77BRudjLiIKI5973BWbQlftupAdfd/aqFeN7DYLM=,tag:q2CAzGZ8lXPS41Uf2NjJTg==,type:str] pyload-extraction-passwords: ENC[AES256_GCM,data:rYlrVlEpPw0ZH9MgH3zrWk9vERGN9BAiq7Luspxlf6gYFZvRfK/McA==,iv:+bVb7Km4e8x1jK3hB2aULWPfNlJzBTrOYvfpIYcLAh8=,tag:zgIQ8nG8CpI8+E2gMthkJw==,type:str]
cyberghost-auth: ENC[AES256_GCM,data:v9RP+XTZv6llTaIiE7EzEgtNxJyCBw==,iv:hPlJ8UvAtlOTfWikd4NxcfkB8IA3EMzURWkjl0tYC1E=,tag:hEbN6u2QFt1hgw0SioXu2g==,type:str]
cyberghost-ca: ENC[AES256_GCM,data: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,iv:Vr9+Rqu+TR7AT4mdPFjDwjId0jxRD9GtFn/j4HRS8po=,tag:W9KGh26VKhQOcBwIel1lXw==,type:str]
cyberghost-cert: ENC[AES256_GCM,data:em3Z4369nsv/ROXrNvuKx8MP722YoB2M+C1G+XjHMJnB2YcMTguo5W7Ho6nq6HfvYB98kCbG3suKTTSsHy34uvfwSmV/x1fUNR2NJA1L+NXzVkS9Og4R/5DOB74FoWOUFT+olVj9xQGrqJfH8/EGLX9YlJ+EVt09NWLMd0Am6YwuUqUOtXen4j/aY5rj1RBi2WckW0Z1IS1xwQuYA39bPCO5yWqTzoA3VjXhe9ADbm3O9PFc5G16zAZD4XH7V7E3TViZ8pXJ589/rmNxW4bvd14/eoXdh54T7l5nqVuyhvMZbrj56PQSq7sO3fkHYZgAlkbUypvIWK2UhOGgNk8t80c5NFXJsW4AyjLys4drJ2FCa947UE4ma1IfjDIpEM9zUiK9UmKLKRDsryoMjghA5yk0+c/yz5FRdcn4X/2wXj3zMSXg/iW1rNM3XBpD/SYnKpzvx18IMDVkFbop11TZnFgwrWlKBZEVRaGPQpWOmmbms2Xnfz8SSrFDNjft2IhWnBuPQshPTbQPCrg/l3WYCNYz5roQT3VAwxFS+mR9q1sdenC2XTk78RCLyU75uP/ptTMNfGyXBhcq+OnYnyNu7vb+WPnNcbZMZvs0L6rWNCrUnYFd6tN3M/j9BSbZmuLx+c/pT6RfdEgQu6tj90TUtJkcMTD29+JS9KRIvzS0wLIbPi6MTb5WdENTGzbrueiXEXExWDrJ8CmgajzKRL8xd+Qz3l/kA/xMVhljReNJtbvM2ArW82GczXwtAuyV5cBpuVesg4LDz7KzytV0Ygg99+m/VLTUkTNWlnGwPaIKi643ZTQQ7xzHawcCRMpe1Doj3YJf/UbOd7MgJ2fOj9VAKruKJgle5PLSQrr7J4L+wfOYbcj9PzpG7l2oDL01uE9XqG8l97HOn77L7oae7JgQl1izoZREbf+4aOZfDJ6HWTDANx4nolT+VH7hMh3TfgKLk3fRJUtrcUvrT6xL3xbqcvc2yXU5jXQbvIdalP1WL6U9ihS/9zTe6dUGBPx5BP9d8tAQS04pOU3NoCPJfVxYEBOvmmVR9CBRsQruUfk2sqwlO1KQyupNRnFALtfD2Ke5ZDni9IK18XhkQXRL8l5QbFtH28aiWqPayxC0hGrcb1JEe4v2TBzihJQuSJ40+OuQyOwrQRuV7cvwgIFlbPSn6s1Q0n6RlEluMKjvE71HYOTs+zPiMQ1FOozCOo4beLiB8yKbr2aOLN30Xyv7MyGjcNylJy3SYeaLFAyFqy5vGBqRb+6smPNkUNxgrD6TjLYBJty7iDDc1+1DLWZVX/6tjIL5Ut0xRu1xfWL+MQHqOE+esUcLgS3G6XwR3jx3nnmESz7qWyyafFWHLUnT0xRKgFDiGbDhQBQxmbDhMPBV4AzgxfKxAqowmuI396F+qlOcJdoGZ8Uux5D4mUncwHddQ/ovp4RQz/3TZXzRzN3Tx6sOHQioAw7/ZYocoG1cFT0fBfOH2TB4rCSNlUKQHe87xLacozAd+3y0HNmVCZTdNmqIrNyZ5o1OgUUoy4b02ixkvbnHlD+RwRz7CD+Px2Pemli74/vwrF2iTHvO3wCkIZeG+3k63YmRqfOVcRXpGJlETDFTkkcK0CqIhWu5qjhMFhpGCK2VpnvvW0hsjMZCS+cdO92VGEc6ckYdR5U1Q0fiRts03C49XvewrsfH6hX365iT5rlyg/NPLPD2/y0LEsqditMWKVuGlC1XIf8kI/t8z63GxffoSrCMFPmJ/UzupZT0fi7dfOOKEe5g4JA3mTx6zboFd/gv6Odcz9duNY9tbmg+MBrejjMSparEP579F0R3Guma60YlpUNxSaZ3LXwjgrC07MM6QOh6Xr9vGvOd7q5HGMuY+aL59pv+7acrTieS0QDmGLVFW6O3L1Xd9Ewa8Qfzi/m+wg1u7oh9wa/2P2x7xhDteD4fpwX38TcjEJ3973UMuTDv+/RT9tVjxyYfgpSxDmeZGaI9qjPWXbnmHxu4CvCo/1QJhAcUtyp1si0TbkNLNHi4LSyU+CLqFx+XKvPIxQjNttF2bXPL2CQqDFY+Gyg/2Vm8Ot3mHbPLS/dy4ecK81o8LeMMLpX2+wP5rNLNWa5f+1DZ8YtEjwuG3T4tu1OuTAfaVYZpS7mcYZfXVfWbKhGYd9q2UGOzvRW4I2UjugKMcJCpYSZK7M15n8IwIZg14uEhKNpUiCgaSej3vQV+ADBQGRmiUBm8ABArRTQYiHUOrJPKuWv0s3RJvO6GIULjQcAemc15Cc7akxwMeIak9Jp6WLcVUxLCP2kCYHyHKxuuGZrkKdsk//GHL5Hf2sGPLzO17Kn04REDUYvo+prY5KZUsdq5AkADKeAWk7FXlAPm4x3yXQsr3VK7uDedpg5rbA62kMn9alca3s/c3RF1dVrjXQf36cUPIjSmp/Ka+YSfWzQBs2PK8tkVSRuTzVb5xnRyAQXNqRgFlnuz0jnvJgQcc+SykpCgKZyEmJt9gI/E74ST1xidNsZvrv9zIfWy+jyJCucnCQ1PKHRQfVF8krz9TNJi+SH2teYXLEqZJU6ycWuGZi9LntPvytI6IlNUNkFwBCPgOw7ZAbD1lTUHWUK4P+QleCBihhCpM+kFtKUa7oQKlx5XxpzXbvwUYyOtqUgfBBkkDor3+gRihJymK2KqQeP/YFMq0+RV4PY7CzqiqsxApcJVTdVvspSVuuY46cBgLLFJKvpVIQ48/0WYqxYaBH+ILhOQtq4L0ZIYv03EzxzRMLchcwPmb4zu2XTsFG/svzwkdjaNV5XzITUdDNdgiDTUW9NECmWzcSFKEMtHn0iZmbc8SUqKYtDT9WKY7XZYPSe2MB/X3bU0Y46zDD/3OUo72G+0KpbclMkJxdEtBoAeZY8n5clQ4RG1o6ratq3vw6EVXFKqQ6RlVU3aOovp/bw6o4SoQ5WuoJjt421YEHn6MQHz9Y1cL9+w8XDE6AcER3Sdr0L6MgdYHV0uKa2iaG12tjCvY34kCQMJap4ppE0hKvUsyOk0E4nLjdTQbsh86GosbPsXYQpvZs2/wXf6uVcn/l383xqsuaZsyJlYS4YZs3M1nUvd2usprD8IcuSTUPI4MA44dWnxvBv+VT+sZXYrCgy42gYL5Zjis/ND+VyLLtAgKQabjSmaWHXwADBx7w==,iv:8QIh1mro4XQRVNA3jdpEYwQduIHQANvx0EDd4+jXGuo=,tag:UWxbmmtvDnU61YyKV5OgNw==,type:str]
cyberghost-key: ENC[AES256_GCM,data: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,iv:coMt1mZj0pVnNEfxStG6NCg6BcC6jloahDWmBTu9ASs=,tag:V2PGkbP2RCHpWoWHeA4CBA==,type:str]
filebot-license: ENC[AES256_GCM,data:RM7VFeecAEN/vcMidDgQU2ghD3ujUax4XXS/1ilDlKZ5WeimNRDhdvx/cJyGsXpcR0vsiqhJhVVX8/0+4IVnFgyVd6MSFwdWf09mUEqB0TdjvHpn7InIuD0N9K6uQUWDUntyk+K6QkTVIvvT3dy4dU/evbhySCyccODvQdNFPFYCPDXb3dQvQpQaYU3EYVJhgWs8ZCYxRTVIBbzX71jjaSPHZDm+pdzC+CC2Wc8Ro8mCWqkdGKUrgMOxFeeghMkbj+QMg+B1yzuCUdFGIvNQKdlJzVjKjSpAgu8PhwxpPP6DqTP0oPtjyT9ZkUS982RdgykPSaIy2z2glfgKkrANCkQVvLGlG487tGhiRo4tzSyG9G/aExWrHYsRBroWsLMLX7Ca+l7DnUhyXRPVMdWWf8oQtunouh5K+oCexg14fKXUAexezKj1elyxGy/Qz2hIOeVkNzdKVTW4TTYDyRukFDvc4LN0DkfyXYGYM70KwEVyJO7lNmHdOIX4gFU5Kzcl1RRaDvaq6oqpNyW8q5WOi/JbOnbNP1+o8wb3iGAmh3cMr8Mu+2o/Z5rf+Q5WSbtiB59N1vqFrc6wz8CRPpyp/GTkFhuepjabWAPSG889H51u9+MRZhGyP1N6AGs8Vqjrk5f+qg46Fg3p9R2r6RL2zNrcRE4VBR9nLgVDyybvw4ZvUGUKbVyuT00TwN3NdlNahPd1OsXczQbKx3//ThwYEQcRfHo1wRFw7aZT8atGhCG9UUuSJTS8boiI6HbnL0NjTttLeTlTeoCpTrwaFzN2igqLVFMylic/xOAnmXY1kX7lVV5m+djLIQpdHZXJbM4TXTKu6fozVTSgr5fPzI+C1SXKGtltOX5QUQ5PmP8KqJMNtR+W298u0AKe6RbmR/s=,iv:2AlDfURrGXEs8tjpi5E2X7HSUXFVEGPf3hzpc84jE6I=,tag:+RwlLLHNe5Fkf3Np6cxw/Q==,type:str]
sops: sops:
age: age:
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7 - recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQWUI4YUsvODJtdTN4NDNn YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVbGZhQ0J3bW5Pa3VFck5R
QkFDVmFSV2Q1Q04rcWtiazZEMzFCYzRJMEJrCjM5RE41TjE0eURrNi9iQnBTR1Fy ODBWUjA0Qm9TZUNPaEdEMDR1OXMxbFloQlFnClQ2YUlacGlTMnl2TCs1TDAwb0pL
SENYYmloSjI1c25pck5CSTJZTDhCeTQKLS0tIEs0SnFSNUdsdzZWS0loTEdBN1RD NmFLNVA5VkJDbzBqUE1LbXlKR3NDV2MKLS0tIHNnYmZ4aTREdW1wdXAzbTZ4U2tJ
ZnhBREtlR3o4VTVMZ1RtY1lVbG40YkUK2isPCoJSTQ6CUbHftSDoUZC8MMTqr512 RDJHZFkxRG9YRE43NURBa2dub0xzdE0KdukLUA5deF7z4jpVWb/JkkB/0PrCLujX
lCoeGQqnArTO8CWDJxIxRczooTo4mW7vDqD7idWdPgOdWZI8hWPE5Q== ycweOSdwDe/ceME9rnckFISyQ++4g2aRVvUwaKl5thu5cl9p+2MDMQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz - recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwdnJIeWh1RkhvLzF2QUlm YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoeDdHNXU1cGRXaERQZ2Rt
ZmNHaFpzL0M3eUdCdk1GL1g2MTdtdTdjVmdjCmVRTWJsajhKT0E5STN2SEUzWHFa WGNtSHZRTTZYNmJ6WE5WMXkwQUFCcXcyeWpBCitPeHlJUVRYb2dlZDd4S3hqMmFP
ak1NelloQnNiY3FaUm9oVGg5eit2eTAKLS0tIEoxcURjUkJsRENtblZpKy9QT0gx T1F1bUFpOGZTclBYazlYeW1FYlVSZk0KLS0tIDRUeEdlRUdjdjZnSmlBR1VYb0s3
ME5kM1EwYUFNMVFkT3VWZmpGSzRoWFUKzGNK5FzRWiY+E1Je6l0veoN5Z3K2TFMY Q2VDbnMvVHlVT1JPR0xOZ0psRVY4cGMK+hTtRCa1DdmmxrKi/M1PF6izWHhkFnIl
pm9+FGuYs+wxSrhLwajITj+NuH0+zK81mrYsugH+6OTNb7cDbLgh/g== 3P1nR7qDtVcnV4NrAp0IyY0NN+F3JgMqK1u9CoBpNtaUifZk67oqew==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch - recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlWCs0a3EvZC9sWmI5S2tU YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGZXhGZ01nNHg2YS9iOTlq
aFpRMVlrZG1zL1ZJSklJMkNQY1RLMW53YUhZCjNpQ1pHUE0yVUxleVovcVRLMFNh aW5zMnFyaGF5U2xhKzhMM3NPVFI4QXhxUmpzCnpNL25wVkgyMEhKcXUwZlc1T2pZ
amVFTnJteW8xRjlFY29HWkJrcVJiQzgKLS0tIFJrWWloc2ZWdGdPNlNQM2szTkZI b3RtWGJXVTJjWXdId0xUQ3ltYTgwUFUKLS0tIEZ5cjV3OVh2a1g5bGk3TzdDMEdn
Zk42dFgrcUJOa3UwSDB3MnpMcVRLdmsKOKbF18HnowVhiEHO2B+BZqpM8Oc8vbDh S3FnSUFvZG95ZDk0d1lsdFhSclZMN1kKWJ/G7punUyloNy1rPk0FW8lL3A1xojpy
hczIpcezwMvv96L2/seX86Hv5mEAQvwN2CVA+sknnDL1XNA/2Ng9cw== lEdKj2GK5ml9HAi/aRb3AmSyBXnpjyMAX6piCa3a+ixzCve/ttwjMA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek - recipient: age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6L1pVc2lRYVVnMU1uR1hn YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHOFI0Y3U3S3NTUXZZRnBl
aldPODkvTVFzNDRuWWFTTU5jU0dyOHFMNkZBCnQ0ZGxUcGR5d0FqK2pOenJzSEN4 U2V2Y1pnQ0lzb1RFZkUrcGhERTk2bUpIWlNVCnY4WHpQemdoczNtN1VHa095ai9N
ak12VXhQSnZlbSs1V3BxZnBIQ0xKV1EKLS0tIEkrc00wTzJzVjVDd0o4WHNQVDV6 TEltU1c0cDNRWDVjTXFKVHp2a1M1QUEKLS0tIGF6VzNCait4QU1MdGI0T2x4dU0v
WGlpR1kvdXFnMkxOQVVuL3pIckdLRGcK+xoZE63l+9mlR5ufN9kEtgKEHdIUcGbI aWsyT0hTWUU1N3RxUStRditOWFNJT0UKtCOhjZ1q0FaTZekXUh3JpLxL+K9+I6x+
CpNhd8RE23tPKaVa0XbQA3bMqc1J9jST3vSWWewexwdLvfjrooSFZw== WxAKrhw9vpCiUs2UQkr+xYh8lwIR+qVpcDn6LRlq0WqW1PhypR8L2g==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-11-28T18:05:54Z" lastmodified: "2025-12-04T22:54:18Z"
mac: ENC[AES256_GCM,data:rmGDt0ZvZ8S//X1sqzkM9GdsoLBTB9dUprWdVN5M9F4/Zpq6Mpyk04VdGxYz421Gi+AsvhAkBaKi+XJjiEjRf9dYON/N18bWeRe3mJMLVOOoxGz+PQOeAuCyphZEKsCkae79WtbRZqONkU+kSqT5ED6iLjhOpLn1h6Cuw4wV1Xc=,iv:XWjRyxlGP4a14eUaJvZpizy2UiCSIi/PIUyaZg6GCJY=,tag:ZNp/U1O3wkcb8o5s1USrsw==,type:str] mac: ENC[AES256_GCM,data:mIcIvHI6RsXMGJ0W6YgtpNLtXd4xOF2PelnGcCCR/EhyVHr6sTGItkvlzWpPjGjZ1t4mgCS8J9t228BU6OIBW/liOAA+1G8P09PNq/FKRnQuXUz3Q1aLawtk6gTWMdkxQ01Wr7P+nWzFcZ4Qi9TktYS0Omu7l+JmUrd0KoY4xdI=,iv:WcquzmjFd6fXj59oRwYMjYnFegn9eVw+vLKDzpf0lw0=,tag:QY6sEBkzT3HhWXQtsaKF3Q==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0

View File

@@ -1 +1 @@
https://channels.nixos.org/nixos-25.05 https://channels.nixos.org/nixos-25.11

View File

@@ -307,7 +307,7 @@ in {
''; '';
}; };
services.xserver.desktopManager.gnome.extraGSettingsOverrides = '' services.desktopManager.gnome.extraGSettingsOverrides = ''
[org.gnome.desktop.interface] [org.gnome.desktop.interface]
cursor-size=24 cursor-size=24
''; '';

View File

@@ -115,7 +115,7 @@
hardware.graphics = { hardware.graphics = {
enable = true; enable = true;
extraPackages = with pkgs; [ extraPackages = with pkgs; [
vaapiVdpau libva-vdpau-driver
libvdpau-va-gl libvdpau-va-gl
libva libva
libva-utils libva-utils

View File

@@ -26,14 +26,13 @@ in
description = "Bitwarden Desktop"; description = "Bitwarden Desktop";
after = [ "graphical-session.target" "network-online.target" ]; after = [ "graphical-session.target" "network-online.target" ];
wantedBy = [ "graphical-session.target" ]; wantedBy = [ "graphical-session.target" ];
serviceConfig.ExecStart = "${pkgs.bitwarden}/bin/bitwarden"; serviceConfig.ExecStart = "${pkgs.bitwarden-desktop}/bin/bitwarden-desktop";
serviceConfig.Restart = "on-abort"; serviceConfig.Restart = "on-abort";
}; };
#### Handy tools ############################################################# #### Handy tools #############################################################
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
goldwarden bitwarden-desktop
bitwarden
bitwarden-cli bitwarden-cli
fprintd fprintd
lxqt.lxqt-policykit lxqt.lxqt-policykit

View File

@@ -57,10 +57,10 @@ in {
netflix netflix
networkmanagerapplet networkmanagerapplet
nextcloud-client nextcloud-client
onlyoffice-bin onlyoffice-desktopeditors
obs-studio obs-studio
pavucontrol pavucontrol
pinentry pinentry-gnome3
rbw rbw
rofi-rbw rofi-rbw
swayimg swayimg
@@ -103,7 +103,7 @@ in {
fonts.packages = with pkgs; [ fonts.packages = with pkgs; [
noto-fonts noto-fonts
noto-fonts-cjk-sans noto-fonts-cjk-sans
noto-fonts-emoji noto-fonts-color-emoji
nerd-fonts._0xproto nerd-fonts._0xproto
nerd-fonts.droid-sans-mono nerd-fonts.droid-sans-mono
open-sans open-sans

View File

@@ -20,14 +20,24 @@ in {
fi fi
''; '';
home.activation.addChromeDevtoolsMCP = lib.hm.dag.entryAfter [ "installClaudeCli" ] '' # Disabled: chrome-devtools MCP spawns headless Chromium for every Claude session.
# Add via STDIO transport: Claude spawns `npx -y chrome-devtools-mcp ...` # For frontend projects, enable per-project with:
# Browser must be running with remote debugging on 127.0.0.1:9222. # claude mcp add --scope project chrome-devtools \
if ${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --help >/dev/null 2>&1; then # -- npx -y chrome-devtools-mcp \
${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --scope user chrome-devtools \ # --executablePath=${pkgs.ungoogled-chromium}/bin/chromium \
-- npx -y chrome-devtools-mcp --executablePath=${pkgs.ungoogled-chromium}/bin/chromium --isolated=true --headless=true --chromeArg=--ozone-platform=wayland --chromeArg=--enable-features=UseOzonePlatform --chromeArg=--force-device-scale-factor=1 || true # --isolated=true --headless=true \
fi # --chromeArg=--ozone-platform=wayland \
''; # --chromeArg=--enable-features=UseOzonePlatform \
# --chromeArg=--force-device-scale-factor=1
#
# home.activation.addChromeDevtoolsMCP = lib.hm.dag.entryAfter [ "installClaudeCli" ] ''
# # Add via STDIO transport: Claude spawns `npx -y chrome-devtools-mcp ...`
# # Browser must be running with remote debugging on 127.0.0.1:9222.
# if ${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --help >/dev/null 2>&1; then
# ${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --scope user chrome-devtools \
# -- npx -y chrome-devtools-mcp --executablePath=${pkgs.ungoogled-chromium}/bin/chromium --isolated=true --headless=true --chromeArg=--ozone-platform=wayland --chromeArg=--enable-features=UseOzonePlatform --chromeArg=--force-device-scale-factor=1 || true
# fi
# '';
}; };
} }

View File

@@ -20,7 +20,7 @@ in {
nixpkgs.config.android_sdk.accept_license = true; nixpkgs.config.android_sdk.accept_license = true;
programs.adb.enable = true; # sets up udev + adb group programs.adb.enable = true; # sets up udev + adb group
services.udev.packages = [ pkgs.android-udev-rules ]; # android-udev-rules removed in 25.11 - superseded by built-in systemd uaccess rules
users.users.dominik.extraGroups = [ "adbusers" ]; users.users.dominik.extraGroups = [ "adbusers" ];
} }

View File

@@ -38,6 +38,7 @@ in {
rbw rbw
sops sops
unzip unzip
uv
vim vim
wget wget
wireguard-tools wireguard-tools
@@ -52,11 +53,6 @@ in {
# Socket activation - only start when needed to save battery # Socket activation - only start when needed to save battery
onBoot = "ignore"; onBoot = "ignore";
onShutdown = "shutdown"; onShutdown = "shutdown";
qemu = { # qemu.swtpm.enable = true; # enable if you need TPM emulation, etc.
ovmf = {
enable = true; # Enable OVMF firmware support
};
# swtpm.enable = true; # enable if you need TPM emulation, etc.
};
}; };
} }

View File

@@ -1,3 +1,6 @@
-- Set leader key before any other mappings
vim.g.mapleader = " "
-- vim.opt.expandtab = true -- vim.opt.expandtab = true
-- vim.opt.hidden = true -- vim.opt.hidden = true
-- vim.opt.incsearch = true -- vim.opt.incsearch = true

View File

@@ -1,54 +1,31 @@
local status, lspc = pcall(require, 'lspconfig') -- LSP Capabilities (for nvim-cmp integration)
if (not status) then return end
lspc.clangd.setup{}
local buf_map = function(bufnr, mode, lhs, rhs, opts)
vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts or {
silent = true,
})
end
local protocol = require('vim.lsp.protocol')
local on_attach = function(client, buffnr)
if client.server.capabilities.documentFormattingProvider then
vim.api.nvim_create_autocmd("BufWritePre", {
group = vim.api.nvim_create_augroup("format", { clear = true }),
buffer = buffnr,
callback = function() vim.lsp.buf.formatting_seq_sync() end
})
end
end
local capabilities = vim.lsp.protocol.make_client_capabilities() local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true capabilities.textDocument.completion.completionItem.snippetSupport = true
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities) capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
local servers = { 'ts_ls', 'lua_ls', 'cssls', 'yamlls', 'intelephense', 'gopls' } -- Global LSP configuration
for _, lsp in pairs(servers) do vim.lsp.config('*', {
require('lspconfig')[lsp].setup {
-- on_attach = on_attach,
capabilities = capabilities, capabilities = capabilities,
} })
end
lspc.yamlls.setup({ -- Server-specific configurations
vim.lsp.config('clangd', {})
vim.lsp.config('yamlls', {
settings = { settings = {
yaml = { yaml = {
keyOrdering = false, keyOrdering = false,
}, },
}, },
}); })
-- autoformat json files with jq -- Enable all LSP servers
vim.lsp.enable({ 'clangd', 'ts_ls', 'lua_ls', 'cssls', 'yamlls', 'intelephense', 'gopls' })
-- JSON file formatting with jq
vim.api.nvim_create_autocmd("FileType", { vim.api.nvim_create_autocmd("FileType", {
pattern = "json", pattern = "json",
callback = function(ev) callback = function(ev)
vim.bo[ev.buf].formatprg = "jq" vim.bo[ev.buf].formatprg = "jq"
print("It's a json file")
end, end,
}) })
-- lspc.intelephense.setup()

View File

@@ -1,41 +1,13 @@
config = { local status_ok, project = pcall(require, "project")
---@usage set to false to disable project.nvim.
--- This is on by default since it's currently the expected behavior.
active = true,
on_config_done = nil,
---@usage set to true to disable setting the current-woriking directory
--- Manual mode doesn't automatically change your root directory, so you have
--- the option to manually do so using `:ProjectRoot` command.
manual_mode = false,
---@usage Methods of detecting the root directory
--- Allowed values: **"lsp"** uses the native neovim lsp
--- **"pattern"** uses vim-rooter like glob pattern matching. Here
--- order matters: if one is not detected, the other is used as fallback. You
--- can also delete or rearangne the detection methods.
-- detection_methods = { "lsp", "pattern" }, -- NOTE: lsp detection will get annoying with multiple langs in one project
detection_methods = { "pattern" },
---@usage patterns used to detect root dir, when **"pattern"** is in detection_methods
patterns = { ".git", "_darcs", ".hg", ".bzr", ".svn", "Makefile", "package.json", "pom.xml" },
---@ Show hidden files in telescope when searching for files in a project
show_hidden = false,
---@usage When set to false, you will get a message when project.nvim changes your directory.
-- When set to false, you will get a message when project.nvim changes your directory.
silent_chdir = true,
---@usage list of lsp client names to ignore when using **lsp** detection. eg: { "efm", ... }
ignore_lsp = {},
}
local status_ok, project = pcall(require, "project_nvim")
if not status_ok then if not status_ok then
return return
end end
project.setup(config) project.setup({
use_lsp = false, -- Use pattern matching only (equivalent to old detection_methods = { "pattern" })
manual_mode = false,
patterns = { ".git", "_darcs", ".hg", ".bzr", ".svn", "Makefile", "package.json", "pom.xml" },
show_hidden = false,
silent_chdir = true,
ignore_lsp = {},
})

View File

@@ -1,7 +1,7 @@
-- none-ls -- none-ls (module is still named "null-ls" for backward compatibility)
local status_ok_nls, none_ls_module = pcall(require, "none-ls") local status_ok_nls, none_ls_module = pcall(require, "null-ls")
if not status_ok_nls then if not status_ok_nls then
vim.notify("none-ls plugin not found or failed to load. Check Nix config and plugin paths.", vim.log.levels.WARN) vim.notify("null-ls plugin not found or failed to load. Check Nix config and plugin paths.", vim.log.levels.WARN)
else else
local nb = none_ls_module.builtins local nb = none_ls_module.builtins
none_ls_module.setup({ none_ls_module.setup({

View File

@@ -1,5 +1,3 @@
vim.g.mapleader = " "
local function smart_quit() local function smart_quit()
local bufnr = vim.api.nvim_get_current_buf() local bufnr = vim.api.nvim_get_current_buf()
local modified = vim.api.nvim_buf_get_option(bufnr, "modified") local modified = vim.api.nvim_buf_get_option(bufnr, "modified")
@@ -27,122 +25,77 @@ end
local wk = require("which-key") local wk = require("which-key")
wk.setup({}) wk.setup({
preset = "classic",
wk.register({ delay = 0,
["<leader>"] = { triggers = {
{ "<auto>", mode = "nxso" },
[";"] = { "<cmd>Alpha<CR>", "Dashboard" }, { " ", mode = "n" }, -- literal space character
["w"] = { "<cmd>w!<CR>", "Save" },
["q"] = { "<cmd>smart_quit()<CR>", "Quit" },
["/"] = { "<Plug>(comment_toggle_linewise_current)", "Comment toggle current line" },
["c"] = { "<cmd>BufferKill<CR>", "Close Buffer" },
["f"] = { find_project_files, "Find File" },
["h"] = { "<cmd>nohlsearch<CR>", "No Highlight" },
["t"] = { "<cmd>TodoTelescope keywords=TODO,FIX<CR>", "Find TODO,FIX" },
b = {
name = "Buffers",
j = { "<cmd>BufferLinePick<cr>", "Jump" },
f = { "<cmd>Telescope buffers<cr>", "Find" },
b = { "<cmd>BufferLineCyclePrev<cr>", "Previous" },
n = { "<cmd>BufferLineCycleNext<cr>", "Next" },
-- w = { "<cmd>BufferWipeout<cr>", "Wipeout" }, -- TODO: implement this for bufferline
e = {
"<cmd>BufferLinePickClose<cr>",
"Pick which buffer to close",
}, },
h = { "<cmd>BufferLineCloseLeft<cr>", "Close all to the left" },
l = {
"<cmd>BufferLineCloseRight<cr>",
"Close all to the right",
},
D = {
"<cmd>BufferLineSortByDirectory<cr>",
"Sort by directory",
},
L = {
"<cmd>BufferLineSortByExtension<cr>",
"Sort by language",
},
},
-- " Available Debug Adapters:
-- " https://microsoft.github.io/debug-adapter-protocol/implementors/adapters/
-- " Adapter configuration and installation instructions:
-- " https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation
-- " Debug Adapter protocol:
-- " https://microsoft.github.io/debug-adapter-protocol/
-- " Debugging
g = {
name = "Git",
g = { Lazygit_toggle, "Lazygit" },
j = { "<cmd>lua require 'gitsigns'.next_hunk({navigation_message = false})<cr>", "Next Hunk" },
k = { "<cmd>lua require 'gitsigns'.prev_hunk({navigation_message = false})<cr>", "Prev Hunk" },
l = { "<cmd>lua require 'gitsigns'.blame_line()<cr>", "Blame" },
p = { "<cmd>lua require 'gitsigns'.preview_hunk()<cr>", "Preview Hunk" },
r = { "<cmd>lua require 'gitsigns'.reset_hunk()<cr>", "Reset Hunk" },
R = { "<cmd>lua require 'gitsigns'.reset_buffer()<cr>", "Reset Buffer" },
s = { "<cmd>lua require 'gitsigns'.stage_hunk()<cr>", "Stage Hunk" },
u = {
"<cmd>lua require 'gitsigns'.undo_stage_hunk()<cr>",
"Undo Stage Hunk",
},
o = { "<cmd>Telescope git_status<cr>", "Open changed file" },
b = { "<cmd>Telescope git_branches<cr>", "Checkout branch" },
c = { "<cmd>Telescope git_commits<cr>", "Checkout commit" },
C = {
"<cmd>Telescope git_bcommits<cr>",
"Checkout commit(for current file)",
},
d = {
"<cmd>Gitsigns diffthis HEAD<cr>",
"Git Diff",
},
},
l = {
name = "LSP",
a = { "<cmd>lua vim.lsp.buf.code_action()<cr>", "Code Action" },
d = { "<cmd>Telescope diagnostics bufnr=0 theme=get_ivy<cr>", "Buffer Diagnostics" },
w = { "<cmd>Telescope diagnostics<cr>", "Diagnostics" },
-- f = { require("lvim.lsp.utils").format, "Format" },
i = { "<cmd>LspInfo<cr>", "Info" },
I = { "<cmd>Mason<cr>", "Mason Info" },
j = {
vim.diagnostic.goto_next,
"Next Diagnostic",
},
k = {
vim.diagnostic.goto_prev,
"Prev Diagnostic",
},
l = { vim.lsp.codelens.run, "CodeLens Action" },
q = { vim.diagnostic.setloclist, "Quickfix" },
r = { vim.lsp.buf.rename, "Rename" },
s = { "<cmd>Telescope lsp_document_symbols<cr>", "Document Symbols" },
S = {
"<cmd>Telescope lsp_dynamic_workspace_symbols<cr>",
"Workspace Symbols",
},
e = { "<cmd>Telescope quickfix<cr>", "Telescope Quickfix" },
},
a = { "<cmd>lua require('telescope.builtin').lsp_code_actions()<cr>", "Code Actions" },
d = { "<cmd>lua require('telescope.builtin').lsp_document_diagnostics()<cr>", "LSP Diagnostics" },
k = { "<cmd>lua vim.lsp.buf.signature_help()<cr>", "Signature Help" },
P = { "<cmd>lua require'telescope'.extensions.projects.projects{}<cr>", "Signature Help" },
}
}) })
wk.register( wk.add({
{ -- Single key mappings
["/"] = { "<Plug>(comment_toggle_linewise_visual)", "Comment toggle linewise (visual)" }, { "<leader>;", "<cmd>Alpha<CR>", desc = "Dashboard" },
}, { "<leader>w", "<cmd>w!<CR>", desc = "Save" },
{ { "<leader>q", smart_quit, desc = "Quit" },
mode = "v", -- VISUAL mode { "<leader>/", "<Plug>(comment_toggle_linewise_current)", desc = "Comment toggle current line" },
prefix = "<leader>", { "<leader>c", "<cmd>BufferKill<CR>", desc = "Close Buffer" },
buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings { "<leader>f", find_project_files, desc = "Find File" },
silent = true, -- use `silent` when creating keymaps { "<leader>h", "<cmd>nohlsearch<CR>", desc = "No Highlight" },
noremap = true, -- use `noremap` when creating keymaps { "<leader>t", "<cmd>TodoTelescope keywords=TODO,FIX<CR>", desc = "Find TODO,FIX" },
nowait = true, -- use `nowait` when creating keymaps
} -- Buffers group
) { "<leader>b", group = "Buffers" },
{ "<leader>bj", "<cmd>BufferLinePick<cr>", desc = "Jump" },
{ "<leader>bf", "<cmd>Telescope buffers<cr>", desc = "Find" },
{ "<leader>bb", "<cmd>BufferLineCyclePrev<cr>", desc = "Previous" },
{ "<leader>bn", "<cmd>BufferLineCycleNext<cr>", desc = "Next" },
{ "<leader>be", "<cmd>BufferLinePickClose<cr>", desc = "Pick which buffer to close" },
{ "<leader>bh", "<cmd>BufferLineCloseLeft<cr>", desc = "Close all to the left" },
{ "<leader>bl", "<cmd>BufferLineCloseRight<cr>", desc = "Close all to the right" },
{ "<leader>bD", "<cmd>BufferLineSortByDirectory<cr>", desc = "Sort by directory" },
{ "<leader>bL", "<cmd>BufferLineSortByExtension<cr>", desc = "Sort by language" },
-- Git group
{ "<leader>g", group = "Git" },
{ "<leader>gg", Lazygit_toggle, desc = "Lazygit" },
{ "<leader>gj", "<cmd>lua require 'gitsigns'.next_hunk({navigation_message = false})<cr>", desc = "Next Hunk" },
{ "<leader>gk", "<cmd>lua require 'gitsigns'.prev_hunk({navigation_message = false})<cr>", desc = "Prev Hunk" },
{ "<leader>gl", "<cmd>lua require 'gitsigns'.blame_line()<cr>", desc = "Blame" },
{ "<leader>gp", "<cmd>lua require 'gitsigns'.preview_hunk()<cr>", desc = "Preview Hunk" },
{ "<leader>gr", "<cmd>lua require 'gitsigns'.reset_hunk()<cr>", desc = "Reset Hunk" },
{ "<leader>gR", "<cmd>lua require 'gitsigns'.reset_buffer()<cr>", desc = "Reset Buffer" },
{ "<leader>gs", "<cmd>lua require 'gitsigns'.stage_hunk()<cr>", desc = "Stage Hunk" },
{ "<leader>gu", "<cmd>lua require 'gitsigns'.undo_stage_hunk()<cr>", desc = "Undo Stage Hunk" },
{ "<leader>go", "<cmd>Telescope git_status<cr>", desc = "Open changed file" },
{ "<leader>gb", "<cmd>Telescope git_branches<cr>", desc = "Checkout branch" },
{ "<leader>gc", "<cmd>Telescope git_commits<cr>", desc = "Checkout commit" },
{ "<leader>gC", "<cmd>Telescope git_bcommits<cr>", desc = "Checkout commit(for current file)" },
{ "<leader>gd", "<cmd>Gitsigns diffthis HEAD<cr>", desc = "Git Diff" },
-- LSP group
{ "<leader>l", group = "LSP" },
{ "<leader>la", "<cmd>lua vim.lsp.buf.code_action()<cr>", desc = "Code Action" },
{ "<leader>ld", "<cmd>Telescope diagnostics bufnr=0 theme=get_ivy<cr>", desc = "Buffer Diagnostics" },
{ "<leader>lw", "<cmd>Telescope diagnostics<cr>", desc = "Diagnostics" },
{ "<leader>li", "<cmd>LspInfo<cr>", desc = "Info" },
{ "<leader>lI", "<cmd>Mason<cr>", desc = "Mason Info" },
{ "<leader>lj", vim.diagnostic.goto_next, desc = "Next Diagnostic" },
{ "<leader>lk", vim.diagnostic.goto_prev, desc = "Prev Diagnostic" },
{ "<leader>ll", vim.lsp.codelens.run, desc = "CodeLens Action" },
{ "<leader>lq", vim.diagnostic.setloclist, desc = "Quickfix" },
{ "<leader>lr", vim.lsp.buf.rename, desc = "Rename" },
{ "<leader>ls", "<cmd>Telescope lsp_document_symbols<cr>", desc = "Document Symbols" },
{ "<leader>lS", "<cmd>Telescope lsp_dynamic_workspace_symbols<cr>", desc = "Workspace Symbols" },
{ "<leader>le", "<cmd>Telescope quickfix<cr>", desc = "Telescope Quickfix" },
-- Direct LSP shortcuts
{ "<leader>a", "<cmd>lua require('telescope.builtin').lsp_code_actions()<cr>", desc = "Code Actions" },
{ "<leader>d", "<cmd>lua require('telescope.builtin').lsp_document_diagnostics()<cr>", desc = "LSP Diagnostics" },
{ "<leader>k", "<cmd>lua vim.lsp.buf.signature_help()<cr>", desc = "Signature Help" },
{ "<leader>P", "<cmd>lua require'telescope'.extensions.projects.projects{}<cr>", desc = "Projects" },
-- Visual mode mappings
{ "<leader>/", "<Plug>(comment_toggle_linewise_visual)", desc = "Comment toggle linewise (visual)", mode = "v" },
})

View File

@@ -13,7 +13,7 @@ in
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
nodePackages.typescript-language-server nodePackages.typescript-language-server
sumneko-lua-language-server lua-language-server
nest nest
nodePackages.intelephense nodePackages.intelephense
nodePackages.vscode-langservers-extracted nodePackages.vscode-langservers-extracted

View File

@@ -1,6 +1,6 @@
{ config, pkgs, ... }: { config, pkgs, ... }:
let let
home-manager = builtins.fetchTarball "https://github.com/nix-community/home-manager/archive/release-24.11.tar.gz"; home-manager = builtins.fetchTarball "https://github.com/nix-community/home-manager/archive/release-25.11.tar.gz";
in in

View File

@@ -135,11 +135,11 @@ let
{ name = "q"; value = "{searchTerms}"; } { name = "q"; value = "{searchTerms}"; }
]; ];
}]; }];
iconUpdateURL = "https://perplexity.ai/favicon.ico"; icon = "https://perplexity.ai/favicon.ico";
definedAliases = [ "@perplexity" ]; definedAliases = [ "@perplexity" ];
}; };
"Google".metaData.hidden = true; "google".metaData.hidden = true;
"Bing".metaData.hidden = true; "bing".metaData.hidden = true;
}; };
}; };
@@ -301,26 +301,23 @@ in
programs.git = { programs.git = {
enable = true; enable = true;
lfs.enable = true; lfs.enable = true;
package = pkgs.gitAndTools.gitFull; package = pkgs.gitFull;
userName = "Dominik Polakovics";
userEmail = "dominik.polakovics@cloonar.com";
# signing = { # signing = {
# key = "dominik.polakovics@cloonar.com"; # key = "dominik.polakovics@cloonar.com";
# signByDefault = false; # signByDefault = false;
# }; # };
iniContent = { settings = {
user.name = "Dominik Polakovics";
user.email = "dominik.polakovics@cloonar.com";
# Branch with most recent change comes first # Branch with most recent change comes first
branch.sort = "-committerdate"; branch.sort = "-committerdate";
# Remember and auto-resolve merge conflicts # Remember and auto-resolve merge conflicts
# https://git-scm.com/book/en/v2/Git-Tools-Rerere # https://git-scm.com/book/en/v2/Git-Tools-Rerere
rerere.enabled = true; rerere.enabled = true;
}; "url \"gitea@git.cloonar.com:\"" = {
extraConfig = {
"url.gitea@git.cloonar.com:" = {
insteadOf = "https://git.cloonar.com/"; insteadOf = "https://git.cloonar.com/";
}; };
}; };
}; };
programs.thunderbird = { programs.thunderbird = {
@@ -525,7 +522,7 @@ in
settings = firefoxSettings; settings = firefoxSettings;
# userChrome = firefoxUserChrome; # userChrome = firefoxUserChrome;
search = firefoxSearchSettings; search = firefoxSearchSettings;
extensions = firefoxExtensions; extensions.packages = firefoxExtensions;
}; };
social = { social = {
id = 1; id = 1;
@@ -560,7 +557,7 @@ in
id = 3; id = 3;
}; };
}; };
extensions = firefoxExtensions; extensions.packages = firefoxExtensions;
}; };
}; };
}; };

View File

@@ -1 +1 @@
https://channels.nixos.org/nixos-25.05 https://channels.nixos.org/nixos-25.11

View File

@@ -63,7 +63,7 @@
time.timeZone = "Europe/Vienna"; time.timeZone = "Europe/Vienna";
services.logind.extraConfig = "RuntimeDirectorySize=2G"; services.logind.settings.Login.RuntimeDirectorySize = "2G";
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
sops.defaultSopsFile = ./secrets.yaml; sops.defaultSopsFile = ./secrets.yaml;

View File

@@ -15,7 +15,7 @@
datasourceUid = "vm-datasource-uid"; datasourceUid = "vm-datasource-uid";
model = { model = {
editorMode = "code"; editorMode = "code";
expr = "node_systemd_unit_state{state=\"active\", name=\"dovecot2.service\"} OR on() vector(0)"; expr = "node_systemd_unit_state{state=\"active\", name=\"dovecot.service\"} OR on() vector(0)";
hide = false; hide = false;
intervalMs = 1000; intervalMs = 1000;
legendFormat = "__auto"; legendFormat = "__auto";

View File

@@ -0,0 +1,17 @@
{ lib, pkgs, ... }:
let
smartDashboard = import ./smart-dashboard.nix { inherit lib pkgs; };
dashboardDir = pkgs.linkFarm "grafana-dashboards" [
{ name = "smart.json"; path = smartDashboard; }
];
in
{
services.grafana.provision.dashboards.settings = {
apiVersion = 1;
providers = [{
name = "nix-dashboards";
type = "file";
options.path = dashboardDir;
}];
};
}

View File

@@ -0,0 +1,463 @@
{ lib, pkgs }:
let
datasourceUid = "vm-datasource-uid";
# Helper to create a panel with common defaults
mkPanel = { id, title, type, gridPos, targets, options ? { }, fieldConfig ? { }, ... }@args:
{
inherit id title type gridPos targets;
datasource = { uid = datasourceUid; type = "prometheus"; };
options = options;
fieldConfig = {
defaults = fieldConfig.defaults or { };
overrides = fieldConfig.overrides or [ ];
};
} // (builtins.removeAttrs args [ "id" "title" "type" "gridPos" "targets" "options" "fieldConfig" ]);
# Dashboard definition
dashboard = {
uid = "smart-disk-health";
title = "S.M.A.R.T Disk Health";
description = "S.M.A.R.T metrics and RAID array status";
tags = [ "disk" "smart" "storage" "nas" ];
timezone = "browser";
editable = false;
refresh = "5m";
schemaVersion = 39;
version = 1;
# Variables
templating.list = [
{
name = "host";
label = "Host";
type = "query";
datasource = { uid = datasourceUid; type = "prometheus"; };
query = "label_values(smart_health_passed, instance)";
regex = "";
sort = 1;
refresh = 1;
includeAll = true;
multi = false;
current = { selected = true; text = "All"; value = "$__all"; };
}
];
# Panels
panels = [
# === OVERVIEW ROW ===
{
id = 1;
type = "row";
title = "Overview";
collapsed = false;
gridPos = { x = 0; y = 0; w = 24; h = 1; };
panels = [ ];
}
# Alert Status - Shows firing disk alerts
{
id = 5;
title = "Alert Status";
type = "alertlist";
gridPos = { x = 0; y = 1; w = 6; h = 5; };
options = {
alertInstanceLabelFilter = "";
alertName = "Disk";
dashboardAlerts = false;
groupBy = [ ];
groupMode = "default";
maxItems = 20;
sortOrder = 1;
stateFilter = {
"error" = true;
firing = true;
noData = false;
normal = false;
pending = false;
};
viewMode = "list";
};
}
# Health Status - Stat panel
(mkPanel {
id = 2;
title = "Disk Health Status";
type = "stat";
gridPos = { x = 6; y = 1; w = 6; h = 5; };
targets = [{
expr = ''smart_health_passed{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "A";
}];
options = {
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
orientation = "horizontal";
textMode = "auto";
colorMode = "background";
graphMode = "none";
};
fieldConfig = {
defaults = {
mappings = [
{ type = "value"; options."1" = { text = "PASSED"; color = "green"; index = 0; }; }
{ type = "value"; options."0" = { text = "FAILED"; color = "red"; index = 1; }; }
];
thresholds = {
mode = "absolute";
steps = [
{ color = "red"; value = null; }
{ color = "green"; value = 1; }
];
};
};
};
})
# Temperature Gauge
(mkPanel {
id = 3;
title = "Disk Temperatures";
type = "gauge";
gridPos = { x = 12; y = 1; w = 6; h = 8; };
targets = [{
expr = ''smart_temperature_celsius{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "A";
}];
options = {
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
orientation = "auto";
showThresholdLabels = false;
showThresholdMarkers = true;
};
fieldConfig = {
defaults = {
unit = "celsius";
min = 0;
max = 70;
thresholds = {
mode = "absolute";
steps = [
{ color = "green"; value = null; }
{ color = "yellow"; value = 45; }
{ color = "red"; value = 55; }
];
};
};
};
})
# RAID Status - Stat panel
(mkPanel {
id = 4;
title = "RAID Array Status";
type = "stat";
gridPos = { x = 18; y = 1; w = 6; h = 8; };
targets = [{
expr = ''mdadm_array_state{instance=~"$host"}'';
legendFormat = "{{array}}";
refId = "A";
}];
options = {
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
orientation = "horizontal";
textMode = "auto";
colorMode = "background";
graphMode = "none";
};
fieldConfig = {
defaults = {
mappings = [
{ type = "value"; options."1" = { text = "Healthy"; color = "green"; index = 0; }; }
{ type = "value"; options."0" = { text = "Degraded"; color = "red"; index = 1; }; }
];
thresholds = {
mode = "absolute";
steps = [
{ color = "red"; value = null; }
{ color = "green"; value = 1; }
];
};
};
};
})
# Sector Health Table - Promoted to overview for visibility
(mkPanel {
id = 13;
title = "Sector Health";
type = "table";
gridPos = { x = 0; y = 6; w = 12; h = 4; };
targets = [
{
expr = ''smart_reallocated_sector_ct{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "A";
format = "table";
instant = true;
}
{
expr = ''smart_current_pending_sector{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "B";
format = "table";
instant = true;
}
{
expr = ''smart_offline_uncorrectable{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "C";
format = "table";
instant = true;
}
];
options = {
showHeader = true;
cellHeight = "sm";
};
transformations = [
{ id = "merge"; options = { }; }
{
id = "organize";
options = {
excludeByName = { Time = true; __name__ = true; instance = true; job = true; serial = true; };
renameByName = {
device = "Device";
"Value #A" = "Reallocated Sectors";
"Value #B" = "Pending Sectors";
"Value #C" = "Offline Uncorrectable";
};
};
}
];
fieldConfig = {
defaults = {
thresholds = {
mode = "absolute";
steps = [
{ color = "green"; value = null; }
{ color = "yellow"; value = 1; }
{ color = "red"; value = 10; }
];
};
custom = { displayMode = "color-background-solid"; };
};
};
})
# === DETAILED METRICS ROW ===
{
id = 10;
type = "row";
title = "Detailed Metrics";
collapsed = false;
gridPos = { x = 0; y = 10; w = 24; h = 1; };
panels = [ ];
}
# Temperature Time Series
(mkPanel {
id = 11;
title = "Temperature Over Time";
type = "timeseries";
gridPos = { x = 0; y = 11; w = 12; h = 8; };
targets = [{
expr = ''smart_temperature_celsius{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "A";
}];
options = {
legend = { displayMode = "list"; placement = "bottom"; showLegend = true; };
tooltip = { mode = "multi"; sort = "desc"; };
};
fieldConfig = {
defaults = {
unit = "celsius";
custom = {
drawStyle = "line";
lineInterpolation = "smooth";
fillOpacity = 10;
pointSize = 5;
showPoints = "auto";
};
thresholds = {
mode = "absolute";
steps = [
{ color = "green"; value = null; }
{ color = "yellow"; value = 45; }
{ color = "red"; value = 55; }
];
};
};
};
})
# Power On Hours
(mkPanel {
id = 12;
title = "Power On Hours";
type = "stat";
gridPos = { x = 12; y = 11; w = 12; h = 8; };
targets = [{
expr = ''smart_power_on_hours{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "A";
}];
options = {
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
orientation = "horizontal";
textMode = "value_and_name";
colorMode = "none";
graphMode = "none";
};
fieldConfig = {
defaults = {
unit = "h";
};
};
})
# === RAID DETAILS ROW ===
{
id = 20;
type = "row";
title = "RAID Details";
collapsed = false;
gridPos = { x = 0; y = 19; w = 24; h = 1; };
panels = [ ];
}
# RAID Devices
(mkPanel {
id = 21;
title = "RAID Array Devices";
type = "stat";
gridPos = { x = 0; y = 20; w = 12; h = 4; };
targets = [
{
expr = ''mdadm_array_devices_active{instance=~"$host"}'';
legendFormat = "{{array}} Active";
refId = "A";
}
{
expr = ''mdadm_array_devices_total{instance=~"$host"}'';
legendFormat = "{{array}} Total";
refId = "B";
}
];
options = {
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
orientation = "horizontal";
textMode = "value_and_name";
colorMode = "value";
graphMode = "none";
};
fieldConfig = {
defaults = {
unit = "short";
};
};
})
# UDMA CRC Errors
(mkPanel {
id = 22;
title = "UDMA CRC Errors";
type = "timeseries";
gridPos = { x = 12; y = 20; w = 12; h = 4; };
targets = [{
expr = ''smart_udma_crc_error_count{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "A";
}];
options = {
legend = { displayMode = "list"; placement = "bottom"; showLegend = true; };
tooltip = { mode = "multi"; sort = "desc"; };
};
fieldConfig = {
defaults = {
unit = "short";
custom = {
drawStyle = "line";
lineInterpolation = "stepAfter";
fillOpacity = 0;
pointSize = 5;
showPoints = "auto";
};
};
};
})
# Last Update Timestamp
(mkPanel {
id = 30;
title = "Last Metrics Update";
type = "stat";
gridPos = { x = 0; y = 24; w = 6; h = 5; };
targets = [{
expr = ''time() - disk_metrics_last_update{instance=~"$host"}'';
legendFormat = "Age";
refId = "A";
}];
options = {
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
orientation = "horizontal";
textMode = "value";
colorMode = "value";
graphMode = "none";
};
fieldConfig = {
defaults = {
unit = "s";
thresholds = {
mode = "absolute";
steps = [
{ color = "green"; value = null; }
{ color = "yellow"; value = 1800; }
{ color = "red"; value = 3600; }
];
};
};
};
})
# Device Activity Status
(mkPanel {
id = 31;
title = "Device Activity";
type = "stat";
gridPos = { x = 6; y = 24; w = 18; h = 5; };
targets = [{
expr = ''smart_device_active{instance=~"$host"}'';
legendFormat = "{{device}}";
refId = "A";
}];
options = {
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
orientation = "horizontal";
textMode = "auto";
colorMode = "background";
graphMode = "none";
};
fieldConfig = {
defaults = {
mappings = [
{ type = "value"; options."1" = { text = "Active"; color = "green"; index = 0; }; }
{ type = "value"; options."0" = { text = "Standby"; color = "blue"; index = 1; }; }
];
thresholds = {
mode = "absolute";
steps = [
{ color = "blue"; value = null; }
{ color = "green"; value = 1; }
];
};
};
};
})
];
};
in
pkgs.writeText "smart-dashboard.json" (builtins.toJSON dashboard)

View File

@@ -31,11 +31,13 @@ in
./alerting/system/default.nix ./alerting/system/default.nix
./alerting/service/default.nix ./alerting/service/default.nix
./alerting/websites/default.nix ./alerting/websites/default.nix
# ./alerting/storage/default.nix ./alerting/storage/default.nix
./datasources/victoriametrics.nix ./datasources/victoriametrics.nix
./datasources/loki.nix ./datasources/loki.nix
./dashboards/default.nix
./alert-cleanup.nix ./alert-cleanup.nix
]; ];
@@ -103,11 +105,12 @@ in
contactPoints = { contactPoints = {
settings = { settings = {
apiVersion = 1; apiVersion = 1;
contactPoints = [{ contactPoints = [
{
orgId = 1; orgId = 1;
name = "cp_dominik"; name = "cp_dominik_emergency";
receivers = [{ receivers = [{
uid = "dominik_pushover_cp_receiver"; uid = "dominik_pushover_emergency";
type = "pushover"; type = "pushover";
settings = { settings = {
apiToken = "\${PUSHOVER_API_TOKEN}"; apiToken = "\${PUSHOVER_API_TOKEN}";
@@ -118,12 +121,28 @@ in
expire = "2m"; expire = "2m";
sound = "siren"; sound = "siren";
okSound = "magic"; okSound = "magic";
message = '' message = ''{{ template "default.message" . }}'';
{{ template "default.message" . }}
'';
}; };
}]; }];
}
{
orgId = 1;
name = "cp_dominik_normal";
receivers = [{
uid = "dominik_pushover_normal";
type = "pushover";
settings = {
apiToken = "\${PUSHOVER_API_TOKEN}";
userKey = "\${PUSHOVER_USER_KEY}";
device = "iphone";
priority = 1;
sound = "siren";
okSound = "magic";
message = ''{{ template "default.message" . }}'';
};
}]; }];
}
];
}; };
}; };
@@ -131,7 +150,15 @@ in
settings = { settings = {
apiVersion = 1; apiVersion = 1;
policies = [{ policies = [{
receiver = "cp_dominik"; receiver = "cp_dominik_normal";
repeat_interval = "999d";
routes = [
{
receiver = "cp_dominik_emergency";
matchers = [ "alertname = HostDown" ];
repeat_interval = "999d";
}
];
}]; }];
}; };
}; };

View File

@@ -17,7 +17,7 @@ buildGoModule rec {
subPackages = [ "." ]; subPackages = [ "." ];
# Optional tuning # Optional tuning
CGO_ENABLED = 0; env.CGO_ENABLED = "0";
ldflags = [ "-s" "-w" ]; ldflags = [ "-s" "-w" ];
doCheck = false; doCheck = false;

View File

@@ -19,9 +19,9 @@ fi
HOSTNAME="$1" HOSTNAME="$1"
# Check if 'nixos-rebuild' command is available # Check if 'nix-instantiate' command is available
if ! command -v nixos-rebuild > /dev/null; then if ! command -v nix-instantiate > /dev/null; then
echo "ERROR: 'nixos-rebuild' command not found. Please ensure it is installed and in your PATH." >&2 echo "ERROR: 'nix-instantiate' command not found. Please ensure Nix is installed and in your PATH." >&2
exit 1 exit 1
fi fi
@@ -38,27 +38,42 @@ if [ ! -f "$CONFIG_PATH" ]; then
exit 1 exit 1
fi fi
# Check for host-specific channel file
CHANNEL_PATH="$SCRIPT_DIR/../hosts/$HOSTNAME/channel"
CHANNEL_OPT=""
if [ -f "$CHANNEL_PATH" ]; then
CHANNEL_URL=$(cat "$CHANNEL_PATH")
# Append /nixexprs.tar.xz to get the actual tarball URL
TARBALL_URL="${CHANNEL_URL}/nixexprs.tar.xz"
echo "INFO: Using channel '$TARBALL_URL' from '$CHANNEL_PATH'."
CHANNEL_OPT="-I nixpkgs=$TARBALL_URL"
else
echo "WARNING: No channel file found at '$CHANNEL_PATH'. Using system default." >&2
fi
echo "INFO: Attempting dry-build for host '$HOSTNAME' using configuration '$CONFIG_PATH'..." echo "INFO: Attempting dry-build for host '$HOSTNAME' using configuration '$CONFIG_PATH'..."
if [ "$VERBOSE" = true ]; then if [ "$VERBOSE" = true ]; then
echo "INFO: Verbose mode enabled, --show-trace will be used." echo "INFO: Verbose mode enabled, --show-trace will be used."
fi fi
# Execute nixos-rebuild dry-build # Execute nix-instantiate to evaluate the configuration
# Store the output and error streams, and the exit code # nix-instantiate fetches fresh tarballs and catches all evaluation errors
NIX_OUTPUT_ERR=$(nixos-rebuild dry-build $SHOW_TRACE_OPT -I nixos-config="$CONFIG_PATH" --show-trace 2>&1) # unlike nixos-rebuild which may use cached results
NIX_OUTPUT_ERR=$(nix-instantiate $SHOW_TRACE_OPT $CHANNEL_OPT -I nixos-config="$CONFIG_PATH" '<nixpkgs/nixos>' -A system 2>&1)
NIX_EXIT_STATUS=$? NIX_EXIT_STATUS=$?
# Check the exit status # Check the exit status
if [ "$NIX_EXIT_STATUS" -eq 0 ]; then if [ "$NIX_EXIT_STATUS" -eq 0 ]; then
echo "INFO: Dry-build for host '$HOSTNAME' completed successfully." echo "INFO: Dry-build for host '$HOSTNAME' completed successfully."
if [ "$VERBOSE" = true ]; then if [ "$VERBOSE" = true ]; then
echo "Output from nixos-rebuild:" echo "Output from nix-instantiate:"
echo "$NIX_OUTPUT_ERR" echo "$NIX_OUTPUT_ERR"
fi fi
exit 0 exit 0
else else
echo "ERROR: Dry-build for host '$HOSTNAME' failed. 'nixos-rebuild' exited with status $NIX_EXIT_STATUS." >&2 echo "ERROR: Dry-build for host '$HOSTNAME' failed. 'nix-instantiate' exited with status $NIX_EXIT_STATUS." >&2
echo "Output from nixos-rebuild:" >&2 echo "Output from nix-instantiate:" >&2
echo "$NIX_OUTPUT_ERR" >&2 echo "$NIX_OUTPUT_ERR" >&2
exit "$NIX_EXIT_STATUS" exit "$NIX_EXIT_STATUS"
fi fi

View File

@@ -1,88 +1,97 @@
promtail-password: ENC[AES256_GCM,data:DykxIRTXttQgJ6vv3oBOhX1h2PrPimLz+dEHZwjFvg34UEGWfQu5nODw7h6qAJrKIGR5217LgTGZzg1HedbM4Dsb2OJW9c39bXIga730eVvGCm6RcMbpv8GDHPuVCfO1NwQox9Fba8veDWDNqNisHQuYDRQrNZrg1QEiKsujZdY=,iv:kM5Ec376USXMoXCVF/4g7F1NbJNbWfTMVd7LKsTnTuE=,tag:y8aEF+Q6/Cm16W2LYF+orA==,type:str] promtail-password: ENC[AES256_GCM,data:jooCw16EEw9JC+W19bXvoOjnCo/KP0H1Bpc0UqfGN+mCqFLK98TDU80hNu54pYQowcAtgjB5ZM64gWt+stqFKWVWihF0d4A3KuTTfpxmXGdGi6ThRcAXhMmXLH5SYR4N96d4WsvHNsFTRGItnUlp2juQMKHnZ2At9RQWgBQqK6Q=,iv:HFttRHz2fIU9qZzP5r24/AKMHTWwDhhIrgQpxw6Ol/Q=,tag:umq2lzodL2nijayGms7ciw==,type:str]
sops: sops:
age: age:
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7 - recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxQTkrMDlpM3RnZ0pNZVlM YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFT21QaTZ5ditZekVmaitY
dkNya243OFlycmRRS1o3c3Z1Vm1UNWxBbkVBCmw4dDUrQkg0NExaTHJaSk1JYnpY b2locXR6M1NNSXhIL0pjK3Y4dzlYcjRXMlFNCnVtelpDdWovc0FiUGQwWm5TRlk1
UDNHa09Rd081N1FVbXgyRHVWbUtna1EKLS0tICszQ2Z4aWpNV1U5RVNibllGdGlY b1pka2paQ1NBd2c3WG12U0N3N24vbHMKLS0tIHgrMHdxS3J1WWZNdGdiaVRaWjBm
alFRNFZVNDlOUTJRbVQ0T3dRTTlJZUEKx+ftKJc+RMmxXoRxLd6gsvN6Jfnn5Xre cmh0VEx3UDRocFVlckFUN21YblpEb0kKKF7CPzXn6e9o1+BctLSHcLZTWYdYiXQs
48TolLwPoBSr6uSmfWfcXIL+2uzo5cTGhMReCEQrlHOWGxhk+XDmfw== dwX8ohGJc/Q5Ewrrdmm77gu3ttg7Ml/70ToG/yTBExH1lwGb1z7Qag==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz - recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVSjAvWC9haUh3blQ2bHJj YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3R2hwWmtZVjlTSFZTMUd5
cnh5YkkvUWhEYXFFV3RNbURwVlROZk1yUmc0CjdMdXExWE52WWNyRGdyVFRzT1o1 cXpqbXkwTmQrYS9TT1NYaXZ4azFjRTlOZ0Y0ClBIUGk5R0JGenBrSXhNbExLaWkr
Umo4OWhMYTZjTkJvbW9UaHJVaE1YNG8KLS0tIHBSOEdmQjFCZ25jNGlHMmZoalpW eUJLUVBYYzBFbHg3Z2l4N2JINTBSVjAKLS0tIHFTa3JKUTVPNFM2TktUbm9mSkVo
c0FZUzBVYXRTMHFZSGYxVDdzS2d5a0kK1a/FQ841bIKuXHjVAjV2YPTpkmI0R7fX Vno4TER6SFR4bDM3L3FYSkw1UHJoeXcK0mR/ysz38ZhEAqhEZZXmuH3rykMUeFk4
ohkPSQneoOnwZPXby69PJLSYwX0IcQCckkGXa1z6KLr6iueSpyM6JA== tPvIV3LpRXpU+yiT3zpLJXVi3GDy9vaq/h/uG7rDhE/nPoaIIVBBhg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch - recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtYUNabzVBcFpIdFhUTW85 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtV2tlLzhqQWlDd2FaZ0lJ
ZlVjamVZUERSMGNMUkhlUUVVd2ozUkFLWHlzCnh0c2gweU1ud2cxS2p1eXUxNy9j dEt5Q2YvTWR6Q3pBUHl1elJEN3lJcE01YTBrCm1IWlVVd1poVk1Gazd3NnBCa21L
d2tCTVR0YjY5bktQa09tUmFvM0F3aEkKLS0tIFVWMVFUU1RMV1FoaklnS3Z0VzBJ UXByN05KNGdUTW9uckhvNUE4bFVMME0KLS0tIDVSRCtJNnRSdmFzcWVNNHEzV092
bitzcStWdzM3TXBMbGJKNGVZQTNVZ0EK0qjI7PKk9lUDG+0ZeCL/9ILI9KRIEU+z NHFYYTFRdUVXNFh5Tlc0U2twYWpuWDQKTqRXFxn/OuYrjVSlGNyHWtCwmaV+4PMr
6o4AcdGcd44QkUjYboLTwGvdf4QdKZvyfBk6xliUIzn0tbX0CrEHOA== +wpjkS+3pEWYaMtRhoBKJmPXhbE9e0SSzFV/HEYILswUfIWuQNpNUQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw - recipient: age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQdmhqdXpIblh0QVRuSGdl YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4RFl5UHpOcUIrdEVoREFn
WmpaV20za2d5MFdYbTNTRTEzR3BUVFJLU2tRCkdHT1JFNmVEZGNkRGJ1S1cwU1Ru ZmloRG5nb2d3eHpScmRWVjVLUXpveW9pZzJrClRVa3h1Y2dIL0RCZFJpM3NvTndu
MEp3ck1MN0tYRXBPY2xQR3JIMURpWkEKLS0tIEF2UERsV2J6UzZYUm5sTFdPWGlo VzVMT1dwbWJEOXFKZDNVdEgwV3RHZE0KLS0tIElkTUk1SHRGcFp2d3BpUDgxZWVG
NjVGSDdndDRsQkx4V3U3N3FjNldUTTgKY8ohcy0H+fxkmBksfWzVLZsbfqDfWUzA RE5UQktjTEtzd2I1SmVZWE8xWm5SSDAKOfrr3seS8+UqGZXiJfraGh9wTqx7zFnH
5FUdmqCHdg47Mct3K8qXHSEbvegn/8Hp4vSgkVQcEA2YFcf4J5GRpw== GMBBlCj2SLAHP56efITiPJ6kFISFoc6QgBj024oUXop2HT3CQh5hJw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4 - recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnbFMrNzVMYmlHWE1yb1hj YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoMWhoa1lKWU8yRHZnSUNm
eGw2SS9qNTdKTDdSQUlTNjhnS1dZaTd4NFFzCll6SEhzVnY5UnJUbUtlUzJzZS9N cVhycko2MFczWE56UlNDUGlNdndXNnY0blhvCmlIeGRHUjRuODhCNk5lVlVqZmR1
cUZMYnV0bU5DRjU0MW8vSFpIN2pNT00KLS0tIFdTTlBPT3J0cmF6Y0lnaGRpQW4r ZEpMTUl6dnpoUkhGbTBsOUNzdDliM0UKLS0tIDRHWjllMEZyaDN1OTY0RXpzWUVZ
TjRsa2dlR3hrZkVQTFFWQm1xR1pLQUkK2Kio6ShvcsbJ2n1UG97gxt5AcdqKolMq WklqZW5DT09DclBBOUZ4VmpIMVdCRU0K5c8JtZ5dfzxmtMlnL+3637/6YBWN9qdP
3sdoF7b87Crd3QSzDKx2Rm97EjeQskOBOgpasF2W8GoRYCol05Y0bQ== +/l78vhb0KVt1SOI2d6ZnfkKEXSO/PyBpOkz+AOubxdpQMNOyQsgcA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw - recipient: age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOZGZobW1aaThidmkxSEk1 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3N1BEMjFsZG03K3gxUVli
VXVzdEdIMENnRC9sTGpXQUNwSElPWVlLTVFNClRuQ2pYanFibEJoSllXYVhmSk90 b1hEUEY0NDRRc0xDUkErOXExa05uMkdDK1J3Cm1WUFNRNzZGMWdOOU5kb2kyOFNs
QVlGUVBjMkN4RG9BempCRDlFZHJPancKLS0tIEc3Q29tUzhzYzViMkpzS1RNczBE Y2I1aC90SEZqSER3TXU0RTlVd3VOTHMKLS0tIGFMdDM0YWpJVTFFVEFYcUl3b1Nl
djdYNVdvZHRkOHBWMGk1N3dlb3JLUFEKiruFC9YV3gloPaP9+wY0Sir2xA9NUcPN dlEwNWRmVllHSmtsRWVvb2h3ZGJaZUkKrEzfrlYGgB05NWxc3h6olIzGmdRCYDWj
matBs8oPjlB5dlrCoiHi8kl1i5ROnlu4tlNpLB0PcO9fCUMP1ypAQQ== mr5PEAWo0KGcvPK61lxwpHdThp3NGV0pqAHUU5+7Td/PbguHvaEPhA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df - recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJcFNWb2EvQWF3czQrVVg3 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBandodFZnbU05SVN5cVlO
Wm1hVm16bEtYS2pnM0R4elhGMUlqRUZXcUdvCnlJN3dxU2VKUk9JTk10SjdubmVO V1BVaWtLNWdJMmRtUWxaMGdITmNKUDdXdHlRCk5HbGdSWlFnKytTVWxOcWtPZ3VJ
NlgwU3hqMEp2cmF6R0pmdU9EZllJVTAKLS0tIFNwMC9jdjh0MXJpYzU5cE5mc0Jr bi9EZ2p3VURkeEZuN2xsbEhkcWovUUEKLS0tIGtzTnJFOXFsTittUlZ1eCtjWTVX
KzJoVGlKTUNZYXhpV1NMcVVuVXI3SHcK7PIY6HznGsckYauyFGVxmU344FqkPYhm SXdWczV0ZnI2a24zeElIZUsvU0ZSeUEKNX9qLko/2aFcrwW5LaMjvg9IJlNszSKi
1x74NydHuGLAkMd3H7AchnxP9tVzSX3sOD9AqYqgg3nRS7yaIet+sw== 7nl1d1fTLGCeMUvgwZU1uBIyCm/p0HTikBaDob5L5fJAVlSQNZxiBQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2 - recipient: age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBROTV4Y1dQMkZtM1RUQ1lD YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVdDdBTEpMZEI4NFc2aU1F
QVdyUXY1TVRxWURBZlRsZWVYTEhnQ0lMYUU4ClFLcVdUZ01YcDc1OUYwMWpJRUpy aGlwcDk3ajd6NjE0UUhXbW5WT1RCSmR0M1hzCk1zVVV5V3UvUmx5REZuRk1RT3pW
NHVMM1FrK1B4TEU1QUhsbjdCL3M0dmMKLS0tIFRFSndnZ0V0a2VKV2VXY0N2Qjgz WUVocW9iOXppWFBqRkVlZE1TZzFUbmMKLS0tIDlzLzdnWEhkWWFFQm1seVJlVHBw
dFZQbm13d3JOWlZiSXZTcUpkSSsyVTgKI1GJ1uRRcTH/13lkAiUxNhBNmDgf4MFA THlweWtPcFNyT2RCNk9UQVc3Y0lnNFEK/d2fvmsIrRTHc3kBH2sAUBg0MCp4nXNT
5nk6z1/nJglnvajYyGXlAlZF7XofbUtUWZeBbtWwbeWImjIa/+KaSw== imm7SINgt6aH390yL7BWHMBKzdgNHO6hn3plLV8EW8upsETwJCbrfA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvc3ZteDRVQ0xsRUpxY24x
Wmt4MS9ZL1cybUY4cHdPbC9rQU16WGtESkU4ClhoMnhuY2NrT0xyNzl6Z2hJR3dt
VHhLa2hCeUhCcjFPZTkxeUVaazFWa1kKLS0tIEVndkhYaS9GbDJKSzFIb0xQUzQ0
V2EzOXNWNnYwaXc5dkg5b0RDdjBpa1EKfIC1OigtPBRWIgXUyb4SSjpbO2Koqaiw
TQT+hnR+VkThbcfyWPZ+Zpe4lZzfcdMGfr3m7tdv/xY8epwrThInjA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w - recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjTUhiU0RnMTAyV0xycnNL YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0WHdpbGJNcmZhK3ExRi9t
V2xSWEttejB0SWhJR0FoNTZKL2x6MEJkcUZzCnFjVUVWNGV2SW1NZEpkN24rUVpX K3ZHTld5QmxMYnZXTFZmNFVIbEZtS0pFRDM4CmJPK1BURU83UmFVdVNoam1reG0x
dUJ3Wkx5aUlsWDByOTlpaERpNEpIa0UKLS0tIDExTVVJeDFEUStzamw3RGU5cHdE NlpLbDVQY1FNR1RyWm1TWkQrUHcrNUUKLS0tIFpVVGVscFFPclVTMG5IblkvWXlr
WlNqQm1jRnpLWXBzRVRZUjc3Z0c4dncKonlHRgH7P4da+RJkGdWHRPiN76oPbH5U ek5lNTFkMVUvSU5McTFDS2tWWmZ1UmsKyhUXdaSGxKFFZnATRlTh7GzDu7eZ/mkq
DzNuS7mPsRAuajnCAGeqodzqllsGJatZUOVKFem8Of56Wm3pw3yLhg== V+9pqaob2fshwQ3tNVZtXmWTHv1geyIBxmQCFVSOaHIPVLpiC4Bhow==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-11-14T11:28:03Z" lastmodified: "2025-11-29T19:44:44Z"
mac: ENC[AES256_GCM,data:PBPNSGj6UaGoxH1Jq25bD4q/d42HrnBNhe5KFo1MoQCp/bzsphN8v6+tbHIdGh/VAoU7auRZVWXYALOl/3cGnpL52zvJGDaMlPlDVdzz6wHkl24z1ousWM7FKPwBtvGuAWAknYQW7KpQTtpobbBr8QHy/O4dB/NqxXTj/MSsbxY=,iv:1QOFK1LiKPnAuXeXNBJbeL0d73nsMq+DJCpeVruDumE=,tag:hJH3S3ZurYd0hcoWyWOocw==,type:str] mac: ENC[AES256_GCM,data:pRol7WdkK+Vr3fEc7UaEhoHlLvwwm0KdGOCReS6Rz12gD0Fw2UuNYsPnaj1XTdSLSfJITpEorFTmt455BpC6wMCszICSkqRn+EBgu4WWFZrv5v1m6BjSOTsU8bj1iAggiqsx57WS9opMThCzCOSIJD/EEzQmk5/qva/aBIJni/c=,iv:jorZE1XG0xJDGsXgw8EvuX7AL7yCuSynrqaeveCF4SE=,tag:btZHuaiPdZhdRj/+JU9dSA==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0

View File

@@ -10,6 +10,7 @@ self: super: {
# Python packages # Python packages
python3 = super.python3.override { python3 = super.python3.override {
packageOverrides = pself: psuper: { packageOverrides = pself: psuper: {
aia-chaser = pself.callPackage ../pkgs/aia-chaser { };
mini-racer = pself.callPackage ../pkgs/mini-racer.nix { }; mini-racer = pself.callPackage ../pkgs/mini-racer.nix { };
}; };
}; };

View File

@@ -0,0 +1,32 @@
{ lib
, buildPythonPackage
, fetchPypi
, cryptography
}:
buildPythonPackage rec {
pname = "aia-chaser";
version = "3.3.0";
format = "wheel";
src = fetchPypi {
pname = "aia_chaser";
inherit version format;
dist = "py3";
python = "py3";
hash = "sha256-L0aBV3kfAVI1aJH7VgiiEXzGBSP/HU2zAlahkHeT8hk=";
};
dependencies = [
cryptography
];
pythonImportsCheck = [ "aia_chaser" ];
meta = with lib; {
description = "Retrieve missing certificates to complete SSL certificate chains";
homepage = "https://github.com/dirkjanm/aia-chaser";
license = licenses.mit;
maintainers = [ ];
};
}

View File

@@ -6,8 +6,8 @@ pyload-ng.overridePythonAttrs (oldAttrs: rec {
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "pyload"; owner = "pyload";
repo = "pyload"; repo = "pyload";
rev = "3115740a2210fd57b5d050cd0850a0e61ec493ed"; # [DdownloadCom] fix #4537 rev = "71f2700184ee9344dc313d9833ca7a6bb36007db"; # [DdownloadCom] fix #4537
hash = "sha256-g1eEeNnr3Axtr+0BJzMcNQomTEX4EsUG1Jxt+huPyoc="; hash = "sha256-XAa+XbC3kko+zvEMZkPXRoaHAmEFGsNBDxysX+X06Jc=";
}; };
patches = [ patches = [
@@ -16,6 +16,7 @@ pyload-ng.overridePythonAttrs (oldAttrs: rec {
# Add new dependencies required in newer versions # Add new dependencies required in newer versions
propagatedBuildInputs = (oldAttrs.propagatedBuildInputs or []) ++ (with python3Packages; [ propagatedBuildInputs = (oldAttrs.propagatedBuildInputs or []) ++ (with python3Packages; [
aia-chaser
mini-racer mini-racer
packaging packaging
pydantic pydantic

View File

@@ -1,5 +1,3 @@
diff --git a/src/pyload/core/__init__.py b/src/pyload/core/__init__.py
index 4324fc700..5d915a85e 100644
--- a/src/pyload/core/__init__.py --- a/src/pyload/core/__init__.py
+++ b/src/pyload/core/__init__.py +++ b/src/pyload/core/__init__.py
@@ -130,6 +130,14 @@ class Core: @@ -130,6 +130,14 @@ class Core:
@@ -17,12 +15,13 @@ index 4324fc700..5d915a85e 100644
# If no argument set, read storage dir from config file, # If no argument set, read storage dir from config file,
# otherwise save setting to config dir # otherwise save setting to config dir
if storagedir is None: if storagedir is None:
@@ -227,6 +235,18 @@ class Core: @@ -226,6 +234,20 @@ class Core:
self.acm = self.account_manager = AccountManager(self)
self.thm = self.thread_manager = ThreadManager(self) self.thm = self.thread_manager = ThreadManager(self)
self.cpm = self.captcha_manager = CaptchaManager(self) self.cpm = self.captcha_manager = CaptchaManager(self)
self.adm = self.addon_manager = AddonManager(self)
+ +
+ # Process plugin config environment variables after plugins are loaded (NixOS declarative config) + # Process plugin config environment variables BEFORE AddonManager (NixOS declarative config)
+ # This must happen before AddonManager reads the enabled flag to decide which addons to start
+ # Build case-insensitive lookup map for plugin names + # Build case-insensitive lookup map for plugin names
+ plugin_name_map = {name.lower(): name for name in self.config.plugin.keys()} + plugin_name_map = {name.lower(): name for name in self.config.plugin.keys()}
+ +
@@ -33,6 +32,7 @@ index 4324fc700..5d915a85e 100644
+ if len(parts) == 2 and parts[0] in plugin_name_map: + if len(parts) == 2 and parts[0] in plugin_name_map:
+ actual_plugin_name = plugin_name_map[parts[0]] + actual_plugin_name = plugin_name_map[parts[0]]
+ self.config.set_plugin(actual_plugin_name, parts[1], value) + self.config.set_plugin(actual_plugin_name, parts[1], value)
+
self.adm = self.addon_manager = AddonManager(self)
def _setup_permissions(self): def _setup_permissions(self):
self.log.debug("Setup permissions...")

View File

@@ -7,9 +7,10 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
# Script directory # Script and repo directories
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$(dirname "${BASH_SOURCE[0]}")"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PKG_DIR="$(pwd)"
REPO_ROOT="$(cd ../../.. && pwd)"
# Check if commit SHA is provided # Check if commit SHA is provided
if [ $# -ne 1 ]; then if [ $# -ne 1 ]; then
@@ -34,7 +35,7 @@ fi
echo -e "${GREEN}==> Updating pyload-ng to commit: ${COMMIT_SHA}${NC}" echo -e "${GREEN}==> Updating pyload-ng to commit: ${COMMIT_SHA}${NC}"
# File to update # File to update
PKG_FILE="$REPO_ROOT/utils/pkgs/pyload-ng-updated.nix" PKG_FILE="$PKG_DIR/default.nix"
if [ ! -f "$PKG_FILE" ]; then if [ ! -f "$PKG_FILE" ]; then
echo -e "${RED}Error: Package file not found: $PKG_FILE${NC}" echo -e "${RED}Error: Package file not found: $PKG_FILE${NC}"
@@ -53,7 +54,8 @@ echo " ✓ Updated hash in $PKG_FILE"
# Step 3: Build package to discover the correct hash # Step 3: Build package to discover the correct hash
echo -e "${YELLOW}Step 3: Building package to discover hash...${NC}" echo -e "${YELLOW}Step 3: Building package to discover hash...${NC}"
BUILD_OUTPUT=$(nix-build --impure -E "with import <nixpkgs> { overlays = [ (import $REPO_ROOT/utils/overlays/packages.nix) ]; }; callPackage $PKG_FILE { }" 2>&1 || true) cd "$REPO_ROOT"
BUILD_OUTPUT=$(nix-build --impure -E "with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; }; callPackage ./utils/pkgs/pyload-ng { }" 2>&1 || true)
# Extract hash from error message # Extract hash from error message
HASH=$(echo "$BUILD_OUTPUT" | grep -oP '\s+got:\s+\Ksha256-[A-Za-z0-9+/=]+' | head -1) HASH=$(echo "$BUILD_OUTPUT" | grep -oP '\s+got:\s+\Ksha256-[A-Za-z0-9+/=]+' | head -1)
@@ -74,7 +76,7 @@ echo " ✓ Updated hash in $PKG_FILE"
# Step 5: Verify the build succeeds # Step 5: Verify the build succeeds
echo -e "${YELLOW}Step 5: Verifying build with correct hash...${NC}" echo -e "${YELLOW}Step 5: Verifying build with correct hash...${NC}"
if nix-build --impure -E "with import <nixpkgs> { overlays = [ (import $REPO_ROOT/utils/overlays/packages.nix) ]; }; callPackage $PKG_FILE { }" > /dev/null 2>&1; then if nix-build --impure -E "with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; }; callPackage ./utils/pkgs/pyload-ng { }" > /dev/null 2>&1; then
echo " ✓ Build verification successful" echo " ✓ Build verification successful"
else else
echo -e "${RED}Error: Build verification failed${NC}" echo -e "${RED}Error: Build verification failed${NC}"
@@ -83,7 +85,6 @@ fi
# Step 6: Test configuration for fw host (which uses pyload) # Step 6: Test configuration for fw host (which uses pyload)
echo -e "${YELLOW}Step 6: Testing fw configuration...${NC}" echo -e "${YELLOW}Step 6: Testing fw configuration...${NC}"
cd "$REPO_ROOT"
if ./scripts/test-configuration fw > /dev/null 2>&1; then if ./scripts/test-configuration fw > /dev/null 2>&1; then
echo " ✓ Configuration test passed" echo " ✓ Configuration test passed"
else else