Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Momo Cloud

Momo cloud is a cloud solution for individuals and small organizations.

Our focus is to provide the following benefits:

  • Single-Sign-On everywhere
  • Integrated services
  • Automated & tested backups
  • Automatic https on all services
  • Federated services
  • Full disk encryption
  • Reproducibility: take your config to a new machine and deploy it, then restore a backup to have a 1-1 clone.

Services & Features

Communication

FeatureService used
ContactsNextcloud
E-MailDovecot & Postfix
ChatMatrix
Video CallingNextcloud talk
Mailing listsListmonk

Files

FeatureService used
File Server & SyncNextcloud
Document managementPaperless

Project management & tasks

FeatureService used
CalendarNextcloud
Project managementOpenProject
Ticketing toolZammad
TimetrackingSolidTime

Finances

FeatureService used
InvoicingInvoicePlane

Knowledge management

FeatureService used
WikiMediaWiki
Project ManagementOpenProject

Software development

FeatureService used
Git serverForgejo

Integrations

Integrations that currently work are the following:

  • All services can use the built-in E-Mail server
  • OpenProject can use the nextcloud file storage

momo-cloud documentation

This directory holds a collection of notes and documentation for momo-cloud usage.

Backups with restic

Paperless

Nighly backups can be enabled by using the option momo-cloud.backups.repos.${repo}.

To restore a paperless backup, one option is to use the following commands, which use document_exporter and document_importer paperless manage.py CLI subcommands under the hood.

Before starting the restore, stop the paperless process:

sudo mkdir -p /var/lib/restore/paperless
sudo restic-paperless-${repo} restore latest --tag paperless --target /var/lib/restore/paperless
paperless-restore /var/lib/restore/paperless

Modules

Nextcloud Whiteboard Server

Create & collaborate on an infinite canvas! https://github.com/nextcloud/whiteboard

Usage

momo-cloud.nextcloud-whiteboard = {
  enable = true;
  secretFile = config.age.secrets."nextcloud-whiteboard-server-secrets".path;
};
momo-cloud.nextcloud.whiteboard = {
  enable = true;
  jwtSecretFile = config.age.secrets."nextcloud-whiteboard-jwt-secret".path;
};

Secrets

secretFile

Generate the JWT secret, used by services.nextcloud-whiteboard-server.secretFiles:

openssl rand -hex 24

This secrets needs to be in the format expected by systemd’s EnvironmentFile directory, for example:

JWT_SECRET_KEY=<secret-string>

jwtSecretFile

Should contain the same string as secretFile, in this format:

<secret-string>

Used to automatically configure the Nextcloud whiteboard app.

LimeSurvey

Open source survey application. https://www.limesurvey.org

Prerequisites

Limesurvey does not play nice with postgres. The database connection via a socket is not supported, and as a result the nixpkgs module is also halfbroken with postgres.

This module does not use our usual single sign-on process via OIDC. Support via a plugin is possible, but still needs to be implemented.

There are currently no working tests for this module.

As a workaround Momo Cloud vendors its own limesurvey module, which creates the postgres user together with a password allowing for TCP connections. This opens up the possibility of a createLocal.

Limesurvey uses the yii php framework, currently in Version 1.1 which is EOL at the end of 2026. There appear to not be any public upgrade plans.

Usage

momo-cloud.limesurvey = {
  enable = true;
  databasePasswordFile = config.age.secrets."limesurvey-database-password".path;
  encryptionKeyFile = config.age.secrets."limesurvey-encryption-key".path;
  encryptionNonceFile = config.age.secrets."limesurvey-encryption-nonce".path;
  oidcSecretFile = config.age.secrets."limesurvey-oidc-secret".path;
};

Secrets

To generate the encryption key for encryptionKeyFile, you can use xxd:

xxd -u -l 32 -p /dev/urandom

The encryption nonce for encryptionNonceFile works the same, but with 24 bytes:

xxd -u -l 24 -p /dev/urandom

