Addons

Addons are a way to extend the functionality of the core system. They are currently implemented as a way to add new service components to the system.

Hint

text Whenever [NAME] is mentioned in the article below, it should be replaced with the name of the addon in all lowercase letters.

Addon Structure

Addons could be stored in a single .zip file. However, for more complexity and functionality, they are now packaged and shipped in a .deb file. A good starting point is to use the addon creator inside the Libre Workspace Portal (Systemkonfiguration -> Addons -> Create Addon). It will generate the basic structure for you, even if you are not using any Docker containers. The addon control files are stored in the directory /var/lib/libre-workspace/modules/[NAME]. It should contain the following structure:

- /var/lib/libre-workspace/modules/[NAME]/
    - [NAME].conf
    - setup_[NAME].sh
    - update_[NAME].sh
    - remove_[NAME].sh
    - update_env.sh (optional)
    - [NAME].png / [NAME].svg / [NAME].jpg / [NAME].webp
    - LICENSE
    - patches/ (optional)
        - 1970-01-01_my_first_patch.sh (optional)
    - any other files needed for the installation of the addon (optional)

If you want to distribute your config, add a LICENSE file. Apache 2.0 is highly recommended: https://www.apache.org/licenses/LICENSE-2.0.html#apply )

An example of the structure of the NocoDB addon would be:

- /var/lib/libre-workspace/modules/nocodb/
    - patches/ (optional)
        - 1970-01-01_my_first_patch.sh (optional)
    - nocodb.conf
    - setup_nocodb.sh
    - update_nocodb.sh
    - remove_nocodb.sh
    - nocodb.png
    - LICENSE

Create the structure for your addon

  • To get your addon template, you can use the addon creator in the web interface at Systemkonfiguration -> Addons-Center -> Create Addon.

  • If you are not using Docker, you can simply insert a # into the field and fill in the other fields.

  • Unpack the generated .zip file and edit all files to your needs.

[NAME].conf

This file contains the configuration for the addon. It is a simple text file with the following format:

id="[NAME]"
name="[Nice Name]"
description="[DESCRIPTION]"
author="[AUTHOR]"
email="[EMAIL]"
url="[URL]"

An example of nocodb.conf would be:

id="nocodb"
name="NocoDB"
description="Database Management Tool"
author="NocoDB Developers"
email="db@example.com"
url="db"

The URL should be only the third-level domain name, without the protocol or the path. The shorter, the better. Make sure it doesn’t conflict with any other addon you want to install. You are able to add multiple URLs to the addon. Just separate them with a comma. For example: url="db,db2" In special cases, you can also keep the URL empty. Then set the URL to url="".

setup_[NAME].sh

This file is a simple shell script which is executed when the administrator installs the service (or module) in the system configuration. It is automatically executed as root. These variables are passed to the script:

  • $DOMAIN: The domain name of the service, for example: int.de

  • $ADMIN_PASSWORD: The password of the administrator which is used for the LDAP instance or the system user “systemv” which also has admin rights with sudo

  • $IP: The IP address of the server

  • $LDAP_DC: The domain component of the LDAP instance

  • $LANGUAGE_CODE: The language code of the system, like “de” or “en”

It is a good practice to store the config of the service in the /root/[NAME] directory, for example, the docker-compose.yml file. The addon detection is based on the existence of this folder. For example, even patches are not run if this folder /root/[NAME] does not exist. And it will be easier for system administrators to find the config of the service in the future. Also, you have to remember to add an entry to the /etc/caddy/Caddyfile to make the service accessible.

The current working directory is the root directory of the addon. It may be at /var/lib/libre-workspace/modules/[NAME]. Please do not use cd in all your scripts, because it could lead to unexpected behavior. At least if you are using it, make sure to run cd - at the end.

An example of setup_nocodb.sh would be:

