From 7da6c0c601d276340fada02d4bd45080d927a16b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 21 Jan 2026 15:17:10 +0000 Subject: fix(nix): use separate setup service to create dataDir before namespace setup The main service uses ReadWritePaths for security hardening, but systemd requires these paths to exist BEFORE setting up the mount namespace. ExecStartPre runs AFTER namespace setup, so it cannot create the directories. This fix adds a separate oneshot setup service (ngit-grasp-{name}-setup) that: - Runs before the main service without namespace restrictions - Creates dataDir and subdirectories (git/, relay/) with mkdir -p - Sets proper ownership (user:group) and permissions (750) - Uses RemainAfterExit so it only runs once per boot The main service now depends on the setup service via requires/after. Fixes: 'Failed to set up mount namespacing: /path: No such file or directory' --- nix/module.nix | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/nix/module.nix b/nix/module.nix index e192f95..4a6fc94 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -283,10 +283,27 @@ let }; }; + # Create systemd setup service to ensure directories exist before main service + # This runs without namespace restrictions so it can create directories + # that ReadWritePaths needs to exist before namespace setup + mkSetupService = name: cfg: { + description = "Create data directories for ngit-grasp (${name})"; + before = [ "ngit-grasp-${name}.service" ]; + requiredBy = [ "ngit-grasp-${name}.service" ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = + "${pkgs.bash}/bin/bash -c '${pkgs.coreutils}/bin/mkdir -p \"${cfg.dataDir}/git\" \"${cfg.dataDir}/relay\" && ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} \"${cfg.dataDir}\" && ${pkgs.coreutils}/bin/chmod 750 \"${cfg.dataDir}\" \"${cfg.dataDir}/git\" \"${cfg.dataDir}/relay\"'"; + }; + }; + # Create systemd service config for an instance mkService = name: cfg: { description = "ngit-grasp GRASP relay (${name})"; - after = [ "network.target" ]; + after = [ "network.target" "ngit-grasp-${name}-setup.service" ]; + requires = [ "ngit-grasp-${name}-setup.service" ]; wantedBy = [ "multi-user.target" ]; environment = { @@ -341,19 +358,8 @@ let # Working directory where .relay-owner.nsec will be created if needed WorkingDirectory = cfg.dataDir; - # Ensure data directories exist before service starts - # The + prefix runs these commands as root - # This is necessary because tmpfiles.rules aren't automatically executed - # during nixos-rebuild switch, causing service failures with custom dataDirs - ExecStartPre = [ - "+${pkgs.coreutils}/bin/mkdir -p '${cfg.dataDir}'" - "+${pkgs.coreutils}/bin/mkdir -p '${cfg.dataDir}/git'" - "+${pkgs.coreutils}/bin/mkdir -p '${cfg.dataDir}/relay'" - "+${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} '${cfg.dataDir}'" - "+${pkgs.coreutils}/bin/chmod 750 '${cfg.dataDir}'" - "+${pkgs.coreutils}/bin/chmod 750 '${cfg.dataDir}/git'" - "+${pkgs.coreutils}/bin/chmod 750 '${cfg.dataDir}/relay'" - ]; + # Directory creation is handled by ngit-grasp-${name}-setup.service + # which runs before this service and creates dataDir with proper ownership # Add git, openssh, and coreutils to PATH for purgatory sync operations Environment = @@ -453,9 +459,12 @@ in { users.groups.ngit-grasp = { }; # Create systemd services for all enabled instances - systemd.services = mapAttrs' + # Each instance has a setup service (creates directories) and main service + systemd.services = (mapAttrs' (name: cfg: nameValuePair "ngit-grasp-${name}" (mkService name cfg)) - enabledInstances; + enabledInstances) // (mapAttrs' (name: cfg: + nameValuePair "ngit-grasp-${name}-setup" (mkSetupService name cfg)) + enabledInstances); # Create data directories with proper ownership using tmpfiles # This runs as root before the service starts -- cgit v1.2.3