To generate the OIDC secret for oidcSecretFile, used by services.kanidm.provision, you can use xxd:

xxd -u -l 32 -p /dev/urandom

To generate the postgres database password for databasePasswordFile, you can use xxd:

xxd -u -l 32 -p /dev/urandom

After setup, the admin user account will have the password password. This needs to be changed!

OIDC

An OIDC Plugin is shipped with the momo cloud LimeSurvey module, however it is not enabled by default.

The reason for this is that automatic user and group creation does not exist. You will have to log in to an existing admin user and create user accounts manually. These will then be able to log in via OIDC.

Plugins

Plugins can be added as simple zip file packages. They are available for installation in LimeSurvey, however they are not automatically installed or activated since LimeSurvey does not provide an easy API or CLI to do this.

Kanidm

Self-hosted lightweight identity management service. https://kanidm.github.io

Two groups are created by default:

  1. admins: this will contain all admin groups for enabled applications (e.g. forgejo_admins)
  2. users: this will contain all user groups for enabled applications (e.g. forgejo_users)

Usage

momo-cloud.authentication = {
  enable = true;
  adminPasswordFile = config.age.secrets."kanidm-admin-password".path;
  idmAdminPasswordFile = config.age.secrets."kanidm-idm-admin-password".path;
  mailPasswordFile = config.age.secrets."kanidm-mail-password".path;
};

Secrets

adminPasswordFile

Generate the password:

openssl rand -hex 24

idmAdminPasswordFile

Generate the password:

openssl rand -hex 24

mailPasswordFile

Should be the password for the SMTP mail account.

User management with kanidm

Example ~/.config/kanidm:

["staging"]
uri = "https://account.staging.example.com"

["prod"]
uri = "https://account.example.com"

Initial login:

KANIDM_INSTANCE=staging kanidm login -D idm_admin

Creating users:

KANIDM_INSTANCE=staging kanidm person create $username $displayName

Updating a users email address:

KANIDM_INSTANCE=staging kanidm person update $username --mail "initial@email.address"

Adding users to groups:

KANIDM_INSTANCE=staging kanidm group add-members paperless_users $username

Generating a link to be sent to user for credentials creation:

KANIDM_INSTANCE=staging kanidm person credential create-reset-token $username

Change login timeout, for example to 3 months:

KANIDM_INSTANCE=staging kanidm group account-policy auth-expiry idm_all_accounts 7776000

Update

Pre-update check:

Example:

sudo -u kanidm /nix/store/wwlis495qq380mvwkccw23ixgg92dz61-kanidm-1.6.4/bin/kanidmd domain upgrade-check -c /nix/store/w5bipks8vzzs83xcx0sj8wi5n791lskc-server.toml

📜 Using config file: "/nix/store/w5bipks8vzzs83xcx0sj8wi5n791lskc-server.toml"
00000000-0000-0000-0000-000000000000 INFO     i [info]: Starting Kanidmd | version: 1.6.4
00000000-0000-0000-0000-000000000000 WARN     🚧 [warn]: WARNING: /nix/store/w5bipks8vzzs83xcx0sj8wi5n791lskc-server.toml has 'everyone' permission bits in the mode. This could be a security risk ...
00000000-0000-0000-0000-000000000000 INFO     i [info]: Running domain upgrade check ...
00000000-0000-0000-0000-000000000000 INFO     i [info]: domain_name            : account.staging.momo.koeln
00000000-0000-0000-0000-000000000000 INFO     i [info]: domain_uuid            : f7496e7b-a183-4cc7-bd14-6eb02ca1d288
00000000-0000-0000-0000-000000000000 INFO     i [info]: domain_current_level   : 9
00000000-0000-0000-0000-000000000000 INFO     i [info]: domain_upgrade_level   : 10
Logging pipeline completed shutdown

Monitoring server

Loki logs retention

We keep logs in loki for two weeks, configured by retention_period.

Listmonk

Open source mailing list application. https://listmonk.app

Prerequisites

