how to integrate a daemon into nixos
in this short article i want to show how services can be added/used in nixos. i personally find the nixos way a quite easy and ‘clean’ approach!
as i integrated cntlm my goals were:
- easy to use
- password must be stored in a safe place
- service should not be run as root but as a special user: cntlm
- cntlm/default.nix
the script which describes the software cntlm - nixos/modules/services/networking/cntlm.nix
the script which descirbes the service
- /etc/nixos/configuration.nix
the place where all the configuration happens
/etc/nixos/nixpkgs/pkgs/tools/networking/cntlm/default.nix
this nix expression describes the cntlm software and is quite simple as it basically fetches the software/compiles it/installs it into the system. however, a user does not have to install this software using:
- nix-env -i cntlm
1 { stdenv, fetchurl, which}:
2
3 stdenv.mkDerivation {
4 name = "cntlm-0.35.1";
5
6 src = fetchurl {
7 url = mirror://sourceforge/cntlm/cntlm-0.35.1.tar.gz;
8 sha256 = "7b3fb7184e72cc3f1743bb8e503a5305e96458bc630a7e1ebfc9f3c07ffa6c5e";
9 };
10
11 buildInputs = [ which ];
12
13 installPhase = ''
14 ensureDir $out/bin; cp cntlm $out/bin/;
15 ensureDir $out/share/; cp COPYRIGHT README VERSION doc/cntlm.conf $out/share/;
16 ensureDir $out/man/; cp doc/cntlm.1 $out/man/;
17 '';
18
19 meta = {
20 description = "Cntlm is an NTLM/NTLMv2 authenticating HTTP proxy";
21 homepage = http://cntlm.sourceforge.net/;
22 license = stdenv.lib.licenses.gpl2;
23 maintainers = [ stdenv.lib.maintainers.qknight ];
24 };
25 }
the only point of interest might be the buildInputs (line 11) which includes which. in this build script ‘which gcc’ is used to test if gcc is installed.
/etc/nixos/nixos/modules/services/networking/cntlm.nix
this expression is used to integrate cntlm as a system service.
1 { config, pkgs, ... }:
2
3 with pkgs.lib;
4
5 let
6
7 cfg = config.services.cntlm;
8 uid = config.ids.uids.cntlm;
9
10 in
11
12 {
13
14 options = {
15
16 services.cntlm= {
17
18 enable = mkOption {
19 default = false;
20 description = ''
21 Whether to enable the cntlm, which start a local proxy.
22 '';
23 };
24
25 username = mkOption {
26 description = ''
27 Proxy account name, without the possibility to include domain name ('at' sign is interpreted literally).
28 '';
29 };
30
31 domain = mkOption {
32 description = ''Proxy account domain/workgroup name.'';
33 };
34
35 password = mkOption {
36 default = "/etc/cntlm.password";
37 type = with pkgs.lib.types; string;
38 description = ''Proxy account password. Note: use chmod 0600 on /etc/cntlm.password for security.'';
39 };
40
41 netbios_hostname = mkOption {
42 default = config.networking.hostName;
43 description = ''
44 The hostname of your workstation.
45 '';
46 };
47
48 proxy = mkOption {
49 description = ''
50 A list of NTLM/NTLMv2 authenticating HTTP proxies.
51
52 Parent proxy, which requires authentication. The same as proxy on the command-line, can be used more than once to specify unlimited
53 number of proxies. Should one proxy fail, cntlm automatically moves on to the next one. The connect request fails only if the whole
54 list of proxies is scanned and (for each request) and found to be invalid. Command-line takes precedence over the configuration file.
55 '';
56 };
57
58 port = mkOption {
59 default = [3128];
60 description = "Specifies on which ports the cntlm daemon listens.";
61 };
62
63 extraConfig = mkOption {
64 default = "";
65 description = "Verbatim contents of cntlm.conf.";
66 };
67
68 };
69
70 };
71
72
73 ###### implementation
74
75 config = mkIf config.services.cntlm.enable {
76 users.extraUsers = singleton {
77 name = "cntlm";
78 description = "cntlm system-wide daemon";
79 home = "/var/empty";
80 };
81
82 jobs.cntlm = {
83 description = "cntlm is an NTLM / NTLM Session Response / NTLMv2 authenticating HTTP proxy.";
84 startOn = "started network-interfaces";
85 environment = {
86 };
87
88 preStart = '' '';
89
90 daemonType = "fork";
91
92 exec =
93 ''
94 ${pkgs.cntlm}/bin/cntlm -U cntlm \
95 -c ${pkgs.writeText "cntlm_config" cfg.extraConfig}
96 '';
97 };
98
99 services.cntlm.extraConfig =
100 ''
101 # Cntlm Authentication Proxy Configuration
102 Username ${cfg.username}
103 Domain ${cfg.domain}
104 Password ${cfg.password}
105 Workstation ${cfg.netbios_hostname}
106 ${concatMapStrings (entry: "Proxy ${entry}\n") cfg.proxy}
107
108 ${concatMapStrings (port: ''
109 Listen ${toString port}
110 '') cfg.port}
111 '';
112 };
113 }
notable parts are:
- (line 1-13) if interested in general nix language: read [1] page 6,7 (and 5 might also be interesting)
- (line 18-66) where a list of options is declared (some with default arguments; some without default argument which will enforce the user to set them)
- (line 76-79) where the service is started as a different user (security measure)
- (line 92-97) where the configuration is generated on the fly (yes cntlm.conf is generated everytime the configuration in /etc/nixos/configuration.nix is changed). a user using the cntlm service on nixos does not change the cntlm.conf manually)
- (line 99-111) where a minimal configuration (extract of the example cntlm.conf) is parameterized.
- (line 106) where a list of items is transformed into a multi line structure where:
cfg.proxy = [ "foo" "bar" "baz" ];
is transformed into:
Proxy foo
Proxy bar
Proxy baz
how to make use of the above expressions
a user has to append this configuration into /etc/nixos/configuration.nix and cntlm will be installed/configured and started
services.cntlm = {
enable=true;
username="myusername";
domain="mydomain";
proxy=[ "192.168.3.5:1234" ];
};
summary
in contrast to most other distributions nixos makes not only packaging subject to a ‘clean’ package management but also configuration management (/etc stuff) and runtime management. this is a very clean design helping to avoid lots of pitfalls.
links
[1] http://www.st.ewi.tudelft.nl/~dolstra/pubs/nixos-jfp-final.pdf