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