By default, listmonk does not create an admin user, instead offering a setup page on initial startup. This should be prevented by creating an initial admin user with environment variables. See the secretFile option.

Mutable vs. Immutable settings

Even though a lot of settings are provisioned with nix, they are written to the database and editable via the listmonk UI.

Some settings (like the OIDC settings and root URL) will be overwritten every time the application gets redeployed. Other settings will only be written once during initial install (like the SMTP mailbox).

Usage

momo-cloud.listmonk = {
  enable = true;
  oidcSecretFile = config.age.secrets."listmonk-oidc-secret".path;
  secretFile = config.age.secrets."listmonk-secret-file".path;
};

Secrets

To generate the OIDC secret for oidcSecretFile, used by services.kanidm.provision, you can use xxd:

xxd -u -l 32 -p /dev/urandom

The secret file is a file that will be handed to the listmonk service as a systemd EnvironmentFile. There are two variables that should always be set:

  1. LISTMONK_ADMIN_USER: The admin user name
  2. LISTMONK_ADMIN_PASSWORD The admin user password

To generate the admin password:

xxd -u -l 32 -p /dev/urandom

Immich

Open source Photo organizing app with local AI. https://immich.app

Prerequisites

By default, immich does not create an admin user, instead offering a setup page on initial startup. This should be prevented by creating an initial admin user with environment variables. See the adminPasswordFile option.

Usage

momo-cloud.immich = {
  enable = true;
  oidcSecretFile = config.age.secrets."immich-oidc-secret".path;
  adminPasswordFile = config.age.secrets."immich-admin-password".path;
};

Secrets

To generate the OIDC secret for oidcSecretFile, used by services.kanidm.provision, you can use xxd:

xxd -u -l 32 -p /dev/urandom

The admin password file holds the pasword for the initial admin user that gets created during the setup. To generate:

xxd -u -l 32 -p /dev/urandom

Vaultwarden

Open source password manager. https://github.com/dani-garcia/vaultwarden

Prerequisites

Vaultwarden needs a properly configured SMTP server to complete a user registration.

Usage

momo-cloud.vaultwarden = {
  enable = true;
  oidcSecretFile = config.age.secrets."vaultwarden-oidc-secret".path;
  envFile = config.age.secrets."vaultwarden-secrets-env".path;
};

The secrets in the env file that need to be set are SSO_CLIENT_SECRET and SMTP_PASSWORD.

Example:

SSO_CLIENT_SECRET="secret"
SMTP_PASSWORD="password"

There are two kanidm groups for using vaultwarden:

  1. vaultwarden_admins: grants admin rights (not implemented)
  2. vaultwarden_users: allows users to access the application

Secrets

To generate the OIDC secret for oidcSecretFile, used by services.kanidm.provision, you can use xxd:

xxd -u -l 32 -p /dev/urandom

Zammad

Open source Helpdesk and Support tool. https://zammad.com

Prerequisites

By default, zammad is not connected to OIDC. This has to be done manually with the admin account.

Usage

momo-cloud.zammad = {
  enable = true;
  secretKeyBaseFile = config.age.secrets."zammad-secret-base-file".path;
};

Secrets

To generate the secret for secretKeyBaseFile, you can use openssl:

openssl rand -hex 64

Moving to a different Domain

If the TLD is changed, there are two manual changes that need to change in the zammad config. You can perform these either with the admin account or via the rails console after you’ve deployed the instance with the new domain:

To start the rails console, log in via ssh and run the following:

sudo zammad run rails c
  1. Update the fqdn: see the zammad docs
  2. Update the OIDC config

For the second part, you’ll have to update the kanidm issue URL.

First, get the current config from the database:

c = Setting.get('auth_openid_connect_credentials')
=> {"display_name" => "Teylen Cloud ID",
 "identifier" => "zammad",
 "issuer" => "https://account.OLD_DOMAIN/oauth2/openid/zammad",
 "uid_field" => "",
 "scope" => "",
 "pkce" => true}