#!/bin/bash
# This script gets three variables passed: $DOMAIN, $ADMIN_PASSWORD, $IP, $LDAP_DC, $LANGUAGE_CODE
mkdir -p /root/nocodb
# Don't forget to escape " with a backslash:
echo "
services:
  nocodb:
    depends_on:
      root_db:
        condition: service_healthy
    environment:
      NC_DB: \"mysql2://root_db:3306?u=noco&p=faiTh8ra&d=root_db\"
    image: \"nocodb/nocodb:latest\"
    ports:
      - \"23260:8080\"
    restart: unless-stopped
    volumes:
      - \"./nc_data:/usr/app/data\"
  root_db:
    environment:
      MYSQL_DATABASE: root_db
      MYSQL_PASSWORD: faiTh8ra
      MYSQL_ROOT_PASSWORD: faiTh8ra
      MYSQL_USER: noco
    healthcheck:
      retries: 10
      test:
        - CMD
        - mysqladmin
        - ping
        - \"-h\"
        - localhost
      timeout: 20s
    image: \"mysql:8.0.32\"
    restart: unless-stopped
    volumes:
      - \"./db_data:/var/lib/mysql\"
" > /root/nocodb/docker-compose.yml

docker compose -f /root/nocodb/docker-compose.yml up -d

echo "db.$DOMAIN {
    #tls internal
    reverse_proxy localhost:23260
}

" >> /etc/caddy/Caddyfile

# If domain is "int.de" uncomment the tls internal line for internal https
# IT IS RECOMMENDED THAT YOU DO NOT EDIT THESE 3 LINES unless you know what you're doing.
# (Usually, you don't have to edit them at all)
if [ "$DOMAIN" = "int.de" ]; then
  sed -i 's/#tls internal/tls internal/g' /etc/caddy/Caddyfile
fi

systemctl restart caddy

You can get inspiration for more complicated setups here: https://github.com/Jean28518/libre-workspace/tree/main/src/usr/lib/libre-workspace/modules

update_[NAME].sh

This file is a simple shell script which is executed when the administrator updates the service (or module) in the system configuration. It is normally executed as root every day if the admin has enabled the automatic updates of this service. If you don’t want to update the service, just leave the file empty. But it’s important to have the file.

An example of update_nocodb.sh would be:

#!/bin/bash
docker compose -f /root/nocodb/docker-compose.yml pull
docker compose -f /root/nocodb/docker-compose.yml up -d

remove_[NAME].sh

This file is a simple shell script which is executed when the administrator removes the service (or module) from the system configuration. It is automatically executed as root. It is a good practice to remove the complete folder /root/[NAME] directory, because the addon detection is based on the existence of this folder. It is also good practice to remove all corresponding data. For example, if you have a database, you should remove the database and the database user.

The current working directory is the root directory of the addon. It may be at /var/lib/libre-workspace/modules/[NAME]. Please do not use cd in all your scripts, because it could lead to unexpected behavior. At least if you are using it, make sure to run cd - at the end.

An example of remove_nocodb.sh would be:

#!/bin/bash
# This script gets three variables passed: $DOMAIN, $ADMIN_PASSWORD, $IP, $LDAP_DC
docker compose -f /root/nocodb/docker-compose.yml down --volumes
rm -rf /root/nocodb


# Remove the entry from the Caddyfile
libre-workspace-remove-webserver-entry db.$DOMAIN

systemctl restart caddy

update_env.sh

This file is a simple shell script which is executed when the administrator updates the environment of the system configuration, which could be the master password (which also changes the LDAP administrator password) or the IP address of the server under which it is accessible. If your addon doesn’t rely on the IP address or the master password, you can ignore this file. It is then not necessary to have it.

In our example of NocoDB, we don’t need this file, because we don’t rely on the IP address or the master password. So we don’t even have to create this file.

patches

