1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.minio;
12
13 legacyCredentials =
14 cfg:
15 pkgs.writeText "minio-legacy-credentials" ''
16 MINIO_ROOT_USER=${cfg.accessKey}
17 MINIO_ROOT_PASSWORD=${cfg.secretKey}
18 '';
19in
20{
21 meta.maintainers = with maintainers; [
22 bachp
23 ryan4yin
24 ];
25
26 options.services.minio = {
27 enable = mkEnableOption "Minio Object Storage";
28
29 listenAddress = mkOption {
30 default = ":9000";
31 type = types.str;
32 description = "IP address and port of the server.";
33 };
34
35 consoleAddress = mkOption {
36 default = ":9001";
37 type = types.str;
38 description = "IP address and port of the web UI (console).";
39 };
40
41 dataDir = mkOption {
42 default = [ "/var/lib/minio/data" ];
43 type = types.listOf (types.either types.path types.str);
44 description = "The list of data directories or nodes for storing the objects. Use one path for regular operation and the minimum of 4 endpoints for Erasure Code mode.";
45 };
46
47 configDir = mkOption {
48 default = "/var/lib/minio/config";
49 type = types.path;
50 description = "The config directory, for the access keys and other settings.";
51 };
52
53 certificatesDir = mkOption {
54 default = "/var/lib/minio/certs";
55 type = types.path;
56 description = "The directory where TLS certificates are stored.";
57 };
58
59 accessKey = mkOption {
60 default = "";
61 type = types.str;
62 description = ''
63 Access key of 5 to 20 characters in length that clients use to access the server.
64 This overrides the access key that is generated by minio on first startup and stored inside the
65 `configDir` directory.
66 '';
67 };
68
69 secretKey = mkOption {
70 default = "";
71 type = types.str;
72 description = ''
73 Specify the Secret key of 8 to 40 characters in length that clients use to access the server.
74 This overrides the secret key that is generated by minio on first startup and stored inside the
75 `configDir` directory.
76 '';
77 };
78
79 rootCredentialsFile = mkOption {
80 type = types.nullOr types.path;
81 default = null;
82 description = ''
83 File containing the MINIO_ROOT_USER, default is "minioadmin", and
84 MINIO_ROOT_PASSWORD (length >= 8), default is "minioadmin"; in the format of
85 an EnvironmentFile=, as described by {manpage}`systemd.exec(5)`.
86 '';
87 example = "/etc/nixos/minio-root-credentials";
88 };
89
90 region = mkOption {
91 default = "us-east-1";
92 type = types.str;
93 description = ''
94 The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region.
95 '';
96 };
97
98 browser = mkOption {
99 default = true;
100 type = types.bool;
101 description = "Enable or disable access to web UI.";
102 };
103
104 package = mkPackageOption pkgs "minio" { };
105 };
106
107 config = mkIf cfg.enable {
108 warnings =
109 optional ((cfg.accessKey != "") || (cfg.secretKey != ""))
110 "services.minio.`accessKey` and services.minio.`secretKey` are deprecated, please use services.minio.`rootCredentialsFile` instead.";
111
112 systemd = lib.mkMerge [
113 {
114 tmpfiles.rules = [
115 "d '${cfg.configDir}' - minio minio - -"
116 ]
117 ++ (map (x: "d '" + x + "' - minio minio - - ") (builtins.filter lib.types.path.check cfg.dataDir));
118
119 services.minio = {
120 description = "Minio Object Storage";
121 wants = [ "network-online.target" ];
122 after = [ "network-online.target" ];
123 wantedBy = [ "multi-user.target" ];
124 serviceConfig = {
125 ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} --certs-dir=${cfg.certificatesDir} ${toString cfg.dataDir}";
126 Type = "simple";
127 User = "minio";
128 Group = "minio";
129 LimitNOFILE = 65536;
130 EnvironmentFile =
131 if (cfg.rootCredentialsFile != null) then
132 cfg.rootCredentialsFile
133 else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then
134 (legacyCredentials cfg)
135 else
136 null;
137
138 # hardening
139 DevicePolicy = "closed";
140 CapabilityBoundingSet = "";
141 RestrictAddressFamilies = [
142 "AF_INET"
143 "AF_INET6"
144 "AF_NETLINK"
145 "AF_UNIX"
146 ];
147 DeviceAllow = "";
148 NoNewPrivileges = true;
149 PrivateDevices = true;
150 PrivateMounts = true;
151 PrivateTmp = true;
152 PrivateUsers = true;
153 ProtectClock = true;
154 ProtectControlGroups = true;
155 ProtectHome = true;
156 ProtectKernelLogs = true;
157 ProtectKernelModules = true;
158 ProtectKernelTunables = true;
159 MemoryDenyWriteExecute = true;
160 LockPersonality = true;
161 RemoveIPC = true;
162 RestrictNamespaces = true;
163 RestrictRealtime = true;
164 RestrictSUIDSGID = true;
165 SystemCallArchitectures = "native";
166 SystemCallFilter = [
167 "@system-service"
168 "~@privileged"
169 ];
170 ProtectProc = "invisible";
171 ProtectHostname = true;
172 UMask = "0077";
173 # minio opens /proc/mounts on startup
174 ProcSubset = "all";
175 };
176 environment = {
177 MINIO_REGION = "${cfg.region}";
178 MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
179 };
180 };
181 }
182
183 (lib.mkIf (cfg.rootCredentialsFile != null) {
184 # The service will fail if the credentials file is missing
185 services.minio.unitConfig.ConditionPathExists = cfg.rootCredentialsFile;
186
187 # The service will not restart if the credentials file has
188 # been changed. This can cause stale root credentials.
189 paths.minio-root-credentials = {
190 wantedBy = [ "multi-user.target" ];
191
192 pathConfig = {
193 PathChanged = [ cfg.rootCredentialsFile ];
194 Unit = "minio-restart.service";
195 };
196 };
197
198 services.minio-restart = {
199 description = "Restart MinIO";
200
201 script = ''
202 systemctl restart minio.service
203 '';
204
205 serviceConfig = {
206 Type = "oneshot";
207 Restart = "on-failure";
208 RestartSec = 5;
209 };
210 };
211 })
212 ];
213
214 users.users.minio = {
215 group = "minio";
216 uid = config.ids.uids.minio;
217 };
218
219 users.groups.minio.gid = config.ids.uids.minio;
220 };
221}