1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.services.mongodb;
10
11 mongodb = cfg.package;
12
13 mongoshExe = lib.getExe cfg.mongoshPackage;
14
15 mongoCnf =
16 cfg:
17 pkgs.writeText "mongodb.conf" ''
18 net.bindIp: ${cfg.bind_ip}
19 ${lib.optionalString cfg.quiet "systemLog.quiet: true"}
20 systemLog.destination: syslog
21 storage.dbPath: ${cfg.dbpath}
22 ${lib.optionalString cfg.enableAuth "security.authorization: enabled"}
23 ${lib.optionalString (cfg.replSetName != "") "replication.replSetName: ${cfg.replSetName}"}
24 ${cfg.extraConfig}
25 '';
26
27in
28
29{
30 imports = [
31 (lib.mkRemovedOptionModule [
32 "services"
33 "mongodb"
34 "initialRootPassword"
35 ] "Use services.mongodb.initialRootPasswordFile to securely provide the initial root password.")
36 ];
37
38 ###### interface
39
40 options = {
41
42 services.mongodb = {
43
44 enable = lib.mkEnableOption "the MongoDB server";
45
46 package = lib.mkPackageOption pkgs "mongodb" {
47 example = "pkgs.mongodb-ce";
48 };
49
50 mongoshPackage = lib.mkPackageOption pkgs "mongosh" { };
51
52 user = lib.mkOption {
53 type = lib.types.str;
54 default = "mongodb";
55 description = "User account under which MongoDB runs";
56 };
57
58 bind_ip = lib.mkOption {
59 type = lib.types.str;
60 default = "127.0.0.1";
61 description = "IP to bind to";
62 };
63
64 quiet = lib.mkOption {
65 type = lib.types.bool;
66 default = false;
67 description = "quieter output";
68 };
69
70 enableAuth = lib.mkOption {
71 type = lib.types.bool;
72 default = false;
73 description = "Enable client authentication. Creates a default superuser with username root!";
74 };
75
76 initialRootPasswordFile = lib.mkOption {
77 type = lib.types.nullOr lib.types.path;
78 default = null;
79 description = "Path to the file containing the password for the root user if auth is enabled.";
80 };
81
82 dbpath = lib.mkOption {
83 type = lib.types.str;
84 default = "/var/db/mongodb";
85 description = "Location where MongoDB stores its files";
86 };
87
88 pidFile = lib.mkOption {
89 type = lib.types.str;
90 default = "/run/mongodb.pid";
91 description = "Location of MongoDB pid file";
92 };
93
94 replSetName = lib.mkOption {
95 type = lib.types.str;
96 default = "";
97 description = ''
98 If this instance is part of a replica set, set its name here.
99 Otherwise, leave empty to run as single node.
100 '';
101 };
102
103 extraConfig = lib.mkOption {
104 type = lib.types.lines;
105 default = "";
106 example = ''
107 storage.journal.enabled: false
108 '';
109 description = "MongoDB extra configuration in YAML format";
110 };
111
112 initialScript = lib.mkOption {
113 type = lib.types.nullOr lib.types.path;
114 default = null;
115 description = ''
116 A file containing MongoDB statements to execute on first startup.
117 '';
118 };
119 };
120
121 };
122
123 ###### implementation
124
125 config = lib.mkIf config.services.mongodb.enable {
126 assertions = [
127 {
128 assertion = !cfg.enableAuth || cfg.initialRootPasswordFile != null;
129 message = "`enableAuth` requires `initialRootPasswordFile` to be set.";
130 }
131 ];
132
133 users.users.mongodb = lib.mkIf (cfg.user == "mongodb") {
134 name = "mongodb";
135 isSystemUser = true;
136 group = "mongodb";
137 description = "MongoDB server user";
138 };
139 users.groups.mongodb = lib.mkIf (cfg.user == "mongodb") { };
140
141 systemd.services.mongodb = {
142 description = "MongoDB server";
143
144 wantedBy = [ "multi-user.target" ];
145 after = [ "network.target" ];
146
147 serviceConfig = {
148 ExecStart = "${mongodb}/bin/mongod --config ${mongoCnf cfg} --fork --pidfilepath ${cfg.pidFile}";
149 User = cfg.user;
150 PIDFile = cfg.pidFile;
151 Type = "forking";
152 TimeoutStartSec = 120; # initial creating of journal can take some time
153 PermissionsStartOnly = true;
154 };
155
156 preStart =
157 let
158 cfg_ = cfg // {
159 enableAuth = false;
160 bind_ip = "127.0.0.1";
161 };
162 in
163 ''
164 rm ${cfg.dbpath}/mongod.lock || true
165 if ! test -e ${cfg.dbpath}; then
166 install -d -m0700 -o ${cfg.user} ${cfg.dbpath}
167 # See postStart!
168 touch ${cfg.dbpath}/.first_startup
169 fi
170 if ! test -e ${cfg.pidFile}; then
171 install -D -o ${cfg.user} /dev/null ${cfg.pidFile}
172 fi ''
173 + lib.optionalString cfg.enableAuth ''
174
175 if ! test -e "${cfg.dbpath}/.auth_setup_complete"; then
176 systemd-run --unit=mongodb-for-setup --uid=${cfg.user} ${mongodb}/bin/mongod --config ${mongoCnf cfg_}
177 # wait for mongodb
178 while ! ${mongoshExe} --eval "db.version()" > /dev/null 2>&1; do sleep 0.1; done
179
180 initialRootPassword=$(<${cfg.initialRootPasswordFile})
181 ${mongoshExe} <<EOF
182 use admin;
183 db.createUser(
184 {
185 user: "root",
186 pwd: "$initialRootPassword",
187 roles: [
188 { role: "userAdminAnyDatabase", db: "admin" },
189 { role: "dbAdminAnyDatabase", db: "admin" },
190 { role: "readWriteAnyDatabase", db: "admin" }
191 ]
192 }
193 )
194 EOF
195 touch "${cfg.dbpath}/.auth_setup_complete"
196 systemctl stop mongodb-for-setup
197 fi
198 '';
199 postStart = ''
200 if test -e "${cfg.dbpath}/.first_startup"; then
201 ${lib.optionalString (cfg.initialScript != null) ''
202 initialRootPassword=$(<${cfg.initialRootPasswordFile})
203 ${mongoshExe} ${lib.optionalString (cfg.enableAuth) "-u root -p $initialRootPassword"} admin "${cfg.initialScript}"
204 ''}
205 rm -f "${cfg.dbpath}/.first_startup"
206 fi
207 '';
208 };
209
210 };
211
212}