Then, update the issuer string:

c[:issuer] = "https://account.NEW_DOMAIN/oauth2/openid/zammad"
=> "https://account.NEW_DOMAIN/oauth2/openid/zammad"

Last, write this out to the database:

Setting.set('auth_openid_connect_credentials', c)

Openproject

Project management software. https://openproject.com

Usage

momo-cloud.openproject = {
  enable = true;
  keyBaseFile = config.age.secrets."openproject-keybase".path;
  oidcSecretFile = config.age.secrets."openproject-oidc-secret".path;
  environmentFile = config.age.secrets."openproject-env-secrets".path;
  extraSeedEnvironmentFile = config.age.secrets."openproject-seeder-secrets".path;
};

Secrets

keyBaseFile

Generate the keybase:

openssl rand -hex 32

oidcSecretFile

Generate the OIDC secret, used by services.kanidm.provision:

openssl rand -hex 48

environmentFile

The environmentFile for secrets env should at least contain the SMTP password:

OPENPROJECT_SMTP__PASSWORD="smtp-password"

extraSeedEnvironmentFile

The seeder secrets env looks like this:

OPENPROJECT_OPENID__CONNECT_KANIDM_SECRET="oidc-secret"
OPENPROJECT_SEED__ENTERPRISE__TOKEN='-----BEGIN OPENPROJECT-EE TOKEN-----
eyJkYXRhIjoiLzhGUUJkUmhMbHN6K0JKS2htVVJyTUUrQ2lDSXpsdEs0TzY2
UTFXVDEwUUxXVU1KYm5Fd1p4bkJ0NW1HLzBta093aWR1UVBIN2FtV0pzNERP
WmxwSDQycjBhbDAxM2pYSnBuOGxwL2E3ZUNIc3NCenk1Q1hESzk2VmduUkhS
d1oybllzaVMrSnA2QnE2Zk8xYThFbld5NGN6ZXR2VThaL3JEMzdDVEpuMnhZ
QXhvbzFaZ1VNcmVMSERlSzZnRU9qWFBSQStHWWVKcEtqQ0hnWGxmSzcrQ1ZT
S0p0RUxnUTRTRVI2aVZyVnlPREIwdkZSUVZjdGYrc2IxL2NwcTVqRjBsdURF
WVpmRVZTLzlkbDA5UVAvY3dobnhZUDBOVUt6Wi9CZ1pNUHVHbG52aVA0SHBa
RW1ockZXcUpyYnIvckVPQzdqZXpkSkQxdGw5Y0F6cE1QVk9RYjZGZ3RqQWlw
RnFERXB2MGlXVU5iWWJDTzhDYlNmVVZodnBENEVEQ2FmaTZxZTVlN3ZVNHlR
Vkt6M0hiSXZvQVdOWFRPc2JaQzJTTVpTVkFvMjV0MD0iLCJrZXkiOiJFZG1E
QVpFclpoZWF6YmJKT3BxOCt0bnRza1dCdFU5T1hudllyaEtJSEdKUFZxTHQ4
NHV2Mjd5RS94a1d5dnVxNFRjUlN4Q0JKTDJnOEg2QitjT1V6RjAxV215bzZ6
MVpEenFqcm1neksyS2RkZzZabm50bWRDTnZFRVVGeW5BTGs1VkN2LyszUis2
Y2JSK2pCTkxBc2NhbGY1WE9vYUxWQ1JzTzQ0c251TGNid1JFYTdSVm84b09D
bkNsUTZYMzRoUXBYRG52R0hXWTJPMVpscXhGZ1FwWE5MUHEyUDlxN2VQbTZp
a1RJV3lwL0Nhc3Y0cjhEQS9LWE4zaXUwcGlEaS80eFR5NFpiNXk4RXJvQUt3
emJFTHVYL1NDeU9XWWoyamN4WjluQkcxd05tNmsvbnd5N25wSC9uODduTklj
WFBUOW9BL0RQcWU4c0NkYnBSV0x3L290cElqYVc3Nk5GRzFSbmNiOHN5STJl
Zi9PZFZBZkFhMU1CMFNrcEtVR0w0RGlPQkl4T3FrK2ZvRUpjWEdTallGUTJN
M1Z0VUdNMjhFaHZOTnlVT01XY1ZYV2h2cDZGYVVVRXp6d0pYcy9kQ1dvSXBN
L2E1ZnpHYjR3MjBXWHZxbGpYc1hac2dRYUx0bDk2aGd4TVB2d0gwREdncjhl
bEh4WldqRFR4VEozbXNxR1drYXkzRUVWUmxwY0dtQjJ2aGViUjJxVUNWY1JC
Um9PZmszZmVQMFN4V21KMVVMaE1XVHdBUDAxRG85QTdQV2pMVWdoeEpmc0Rh
ekNrUDBJVk0wbk5LRi82T3JBc3hibkpiUXdzVk85OGlqcEJ5OFlwT0d3RWFo
RGZ1UXJGQW56VE1aWkhIRWRwQUFMcXIwNFJRMGxEZUtlaHVYUHB4MlhzVm1O
UWs0Yz0iLCJpdiI6InYwZmtWWlVlZ29BL3RUN084MGhoWUE9PSJ9
    -----END OPENPROJECT-EE TOKEN-----'

