1{
2 lib,
3 config,
4 pkgs,
5 utils,
6 ...
7}:
8
9let
10 inherit (lib)
11 mkEnableOption
12 mkIf
13 mkOption
14 types
15 ;
16
17 cfg = config.services.imaginary;
18in
19{
20 options.services.imaginary = {
21 enable = mkEnableOption "imaginary image processing microservice";
22
23 address = mkOption {
24 type = types.str;
25 default = "localhost";
26 description = ''
27 Bind address. Corresponds to the `-a` flag.
28 Set to `""` to bind to all addresses.
29 '';
30 example = "[::1]";
31 };
32
33 port = mkOption {
34 type = types.port;
35 default = 8088;
36 description = "Bind port. Corresponds to the `-p` flag.";
37 };
38
39 settings = mkOption {
40 description = ''
41 Command line arguments passed to the imaginary executable, stripped of
42 the prefix `-`. See upstream's
43 [README](https://github.com/h2non/imaginary#command-line-usage) for all
44 options.
45 '';
46 type = types.submodule {
47 freeformType =
48 with types;
49 attrsOf (oneOf [
50 bool
51 int
52 (nonEmptyListOf str)
53 str
54 ]);
55
56 options = {
57 return-size = mkOption {
58 type = types.bool;
59 default = false;
60 description = "Return the image size in the HTTP headers.";
61 };
62 };
63 };
64 };
65 };
66
67 config = mkIf cfg.enable {
68 assertions = [
69 {
70 assertion = !lib.hasAttr "a" cfg.settings;
71 message = "Use services.imaginary.address to specify the -a flag.";
72 }
73 {
74 assertion = !lib.hasAttr "p" cfg.settings;
75 message = "Use services.imaginary.port to specify the -p flag.";
76 }
77 ];
78
79 systemd.services.imaginary = {
80 after = [ "network.target" ];
81 wantedBy = [ "multi-user.target" ];
82 serviceConfig = rec {
83 ExecStart =
84 let
85 args =
86 lib.mapAttrsToList
87 (key: val: "-" + key + "=" + lib.concatStringsSep "," (map toString (lib.toList val)))
88 (
89 cfg.settings
90 // {
91 a = cfg.address;
92 p = cfg.port;
93 }
94 );
95 in
96 "${pkgs.imaginary}/bin/imaginary ${utils.escapeSystemdExecArgs args}";
97 ProtectProc = "invisible";
98 BindReadOnlyPaths = lib.optional (cfg.settings ? mount) cfg.settings.mount;
99 CapabilityBoundingSet = if cfg.port < 1024 then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
100 AmbientCapabilities = CapabilityBoundingSet;
101 NoNewPrivileges = true;
102 DynamicUser = true;
103 ProtectSystem = "strict";
104 ProtectHome = true;
105 TemporaryFileSystem = [ "/:ro" ];
106 PrivateTmp = true;
107 PrivateDevices = true;
108 PrivateUsers = cfg.port >= 1024;
109 ProtectHostname = true;
110 ProtectClock = true;
111 ProtectKernelTunables = true;
112 ProtectKernelModules = true;
113 ProtectKernelLogs = true;
114 ProtectControlGroups = true;
115 RestrictAddressFamilies = [
116 "AF_INET"
117 "AF_INET6"
118 ];
119 RestrictNamespaces = true;
120 LockPersonality = true;
121 MemoryDenyWriteExecute = true;
122 RestrictRealtime = true;
123 PrivateMounts = true;
124 SystemCallFilter = [
125 "@system-service"
126 "~@privileged"
127 ];
128 DevicePolicy = "closed";
129 };
130 };
131 };
132
133 meta = {
134 maintainers = with lib.maintainers; [ dotlambda ];
135 };
136}