1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.gale;
7 # we convert the path to a string to avoid it being copied to the nix store,
8 # otherwise users could read the private key as all files in the store are
9 # world-readable
10 keyPath = toString cfg.keyPath;
11 # ...but we refer to the pubkey file using a path so that we can ensure the
12 # config gets rebuilt if the public key changes (we can assume the private key
13 # will never change without the public key having changed)
14 gpubFile = cfg.keyPath + "/${cfg.domain}.gpub";
15 home = "/var/lib/gale";
16 keysPrepared = cfg.keyPath != null && lib.pathExists cfg.keyPath;
17in
18{
19 options = {
20 services.gale = {
21 enable = mkEnableOption "the Gale messaging daemon";
22
23 user = mkOption {
24 default = "gale";
25 type = types.str;
26 description = "Username for the Gale daemon.";
27 };
28
29 group = mkOption {
30 default = "gale";
31 type = types.str;
32 description = "Group name for the Gale daemon.";
33 };
34
35 setuidWrapper = mkOption {
36 default = null;
37 description = "Configuration for the Gale gksign setuid wrapper.";
38 };
39
40 domain = mkOption {
41 default = "";
42 type = types.str;
43 description = "Domain name for the Gale system.";
44 };
45
46 keyPath = mkOption {
47 default = null;
48 type = types.nullOr types.path;
49 description = ''
50 Directory containing the key pair for this Gale domain. The expected
51 filename will be taken from the domain option with ".gpri" and ".gpub"
52 appended.
53 '';
54 };
55
56 extraConfig = mkOption {
57 type = types.lines;
58 default = "";
59 description = ''
60 Additional text to be added to <filename>/etc/gale/conf</filename>.
61 '';
62 };
63 };
64 };
65
66 config = mkMerge [
67 (mkIf cfg.enable {
68 assertions = [{
69 assertion = cfg.domain != "";
70 message = "A domain must be set for Gale.";
71 }];
72
73 warnings = mkIf (!keysPrepared) [
74 "You must run gale-install in order to generate a domain key."
75 ];
76
77 system.activationScripts.gale = mkIf cfg.enable (
78 stringAfter [ "users" "groups" ] ''
79 chmod 755 ${home}
80 mkdir -m 0777 -p ${home}/auth/cache
81 mkdir -m 1777 -p ${home}/auth/local # GALE_DOMAIN.gpub
82 mkdir -m 0700 -p ${home}/auth/private # ROOT.gpub
83 mkdir -m 0755 -p ${home}/auth/trusted # ROOT
84 mkdir -m 0700 -p ${home}/.gale
85 mkdir -m 0700 -p ${home}/.gale/auth
86 mkdir -m 0700 -p ${home}/.gale/auth/private # GALE_DOMAIN.gpri
87
88 ln -sf ${pkgs.gale}/etc/gale/auth/trusted/ROOT "${home}/auth/trusted/ROOT"
89 chown ${cfg.user}:${cfg.group} ${home} ${home}/auth ${home}/auth/*
90 chown ${cfg.user}:${cfg.group} ${home}/.gale ${home}/.gale/auth ${home}/.gale/auth/private
91 ''
92 );
93
94 environment = {
95 etc = {
96 "gale/auth".source = home + "/auth"; # symlink /var/lib/gale/auth
97 "gale/conf".text = ''
98 GALE_USER ${cfg.user}
99 GALE_DOMAIN ${cfg.domain}
100 ${cfg.extraConfig}
101 '';
102 };
103
104 systemPackages = [ pkgs.gale ];
105 };
106
107 users.extraUsers = [{
108 name = cfg.user;
109 description = "Gale daemon";
110 uid = config.ids.uids.gale;
111 group = cfg.group;
112 home = home;
113 createHome = true;
114 }];
115
116 users.extraGroups = [{
117 name = cfg.group;
118 gid = config.ids.gids.gale;
119 }];
120 })
121 (mkIf (cfg.enable && keysPrepared) {
122 assertions = [
123 {
124 assertion = cfg.keyPath != null
125 && lib.pathExists (cfg.keyPath + "/${cfg.domain}.gpub");
126 message = "Couldn't find a Gale public key for ${cfg.domain}.";
127 }
128 {
129 assertion = cfg.keyPath != null
130 && lib.pathExists (cfg.keyPath + "/${cfg.domain}.gpri");
131 message = "Couldn't find a Gale private key for ${cfg.domain}.";
132 }
133 ];
134
135 services.gale.setuidWrapper = {
136 program = "gksign";
137 source = "${pkgs.gale}/bin/gksign";
138 owner = cfg.user;
139 group = cfg.group;
140 setuid = true;
141 setgid = false;
142 };
143
144 security.wrappers.gksign = cfg.setuidWrapper;
145
146 systemd.services.gale-galed = {
147 description = "Gale messaging daemon";
148 wantedBy = [ "multi-user.target" ];
149 wants = [ "gale-gdomain.service" ];
150 after = [ "network.target" ];
151
152 preStart = ''
153 install -m 0640 -o ${cfg.user} -g ${cfg.group} ${keyPath}/${cfg.domain}.gpri "${home}/.gale/auth/private/"
154 install -m 0644 -o ${cfg.user} -g ${cfg.group} ${gpubFile} "${home}/.gale/auth/private/${cfg.domain}.gpub"
155 install -m 0644 -o ${cfg.user} -g ${cfg.group} ${gpubFile} "${home}/auth/local/${cfg.domain}.gpub"
156 '';
157
158 serviceConfig = {
159 Type = "forking";
160 ExecStart = "@${pkgs.gale}/bin/galed galed";
161 User = cfg.user;
162 Group = cfg.group;
163 PermissionsStartOnly = true;
164 };
165 };
166
167 systemd.services.gale-gdomain = {
168 description = "Gale AKD daemon";
169 wantedBy = [ "multi-user.target" ];
170 requires = [ "gale-galed.service" ];
171 after = [ "gale-galed.service" ];
172
173 serviceConfig = {
174 Type = "forking";
175 ExecStart = "@${pkgs.gale}/bin/gdomain gdomain";
176 User = cfg.user;
177 Group = cfg.group;
178 };
179 };
180 })
181 ];
182}