Compare commits

..

16 Commits

Author SHA1 Message Date
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
44 changed files with 395 additions and 307 deletions

8
.mcp.json Normal file
View File

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

View File

@@ -40,7 +40,7 @@ Each host in `hosts/<hostname>/` contains:
- `fleet.nix` → symlink to root `fleet.nix` (SFTP user provisioning)
- `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/`)
- `modules/` - Reusable NixOS modules (nginx, sops, borgbackup, lego, promtail, etc.)
@@ -87,6 +87,7 @@ utils/pkgs/<package-name>/
## Conventions
- 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
- 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/modules/sops.nix
./utils/modules/nginx.nix
./utils/modules/set-nix-channel.nix
./modules/mysql.nix
./modules/web/stack.nix
./modules/laravel-storage.nix
./modules/laravel-scheduler.nix
./modules/blackbox-exporter.nix
./modules/postfix.nix
./modules/rspamd.nix
@@ -67,7 +69,7 @@
networking.firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 ];
allowedTCPPorts = [ 22 80 443 3306 ];
# Allow MariaDB access only from specific IP
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
, ...
}:
{
# Header checks file for validating email headers
environment.etc."postfix/header_checks".text = ''
let
headerChecksFile = pkgs.writeText "header_checks" ''
# Warn about missing critical headers (but don't reject from localhost)
# These help identify misconfigured applications
/^$/ WARN Missing headers detected
'';
in
{
services.postfix = {
mapFiles."header_checks" = headerChecksFile;
enable = true;
hostname = "amzebs-01.amz.at";
domain = "amz.at";
@@ -34,20 +35,20 @@
compatibility_level = "2";
# 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
mailbox_size_limit = "202400000"; # ~200MB
message_size_limit = "51200000"; # ~50MB
mailbox_size_limit = 202400000; # ~200MB
message_size_limit = 51200000; # ~50MB
# Ensure proper header handling
# 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
# Allow reasonable sending rates for applications
smtpd_client_message_rate_limit = "100";
smtpd_client_recipient_rate_limit = "200";
smtpd_client_message_rate_limit = 100;
smtpd_client_recipient_rate_limit = 200;
# 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

@@ -10,6 +10,7 @@
./utils/modules/victoriametrics
./utils/modules/promtail
./utils/modules/borgbackup.nix
./utils/modules/set-nix-channel.nix
# fw
./modules/network-prefix.nix
@@ -103,7 +104,7 @@
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.defaultSopsFile = ./secrets.yaml;

View File

@@ -19,21 +19,19 @@
};
# n8n service configuration
services.n8n = {
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
};
};
services.n8n.enable = true;
# Configure git integration via environment variables
# Configure n8n via environment variables
systemd.services.n8n = {
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_VERSION_NOTIFICATIONS_ENABLED = "false";
N8N_DIAGNOSTICS_ENABLED = "false";

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 = { };
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
'';
systemd.services.dovecot2 = {
systemd.services.dovecot = {
wants = [ "acme-imap.${domain}.service" ];
after = [ "acme-imap.${domain}.service" ];
};
@@ -257,7 +257,7 @@ in
"imap-test.${domain}"
"imap-02.${domain}"
];
postRun = "systemctl --no-block restart dovecot2.service";
postRun = "systemctl --no-block restart dovecot.service";
};
networking.firewall.allowedTCPPorts = [

View File

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

View File

@@ -128,16 +128,16 @@ in
compatibility_level = "2";
# bigger attachement size
mailbox_size_limit = "202400000";
message_size_limit = "51200000";
mailbox_size_limit = 202400000;
message_size_limit = 51200000;
smtpd_helo_required = "yes";
smtpd_delay_reject = "yes";
strict_rfc821_envelopes = "yes";
# send Limit
smtpd_error_sleep_time = "1s";
smtpd_soft_error_limit = "10";
smtpd_hard_error_limit = "20";
smtpd_soft_error_limit = 10;
smtpd_hard_error_limit = 20;
smtpd_use_tls = "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_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_session_cache_database = ''btree:''${data_directory}/smtpd_scache'';
smtpd_tls_mandatory_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
smtpd_tls_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
smtpd_tls_mandatory_ciphers = "medium";
tls_medium_cipherlist = "AES128+EECDH:AES128+EDH";
tls_medium_cipherlist = "ECDHE+AESGCM:DHE+AESGCM:ECDHE+CHACHA20:DHE+CHACHA20";
# authentication
smtpd_sasl_auth_enable = "yes";
@@ -225,8 +224,7 @@ in
security.dhparams = {
enable = true;
params.postfix512.bits = 512;
params.postfix2048.bits = 1024;
params.postfix2048.bits = 2048;
};
security.acme.certs."mail.${domain}" = {

View File

@@ -119,7 +119,7 @@ in
# systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "redis-rspamd" ];
systemd.services.dovecot2.preStart = ''
systemd.services.dovecot.preStart = ''
mkdir -p /var/lib/dovecot/sieve/
for i in ${sieve-spam-filter}/share/sieve-rspamd-filter/*.sieve; do
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,6 +9,7 @@ in {
"${impermanence}/nixos.nix"
./utils/bento.nix
./utils/modules/sops.nix
./utils/modules/set-nix-channel.nix
./utils/modules/victoriametrics
./utils/modules/promtail
@@ -76,6 +77,12 @@ in {
];
};
# System packages
environment.systemPackages = with pkgs; [
vim
screen
];
# Nix settings
nix = {
settings = {

View File

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

View File

@@ -9,6 +9,10 @@ let
disks = [
"/dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52TBSB"
"/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";
@@ -44,8 +48,8 @@ let
device=$(readlink -f "$disk")
short_name=$(basename "$device")
# Extract serial from disk ID for labels
serial=$(basename "$disk" | sed 's/ata-ST18000NM000J-2TV103_//')
# Extract serial from disk ID for labels (part after last underscore)
serial=$(basename "$disk" | sed 's/.*_//')
# 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")

View File

@@ -15,5 +15,10 @@
ACTION=="add", KERNEL=="sd[a-z]", SUBSYSTEM=="block", \
ATTRS{model}=="ST18000NM000J*", \
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 MG10ACA20TE*", \
RUN+="${pkgs.hdparm}/bin/hdparm -B 127 -S 180 /dev/%k"
'';
}

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]
cursor-size=24
'';

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ in {
nixpkgs.config.android_sdk.accept_license = true;
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" ];
}

View File

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

View File

@@ -1,54 +1,31 @@
local status, lspc = pcall(require, 'lspconfig')
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
-- LSP Capabilities (for nvim-cmp integration)
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
local servers = { 'ts_ls', 'lua_ls', 'cssls', 'yamlls', 'intelephense', 'gopls' }
for _, lsp in pairs(servers) do
require('lspconfig')[lsp].setup {
-- on_attach = on_attach,
-- Global LSP configuration
vim.lsp.config('*', {
capabilities = capabilities,
}
end
})
lspc.yamlls.setup({
-- Server-specific configurations
vim.lsp.config('clangd', {})
vim.lsp.config('yamlls', {
settings = {
yaml = {
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", {
pattern = "json",
callback = function(ev)
vim.bo[ev.buf].formatprg = "jq"
print("It's a json file")
end,
})
-- lspc.intelephense.setup()

View File

@@ -1,41 +1,13 @@
config = {
---@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")
local status_ok, project = pcall(require, "project")
if not status_ok then
return
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
local status_ok_nls, none_ls_module = pcall(require, "none-ls")
-- none-ls (module is still named "null-ls" for backward compatibility)
local status_ok_nls, none_ls_module = pcall(require, "null-ls")
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
local nb = none_ls_module.builtins
none_ls_module.setup({

View File

@@ -1,5 +1,3 @@
vim.g.mapleader = " "
local function smart_quit()
local bufnr = vim.api.nvim_get_current_buf()
local modified = vim.api.nvim_buf_get_option(bufnr, "modified")
@@ -27,122 +25,77 @@ end
local wk = require("which-key")
wk.setup({})
wk.register({
["<leader>"] = {
[";"] = { "<cmd>Alpha<CR>", "Dashboard" },
["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",
wk.setup({
preset = "classic",
delay = 0,
triggers = {
{ "<auto>", mode = "nxso" },
{ " ", mode = "n" }, -- literal space character
},
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(
{
["/"] = { "<Plug>(comment_toggle_linewise_visual)", "Comment toggle linewise (visual)" },
},
{
mode = "v", -- VISUAL mode
prefix = "<leader>",
buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
silent = true, -- use `silent` when creating keymaps
noremap = true, -- use `noremap` when creating keymaps
nowait = true, -- use `nowait` when creating keymaps
}
)
wk.add({
-- Single key mappings
{ "<leader>;", "<cmd>Alpha<CR>", desc = "Dashboard" },
{ "<leader>w", "<cmd>w!<CR>", desc = "Save" },
{ "<leader>q", smart_quit, desc = "Quit" },
{ "<leader>/", "<Plug>(comment_toggle_linewise_current)", desc = "Comment toggle current line" },
{ "<leader>c", "<cmd>BufferKill<CR>", desc = "Close Buffer" },
{ "<leader>f", find_project_files, desc = "Find File" },
{ "<leader>h", "<cmd>nohlsearch<CR>", desc = "No Highlight" },
{ "<leader>t", "<cmd>TodoTelescope keywords=TODO,FIX<CR>", desc = "Find TODO,FIX" },
-- 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; [
nodePackages.typescript-language-server
sumneko-lua-language-server
lua-language-server
nest
nodePackages.intelephense
nodePackages.vscode-langservers-extracted
@@ -105,9 +105,9 @@ in
"sops"
]);
in ''
lua << EOF
${luaConfig}
EOF
lua << EOF
${luaConfig}
EOF
'';
};
extraLuaPackages = luaPackages: [ luaPackages.lyaml ];

View File

@@ -1,6 +1,6 @@
{ config, pkgs, ... }:
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

View File

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

View File

@@ -15,7 +15,7 @@
datasourceUid = "vm-datasource-uid";
model = {
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;
intervalMs = 1000;
legendFormat = "__auto";

View File

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

View File

@@ -19,9 +19,9 @@ fi
HOSTNAME="$1"
# Check if 'nixos-rebuild' command is available
if ! command -v nixos-rebuild > /dev/null; then
echo "ERROR: 'nixos-rebuild' command not found. Please ensure it is installed and in your PATH." >&2
# Check if 'nix-instantiate' command is available
if ! command -v nix-instantiate > /dev/null; then
echo "ERROR: 'nix-instantiate' command not found. Please ensure Nix is installed and in your PATH." >&2
exit 1
fi
@@ -38,27 +38,42 @@ if [ ! -f "$CONFIG_PATH" ]; then
exit 1
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'..."
if [ "$VERBOSE" = true ]; then
echo "INFO: Verbose mode enabled, --show-trace will be used."
fi
# Execute nixos-rebuild dry-build
# Store the output and error streams, and the exit code
NIX_OUTPUT_ERR=$(nixos-rebuild dry-build $SHOW_TRACE_OPT -I nixos-config="$CONFIG_PATH" --show-trace 2>&1)
# Execute nix-instantiate to evaluate the configuration
# nix-instantiate fetches fresh tarballs and catches all evaluation errors
# 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=$?
# Check the exit status
if [ "$NIX_EXIT_STATUS" -eq 0 ]; then
echo "INFO: Dry-build for host '$HOSTNAME' completed successfully."
if [ "$VERBOSE" = true ]; then
echo "Output from nixos-rebuild:"
echo "Output from nix-instantiate:"
echo "$NIX_OUTPUT_ERR"
fi
exit 0
else
echo "ERROR: Dry-build for host '$HOSTNAME' failed. 'nixos-rebuild' exited with status $NIX_EXIT_STATUS." >&2
echo "Output from nixos-rebuild:" >&2
echo "ERROR: Dry-build for host '$HOSTNAME' failed. 'nix-instantiate' exited with status $NIX_EXIT_STATUS." >&2
echo "Output from nix-instantiate:" >&2
echo "$NIX_OUTPUT_ERR" >&2
exit "$NIX_EXIT_STATUS"
fi

View File

@@ -10,6 +10,7 @@ self: super: {
# Python packages
python3 = super.python3.override {
packageOverrides = pself: psuper: {
aia-chaser = pself.callPackage ../pkgs/aia-chaser { };
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 {
owner = "pyload";
repo = "pyload";
rev = "3115740a2210fd57b5d050cd0850a0e61ec493ed"; # [DdownloadCom] fix #4537
hash = "sha256-g1eEeNnr3Axtr+0BJzMcNQomTEX4EsUG1Jxt+huPyoc=";
rev = "71f2700184ee9344dc313d9833ca7a6bb36007db"; # [DdownloadCom] fix #4537
hash = "sha256-XAa+XbC3kko+zvEMZkPXRoaHAmEFGsNBDxysX+X06Jc=";
};
patches = [
@@ -16,6 +16,7 @@ pyload-ng.overridePythonAttrs (oldAttrs: rec {
# Add new dependencies required in newer versions
propagatedBuildInputs = (oldAttrs.propagatedBuildInputs or []) ++ (with python3Packages; [
aia-chaser
mini-racer
packaging
pydantic

View File

@@ -7,9 +7,10 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Script and repo directories
cd "$(dirname "${BASH_SOURCE[0]}")"
PKG_DIR="$(pwd)"
REPO_ROOT="$(cd ../../.. && pwd)"
# Check if commit SHA is provided
if [ $# -ne 1 ]; then
@@ -34,7 +35,7 @@ fi
echo -e "${GREEN}==> Updating pyload-ng to commit: ${COMMIT_SHA}${NC}"
# File to update
PKG_FILE="$REPO_ROOT/utils/pkgs/pyload-ng-updated.nix"
PKG_FILE="$PKG_DIR/default.nix"
if [ ! -f "$PKG_FILE" ]; then
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
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
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
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"
else
echo -e "${RED}Error: Build verification failed${NC}"
@@ -83,7 +85,6 @@ fi
# Step 6: Test configuration for fw host (which uses pyload)
echo -e "${YELLOW}Step 6: Testing fw configuration...${NC}"
cd "$REPO_ROOT"
if ./scripts/test-configuration fw > /dev/null 2>&1; then
echo " ✓ Configuration test passed"
else