diff --git a/content/en/docs/Theory/selfprivacy_modules/_index.md b/content/en/docs/Theory/selfprivacy_modules/_index.md
new file mode 100644
index 0000000..85afa87
--- /dev/null
+++ b/content/en/docs/Theory/selfprivacy_modules/_index.md
@@ -0,0 +1,1271 @@
+---
+title: "SelfPrivacy modules"
+linkTitle: "SelfPrivacy modules"
+weight: 6
+date: 2022-01-09
+description: >
+ The building blocks of SelfPrivacy.
+markup:
+ tableOfContents:
+ startLevel: 2
+ endLevel: 6
+ ordered: false
+---
+
+## Overview
+
+SelfPrivacy modules are the building blocks of SelfPrivacy. Every service you see in the "Services" tab of your app is a SelfPrivacy module. Usually a single SelfPrivacy module contains a single service, so these might be used interchangeably in this context. Sometimes we refer to these modules as "SP Modules".
+
+SelfPrivacy modules contain the following:
+
+- Nix expressions that tell [NixOS](https://nixos.org) how to build and install a service;
+- Metadata used by SelfPrivacy API to manipulate a service;
+- Metadata used by SelfPrivacy app to render user interface about a service.
+
+SelfPrivacy module is a folder that contains a [Nix Flake](https://wiki.nixos.org/wiki/Flakes). This flake must have three outputs:
+
+- The NixOS module that is used to install the service;
+- List of data the module needs to know to install the service correctly;
+- Metadata object.
+
+{{% alert title="SelfPrivacy modules can do anything on your server" color="warning" %}}
+While custom SelfPrivacy modules give you great flexibility, using third-party modules comes with a huge risk! SelfPrivacy modules contain NixOS expressions that can do anything to your system, and installing a backdoor this way is trivial.
+
+We recommend that you only use modules you have written yourself or forked and analyzed carefully.
+{{% /alert %}}
+
+
+## Directory structure
+
+Here is the minimal SelfPrivacy module structure.
+
+```bash
+.
+├── config-paths-needed.json
+├── flake.nix
+├── icon.svg
+└── module.nix
+```
+
+### flake.nix
+
+This is the entry point of a SelfPrivacy module. It is a Nix flake that has three outputs:
+
+- `nixosModules.default` — The NixOS module that installs and configures a service.
+- `configPathsNeeded` — A JSON file with a list of config paths a module requires.
+- `meta` — Meta information about this module that SelfPrivacy API uses for different tasks.
+
+An example of a `flake.nix` file that shows all metadata fields:
+
+```nix
+{
+ description = "Flake description";
+
+ outputs = { self }: {
+ nixosModules.default = import ./module.nix;
+ configPathsNeeded =
+ builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
+ meta = {lib, ...}: {
+ spModuleSchemaVersion = 1;
+ id = "jitsi-meet";
+ name = "JitsiMeet";
+ description = "Jitsi Meet is a free and open-source video conferencing solution.";
+ svgIcon = builtins.readFile ./icon.svg;
+ showUrl = true;
+ primarySubdomain = "subdomain";
+ isMovable = false;
+ isRequired = false;
+ canBeBackedUp = true;
+ backupDescription = "Secrets that are used to encrypt the communication.";
+ systemdServices = [
+ "prosody.service"
+ "jitsi-videobridge2.service"
+ "jicofo.service"
+ ];
+ user = "jitsi-meet";
+ group = "jitsi-meet";
+ folders = [
+ "/var/lib/jitsi-meet"
+ ];
+ ownedFolders = [
+ {
+ path = "/var/lib/prometheus";
+ owner = "prometheus";
+ group = "prometheus";
+ }
+ ];
+ postgreDatabases = [];
+ license = [
+ lib.licenses.asl20
+ ];
+ homepage = "https://jitsi.org/meet";
+ sourcePage = "https://github.com/jitsi/jitsi-meet";
+ supportLevel = "normal";
+ };
+ };
+}
+```
+
+In practice, you don't need to define all metadata fields. Please refer to [Flake metadata section](#flake-metadata) to learn more about them.
+
+### config-paths-needed.json
+
+It is a JSON file that contains a list of Nix config paths. Only paths defined here will be available to your Nix flake.
+
+```json
+[
+ ["selfprivacy", "domain"],
+ ["selfprivacy", "modules", "roundcube"],
+ ["mailserver", "fqdn"]
+]
+```
+
+Commonly used paths include:
+
+- `[ "selfprivacy", "modules", "YOUR_MODULE_ID" ]` — Your flake needs access to its config. Replace `YOUR_MODULE_ID` with your module's ID.
+- `[ "selfprivacy", "domain" ]` — Server's domain. Usually needed by web services.
+- `[ "selfprivacy", "useBinds" ]` — Add this if your service stores data on the disk. Refer to [Mounting user data](#mounting-user-data) section.
+- `[ "security", "acme", "certs" ]` — Only use this if you need direct access to TLS certificates.
+
+### icon.svg
+
+This is an icon of the service that shall be displayed in user interface. It has the following requirements:
+
+- Icon must only use the black color. It will be recolored by an app depending on the user's theme and service's state.
+- Icon must be a square and have a square view box.
+- Try to flatten the icon and minimize its size.
+
+### module.nix
+
+This file contains the actual contents of your module! They vary heavily depending on your goals, but you can do anything that NixOS allows. Here is a generalized example of how the module might look like:
+
+```nix
+
+{ config, lib, pkgs, ... }:
+let
+ # Just for convinience, this module's config values
+ sp = config.selfprivacy;
+ cfg = sp.modules.service_id;
+in
+{
+ # Here go the options you expose to the user.
+ options.selfprivacy.modules.service_id = {
+ # This is required and must always be named "enable"
+ enable = (lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ description = "Enable the service";
+ }) // {
+ meta = {
+ type = "enable";
+ };
+ };
+ # This is required if your service stores data on disk
+ location = (lib.mkOption {
+ type = lib.types.str;
+ description = "Service location";
+ }) // {
+ meta = {
+ type = "location";
+ };
+ };
+ # This is required if your service needs a subdomain
+ subdomain = (lib.mkOption {
+ default = "password";
+ type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
+ description = "Subdomain";
+ }) // {
+ meta = {
+ widget = "subdomain";
+ type = "string";
+ regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
+ weight = 0;
+ };
+ };
+ # Other options, that user sees directly.
+ # Refer to Module options reference to learn more.
+ signupsAllowed = (lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ description = "Allow new user signups";
+ }) // {
+ meta = {
+ type = "bool";
+ weight = 1;
+ };
+ };
+ appName = (lib.mkOption {
+ default = "SelfPrivacy Service";
+ type = lib.types.str;
+ description = "The name displayed in the web interface";
+ }) // {
+ meta = {
+ type = "string";
+ weight = 2;
+ };
+ };
+ defaultTheme = (lib.mkOption {
+ default = "auto";
+ type = lib.types.enum [
+ "auto"
+ "light"
+ "dark"
+ ];
+ description = "Default theme";
+ }) // {
+ meta = {
+ type = "enum";
+ options = themes;
+ weight = 3;
+ };
+ };
+ };
+
+ # All your changes to the system must go to this config attrset.
+ # It MUST use lib.mkIf with an enable option.
+ # This makes sure your module only makes changes to the system
+ # if the module is enabled.
+ config = lib.mkIf config.selfprivacy.modules.service_id.enable {
+ # If your service stores data on disk, you have to mount a folder
+ # for this. useBinds is always true on modern SelfPrivacy installations
+ # but we keep this mkIf to keep migration flow possible.
+ fileSystems = lib.mkIf sp.useBinds {
+ "/var/lib/service_id" = {
+ device = "/volumes/${cfg.location}/service_id";
+ # Make sure that your service does not start before folder mounts
+ options = [
+ "bind"
+ "x-systemd.required-by=service-id.service"
+ "x-systemd.before=service-id.service"
+ ];
+ };
+ };
+ # Your service configuration, varies heavily.
+ # Refer to NixOS Options search.
+ # You can use defined options here.
+ services.service = {
+ enable = true;
+ domain = "${cfg.subdomain}.${config.selfprivacy.domain}";
+ config = {
+ theme = cfg.defaultTheme;
+ appName = cfg.appName;
+ signupsAllowed = cfg.signupsAllowed
+ };
+ };
+ systemd = {
+ services = {
+ # Make sure all systemd units your module adds belong to a slice.
+ # Slice must be named the same as your module id.
+ # If your module id contains `-`, replace them with `_`.
+ # For example, "my-awesome-service" becomes "my_awesome_service.slice"
+ service-id.serviceConfig.Slice = "service_id.slice";
+ };
+ # Define the slice itself
+ slices.service_id = {
+ description = "Service slice";
+ };
+ };
+ # You can define a reverse proxy for your service like this
+ services.nginx.virtualHosts."${cfg.subdomain}.${config.selfprivacy.domain}" = {
+ useACMEHost = config.selfprivacy.domain;
+ forceSSL = true;
+ extraConfig = ''
+ add_header Strict-Transport-Security $hsts_header;
+ add_header 'Referrer-Policy' 'origin-when-cross-origin';
+ add_header X-Frame-Options SAMEORIGIN;
+ add_header X-Content-Type-Options nosniff;
+ add_header X-XSS-Protection "1; mode=block";
+ proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
+ '';
+ locations = {
+ "/" = {
+ proxyPass = "http://127.0.0.1:8222";
+ };
+ };
+ };
+ };
+}
+
+
+```
+
+### Module examples
+
+You can find our official SelfPrivacy modules on our [Git repostory](https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config/src/branch/flakes/sp-modules). Every folder there is a SelfPrivacy module.
+
+## Getting started
+
+Let's walk over the process of developing and deploying the SeflPrivacy module. As a trivial example let's package [Syncplay](https://github.com/Syncplay/syncplay) — a simple service that synchronizes video playback across different players and allows you to watch films together with friends over the internet!
+
+This service is already packaged for NixOS, and there is a module available. You can view available options via [NixOS search](https://search.nixos.org/options?channel=24.05&from=0&size=50&sort=relevance&type=packages&query=syncplay). All we actually have to do to get started is to set `services.syncplay.enable = true`. This service doesn't store any data, and we don't even need a subdomain for this.
+
+Start by creating a folder with a git repository. There, create 4 required files. Let's go over them, starting with `flake.nix`.
+
+```nix
+{
+ description = "An example of packaging to SelfPrivacy!";
+
+ outputs = { self }: {
+ nixosModules.default = import ./module.nix;
+ configPathsNeeded =
+ builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
+ meta = { lib, ... }: {
+ spModuleSchemaVersion = 1;
+ id = "syncplay";
+ name = "Syncplay";
+ description = "Solution to synchronize video playback across multiple instances of mpv, VLC, MPC-HC, MPC-BE and mplayer2 over the Internet.";
+ svgIcon = builtins.readFile ./icon.svg;
+ isMovable = false;
+ isRequired = false;
+ backupDescription = "Nothing to back up.";
+ systemdServices = [
+ "syncplay.service"
+ ];
+ license = [
+ lib.licenses.asl20
+ ];
+ homepage = "https://syncplay.pl";
+ sourcePage = "https://github.com/Syncplay/syncplay";
+ supportLevel = "experimental";
+ };
+ };
+}
+```
+
+- Flake description on the second line can be anything.
+- We set `isMovable` to false because there is nothing to move.
+- We set `canBeBackedUp` to false because there is nothing to back up
+- Getting systemd services might be tricky before you check it yourself. API will track the status of systemd units you put here, and report it to the user. When you restart a service from the app, services defined here get rebooted. If you have some setup units, you probably don't want them here, but you will still need to put them in a systemd slice.
+- You can get a license by looking at the [Nix derivation](https://github.com/NixOS/nixpkgs/blob/nixos-24.05/pkgs/applications/networking/syncplay/default.nix#L55).
+
+Next, we need to define `config-paths-needed.json`:
+
+```json
+[
+ [ "selfprivacy", "modules", "syncplay" ]
+]
+```
+
+We don't need much here because this service does not use domains and does not store data on the disk.
+
+Next, populate icon.svg with a black icon of the service. Here, I traced it into SVG:
+
+```xml
+
+```
+
+Now, the Nix module itself. Add this to `module.nix`:
+
+```nix
+{ config, lib, ... }:
+let
+ # Just a shorthand for the config
+ sp = config.selfprivacy;
+ cfg = sp.modules.syncplay;
+in
+{
+ options.selfprivacy.modules.syncplay = {
+ # We are required to add an enable option.
+ enable = (lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ description = "Enable Syncplay";
+ }) // {
+ meta = {
+ type = "enable";
+ };
+ };
+
+ # We don't need a "location" or a "subdomain" option, because
+ # Syncplay doesn't use a web interface, and doesn't store any data.
+
+ # Let's add some options to make it more interesting!
+
+ enableChat = (lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ description = "Enable chat feature";
+ }) // {
+ meta = {
+ type = "bool";
+ weight = 1;
+ };
+ };
+ # Message of the day.
+ motd = (lib.mkOption {
+ default = "Welcome to Syncplay!";
+ type = lib.types.str;
+ description = "Text to display when users join.";
+ }) // {
+ meta = {
+ type = "string";
+ weight = 2;
+ };
+ };
+ };
+
+ # The config itself, applied only if we enable the module.
+ config = lib.mkIf cfg.enable {
+ services.syncplay = {
+ # We enable syncplay...
+ enable = true;
+ # ...and set some extra arguments.
+ # MOTD text has to be converted to a text file
+ extraArgs = [ "--motd-file" (builtins.toFile "motd" cfg.motd) ]
+ # We only need to add this option if we've disable the chat.
+ ++ lib.optional (!cfg.enableChat) [ "--disable-chat" ];
+ };
+
+ # Now we need to define a systemd slice and put syncplay service there.
+ # It is required to track module's resource consumption.
+ systemd = {
+ services.syncplay = {
+ serviceConfig = {
+ Slice = "syncplay.slice";
+ };
+ };
+ slices.syncplay = {
+ description = "Syncplay service slice";
+ };
+ };
+ };
+}
+```
+
+Comments in the code should explain the idea.
+
+Now, commit and push it somewhere! This can be your own Forgejo on your SelfPrivacy server, GitHub, or any other git forge. Grab a HTTPS link to your repository. In my case it is `https://git.selfprivacy.org/SelfPrivacy/syncplay-module.git`.
+
+SSH into your server, go into `/etc/nixos` and edit the `/etc/nixos/sp-modules/flake.nix` file.
+
+```bash
+cd /etc/nixos
+nano sp-modules/flake.nix
+```
+
+You will need to add your SelfPrivacy module as an input in the following format, prepending `git+` in the front of your URL:
+
+```nix
+inputs.syncplay.url = git+https://git.selfprivacy.org/SelfPrivacy/syncplay-module.git;
+```
+
+The file should look something like this in the end:
+
+```nix
+{
+ description = "SelfPrivacy NixOS PoC modules/extensions/bundles/packages/etc";
+
+ # Here go the modules you already have preinstalled
+ inputs.bitwarden.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/bitwarden;
+ inputs.gitea.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/gitea;
+ inputs.jitsi-meet.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/jitsi-meet;
+ inputs.nextcloud.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/nextcloud;
+ inputs.ocserv.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/ocserv;
+ inputs.pleroma.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/pleroma;
+ inputs.simple-nixos-mailserver.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/simple-nixos-mailserver;
+ inputs.monitoring.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/monitoring;
+ inputs.roundcube.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/roundcube;
+ inputs.mumble.url = git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/mumble;
+
+ # Your own module!
+ inputs.syncplay.url = git+https://git.selfprivacy.org/SelfPrivacy/syncplay-module.git;
+
+ outputs = _: { };
+}
+
+```
+
+Now, make sure your working directory is `/etc/nixos`. Update flake inputs first with this command:
+
+```bash
+nix flake update --override-input selfprivacy-nixos-config git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes
+```
+
+If all went well, you will see that your flake got added as an input:
+
+```bash
+warning: updating lock file '/etc/nixos/flake.lock':
+• Updated input 'sp-modules':
+ 'path:./sp-modules?lastModified=1&narHash=sha256-HFXUKSRXMVIMQtC/C3G2xHuTP1l5XmA5PJYKphyZQ5Q%3D' (1970-01-01)
+ → 'path:./sp-modules?lastModified=1&narHash=sha256-oNTIichm/6AnXjV1ytNZdTdMDQasPoUYFITuulmB83Y%3D' (1970-01-01)
+• Added input 'sp-modules/syncplay':
+ 'git+https://git.selfprivacy.org/SelfPrivacy/syncplay-module.git?ref=refs/heads/master&rev=7e04cef393909231cdf1d162acd357fd5d36136d' (2024-12-29)
+```
+
+Now, we can rebuild the system with this command:
+
+```bash
+nixos-rebuild switch --flake .#default
+```
+
+After system rebuild, open your SelfPrivacy app. You will see a Syncplay service appear in your Services list. It is disabled right now, so go ahead and enable it. You can also change the MOTD or disable chat function from the app now.
+
+{{< imgproc deactivated_syncplay Fill "1344x888" />}}
+{{< imgproc syncplay_settings Fill "1344x944" />}}
+
+
+You can now try it out by connecting a Syncplay client to your server, using `your.domain:8999`
+
+{{< imgproc syncplay_client Fill "956x697" />}}
+
+That's it! Now, you can read the reference below for more advanced topics.
+
+**If you do any changes to your SelfPrivacy module and push it to your git repository, these changes will be applied with the next server upgrade.**
+
+## Reference
+
+### Flake metadata
+
+Here is a general overview of all metadata options in the `flake.nix` file.
+
+```nix
+{
+ description = "Flake description";
+
+ outputs = { self }: {
+ nixosModules.default = import ./module.nix;
+ configPathsNeeded =
+ builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
+ meta = {lib, ...}: {
+ # Schema version
+ spModuleSchemaVersion = 1;
+ # Must be the same name as flake and Systemd slice
+ id = "jitsi-meet";
+ # Service name displayed to a user
+ name = "JitsiMeet";
+ # Description displayed to a user
+ description = "Jitsi Meet is a free and open-source video conferencing solution.";
+ # Icon of the service
+ svgIcon = builtins.readFile ./icon.svg;
+ # Do we need to show URL in the UI? True by default
+ showUrl = true;
+ # If there are several subdomain options, which one to use to generate the URL?
+ primarySubdomain = "subdomain";
+ # Can be moved to another volume?
+ isMovable = false;
+ # Is required for SelfPrivacy operation?
+ isRequired = false;
+ # Can be backed up by API?
+ # Implied to be TRUE by default
+ canBeBackedUp = true;
+ # Description of the backup
+ backupDescription = "Secrets that are used to encrypt the communication.";
+ # Systemd services that API checks and manipulates
+ systemdServices = [
+ "prosody.service"
+ "jitsi-videobridge2.service"
+ "jicofo.service"
+ ];
+ # A unix user used by this service
+ # By default implied to be the same as the service ID
+ user = "jitsi-meet";
+ # A unix group used by this group
+ # By default implied to be the same as the user
+ group = "jitsi-meet";
+ # Folders that have to be moved or backed up
+ # Ownership is implied by the user/group defined above
+ folders = [
+ "/var/lib/jitsi-meet"
+ ];
+ # Same as above, but if you need to overwrite ownership
+ ownedFolders = [
+ {
+ path = "/var/lib/prometheus";
+ owner = "prometheus";
+ group = "prometheus";
+ }
+ ];
+ # PostgreSQL databases to back up
+ postgreDatabases = [];
+ # Licenses of this service
+ license = [
+ lib.licenses.asl20
+ ];
+ # Homepage for this service
+ homepage = "https://jitsi.org/meet";
+ # Git repository with the sources of this service
+ sourcePage = "https://github.com/jitsi/jitsi-meet";
+ # What is our support level for this service?
+ # Supported values:
+ # - normal
+ # - deprecated
+ # - experimental
+ # - community
+ supportLevel = "normal";
+ };
+ };
+}
+```
+
+#### spModuleSchemaVersion
+
+Integer, required. Set it to `1`. We will increment it in case we make backwards incompatible changes to our module schema.
+
+#### id
+
+**String, required.**
+
+An ID of your module. Generally, it is the same as the name of the service your module provides.
+
+Alphanumeric (`A-Za-z0-9`) symbols and hyphens (`-`) are allowed. Your systemd slice must have the same name, but with hyphens (`-`) replaced by underscores (`_`). It is implied by default that the unix user and group names are the same as the ID. If they are not, you will have to define them separately.
+
+#### name
+
+**String, required.**
+
+A display name of your module. Generally, it is a display name of the service your module provides.
+
+It will be shown to the user in the app.
+
+#### description
+
+**String, required.**
+
+A description of your module. Generally, it is a description of the service your module provides.
+
+It will be shown to the user in the app.
+
+#### svgIcon
+
+**String, required.**
+
+An icon of your module. Generally, it is an icon of the service your module provides.
+
+Usually defined like this: `svgIcon = builtins.readFile ./icon.svg;`
+
+Place an icon into the `icon.svg` file. It has the following requirements:
+
+- Icon must only use the black color. It will be recolored by an app depending on the user's theme and service's state.
+- Icon must be a square and have a square view box.
+- Try to flatten the icon and minimize its size.
+
+#### showUrl
+
+**Bool, optional. Implied to be true by default.**
+
+If **true**, the app will show an "open in browser" button in the interface.
+
+Usually turned off for the services that don't provide a web interface.
+
+#### primarySubdomain
+
+**String, optional.**
+
+By default, API looks at the `subdomain` option to determine service's URL. If your service has multiple subdomain, you can set which subdomain option should be used during URL generation.
+
+The option you provide here MUST have a type of `string` and a widget set to `subdomain` in its metadata.
+
+#### isMovable
+
+**Bool, optional. Implied to be false by default**
+
+If **true**, API will allow the user to move the module's floders between disk volumes. In this case, your module MUST have `folders` or `ownedFolders` defined, and a `location` option in your module options is also required. Refer to [Mounting user data](#mounting-user-data) section.
+
+If **false**, API won't allow the user to move the data between disk volumes.
+
+#### isRequired
+
+**Bool, optional. Implied to be false by default**
+
+If **true**, the option to disable or enable the module will be unavailable.
+
+Custom modules MUST set this to `false`.
+
+#### canBeBackedUp
+
+**Bool, optional. Implied to be true by default**
+
+If **true**, API will allow the user to make backups of this module. It will back up defined `folders`, `ownedFolders` and `postgreDatabases`.
+
+If **false**, backups feature will be disabled for this module.
+
+#### backupDescription
+
+**String, optional. Implied to be "No backup description found!" by default.**
+
+If you set `canBeBackedUp` to `true`, you must define a brief description of what will be backed up. Here are some examples:
+
+- Password database, encryption certificate and attachments.
+- Git repositories, database and user data.
+- All the files and other data stored in Nextcloud.
+
+Keep it short, in a single sentence.
+
+#### systemdServices
+
+**A list of strings, required.**
+
+Here you must define all sytsemd units that API must manage. Use full forms, such as `forgejo.service`, instead of `forgejo`.
+
+API will:
+
+- Track their status
+- Allow the user to restart them
+- Stop these services during backup restoration or when moving between volumes
+
+Generally, all service daemons go here. Utility one-shot units can be omitted here.
+
+Examples:
+
+```nix
+# Fogejo is a single service.
+systemdServices = [
+ "forgejo.service"
+];
+# PHP software on NixOS has units like these
+systemdServices = [
+ "phpfpm-roundcube.service"
+];
+# Here, we do not include services such as
+# nextcloud-setup, nextcloud-cron or nextcloud-update-db
+# because they do not represent the current state of the service.
+systemdServices = [
+ "phpfpm-nextcloud.service"
+];
+# Jitsi has several services
+systemdServices = [
+ "prosody.service"
+ "jitsi-videobridge2.service"
+ "jicofo.service"
+];
+```
+
+#### user
+
+**String, optional. Implied to be the same value as `id` by default.**
+
+The unix user of the service provided by this module. Used to ensure ownership of data folders.
+
+#### group
+
+**String, optional. Implied to be the same value as `user` by default.**
+
+The unix group of the service provided by this module. Used to ensure ownership of data folders.
+
+#### folders
+
+**List of strings, optional.**
+
+A list of folders that have to be backed up and moved between volumes. Generally, you should gut here folders that you bind mount somewhere in `/var/lib`. It is implied that these folders are owned by `user` and `group` described above. If you need the folder to be owned by someone else, use `ownedFolders`.
+
+All folders here must be bind mounted! Refer to [Mounting user data](#mounting-user-data) section for more info.
+
+Examples:
+
+```nix
+folders = [
+ "/var/lib/gitea"
+];
+folders = [
+ "/var/lib/nextcloud"
+];
+# Due to historical reasons, our Vaultwarden module
+# has two folders.
+folders = [
+ "/var/lib/bitwarden"
+ "/var/lib/bitwarden_rs"
+];
+```
+
+#### ownedFolders
+
+**List of attrsets, optional.**
+
+A list of folders that have to be backed up and moved between volumes, but require ownership other than defined in `user` and `group`.
+
+All folders here must be bind mounted! Refer to [Mounting user data](#mounting-user-data) section for more info.
+
+Examples:
+
+```nix
+ownedFolders = [
+ {
+ path = "/var/lib/prometheus";
+ owner = "prometheus";
+ group = "prometheus";
+ }
+];
+```
+
+#### postgreDatabases
+
+**List of strings, optional**
+
+A list of database names, that must be backed up. It is your responsibility to ensure that they exist. Refer to [PostgreSQL databases](#postgresql-databases) section for more info.
+
+#### license
+
+**List of licenses, optional**
+
+A list of license objects. Full list of licenses available: [https://github.com/NixOS/nixpkgs/blob/master/lib/licenses.nix](https://github.com/NixOS/nixpkgs/blob/master/lib/licenses.nix)
+
+Examples:
+
+```nix
+# GNU Affero General Public License v3.0 only
+license = [
+ lib.licenses.agpl3Only
+];
+# GNU General Public License v3.0 or later
+license = [
+ lib.licenses.gpl3Plus
+];
+# Apache License 2.0
+license = [
+ lib.licenses.asl20
+];
+# BSD 3-clause "New" or "Revised" License
+license = [
+ lib.licenses.bsd3
+];
+```
+
+#### homepage
+
+**String, optional**
+
+URL to the homepage of the service.
+
+#### sourcePage
+
+**String, optional**
+
+URL to the source code of the service.
+
+#### supportLevel
+
+**Enum, required**
+
+The support level of this module. One of the following values:
+
+- `normal` — A general support level for official modules.
+- `deprecated` — Deprecated modules that we provide as is.
+- `experimental` — Experimental modules where we can't guarantee stable operation yet.
+- `community` — Modules managed by community.
+
+If you make a custom SelfPrivacy module we advise you to set `community` value.
+
+### Module options
+
+All module options are defined in the following structure:
+
+```nix
+options.selfprivacy.modules. = {
+