1{
2 config,
3 options,
4 pkgs,
5 lib,
6 ...
7}:
8let
9 cfg = config.services.memos;
10 opt = options.services.memos;
11 envFileFormat = pkgs.formats.keyValue { };
12in
13{
14 options.services.memos = {
15 enable = lib.mkEnableOption "Memos note-taking";
16 package = lib.mkPackageOption pkgs "Memos" {
17 default = "memos";
18 };
19
20 openFirewall = lib.mkEnableOption "opening the ports in the firewall";
21
22 user = lib.mkOption {
23 type = lib.types.str;
24 description = ''
25 The user to run Memos as.
26
27 ::: {.note}
28 If changing the default value, **you** are responsible of creating the corresponding user with [{option}`users.users`](#opt-users.users).
29 :::
30 '';
31 default = "memos";
32 };
33
34 group = lib.mkOption {
35 type = lib.types.str;
36 description = ''
37 The group to run Memos as.
38
39 ::: {.note}
40 If changing the default value, **you** are responsible of creating the corresponding group with [{option}`users.groups`](#opt-users.groups).
41 :::
42 '';
43 default = "memos";
44 };
45
46 dataDir = lib.mkOption {
47 default = "/var/lib/memos/";
48 type = lib.types.path;
49 description = ''
50 Specifies the directory where Memos will store its data.
51
52 ::: {.note}
53 It will be automatically created with the permissions of [{option}`services.memos.user`](#opt-services.memos.user) and [{option}`services.memos.group`](#opt-services.memos.group).
54 :::
55 '';
56 };
57
58 settings = lib.mkOption {
59 type = envFileFormat.type;
60 description = ''
61 The environment variables to configure Memos.
62
63 ::: {.note}
64 At time of writing, there is no clear documentation about possible values.
65 It's possible to convert CLI flags into these variables.
66 Example : CLI flag "--unix-sock" converts to {env}`MEMOS_UNIX_SOCK`.
67 :::
68 '';
69 default = {
70 MEMOS_MODE = "prod";
71 MEMOS_ADDR = "127.0.0.1";
72 MEMOS_PORT = "5230";
73 MEMOS_DATA = cfg.dataDir;
74 MEMOS_DRIVER = "sqlite";
75 MEMOS_INSTANCE_URL = "http://localhost:5230";
76 };
77 defaultText = lib.literalExpression ''
78 {
79 MEMOS_MODE = "prod";
80 MEMOS_ADDR = "127.0.0.1";
81 MEMOS_PORT = "5230";
82 MEMOS_DATA = config.${opt.dataDir};
83 MEMOS_DRIVER = "sqlite";
84 MEMOS_INSTANCE_URL = "http://localhost:5230";
85 }
86 '';
87 };
88
89 environmentFile = lib.mkOption {
90 type = lib.types.path;
91 description = ''
92 The environment file to use when starting Memos.
93
94 ::: {.note}
95 By default, generated from [](opt-${opt.settings}).
96 :::
97 '';
98 example = "/var/lib/memos/memos.env";
99 default = envFileFormat.generate "memos.env" cfg.settings;
100 defaultText = lib.literalMD ''
101 generated from {option}`${opt.settings}`
102 '';
103 };
104 };
105
106 config = lib.mkIf cfg.enable {
107 users.users = lib.mkIf (cfg.user == "memos") {
108 ${cfg.user} = {
109 description = lib.mkDefault "Memos service user";
110 isSystemUser = true;
111 group = cfg.group;
112 };
113 };
114
115 users.groups = lib.mkIf (cfg.group == "memos") {
116 ${cfg.group} = { };
117 };
118
119 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
120 cfg.port
121 ];
122
123 systemd.tmpfiles.settings."10-memos" = {
124 "${cfg.dataDir}" = {
125 d = {
126 mode = "0750";
127 user = cfg.user;
128 group = cfg.group;
129 };
130 };
131 };
132
133 systemd.services.memos = {
134 wantedBy = [ "multi-user.target" ];
135 after = [ "network.target" ];
136 wants = [ "network.target" ];
137 description = "Memos, a privacy-first, lightweight note-taking solution";
138 serviceConfig = {
139 User = cfg.user;
140 Group = cfg.group;
141 Type = "simple";
142 RestartSec = 60;
143 LimitNOFILE = 65536;
144 NoNewPrivileges = true;
145 LockPersonality = true;
146 RemoveIPC = true;
147 ReadWritePaths = [
148 cfg.dataDir
149 ];
150 ProtectSystem = "strict";
151 PrivateUsers = true;
152 ProtectHome = true;
153 PrivateTmp = true;
154 PrivateDevices = true;
155 ProtectHostname = true;
156 ProtectClock = true;
157 UMask = "0077";
158 ProtectKernelTunables = true;
159 ProtectKernelModules = true;
160 ProtectControlGroups = true;
161 ProtectProc = "invisible";
162 SystemCallFilter = [
163 " " # This is needed to clear the SystemCallFilter existing definitions
164 "~@reboot"
165 "~@swap"
166 "~@obsolete"
167 "~@mount"
168 "~@module"
169 "~@debug"
170 "~@cpu-emulation"
171 "~@clock"
172 "~@raw-io"
173 "~@privileged"
174 "~@resources"
175 ];
176 CapabilityBoundingSet = [
177 " " # Reset all capabilities to an empty set
178 ];
179 RestrictAddressFamilies = [
180 " " # This is needed to clear the RestrictAddressFamilies existing definitions
181 "none" # Remove all addresses families
182 "AF_UNIX"
183 "AF_INET"
184 "AF_INET6"
185 ];
186 DevicePolicy = "closed";
187 ProtectKernelLogs = true;
188 SystemCallArchitectures = "native";
189 RestrictNamespaces = true;
190 RestrictRealtime = true;
191 RestrictSUIDSGID = true;
192 EnvironmentFile = cfg.environmentFile;
193 ExecStart = lib.getExe cfg.package;
194 };
195 };
196 };
197
198 meta.maintainers = [ lib.maintainers.m0ustach3 ];
199}