<!--
SPDX-FileCopyrightText: 2023 Bundesministerium des Innern und für Heimat, PG ZenDiS "Projektgruppe für Aufbau ZenDiS"
SPDX-License-Identifier: Apache-2.0
-->

<h1>Getting started</h1>

This documentation lets you create an openDesk evaluation instance on your Kubernetes cluster.

<!-- TOC -->
* [Requirements](#requirements)
* [Customize environment](#customize-environment)
  * [DNS](#dns)
  * [Domain](#domain)
    * [Apps](#apps)
  * [Private registries](#private-registries)
  * [Cluster capabilities](#cluster-capabilities)
    * [Service](#service)
    * [Networking](#networking)
    * [Ingress](#ingress)
    * [Container runtime](#container-runtime)
    * [Volumes](#volumes)
  * [Customize deployment](#customize-deployment)
  * [Connectivity](#connectivity)
    * [Ports](#ports)
      * [Web-based user interface](#web-based-user-interface)
      * [Mail clients](#mail-clients)
    * [Mail/SMTP configuration](#mailsmtp-configuration)
    * [TURN configuration](#turn-configuration)
    * [Certificate issuer](#certificate-issuer)
  * [Password seed](#password-seed)
  * [Install](#install)
  * [Install single app](#install-single-app)
  * [Install single release/chart](#install-single-releasechart)
  * [Using from external repository](#using-from-external-repository)
* [Access deployment](#access-deployment)
  * [Login](#login)
  * [Credentials](#credentials)
* [Uninstall](#uninstall)
<!-- TOC -->

Thanks for looking into the openDesk Getting Started guide. This document covers essential configuration steps to
deploy openDesk onto your Kubernetes infrastructure.

# Requirements

Detailed system requirements are covered on the [requirements](./docs/requirements.md) page.

# Customize environment

Before deploying openDesk, you must configure the deployment to fit your environment.
To keep your deployment up to date, we recommend customizing in `dev`, `test`, or `prod` and not in `default` environment
files.

> All configuration options and their default values can be found in files at [`helmfile/environments/default/`](../helmfile/environments/default/)

For the following guide, we will use `dev` as environment where variables can be set in
`helmfile/environments/dev/values.yaml.gotmpl`.

## DNS

The deployment is designed to deploy each application/service under a dedicated subdomain.
For your convenience, we recommend creating a `*.domain.tld` A-Record for your cluster Ingress Controller; otherwise, you must create an A-Record for each subdomain.

| Record name                   | Type | Value                                              | Additional information                                            |
|-------------------------------|------|----------------------------------------------------|-------------------------------------------------------------------|
| *.domain.tld                  | A    | IPv4 address of your Ingress Controller            |                                                                   |
| *.domain.tld                  | AAAA | IPv6 address of your Ingress Controller            |                                                                   |
| mail.domain.tld               | A    | IPv4 address of your postfix NodePort/LoadBalancer | Optional, mail should directly be delivered to openDesk's Postfix |
| mail.domain.tld               | AAAA | IPv6 address of your postfix NodePort/LoadBalancer | Optional, mail should directly be delivered to openDesk's Postfix |
| domain.tld                    | MX   | `10 mail.domain.tld`                               |                                                                   |
| domain.tld                    | TXT  | `v=spf1 +a +mx +a:mail.domain.tld ~all`            | Optional, use proper MTA record if present                        |
| _dmarc.domain.tld             | TXT  | `v=DMARC1; p=quarantine`                           | Optional                                                          |
| default._domainkey.domain.tld | TXT  | `v=DKIM1; k=rsa; h=sha256; ...`                    | Optional, DKIM settings                                           |
| _caldavs._tcp.domain.tld      | SRV  | 10 1 443 dav.domain.tld.                           | Optional, CalDav auto discovery                                   |
| _caldav._tcp.domain.tld       | SRV  | 10 1  80 dav.domain.tld.                           | Optional, CalDav auto discovery                                   |
| _carddavs._tcp.domain.tld     | SRV  | 10 1 443 dav.domain.tld.                           | Optional, CardDav auto discovery                                  |
| _carddav._tcp.domain.tld      | SRV  | 10 1  80 dav.domain.tld.                           | Optional, CardDav auto discovery                                  |

## Domain

As example base domain for your deployment we use `domain.tld` in this document.

A list of all subdomains can be found in `helmfile/environments/default/global.yaml.gotmpl`.

All subdomains can be customized. For example, _Nextcloud_ can be changed to `files.domain.tld` in `dev` environment:

```yaml
global:
  hosts:
    nextcloud: "files"
```

The domain has to be set either via `dev` environment:

```yaml
global:
  domain: "domain.tld"
```

or alternatively via environment variable:

```shell
export DOMAIN=domain.tld
```

> [!warning]
> Due to a limitation caused by a [bug in the SSSD subcomponent](https://github.com/SSSD/sssd/issues/7246), there
> is an upper bound on the total domain length used by openDesk. To avoid issues, we recommend keeping the openDesk
> base domain length below 94 characters.

### Apps

Depending on your ideal openDesk deployment, you may wish to disable or enable certain apps.
All available apps and their default values are located in `helmfile/environments/default/opendesk_main.yaml.gotmpl`.

| Component            | Name                        | Default | Description                    |
| -------------------- | --------------------------- | ------- | ------------------------------ |
| Certificates         | `apps.certificates.enabled`      | `true`  | TLS certificates               |
| ClamAV (Distributed) | `apps.clamavDistributed.enabled` | `false` | Antivirus engine               |
| ClamAV (Simple)      | `apps.clamavSimple.enabled`      | `true`  | Antivirus engine               |
| Collabora            | `apps.collabora.enabled`         | `true`  | Weboffice                      |
| CryptPad             | `apps.cryptpad.enabled`          | `true`  | Weboffice                      |
| dkimpy               | `apps.dkimpy.enabled`            | `false` | Postfix milter for DKIM        |
| Dovecot              | `apps.dovecot.enabled`           | `true`  | Mail backend                   |
| Element              | `apps.element.enabled`           | `true`  | Secure communications platform |
| Home                 | `apps.home.enabled`              | `true`  | Base domain portal redirect    |
| Jitsi                | `apps.jitsi.enabled`             | `true`  | Videoconferencing              |
| MariaDB              | `apps.mariadb.enabled`           | `true`  | Database                       |
| Memcached            | `apps.memcached.enabled`         | `true`  | Cache Database                 |
| MinIO                | `apps.minio.enabled`             | `true`  | Object Storage                 |
| Nextcloud            | `apps.nextcloud.enabled`         | `true`  | File share                     |
| Nubus                | `apps.nubus.enabled`             | `true`  | Identity Management & Portal   |
| OpenProject          | `apps.openproject.enabled`       | `true`  | Project management             |
| OX App Suite         | `apps.oxAppSuite.enabled`        | `true`  | Groupware                      |
| Postfix              | `apps.postfix.enabled`           | `true`  | MTA                            |
| PostgreSQL           | `apps.postgresql.enabled`        | `true`  | Database                       |
| Redis                | `apps.redis.enabled`             | `true`  | Cache Database                 |
| XWiki                | `apps.xwiki.enabled`             | `true`  | Knowledge management           |

For example, Jitsi can be disabled like this:

```yaml
apps:
  jitsi:
    enabled: false
```

## Private registries

By default, Helm charts and container images are fetched from OCI registries. These registries can be found in most cases
in the [openDesk/component section on openCode](https://gitlab.opencode.de/bmi/opendesk/components).

For untouched upstream artifacts that do not belong to a functional component's core, we use upstream registries
like Docker Hub.

Doing a test deployment will be fine with this setup. In case you want to deploy multiple times a day
and fetch from the same IP address, you might run into rate limits at Docker Hub. In that case, and in case you
prefer the use of a private image registry, you can configure these in
[your target environment](../helmfile/environments/dev/values.yaml.gotmpl.sample) by setting
- `global.imageRegistry` for a private image registry and
- `global.helmRegistry` for a private Helm chart registry.

```yaml
global:
  imageRegistry: "my_private_registry.domain.tld"
```

alternatively, you can use an environment variable:

```shell
export PRIVATE_IMAGE_REGISTRY_URL=my_private_registry.domain.tld
```

or for more granular control over repository overrides per registry (rewrites):

```yaml
repositories:
  image:
    dockerHub: "my_private_registry.domain.tld/docker.io/"
    registryOpencodeDe: "my_private_registry.domain.tld/registry.opencode.de/"
```

If authentication is required, you can reference `imagePullSecrets` as follows:

```yaml
global:
  imagePullSecrets:
    - "external-registry"
```

## Cluster capabilities

### Service

Some apps, like Jitsi and Dovecot, require HTTP and external TCP connections.
These apps create a Kubernetes service object.
You can configure whether `NodePort` (for on-premises), `LoadBalancer` (for cloud), or `ClusterIP` (to disable) should be
used:

```yaml
cluster:
  service:
    type: "NodePort"
```

### Networking

If your cluster does not have the default `cluster.local` domain configured, you need to provide the domain via:

```yaml
cluster:
  networking:
    domain: "acme.internal"
```

If your cluster does not have the default `10.0.0.0/8` CIDR configured, you need to provide the CIDR via the following:

```yaml
cluster:
  networking:
    cidr:
      - "127.0.0.0/8"
```

If your load balancer / reverse proxy IPs are not already included in the above `cidr` you need to
explicitly configure their related IPs or IP ranges:

```yaml
cluster:
  networking:
    incomingCIDR:
      - "172.16.0.0/12"
```

### Ingress

The default value for the `ingressClassName` in openDesk is set to `nginx`. This prevents fallback to the
cluster’s default ingress class, since the Helm charts used by openDesk components are not consistently aligned in
how they handle a missing or empty `ingressClassName`. In case you are using a non-standard `ingressClassName` for
your `ingress-nginx` controller you have to configure it as follows:

```yaml
ingress:
  ingressClassName: "nginx"
```

> [!note]
> Currently, the only supported ingress controller is `ingress-nginx` (see
> [requirements.md](./docs/requirements.md) for reference).

### Container runtime

Some apps require specific configurations for the container runtime. You can set your container runtime like `cri-o`,
`containerd` or `docker` by using the following attribute:

```yaml
cluster:
  container:
    engine: "containerd"
```

### Volumes
The StorageClass must be set using the following attribute:

```yaml
persistence:
  storageClassNames:
    RWX: "my-read-write-many-class"
    RWO: "my-read-write-once-class"
```

`RWX` is optional and requires that your cluster has a `ReadWriteMany` volume provisioner. If you can make use
of it, it largely benefits the distribution and scaling of apps. By default, only `ReadWriteOnce` is enabled.
To enable `ReadWriteMany` you can use the following attribute:

```yaml
cluster:
  persistence:
    readWriteMany: true
```

## Customize deployment

While openDesk configures the applications with meaningful defaults, you can check [functional.md](./docs/functional.md) if you want to change these defaults to better match your use case.

## Connectivity

### Ports

> [!note]
> If you use `NodePort` for service exposure, you must check your deployment for the actual ports and ensure
> they are opened where necessary.

#### Web-based user interface

To use the openDesk functionality with its web-based user interface, you need to expose the following ports publicly:

| Component          | Description             |  Port | Type |
| ------------------ | ----------------------- | ----: | ---: |
| openDesk           | Kubernetes Ingress      |    80 |  TCP |
| openDesk           | Kubernetes Ingress      |   443 |  TCP |
| Jitsi Video Bridge | ICE Port for video data | 10000 |  UDP |

#### Mail clients

To connect with mail clients like [Thunderbird](https://www.thunderbird.net/), the following ports need to be publicly exposed:

| Component          | Description             |  Port | Type |
| ------------------ | ----------------------- | ----: | ---: |
| Dovecot            | IMAPS                   |   993 |  TCP |
|                    | POP3S                   |   995 |  TCP |
| Postfix            | SMTP                    |    25 |  TCP |
|                    | SMTPS                   |   587 |  TCP |

### Mail/SMTP configuration

Enabling DKIM signing for outgoing emails helps reduce the risk of messages being marked as spam and improves recipient trust.
openDesk includes `dkimpy-milter` as a Postfix milter for signing emails. You can configure the following attributes:

```yaml
apps:
  dkimpy:
    enabled: true
smtp:
  dkim:
    key:
      value: "HzZs08QF1O7UiAkcM9T3U7rePPECtSFvWZIvyKqdg8E="
    selector: "default"
    useED25519: true # when false, RSA is used
```

A common scenario for outgoing mail is to send it through a smarthost or mail relay, which often handles DKIM signing as well.

If you prefer to use a smarthost, you can configure it as follows:

```yaml
smtp:
  host: "smarthost.domain.tld"
  username: "smarthost-auth-username"
  password: "secret"
```

### TURN configuration

Some components (Jitsi, Element) use a TURN server for direct communication. You can configure your own TURN server with
these options:

```yaml
turn:
  transport: "udp" # or tcp
  credentials: "secret"
  server:
    host: "turn.domain.tld"
    port: "3478"
  tls:
    host: "turns.domain.tld"
    port: "5349"
```

### Certificate issuer

As mentioned in [requirements](requirements.md#certificate-management), you can provide your own valid certificate. A TLS type
secret named `opendesk-certificates-tls` must be present in the application namespace. For deployment, you can
turn off `Certificate` resource creation with:

```yaml
apps:
  certificates:
    enabled: false
```

If you want to leverage `cert-manager.io` to handle certificates, like `Let's encrypt`, you need to provide the
configured cluster issuer:

```yaml
certificate:
  issuerRef:
    name: "letsencrypt-prod"
```

Additionally, it is possible to request wildcard certificates with:

```yaml
certificate:
  wildcard: true
```

## Password seed

All secrets are generated from a master password via [Master Password (algorithm)](https://en.wikipedia.org/wiki/Master_Password_(algorithm)).
To prevent others from using your openDesk instance, you must set your individual master password via:

```shell
export MASTER_PASSWORD="your_individual_master_password"
```

> [!important]
> Currently a
> [documented](https://docs.software-univention.de/nubus-kubernetes-operation/1.x/en/configuration/nats.html#configure-the-secrets)
> upstream [bug](https://forge.univention.org/bugzilla/show_bug.cgi?id=58357) causes a failure when
> passwords/secrets beginning with certain numbers are using for the Nubus subcomponent NATS. With openDesk
> 1.6.0 an update-aware workaround was implemented that prefixes the affected secrets in the openDesk included
> `secrets.yaml.gotmpl` that derives all secrets from the previously mentioned `MASTER_PASSWORD`. If you are
> using externally provided passwords/secrets make sure that none of the ones listed below are starting with a
> number:
>
> - `secrets.nubus.provisioning.api.natsPassword`
> - `secrets.nubus.provisioning.dispatcherNatsPassword`
> - `secrets.nubus.provisioning.prefillNatsPassword`
> - `secrets.nubus.provisioning.udmListenerNatsPassword`
> - `secrets.nubus.provisioning.udmTransformerNatsPassword`
> - `secrets.nats.natsAdminPassword`

## Install

After setting your environment-specific values in `dev` environment, you can start deployment by:

```shell
helmfile apply -e dev -n <NAMESPACE> [-l <label>] [--suppress-diff]
```

**Arguments:**

- `-e <env>`: Environment name out of `default`, `dev`, `test`, `prod`
- `-n <namespace>`: Kubernetes namespace
- `-l <label>`: Label selector
- `--suppress-diff`: Disable diff printing

## Install single app

You can also install or upgrade only a single app like Collabora, either by using a label selector:

```shell
helmfile apply -e dev -n <NAMESPACE> -l component=collabora
```

or by switching to the apps' directory (faster) and install or upgrade from there directly:

```shell
cd helmfile/apps/collabora
helmfile apply -e dev -n <NAMESPACE>
```

## Install single release/chart

Instead of iterating through all services, you can also deploy a single release like `mariadb` by executing the following:

```shell
helmfile apply -e dev -n <NAMESPACE> -l name=mariadb
```

## Using from external repository

Referring to `./helmfile_generic.yaml.gotmpl` from an external
directory or repository is possible. The `helmfile.yaml.gotmpl` that refers to
`./helmfile_generic.yaml.gotmpl` may define custom environments. These custom
environments may overwrite specific configuration values. These
configuration values are:

* `global.domain`
* `global.helmRegistry`
* `global.master_password`

# Access deployment

## Login

When all apps are successfully deployed, and their Pod status is `Running` or `Succeeded`, you can navigate to

```text
https://domain.tld
```

which will redirect you to the actual URL of the openDesk portal:

```text
https://portal.domain.tld
```

By default the portal will send you to openDesk's login screen.

## Credentials

openDesk deploys with the standard user account `Administrator`, the password for which can be retrieved as follows:

```shell
# Set your namespace
NAMESPACE=<your_namespace>

# Get password for IAM "Administrator" account
kubectl -n ${NAMESPACE} get secret ums-nubus-credentials -o jsonpath='{.data.administrator_password}' | base64 -d
```

Using the aforementioned account, you can either create new accounts manually or make use of the
[openDesk User Importer](https://gitlab.opencode.de/bmi/opendesk/components/platform-development/images/user-import/)
script or container.

In the following snippet, after defining the values of the first three lines and executing the command,
you get two accounts, `default` and `default-admin`:

```shell
ADMINISTRATOR_PASSWORD=<your_administrator_password_see_above>
DOMAIN=<your_domain>
DEFAULT_USERS_PASSWORD=<password_for_the_created_default_accounts>
docker run --rm registry.opencode.de/bmi/opendesk/components/platform-development/images/user-import:3.0.0 \
  ./user_import_udm_rest_api.py \
    --import_domain ${DOMAIN} \
    --udm_api_password ${ADMINISTRATOR_PASSWORD} \
    --set_default_password ${DEFAULT_USERS_PASSWORD} \
    --import_filename template.ods \
    --create_admin_accounts True
```

# Uninstall

You can uninstall the deployment by executing the following:

```shell
helmfile destroy -n <NAMESPACE>
```

> [!note]
> Not all Jobs, PersistentVolumeClaims, or Certificates are deleted; you have to delete them manually

**'Sledgehammer destroy'** - for fast development turn-around times (at your own risk):

```shell
NAMESPACE=your-namespace

# Uninstall all Helm charts
for OPENDESK_RELEASE in $(helm ls -n ${NAMESPACE} -aq); do
  helm uninstall -n ${NAMESPACE} ${OPENDESK_RELEASE};
done

# Delete leftover resources
kubectl delete pvc --all --namespace ${NAMESPACE};
kubectl delete jobs --all --namespace ${NAMESPACE};
kubectl delete configmaps --all --namespace ${NAMESPACE};
```

> [!warning]
> Without specifying a `--namespace` flag, or by leaving it empty, cluster-wide components will get deleted!
