lol

ddclient nixos module: follow best practice for running daemons

Couple of changes:

- move home to /var/lib/ddclient so we can enable ProtectSystem=full
- do not stick binary into systemPackages as it will only run as a daemon
- run as dedicated user/group
- document why we cannot run as type=forking (output is swallowed)
- secure things by running with ProtectSystem and PrivateTmp
- .pid file goes into /run/ddclient
- let nix create the home directory instead of handling it manually
- make the interval configurable

+55 -43
+55 -43
nixos/modules/services/networking/ddclient.nix
··· 1 1 { config, pkgs, lib, ... }: 2 2 3 3 let 4 + cfg = config.services.ddclient; 5 + boolToStr = bool: if bool then "yes" else "no"; 4 6 5 - inherit (lib) mkOption mkIf singleton; 6 - inherit (pkgs) ddclient; 7 + configText = '' 8 + # This file can be used as a template for configFile or is automatically generated by Nix options. 9 + daemon=${toString cfg.interval} 10 + cache=${cfg.homeDir}/ddclient.cache 11 + pid=/run/ddclient/ddclient.pid 12 + foreground=NO 13 + use=${cfg.use} 14 + login=${cfg.username} 15 + password=${cfg.password} 16 + protocol=${cfg.protocol} 17 + ${let server = cfg.server; in 18 + lib.optionalString (server != "") "server=${server}"} 19 + ssl=${boolToStr cfg.ssl} 20 + wildcard=YES 21 + quiet=${boolToStr cfg.quiet} 22 + verbose=${boolToStr cfg.verbose} 23 + ${cfg.domain} 24 + ${cfg.extraConfig} 25 + ''; 7 26 8 - stateDir = "/var/spool/ddclient"; 9 - ddclientUser = "ddclient"; 10 - ddclientFlags = "-foreground -file ${config.services.ddclient.configFile}"; 11 - ddclientPIDFile = "${stateDir}/ddclient.pid"; 27 + in 12 28 13 - in 29 + with lib; 14 30 15 31 { 16 32 ··· 28 44 ''; 29 45 }; 30 46 47 + homeDir = mkOption { 48 + default = "/var/lib/ddclient"; 49 + type = str; 50 + description = "Home directory for the daemon user."; 51 + }; 52 + 31 53 domain = mkOption { 32 54 default = ""; 33 55 type = str; ··· 50 72 description = '' 51 73 Password. WARNING: The password becomes world readable in the Nix store. 52 74 ''; 75 + }; 76 + 77 + interval = mkOption { 78 + default = 600; 79 + type = int; 80 + description = "The interval at which to run the check and update."; 53 81 }; 54 82 55 83 configFile = mkOption { ··· 126 154 127 155 config = mkIf config.services.ddclient.enable { 128 156 129 - environment.systemPackages = [ ddclient ]; 157 + users = { 158 + extraGroups.ddclient.gid = config.ids.gids.ddclient; 130 159 131 - users.extraUsers = singleton { 132 - name = ddclientUser; 133 - uid = config.ids.uids.ddclient; 134 - description = "ddclient daemon user"; 135 - home = stateDir; 160 + extraUsers.ddclient = { 161 + uid = config.ids.uids.ddclient; 162 + description = "ddclient daemon user"; 163 + group = "ddclient"; 164 + home = cfg.homeDir; 165 + createHome = true; 166 + }; 136 167 }; 137 168 138 169 environment.etc."ddclient.conf" = { 139 - enable = config.services.ddclient.configFile == "/etc/ddclient.conf"; 170 + enable = cfg.configFile == "/etc/ddclient.conf"; 140 171 uid = config.ids.uids.ddclient; 172 + gid = config.ids.gids.ddclient; 141 173 mode = "0600"; 142 - text = '' 143 - # This file can be used as a template for configFile or is automatically generated by Nix options. 144 - daemon=600 145 - cache=${stateDir}/ddclient.cache 146 - pid=${ddclientPIDFile} 147 - use=${config.services.ddclient.use} 148 - login=${config.services.ddclient.username} 149 - password=${config.services.ddclient.password} 150 - protocol=${config.services.ddclient.protocol} 151 - ${let server = config.services.ddclient.server; in 152 - lib.optionalString (server != "") "server=${server}"} 153 - ssl=${if config.services.ddclient.ssl then "yes" else "no"} 154 - wildcard=YES 155 - quiet=${if config.services.ddclient.quiet then "yes" else "no"} 156 - verbose=${if config.services.ddclient.verbose then "yes" else "no"} 157 - ${config.services.ddclient.domain} 158 - ${config.services.ddclient.extraConfig} 159 - ''; 174 + text = configText; 160 175 }; 161 176 162 177 systemd.services.ddclient = { ··· 166 181 restartTriggers = [ config.environment.etc."ddclient.conf".source ]; 167 182 168 183 serviceConfig = { 169 - # Uncomment this if too many problems occur: 170 - # Type = "forking"; 171 - User = ddclientUser; 172 - Group = "nogroup"; #TODO get this to work 173 - PermissionsStartOnly = "true"; 174 - PIDFile = ddclientPIDFile; 175 - ExecStartPre = '' 176 - ${pkgs.stdenv.shell} -c "${pkgs.coreutils}/bin/mkdir -m 0755 -p ${stateDir} && ${pkgs.coreutils}/bin/chown ${ddclientUser} ${stateDir}" 177 - ''; 178 - ExecStart = "${ddclient}/bin/ddclient ${ddclientFlags}"; 179 - #ExecStartPost = "${pkgs.coreutils}/bin/rm -r ${stateDir}"; # Should we have this? 184 + RuntimeDirectory = "ddclient"; 185 + # we cannot run in forking mode as it swallows all the program output 186 + Type = "simple"; 187 + User = "ddclient"; 188 + Group = "ddclient"; 189 + ExecStart = "${lib.getBin pkgs.ddclient}/bin/ddclient -foreground -file ${cfg.configFile}"; 190 + ProtectSystem = "full"; 191 + PrivateTmp = true; 180 192 }; 181 193 }; 182 194 };