1 { config, lib, pkgs, ... }:
6 cfg = config.services.nbd;
7 iniFields = with types; attrsOf (oneOf [ bool int float str ]);
8 # The `[generic]` section must come before all the others in the
9 # config file. This means we can't just dump an attrset to INI
10 # because that sorts the sections by name. Instead, we serialize it
13 generic = (cfg.server.extraOptions // {
16 port = cfg.server.listenPort;
17 } // (optionalAttrs (cfg.server.listenAddress != null) {
18 listenaddr = cfg.server.listenAddress;
23 (_: { path, allowAddresses, extraOptions }:
26 } // (optionalAttrs (allowAddresses != null) {
27 authfile = pkgs.writeText "authfile" (concatStringsSep "\n" allowAddresses);
31 pkgs.writeText "nbd-server-config" ''
32 ${lib.generators.toINI {} genericSection}
33 ${lib.generators.toINI {} exportSections}
37 (path: hasPrefix "/dev/" path)
38 (mapAttrsToList (_: { path, ... }: path) cfg.server.exports);
39 allowedDevices = splitLists.right;
40 boundPaths = splitLists.wrong;
46 enable = mkEnableOption "the Network Block Device (nbd) server";
48 listenPort = mkOption {
51 description = "Port to listen on. The port is NOT automatically opened in the firewall.";
54 extraOptions = mkOption {
60 Extra options for the server. See
61 {manpage}`nbd-server(5)`.
66 description = "Files or block devices to make available over the network.";
68 type = with types; attrsOf
73 description = "File or block device to export.";
74 example = "/dev/sdb1";
77 allowAddresses = mkOption {
78 type = nullOr (listOf str);
80 example = [ "10.10.0.0/24" "127.0.0.1" ];
81 description = "IPs and subnets that are authorized to connect for this device. If not specified, the server will allow all connections.";
84 extraOptions = mkOption {
91 Extra options for this export. See
92 {manpage}`nbd-server(5)`.
99 listenAddress = mkOption {
100 type = with types; nullOr str;
101 description = "Address to listen on. If not specified, the server will listen on all interfaces.";
103 example = "10.10.0.1";
109 config = mkIf cfg.server.enable {
112 assertion = !(cfg.server.exports ? "generic");
113 message = "services.nbd.server exports must not be named 'generic'";
117 boot.kernelModules = [ "nbd" ];
119 systemd.services.nbd-server = {
120 wants = [ "network-online.target" ];
121 after = [ "network-online.target" ];
122 before = [ "multi-user.target" ];
123 wantedBy = [ "multi-user.target" ];
125 ExecStart = "${pkgs.nbd}/bin/nbd-server -C ${serverConfig}";
128 DeviceAllow = map (path: "${path} rw") allowedDevices;
129 BindPaths = boundPaths;
131 CapabilityBoundingSet = "";
132 DevicePolicy = "closed";
133 LockPersonality = true;
134 MemoryDenyWriteExecute = true;
135 NoNewPrivileges = true;
136 PrivateDevices = false;
137 PrivateMounts = true;
142 ProtectControlGroups = true;
144 ProtectHostname = true;
145 ProtectKernelLogs = true;
146 ProtectKernelModules = true;
147 ProtectKernelTunables = true;
148 ProtectProc = "noaccess";
149 ProtectSystem = "strict";
150 RestrictAddressFamilies = "AF_INET AF_INET6";
151 RestrictNamespaces = true;
152 RestrictRealtime = true;
153 RestrictSUIDSGID = true;