diff --git a/kolabctl b/kolabctl index d938ffe3..97880f3a 100755 --- a/kolabctl +++ b/kolabctl @@ -1,491 +1,491 @@ #!/bin/bash set -e export CONFIG=${CONFIG:-"config.prod"} export HOST=${HOST:-"kolab.local"} OPENEXCHANGERATES_API_KEY=${OPENEXCHANGERATES_API_KEY} FIREBASE_API_KEY=${FIREBASE_API_KEY} PUBLIC_IP=${PUBLIC_IP:-"127.0.0.1"} export CERTS_PATH=./docker/certs export POD=kolab-prod export IMAP_SPOOL_STORAGE="--mount=type=volume,src=$POD-imap-spool,destination=/var/spool/imap,U=true" export IMAP_LIB_STORAGE="--mount=type=volume,src=$POD-imap-lib,destination=/var/lib/imap,U=true" export POSTFIX_SPOOL_STORAGE="--mount=type=volume,src=$POD-postfix-spool,destination=/var/spool/imap,U=true" export POSTFIX_LIB_STORAGE="--mount=type=volume,src=$POD-postfix-lib,destination=/var/lib/imap,U=true" export SYNAPSE_STORAGE="--mount=type=volume,src=$POD-synapse-data,destination=/data,U=true" export MARIADB_STORAGE="--mount=type=volume,src=$POD-mariadb-data,destination=/var/lib/mysql,U=true" export REDIS_STORAGE="--mount=type=volume,src=$POD-redis-data,destination=/var/lib/redis,U=true" export MINIO_STORAGE="--mount=type=volume,src=$POD-minio-data,destination=/data,U=true" export ENV_FILE=src/.env export PODMAN_IGNORE_CGROUPSV1_WARNING=true source bin/podman_shared __export_env() { source src/.env export APP_WEBSITE_DOMAIN export APP_DOMAIN export DB_HOST export IMAP_HOST export IMAP_PORT export IMAP_ADMIN_LOGIN export IMAP_ADMIN_PASSWORD export MAIL_HOST export MAIL_PORT export IMAP_DEBUG export FILEAPI_WOPI_OFFICE export CALENDAR_CALDAV_SERVER export KOLAB_ADDRESSBOOK_CARDDAV_SERVER export DB_ROOT_PASSWORD export DB_USERNAME export DB_PASSWORD export DB_DATABASE export REDIS_HOST export REDIS_PASSWORD export MINIO_USER export MINIO_PASSWORD export PASSPORT_PRIVATE_KEY export PASSPORT_PUBLIC_KEY export DES_KEY export MEET_SERVER_TOKEN export MEET_WEBHOOK_TOKEN export KOLAB_SSL_CERTIFICATE export KOLAB_SSL_CERTIFICATE_FULLCHAIN export KOLAB_SSL_CERTIFICATE_KEY export PUBLIC_IP } kolab__configure() { if [[ "$1" == "--force" ]]; then rm src/.env fi # Generate the .env once with all the necessary secrets if [[ -f src/.env ]]; then echo "src/.env already exists, not regenerating" return fi cp "$CONFIG/src/.env" src/.env if [[ -z $ADMIN_PASSWORD ]]; then echo "Please enter your new infrastructure admin password used for various infrastructure components (mysql, imap, ...):" read -r ADMIN_PASSWORD fi if [[ -z $PUBLIC_IP ]]; then PUBLIC_IP=$(ip -o route get to 8.8.8.8 | sed -n 's/.*src \([0-9.]\+\).*/\1/p') fi # Generate random secrets if ! grep -q "COTURN_STATIC_SECRET" src/.env; then COTURN_STATIC_SECRET=$(openssl rand -hex 32); echo "COTURN_STATIC_SECRET=${COTURN_STATIC_SECRET}" >> src/.env fi if ! grep -q "MEET_WEBHOOK_TOKEN" src/.env; then MEET_WEBHOOK_TOKEN=$(openssl rand -hex 32); echo "MEET_WEBHOOK_TOKEN=${MEET_WEBHOOK_TOKEN}" >> src/.env fi if ! grep -q "MEET_SERVER_TOKEN" src/.env; then MEET_SERVER_TOKEN=$(openssl rand -hex 32); echo "MEET_SERVER_TOKEN=${MEET_SERVER_TOKEN}" >> src/.env fi if ! grep -q "APP_KEY=base64:" src/.env; then APP_KEY=$(openssl rand -base64 32); echo "APP_KEY=base64:${APP_KEY}" >> src/.env fi if ! grep -q "PASSPORT_PROXY_OAUTH_CLIENT_ID=" src/.env; then PASSPORT_PROXY_OAUTH_CLIENT_ID=$(uuidgen); echo "PASSPORT_PROXY_OAUTH_CLIENT_ID=${PASSPORT_PROXY_OAUTH_CLIENT_ID}" >> src/.env fi if ! grep -q "PASSPORT_PROXY_OAUTH_CLIENT_SECRET=" src/.env; then PASSPORT_PROXY_OAUTH_CLIENT_SECRET=$(openssl rand -base64 32); echo "PASSPORT_PROXY_OAUTH_CLIENT_SECRET=${PASSPORT_PROXY_OAUTH_CLIENT_SECRET}" >> src/.env fi if ! grep -q "PASSPORT_SYNAPSE_OAUTH_CLIENT_ID=" src/.env; then PASSPORT_SYNAPSE_OAUTH_CLIENT_ID=$(uuidgen); echo "PASSPORT_SYNAPSE_OAUTH_CLIENT_ID=${PASSPORT_SYNAPSE_OAUTH_CLIENT_ID}" >> src/.env fi if ! grep -q "PASSPORT_SYNAPSE_OAUTH_CLIENT_SECRET=" src/.env; then PASSPORT_SYNAPSE_OAUTH_CLIENT_SECRET=$(openssl rand -base64 32); echo "PASSPORT_SYNAPSE_OAUTH_CLIENT_SECRET=${PASSPORT_SYNAPSE_OAUTH_CLIENT_SECRET}" >> src/.env fi if ! grep -q "PASSPORT_PUBLIC_KEY=|PASSPORT_PRIVATE_KEY=" src/.env; then PASSPORT_PRIVATE_KEY=$(openssl genrsa 4096); echo "PASSPORT_PRIVATE_KEY=\"${PASSPORT_PRIVATE_KEY}\"" >> src/.env PASSPORT_PUBLIC_KEY=$(echo "$PASSPORT_PRIVATE_KEY" | openssl rsa -pubout 2>/dev/null) echo "PASSPORT_PUBLIC_KEY=\"${PASSPORT_PUBLIC_KEY}\"" >> src/.env fi if ! grep -q "DES_KEY=" src/.env; then DES_KEY=$(openssl rand -base64 24); echo "DES_KEY=${DES_KEY}" >> src/.env fi # Customize configuration sed -i \ -e "s/{{ host }}/${HOST}/g" \ -e "s/{{ openexchangerates_api_key }}/${OPENEXCHANGERATES_API_KEY}/g" \ -e "s/{{ firebase_api_key }}/${FIREBASE_API_KEY}/g" \ -e "s/{{ public_ip }}/${PUBLIC_IP}/g" \ -e "s/{{ admin_password }}/${ADMIN_PASSWORD}/g" \ src/.env if [ -f /etc/letsencrypt/live/${HOST}/cert.pem ]; then echo "Using the available letsencrypt certificate for ${HOST}" cat >> src/.env << EOF KOLAB_SSL_CERTIFICATE=/etc/letsencrypt/live/${HOST}/cert.pem KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/letsencrypt/live/${HOST}/fullchain.pem KOLAB_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/${HOST}/privkey.pem EOF fi } kolab__deploy() { if [[ -z $ADMIN_PASSWORD ]]; then echo "Please enter your new admin password for the admin@$HOST user:" read -r ADMIN_PASSWORD fi echo "Deploying $CONFIG on $HOST" if [ `getenforce` == "Enforcing" ]; then # Patches on how to correctly configure selinux are welcome echo "selinux breaks networking, please disable" exit 1 fi if [[ ! -f src/.env ]]; then echo "Missing src/.env file, run 'kolabctl configure' to generate" exit 1 fi if [[ "$1" == "--reset" ]]; then kolab__reset --force fi __export_env podman volume create $POD-imap-spool --ignore -l=kolab podman volume create $POD-imap-lib --ignore -l=kolab podman volume create $POD-postfix-spool --ignore -l=kolab podman volume create $POD-postfix-lib --ignore -l=kolab podman volume create $POD-synapse-data --ignore -l=kolab podman volume create $POD-mariadb-data --ignore -l=kolab podman volume create $POD-redis-data --ignore -l=kolab podman volume create $POD-minio-data --ignore -l=kolab kolab__build # Create the pod first $PODMAN pod create \ --replace \ --add-host=$HOST:127.0.0.1 \ --publish "443:6443" \ --publish "465:6465" \ --publish "587:6587" \ --publish "143:6143" \ --publish "993:6993" \ --publish "44444:44444/udp" \ --publish "44444:44444/tcp" \ --name $POD podman__run_mariadb podman__run_redis podman__healthcheck $POD-mariadb $POD-redis # IMAP must be avialable for the seeder podman__run_imap podman__healthcheck $POD-imap podman__run_webapp podman__healthcheck $POD-webapp # Ensure all commands are processed echo "Flushing work queue" $PODMAN exec -ti $POD-webapp ./artisan queue:work --stop-when-empty if [[ -n $ADMIN_PASSWORD ]]; then podman exec $POD-webapp ./artisan user:password "admin@$APP_DOMAIN" "$ADMIN_PASSWORD" fi podman__run_synapse podman__run_element podman__run_minio podman__healthcheck $POD-minio podman__run_meet podman__run_roundcube podman__run_postfix podman__run_amavis podman__run_collabora podman__run_proxy echo "Deployment complete!" } kolab__reset() { if [[ "$1" == "--force" ]]; then REPLY="y" else read -p "Are you sure? This will delete the pod including all data. Type y to confirm." -n 1 -r echo fi if [[ "$REPLY" =~ ^[Yy]$ ]]; then podman pod rm --force $POD volumes=($(podman volume ls -f name=$POD | awk '{if (NR > 1) print $2}')) for v in "${volumes[@]}" do podman volume rm --force $v done fi } kolab__start() { podman pod start $POD } kolab__stop() { podman pod stop $POD } kolab__update() { kolab__stop podman pull quay.io/sclorg/mariadb-105-c9s podman pull minio/minio:latest podman pull almalinux:9 kolab__build kolab__start } kolab__backup() { backup_path="$(pwd)/backup/" mkdir -p "$backup_path" echo "Stopping containers" kolab__stop echo "Backing up volumes" volumes=($(podman volume ls -f name=$POD | awk '{if (NR > 1) print $2}')) for v in "${volumes[@]}" do podman export -o="$backup_path/$v.tar" done echo "Restarting containers" kolab__start } kolab__restore() { backup_path="$(pwd)/backup/" echo "Stopping containers" kolab__stop # We currently expect the volumes to exist. # We could alternatively create volumes form existing tar files # for f in backup/*.tar; do # echo "$(basename $f .tar)" ; # done echo "Restoring volumes" volumes=($(podman volume ls -f name=$POD | awk '{if (NR > 1) print $2}')) for v in "${volumes[@]}" do podman import $v "$backup_path/$v.tar" done echo "Restarting containers" kolab__start } kolab__selfcheck() { set -e APP_DOMAIN=$(grep APP_DOMAIN src/.env | tail -n1 | sed "s/APP_DOMAIN=//") if [ -z "$ADMIN_PASSWORD" ]; then echo "Please enter your new admin password for the admin@$HOST user:" read -r ADMIN_PASSWORD fi if [ -z "$ADMIN_USER" ]; then ADMIN_USER="admin@$APP_DOMAIN" fi echo "Checking for containers" podman__is_ready $POD-imap podman__is_ready $POD-mariadb podman__is_ready $POD-redis podman__is_ready $POD-webapp podman__is_ready $POD-minio podman__is_ready $POD-meet podman__is_ready $POD-roundcube podman__is_ready $POD-postfix podman__is_ready $POD-amavis podman__is_ready $POD-collabora podman__is_ready $POD-proxy echo "All containers are available" # We skip mollie and openexchange - podman exec $POD-webapp env APP_DEBUG=false ./artisan status:health --check DB --check Redis --check IMAP --check Roundcube --check Meet --check DAV + podman exec $POD-webapp env APP_DEBUG=false ./artisan status:health --user="$ADMIN_USER" --password="$ADMIN_PASSWORD" --check DB --check Redis --check IMAP --check Roundcube --check Meet --check DAV --check SMTP --check Auth echo "Checking postfix authentication" podman exec $POD-postfix testsaslauthd -u "$ADMIN_USER" -p "$ADMIN_PASSWORD" echo "Checking imap authentication" podman exec $POD-imap testsaslauthd -u "$ADMIN_USER" -p "$ADMIN_PASSWORD" # podman run -ti --rm utils ./mailtransporttest.py --sender-username "$ADMIN_USER" --sender-password "$ADMIN_PASSWORD" --sender-host "127.0.0.1" --recipient-username "$ADMIN_USER" --recipient-password "$ADMIN_PASSWORD" --recipient-host "127.0.0.1" --recipient-port "11143" # podman run -ti --rm utils ./kolabendpointtester.py --verbose --host "$APP_DOMAIN" --dav "https://$APP_DOMAIN/dav/" --imap "$APP_DOMAIN" --activesync "$APP_DOMAIN" --user "$ADMIN_USER" --password "$ADMIN_PASSWORD" echo "All tests have passed!" } kolab__ps() { command podman ps | grep $POD } kolab__exec() { container=$1 shift command podman exec -ti $POD-$container $@ } kolab__run() { __export_env podman__run_$1 } kolab__pull() { podman pull quay.io/apheleiait/kolab/imap:latest podman tag kolab/imap:latest kolab-imap:latest podman pull quay.io/apheleiait/kolab/webapp:latest podman tag kolab/webapp:latest kolab-webapp:latest podman pull quay.io/apheleiait/kolab/collabora:latest podman tag kolab/collabora:latest kolab-collabora:latest podman pull quay.io/apheleiait/kolab/redis:latest podman tag kolab/redis:latest redis:latest podman pull quay.io/apheleiait/kolab/roundcube:latest podman tag kolab/roundcube:latest roundcube:latest podman pull quay.io/apheleiait/kolab/mariadb:latest podman tag kolab/mariadb:latest mariadb:latest podman pull quay.io/apheleiait/kolab/meet:latest podman tag kolab/meet:latest kolab-meet:latest podman pull quay.io/apheleiait/kolab/coturn:latest podman tag kolab/coturn:latest kolab-coturn:latest podman pull quay.io/apheleiait/kolab/postfix:latest podman tag kolab/postfix:latest kolab-postfix:latest podman pull quay.io/apheleiait/kolab/amavis:latest podman tag kolab/amavis:latest kolab-amavis:latest podman pull quay.io/apheleiait/kolab/utils:latest podman tag kolab/utils:latest kolab-utils:latest podman pull quay.io/apheleiait/kolab/minio:latest podman tag kolab/minio:latest minio/minio:latest podman pull quay.io/apheleiait/kolab/proxy:latest podman tag kolab/proxy:latest kolab-proxy:latest # podman pull quay.io/apheleiait/kolab/synapse:latest # podman tag kolab/synapse:latest synapse:latest # podman pull quay.io/apheleiait/kolab/element:latest # podman tag kolab/element:latest element:latest } kolab__build() { pin_git_refs if [[ $1 != "" ]]; then podman__build_$1 else podman__build_base podman__build_webapp podman__build_meet podman__build_imap podman__build docker/mariadb mariadb podman__build docker/redis redis podman__build_proxy podman__build docker/synapse synapse podman__build docker/element element podman__build_roundcube podman__build_utils podman__build_postfix podman__build_amavis podman__build_collabora env CERT_DIR=docker/certs APP_DOMAIN=$HOST bin/regen-certs fi } kolab__cyradm() { # command podman exec -ti $POD-imap cyradm --auth PLAIN -u admin@kolab.local -w simple123 --port 11143 localhost if [[ "$@" ]]; then command podman exec -ti $POD-imap echo "$@" | cyradm --auth PLAIN -u $(grep IMAP_ADMIN_LOGIN src/.env | cut -d '=' -f 2 ) -w $(grep IMAP_ADMIN_PASSWORD src/.env | cut -d '=' -f 2 ) --port 11143 localhost else command podman exec -ti $POD-imap cyradm --auth PLAIN -u $(grep IMAP_ADMIN_LOGIN src/.env | cut -d '=' -f 2 ) -w $(grep IMAP_ADMIN_PASSWORD src/.env | cut -d '=' -f 2 ) --port 11143 localhost fi } kolab__shell() { kolab__exec $1 /bin/bash } kolab__run() { __export_env podman__run_$1 } kolab__logs() { command podman logs -f $POD-$1 } kolab__help() { cat </dev/null 2>&1; then "kolab__$cmdname" "${@:1}" else echo "Function $cmdname not recognized" >&2 kolab__help exit 1 fi diff --git a/src/app/Console/Commands/Status/Health.php b/src/app/Console/Commands/Status/Health.php index abadff43..17bd2ff9 100644 --- a/src/app/Console/Commands/Status/Health.php +++ b/src/app/Console/Commands/Status/Health.php @@ -1,197 +1,226 @@ line($exception); return false; } } private function checkOpenExchangeRates() { try { OpenExchangeRates::healthcheck(); return true; } catch (\Exception $exception) { $this->line($exception); return false; } } private function checkMollie() { try { return Mollie::healthcheck(); } catch (\Exception $exception) { $this->line($exception); return false; } } private function checkDAV() { try { DAV::healthcheck(); return true; } catch (\Exception $exception) { $this->line($exception); return false; } } private function checkLDAP() { try { LDAP::healthcheck(); return true; } catch (\Exception $exception) { $this->line($exception); return false; } } private function checkIMAP() { try { IMAP::healthcheck(); return true; } catch (\Exception $exception) { $this->line($exception); return false; } } + private function checkSMTP() + { + try { + \App\Mail\Helper::sendMail( + new \App\Mail\TrialEnd(\App\User::findByEmail($this->option('user'))), + null, + ["to" => [$this->option('user')]] + ); + return true; + } catch (\Exception $exception) { + $this->line($exception); + return false; + } + } + + private function checkAuth() + { + try { + $user = \App\User::findByEmail($this->option('user')); + $response = \App\Http\Controllers\API\AuthController::logonResponse($user, $this->option('password')); + return $response->getData()->status == 'success'; + } catch (\Exception $exception) { + $this->line($exception); + return false; + } + } + private function checkRoundcube() { try { //TODO maybe run a select? Roundcube::dbh(); return true; } catch (\Exception $exception) { $this->line($exception); return false; } } private function checkRedis() { try { Redis::connection(); return true; } catch (\Exception $exception) { $this->line($exception); return false; } } private function checkStorage() { try { Storage::healthcheck(); return true; } catch (\Exception $exception) { $this->line($exception); return false; } } private function checkMeet() { $urls = \config('meet.api_urls'); $success = true; foreach ($urls as $url) { $this->line("Checking $url"); try { $response = \App\Meet\Service::client($url)->get('ping'); if (!$response->ok()) { $success = false; $this->line("Backend {$url} not available. Status: " . $response->status()); } } catch (\Exception $exception) { $success = false; $this->line("Backend {$url} not available. Error: " . $exception->getMessage()); } } return $success; } /** * Execute the console command. * * @return mixed */ public function handle() { $result = 0; $steps = $this->option('check'); if (empty($steps)) { $steps = [ - 'DB', 'Redis', 'IMAP', 'Roundcube', 'Meet', 'DAV', 'Mollie', 'OpenExchangeRates' + 'DB', 'Redis', 'IMAP', 'Roundcube', 'Meet', 'DAV', 'Mollie', 'OpenExchangeRates', 'SMTP', 'Auth' ]; if (\config('app.with_ldap')) { array_unshift($steps, 'LDAP'); } if (\config('app.with_imap')) { array_unshift($steps, 'IMAP'); } if (\config('app.with_files')) { array_unshift($steps, 'Storage'); } } foreach ($steps as $step) { $func = "check{$step}"; $this->line("Checking {$step}..."); if ($this->{$func}()) { $this->info("OK"); } else { - $this->error("Not found"); + $this->error("Error while checking: $step"); $result = 1; } } return $result; } }