Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions named-hosts/matic/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ inputs.nixpkgs.lib.nixosSystem {
# Pin kernel to 6.18 for CrowdStrike Falcon compatibility (RFM on 6.19)
boot.kernelPackages = pkgs.linuxPackages_6_18;

# Filesystem hardening
boot.kernel.sysctl = {
"fs.protected_regular" = 2;
"fs.protected_fifos" = 2;
"fs.protected_symlinks" = 1;
"fs.protected_hardlinks" = 1;
};

# AMD power management kernel params
boot.kernelParams = [
"amdgpu.abmlevel=3" # auto backlight management
Expand Down Expand Up @@ -88,6 +96,18 @@ inputs.nixpkgs.lib.nixosSystem {
virtualisation.docker.enable = true;

security.sudo.wheelNeedsPassword = false;

# Immutable root — prevents rm -rf / by blocking top-level entry removal
systemd.services.immutable-root = {
description = "Set immutable flag on /";
wantedBy = [ "multi-user.target" ];
Comment on lines +100 to +103

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment/PR intent says this “prevents rm -rf /”, but chattr +i / only prevents creating/removing top-level entries under / (e.g., /etc itself). It does not prevent deletion of contents inside existing directories (e.g., rm -rf --no-preserve-root /etc/* would still succeed), so this provides a false sense of protection. Please either (a) reword the comment (and PR description/test plan) to match the actual behavior, or (b) change the approach if the goal is to prevent destructive recursive deletion of the filesystem contents.

Copilot uses AI. Check for mistakes.
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${pkgs.e2fsprogs}/bin/chattr +i /";
ExecStop = "${pkgs.e2fsprogs}/bin/chattr -i /";
};
Comment on lines +101 to +109

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In systemd, if a service is only WantedBy=multi-user.targetand has noAfter=dependency, can it start in parallel with other units wanted bymulti-user.target?

💡 Result:

Yes, a systemd service with only WantedBy=multi-user.target and no After= (or other ordering dependencies like Before=) can start in parallel with other units that are also wanted by multi-user.target. Systemd activates all units wanted by a target (via Wants= or WantedBy=) when the target is reached. Without explicit ordering dependencies (After=/Before=), these units have no required sequence and are scheduled as parallel jobs. Systemd's design emphasizes parallel activation to speed up boot, as confirmed in official documentation: "NB: ordering and requirement dependencies are orthogonal. If only a requirement dependency exists between two units (e.g. foo.service requires bar.service), but no ordering dependency (e.g. foo.service after bar.service) and both are requested to start, they will be started in parallel." (systemd(1) man page). Units wanted by multi-user.target typically share common implicit or default dependencies (e.g., After=local-fs.target), but among themselves, without After=, they activate concurrently.

Citations:


Add explicit After= dependencies or use a later target to prevent boot failures.

The immutable-root service sets the immutable flag on / but lacks ordering constraints. With only WantedBy=multi-user.target and no After=, systemd will activate this unit in parallel with other multi-user services. Any peer service that needs to write to root-level filesystem locations will fail once the immutable bit is flipped (line 107). Either add After= edges for all root-mutating units, or move this behind a later target that executes after boot-critical services complete.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@named-hosts/matic/default.nix` around lines 101 - 109, The unit
systemd.services.immutable-root flips / immutable (ExecStart/ExecStop) but has
no ordering constraints; add an After= stanza to systemd.services.immutable-root
so it runs only after filesystem mounts and any services that may write to /
(e.g. local-fs.target and systemd-remount-fs.service, plus your distro
package/update services), or move its activation to a later target (create/use a
late-boot/graphical.target and set WantedBy accordingly) so the chattr +i runs
after boot-critical and root-mutating units complete.

Comment on lines +101 to +109

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

immutable-root is wanted by multi-user.target but has no explicit ordering. To avoid racing with activation/tmpfiles work that might still need to create top-level mountpoints/directories on first boot (or after config changes), consider ordering it after nixos-activation.service, systemd-tmpfiles-setup.service, and local-fs.target (and optionally making it requires = [ "local-fs.target" ]). This makes the hardening less likely to cause boot-time failures.

Copilot uses AI. Check for mistakes.
};
Comment on lines +100 to +110

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The current implementation using a systemd service to make the root filesystem immutable will break the standard NixOS update process (nixos-rebuild switch). When the service is active, chattr +i / prevents NixOS activation scripts from modifying files and symlinks in top-level directories (e.g., in /etc), which is a necessary part of an update. This will cause nixos-rebuild switch to fail.

A more idiomatic and robust approach in NixOS is to use system.preActivation and system.postActivation scripts. This ensures the root filesystem is automatically made mutable before an update begins and is made immutable again immediately after it completes, requiring no manual intervention.

        # Immutable root — prevents rm -rf / by blocking top-level entry removal
        # This is handled by pre/post activation scripts to allow `nixos-rebuild switch` to work.
        system.preActivation = ''
          echo "Making root mutable for system update..."
          # Don't fail if the flag isn't set (e.g., on first boot).
          ${pkgs.e2fsprogs}/bin/chattr -i / || true
        '';
        system.postActivation = ''
          echo "Making root immutable..."
          ${pkgs.e2fsprogs}/bin/chattr +i /
        '';

# Keyd configuration (Linux desktop only)
services.keyd.enable = true;
users.groups.keyd = { };
Expand Down
Loading