To generate the enterprise-token, follow the directions in the momo/openproject-token-js repository.

Forgejo

Self-hosted lightweight software forge. https://forgejo.org

Prerequisites

To allow SSH access to forgejo’s built-in SSH server, a firewall rule needs to be added to the host using the correct interface name. In the below usage example, the interface name is enp3s0.

Usage

momo-cloud.forgejo = {
  enable = true;
  sshListenInterface = "enp3s0";
  sshServerHostKeysFile = config.age.secrets."forgejo-ssh-host-key".path;
  repositorySigningPublicKeyFile = config.age.secrets."forgejo-repository-signing-key.pub".path;
  repositorySigningPrivateKeyFile = config.age.secrets."forgejo-repository-signing-key".path;
  oidcSecretFile = config.age.secrets."forgejo-oidc-secret".path;
};

There are two kanidm groups for using forgejo:

  1. forgejo_admins: grants admin rights (not implemented)
  2. forgejo_users: allows users to access the application

Secrets

oidcSecretFile

Generate the OIDC secret, used by services.kanidm.provision:

openssl rand -hex 24

sshServerHostKeysFile

Generate the SSH server host key with an empty passphrase, used by services.forgejo.settings.server.SSH_SERVER_HOST_KEYS:

ssh-keygen -t ed25519

repositorySigningPublicKeyFile and repositorySigningPrivateKeyFile

Generate the repository signing key with an empty passphrase, used by services.forgejo.settings."repository.signing".SIGNING_KEY Set this to the public key path. Note that the value for the SIGNING_KEY setting needs to be an absolute path to the public key, where the private key needs to be available in the same directory without the .pub suffix.

ssh-keygen -t ed25519

Troubleshooting

After initial deployment, a restart of nginx.service might be required because the nginx UNIX user needs to be part of the anubis group.

Forgejo

Self-hosted CI Actions runners for forgejo. https://forgejo.org

momo-cloud includes defaults for gitea-runners. They are automatically activated once you enable an instance.

This includes:

  • Enabling the docker service
  • Adding a user gitea-runner for the runners
  • Adding bridges to the trusted firewall interfaces

Usage

  services.gitea-actions-runner.instances = {
    atlas = {
      enable = true;
      url = "https://git.${config.networking.hostName}";
      name = config.networking.hostName;
      tokenFile = config.age.secrets.forgejo-actions-runner-token.path;
      labels = [
        "self-hosted:host"
        "fast-self-hosted:host"
      ];
    };
  };

Secrets

tokenFile

Found in forgejo as “Registration Token”, prepend with TOKEN=:

TOKEN=<registration token>