Integration with UCS/Join

From Univention Wiki

Jump to: navigation, search

The join script is a fundamental feature of UCS. UCS is used to run and administrate a domain. New computers may "join" the domain. The computer searches for the Domain Controller Master (DC Master) and adds itself to LDAP (hostname, IP address, etc). Join scripts are used to "join software packages" into the domain. This means that if you install your App, it may need to register important bits somewhere and make some changes in the domain.

The domain is administrated by manipulating the core database on the DC Master, the LDAP database. Normally, this is done by using tools provided by Univention, mainly the Univention Directory Manager (UDM).

UDM needs Admin credentials to work on any other system then the DC Master. For that, calling UDM in the postinst of a package will not work in general.

For App Providers, a Join Script functions as a postinst of the App. But it has write access to the LDAP database (even when not installed on the DC Master).

Join scripts that fail to run through do not abort the installation. Instead, administrators are notified that a join script has not yet been executed.
Output of the Join scripts goes to /var/log/univention/join.log on the Docker Host.

Example Join script

. /usr/share/univention-appcenter/
eval "$(ucr shell ldap/base)"

joinscript_run_in_container sed -i /opt/myapp/some_script ... || die

udm users/user create "$@" --ignore_exists \
	--position "cn=users,$ldap_base" \
	--set username="myapp-systemuser" \
	--set lastname="My App" \
	--set password="$(makepasswd --chars 20)" \
	--option ldap_pwd || die

exit 0

Some points to the usage of udm in the script above:

  • UDM needs Admin credentials! Join scripts are called with Admin credentials. To pass them over to UDM, just *use "$@" in any udm call*.
  • Join scripts may be run more than once. The user may have already been created. That's why --ignore_exists has to be passed. Else udm fails.
  • $ldap_base was set by "eval "$(ucr shell ldap/base)"" a few lines earlier

Join Script Helper

When including a joinscript helper lib shipped with the App Center, you get access to some convenience funtions and environment variables:

. /usr/share/univention-appcenter/

will give you access to

$APP # app id
$APP_VERSION # app version
$SERVICE # app name
$CONTAINER # docker container id

These can be used throughout the script. In fact, many of the following functions make use of them. Functions added with this script:

Adds a domain wide user to LDAP that is not a real Domain User (like employees), but a mere "authentication account" that can be used to bind one's App to LDAP. The password will be stored on the Docker Host at /etc/$APP.secret. The DN will be "uid=$APP-systemuser,cn=users,$ldap_base".
There are issues with this approach with Apps being installed on multiple servers. Each server will essentially overwrite the password, leaving the content of /etc/$APP.secret on systems of the earlier installations oudated!
joinscript_add_simple_app_system_user "$@" --set mailPrimaryAddress=...
Returns whether or not the Docker Container is currently running. 0: Yes, 1: No. Can be used in an if.
joinscript_container_is_running || die "Container is not running"
Run one command inside the container. Returns the return code of the command.
joinscript_run_in_container service myapp restart || die "Could not restart the service"
Given a filename, prints the absolute path for the Docker Host for that file inside the container.
FILENAME="$(joinscript_container_file "/opt/$APP/my.cnf")"
Creates a file inside the container. Directories are created along the way. Prints the resulting filename just like "joinscript_container_file".
cat > "$(joinscript_container_file_touch /etc/myapp/config)" <<- EOF
Creating a new file inside the container
from outside and with no special package tweaking
Registers a (shipped) schema file semi automatically. See Docker_Apps/Files.
joinscript_register_schema "$@"
Adds a service name to the list of services for localhost. Useful for admins to keep track of what happens on which server. Also useful for you to see where this App is already running. Note that "$SERVICE" is already set to a reasonable value.
ucs_addServiceToLocalhost "$SERVICE" "$@"
Opposite of "ucs_addServiceToLocalhost". Should be run on a unjoin script.
ucs_removeServiceFromLocalhost "$SERVICE" "$@"
Returns 0 iff the service is not used in the domain. This means that no further installation of the App exists.
if ucs_isServiceUnused "${SERVICE}" "$@"; then
Saves "$VERSION" in a status file: /var/univention-join/status. This means that this join script is not run again, until the $VERSION increases. Should always be the last command (besides exit 0) so that nothing is "lost".
joinscript_remove_script_from_status_file app
Opposite of "joinscript_save_current_version". Should be run as the last command in an unjoin script. The corresponding join script will be deleted from the status file so that a reinstallation will run the (old) join script again. Without this command, UCS thinks that the join script has already been executed and skips it.
joinscript_remove_script_from_status_file "$APP"

Best practices


Secure successful execution of important commands with a meaningful error message.

udm users/user create "$@" ... || die "Could not create user"

Most, if not all, commands are important. You may use "die" everywhere.


It is a good idea to add a service name to the localhost, this is basically a human readable way of telling: "This system runs My App".

ucs_addServiceToLocalhost "${SERVICE}" "$@"

Other files

Not really a "best practice", but rather a workaround, but very versatile: The Join Script can be used to add files in the container and on the host where no dedicated integration exists. Or where the integration does not go far enough.

The following example adds a second Apache configuration, because WebInterface alone was not enough for the App KIX2016:

container_port="$(ucr get appcenter/apps/kix2016/ports/80)"
cat > "$apache_cfg" << EOF
ProxyPass /kix-web/$container_port/kix-web/ retry=0
ProxyPassReverse /kix-web/$container_port/kix-web/
service apache2 reload
Do not forget to cleanup these files in the unjoin script!


Unjoin is the opposite of the join script, called after the App is uninstalled, not after it is installed. It serves the same purpose, but as a postrm.


. /usr/share/univention-lib/
. /usr/share/univention-appcenter/


eval "$(ucr shell)"

ucs_removeServiceFromLocalhost "${SERVICE}" "$@"

if ucs_isServiceUnused "${SERVICE}" "$@"; then
  udm users/user remove --dn "uid=myapp-systemuser,cn=users,$ldap_base"

joinscript_remove_script_from_status_file "$APP"
exit 0
Personal tools