1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib) types;
10 cfg = config.services.opencloud;
11
12 defaultUser = "opencloud";
13 defaultGroup = defaultUser;
14
15 settingsFormat = pkgs.formats.yaml { };
16in
17{
18 options = {
19 services.opencloud = {
20 enable = lib.mkEnableOption "OpenCloud";
21
22 package = lib.mkPackageOption pkgs "opencloud" { };
23 webPackage = lib.mkPackageOption pkgs [ "opencloud" "web" ] { };
24 idpWebPackage = lib.mkPackageOption pkgs [ "opencloud" "idp-web" ] { };
25
26 user = lib.mkOption {
27 type = types.str;
28 default = defaultUser;
29 example = "mycloud";
30 description = ''
31 The user to run OpenCloud as.
32 By default, a user named `${defaultUser}` will be created whose home
33 directory is [](#opt-services.opencloud.stateDir).
34 '';
35 };
36
37 group = lib.mkOption {
38 type = types.str;
39 default = defaultGroup;
40 example = "mycloud";
41 description = ''
42 The group to run OpenCloud under.
43 By default, a group named `${defaultGroup}` will be created.
44 '';
45 };
46
47 address = lib.mkOption {
48 type = types.str;
49 default = "127.0.0.1";
50 description = "Web server bind address.";
51 };
52
53 port = lib.mkOption {
54 type = types.port;
55 default = 9200;
56 description = "Web server port.";
57 };
58
59 url = lib.mkOption {
60 type = types.str;
61 default = "https://localhost:9200";
62 example = "https://cloud.example.com";
63 description = "Web interface root public URL, including scheme and port (if non-default).";
64 };
65
66 stateDir = lib.mkOption {
67 default = "/var/lib/opencloud";
68 type = types.str;
69 description = "OpenCloud data directory.";
70 };
71
72 settings = lib.mkOption {
73 type = lib.types.attrsOf settingsFormat.type;
74 default = { };
75 description = ''
76 Additional YAML configuration for OpenCloud services.
77
78 Every item in this attrset will be mapped to a .yaml file in /etc/opencloud.
79
80 The possible config options are currently not well documented, see source code:
81 https://github.com/opencloud-eu/opencloud/blob/main/pkg/config/config.go
82 '';
83
84 example = {
85 proxy = {
86 auto_provision_accounts = true;
87 oidc.rewrite_well_known = true;
88 role_assignment = {
89 driver = "oidc";
90 oidc_role_mapper.role_claim = "opencloud_roles";
91 };
92 };
93 web.web.config.oidc.scope = "openid profile email opencloud_roles";
94 };
95 };
96
97 environmentFile = lib.mkOption {
98 type = types.nullOr types.path;
99 default = null;
100 example = "/run/keys/opencloud.env";
101 description = ''
102 An environment file as defined in {manpage}`systemd.exec(5)`.
103
104 Use this to inject secrets, e.g. database or auth credentials out of band.
105
106 Configuration provided here will override `settings` and `environment`.
107 '';
108 };
109
110 environment = lib.mkOption {
111 type = types.attrsOf types.str;
112 default = {
113 OC_INSECURE = "true";
114 };
115 description = ''
116 Extra environment variables to set for the service.
117
118 Use this to set configuration that may affect multiple microservices.
119
120 Set `OC_INSECURE = "false"` if you want OpenCloud to terminate TLS.
121
122 Configuration provided here will override `settings`.
123 '';
124 example = {
125 OC_INSECURE = "false";
126 OC_LOG_LEVEL = "error";
127 };
128 };
129 };
130 };
131
132 config = lib.mkIf cfg.enable {
133 users.users.${defaultUser} = lib.mkIf (cfg.user == defaultUser) {
134 group = cfg.group;
135 home = cfg.stateDir;
136 isSystemUser = true;
137 createHome = true;
138 description = "OpenCloud daemon user";
139 };
140
141 users.groups = lib.mkIf (cfg.group == defaultGroup) { ${defaultGroup} = { }; };
142
143 systemd = {
144 services =
145 let
146 environment = {
147 PROXY_HTTP_ADDR = "${cfg.address}:${toString cfg.port}";
148 OC_URL = cfg.url;
149 OC_BASE_DATA_PATH = cfg.stateDir;
150 WEB_ASSET_CORE_PATH = "${cfg.webPackage}";
151 IDP_ASSET_PATH = "${cfg.idpWebPackage}/assets";
152 OC_CONFIG_DIR = "/etc/opencloud";
153 }
154 // cfg.environment;
155 commonServiceConfig = {
156 EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
157 MemoryDenyWriteExecute = true;
158 NoNewPrivileges = true;
159 PrivateTmp = true;
160 PrivateDevices = true;
161 ProtectSystem = "strict";
162 ProtectHome = true;
163 ProtectControlGroups = true;
164 ProtectKernelModules = true;
165 ProtectKernelTunables = true;
166 ProtectKernelLogs = true;
167 RestrictAddressFamilies = [
168 "AF_UNIX"
169 "AF_INET"
170 "AF_INET6"
171 ];
172 RestrictNamespaces = true;
173 RestrictRealtime = true;
174 RestrictSUIDSGID = true;
175 LockPersonality = true;
176 SystemCallArchitectures = "native";
177 };
178 in
179 {
180 opencloud-init-config = lib.mkIf (cfg.settings.opencloud or { } == { }) {
181 description = "Provision initial OpenCloud config";
182 before = [ "opencloud.service" ];
183 wantedBy = [ "multi-user.target" ];
184
185 inherit environment;
186
187 serviceConfig = {
188 Type = "oneshot";
189 ReadWritePaths = [ "/etc/opencloud" ];
190 }
191 // commonServiceConfig;
192
193 path = [ cfg.package ];
194 script = ''
195 set -x
196 config="''${OC_CONFIG_DIR}/opencloud.yaml"
197 if [ ! -e "$config" ]; then
198 echo "Provisioning initial OpenCloud config..."
199 opencloud init --insecure "''${OC_INSECURE:false}" --config-path "''${OC_CONFIG_DIR}"
200 chown ${cfg.user}:${cfg.group} "$config"
201 fi
202 '';
203 };
204
205 opencloud = {
206 description = "OpenCloud - a secure and private way to store, access, and share your files";
207 after = [ "network.target" ];
208 wantedBy = [ "multi-user.target" ];
209
210 inherit environment;
211
212 serviceConfig = {
213 Type = "simple";
214 ExecStart = "${lib.getExe cfg.package} server";
215 WorkingDirectory = cfg.stateDir;
216 User = cfg.user;
217 Group = cfg.group;
218 Restart = "always";
219 ReadWritePaths = [ cfg.stateDir ];
220 }
221 // commonServiceConfig;
222
223 restartTriggers = lib.mapAttrsToList (
224 name: _: config.environment.etc."opencloud/${name}.yaml".source
225 ) cfg.settings;
226 };
227 };
228 };
229
230 systemd.tmpfiles.settings."10-opencloud" = {
231 ${cfg.stateDir}.d = {
232 inherit (cfg) user group;
233 mode = "0750";
234 };
235 "${cfg.stateDir}/idm".d = {
236 inherit (cfg) user group;
237 mode = "0750";
238 };
239 };
240
241 environment.etc =
242 (lib.mapAttrs' (name: value: {
243 name = "opencloud/${name}.yaml";
244 value.source = settingsFormat.generate "${name}.yaml" value;
245 }) cfg.settings)
246 // {
247 # ensure /etc/opencloud gets created, so we can provision the config
248 "opencloud/.keep".text = "";
249 };
250 };
251
252 meta = {
253 doc = ./opencloud.md;
254 maintainers = with lib.maintainers; [
255 christoph-heiss
256 k900
257 ];
258 };
259}