1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.garage;
12 toml = pkgs.formats.toml { };
13 configFile = toml.generate "garage.toml" cfg.settings;
14in
15{
16 meta = {
17 doc = ./garage.md;
18 maintainers = with lib.maintainers; [
19 mjm
20 cything
21 ];
22 };
23
24 options.services.garage = {
25 enable = mkEnableOption "Garage Object Storage (S3 compatible)";
26
27 extraEnvironment = mkOption {
28 type = types.attrsOf types.str;
29 description = "Extra environment variables to pass to the Garage server.";
30 default = { };
31 example = {
32 RUST_BACKTRACE = "yes";
33 };
34 };
35
36 environmentFile = mkOption {
37 type = types.nullOr types.path;
38 description = "File containing environment variables to be passed to the Garage server.";
39 default = null;
40 };
41
42 logLevel = mkOption {
43 type = types.enum [
44 "error"
45 "warn"
46 "info"
47 "debug"
48 "trace"
49 ];
50 default = "info";
51 example = "debug";
52 description = "Garage log level, see <https://garagehq.deuxfleurs.fr/documentation/quick-start/#launching-the-garage-server> for examples.";
53 };
54
55 settings = mkOption {
56 type = types.submodule {
57 freeformType = toml.type;
58
59 options = {
60 metadata_dir = mkOption {
61 default = "/var/lib/garage/meta";
62 type = types.path;
63 description = "The metadata directory, put this on a fast disk (e.g. SSD) if possible.";
64 };
65
66 data_dir = mkOption {
67 default = "/var/lib/garage/data";
68 example = [
69 {
70 path = "/var/lib/garage/data";
71 capacity = "2T";
72 }
73 ];
74 type = with types; either path (listOf attrs);
75 description = ''
76 The directory in which Garage will store the data blocks of objects. This folder can be placed on an HDD.
77 Since v0.9.0, Garage supports multiple data directories, refer to https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#data_dir for the exact format.
78 '';
79 };
80 };
81 };
82 description = "Garage configuration, see <https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/> for reference.";
83 };
84
85 package = mkOption {
86 type = types.package;
87 description = "Garage package to use, needs to be set explicitly. If you are upgrading from a major version, please read NixOS and Garage release notes for upgrade instructions.";
88 };
89 };
90
91 config = mkIf cfg.enable {
92 environment.etc."garage.toml" = {
93 source = configFile;
94 };
95
96 # For administration
97 environment.systemPackages = [
98 (pkgs.writeScriptBin "garage" ''
99 # make it so all future variables set are automatically exported as environment variables
100 set -a
101
102 # source the set environmentFile (since systemd EnvironmentFile is supposed to be a minor subset of posix sh parsing) (with shell arg escaping to avoid quoting issues)
103 [ -f ${lib.escapeShellArg cfg.environmentFile} ] && . ${lib.escapeShellArg cfg.environmentFile}
104
105 # exec the program with quoted args (also with shell arg escaping for the program path to avoid quoting issues there)
106 exec ${lib.escapeShellArg (lib.getExe cfg.package)} "$@"
107 '')
108 ];
109
110 systemd.services.garage = {
111 description = "Garage Object Storage (S3 compatible)";
112 after = [
113 "network.target"
114 "network-online.target"
115 ];
116 wants = [
117 "network.target"
118 "network-online.target"
119 ];
120 wantedBy = [ "multi-user.target" ];
121 restartTriggers = [
122 configFile
123 ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile);
124 serviceConfig =
125 let
126 paths = lib.flatten (
127 with cfg.settings;
128 [
129 metadata_dir
130 ]
131 # data_dir can either be a string or a list of attrs
132 # if data_dir is a list, the actual path will in in the `path` attribute of each item
133 # see https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#data_dir
134 ++ lib.optional (lib.isList data_dir) (map (item: item.path) data_dir)
135 ++ lib.optional (lib.isString data_dir) [ data_dir ]
136 );
137 isDefault = lib.hasPrefix "/var/lib/garage";
138 isDefaultStateDirectory = lib.any isDefault paths;
139 in
140 {
141 ExecStart = "${cfg.package}/bin/garage server";
142
143 StateDirectory = lib.mkIf isDefaultStateDirectory "garage";
144 DynamicUser = lib.mkDefault true;
145 ProtectHome = true;
146 NoNewPrivileges = true;
147 EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
148 ReadWritePaths = lib.filter (x: !(isDefault x)) (lib.flatten [ paths ]);
149 };
150 environment = {
151 RUST_LOG = lib.mkDefault "garage=${cfg.logLevel}";
152 } // cfg.extraEnvironment;
153 };
154 };
155}