For future updates of the addon, you can add patches to the patches folder (which is optional). It is a good practice to name the patch with the date when it was created, so you can easily see the order of the patches. These patch scripts are executed after a daily backup and update of the system or daily at 02:00 am. The run order is based on the filename. The patch with the oldest date is executed first. The patch scripts are executed as root and the current working directory is the root directory of the addon. The environment variables $DOMAIN, $ADMIN_PASSWORD, $IP, $LDAP_DC, and $LANGUAGE_CODE are passed to the script and are available in the script.

The patch should only patch your addon once. But for this, you have to check by yourself if the patched settings are present or not. This can be different for every single patch. Also, it is highly recommended to keep your addon consistent over time, so it should disable itself after 1 year of its release.

Here you can see an example of a Redis patch for Nextcloud:

#!/bin/bash

# IS THIS PATCH OLDER THAN 365 DAYS?
# Get the current file name
FILE_NAME=$(basename $0)
# Get the date of the filename which is like this: 2024-06-25
DATE=${FILE_NAME:0:10}
# Check if the file is older than 365 days
if [ $(( ($(date +%s) - $(date -d $DATE +%s)) / 86400 )) -gt 365 ]; then
  echo "Patch is older than 365 days. Exiting patch."
  exit 0
fi


# Check if we need to apply the patch
# Is redis installed?
if [ -x "$(command -v redis-server)" ]; then
  echo "Redis is already installed. Exiting patch."
  exit 0
fi

# BEGIN APPLYING PATCH
# Install redis and php packages
apt-get install redis php-redis php-apcu php-memcache pwgen -y

# ... do the rest

Libre Workspace Commands

To make the addon development easier and more consistent, libre-workspace comes with some helper commands you can/should use in your scripts.

libre-workspace-add-api-key

This command adds an API key to the libre workspace portal. It is useful if your addon needs to access the libre workspace portal API.

Usage: libre-workspace-add-api-key <name> <permissions> <expire_date>
Example: API_KEY=$(libre-workspace-add-api-key 'My API Key' 'linux_client,administrator' '2025-12-31')
If you want to set no expiration date, use '0' as value

libre-workspace-add-oidc-client

This command adds an OIDC client to the libre workspace portal. It is useful if your addon needs to access the libre workspace portal via OIDC.

Usage: libre-workspace-add-oidc-client <name> <client_id> <client_secret> <redirect_uri>
Example: libre-workspace-add-oidc-client 'MyOIDCClient' 'my-client-id' 'my-client-secret' 'https://my-addon.$DOMAIN/callback'

libre-workspace-remove-oidc-client

With this command, you can remove an OIDC client from the libre workspace portal.

Usage: /usr/bin/libre-workspace-remove-oidc-client <name>
Example: libre-workspace-remove-oidc-client 'MyOIDCClient'

libre-workspace-generate-secret

Like pwgen, this command generates a random secret. It is useful if your addon needs a random password or secret.

Usage: libre-workspace-generate-secret [length]
Example: MY_PASSWORD=$(libre-workspace-generate-secret 32)
If no length is given, the default length is 32

libre-workspace-remove-webserver-entry

With this command, you can remove a webserver entry from the Caddy webserver configuration.

Usage: libre-workspace-remove-webserver-entry <url>
Example: libre-workspace-remove-webserver-entry my-addon.$DOMAIN

libre-workspace-send-mail

With this command, you can send an email via the libre workspace portal’s email configuration to the Administrator if he has set it up.

Usage: libre-workspace-send-mail <subject> <message> [attachment_path (optional)]
Example: libre-workspace-send-mail "My Addon" "My Addon was installed successfully with this password: $MY_PASSWORD"
Example: libre-workspace-send-mail "My Addon" "My Addon was installed successfully" "/path/to/attachment.txt"

General Tips

  • Never experiment on production systems. Always test your scripts on a test system first.

  • It is a good practice to run the commands line by line manually on a test system to see if everything works as expected.

  • Please don’t use the $ADMIN_PASSWORD variable anymore. Instead, you can get a password with libre-workspace-generate-secret.