1{
2 lib,
3 pkgs,
4 config,
5 ...
6}:
7let
8 cfg = config.services.matter-server;
9 storageDir = "matter-server";
10 storagePath = "/var/lib/${storageDir}";
11 vendorId = "4939"; # home-assistant vendor ID
12in
13
14{
15 meta.maintainers = with lib.maintainers; [ leonm1 ];
16
17 options.services.matter-server = with lib.types; {
18 enable = lib.mkEnableOption "Matter-server";
19
20 package = lib.mkPackageOption pkgs "python-matter-server" { };
21
22 port = lib.mkOption {
23 type = lib.types.port;
24 default = 5580;
25 description = "Port to expose the matter-server service on.";
26 };
27
28 logLevel = lib.mkOption {
29 type = lib.types.enum [
30 "critical"
31 "error"
32 "warning"
33 "info"
34 "debug"
35 ];
36 default = "info";
37 description = "Verbosity of logs from the matter-server";
38 };
39
40 extraArgs = lib.mkOption {
41 type = listOf str;
42 default = [ ];
43 description = ''
44 Extra arguments to pass to the matter-server executable.
45 See https://github.com/home-assistant-libs/python-matter-server?tab=readme-ov-file#running-the-development-server for options.
46 '';
47 };
48 };
49
50 config = lib.mkIf cfg.enable {
51 systemd.services.matter-server = {
52 after = [ "network-online.target" ];
53 before = [ "home-assistant.service" ];
54 wants = [ "network-online.target" ];
55 wantedBy = [ "multi-user.target" ];
56 description = "Matter Server";
57 environment.HOME = storagePath;
58 serviceConfig = {
59 ExecStart = (
60 lib.concatStringsSep " " [
61 # `python-matter-server` writes to /data even when a storage-path
62 # is specified. This symlinks /data at the systemd-managed
63 # /var/lib/matter-server, so all files get dropped into the state
64 # directory.
65 "${pkgs.bash}/bin/sh"
66 "-c"
67 "'"
68 "${pkgs.coreutils}/bin/ln -s %S/matter-server/ %t/matter-server/root/data"
69 "&&"
70 "${cfg.package}/bin/matter-server"
71 "--port"
72 (toString cfg.port)
73 "--vendorid"
74 vendorId
75 "--storage-path"
76 storagePath
77 "--log-level"
78 "${cfg.logLevel}"
79 "${lib.escapeShellArgs cfg.extraArgs}"
80 "'"
81 ]
82 );
83 # Start with a clean root filesystem, and allowlist what the container
84 # is permitted to access.
85 # See https://discourse.nixos.org/t/hardening-systemd-services/17147/14.
86 RuntimeDirectory = [ "matter-server/root" ];
87 RootDirectory = "%t/matter-server/root";
88
89 # Allowlist /nix/store (to allow the binary to find its dependencies)
90 # and dbus.
91 BindReadOnlyPaths = "/nix/store /run/dbus";
92 # Let systemd manage `/var/lib/matter-server` for us inside the
93 # ephemeral TemporaryFileSystem.
94 StateDirectory = storageDir;
95
96 # Hardening bits
97 AmbientCapabilities = "";
98 CapabilityBoundingSet = "";
99 DevicePolicy = "closed";
100 DynamicUser = true;
101 LockPersonality = true;
102 MemoryDenyWriteExecute = true;
103 NoNewPrivileges = true;
104 PrivateDevices = true;
105 PrivateTmp = true;
106 PrivateUsers = true;
107 ProcSubset = "pid";
108 ProtectClock = true;
109 ProtectControlGroups = true;
110 ProtectHome = true;
111 ProtectHostname = true;
112 ProtectKernelLogs = true;
113 ProtectKernelModules = true;
114 ProtectKernelTunables = true;
115 ProtectProc = "invisible";
116 RestrictAddressFamilies = [
117 "AF_INET"
118 "AF_INET6"
119 "AF_NETLINK"
120 "AF_UNIX"
121 ];
122 RestrictNamespaces = true;
123 RestrictRealtime = true;
124 RestrictSUIDSGID = true;
125 SystemCallFilter = lib.concatStringsSep " " [
126 "~" # Blocklist
127 "@clock"
128 "@cpu-emulation"
129 "@debug"
130 "@module"
131 "@mount"
132 "@obsolete"
133 "@privileged"
134 "@raw-io"
135 "@reboot"
136 "@resources"
137 "@swap"
138 ];
139 UMask = "0077";
140 };
141 };
142 };
143}