Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117753641
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
40 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/kolabctl b/kolabctl
index ebe30fe..ad5b942 100755
--- a/kolabctl
+++ b/kolabctl
@@ -1,1155 +1,1173 @@
#!/bin/bash
# shellcheck disable=SC2068
# shellcheck disable=SC2086
# shellcheck disable=SC2001
# shellcheck disable=SC2002
# shellcheck disable=SC2219
# shellcheck disable=SC2059
# shellcheck disable=SC2155
NAMESPACE=${NAMESPACE:-kolab}
CHART_VERSION=${CHART_VERSION:-"4.0.16"}
CHART_URL=${CHART_URL:-"oci://quay.io/apheleiait/kolab/kolab"}
USERNAME=${USERNAME:-"apheleiait+kolab"}
PASSWORD=${PASSWORD:-"Z8Q8KT04M4ADF7D18PDINUG4V3ZO6GYP4JPPAUGPANO4KWGYS93LR7QQ5DGLFRQP"}
IMAGE_REGISTRY=${IMAGE_REGISTRY:-"quay.io/apheleiait/kolab"}
IMAGE_VERSION=${IMAGE_VERSION:-"4.0.16"}
export KUBECONFIG=${KUBECONFIG:-"$HOME/.kube/config"}
# If we have a local kubeconfig.yaml file, take KUBECONFIG and namespace from that file
if [ -f kubeconfig.yaml ]; then
export KUBECONFIG="kubeconfig.yaml"
export NAMESPACE=$(kubectl config view --minify -o jsonpath='{..namespace}')
fi
cert_dir=${CERT_DIR:-${PWD}/certs/}
if ! command -v yq >/dev/null 2>&1
then
echo "yq could not be found"
exit 1
fi
### ========== Implementation Details ============
#Ensure we are connected to the correct cluster (localhost only for now)
__ensure_server() {
# We assume that if the namespace is kolab it's a k3s setup.
if [[ "$NAMESPACE" == "kolab" ]]; then
# Do we have any means to check if this makes sense?
echo
else
if [[ "$(oc project -q)" != "$NAMESPACE" ]]; then
echo "Wrong project on the server, or not openshift"
exit 1
fi
fi
}
__helm_upgrade() {
set -e
if [[ -f values.yaml ]]; then
ARGS="-f values.yaml"
fi
time helm upgrade -i kolab --create-namespace $ARGS $@ --namespace "$NAMESPACE" "$CHART_URL" --version "$CHART_VERSION"
command kubectl -n $NAMESPACE rollout status deployment
command helm status kolab -n $NAMESPACE
}
__helm_template() {
if [[ -f values.yaml ]]; then
ARGS="-f values.yaml"
fi
command helm template kolab --namespace "$NAMESPACE" "$CHART_URL" $ARGS $@ --version "$CHART_VERSION"
}
kolab__apply() {
__helm_template > inflated.yaml
cat <<EOF > kustomization.yaml
resources:
- inflated.yaml
EOF
if [[ "$1" =~ "--replace" ]]; then
# apply does not always remove fields that were removed from the spec (such as extra volumes)
# replace fully replaces the spec, and thus makes sure the configuration is in sync,
# but will also restart all pods.
command kubectl replace --dry-run=server -k ./
elif [[ "$1" =~ "--force" ]]; then
command kubectl apply -k ./
else
command kubectl diff -k ./
read -p "Does this look ok?" -n 1 -r
echo
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
command kubectl apply -k ./
fi
fi
}
__k3s_login() {
# Copy k3s config
mkdir -p "$HOME/.kube"
sudo cp /etc/rancher/k3s/k3s.yaml "$HOME/.kube/config"
sudo chown "$(id -u):$(id -g)" "$HOME/.kube/config"
chmod 600 "$HOME/.kube/config"
echo "Export this: export KUBECONFIG=$HOME/.kube/config"
}
# Pull images as defined in helm chart.
# Pass in username and password for registry
# This currently only pulls the kolab images (for the rest we'd have to figure out where to pull from)
__k3s_pull() {
# Figure out which images to pull
images=$(__helm_template | grep "image: " | grep -v "#" | grep -oP "(?<=image: ).*" | sed 's/\"//g' | uniq )
for image in $images; do
echo "Pulling $image"
# For the quay.io/apheleiait images we need to pass in the credentials
# if [[ $image == *"quay.io/apheleiait"* ]]; then
# if [[ -z $1 || -z $2 ]]; then
# echo "You need to specify the quay.io/apheleiait username and password"
# exit 1
# fi
# sudo k3s ctr images pull --user "$1:$2" "$image"
# else
#TODO Support non apheleiait images?
sudo k3s ctr images pull "$image"
# fi
done
}
__k3s_install() {
sudo mkdir -p /etc/rancher/k3s/
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server" sh -s - --disable traefik,servicelb --write-kubeconfig-mode=644
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
}
__k3s_setup() {
set -e
# Make sure we have a connection to kubernetes
kubectl get pods
CERT_MANAGER_VERSION="v1.17.4"
# Install certmanager
if [[ $DISABLE_CERT_MANAGER != "true" ]]; then
wget https://github.com/cert-manager/cert-manager/releases/download/$CERT_MANAGER_VERSION/cert-manager.crds.yaml
kubectl apply -f cert-manager.crds.yaml
helm repo add cert-manager https://charts.jetstack.io
fi
[ -z $DISABLE_INGRESS ] && helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add metallb https://metallb.github.io/metallb
helm repo update
if [[ $DISABLE_CERT_MANAGER != "true" ]]; then
if ! helm --namespace cert-manager status cert-manager; then
helm install cert-manager cert-manager/cert-manager \
--wait \
--namespace cert-manager \
--create-namespace \
--version $CERT_MANAGER_VERSION
fi
fi
if [[ $DISABLE_INGRESS != "true" ]]; then
export TLS_SECRET=$(yq '.tlsSecret.name' values.yaml)
# HAPROXY_CONTROLLER_ARGS="--set controller.image.tag=latest --set controller.image.repository=apheleiait/haproxytech/kubernetes-ingress"
if ! helm --namespace haproxy-controller status haproxy-ingress; then
helm install haproxy-ingress haproxytech/kubernetes-ingress \
--wait \
--namespace haproxy-controller \
--create-namespace \
--set controller.config.ssl-certificate=$TLS_SECRET \
--set controller.kind=DaemonSet \
--set controller.daemonset.useHostPort=true \
$HAPROXY_CONTROLLER_ARGS --version 1.38.5
# Delete existing ingressclass, otherwise we can't apply our chart with the subcharts, which tries to create the same ingressclass
kubectl delete --ignore-not-found ingressclass/haproxy
fi
fi
if ! helm --namespace default status kube-state-metrics; then
helm install kube-state-metrics prometheus-community/kube-state-metrics \
--wait \
--namespace default \
--version 3.0.0
fi
if ! helm --namespace metallb-system status metallb; then
helm install metallb metallb/metallb --create-namespace \
--namespace metallb-system --wait
fi
# Prepare the namespace
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: kolab
EOF
# The ip address range just has to not conflict, it doesn't need to exist beforehand
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: kolab-l2
namespace: metallb-system
spec:
addresses:
- 10.30.0.128/28
autoAssign: false
serviceAllocation:
priority: 50
serviceSelectors:
- matchLabels:
IPAddressPool: l2
namespaces:
- kolab
EOF
}
__sum_memory_requests() {
res=$(kubectl get pods -o=jsonpath='{.items[*]..resources.requests.memory}' -n "$NAMESPACE")
let tot=0
for i in $res; do
if [[ $i =~ "Mi" ]]; then
i=$(echo $i | sed 's/[^0-9]*//g')
tot=$(( tot + i ))
elif [[ $i =~ "Gi" ]]; then
i=$(echo $i | sed 's/[^0-9]*//g')
tot=$(( tot + i * 1000 ))
fi
done
echo $tot
}
__sum_cpu_requests() {
res=$(kubectl get pods -o=jsonpath='{.items[*]..resources.requests.cpu}' -n "$NAMESPACE")
let tot=0
for i in $res; do
if [[ $i =~ "m" ]]; then
i=$(echo $i | sed 's/[^0-9]*//g')
tot=$(( tot + i ))
else
tot=$(( tot + i*1000 ))
fi
done
echo $tot
}
__replace_multiline() {
# Echo content, indent by the passed in spaces, insert before matching line
echo -n "$2" | sed -e "s/^/$4/" | sed -i "/$1/e cat /dev/stdin;echo" $3
# delete the matching line
sed -i "/$1/d" $3
}
### ========== End of Implementation ============
#
### ========== Commands ============
kolab__k3s-setup() {
__k3s_setup
}
# Cluster admin commands
kolab__deploy() {
if [[ ! -f values.yaml ]]; then
echo "Missing a values.yaml file, run configure first."
exit 1
fi
if [[ "$1" == "--k3s" ]]; then
shift
__k3s_install
__k3s_setup
__k3s_pull $USERNAME $PASSWORD
# kolab__registry_login $USERNAME $PASSWORD
fi
if [[ -f /etc/rancher/k3s/k3s.yaml ]]; then
# Shouldn't be necessary but seems to be in the vm case
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
fi
if [[ "$1" != "--prepare" ]]; then
shift
__ensure_server
echo "This is going to take a while (timeout after 30 minutes)"
__helm_upgrade
fi
}
kolab__uninstall() {
command helm uninstall kolab --wait --timeout 10m0s --debug $ARGS $@ --namespace "$NAMESPACE"
}
kolab__update() {
__helm_upgrade $@
}
kolab__template() {
__helm_template $@
}
kolab__generate_local_cert() {
name="$1"
if [[ -z $name ]]; then
echo "Pass domain to generate the certificate for"
exit 1
fi
if [ ! -d "${cert_dir}" ]; then
mkdir -p ${cert_dir}
fi
if [ ! -f "${cert_dir}/ca.key" ]; then
openssl genrsa -out ${cert_dir}/ca.key 4096
openssl req \
-new \
-x509 \
-nodes \
-days 3650 \
-key ${cert_dir}/ca.key \
-out ${cert_dir}/ca.cert \
-subj '/O=Example CA/'
# Trust the new ca
sudo trust anchor --store certs/ca.cert
sudo update-ca-trust
fi
if [ -f /etc/pki/tls/openssl.cnf ]; then
openssl_cnf="/etc/pki/tls/openssl.cnf"
elif [ -f /etc/ssl/openssl.cnf ]; then
openssl_cnf="/etc/ssl/openssl.cnf"
else
echo "No openssl.cnf"
exit 1
fi
openssl genrsa -out ${cert_dir}/${name}.key 4096
SAN="[SAN]\nsubjectAltName=DNS:${name},DNS:admin.${name},DNS:services.${name}"
openssl req \
-new \
-key ${cert_dir}/${name}.key \
-out ${cert_dir}/${name}.csr \
-subj "/O=Example CA/CN=${name}/" \
-reqexts SAN \
-config <(cat ${openssl_cnf} \
<(printf $SAN))
openssl x509 \
-req \
-in ${cert_dir}/${name}.csr \
-CA ${cert_dir}/ca.cert \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${name}.cert \
-days 28 \
-extfile <(cat ${openssl_cnf} \
<(printf $SAN)) \
-extensions SAN
cat ${cert_dir}/${name}.cert \
${cert_dir}/ca.cert > ${cert_dir}/${name}.chain.pem
chmod 644 ${cert_dir}/*.{cert,key,pem}
}
# Update the kolabctl script from the latest tarball
kolab__selfupdate() {
wget https://mirror.apheleia-it.ch/pub/kolab-kubernetes-latest.tar.gz -O /tmp/kolab-kubernetes-latest.tar.gz
tar -zxvf /tmp/kolab-kubernetes-latest.tar.gz kolabctl
}
# kolab__backup() {
# command bin/backup.sh
# }
# kolab__restore() {
# command bin/restore.sh
# }
# Render a values.yaml file from a template in docs
kolab__configure() {
if [[ -z $TEMPLATE ]]; then
if [[ "$1" == "--openshift" ]]; then
TEMPLATE="deployments/openshift-cluster/values.yaml"
else
TEMPLATE="deployments/k3s-single-node/values.yaml"
fi
fi
if [[ -f values.yaml ]]; then
echo "values.yaml already exists";
exit 1
fi
if [[ -z $DOMAIN ]]; then
echo "Please enter your domain:"
read -r DOMAIN
fi
if [[ -z $ADMIN_PASSWORD ]]; then
echo "Please enter your new admin password for the admin@$DOMAIN user:"
read -r ADMIN_PASSWORD
fi
if [[ "$1" == "--openshift" ]]; then
if [[ -z $POSTFIX_LOADBALANCER_IP ]]; then
echo "Please enter your postfix loadbalancer ip:"
read -r POSTFIX_LOADBALANCER_IP
fi
if [[ -z $PROXY_LOADBALANCER_IP ]]; then
echo "Please enter your proxy loadbalancer ip:"
read -r PROXY_LOADBALANCER_IP
fi
if [[ -z $MEET_LOADBALANCER_IP ]]; then
echo "Please enter your meet loadbalancer ip:"
read -r MEET_LOADBALANCER_IP
fi
if [[ -z $METALLB_ADDRESS_POOL ]]; then
echo "Please enter your metallb address pool:"
read -r METALLB_ADDRESS_POOL
fi
if [[ -z $STORAGE_CLASS ]]; then
echo "Please enter your storage class (e.g. ocs-storagecluster-ceph-rbd):"
read -r STORAGE_CLASS
fi
else
# Assamble the docker secret, which will be our pull secret
PULL_SECRET=$(
cat <<EOF | base64 -w0
{
"auths": {
"quay.io": {
"auth": "$(echo -n "$USERNAME:$PASSWORD" | base64 -w0)",
"email": ""
}
}
}
EOF
)
fi
if [[ -z $PUBLIC_IP ]]; then
echo "Please enter your public ip:"
read -r PUBLIC_IP
else
echo "Using public ip: $PUBLIC_IP"
fi
# Generate secrets
[ -z $TEST_PASSWORD ] && TEST_PASSWORD=$(openssl rand -base64 24)
[ -z $APP_KEY ] && APP_KEY="base64:$(openssl rand -base64 32)"
[ -z $COTURN_STATIC_SECRET ] && COTURN_STATIC_SECRET=$(openssl rand -hex 32)
[ -z $PASSPORT_PROXY_OAUTH_CLIENT_ID ] && PASSPORT_PROXY_OAUTH_CLIENT_ID=$(uuidgen)
[ -z $PASSPORT_PROXY_OAUTH_CLIENT_SECRET ] && PASSPORT_PROXY_OAUTH_CLIENT_SECRET=$(openssl rand -base64 32)
[ -z $PASSPORT_WEBMAIL_SSO_CLIENT_ID ] && PASSPORT_WEBMAIL_SSO_CLIENT_ID=$(uuidgen)
[ -z $PASSPORT_WEBMAIL_SSO_CLIENT_SECRET ] && PASSPORT_WEBMAIL_SSO_CLIENT_SECRET=$(openssl rand -base64 32)
[ -z "$PASSPORT_PRIVATE_KEY" ] && PASSPORT_PRIVATE_KEY=$(openssl genrsa 4096)
PASSPORT_PUBLIC_KEY=$(echo "$PASSPORT_PRIVATE_KEY" | openssl rsa -pubout 2>/dev/null)
[ -z $MEET_WEBHOOK_TOKEN ] && MEET_WEBHOOK_TOKEN=$(openssl rand -hex 32)
[ -z $MEET_SERVER_TOKEN ] && MEET_SERVER_TOKEN=$(openssl rand -hex 32)
MEET_PUBLIC_IP="$PUBLIC_IP"
[ -z $DB_ROOT_PASSWORD ] && DB_ROOT_PASSWORD=$(openssl rand -hex 16)
[ -z $DB_KOLAB_PASSWORD ] && DB_KOLAB_PASSWORD=$(openssl rand -hex 16)
[ -z $DB_LEGACY_PASSWORD ] && DB_LEGACY_PASSWORD=$(openssl rand -hex 16)
[ -z $DB_ROUNDCUBE_PASSWORD ] && DB_ROUNDCUBE_PASSWORD=$(openssl rand -hex 16)
[ -z $DB_MONITORING_PASSWORD ] && DB_MONITORING_PASSWORD=$(openssl rand -hex 16)
[ -z $REDIS_PASSWORD ] && REDIS_PASSWORD=$(openssl rand -hex 16)
[ -z $MAIL_NOREPLY_PASSWORD ] && MAIL_NOREPLY_PASSWORD=$(openssl rand -hex 16)
[ -z $MINIO_ROOT_PASSWORD ] && MINIO_ROOT_PASSWORD=$(openssl rand -hex 16)
[ -z $IMAP_ADMIN_PASSWORD ] && IMAP_ADMIN_PASSWORD=$(openssl rand -hex 16)
[ -z $ROUNDCUBE_DES_KEY ] && ROUNDCUBE_DES_KEY=$(openssl rand -base64 24)
[ -z $EXTERNAL_SERVICE_USER_PASSWORD ] && EXTERNAL_SERVICE_USER_PASSWORD=$(openssl passwd -1 $ADMIN_PASSWORD);
DKIM_IDENTIFIER="dkim1";
[ -z "$DKIM_KEY" ] && DKIM_KEY=$(openssl genrsa 2048);
cp "$TEMPLATE" values.yaml
# / can appear in base64, but | not
sed -i \
-e "s|DOMAIN|${DOMAIN}|g" \
-e "s|TEST_PASSWORD|${TEST_PASSWORD}|g" \
-e "s|IMAP_ADMIN_PASSWORD|${IMAP_ADMIN_PASSWORD}|g" \
-e "s|APP_KEY|${APP_KEY}|g" \
-e "s|IMAGE_REGISTRY|${IMAGE_REGISTRY}|g" \
-e "s|IMAGE_VERSION|${IMAGE_VERSION}|g" \
-e "s|PULL_SECRET|${PULL_SECRET}|g" \
-e "s|PASSPORT_PROXY_OAUTH_CLIENT_ID|${PASSPORT_PROXY_OAUTH_CLIENT_ID}|g" \
-e "s|PASSPORT_PROXY_OAUTH_CLIENT_SECRET|${PASSPORT_PROXY_OAUTH_CLIENT_SECRET}|g" \
-e "s|PASSPORT_WEBMAIL_SSO_CLIENT_ID|${PASSPORT_WEBMAIL_SSO_CLIENT_ID}|g" \
-e "s|PASSPORT_WEBMAIL_SSO_CLIENT_SECRET|${PASSPORT_WEBMAIL_SSO_CLIENT_SECRET}|g" \
-e "s|ROUNDCUBE_DES_KEY|${ROUNDCUBE_DES_KEY}|g" \
-e "s|MEET_WEBHOOK_TOKEN|${MEET_WEBHOOK_TOKEN}|g" \
-e "s|MEET_SERVER_TOKEN|${MEET_SERVER_TOKEN}|g" \
-e "s|MEET_PUBLIC_IP|${MEET_PUBLIC_IP}|g" \
-e "s|COTURN_STATIC_SECRET|${COTURN_STATIC_SECRET}|g" \
-e "s|DB_ROOT_PASSWORD|${DB_ROOT_PASSWORD}|g" \
-e "s|DB_KOLAB_PASSWORD|${DB_KOLAB_PASSWORD}|g" \
-e "s|DB_LEGACY_PASSWORD|${DB_LEGACY_PASSWORD}|g" \
-e "s|DB_ROUNDCUBE_PASSWORD|${DB_ROUNDCUBE_PASSWORD}|g" \
-e "s|DB_MONITORING_PASSWORD|${DB_MONITORING_PASSWORD}|g" \
-e "s|DB_HOST|${DB_HOST}|g" \
-e "s|REDIS_PASSWORD|${REDIS_PASSWORD}|g" \
-e "s|MAIL_NOREPLY_PASSWORD|${MAIL_NOREPLY_PASSWORD}|g" \
-e "s|MINIO_ROOT_PASSWORD|${MINIO_ROOT_PASSWORD}|g" \
-e "s|DKIM_IDENTIFIER|${DKIM_IDENTIFIER}|g" \
-e "s|ADMIN_PASSWORD|${ADMIN_PASSWORD}|g" \
-e "s|PUBLIC_IP|${PUBLIC_IP}|g" \
-e "s|PRIMARY_IP|${PRIMARY_IP}|g" \
-e "s|STORAGE_CLASS|${STORAGE_CLASS}|g" \
-e "s|MEET_LOADBALANCER_IP|${MEET_LOADBALANCER_IP}|g" \
-e "s|PROXY_LOADBALANCER_IP|${PROXY_LOADBALANCER_IP}|g" \
-e "s|POSTFIX_LOADBALANCER_IP|${POSTFIX_LOADBALANCER_IP}|g" \
-e "s|METALLB_ADDRESS_POOL|${METALLB_ADDRESS_POOL}|g" \
-e "s|EXTERNAL_SERVICE_USER_PASSWORD|${EXTERNAL_SERVICE_USER_PASSWORD}|g" \
values.yaml
__replace_multiline PASSPORT_PRIVATE_KEY "$PASSPORT_PRIVATE_KEY" values.yaml " "
__replace_multiline PASSPORT_PUBLIC_KEY "$PASSPORT_PUBLIC_KEY" values.yaml " "
__replace_multiline DKIM_KEY "$DKIM_KEY" values.yaml " "
if [[ "$1" != "--openshift" ]]; then
# Default to a static local cert
kolab__generate_local_cert "$DOMAIN"
[ -z "$TLS_CERT" ] && TLS_CERT=$(cat certs/$DOMAIN.cert)
[ -z "$TLS_KEY" ] && TLS_KEY=$(cat certs/$DOMAIN.key)
[ -z "$CA_CERT" ] && CA_CERT=$(cat certs/ca.cert)
__replace_multiline TLS_CERT "$TLS_CERT" values.yaml " "
__replace_multiline TLS_KEY "$TLS_KEY" values.yaml " "
__replace_multiline CA_CERT "$CA_CERT" values.yaml " "
fi
echo "generated values.yaml successfully"
}
# Re-render values.yaml, but preserve secrets
kolab__reconfigure() {
if [[ -f values.yaml ]]; then
# Load all secrets from the existing values.yaml file (except the once that we expect to be set already)
export TEST_PASSWORD=$(yq '.serviceAccounts.monitoring1.password' values.yaml)
export APP_KEY=$(yq '.appKey' values.yaml)
# export COTURN_STATIC_SECRET=(yq '.appKey' values.yaml)
export PASSPORT_PROXY_OAUTH_CLIENT_ID=$(yq '.passport.proxyOauthClientId' values.yaml)
export PASSPORT_PROXY_OAUTH_CLIENT_SECRET=$(yq '.passport.proxyOauthClientSecret' values.yaml)
export PASSPORT_WEBMAIL_SSO_CLIENT_ID=$(yq '.passport.webmailSSOClientId' values.yaml)
export PASSPORT_WEBMAIL_SSO_CLIENT_SECRET=$(yq '.passport.webmailSSOClientSecret' values.yaml)
export PASSPORT_PRIVATE_KEY="$(yq '.passport.privateKey' values.yaml)"
export MEET_WEBHOOK_TOKEN=$(yq '.meet.webhookToken' values.yaml)
export MEET_SERVER_TOKEN=$(yq '.meet.serverToken' values.yaml)
export DB_ROOT_PASSWORD=$(yq '.mariadb.rootPassword' values.yaml)
export DB_KOLAB_PASSWORD=$(yq '.mariadb.kolabPassword' values.yaml)
export DB_LEGACY_PASSWORD=$(yq '.mariadb.kolabLegacyPassword' values.yaml)
export DB_ROUNDCUBE_PASSWORD=$(yq '.mariadb.roundcubePassword' values.yaml)
# [ -z $DB_MONITORING_PASSWORD ] && DB_MONITORING_PASSWORD=$(openssl rand -hex 16)
export REDIS_PASSWORD=$(yq '.redis.password' values.yaml)
export MAIL_NOREPLY_PASSWORD=$(yq '.mail.noreplyPassword' values.yaml)
export MINIO_ROOT_PASSWORD=$(yq '.minio.rootPassword' values.yaml)
export IMAP_ADMIN_PASSWORD=$(yq '.imap.adminPassword' values.yaml)
export ROUNDCUBE_DES_KEY=$(yq '.roundcube.desKey' values.yaml)
export EXTERNAL_SERVICE_USER_PASSWORD=$(yq '.externalServiceUserPassword' values.yaml)
export DKIM_KEY="$(yq '.amavis.dkim.key' values.yaml)"
export DOMAIN="$(yq '.domainName' values.yaml)"
export ADMIN_PASSWORD="$(yq '.adminPassword' values.yaml)"
export PUBLIC_IP="$(yq '.meet.publicIp' values.yaml)"
# Preserve the current cert by default
if [[ "$(yq '.tlsSecret.type' values.yaml)" == "static" ]]; then
export TLS_CERT="$(yq '.tlsSecret.crt' values.yaml)"
export TLS_KEY="$(yq '.tlsSecret.key' values.yaml)"
export CA_CERT="$(yq '.tlsSecret.ca' values.yaml)"
fi
rm -f values.yaml.backup
mv values.yaml values.yaml.backup
fi
kolab__configure $@
}
kolab__refresh_cert() {
if [[ "$(yq '.tlsSecret.type' values.yaml)" != "static" ]]; then
echo "This only makes sense for self-signed certificates."
fi
DOMAIN="$(yq '.domainName' values.yaml)"
kolab__generate_local_cert "$DOMAIN"
TLS_CERT=$(cat certs/$DOMAIN.cert)
TLS_KEY=$(cat certs/$DOMAIN.key)
CA_CERT=$(cat certs/ca.cert)
yq e -i '.tlsSecret.crt = "TLS_CERT"' values.yaml
yq e -i '.tlsSecret.key = "TLS_KEY"' values.yaml
yq e -i '.tlsSecret.ca = "CA_CERT"' values.yaml
__replace_multiline TLS_CERT "$TLS_CERT" values.yaml " "
__replace_multiline TLS_KEY "$TLS_KEY" values.yaml " "
__replace_multiline CA_CERT "$CA_CERT" values.yaml " "
}
kolab__enable_letsencrypt() {
sed -i \
-e "s/kolab-cert-static/kolab-cert-letsencrypt/g" \
-e "s/type: static/type: letsencrypt/g" \
values.yaml
yq e -i '.certManager.letsencryptIssuer = true' values.yaml
__helm_upgrade
}
kolab__update_public_ip() {
sed -i \
-e "s/publicIp: .*/publicIp: $PUBLIC_IP/g" \
values.yaml
__helm_upgrade
}
kolab__registry_login() {
helm registry login quay.io/apheleiait -u "$1" -p "$2"
}
__wait_for_service() {
echo "Waiting for service/$1"
# Wait for the ingress section with the assigned ip to appear
command timeout 10s bash -c "until kubectl -n $NAMESPACE get service/$1 --output=jsonpath='{.status.loadBalancer.ingress[0].ip}' | grep -q ^; do : ; done" || :
if command kubectl -n $NAMESPACE get service/external-proxy --output=jsonpath='{.status.loadBalancer.ingress[0].ip}' | grep -q ^; then
echo "Service $1 is bound";
else
echo "Service $1 does not have an external ip";
exit 1;
fi
}
kolab__connectiontest() {
set -e
if [[ -f values.yaml ]]; then
export DOMAIN=$(yq '.domainName' values.yaml)
fi
for port in 25 143 443 465 587 993 44444 44445 44446; do
echo "Testing $port over tcp"
nc -z $DOMAIN $port
done
echo "All connections successful"
}
kolab__mailtransporttest() {
set -e
if [[ ! -f values.yaml ]]; then
echo "Can't run without values.yaml file"
exit 1
fi
DOMAIN=$(yq '.domainName' values.yaml)
USER1="$(yq '.serviceAccounts.monitoring1.user' values.yaml)"
PASSWORD1="$(yq '.serviceAccounts.monitoring1.password' values.yaml)"
USER2="$(yq '.serviceAccounts.monitoring2.user' values.yaml)"
PASSWORD2="$(yq '.serviceAccounts.monitoring2.password' values.yaml)"
cat <<EOF | kubectl -n $NAMESPACE run mailtransporttest --image utils:latest -i --rm --restart=Never --command -- bash
echo "starting mailtransporttest"
timeout 1m ./mailtransporttest.py --sender-username "$USER1" --sender-password "$PASSWORD1" --sender-host proxy --recipient-username "$USER2" --recipient-password "$PASSWORD2" --recipient-host proxy --starttls --verbose
EOF
}
kolab__selfcheck() {
set -e
if [[ -f values.yaml ]]; then
export DOMAIN=$(yq '.domainName' values.yaml)
fi
if [[ -f values.yaml ]]; then
if [[ "$(yq '.tlsSecret.type' values.yaml)" == "external" ]]; then
if command kubectl -n $NAMESPACE describe "secret/$(yq '.tlsSecret.secretName' values.yaml)" --request-timeout "5s" &> /dev/null; then
echo "Found external tls secret"
else
echo "TLS secret not found"
exit 1
fi
elif command kubectl -n $NAMESPACE describe secret/kolab-cert-static --request-timeout "5s" &> /dev/null; then
echo "Found static tls secret"
elif command kubectl -n $NAMESPACE describe secret/kolab-cert-letsencrypt --request-timeout "5s" &> /dev/null; then
echo "Found letsencrypt secret"
else
echo "TLS secret not found"
exit 1
fi
fi
# Wait for all rollouts to complete
command kubectl -n $NAMESPACE rollout status deployment
# Just a very complicated way to run a sql query because ./artisan db doesn't run queries.
# Also, we get a bunch of invisible characters, hence the tail and cut calls.
PRIMARY_DOMAIN_COUNT="$(kolab__exec kolab ./artisan tinker --execute "print(App\Domain::where('namespace','$DOMAIN')->count())" | tail -n 1 | cut -c 1-1 )"
# Ensure seeding has completed (we verify the primary domain exists)
if [[ "$PRIMARY_DOMAIN_COUNT" == "0" ]]; then
echo "The domain $DOMAIN is not created, did seeding fail?"
exit 1
fi
__wait_for_service "external-proxy"
__wait_for_service "external-smtp"
# Check that all pvcs are available
for pvc in $(kubectl -n $NAMESPACE get --no-headers=true pvc -o name); do
command kubectl -n $NAMESPACE wait --for=jsonpath='{.status.phase}'=Bound $pvc
done
command kubectl exec --stdin --tty -n "$NAMESPACE" deployment/imap -- bash -c "testsaslauthd -u \$IMAP_ADMIN_LOGIN -p \$IMAP_ADMIN_PASSWORD"
export USER=$(yq '.serviceAccounts.monitoring1.user' values.yaml)
export PASSWORD=$(yq '.serviceAccounts.monitoring1.password' values.yaml)
command kubectl exec --stdin --tty -n "$NAMESPACE" deployment/horizon -- ./artisan status:health --user="$USER" --password="$PASSWORD" --check DB --check Redis --check IMAP --check Roundcube --check Meet --check DAV --check Auth --check SMTP
echo "All tests passed"
}
### ========== Commands for a local k3s deployment ============
kolab__k3s__stop() {
command /usr/local/bin/k3s-killall.sh
}
kolab__k3s__start() {
command systemctl start k3s
}
kolab__k3s__pull() {
__k3s_pull $@
}
kolab__k3s() {
cmdname=$1
if declare -f "kolab__k3s__$cmdname" >/dev/null 2>&1; then
shift
"kolab__k3s__$cmdname" "${@:1}"
else
kolab__help kolab__k3s__
fi
}
### ========== End of Commands for a local k3s deployment ============
kolab__status() {
echo "# Cluster info"
command kubectl cluster-info
echo "# Helm deployments"
command helm list -A
echo "# Helm status"
command helm status kolab -n "$NAMESPACE" --show-resources
echo "# Events"
command kubectl get events --sort-by=.metadata.creationTimestamp
}
kolab__shell() {
if [[ $1 == "" ]]; then
# command kubectl run kolab -ti --rm -n "$NAMESPACE" --image localhost:5000/webapp:latest -- /bin/bash
command kubectl run utils -ti --rm -n "$NAMESPACE" --image quay.io/apheleiait/kolab/utils:latest -- /bin/bash
else
deployment=$1
shift
command kubectl exec --stdin --tty -n "$NAMESPACE" "deployment/$deployment" $@ -- /bin/bash
fi
}
kolab__logs() {
if [[ "$1" == "monitoring" ]]; then
# FIXME we should have a label to select on
command kubectl -n "$NAMESPACE" logs "$(oc get pods --no-headers=true | grep "monitoring" | cut -d' ' -f 1 | head -1)"
elif [[ "$1" == "scheduler" ]]; then
# FIXME we should have a label to select on
for i in $(oc get pods --no-headers=true | grep "scheduler" | cut -d' ' -f 1); do
command kubectl -n "$NAMESPACE" logs "$i"
done
else
deployment="$1"
shift
command kubectl -n "$NAMESPACE" logs "deployment/$deployment" $@
fi
}
kolab__logcli() {
if [[ -f values.yaml ]]; then
DOMAIN=$(yq '.domainName' values.yaml)
fi
if [[ "$ADMIN_PASSWORD" == "" ]]; then
ADMIN_PASSWORD=$(yq '.adminPassword' values.yaml)
fi
env LOKI_ADDR=${LOKI_ADDR:-"http://admin.$DOMAIN/"} logcli --username "admin" --password "$ADMIN_PASSWORD" "$@"
}
# Tail the logs via logcli
# --pod=: wildcard match on the pod_name label
# --search=: Run a fulltext query match on the record
# -f: Stream the log
# --full: Full output including the entire record
kolab__tail() {
if [[ "$*" =~ "--pod" ]]; then
ARGUMENT=$(echo "$*" | grep -o -e "--pod=.*" | cut -d ' ' -f 1)
POD_NAME=$(echo "$ARGUMENT" | cut -d '=' -f 2)
POD_FILTER=",pod_name=~\"$POD_NAME.*\""
set -- "$(echo "$*" | sed "s|$ARGUMENT *||")"
fi
if [[ "$*" =~ "--container" ]]; then
ARGUMENT=$(echo "$*" | grep -o -e "--container=.*" | cut -d ' ' -f 1)
CONTAINER_NAME=$(echo "$ARGUMENT" | cut -d '=' -f 2)
CONTAINER_FILTER=",container_name=~\"$CONTAINER_NAME.*\""
set -- "$(echo "$*" | sed "s|$ARGUMENT *||")"
fi
if [[ "$*" =~ "--search" ]]; then
ARGUMENT=$(echo "$*" | grep -o -e "--search=.*" | cut -d ' ' -f 1)
SEARCH_STRING=$(echo "$ARGUMENT" | cut -d '=' -f 2)
FULLTEXT_FILTER=" |~ \"$SEARCH_STRING\""
set -- "$(echo "$*" | sed "s|$ARGUMENT *||")"
fi
if [[ "$*" =~ "--full" ]]; then
set -- "$(echo "$*" | sed "s|--full *||")"
else
OUTPUT="--output=raw"
fi
echo "$*"
if [[ "$*" =~ "-f" ]]; then
FOLLOW="--follow"
fi
# The format is --from=2024-02-22T08:30:24Z --to=2024-02-23T12:30:24Z
if [[ "$*" =~ "--from" ]]; then
# Can't have --from without --to
FROM=$(echo "$*" | grep -o -e "--from=.*" | cut -d ' ' -f 1)
TO=$(echo "$*" | grep -o -e "--to=.*" | cut -d ' ' -f 1)
#beware of --limit (it will continue to apply, so you may get partial results)
# Can't follow with a time range
FOLLOW=""
fi
QUERY="{namespace_name=\"$NAMESPACE\"$POD_FILTER$CONTAINER_FILTER} $FULLTEXT_FILTER | json log | line_format \"{{ __timestamp__ }} \t {{.pod_name | trunc 10}} \t{{.log}}\""
echo "Running query: $QUERY"
kolab__logcli query "$QUERY" $OUTPUT $FOLLOW $FROM $TO
}
kolab__describe() {
if [[ $1 == *"pod"* ]]; then
command kubectl -n "$NAMESPACE" describe $@
else
deployment="$1"
shift
command kubectl -n "$NAMESPACE" describe "deployment/$deployment" $@
fi
}
kolab__restart() {
if [[ $1 == "" ]]; then
command kubectl -n "$NAMESPACE" rollout restart deployment
else
command kubectl -n "$NAMESPACE" rollout restart deployment/$1
fi
}
kolab__ps() {
command kubectl -n "$NAMESPACE" get pods
}
kolab__get() {
command kubectl -n "$NAMESPACE" get $@
}
kolab__volumes() {
command kubectl -n "$NAMESPACE" get persistentvolumeclaims $@
}
kolab__build() {
command env REGISTRY="localhost:5000" build/build.sh --nochecks
}
kolab__top() {
command kubectl top pods -n "$NAMESPACE" --sum=true
command kubectl get pods -n "$NAMESPACE" -o=jsonpath='{range .items[*]}{"\n"}{.metadata.name}{":\t\t"}{..resources.requests.memory}{end}'
echo
echo
echo "Total memory requests: $(__sum_memory_requests) Mi"
echo "Total cpu requests: $(__sum_cpu_requests) m"
}
kolab__metrics() {
command kubectl get --raw /apis/metrics.k8s.io/v1beta1/pods | jq
command kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | jq
# # Get the metrics for node <node_name>
# kubectl get --raw /apis/metrics.k8s.io/v1beta1/<node_name> | jq '.'
# # Get the metrics for pode <pod_name>
# kubectl get --raw /apis/metrics.k8s.io/v1beta1/<pod_name> | jq '.'
}
kolab__k3s_reset() {
if [[ "$1" == "--force" ]]; then
REPLY="y"
else
read -p "Are you sure? This will delete the k3s cluster including all data" -n 1 -r
echo
fi
if [[ "$REPLY" =~ ^[Yy]$ ]];
then
/usr/local/bin/k3s-uninstall.sh
fi
}
kolab__login() {
if [[ "$1" == "--k3s" ]]; then
__k3s_login
else
echo "Configure kubectl manually, login is not implemented"
fi
command kubectl cluster-info
}
kolab__exec() {
container=$1
shift
command kubectl exec -q --stdin --tty -n "$NAMESPACE" "deployment/$container" -- $@
}
kolab__run() {
container=$1
shift
IMAGE=$(kubectl get deployment/$container -o jsonpath="{..image}")
command kubectl run $container --image $IMAGE -ti --rm --restart=Never --command -- $@
}
### ========== Administration commands ============
kolab__artisan() {
kolab__exec horizon ./artisan $@
}
kolab__reseed() {
read -p "Are you sure? This will delete all data" -n 1 -r
echo
if [[ "$REPLY" =~ ^[Yy]$ ]];
then
kolab__artisan migrate:fresh --seed
fi
}
kolab__users() {
kolab__artisan users --attr=email
}
kolab__create-user() {
kolab__artisan user:create $@
}
# Run imapcli commands
# ci/testctl imapcli admin@kolab.local simple123 fetch --debug INBOX 1001
# ci/testctl imapcli admin@kolab.local simple123 tag INBOX 1000..2000 foobar
# ci/testctl imapcli admin@kolab.local simple123 getacl INBOX
kolab__imapcli() {
USER=$1
PASSWORD=$2
CMD=$3
shift
shift
shift
kolab__run utils ./imapcli.rb "$CMD" --host imap --port 143 --username $USER --password "$PASSWORD" "$@"
}
# Generate mail in the admin inbox:
# generate-mail admin@kolab.local simple123 --clear --maxAttachmentSize=0 --type=mail --count 500 INBOX
kolab__generate-mail() {
USER=$1
PASSWORD=$2
CMD=$3
shift
shift
shift
kolab__run utils ./generatemail.py --host imap --port 143 --username $USER --password "$PASSWORD" $@
}
kolab__generate-users() {
for usernum in $(seq 1 100) ; do
USER="testuser$usernum@kolab.local"
kolab__create-user $USER --password simple123
kolab__generate-mail $USER simple123 --clear --maxAttachmentSize=0 --type=mail --count 1000 INBOX
for i in $(seq 1 20) ; do
kolab__imapcli $USER simple123 tag INBOX 1:999 "rctag$i"
done
done
}
kolab__diskusage() {
echo
echo "IMAP:"
kolab__exec imap du -h /var/spool/imap/ -d 0
kolab__exec imap du -h /var/lib/imap/ -d 0
kolab__exec imap df -ah | grep imap
echo
echo "Postfix:"
kolab__exec postfix du -h /var/spool/postfix/ -d 0
kolab__exec postfix df -ah | grep postfix
# For monitoring kubelet_volume_stats_available_bytes
}
kolab__enable-roundcube-debug() {
kolab__exec roundcube mkdir -m 777 -p "roundcubemail/logs/$1"
kolab__exec roundcube mkdir -m 777 -p "syncroton/logs/$1"
echo "Created log marker directory for $1"
}
kolab__disable-roundcube-debug() {
kolab__exec roundcube rm -rf "roundcubemail/logs/$1"
kolab__exec roundcube rm -rf "syncroton/logs/$1"
echo "Removed log marker directory for $1"
}
kolab__password() {
kolab__artisan user:password $@
}
kolab__db() {
kolab__exec mariadb mysql -u root -h mariadb -p"$(yq '.mariadb.rootPassword' values.yaml)" $@
}
kolab__cleanup() {
kubectl -n $NAMESPACE delete pod --field-selector=status.phase==Succeeded
kubectl -n $NAMESPACE delete pod --field-selector=status.phase==Failed
}
# Show dns configuration based on values.yaml values
kolab__dns() {
PUBLIC_IP=$(cat values.yaml | yq '.meet.publicIp')
DOMAIN=$(cat values.yaml | yq '.domainName')
EMAILDOMAIN=$DOMAIN
DKIM_IDENTIFIER=$(cat values.yaml | yq '.amavis.dkim.identifier')
# Extract public key from private key, remove delimiters and add quotation marks on each line. Finally indent result
DKIM_PUBLIC_KEY=$(cat values.yaml | yq '.amavis.dkim.key' | openssl pkey -pubout | grep --invert-match "\----" | sed -e 's/^\|$/"/g' | sed 's/^/ /';)
echo
echo "DNS Configuration:"
echo
cat <<EOF
$DOMAIN A $PUBLIC_IP
$EMAILDOMAIN MX 10 $DOMAIN.
$EMAILDOMAIN TXT "v=spf1 mx a:$DOMAIN mx:$DOMAIN -all"
autodiscover.$EMAILDOMAIN CNAME $DOMAIN.
_autodiscover._tcp.$DOMAIN SRV 0 0 443 $DOMAIN.
_caldav._tcp.$DOMAIN SRV 0 0 80 $DOMAIN.
_caldavs._tcp.$DOMAIN SRV 0 1 443 $DOMAIN.
_carddav._tcp.$DOMAIN SRV 0 0 80 $DOMAIN.
_carddavs._tcp.$DOMAIN SRV 0 1 443 $DOMAIN.
_imap._tcp.$DOMAIN SRV 0 0 143 $DOMAIN.
_imaps._tcp.$DOMAIN SRV 0 1 993 $DOMAIN.
_pop3._tcp.$DOMAIN SRV 10 0 110 $DOMAIN.
_pop3s._tcp.$DOMAIN SRV 10 1 995 $DOMAIN.
_sieve._tcp.$DOMAIN SRV 0 0 4190 $DOMAIN.
_submission._tcp.$DOMAIN SRV 0 1 587 $DOMAIN.
_webdav._tcp.$DOMAIN SRV 0 0 80 $DOMAIN.
_webdavs._tcp.$DOMAIN SRV 0 1 443 $DOMAIN.
_smtp._tls.$DOMAIN TXT "v=TLSRPTv1; rua=mailto:admin@$DOMAIN"
_dmarc.$DOMAIN TXT "v=DMARC1; p=quarantine; adkim=s; aspf=s; rua=mailto:admin@$DOMAIN; ruf=mailto:admin@$DOMAIN"
mta-sts.$DOMAIN CNAME $DOMAIN.
_mta-sts.$DOMAIN TXT "v=STSv1; id=2024031901;"
$DKIM_IDENTIFIER._domainkey.$DOMAIN TXT (
"v=DKIM1; p="
$DKIM_PUBLIC_KEY
)
EOF
}
### ========== Commands for imap ============
kolab__imap__cyradm() {
echo "$@"
ADMIN_PASSWORD="$(yq '.imap.adminPassword' values.yaml)"
if [ "$#" -eq 0 ]; then
kolab__exec imap cyradm --auth PLAIN -u cyrus-admin -w "$ADMIN_PASSWORD" --port 11143 localhost
else
echo "Running command $@"
echo "$@" | kolab__exec imap cyradm --auth PLAIN -u cyrus-admin -w "$ADMIN_PASSWORD" --port 11143 localhost
fi
}
kolab__imap() {
cmdname=$1
if declare -f "kolab__imap__$cmdname" >/dev/null 2>&1; then
shift
"kolab__imap__$cmdname" "${@:1}"
else
kolab__exec imap runuser -u 1001 -- "$@"
fi
}
### ========== End of Commands for imap ============
### ========== Commands for postfix ============
kolab__postfix() {
cmdname=$1
if declare -f "kolab__postfix__$cmdname" >/dev/null 2>&1; then
shift
"kolab__postfix__$cmdname" "${@:1}"
else
kolab__exec postfix "$@"
fi
}
### ========== End of Commands for postfix ============
+### ========== Commands for proxy ============
+
+kolab__proxy() {
+ cmdname=$1
+ if declare -f "kolab__proxy__$cmdname" >/dev/null 2>&1; then
+ shift
+ "kolab__proxy__$cmdname" "${@:1}"
+ else
+ kolab__exec proxy "$@"
+ fi
+}
+
+kolab__proxy_checkcert() {
+ kolab__exec proxy bash -c "while openssl x509 -noout -subject -issuer -dates; do echo; done < /etc/certs/tls.crt"
+}
+
+### ========== End of Commands for proxy ============
+
kolab__help() {
cat <<EOF
This is the kolab commandline utility.
EOF
# Grep for functions with one line of context, then prettify and remove empty lines
grep -B1 ^$1 $0 | sed 's/ {//' | sed '/^$/d'
}
cmdname=$1
shift
if [[ "${cmdname}" != "deploy" && "${cmdname}" != "configure" && "${cmdname}" != "reconfigure" && "${cmdname}" != "login" && "${cmdname}" != "tail" && "${cmdname}" != "stop" ]]; then
__ensure_server
fi
# make sure we actually *did* get passed a valid function name
if declare -f "kolab__$cmdname" >/dev/null 2>&1; then
"kolab__$cmdname" "${@:1}"
else
echo "Function $cmdname not recognized" >&2
kolab__help kolab__
exit 1
fi
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Apr 4, 5:49 AM (1 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822752
Default Alt Text
(40 KB)
Attached To
Mode
R114 kolab-infrastructure
Attached
Detach File
Event Timeline