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 (lib.mdDoc "the Network Block Device (nbd) server");
48 listenPort = mkOption {
51 description = lib.mdDoc "Port to listen on. The port is NOT automatically opened in the firewall.";
54 extraOptions = mkOption {
59 description = lib.mdDoc ''
60 Extra options for the server. See
61 {manpage}`nbd-server(5)`.
66 description = lib.mdDoc "Files or block devices to make available over the network.";
68 type = with types; attrsOf
73 description = lib.mdDoc "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 = lib.mdDoc "IPs and subnets that are authorized to connect for this device. If not specified, the server will allow all connections.";
84 extraOptions = mkOption {
90 description = lib.mdDoc ''
91 Extra options for this export. See
92 {manpage}`nbd-server(5)`.
99 listenAddress = mkOption {
100 type = with types; nullOr str;
101 description = lib.mdDoc "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 after = [ "network-online.target" ];
121 before = [ "multi-user.target" ];
122 wantedBy = [ "multi-user.target" ];
124 ExecStart = "${pkgs.nbd}/bin/nbd-server -C ${serverConfig}";
127 DeviceAllow = map (path: "${path} rw") allowedDevices;
128 BindPaths = boundPaths;
130 CapabilityBoundingSet = "";
131 DevicePolicy = "closed";
132 LockPersonality = true;
133 MemoryDenyWriteExecute = true;
134 NoNewPrivileges = true;
135 PrivateDevices = false;
136 PrivateMounts = true;
141 ProtectControlGroups = true;
143 ProtectHostname = true;
144 ProtectKernelLogs = true;
145 ProtectKernelModules = true;
146 ProtectKernelTunables = true;
147 ProtectProc = "noaccess";
148 ProtectSystem = "strict";
149 RestrictAddressFamilies = "AF_INET AF_INET6";
150 RestrictNamespaces = true;
151 RestrictRealtime = true;
152 RestrictSUIDSGID = true;