1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.redmine;
7
8 bundle = "${pkgs.redmine}/share/redmine/bin/bundle";
9
10 databaseYml = pkgs.writeText "database.yml" ''
11 production:
12 adapter: ${cfg.database.type}
13 database: ${cfg.database.name}
14 host: ${cfg.database.host}
15 port: ${toString cfg.database.port}
16 username: ${cfg.database.user}
17 password: #dbpass#
18 '';
19
20 configurationYml = pkgs.writeText "configuration.yml" ''
21 default:
22 scm_subversion_command: ${pkgs.subversion}/bin/svn
23 scm_mercurial_command: ${pkgs.mercurial}/bin/hg
24 scm_git_command: ${pkgs.gitAndTools.git}/bin/git
25 scm_cvs_command: ${pkgs.cvs}/bin/cvs
26 scm_bazaar_command: ${pkgs.bazaar}/bin/bzr
27 scm_darcs_command: ${pkgs.darcs}/bin/darcs
28
29 ${cfg.extraConfig}
30 '';
31
32in
33
34{
35 options = {
36 services.redmine = {
37 enable = mkOption {
38 type = types.bool;
39 default = false;
40 description = "Enable the Redmine service.";
41 };
42
43 user = mkOption {
44 type = types.str;
45 default = "redmine";
46 description = "User under which Redmine is ran.";
47 };
48
49 group = mkOption {
50 type = types.str;
51 default = "redmine";
52 description = "Group under which Redmine is ran.";
53 };
54
55 stateDir = mkOption {
56 type = types.str;
57 default = "/var/lib/redmine";
58 description = "The state directory, logs and plugins are stored here.";
59 };
60
61 extraConfig = mkOption {
62 type = types.lines;
63 default = "";
64 description = ''
65 Extra configuration in configuration.yml.
66
67 See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration
68 '';
69 };
70
71 database = {
72 type = mkOption {
73 type = types.enum [ "mysql2" "postgresql" ];
74 example = "postgresql";
75 default = "mysql2";
76 description = "Database engine to use.";
77 };
78
79 host = mkOption {
80 type = types.str;
81 default = "127.0.0.1";
82 description = "Database host address.";
83 };
84
85 port = mkOption {
86 type = types.int;
87 default = 3306;
88 description = "Database host port.";
89 };
90
91 name = mkOption {
92 type = types.str;
93 default = "redmine";
94 description = "Database name.";
95 };
96
97 user = mkOption {
98 type = types.str;
99 default = "redmine";
100 description = "Database user.";
101 };
102
103 password = mkOption {
104 type = types.str;
105 default = "";
106 description = ''
107 The password corresponding to <option>database.user</option>.
108 Warning: this is stored in cleartext in the Nix store!
109 Use <option>database.passwordFile</option> instead.
110 '';
111 };
112
113 passwordFile = mkOption {
114 type = types.nullOr types.path;
115 default = null;
116 example = "/run/keys/redmine-dbpassword";
117 description = ''
118 A file containing the password corresponding to
119 <option>database.user</option>.
120 '';
121 };
122 };
123 };
124 };
125
126 config = mkIf cfg.enable {
127
128 assertions = [
129 { assertion = cfg.database.passwordFile != null || cfg.database.password != "";
130 message = "either services.redmine.database.passwordFile or services.redmine.database.password must be set";
131 }
132 ];
133
134 environment.systemPackages = [ pkgs.redmine ];
135
136 systemd.services.redmine = {
137 after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ];
138 wantedBy = [ "multi-user.target" ];
139 environment.HOME = "${pkgs.redmine}/share/redmine";
140 environment.RAILS_ENV = "production";
141 environment.RAILS_CACHE = "${cfg.stateDir}/cache";
142 environment.REDMINE_LANG = "en";
143 environment.SCHEMA = "${cfg.stateDir}/cache/schema.db";
144 path = with pkgs; [
145 imagemagickBig
146 bazaar
147 cvs
148 darcs
149 gitAndTools.git
150 mercurial
151 subversion
152 ];
153 preStart = ''
154 # start with a fresh config directory every time
155 rm -rf ${cfg.stateDir}/config
156 cp -r ${pkgs.redmine}/share/redmine/config.dist ${cfg.stateDir}/config
157
158 # create the basic state directory layout pkgs.redmine expects
159 mkdir -p /run/redmine
160
161 for i in config files log plugins tmp; do
162 mkdir -p ${cfg.stateDir}/$i
163 ln -fs ${cfg.stateDir}/$i /run/redmine/$i
164 done
165
166 # ensure cache directory exists for db:migrate command
167 mkdir -p ${cfg.stateDir}/cache
168
169 # link in the application configuration
170 ln -fs ${configurationYml} ${cfg.stateDir}/config/configuration.yml
171
172 chmod -R ug+rwX,o-rwx+x ${cfg.stateDir}/
173
174 # handle database.passwordFile
175 DBPASS=$(head -n1 ${cfg.database.passwordFile})
176 cp -f ${databaseYml} ${cfg.stateDir}/config/database.yml
177 sed -e "s,#dbpass#,$DBPASS,g" -i ${cfg.stateDir}/config/database.yml
178 chmod 440 ${cfg.stateDir}/config/database.yml
179
180 # generate a secret token if required
181 if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then
182 ${bundle} exec rake generate_secret_token
183 chmod 440 ${cfg.stateDir}/config/initializers/secret_token.rb
184 fi
185
186 # ensure everything is owned by ${cfg.user}
187 chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
188
189 ${bundle} exec rake db:migrate
190 ${bundle} exec rake redmine:load_default_data
191 '';
192
193 serviceConfig = {
194 PermissionsStartOnly = true; # preStart must be run as root
195 Type = "simple";
196 User = cfg.user;
197 Group = cfg.group;
198 TimeoutSec = "300";
199 WorkingDirectory = "${pkgs.redmine}/share/redmine";
200 ExecStart="${bundle} exec rails server webrick -e production -P ${cfg.stateDir}/redmine.pid";
201 };
202
203 };
204
205 users.extraUsers = optionalAttrs (cfg.user == "redmine") (singleton
206 { name = "redmine";
207 group = cfg.group;
208 home = cfg.stateDir;
209 createHome = true;
210 uid = config.ids.uids.redmine;
211 });
212
213 users.extraGroups = optionalAttrs (cfg.group == "redmine") (singleton
214 { name = "redmine";
215 gid = config.ids.gids.redmine;
216 });
217
218 warnings = optional (cfg.database.password != "")
219 ''config.services.redmine.database.password will be stored as plaintext
220 in the Nix store. Use database.passwordFile instead.'';
221
222 # Create database passwordFile default when password is configured.
223 services.redmine.database.passwordFile =
224 (mkDefault (toString (pkgs.writeTextFile {
225 name = "redmine-database-password";
226 text = cfg.database.password;
227 })));
228
229 };
230
231}