diff --git a/README.md b/README.md index 79144a89..91066d96 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,42 @@ ## Quickstart Instructions to try it out * Make sure you have docker and docker-compose available. -* Run 'make deploy' in the base directory. +* Change to the base directory of this repository. +* Run 'HOSTNAME=kolab.local ADMIN_PASSWORD="simple123" bin/configure.sh config.prod' to configure this deployment. +* Run 'bin/deploy.sh' to start the deployment. +* Run 'docker exec -w /src/kolabsrc/ kolab-webapp ./artisan user:password admin@kolab.local simple123' to set your admin password * Add an /etc/hosts entry "127.0.0.1 kolab.local" * navigate to https://kolab.local -* login as "john@kolab.org" with password "simple123" +* login as "admin@kolab.local" with password "simple123" (or whatever you have set), and create your users. -# Setup env.local +# Customization -To customize the installation, create a file src/env.local to override setting in src/.env.example. +To customize the installation, copy config.prod and adjust to your liking. You can then install the configuration using 'bin/configure.sh $YOURCONFIG', +and afterwards 'bin/deploy.sh' again. -The setup script with merge these settings into src/.env, which is what is ultimately used by the installation. - -Take a look at ansible/env.local for an example of typical modifications required for an installation. +Please note that bin/deploy.sh will remove any existing data. # Use the ansible setup The ansible/ directory contains setup scripts to setup a fresh Fedora system with a kolab deployment. Modify the Makefile with the required variables and then execute `make setup`. -This will configure the remote system and execute bin/deploy.sh +This will configure the remote system and execute the above steps. ### Update +To update the containers without removing the data: + * git pull * Run "bin/update.sh" ### Backup / Restore The "bin/backup.sh" script will stop all containers, snapshot the volumes to the backup/ directory, and restart the containers. "bin/restore.sh" will stop all containers, restore the volumes from tarballs in the backup/ directory, and restart the containers. ### Requirements * docker * openssl - -## TODO -* Only seed admin user, but not all the development stuff? diff --git a/ansible/Makefile b/ansible/Makefile index 55869f2d..1f5bdcae 100644 --- a/ansible/Makefile +++ b/ansible/Makefile @@ -1,10 +1,11 @@ OX_API_KEY= FIREBASE_API_KEY= HOSTNAME= PUBLIC_IP= +ADMIN_PASSWORD= GIT_BRANCH=beta-release setup: touch ./hosts echo "${HOSTNAME}" > ./hosts - ansible-playbook -v --inventory=./hosts --extra-vars="hostname=${HOSTNAME} openexchangerates_api_key=${OX_API_KEY} firebase_api_key=${FIREBASE_API_KEY} public_ip=${PUBLIC_IP} git_branch=${GIT_BRANCH}" setup.yml + ansible-playbook -v --inventory=./hosts --extra-vars="hostname=${HOSTNAME} openexchangerates_api_key=${OX_API_KEY} firebase_api_key=${FIREBASE_API_KEY} public_ip=${PUBLIC_IP} admin_password=${ADMIN_PASSWORD} git_branch=${GIT_BRANCH}" setup.yml diff --git a/ansible/env.local b/ansible/env.local deleted file mode 100644 index 54fe6ecd..00000000 --- a/ansible/env.local +++ /dev/null @@ -1,43 +0,0 @@ -APP_DOMAIN={{ host }} -APP_WEBSITE_DOMAIN={{ host }} -APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E= -APP_PUBLIC_URL=https://{{ host }} -COTURN_STATIC_SECRET=uzYguvIl9tpZFMuQOE78DpOi6Jc7VFSD0UAnvgMsg5n4e74MgIf6vQvbc6LWzZjz -COTURN_PUBLIC_IP='{{ public_ip }}' -MEET_TURN_SERVER='turn:{{ public_ip }}:3478' -MEET_WEBRTC_LISTEN_IP='{{ public_ip }}' -MEET_PUBLIC_DOMAIN={{ host }} -MEET_SERVER_URLS=https://{{ host }}/meetmedia/api/ -APP_URL=https://{{ host }} -ASSET_URL=https://{{ host }} - -DB_HOST=mariadb -REDIS_HOST=redis -IMAP_URI=ssl://kolab:11993 -LDAP_HOSTS=kolab - -MOLLIE_KEY= -STRIPE_KEY= -STRIPE_PUBLIC_KEY= -STRIPE_WEBHOOK_SECRET= - -OCTANE_HTTP_HOST={{ host }} -OPENEXCHANGERATES_API_KEY={{ openexchangerates_api_key }} -FIREBASE_API_KEY={{ firebase_api_key }} - -#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) -PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee -PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf - -#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) -PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb -PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz - -APP_PASSPHRASE=simple123 - -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 - -PROXY_SSL_CERTIFICATE=/etc/letsencrypt/live/{{ host }}/fullchain.pem -PROXY_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/{{ host }}/privkey.pem diff --git a/ansible/setup.yml b/ansible/setup.yml index 32cc8831..1dac567e 100755 --- a/ansible/setup.yml +++ b/ansible/setup.yml @@ -1,121 +1,122 @@ #!/usr/bin/ansible-playbook - name: Setup kolab deployment on fedora server hosts: "{{ hostname }}" remote_user: root tasks: - import_tasks: grub.yml - name: Set hostname ansible.builtin.hostname: name: "{{ hostname }}" - import_tasks: packages.yml - name: Put SELinux in permissive mode for docker selinux: policy: targeted state: permissive - name: Setup user kolab ansible.builtin.user: name: kolab shell: /bin/bash groups: wheel, audio, docker append: yes - name: sudo without password ansible.builtin.lineinfile: path: /etc/sudoers state: present regexp: '^%wheel\s' line: '%wheel ALL=(ALL) NOPASSWD: ALL' - name: Start service docker, if not started ansible.builtin.service: name: docker state: started - import_tasks: certbot.yml - name: get kolab git repo become: true become_user: kolab git: repo: https://git.kolab.org/source/kolab.git dest: /home/kolab/kolab version: "{{ git_branch }}" force: yes - - name: "kolab env.local" - vars: - host: "{{ hostname }}" - openexchangerates_api_key: "{{ openexchangerates_api_key }}" - firebase_api_key: "{{ firebase_api_key }}" - public_ip: "{{ public_ip }}" - ansible.builtin.template: - src: env.local - dest: /home/kolab/kolab/src/env.local - owner: kolab - group: kolab - mode: '0766' + - name: Run bin/configure + become: true + become_user: kolab + ansible.builtin.command: bin/configure.sh config.production + args: + chdir: /home/kolab/kolab + environment: + HOSTNAME: "{{ hostname }}" + OPENEXCHANGERATES_API_KEY: "{{ openexchangerates_api_key }}" + FIREBASE_API_KEY: "{{ firebase_api_key }}" + PUBLIC_IP: "{{ public_ip }}" + ADMIN_PASSWORD: "{{ admin_password }}" + - name: Permit receiving mail firewalld: port: 25/tcp permanent: yes state: enabled zone: FedoraServer - name: Permit http traffic firewalld: port: 80/tcp permanent: yes state: enabled zone: FedoraServer - name: Permit https traffic firewalld: port: 443/tcp permanent: yes state: enabled zone: FedoraServer - name: Permit TCP trafic for coturn firewalld: port: 3478/tcp permanent: yes state: enabled zone: FedoraServer - name: Permit TCP trafic for coturn firewalld: port: 5349/tcp permanent: yes state: enabled zone: FedoraServer - name: Permit UDP trafic for coturn firewalld: port: 3478/udp permanent: yes state: enabled zone: FedoraServer - name: Permit UDP trafic for coturn firewalld: port: 5349/udp permanent: yes state: enabled zone: FedoraServer - name: Always restart docker before deploy (because of potential network issues otherwise) ansible.builtin.service: name: docker state: restarted - name: Run bin/deploy become: true become_user: kolab ansible.builtin.command: bin/deploy.sh args: chdir: /home/kolab/kolab diff --git a/bin/configure.sh b/bin/configure.sh new file mode 100755 index 00000000..ac385384 --- /dev/null +++ b/bin/configure.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# Uninstall the old config +if [ -d config ]; then + echo "Uninstalling the old config." + find -L config/ -type f | while read file; do + file=$(echo $file | sed -e 's|^config||g') + file="./$file" + + rm -v $file + done +fi + +if [ "$1" == "" ]; then + echo "Failed to find the configuration folder, please pass one as argument (e.g. config.demo)." + exit 1 +fi + +if [ ! -d $1 ]; then + echo "Failed to find the configuration folder, please pass one as argument (e.g. config.demo)." + exit 1 +fi + +echo "Installing $1." +# Link new config +rm config +ln -s $1 config + +# Install new config +find -L config/ -type f | while read file; do + dir=$(dirname $file | sed -e 's|^config||g') + dir="./$dir" + + if [ ! -d $dir ]; then + mkdir -p $dir + fi + + cp -v $file $dir/ +done + + +# Generate random secrets +if ! grep -q "COTURN_STATIC_SECRET" .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" .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" .env; then + MEET_SERVER_TOKEN=$(openssl rand -hex 32); + echo "MEET_SERVER_TOKEN=${MEET_SERVER_TOKEN}" >> src/.env +fi + +# Customize configuration +sed -i \ + -e "s/{{ host }}/${HOSTNAME}/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/${HOSTNAME}/cert.pem ]; then + echo "Using the available letsencrypt certificate for ${HOSTNAME}" + cat >> .env << EOF +KOLAB_SSL_CERTIFICATE=/etc/letsencrypt/live/${HOSTNAME}/cert.pem +KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem +KOLAB_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/${HOSTNAME}/privkey.pem +PROXY_SSL_CERTIFICATE=/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem +PROXY_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/${HOSTNAME}/privkey.pem +EOF +fi diff --git a/bin/deploy.sh b/bin/deploy.sh index a036a0b2..07f8c7e4 100755 --- a/bin/deploy.sh +++ b/bin/deploy.sh @@ -1,4 +1,2 @@ #!/bin/bash bin/quickstart.sh --nodev - -docker exec -w /src/kolabsrc/ kolab-webapp ./artisan user:assign john@kolab.org beta diff --git a/bin/quickstart.sh b/bin/quickstart.sh index 224b4637..f48ff50c 100755 --- a/bin/quickstart.sh +++ b/bin/quickstart.sh @@ -1,157 +1,141 @@ #!/bin/bash set -e function die() { echo "$1" exit 1 } rpm -qv docker-compose >/dev/null 2>&1 || \ test ! -z "$(which docker-compose 2>/dev/null)" || \ die "Is docker-compose installed?" test ! -z "$(grep 'systemd.unified_cgroup_hierarchy=0' /proc/cmdline)" || \ die "systemd containers only work with cgroupv1 (use 'grubby --update-kernel=ALL --args=\"systemd.unified_cgroup_hierarchy=0\"' and a reboot to fix)" base_dir=$(dirname $(dirname $0)) -# Always reset .env with .env.example -cp src/.env.example src/.env -if [ -f "src/env.local" ]; then - # Ensure there's a line ending - echo "" >> src/.env - cat src/env.local >> src/.env -fi export DOCKER_BUILDKIT=0 -COMPOSE_ARGS= -if [ "$1" != "--nodev" ]; then - COMPOSE_ARGS="-f docker-compose.yml -f docker-compose.local.yml" -fi - docker-compose down --remove-orphans docker volume rm kolab_mariadb || : docker volume rm kolab_imap || : docker volume rm kolab_ldap || : -if [ "$1" != "--nodev" ]; then - src/artisan octane:stop >/dev/null 2>&1 || : - src/artisan horizon:terminate >/dev/null 2>&1 || : -else - # If we switch from an existing development setup to a compose deployment, - # we don't have a nice way to terminate octane/horizon. - # We can't use the artisan command because it will just block if redis is, - # no longer available, so we just kill all artisan processes running. - pkill -9 -f artisan || : -fi +# We can't use the following artisan commands because it will just block if redis is unavailable: +# src/artisan octane:stop >/dev/null 2>&1 || : +# src/artisan horizon:terminate >/dev/null 2>&1 || : +# we therefore just kill all artisan processes running. +pkill -9 -f artisan || : +pkill -9 -f swoole || : bin/regen-certs docker-compose build coturn kolab mariadb meet pdns proxy redis haproxy -docker-compose ${COMPOSE_ARGS} up -d coturn kolab mariadb meet pdns redis +docker-compose up -d coturn kolab mariadb meet pdns redis # Workaround until we have docker-compose --wait (https://github.com/docker/compose/pull/8777) function wait_for_container { container_id="$1" container_name="$(docker inspect "${container_id}" --format '{{ .Name }}')" echo "Waiting for container: ${container_name} [${container_id}]" waiting_done="false" while [[ "${waiting_done}" != "true" ]]; do container_state="$(docker inspect "${container_id}" --format '{{ .State.Status }}')" if [[ "${container_state}" == "running" ]]; then health_status="$(docker inspect "${container_id}" --format '{{ .State.Health.Status }}')" echo "${container_name}: container_state=${container_state}, health_status=${health_status}" if [[ ${health_status} == "healthy" ]]; then waiting_done="true" fi else echo "${container_name}: container_state=${container_state}" waiting_done="true" fi sleep 1; done; } if [ "$1" == "--nodev" ]; then echo "starting everything in containers" docker-compose -f docker-compose.build.yml build swoole docker-compose build webapp docker-compose up -d webapp proxy haproxy wait_for_container 'kolab-webapp' exit 0 fi echo "Starting the development environment" rpm -qv composer >/dev/null 2>&1 || \ test ! -z "$(which composer 2>/dev/null)" || \ die "Is composer installed?" rpm -qv npm >/dev/null 2>&1 || \ test ! -z "$(which npm 2>/dev/null)" || \ die "Is npm installed?" rpm -qv php >/dev/null 2>&1 || \ test ! -z "$(which php 2>/dev/null)" || \ die "Is php installed?" rpm -qv php-ldap >/dev/null 2>&1 || \ test ! -z "$(php --ini | grep ldap)" || \ die "Is php-ldap installed?" rpm -qv php-mysqlnd >/dev/null 2>&1 || \ test ! -z "$(php --ini | grep mysql)" || \ die "Is php-mysqlnd installed?" test ! -z "$(php --modules | grep swoole)" || \ die "Is swoole installed?" # Ensure the containers we depend on are fully started wait_for_container 'kolab' wait_for_container 'kolab-redis' pushd ${base_dir}/src/ rm -rf vendor/ composer.lock php -dmemory_limit=-1 $(which composer) install npm install find bootstrap/cache/ -type f ! -name ".gitignore" -delete ./artisan key:generate ./artisan clear-compiled ./artisan cache:clear ./artisan horizon:install if [ ! -f storage/oauth-public.key -o ! -f storage/oauth-private.key ]; then ./artisan passport:keys --force fi cat >> .env << EOF PASSPORT_PRIVATE_KEY="$(cat storage/oauth-private.key)" PASSPORT_PUBLIC_KEY="$(cat storage/oauth-public.key)" EOF if rpm -qv chromium 2>/dev/null; then chver=$(rpmquery --queryformat="%{VERSION}" chromium | awk -F'.' '{print $1}') ./artisan dusk:chrome-driver ${chver} fi if [ ! -f 'resources/countries.php' ]; then ./artisan data:countries fi npm run dev popd pushd ${base_dir}/src/ rm -rf database/database.sqlite ./artisan db:ping --wait php -dmemory_limit=512M ./artisan migrate:refresh --seed ./artisan data:import || : nohup ./artisan octane:start --host=$(grep OCTANE_HTTP_HOST .env | tail -n1 | sed "s/OCTANE_HTTP_HOST=//") > octane.out & nohup ./artisan horizon > horizon.out & popd -docker-compose ${COMPOSE_ARGS} up --no-deps -d proxy haproxy +docker-compose up --no-deps -d proxy haproxy diff --git a/ci/Makefile b/ci/Makefile index 5a19745c..5391be7e 100644 --- a/ci/Makefile +++ b/ci/Makefile @@ -1,38 +1,30 @@ -HOSTNAME=ci.local -PUBLIC_IP=127.0.0.1 -OPENEXCHANGERATES_API_KEY=dummy -FIREBASE_API_KEY=dummy PWD=$(shell pwd) configure: cd .. ; \ - cp ci/env.local src/env.local ; \ - sed -i 's/{{ host }}/${HOSTNAME}/g' src/env.local ; \ - sed -i 's/{{ public_ip }}/${PUBLIC_IP}/g' src/env.local ; \ - sed -i 's/{{ openexchangerates_api_key }}/${OPENEXCHANGERATES_API_KEY}/g' src/env.local ; \ - sed -i 's/{{ firebase_api_key }}/${FIREBASE_API_KEY}/g' src/env.local ; + bin/configure.sh config.demo ; setup: cd .. && bin/quickstart.sh --nodev build: cd .. && DOCKER_BUILDKIT=0 docker compose -f docker-compose.yml -f docker-compose.build.yml build swoole && DOCKER_BUILDKIT=0 docker compose -f docker-compose.yml -f docker-compose.build.yml build tests && cd ci lint: docker kill kolab-tests || : ; \ kolab rm kolab-tests || : ; \ docker run -v ${PWD}/../:/src/kolab.orig -t kolab-tests /lint.sh test: docker kill kolab-tests || : ; \ docker rm kolab-tests || : ; \ docker run --network=kolab_kolab -v ${PWD}/../src:/src/kolabsrc.orig -t kolab-tests /init.sh testsuite shell: docker kill kolab-tests || : ; \ docker rm kolab-tests || : ; \ docker run --network=kolab_kolab -v ${PWD}/../src:/src/kolabsrc.orig -ti kolab-tests /init.sh shell all: configure build setup lint test diff --git a/ci/env.local b/ci/env.local deleted file mode 100644 index 3991e0db..00000000 --- a/ci/env.local +++ /dev/null @@ -1,49 +0,0 @@ -MFA_DSN=mysql://root:Welcome2KolabSystems@mariadb/roundcube -APP_DOMAIN={{ host }} -APP_WEBSITE_DOMAIN={{ host }} -APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E= -APP_PUBLIC_URL=https://{{ host }} -COTURN_STATIC_SECRET=uzYguvIl9tpZFMuQOE78DpOi6Jc7VFSD0UAnvgMsg5n4e74MgIf6vQvbc6LWzZjz -COTURN_PUBLIC_IP='{{ public_ip }}' -MEET_TURN_SERVER='turn:{{ public_ip }}:3478' -MEET_WEBRTC_LISTEN_IP='{{ public_ip }}' -MEET_PUBLIC_DOMAIN={{ host }} -MEET_SERVER_URLS=https://{{ host }}/meetmedia/api/ -MEET_LISTENING_HOST=172.18.0.1 -WEBMAIL_URL=/roundcubemail -APP_URL=https://{{ host }} -ASSET_URL=https://{{ host }} - -DB_HOST=mariadb -REDIS_HOST=redis -IMAP_URI=ssl://kolab:11993 -LDAP_HOSTS=kolab - -MOLLIE_KEY= -STRIPE_KEY= -STRIPE_PUBLIC_KEY= -STRIPE_WEBHOOK_SECRET= - -OCTANE_HTTP_HOST={{ host }} -OPENEXCHANGERATES_API_KEY={{ openexchangerates_api_key }} -FIREBASE_API_KEY={{ firebase_api_key }} - -#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) -PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee -PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf - -#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) -PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb -PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz - -APP_TENANT_ID=42 -APP_PASSPHRASE=simple123 - -MAIL_DRIVER=log - -KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert -KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem -KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key - -PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert -PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key diff --git a/config.demo/docker-compose.override.yml b/config.demo/docker-compose.override.yml new file mode 100644 index 00000000..bf93c1d5 --- /dev/null +++ b/config.demo/docker-compose.override.yml @@ -0,0 +1,47 @@ +version: '3' +services: + haproxy: + depends_on: + proxy: + condition: service_healthy + proxy: + depends_on: + kolab: + condition: service_healthy + webapp: + condition: service_healthy + build: + context: ./docker/proxy/ + args: + APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err} + SSL_CERTIFICATE: ${PROXY_SSL_CERTIFICATE:?err} + SSL_CERTIFICATE_KEY: ${PROXY_SSL_CERTIFICATE_KEY:?err} + healthcheck: + interval: 10s + test: "kill -0 $$(cat /run/nginx.pid)" + timeout: 5s + retries: 30 + container_name: kolab-proxy + hostname: proxy + image: kolab-proxy + extra_hosts: + - "meet:${MEET_LISTENING_HOST}" + networks: + kolab: + ipv4_address: 172.18.0.7 + tmpfs: + - /run + - /tmp + - /var/run + - /var/tmp + tty: true + volumes: + - ./docker/certs/:/etc/certs/:ro + - /etc/letsencrypt/:/etc/letsencrypt/:ro + ports: + # - "80:80" + - "443:443" + - "465:465" + - "587:587" + - "143:143" + - "993:993" diff --git a/src/.env.example b/config.demo/src/.env similarity index 81% copy from src/.env.example copy to config.demo/src/.env index b093a100..91d4030c 100644 --- a/src/.env.example +++ b/config.demo/src/.env @@ -1,185 +1,206 @@ APP_NAME=Kolab APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=https://kolab.local -#APP_PASSPHRASE= +APP_PASSPHRASE=simple123 APP_PUBLIC_URL=https://kolab.local APP_DOMAIN=kolab.local APP_WEBSITE_DOMAIN=kolab.local APP_THEME=default APP_TENANT_ID=5 APP_LOCALE=en APP_LOCALES= APP_WITH_ADMIN=1 APP_WITH_RESELLER=1 APP_WITH_SERVICES=1 APP_WITH_FILES=1 APP_LDAP=1 +APP_IMAP=0 APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';" APP_HEADER_XFO=sameorigin SIGNUP_LIMIT_EMAIL=0 SIGNUP_LIMIT_IP=0 ASSET_URL=https://kolab.local WEBMAIL_URL=/roundcubemail/ SUPPORT_URL=/support SUPPORT_EMAIL= LOG_CHANNEL=stack LOG_SLOW_REQUESTS=5 LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug DB_CONNECTION=mysql DB_DATABASE=kolabdev DB_HOST=mariadb DB_PASSWORD=kolab DB_PORT=3306 DB_USERNAME=kolabdev BROADCAST_DRIVER=redis CACHE_DRIVER=redis QUEUE_CONNECTION=redis SESSION_DRIVER=file SESSION_LIFETIME=120 OPENEXCHANGERATES_API_KEY="from openexchangerates.org" -MFA_DSN=mysql://roundcube:Welcome2KolabSystems@mariadb/roundcube +MFA_DSN=mysql://roundcube:kolab@mariadb/roundcube MFA_TOTP_DIGITS=6 MFA_TOTP_INTERVAL=30 MFA_TOTP_DIGEST=sha1 IMAP_URI=ssl://kolab:11993 IMAP_HOST=172.18.0.5 IMAP_ADMIN_LOGIN=cyrus-admin IMAP_ADMIN_PASSWORD=Welcome2KolabSystems IMAP_VERIFY_HOST=false IMAP_VERIFY_PEER=false LDAP_BASE_DN="dc=mgmt,dc=com" LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com" LDAP_HOSTS=kolab LDAP_PORT=389 LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com" LDAP_SERVICE_BIND_PW="Welcome2KolabSystems" LDAP_USE_SSL=false LDAP_USE_TLS=false # Administrative LDAP_ADMIN_BIND_DN="cn=Directory Manager" LDAP_ADMIN_BIND_PW="Welcome2KolabSystems" LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com" # Hosted (public registration) LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com" LDAP_HOSTED_BIND_PW="Welcome2KolabSystems" LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com" COTURN_PUBLIC_IP='172.18.0.1' COTURN_STATIC_SECRET="Welcome2KolabSystems" MEET_WEBHOOK_TOKEN=Welcome2KolabSystems MEET_SERVER_TOKEN=Welcome2KolabSystems MEET_SERVER_URLS=https://kolab.local/meetmedia/api/ MEET_SERVER_VERIFY_TLS=false MEET_WEBRTC_LISTEN_IP='172.18.0.1' MEET_PUBLIC_DOMAIN=kolab.local MEET_TURN_SERVER='turn:172.18.0.1:3478' MEET_LISTENING_HOST=172.18.0.1 PGP_ENABLE=true PGP_BINARY=/usr/bin/gpg PGP_AGENT=/usr/bin/gpg-agent PGP_GPGCONF=/usr/bin/gpgconf PGP_LENGTH= # Set these to IP addresses you serve WOAT with. # Have the domain owner point _woat. NS RRs refer to ns0{1,2}. WOAT_NS1=ns01.domain.tld WOAT_NS2=ns02.domain.tld REDIS_HOST=redis REDIS_PASSWORD=null REDIS_PORT=6379 -OCTANE_HTTP_HOST=127.0.0.1 +OCTANE_HTTP_HOST=0.0.0.0 SWOOLE_PACKAGE_MAX_LENGTH=10485760 PAYMENT_PROVIDER= MOLLIE_KEY= STRIPE_KEY= STRIPE_PUBLIC_KEY= STRIPE_WEBHOOK_SECRET= MAIL_DRIVER=log MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="Example.com" MAIL_REPLYTO_ADDRESS="replyto@example.com" MAIL_REPLYTO_NAME=null DNS_TTL=3600 DNS_SPF="v=spf1 mx -all" DNS_STATIC="%s. MX 10 ext-mx01.mykolab.com." DNS_COPY_FROM=null AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 MIX_ASSET_PATH='/' MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" # Generate with ./artisan passport:client --password #PASSPORT_PROXY_OAUTH_CLIENT_ID= #PASSPORT_PROXY_OAUTH_CLIENT_SECRET= PASSPORT_PRIVATE_KEY= PASSPORT_PUBLIC_KEY= PASSWORD_POLICY= COMPANY_NAME= COMPANY_ADDRESS= COMPANY_DETAILS= COMPANY_EMAIL= COMPANY_LOGO= COMPANY_FOOTER= VAT_COUNTRIES=CH,LI VAT_RATE=7.7 KB_ACCOUNT_DELETE= KB_ACCOUNT_SUSPENDED= KB_PAYMENT_SYSTEM= KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key + +APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E= +COTURN_STATIC_SECRET=uzYguvIl9tpZFMuQOE78DpOi6Jc7VFSD0UAnvgMsg5n4e74MgIf6vQvbc6LWzZjz + +MOLLIE_KEY="from mollie" +STRIPE_KEY="from stripe" +STRIPE_PUBLIC_KEY="from stripe" +STRIPE_WEBHOOK_SECRET="from stripe" + +OX_API_KEY="from openexchange" +FIREBASE_API_KEY="from firebase" + +#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) +PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee +PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf + +#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) +PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb +PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz + diff --git a/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php b/config.demo/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php similarity index 100% rename from src/database/migrations/2021_01_26_150000_change_sku_descriptions.php rename to config.demo/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php diff --git a/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php b/config.demo/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php similarity index 100% rename from src/database/migrations/2021_02_19_100000_transaction_amount_fix.php rename to config.demo/src/database/migrations/2021_02_19_100000_transaction_amount_fix.php diff --git a/src/database/migrations/2021_12_15_100000_rename_beta_skus.php b/config.demo/src/database/migrations/2021_12_15_100000_rename_beta_skus.php similarity index 100% rename from src/database/migrations/2021_12_15_100000_rename_beta_skus.php rename to config.demo/src/database/migrations/2021_12_15_100000_rename_beta_skus.php diff --git a/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php b/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php new file mode 100644 index 00000000..0cabaf4f --- /dev/null +++ b/config.demo/src/database/migrations/2022_05_13_090000_permissions_and_room_subscriptions.php @@ -0,0 +1,57 @@ + 'group-room', + 'name' => 'Group conference room', + 'description' => 'Shareable audio & video conference room', + 'cost' => 0, + 'units_free' => 0, + 'period' => 'monthly', + 'handler_class' => 'App\Handlers\GroupRoom', + 'active' => true, + ]); + + \App\Sku::create([ + 'title' => 'room', + 'name' => 'Standard conference room', + 'description' => 'Audio & video conference room', + 'cost' => 0, + 'units_free' => 0, + 'period' => 'monthly', + 'handler_class' => 'App\Handlers\Room', + 'active' => true, + ]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + \App\Sku::where('title', 'room')->delete(); + \App\Sku::where('title', 'group-room')->delete(); + + \App\Sku::create([ + 'title' => 'meet', + 'name' => 'Voice & Video Conferencing (public beta)', + 'description' => 'Video conferencing tool', + 'cost' => 0, + 'units_free' => 0, + 'period' => 'monthly', + 'handler_class' => 'App\Handlers\Meet', + 'active' => true, + ]); + } +}; diff --git a/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php b/config.demo/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php similarity index 100% rename from src/database/migrations/2022_07_08_100000_fix_group_sku_name.php rename to config.demo/src/database/migrations/2022_07_08_100000_fix_group_sku_name.php diff --git a/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php b/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php new file mode 100644 index 00000000..4453990d --- /dev/null +++ b/config.demo/src/database/migrations/2022_09_08_100001_plans_free_months.php @@ -0,0 +1,26 @@ +update(['free_months' => 1]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + } +}; diff --git a/config.demo/src/database/seeds/DatabaseSeeder.php b/config.demo/src/database/seeds/DatabaseSeeder.php new file mode 100644 index 00000000..cebfef72 --- /dev/null +++ b/config.demo/src/database/seeds/DatabaseSeeder.php @@ -0,0 +1,32 @@ +call([ + Seeds\IP4NetSeeder::class, + Seeds\TenantSeeder::class, + Seeds\DiscountSeeder::class, + Seeds\DomainSeeder::class, + Seeds\SkuSeeder::class, + Seeds\PackageSeeder::class, + Seeds\PlanSeeder::class, + Seeds\PowerDNSSeeder::class, + Seeds\UserSeeder::class, + Seeds\OauthClientSeeder::class, + Seeds\ResourceSeeder::class, + Seeds\SharedFolderSeeder::class, + Seeds\MeetRoomSeeder::class, + ]); + } +} diff --git a/src/database/seeds/local/DiscountSeeder.php b/config.demo/src/database/seeds/DiscountSeeder.php similarity index 97% rename from src/database/seeds/local/DiscountSeeder.php rename to config.demo/src/database/seeds/DiscountSeeder.php index 1d22bdd5..23633db2 100644 --- a/src/database/seeds/local/DiscountSeeder.php +++ b/config.demo/src/database/seeds/DiscountSeeder.php @@ -1,58 +1,58 @@ 'Free Account', 'discount' => 100, 'active' => true, ] ); Discount::create( [ 'description' => 'Student or Educational Institution', 'discount' => 30, 'active' => true, ] ); Discount::create( [ 'description' => 'Test voucher', 'discount' => 10, 'active' => true, 'code' => 'TEST', ] ); // We're running in reseller mode, add a sample discount $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get(); foreach ($tenants as $tenant) { $discount = Discount::create( [ 'description' => "Sample Discount by Reseller '{$tenant->title}'", 'discount' => 10, 'active' => true, ] ); $discount->tenant_id = $tenant->id; $discount->save(); } } } diff --git a/src/database/seeds/local/DomainSeeder.php b/config.demo/src/database/seeds/DomainSeeder.php similarity index 98% rename from src/database/seeds/local/DomainSeeder.php rename to config.demo/src/database/seeds/DomainSeeder.php index 777c3c4b..c8834b03 100644 --- a/src/database/seeds/local/DomainSeeder.php +++ b/config.demo/src/database/seeds/DomainSeeder.php @@ -1,86 +1,86 @@ $domain, 'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE, 'type' => Domain::TYPE_PUBLIC, ] ); } if (!in_array(\config('app.domain'), $domains)) { Domain::create( [ 'namespace' => \config('app.domain'), 'status' => DOMAIN::STATUS_CONFIRMED + Domain::STATUS_ACTIVE, 'type' => Domain::TYPE_PUBLIC, ] ); } $domains = [ 'example.com', 'example.net', 'example.org' ]; foreach ($domains as $domain) { Domain::create( [ 'namespace' => $domain, 'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE, 'type' => Domain::TYPE_EXTERNAL, ] ); } // We're running in reseller mode, add a sample discount $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get(); foreach ($tenants as $tenant) { $domainNamespace = strtolower(str_replace(' ', '-', $tenant->title)) . '.dev-local'; $domain = Domain::create( [ 'namespace' => $domainNamespace, 'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE, 'type' => Domain::TYPE_PUBLIC, ] ); $domain->tenant_id = $tenant->id; $domain->save(); } } } diff --git a/src/database/seeds/local/IP4NetSeeder.php b/config.demo/src/database/seeds/IP4NetSeeder.php similarity index 95% rename from src/database/seeds/local/IP4NetSeeder.php rename to config.demo/src/database/seeds/IP4NetSeeder.php index 7e46e52b..b6ff03d1 100644 --- a/src/database/seeds/local/IP4NetSeeder.php +++ b/config.demo/src/database/seeds/IP4NetSeeder.php @@ -1,31 +1,31 @@ 'ripencc', 'net_number' => '212.103.64.0', 'net_mask' => 19, 'net_broadcast' => '212.103.95.255', 'country' => 'CH', 'serial' => 1, 'created_at' => '1999-02-05 00:00:00', 'updated_at' => '2021-07-30 08:49:30' ] ); } } diff --git a/src/database/seeds/local/MeetRoomSeeder.php b/config.demo/src/database/seeds/MeetRoomSeeder.php similarity index 96% rename from src/database/seeds/local/MeetRoomSeeder.php rename to config.demo/src/database/seeds/MeetRoomSeeder.php index b38bb27a..d09b9189 100644 --- a/src/database/seeds/local/MeetRoomSeeder.php +++ b/config.demo/src/database/seeds/MeetRoomSeeder.php @@ -1,39 +1,39 @@ first(); $wallet = $john->wallets()->first(); $rooms = [ [ 'name' => 'john', 'description' => "Standard room" ], [ 'name' => 'shared', 'description' => "Shared room" ] ]; foreach ($rooms as $idx => $room) { $room = \App\Meet\Room::create($room); $rooms[$idx] = $room; } $rooms[0]->assignToWallet($wallet, 'room'); $rooms[1]->assignToWallet($wallet, 'group-room'); $rooms[1]->setConfig(['acl' => 'jack@kolab.org, full']); } } diff --git a/src/database/seeds/local/OauthClientSeeder.php b/config.demo/src/database/seeds/OauthClientSeeder.php similarity index 96% rename from src/database/seeds/local/OauthClientSeeder.php rename to config.demo/src/database/seeds/OauthClientSeeder.php index 5a608406..db20521f 100644 --- a/src/database/seeds/local/OauthClientSeeder.php +++ b/config.demo/src/database/seeds/OauthClientSeeder.php @@ -1,33 +1,33 @@ forceFill([ 'user_id' => null, 'name' => "Kolab Password Grant Client", 'secret' => \config('auth.proxy.client_secret'), 'provider' => 'users', 'redirect' => 'https://' . \config('app.website_domain'), 'personal_access_client' => 0, 'password_client' => 1, 'revoked' => false, ]); $client->id = \config('auth.proxy.client_id'); $client->save(); } } diff --git a/src/database/seeds/local/PackageSeeder.php b/config.demo/src/database/seeds/PackageSeeder.php similarity index 99% rename from src/database/seeds/local/PackageSeeder.php rename to config.demo/src/database/seeds/PackageSeeder.php index 78ecdf9c..7ef80906 100644 --- a/src/database/seeds/local/PackageSeeder.php +++ b/config.demo/src/database/seeds/PackageSeeder.php @@ -1,166 +1,166 @@ 'domain-hosting', 'tenant_id' => \config('app.tenant_id')])->first(); $skuGroupware = Sku::where(['title' => 'groupware', 'tenant_id' => \config('app.tenant_id')])->first(); $skuMailbox = Sku::where(['title' => 'mailbox', 'tenant_id' => \config('app.tenant_id')])->first(); $skuStorage = Sku::where(['title' => 'storage', 'tenant_id' => \config('app.tenant_id')])->first(); $package = Package::create( [ 'title' => 'kolab', 'name' => 'Groupware Account', 'description' => 'A fully functional groupware account.', 'discount_rate' => 0, ] ); $skus = [ $skuMailbox, $skuGroupware, $skuStorage ]; $package->skus()->saveMany($skus); // This package contains 2 units of the storage SKU, which just so happens to also // be the number of SKU free units. $package->skus()->updateExistingPivot( $skuStorage, ['qty' => 5], false ); $package = Package::create( [ 'title' => 'lite', 'name' => 'Lite Account', 'description' => 'Just mail and no more.', 'discount_rate' => 0, ] ); $skus = [ $skuMailbox, $skuStorage ]; $package->skus()->saveMany($skus); $package->skus()->updateExistingPivot( $skuStorage, ['qty' => 5], false ); $package = Package::create( [ 'title' => 'domain-hosting', 'name' => 'Domain Hosting', 'description' => 'Use your own, existing domain.', 'discount_rate' => 0, ] ); $skus = [ $skuDomain ]; $package->skus()->saveMany($skus); // We're running in reseller mode, add a sample discount $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get(); foreach ($tenants as $tenant) { $skuDomain = Sku::where(['title' => 'domain-hosting', 'tenant_id' => $tenant->id])->first(); $skuGroupware = Sku::where(['title' => 'groupware', 'tenant_id' => $tenant->id])->first(); $skuMailbox = Sku::where(['title' => 'mailbox', 'tenant_id' => $tenant->id])->first(); $skuStorage = Sku::where(['title' => 'storage', 'tenant_id' => $tenant->id])->first(); $package = Package::create( [ 'title' => 'kolab', 'name' => 'Groupware Account', 'description' => 'A fully functional groupware account.', 'discount_rate' => 0 ] ); $package->tenant_id = $tenant->id; $package->save(); $skus = [ $skuMailbox, $skuGroupware, $skuStorage ]; $package->skus()->saveMany($skus); // This package contains 2 units of the storage SKU, which just so happens to also // be the number of SKU free units. $package->skus()->updateExistingPivot( $skuStorage, ['qty' => 5], false ); $package = Package::create( [ 'title' => 'lite', 'name' => 'Lite Account', 'description' => 'Just mail and no more.', 'discount_rate' => 0 ] ); $package->tenant_id = $tenant->id; $package->save(); $skus = [ $skuMailbox, $skuStorage ]; $package->skus()->saveMany($skus); $package->skus()->updateExistingPivot( $skuStorage, ['qty' => 5], false ); $package = Package::create( [ 'title' => 'domain-hosting', 'name' => 'Domain Hosting', 'description' => 'Use your own, existing domain.', 'discount_rate' => 0 ] ); $package->tenant_id = $tenant->id; $package->save(); $skus = [ $skuDomain ]; $package->skus()->saveMany($skus); } } } diff --git a/src/database/seeds/local/PlanSeeder.php b/config.demo/src/database/seeds/PlanSeeder.php similarity index 99% rename from src/database/seeds/local/PlanSeeder.php rename to config.demo/src/database/seeds/PlanSeeder.php index a21530f5..d420dc42 100644 --- a/src/database/seeds/local/PlanSeeder.php +++ b/config.demo/src/database/seeds/PlanSeeder.php @@ -1,145 +1,145 @@ Everything you need to get started or try Kolab Now, including:

EOD; $plan = Plan::create( [ 'title' => 'individual', 'name' => 'Individual Account', 'description' => $description, 'free_months' => 1, 'discount_qty' => 0, 'discount_rate' => 0 ] ); $packages = [ Package::where(['title' => 'kolab', 'tenant_id' => \config('app.tenant_id')])->first() ]; $plan->packages()->saveMany($packages); $description = <<<'EOD'

All the features of the Individual Account, with the following extras:

EOD; $plan = Plan::create( [ 'title' => 'group', 'name' => 'Group Account', 'description' => $description, 'free_months' => 1, 'discount_qty' => 0, 'discount_rate' => 0 ] ); $packages = [ Package::where(['title' => 'domain-hosting', 'tenant_id' => \config('app.tenant_id')])->first(), Package::where(['title' => 'kolab', 'tenant_id' => \config('app.tenant_id')])->first() ]; $plan->packages()->saveMany($packages); // We're running in reseller mode, add a sample discount $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get(); foreach ($tenants as $tenant) { $description = <<<'EOD'

Everything you need to get started or try Kolab Now, including:

EOD; $plan = Plan::create( [ 'title' => 'individual', 'name' => 'Individual Account', 'free_months' => 1, 'description' => $description, 'discount_qty' => 0, 'discount_rate' => 0 ] ); $plan->tenant_id = $tenant->id; $plan->save(); $packages = [ Package::where(['title' => 'kolab', 'tenant_id' => $tenant->id])->first() ]; $plan->packages()->saveMany($packages); $description = <<<'EOD'

All the features of the Individual Account, with the following extras:

EOD; $plan = Plan::create( [ 'title' => 'group', 'name' => 'Group Account', 'description' => $description, 'free_months' => 1, 'discount_qty' => 0, 'discount_rate' => 0 ] ); $plan->tenant_id = $tenant->id; $plan->save(); $packages = [ Package::where(['title' => 'domain-hosting', 'tenant_id' => $tenant->id])->first(), Package::where(['title' => 'kolab', 'tenant_id' => $tenant->id])->first() ]; $plan->packages()->saveMany($packages); } } } diff --git a/src/database/seeds/local/PowerDNSSeeder.php b/config.demo/src/database/seeds/PowerDNSSeeder.php similarity index 96% rename from src/database/seeds/local/PowerDNSSeeder.php rename to config.demo/src/database/seeds/PowerDNSSeeder.php index 540b6184..d124fea3 100644 --- a/src/database/seeds/local/PowerDNSSeeder.php +++ b/config.demo/src/database/seeds/PowerDNSSeeder.php @@ -1,43 +1,43 @@ '_woat.' . \config('app.domain') ] ); /* $domainLuaSetting = \App\PowerDNS\DomainSetting::where( ['domain_id' => $domain->id, 'kind' => 'ENABLE-LUA-RECORDS'] )->first(); $domainLuaSetting->{'content'} = "1"; $domainLuaSetting->save(); */ /* \App\PowerDNS\Record::create( [ 'domain_id' => $domain->id, 'name' => $domain->{'name'}, 'type' => 'A', 'content' => '10.4.2.23' ] ); */ } } diff --git a/src/database/seeds/local/ResourceSeeder.php b/config.demo/src/database/seeds/ResourceSeeder.php similarity index 95% rename from src/database/seeds/local/ResourceSeeder.php rename to config.demo/src/database/seeds/ResourceSeeder.php index 36061c44..71778655 100644 --- a/src/database/seeds/local/ResourceSeeder.php +++ b/config.demo/src/database/seeds/ResourceSeeder.php @@ -1,33 +1,33 @@ first(); $wallet = $john->wallets()->first(); $resource = Resource::create([ 'name' => 'Conference Room #1', 'email' => 'resource-test1@kolab.org', ]); $resource->assignToWallet($wallet); $resource = Resource::create([ 'name' => 'Conference Room #2', 'email' => 'resource-test2@kolab.org', ]); $resource->assignToWallet($wallet); } } diff --git a/src/database/seeds/local/SharedFolderSeeder.php b/config.demo/src/database/seeds/SharedFolderSeeder.php similarity index 96% rename from src/database/seeds/local/SharedFolderSeeder.php rename to config.demo/src/database/seeds/SharedFolderSeeder.php index fdb9ed7c..c697ada7 100644 --- a/src/database/seeds/local/SharedFolderSeeder.php +++ b/config.demo/src/database/seeds/SharedFolderSeeder.php @@ -1,35 +1,35 @@ first(); $wallet = $john->wallets()->first(); $folder = SharedFolder::create([ 'name' => 'Calendar', 'email' => 'folder-event@kolab.org', 'type' => 'event', ]); $folder->assignToWallet($wallet); $folder = SharedFolder::create([ 'name' => 'Contacts', 'email' => 'folder-contact@kolab.org', 'type' => 'contact', ]); $folder->assignToWallet($wallet); } } diff --git a/src/database/seeds/local/SkuSeeder.php b/config.demo/src/database/seeds/SkuSeeder.php similarity index 99% rename from src/database/seeds/local/SkuSeeder.php rename to config.demo/src/database/seeds/SkuSeeder.php index 57f325a9..de661d07 100644 --- a/src/database/seeds/local/SkuSeeder.php +++ b/config.demo/src/database/seeds/SkuSeeder.php @@ -1,250 +1,250 @@ 'mailbox', 'name' => 'User Mailbox', 'description' => 'Just a mailbox', 'cost' => 500, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Mailbox', 'active' => true, ], [ 'title' => 'domain', 'name' => 'Hosted Domain', 'description' => 'Somewhere to place a mailbox', 'cost' => 100, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Domain', 'active' => false, ], [ 'title' => 'domain-registration', 'name' => 'Domain Registration', 'description' => 'Register a domain with us', 'cost' => 101, 'period' => 'yearly', 'handler_class' => 'App\Handlers\DomainRegistration', 'active' => false, ], [ 'title' => 'domain-hosting', 'name' => 'External Domain', 'description' => 'Host a domain that is externally registered', 'cost' => 100, 'units_free' => 1, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainHosting', 'active' => true, ], [ 'title' => 'domain-relay', 'name' => 'Domain Relay', 'description' => 'A domain you host at home, for which we relay email', 'cost' => 103, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainRelay', 'active' => false, ], [ 'title' => 'storage', 'name' => 'Storage Quota', 'description' => 'Some wiggle room', 'cost' => 25, 'units_free' => 5, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Storage', 'active' => true, ], [ 'title' => 'groupware', 'name' => 'Groupware Features', 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.', 'cost' => 490, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Groupware', 'active' => true, ], [ 'title' => 'resource', 'name' => 'Resource', 'description' => 'Reservation taker', 'cost' => 101, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Resource', 'active' => true, ], [ 'title' => 'shared-folder', 'name' => 'Shared Folder', 'description' => 'A shared folder', 'cost' => 89, 'period' => 'monthly', 'handler_class' => 'App\Handlers\SharedFolder', 'active' => true, ], [ 'title' => '2fa', 'name' => '2-Factor Authentication', 'description' => 'Two factor authentication for webmail and administration panel', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Auth2F', 'active' => true, ], [ 'title' => 'activesync', 'name' => 'Activesync', 'description' => 'Mobile synchronization', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Activesync', 'active' => true, ], [ 'title' => 'beta', 'name' => 'Private Beta (invitation only)', 'description' => 'Access to the private beta program features', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Beta', 'active' => false, ], [ 'title' => 'group', 'name' => 'Distribution list', 'description' => 'Mail distribution list', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Group', 'active' => true, ], [ 'title' => 'group-room', 'name' => 'Group conference room', 'description' => 'Shareable audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\GroupRoom', 'active' => true, ], [ 'title' => 'room', 'name' => 'Standard conference room', 'description' => 'Audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Room', 'active' => true, ], ]; foreach ($skus as $sku) { // Check existence because migration might have added this already if (!Sku::where('title', $sku['title'])->where('tenant_id', \config('app.tenant_id'))->first()) { Sku::create($sku); } } $skus = [ [ 'title' => 'mailbox', 'name' => 'User Mailbox', 'description' => 'Just a mailbox', 'cost' => 500, 'fee' => 333, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Mailbox', 'active' => true, ], [ 'title' => 'storage', 'name' => 'Storage Quota', 'description' => 'Some wiggle room', 'cost' => 25, 'fee' => 16, 'units_free' => 5, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Storage', 'active' => true, ], [ 'title' => 'domain-hosting', 'name' => 'External Domain', 'description' => 'Host a domain that is externally registered', 'cost' => 100, 'fee' => 66, 'units_free' => 1, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainHosting', 'active' => true, ], [ 'title' => 'groupware', 'name' => 'Groupware Features', 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.', 'cost' => 490, 'fee' => 327, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Groupware', 'active' => true, ], [ 'title' => '2fa', 'name' => '2-Factor Authentication', 'description' => 'Two factor authentication for webmail and administration panel', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Auth2F', 'active' => true, ], [ 'title' => 'activesync', 'name' => 'Activesync', 'description' => 'Mobile synchronization', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Activesync', 'active' => true, ], ]; // for tenants that are not the configured tenant id $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get(); foreach ($tenants as $tenant) { foreach ($skus as $sku) { $sku = Sku::create($sku); $sku->tenant_id = $tenant->id; $sku->save(); } } } } diff --git a/src/database/seeds/local/TenantSeeder.php b/config.demo/src/database/seeds/TenantSeeder.php similarity index 96% rename from src/database/seeds/local/TenantSeeder.php rename to config.demo/src/database/seeds/TenantSeeder.php index 0c8c18f4..57fcb168 100644 --- a/src/database/seeds/local/TenantSeeder.php +++ b/config.demo/src/database/seeds/TenantSeeder.php @@ -1,37 +1,37 @@ 'Kolab Now'])->first(); if (!$tenant) { Tenant::create(['title' => 'Kolab Now']); } $tenant = Tenant::where(['title' => 'Sample Tenant'])->first(); if (!$tenant) { $tenant = Tenant::create(['title' => 'Sample Tenant']); } $tenant = Tenant::where(['title' => 'kanarip.ch'])->first(); if (!$tenant) { $tenant = Tenant::create(['title' => 'kanarip.ch']); } } } } diff --git a/src/database/seeds/local/UserSeeder.php b/config.demo/src/database/seeds/UserSeeder.php similarity index 99% rename from src/database/seeds/local/UserSeeder.php rename to config.demo/src/database/seeds/UserSeeder.php index b93db82f..8812f910 100644 --- a/src/database/seeds/local/UserSeeder.php +++ b/config.demo/src/database/seeds/UserSeeder.php @@ -1,250 +1,250 @@ 'kolab.org', 'status' => Domain::STATUS_NEW + Domain::STATUS_ACTIVE + Domain::STATUS_CONFIRMED + Domain::STATUS_VERIFIED, 'type' => Domain::TYPE_EXTERNAL ] ); $john = User::create( [ 'email' => 'john@kolab.org', 'password' => \App\Utils::generatePassphrase() ] ); $john->setSettings( [ 'first_name' => 'John', 'last_name' => 'Doe', 'currency' => 'USD', 'country' => 'US', 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005", 'external_email' => 'john.doe.external@gmail.com', 'organization' => 'Kolab Developers', 'phone' => '+1 509-248-1111', ] ); $john->setAliases(['john.doe@kolab.org']); $wallet = $john->wallets->first(); $packageDomain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $packageKolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $packageLite = \App\Package::withEnvTenantContext()->where('title', 'lite')->first(); $domain->assignPackage($packageDomain, $john); $john->assignPackage($packageKolab); $appDomain = \App\Domain::where( [ 'namespace' => \config('app.domain') ] )->first(); $fred = User::create( [ 'email' => 'fred@' . \config('app.domain'), 'password' => \App\Utils::generatePassphrase() ] ); $fred->setSettings( [ 'first_name' => 'fred', 'last_name' => 'Doe', 'currency' => 'USD', 'country' => 'US', 'billing_address' => "601 13th Street NW\nSuite 900 South\nWashington, DC 20005", 'external_email' => 'fred.doe.external@gmail.com', 'organization' => 'Kolab Developers', 'phone' => '+1 509-248-1111', ] ); $appDomain->assignPackage($packageDomain, $fred); $fred->assignPackage($packageKolab); $jack = User::create( [ 'email' => 'jack@kolab.org', 'password' => \App\Utils::generatePassphrase() ] ); $jack->setSettings( [ 'first_name' => 'Jack', 'last_name' => 'Daniels', 'currency' => 'USD', 'country' => 'US' ] ); $jack->setAliases(['jack.daniels@kolab.org']); $john->assignPackage($packageKolab, $jack); foreach ($john->entitlements as $entitlement) { $entitlement->created_at = Carbon::now()->subMonthsWithoutOverflow(1); $entitlement->updated_at = Carbon::now()->subMonthsWithoutOverflow(1); $entitlement->save(); } $ned = User::create( [ 'email' => 'ned@kolab.org', 'password' => \App\Utils::generatePassphrase() ] ); $ned->setSettings( [ 'first_name' => 'Edward', 'last_name' => 'Flanders', 'currency' => 'USD', 'country' => 'US', 'guam_enabled' => false, ] ); $john->assignPackage($packageKolab, $ned); $ned->assignSku(\App\Sku::withEnvTenantContext()->where('title', 'activesync')->first(), 1); // Ned is a controller on Jack's wallet $john->wallets()->first()->addController($ned); // Ned is also our 2FA test user $sku2fa = Sku::withEnvTenantContext()->where('title', '2fa')->first(); $ned->assignSku($sku2fa); SecondFactor::seed('ned@kolab.org'); $joe = User::create( [ 'email' => 'joe@kolab.org', 'password' => \App\Utils::generatePassphrase() ] ); $john->assignPackage($packageLite, $joe); //$john->assignSku(Sku::firstOrCreate(['title' => 'beta'])); //$john->assignSku(Sku::firstOrCreate(['title' => 'meet'])); $joe->setAliases(['joe.monster@kolab.org']); // This only exists so the user create job doesn't fail because the domain is not found Domain::create( [ 'namespace' => 'jeroen.jeroen', 'status' => Domain::STATUS_NEW + Domain::STATUS_ACTIVE + Domain::STATUS_CONFIRMED + Domain::STATUS_VERIFIED, 'type' => Domain::TYPE_EXTERNAL ] ); $jeroen = User::create( [ 'email' => 'jeroen@jeroen.jeroen', 'password' => \App\Utils::generatePassphrase() ] ); $jeroen->role = 'admin'; $jeroen->save(); $reseller = User::create( [ 'email' => 'reseller@' . \config('app.domain'), 'password' => \App\Utils::generatePassphrase() ] ); $reseller->role = 'reseller'; $reseller->save(); $reseller->assignPackage($packageKolab); // for tenants that are not the configured tenant id $tenants = \App\Tenant::where('id', '!=', \config('app.tenant_id'))->get(); foreach ($tenants as $tenant) { $domain = Domain::where('tenant_id', $tenant->id)->first(); $packageKolab = \App\Package::where( [ 'title' => 'kolab', 'tenant_id' => $tenant->id ] )->first(); if ($domain) { $reseller = User::create( [ 'email' => 'reseller@' . $domain->namespace, 'password' => \App\Utils::generatePassphrase() ] ); $reseller->role = 'reseller'; $reseller->tenant_id = $tenant->id; $reseller->save(); $reseller->assignPackage($packageKolab); $user = User::create( [ 'email' => 'user@' . $domain->namespace, 'password' => \App\Utils::generatePassphrase() ] ); $user->tenant_id = $tenant->id; $user->save(); $user->assignPackage($packageKolab); } } // Create imap admin service account User::create( [ 'email' => \config('imap.admin_login'), 'password' => \config('imap.admin_password') ] ); } } diff --git a/config.dev/docker-compose.override.yml b/config.dev/docker-compose.override.yml new file mode 100644 index 00000000..319c9b6f --- /dev/null +++ b/config.dev/docker-compose.override.yml @@ -0,0 +1,55 @@ +version: '3' +services: + kolab: + ports: + - "389:389" + - "8880:8880" + - "8443:8443" + - "10143:10143" + - "10587:10587" + - "11143:11143" + - "11993:11993" + - "12143:12143" + mariadb: + ports: + - "3306:3306" + redis: + ports: + - "6379:6379" + haproxy: + depends_on: + proxy: + condition: service_healthy + proxy: + depends_on: + kolab: + condition: service_healthy + webapp: + condition: service_healthy + build: + context: ./docker/proxy/ + args: + APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err} + SSL_CERTIFICATE: ${PROXY_SSL_CERTIFICATE:?err} + SSL_CERTIFICATE_KEY: ${PROXY_SSL_CERTIFICATE_KEY:?err} + healthcheck: + interval: 10s + test: "kill -0 $$(cat /run/nginx.pid)" + timeout: 5s + retries: 30 + container_name: kolab-proxy + hostname: proxy + image: kolab-proxy + extra_hosts: + - "meet:${MEET_LISTENING_HOST}" + - "webapp:127.0.0.1" + network_mode: host + tmpfs: + - /run + - /tmp + - /var/run + - /var/tmp + tty: true + volumes: + - ./docker/certs/:/etc/certs/:ro + - /etc/letsencrypt/:/etc/letsencrypt/:ro diff --git a/src/.env.example b/config.dev/src/.env similarity index 77% copy from src/.env.example copy to config.dev/src/.env index b093a100..dac7465e 100644 --- a/src/.env.example +++ b/config.dev/src/.env @@ -1,185 +1,200 @@ APP_NAME=Kolab APP_ENV=local -APP_KEY= +APP_KEY=base64:FG6ECzyAMSmyX+eYwO/FW3bwnarbKkBhqtO65vlMb1E= APP_DEBUG=true APP_URL=https://kolab.local -#APP_PASSPHRASE= +APP_PASSPHRASE=simple123 APP_PUBLIC_URL=https://kolab.local APP_DOMAIN=kolab.local APP_WEBSITE_DOMAIN=kolab.local APP_THEME=default APP_TENANT_ID=5 APP_LOCALE=en APP_LOCALES= APP_WITH_ADMIN=1 APP_WITH_RESELLER=1 APP_WITH_SERVICES=1 APP_WITH_FILES=1 APP_LDAP=1 APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';" APP_HEADER_XFO=sameorigin SIGNUP_LIMIT_EMAIL=0 SIGNUP_LIMIT_IP=0 ASSET_URL=https://kolab.local WEBMAIL_URL=/roundcubemail/ SUPPORT_URL=/support SUPPORT_EMAIL= LOG_CHANNEL=stack LOG_SLOW_REQUESTS=5 LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug DB_CONNECTION=mysql DB_DATABASE=kolabdev -DB_HOST=mariadb +DB_HOST=127.0.0.1 DB_PASSWORD=kolab DB_PORT=3306 DB_USERNAME=kolabdev BROADCAST_DRIVER=redis CACHE_DRIVER=redis QUEUE_CONNECTION=redis SESSION_DRIVER=file SESSION_LIFETIME=120 OPENEXCHANGERATES_API_KEY="from openexchangerates.org" -MFA_DSN=mysql://roundcube:Welcome2KolabSystems@mariadb/roundcube +MFA_DSN=mysql://root:Welcome2KolabSystems@127.0.0.1/roundcube MFA_TOTP_DIGITS=6 MFA_TOTP_INTERVAL=30 MFA_TOTP_DIGEST=sha1 -IMAP_URI=ssl://kolab:11993 -IMAP_HOST=172.18.0.5 +IMAP_URI=ssl://127.0.0.1:11993 +IMAP_HOST=127.0.0.1 IMAP_ADMIN_LOGIN=cyrus-admin IMAP_ADMIN_PASSWORD=Welcome2KolabSystems IMAP_VERIFY_HOST=false IMAP_VERIFY_PEER=false LDAP_BASE_DN="dc=mgmt,dc=com" LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com" -LDAP_HOSTS=kolab +LDAP_HOSTS=127.0.0.1 LDAP_PORT=389 LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com" LDAP_SERVICE_BIND_PW="Welcome2KolabSystems" LDAP_USE_SSL=false LDAP_USE_TLS=false # Administrative LDAP_ADMIN_BIND_DN="cn=Directory Manager" LDAP_ADMIN_BIND_PW="Welcome2KolabSystems" LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com" # Hosted (public registration) LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com" LDAP_HOSTED_BIND_PW="Welcome2KolabSystems" LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com" -COTURN_PUBLIC_IP='172.18.0.1' +COTURN_PUBLIC_IP='127.0.0.1' COTURN_STATIC_SECRET="Welcome2KolabSystems" MEET_WEBHOOK_TOKEN=Welcome2KolabSystems MEET_SERVER_TOKEN=Welcome2KolabSystems -MEET_SERVER_URLS=https://kolab.local/meetmedia/api/ -MEET_SERVER_VERIFY_TLS=false -MEET_WEBRTC_LISTEN_IP='172.18.0.1' +MEET_TURN_SERVER='turn:127.0.0.1:3478' +MEET_WEBRTC_LISTEN_IP='127.0.0.1' +MEET_LISTENING_HOST=127.0.0.1 MEET_PUBLIC_DOMAIN=kolab.local -MEET_TURN_SERVER='turn:172.18.0.1:3478' -MEET_LISTENING_HOST=172.18.0.1 +MEET_SERVER_URLS=https://127.0.0.1/meetmedia/api/ +MEET_SERVER_VERIFY_TLS=false PGP_ENABLE=true PGP_BINARY=/usr/bin/gpg PGP_AGENT=/usr/bin/gpg-agent PGP_GPGCONF=/usr/bin/gpgconf PGP_LENGTH= # Set these to IP addresses you serve WOAT with. # Have the domain owner point _woat. NS RRs refer to ns0{1,2}. WOAT_NS1=ns01.domain.tld WOAT_NS2=ns02.domain.tld -REDIS_HOST=redis +REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 OCTANE_HTTP_HOST=127.0.0.1 SWOOLE_PACKAGE_MAX_LENGTH=10485760 PAYMENT_PROVIDER= MOLLIE_KEY= STRIPE_KEY= STRIPE_PUBLIC_KEY= STRIPE_WEBHOOK_SECRET= MAIL_DRIVER=log MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="Example.com" MAIL_REPLYTO_ADDRESS="replyto@example.com" MAIL_REPLYTO_NAME=null DNS_TTL=3600 DNS_SPF="v=spf1 mx -all" DNS_STATIC="%s. MX 10 ext-mx01.mykolab.com." DNS_COPY_FROM=null AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 MIX_ASSET_PATH='/' MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" # Generate with ./artisan passport:client --password #PASSPORT_PROXY_OAUTH_CLIENT_ID= #PASSPORT_PROXY_OAUTH_CLIENT_SECRET= +# Generate with ./artisan passport:client --password +#PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID= +#PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET= + PASSPORT_PRIVATE_KEY= PASSPORT_PUBLIC_KEY= PASSWORD_POLICY= COMPANY_NAME= COMPANY_ADDRESS= COMPANY_DETAILS= COMPANY_EMAIL= COMPANY_LOGO= COMPANY_FOOTER= VAT_COUNTRIES=CH,LI VAT_RATE=7.7 KB_ACCOUNT_DELETE= KB_ACCOUNT_SUSPENDED= KB_PAYMENT_SYSTEM= KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key + +OX_API_KEY= +FIREBASE_API_KEY= + +#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) +PASSPORT_PROXY_OAUTH_CLIENT_ID=942edef5-3dbd-4a14-8e3e-d5d59b727bee +PASSPORT_PROXY_OAUTH_CLIENT_SECRET=L6L0n56ecvjjK0cJMjeeV1pPAeffUBO0YSSH63wf + +#Generated by php artisan passport:client --password, but can be left hardcoded (the seeder will pick it up) +PASSPORT_COMPANIONAPP_OAUTH_CLIENT_ID=9566e018-f05d-425c-9915-420cdb9258bb +PASSPORT_COMPANIONAPP_OAUTH_CLIENT_SECRET=XjgV6SU9shO0QFKaU6pQPRC5rJpyRezDJTSoGLgz diff --git a/config.dev/src/database b/config.dev/src/database new file mode 120000 index 00000000..28204f1b --- /dev/null +++ b/config.dev/src/database @@ -0,0 +1 @@ +../../config.demo/src/database \ No newline at end of file diff --git a/config.prod/docker-compose.override.yml b/config.prod/docker-compose.override.yml new file mode 120000 index 00000000..16676b85 --- /dev/null +++ b/config.prod/docker-compose.override.yml @@ -0,0 +1 @@ +../config.demo/docker-compose.override.yml \ No newline at end of file diff --git a/src/.env.example b/config.prod/src/.env similarity index 68% rename from src/.env.example rename to config.prod/src/.env index b093a100..c3c6a6b1 100644 --- a/src/.env.example +++ b/config.prod/src/.env @@ -1,185 +1,157 @@ APP_NAME=Kolab APP_ENV=local APP_KEY= APP_DEBUG=true -APP_URL=https://kolab.local -#APP_PASSPHRASE= -APP_PUBLIC_URL=https://kolab.local -APP_DOMAIN=kolab.local -APP_WEBSITE_DOMAIN=kolab.local +APP_URL=https://{{ host }} +APP_PUBLIC_URL=https://{{ host }} +APP_DOMAIN={{ host }} +APP_WEBSITE_DOMAIN={{ host }} APP_THEME=default APP_TENANT_ID=5 APP_LOCALE=en APP_LOCALES= APP_WITH_ADMIN=1 APP_WITH_RESELLER=1 APP_WITH_SERVICES=1 APP_WITH_FILES=1 APP_LDAP=1 +APP_IMAP=0 APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';" APP_HEADER_XFO=sameorigin SIGNUP_LIMIT_EMAIL=0 SIGNUP_LIMIT_IP=0 -ASSET_URL=https://kolab.local +ASSET_URL=https://{{ host }} WEBMAIL_URL=/roundcubemail/ SUPPORT_URL=/support SUPPORT_EMAIL= LOG_CHANNEL=stack LOG_SLOW_REQUESTS=5 LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug DB_CONNECTION=mysql DB_DATABASE=kolabdev DB_HOST=mariadb -DB_PASSWORD=kolab +DB_PASSWORD={{ admin_password }} DB_PORT=3306 DB_USERNAME=kolabdev BROADCAST_DRIVER=redis CACHE_DRIVER=redis QUEUE_CONNECTION=redis SESSION_DRIVER=file SESSION_LIFETIME=120 OPENEXCHANGERATES_API_KEY="from openexchangerates.org" -MFA_DSN=mysql://roundcube:Welcome2KolabSystems@mariadb/roundcube +MFA_DSN=mysql://roundcube:{{ admin_password }}@mariadb/roundcube MFA_TOTP_DIGITS=6 MFA_TOTP_INTERVAL=30 MFA_TOTP_DIGEST=sha1 IMAP_URI=ssl://kolab:11993 IMAP_HOST=172.18.0.5 IMAP_ADMIN_LOGIN=cyrus-admin -IMAP_ADMIN_PASSWORD=Welcome2KolabSystems +IMAP_ADMIN_PASSWORD={{ admin_password }} IMAP_VERIFY_HOST=false IMAP_VERIFY_PEER=false LDAP_BASE_DN="dc=mgmt,dc=com" LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com" LDAP_HOSTS=kolab LDAP_PORT=389 LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com" -LDAP_SERVICE_BIND_PW="Welcome2KolabSystems" +LDAP_SERVICE_BIND_PW="{{ admin_password }}" LDAP_USE_SSL=false LDAP_USE_TLS=false # Administrative LDAP_ADMIN_BIND_DN="cn=Directory Manager" -LDAP_ADMIN_BIND_PW="Welcome2KolabSystems" +LDAP_ADMIN_BIND_PW="{{ admin_password }}" LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com" # Hosted (public registration) LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com" -LDAP_HOSTED_BIND_PW="Welcome2KolabSystems" +LDAP_HOSTED_BIND_PW="{{ admin_password }}" LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com" -COTURN_PUBLIC_IP='172.18.0.1' -COTURN_STATIC_SECRET="Welcome2KolabSystems" +COTURN_PUBLIC_IP='{{ public_ip }}' -MEET_WEBHOOK_TOKEN=Welcome2KolabSystems -MEET_SERVER_TOKEN=Welcome2KolabSystems -MEET_SERVER_URLS=https://kolab.local/meetmedia/api/ +MEET_SERVER_URLS=https://{{ host }}/meetmedia/api/ MEET_SERVER_VERIFY_TLS=false MEET_WEBRTC_LISTEN_IP='172.18.0.1' -MEET_PUBLIC_DOMAIN=kolab.local +MEET_PUBLIC_DOMAIN={{ host }} MEET_TURN_SERVER='turn:172.18.0.1:3478' MEET_LISTENING_HOST=172.18.0.1 PGP_ENABLE=true PGP_BINARY=/usr/bin/gpg PGP_AGENT=/usr/bin/gpg-agent PGP_GPGCONF=/usr/bin/gpgconf PGP_LENGTH= -# Set these to IP addresses you serve WOAT with. -# Have the domain owner point _woat. NS RRs refer to ns0{1,2}. -WOAT_NS1=ns01.domain.tld -WOAT_NS2=ns02.domain.tld - REDIS_HOST=redis REDIS_PASSWORD=null REDIS_PORT=6379 -OCTANE_HTTP_HOST=127.0.0.1 +OCTANE_HTTP_HOST={{ host }} SWOOLE_PACKAGE_MAX_LENGTH=10485760 -PAYMENT_PROVIDER= -MOLLIE_KEY= -STRIPE_KEY= -STRIPE_PUBLIC_KEY= -STRIPE_WEBHOOK_SECRET= - MAIL_DRIVER=log MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="Example.com" MAIL_REPLYTO_ADDRESS="replyto@example.com" MAIL_REPLYTO_NAME=null DNS_TTL=3600 DNS_SPF="v=spf1 mx -all" DNS_STATIC="%s. MX 10 ext-mx01.mykolab.com." DNS_COPY_FROM=null -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -AWS_DEFAULT_REGION=us-east-1 -AWS_BUCKET= -AWS_USE_PATH_STYLE_ENDPOINT=false - -PUSHER_APP_ID= -PUSHER_APP_KEY= -PUSHER_APP_SECRET= -PUSHER_APP_CLUSTER=mt1 - MIX_ASSET_PATH='/' -MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" -MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" - -# Generate with ./artisan passport:client --password -#PASSPORT_PROXY_OAUTH_CLIENT_ID= -#PASSPORT_PROXY_OAUTH_CLIENT_SECRET= PASSPORT_PRIVATE_KEY= PASSPORT_PUBLIC_KEY= PASSWORD_POLICY= COMPANY_NAME= COMPANY_ADDRESS= COMPANY_DETAILS= COMPANY_EMAIL= COMPANY_LOGO= COMPANY_FOOTER= VAT_COUNTRIES=CH,LI VAT_RATE=7.7 KB_ACCOUNT_DELETE= KB_ACCOUNT_SUSPENDED= KB_PAYMENT_SYSTEM= KOLAB_SSL_CERTIFICATE=/etc/pki/tls/certs/kolab.hosted.com.cert KOLAB_SSL_CERTIFICATE_FULLCHAIN=/etc/pki/tls/certs/kolab.hosted.com.chain.pem KOLAB_SSL_CERTIFICATE_KEY=/etc/pki/tls/certs/kolab.hosted.com.key PROXY_SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert PROXY_SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key + +OPENEXCHANGERATES_API_KEY={{ openexchangerates_api_key }} +FIREBASE_API_KEY={{ firebase_api_key }} diff --git a/config.prod/src/database/migrations b/config.prod/src/database/migrations new file mode 120000 index 00000000..dae12a41 --- /dev/null +++ b/config.prod/src/database/migrations @@ -0,0 +1 @@ +../../../config.demo/src/database/migrations \ No newline at end of file diff --git a/config.prod/src/database/seeds/AdminSeeder.php b/config.prod/src/database/seeds/AdminSeeder.php new file mode 100644 index 00000000..35e0758b --- /dev/null +++ b/config.prod/src/database/seeds/AdminSeeder.php @@ -0,0 +1,86 @@ + 'domain-hosting', 'tenant_id' => \config('app.tenant_id')])->first(); + $skuGroupware = Sku::where(['title' => 'groupware', 'tenant_id' => \config('app.tenant_id')])->first(); + $skuMailbox = Sku::where(['title' => 'mailbox', 'tenant_id' => \config('app.tenant_id')])->first(); + $skuStorage = Sku::where(['title' => 'storage', 'tenant_id' => \config('app.tenant_id')])->first(); + + $packageKolab = Package::create( + [ + 'title' => 'kolab', + 'name' => 'Groupware Account', + 'description' => 'A fully functional groupware account.', + 'discount_rate' => 0, + ] + ); + $packageKolab->skus()->saveMany([ + $skuMailbox, + $skuGroupware, + $skuStorage + ]); + + + $packageDomain = Package::create( + [ + 'title' => 'domain-hosting', + 'name' => 'Domain Hosting', + 'description' => 'Use your own, existing domain.', + 'discount_rate' => 0, + ] + ); + $packageDomain->skus()->saveMany([ + $skuDomain + ]); + + + + //Create primary domain + $appDomain = Domain::create( + [ + 'namespace' => \config('app.domain'), + 'status' => DOMAIN::STATUS_CONFIRMED + Domain::STATUS_ACTIVE, + 'type' => Domain::TYPE_PUBLIC, + ] + ); + + //Create admin user + $admin = User::create( + [ + 'email' => 'admin@' . \config('app.domain'), + 'password' => \App\Utils::generatePassphrase() + ] + ); + + $admin->setSettings( + [ + 'first_name' => 'Admin', + ] + ); + + $appDomain->assignPackage($packageDomain, $admin); + $admin->assignPackage($packageKolab); + } +} + diff --git a/config.prod/src/database/seeds/AppKeySeeder.php b/config.prod/src/database/seeds/AppKeySeeder.php new file mode 100644 index 00000000..4554bf5d --- /dev/null +++ b/config.prod/src/database/seeds/AppKeySeeder.php @@ -0,0 +1,62 @@ +generateRandomKey(); + $this->writeNewEnvironmentFileWith($key); + } + + /** + * Generate a random key for the application. + * + * @return string + */ + protected function generateRandomKey() + { + return 'base64:' . base64_encode( + Encrypter::generateKey(\config('app.cipher')) + ); + } + + /** + * Write a new environment file with the given key. + * + * @param string $key + * @return void + */ + protected function writeNewEnvironmentFileWith($key) + { + file_put_contents(\app()->environmentFilePath(), preg_replace( + $this->keyReplacementPattern(), + 'APP_KEY=' . $key, + file_get_contents(\app()->environmentFilePath()) + )); + } + + /** + * Get a regex pattern that will match env APP_KEY with any random key. + * + * @return string + */ + protected function keyReplacementPattern() + { + $escaped = preg_quote('=' . \config('app.key'), '/'); + return "/^APP_KEY{$escaped}/m"; + } +} + diff --git a/config.prod/src/database/seeds/DatabaseSeeder.php b/config.prod/src/database/seeds/DatabaseSeeder.php new file mode 100644 index 00000000..b6b4d24a --- /dev/null +++ b/config.prod/src/database/seeds/DatabaseSeeder.php @@ -0,0 +1,24 @@ +call([ + Seeds\AppKeySeeder::class, + Seeds\PassportSeeder::class, + Seeds\PowerDNSSeeder::class, + Seeds\SkuSeeder::class, + Seeds\AdminSeeder::class, + ]); + } +} diff --git a/config.prod/src/database/seeds/PassportSeeder.php b/config.prod/src/database/seeds/PassportSeeder.php new file mode 100644 index 00000000..4bc8aa1e --- /dev/null +++ b/config.prod/src/database/seeds/PassportSeeder.php @@ -0,0 +1,103 @@ +getPublicKey()); + file_put_contents($privateKey, (string) $key); + + $this->writeNewEnvironmentFileWith('PASSPORT_PRIVATE_KEY', 'passport.private_key', $key); + $this->writeNewEnvironmentFileWith('PASSPORT_PUBLIC_KEY', 'passport.public_key', (string) $key->getPublicKey()); + + //Create a password grant client for the webapp + $secret = $this->generateRandomKey(); + + $client = Passport::client()->forceFill([ + 'user_id' => null, + 'name' => "Kolab Password Grant Client", + 'secret' => $secret, + 'provider' => 'users', + 'redirect' => 'https://' . \config('app.website_domain'), + 'personal_access_client' => 0, + 'password_client' => 1, + 'revoked' => false, + ]); + $client->save(); + + $this->writeNewEnvironmentFileWith('PASSPORT_PROXY_OAUTH_CLIENT_ID', 'auth.proxy.client_id', $client->id); + $this->writeNewEnvironmentFileWith('PASSPORT_PROXY_OAUTH_CLIENT_SECRET', 'auth.proxy.client_secret', $secret); + } + + /** + * Generate a random key for the application. + * + * @return string + */ + protected function generateRandomKey() + { + return base64_encode( + Encrypter::generateKey(\config('app.cipher')) + ); + } + + /** + * Write a new environment file with the given key. + * + * @param string $key + * @param string $configKey + * @param string $value + * @return void + */ + protected function writeNewEnvironmentFileWith($key, $configKey, $value) + { + $path = \app()->environmentFilePath(); + $count = 0; + $line = "{$key}=\"{$value}\""; + $result = preg_replace( + $this->keyReplacementPattern($key, \config($configKey)), + $line, + file_get_contents($path), + -1, + $count + ); + //Append if it doesn't exist + if ($count == 0) { + $result = $result . "\n$line"; + } + file_put_contents($path, $result); + } + + /** + * Get a regex pattern that will match env APP_KEY with any random key. + * + * @return string + */ + protected function keyReplacementPattern($key, $value) + { + $escaped = preg_quote("={$value}", '/'); + return "/^{$key}{$escaped}/m"; + } +} diff --git a/config.prod/src/database/seeds/PowerDNSSeeder.php b/config.prod/src/database/seeds/PowerDNSSeeder.php new file mode 100644 index 00000000..6cd5f338 --- /dev/null +++ b/config.prod/src/database/seeds/PowerDNSSeeder.php @@ -0,0 +1,23 @@ + '_woat.' . \config('app.domain') + ] + ); + } +} diff --git a/src/database/seeds/production/SkuSeeder.php b/config.prod/src/database/seeds/SkuSeeder.php similarity index 93% rename from src/database/seeds/production/SkuSeeder.php rename to config.prod/src/database/seeds/SkuSeeder.php index c18b77aa..cceae31d 100644 --- a/src/database/seeds/production/SkuSeeder.php +++ b/config.prod/src/database/seeds/SkuSeeder.php @@ -1,172 +1,172 @@ 'mailbox', 'name' => 'User Mailbox', 'description' => 'Just a mailbox', - 'cost' => 444, + 'cost' => 500, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Mailbox', 'active' => true, ], [ 'title' => 'domain', 'name' => 'Hosted Domain', 'description' => 'Somewhere to place a mailbox', 'cost' => 100, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Domain', 'active' => false, ], [ 'title' => 'domain-registration', 'name' => 'Domain Registration', 'description' => 'Register a domain with us', 'cost' => 101, 'period' => 'yearly', 'handler_class' => 'App\Handlers\DomainRegistration', 'active' => false, ], [ 'title' => 'domain-hosting', 'name' => 'External Domain', 'description' => 'Host a domain that is externally registered', 'cost' => 100, 'units_free' => 1, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainHosting', 'active' => true, ], [ 'title' => 'domain-relay', 'name' => 'Domain Relay', 'description' => 'A domain you host at home, for which we relay email', 'cost' => 103, 'period' => 'monthly', 'handler_class' => 'App\Handlers\DomainRelay', 'active' => false, ], [ 'title' => 'storage', 'name' => 'Storage Quota', 'description' => 'Some wiggle room', - 'cost' => 50, - 'units_free' => 2, + 'cost' => 25, + 'units_free' => 5, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Storage', 'active' => true, ], [ 'title' => 'groupware', 'name' => 'Groupware Features', 'description' => 'Groupware functions like Calendar, Tasks, Notes, etc.', - 'cost' => 555, + 'cost' => 490, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Groupware', 'active' => true, ], [ 'title' => 'resource', 'name' => 'Resource', 'description' => 'Reservation taker', - 'cost' => 0, + 'cost' => 101, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Resource', 'active' => true, ], [ 'title' => 'shared-folder', 'name' => 'Shared Folder', 'description' => 'A shared folder', 'cost' => 89, 'period' => 'monthly', 'handler_class' => 'App\Handlers\SharedFolder', - 'active' => false, + 'active' => true, ], [ 'title' => '2fa', 'name' => '2-Factor Authentication', 'description' => 'Two factor authentication for webmail and administration panel', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Auth2F', 'active' => true, ], [ 'title' => 'activesync', 'name' => 'Activesync', 'description' => 'Mobile synchronization', - 'cost' => 100, + 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Activesync', 'active' => true, ], [ 'title' => 'beta', 'name' => 'Private Beta (invitation only)', - 'description' => 'Access to the private beta program subscriptions', + 'description' => 'Access to the private beta program features', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Beta', 'active' => false, ], [ 'title' => 'group', 'name' => 'Distribution list', 'description' => 'Mail distribution list', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Group', 'active' => true, ], [ 'title' => 'group-room', 'name' => 'Group conference room', 'description' => 'Shareable audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\GroupRoom', 'active' => true, ], [ - 'title' => 'room', + 'title' => 'room', 'name' => 'Standard conference room', 'description' => 'Audio & video conference room', 'cost' => 0, 'units_free' => 0, 'period' => 'monthly', 'handler_class' => 'App\Handlers\Room', 'active' => true, ], ]; foreach ($skus as $sku) { // Check existence because migration might have added this already - if (!Sku::where('title', $sku['title'])->first()) { + if (!Sku::where('title', $sku['title'])->where('tenant_id', \config('app.tenant_id'))->first()) { Sku::create($sku); } } } } diff --git a/docker-compose.local.yml b/docker-compose.local.yml deleted file mode 100644 index 528a53a4..00000000 --- a/docker-compose.local.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: '3' -services: - kolab: - ports: - - "389:389" - - "8880:8880" - - "8443:8443" - - "10143:10143" - - "10587:10587" - - "11143:11143" - - "11993:11993" - - "12143:12143" - mariadb: - ports: - - "3306:3306" - redis: - ports: - - "6379:6379" - proxy: - extra_hosts: - - "webapp:127.0.0.1" diff --git a/docker-compose.yml b/docker-compose.yml index f1151661..07dbe8d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,277 +1,240 @@ version: '3' services: coturn: build: context: ./docker/coturn/ container_name: kolab-coturn healthcheck: interval: 10s test: "kill -0 $$(cat /tmp/turnserver.pid)" timeout: 5s retries: 30 environment: - TURN_PUBLIC_IP=${COTURN_PUBLIC_IP} - TURN_LISTEN_PORT=3478 - TURN_STATIC_SECRET=${COTURN_STATIC_SECRET} hostname: sturn.mgmt.com image: kolab-coturn network_mode: host restart: on-failure tty: true kolab: build: context: ./docker/kolab/ container_name: kolab privileged: true depends_on: mariadb: condition: service_healthy pdns: condition: service_healthy extra_hosts: - "kolab.mgmt.com:127.0.0.1" - "services.${APP_DOMAIN}:172.18.0.4" environment: - APP_DOMAIN=${APP_DOMAIN} - LDAP_HOST=127.0.0.1 - - LDAP_ADMIN_BIND_DN="cn=Directory Manager" - - LDAP_ADMIN_BIND_PW=Welcome2KolabSystems + - LDAP_ADMIN_BIND_DN=${LDAP_ADMIN_BIND_DN} + - LDAP_ADMIN_BIND_PW=${LDAP_ADMIN_BIND_PW} + - LDAP_SERVICE_BIND_PW=${LDAP_SERVICE_BIND_PW} + - LDAP_HOSTED_BIND_PW=${LDAP_HOSTED_BIND_PW} - DB_HOST=mariadb - - DB_ROOT_PASSWORD=Welcome2KolabSystems + - DB_ROOT_PASSWORD=${DB_PASSWORD} - DB_HKCCP_DATABASE=${DB_DATABASE} - DB_HKCCP_USERNAME=${DB_USERNAME} - DB_HKCCP_PASSWORD=${DB_PASSWORD} - DB_KOLAB_DATABASE=kolab - DB_KOLAB_USERNAME=kolab - - DB_KOLAB_PASSWORD=Welcome2KolabSystems + - DB_KOLAB_PASSWORD=${DB_PASSWORD} - DB_RC_USERNAME=roundcube - - DB_RC_PASSWORD=Welcome2KolabSystems + - DB_RC_PASSWORD=${DB_PASSWORD} - SSL_CERTIFICATE=${KOLAB_SSL_CERTIFICATE:?err} - SSL_CERTIFICATE_FULLCHAIN=${KOLAB_SSL_CERTIFICATE_FULLCHAIN:?err} - SSL_CERTIFICATE_KEY=${KOLAB_SSL_CERTIFICATE_KEY:?err} - IMAP_HOST=127.0.0.1 - IMAP_PORT=11993 + - IMAP_ADMIN_LOGIN=${IMAP_ADMIN_LOGIN} + - IMAP_ADMIN_PASSWORD=${IMAP_ADMIN_PASSWORD} - MAIL_HOST=127.0.0.1 - MAIL_PORT=10587 healthcheck: interval: 10s test: "systemctl is-active kolab-init || exit 1" timeout: 5s retries: 30 start_period: 5m # This makes docker's dns, resolve via pdns for this container. # Please note it does not affect /etc/resolv.conf dns: 172.18.0.11 hostname: kolab.mgmt.com image: kolab networks: kolab: ipv4_address: 172.18.0.5 ports: - "12143:12143" tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./ext/:/src/:ro - /etc/letsencrypt/:/etc/letsencrypt/:ro - ./docker/certs/ca.cert:/etc/pki/tls/certs/ca.cert:ro - ./docker/certs/ca.cert:/etc/pki/ca-trust/source/anchors/ca.cert:ro - ./docker/certs/kolab.hosted.com.cert:${KOLAB_SSL_CERTIFICATE:?err} - ./docker/certs/kolab.hosted.com.chain.pem:${KOLAB_SSL_CERTIFICATE_FULLCHAIN:?err} - ./docker/certs/kolab.hosted.com.key:${KOLAB_SSL_CERTIFICATE_KEY:?err} - ./docker/kolab/utils:/root/utils:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro - imap:/imapdata - ldap:/ldapdata mariadb: container_name: kolab-mariadb environment: - - MARIADB_ROOT_PASSWORD=Welcome2KolabSystems + - MARIADB_ROOT_PASSWORD=${DB_PASSWORD} - TZ="+02:00" - DB_HKCCP_DATABASE=${DB_DATABASE} - DB_HKCCP_USERNAME=${DB_USERNAME} - DB_HKCCP_PASSWORD=${DB_PASSWORD} healthcheck: interval: 10s test: test -e /var/run/mysqld/mysqld.sock timeout: 5s retries: 30 image: mariadb:latest networks: - kolab volumes: - ./docker/mariadb/mysql-init/:/docker-entrypoint-initdb.d/ - mariadb:/var/lib/mysql haproxy: - depends_on: - proxy: - condition: service_healthy build: context: ./docker/haproxy/ healthcheck: interval: 10s test: "kill -0 $$(cat /var/run/haproxy.pid)" timeout: 5s retries: 30 container_name: kolab-haproxy hostname: haproxy.hosted.com image: kolab-haproxy networks: - kolab tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./docker/certs/:/etc/certs/:ro - /etc/letsencrypt/:/etc/letsencrypt/:ro pdns: build: context: ./docker/pdns/ + args: + DB_HOST: ${DB_HOST:?err} + DB_DATABASE: ${DB_DATABASE:?err} + DB_USERNAME: ${DB_USERNAME:?err} + DB_PASSWORD: ${DB_PASSWORD:?err} container_name: kolab-pdns hostname: pdns depends_on: mariadb: condition: service_healthy healthcheck: interval: 10s test: "systemctl status pdns || exit 1" timeout: 5s retries: 30 image: kolab-pdns networks: kolab: ipv4_address: 172.18.0.11 tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - /sys/fs/cgroup:/sys/fs/cgroup:ro - proxy: - depends_on: - kolab: - condition: service_healthy - webapp: - condition: service_healthy - build: - context: ./docker/proxy/ - args: - APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err} - SSL_CERTIFICATE: ${PROXY_SSL_CERTIFICATE:?err} - SSL_CERTIFICATE_KEY: ${PROXY_SSL_CERTIFICATE_KEY:?err} - healthcheck: - interval: 10s - test: "kill -0 $$(cat /run/nginx.pid)" - timeout: 5s - retries: 30 - container_name: kolab-proxy - hostname: proxy - image: kolab-proxy - extra_hosts: - - "meet:${MEET_LISTENING_HOST}" - networks: - kolab: - ipv4_address: 172.18.0.7 - tmpfs: - - /run - - /tmp - - /var/run - - /var/tmp - tty: true - volumes: - - ./docker/certs/:/etc/certs/:ro - - /etc/letsencrypt/:/etc/letsencrypt/:ro - ports: - # - "80:80" - - "443:443" - - "465:465" - - "587:587" - - "143:143" - - "993:993" redis: build: context: ./docker/redis/ healthcheck: interval: 10s test: "redis-cli ping || exit 1" timeout: 5s retries: 30 container_name: kolab-redis hostname: redis image: redis networks: - kolab volumes: - ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro - # ports: - # - "6379:6379" webapp: build: context: ./docker/webapp/ container_name: kolab-webapp image: kolab-webapp healthcheck: interval: 10s test: "/src/kolabsrc/artisan octane:status || exit 1" timeout: 5s retries: 30 start_period: 5m depends_on: kolab: condition: service_healthy redis: condition: service_healthy networks: kolab: ipv4_address: 172.18.0.4 volumes: - ./src:/src/kolabsrc.orig:ro ports: - "8000:8000" meet: build: context: ./docker/meet/ healthcheck: interval: 10s test: "curl --insecure -H 'X-AUTH-TOKEN: ${MEET_SERVER_TOKEN}' --fail https://${MEET_LISTENING_HOST}:12443/meetmedia/api/health || exit 1" timeout: 5s retries: 30 start_period: 5m environment: - WEBRTC_LISTEN_IP=${MEET_WEBRTC_LISTEN_IP:?err} - PUBLIC_DOMAIN=${MEET_PUBLIC_DOMAIN:?err} - LISTENING_HOST=${MEET_LISTENING_HOST:?err} - LISTENING_PORT=12443 - TURN_SERVER=${MEET_TURN_SERVER} - TURN_STATIC_SECRET=${COTURN_STATIC_SECRET} - AUTH_TOKEN=${MEET_SERVER_TOKEN:?err} - WEBHOOK_TOKEN=${MEET_WEBHOOK_TOKEN:?err} - WEBHOOK_URL=${APP_PUBLIC_URL:?err}/api/webhooks/meet - SSL_CERT=/etc/pki/tls/certs/meet.${APP_WEBSITE_DOMAIN:?err}.cert - SSL_KEY=/etc/pki/tls/private/meet.${APP_WEBSITE_DOMAIN:?err}.key network_mode: host container_name: kolab-meet image: kolab-meet volumes: - ./meet/server:/src/meet/:ro - ./docker/certs/meet.${APP_WEBSITE_DOMAIN}.cert:/etc/pki/tls/certs/meet.${APP_WEBSITE_DOMAIN}.cert - ./docker/certs/meet.${APP_WEBSITE_DOMAIN}.key:/etc/pki/tls/private/meet.${APP_WEBSITE_DOMAIN}.key networks: kolab: driver: bridge ipam: config: - subnet: "172.18.0.0/24" volumes: mariadb: imap: ldap: diff --git a/docker/kolab/kolab.conf b/docker/kolab/kolab.conf index b320e19e..4a8ff5b5 100644 --- a/docker/kolab/kolab.conf +++ b/docker/kolab/kolab.conf @@ -1,83 +1,83 @@ [kolab] primary_domain = mgmt.com auth_mechanism = ldap imap_backend = cyrus-imap default_locale = en_US sync_interval = 300 domain_sync_interval = 600 policy_uid = %(surname)s.lower() daemon_rcpt_policy = False [imap] virtual_domains = userid [ldap] ldap_uri = ldap://127.0.0.1:389 timeout = 10 supported_controls = 0,2,3 base_dn = dc=mgmt,dc=com bind_dn = cn=Directory Manager -bind_pw = Welcome2KolabSystems +bind_pw = service_bind_dn = uid=kolab-service,ou=Special Users,dc=mgmt,dc=com -service_bind_pw = Welcome2KolabSystems +service_bind_pw = user_base_dn = dc=hosted,dc=com user_scope = sub user_filter = (objectclass=inetorgperson) kolab_user_base_dn = dc=hosted,dc=com kolab_user_filter = (objectclass=kolabinetorgperson) group_base_dn = dc=hosted,dc=com group_filter = (|(objectclass=groupofuniquenames)(objectclass=groupofurls)) group_scope = sub kolab_group_filter = (|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgroupofurls)) sharedfolder_base_dn = dc=hosted,dc=com sharedfolder_filter = (objectclass=kolabsharedfolder) sharedfolder_acl_entry_attribute = acl resource_base_dn = dc=hosted,dc=com resource_filter = (|%(group_filter)s(objectclass=kolabsharedfolder)) domain_base_dn = ou=Domains,dc=mgmt,dc=com domain_filter = (&(associatedDomain=*)) domain_name_attribute = associateddomain domain_rootdn_attribute = inetdomainbasedn quota_attribute = mailquota modifytimestamp_format = %Y%m%d%H%M%SZ unique_attribute = nsuniqueid mail_attributes = mail, alias mailserver_attribute = mailhost auth_attributes = mail, uid [kolab_smtp_access_policy] -cache_uri = mysql://kolab:Welcome2KolabSystems@mariadb/kolab +cache_uri = mysql://kolab:@mariadb/kolab cache_retention = 86400 address_search_attrs = mail, alias delegate_sender_header = True alias_sender_header = True sender_header = True xsender_header = True empty_sender_hosts = 3.2.1.0/24, 6.6.6.0/24 [kolab_wap] mgmt_root_dn = dc=mgmt,dc=com hosted_root_dn = dc=hosted,dc=com api_url = http://127.0.0.1:9080/kolab-webadmin/api skin = default -sql_uri = mysql://kolab:Welcome2KolabSystems@mariadb/kolab +sql_uri = mysql://kolab:@mariadb/kolab ssl_verify_peer = false ssl_verify_host = false [cyrus-imap] uri = imaps://127.0.0.1:11993 admin_login = cyrus-admin -admin_password = Welcome2KolabSystems +admin_password = [cyrus-sasl] result_attribute = mail [wallace] webmail_url = https://%(domain)s/roundcubemail modules = resources, invitationpolicy kolab_invitation_policy = ACT_ACCEPT_IF_NO_CONFLICT:example.org, ACT_MANUAL invitationpolicy_autoupdate_other_attendees_on_reply = false resource_calendar_expire_days = 100 [mgmt.com] default_quota = 1048576 daemon_rcpt_policy = False diff --git a/docker/kolab/utils/settings.sh b/docker/kolab/utils/settings.sh index fbf6f045..142d7aa6 100755 --- a/docker/kolab/utils/settings.sh +++ b/docker/kolab/utils/settings.sh @@ -1,24 +1,22 @@ #!/bin/bash export rootdn=${LDAP_ADMIN_ROOT_DN:-"dc=mgmt,dc=com"} export domain=${DOMAIN:-"mgmt.com"} export domain_db=${DOMAIN_DB:-"mgmt_com"} export ldap_host=${LDAP_HOST} -export ldap_binddn=${LDAP_ADMIN_BIND_DN:-"cn=Directory Manager"} -export ldap_bindpw=${LDAP_ADMIN_BIND_PW:-"Welcome2KolabSystems"} +export ldap_binddn=${LDAP_ADMIN_BIND_DN} +export ldap_bindpw=${LDAP_ADMIN_BIND_PW} -export cyrus_admin=${IMAP_ADMIN_LOGIN:-"cyrus-admin"} +export cyrus_admin=${IMAP_ADMIN_LOGIN} export imap_host=${IMAP_HOST} -export cyrus_admin_pw=${IMAP_ADMIN_PASSWORD:-"Welcome2KolabSystems"} +export cyrus_admin_pw=${IMAP_ADMIN_PASSWORD} -export kolab_service_pw=${LDAP_SERVICE_BIND_PW:-"Welcome2KolabSystems"} -export hosted_kolab_service_pw=${LDAP_HOSTED_BIND_PW:-"Welcome2KolabSystems"} +export kolab_service_pw=${LDAP_SERVICE_BIND_PW} +export hosted_kolab_service_pw=${LDAP_HOSTED_BIND_PW} export hosted_domain=${HOSTED_DOMAIN:-"hosted.com"} export hosted_domain_db=${HOSTED_DOMAIN_DB:-"hosted_com"} export hosted_domain_rootdn=${LDAP_HOSTED_ROOT_DN:-"dc=hosted,dc=com"} export domain_base_dn=${LDAP_DOMAIN_BASE_DN:-"ou=Domains,dc=mgmt,dc=com"} - -export default_user_password=${DEFAULT_USER_PASSWORD:-"Welcome2KolabSystems"} diff --git a/docker/pdns/Dockerfile b/docker/pdns/Dockerfile index a75296c6..731ed9c3 100644 --- a/docker/pdns/Dockerfile +++ b/docker/pdns/Dockerfile @@ -1,37 +1,48 @@ FROM fedora:35 ENV container docker ENV SYSTEMD_PAGER='' RUN dnf -y install \ --setopt 'tsflags=nodocs' \ bind-utils \ iproute \ iptables \ iputils \ net-tools \ pdns \ pdns-backend-mysql \ pdns-recursor \ pdns-tools \ procps-ng \ vim-enhanced \ wget \ which && \ dnf clean all COPY pdns.conf /etc/pdns/pdns.conf COPY recursor.conf /etc/pdns-recursor/recursor.conf +ARG DB_HOST +ARG DB_DATABASE +ARG DB_USERNAME +ARG DB_PASSWORD +RUN sed -i -r \ + -e "s|DB_HOST|$DB_HOST|g" \ + -e "s|DB_DATABASE|$DB_DATABASE|g" \ + -e "s|DB_USERNAME|$DB_USERNAME|g" \ + -e "s|DB_PASSWORD|$DB_PASSWORD|g" \ + /etc/pdns/pdns.conf + RUN systemctl disable systemd-resolved && systemctl enable pdns && systemctl enable pdns-recursor # This is how we could run pdns without systemd # ENV PDNS_guardian=yes \ # PDNS_setuid=pdns \ # PDNS_setgid=pdns \ # PDNS_launch=gmysql # CMD ["/usr/sbin/pdns_server", "--guardian=no", "--daemon=no", "--disable-syslog", "--log-timestamp=no", "--write-pid=no"] CMD ["/lib/systemd/systemd", "--system"] EXPOSE 53 53/udp diff --git a/docker/pdns/pdns.conf b/docker/pdns/pdns.conf index 07ecf0f0..dabe3665 100644 --- a/docker/pdns/pdns.conf +++ b/docker/pdns/pdns.conf @@ -1,69 +1,69 @@ launch=gmysql log-dns-details query-logging=yes local-address=127.0.0.1:5300 edns-subnet-processing -gmysql-host=mariadb -gmysql-dbname=kolabdev -gmysql-password=kolab -gmysql-user=kolabdev +gmysql-host=DB_HOST +gmysql-dbname=DB_DATABASE +gmysql-password=DB_PASSWORD +gmysql-user=DB_USERNAME gmysql-activate-domain-key-query=UPDATE powerdns_cryptokeys SET active=1 WHERE domain_id=(SELECT id FROM domains WHERE name=?) AND powerdns_cryptokeys.id=? gmysql-add-domain-key-query=INSERT INTO powerdns_cryptokeys (domain_id, flags, active, content) SELECT id, ?, ?, ? FROM powerdns_domains WHERE name=? gmysql-any-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND name=? AND domain_id=? gmysql-any-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND name=? gmysql-basic-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND type=? AND name=? gmysql-clear-domain-all-keys-query=delete FROM powerdns_cryptokeys WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) gmysql-clear-domain-all-metadata-query=delete FROM powerdns_domain_settings WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) gmysql-clear-domain-metadata-query=delete FROM powerdns_domain_settings WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_domain_settings.kind=? gmysql-deactivate-domain-key-query=UPDATE powerdns_cryptokeys SET active=0 WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_cryptokeys.id=? gmysql-delete-comment-rrset-query=DELETE FROM powerdns_comments WHERE domain_id=? AND name=? AND type=? gmysql-delete-comments-query=DELETE FROM powerdns_comments WHERE domain_id=? gmysql-delete-domain-query=delete FROM powerdns_domains WHERE name=? gmysql-delete-empty-non-terminal-query=delete FROM powerdns_records WHERE domain_id=? AND name=? AND type is null gmysql-delete-names-query=delete FROM powerdns_records WHERE domain_id=? AND name=? gmysql-delete-rrset-query=delete FROM powerdns_records WHERE domain_id=? AND name=? AND type=? gmysql-delete-tsig-key-query=delete FROM powerdns_tsigkeys WHERE name=? gmysql-delete-zone-query=delete FROM powerdns_records WHERE domain_id=? gmysql-get-all-domain-metadata-query=SELECT kind,content FROM powerdns_domains, powerdns_domain_settings WHERE powerdns_domain_settings.domain_id=powerdns_domains.id AND name=? gmysql-get-all-domains-query=SELECT powerdns_domains.id, powerdns_domains.name, powerdns_records.content, powerdns_domains.type, powerdns_domains.master, powerdns_domains.notified_serial, powerdns_domains.last_check, powerdns_domains.account FROM powerdns_domains LEFT JOIN powerdns_records ON powerdns_records.domain_id=powerdns_domains.id AND powerdns_records.type='SOA' AND powerdns_records.name=powerdns_domains.name WHERE powerdns_records.disabled=0 OR ? gmysql-get-domain-metadata-query=SELECT content FROM powerdns_domains, powerdns_domain_settings WHERE powerdns_domain_settings.domain_id=powerdns_domains.id AND name=? AND powerdns_domain_settings.kind=? gmysql-get-last-inserted-key-id-query=SELECT LAST_INSERT_ID() gmysql-get-order-after-query=SELECT ordername FROM powerdns_records WHERE ordername > ? AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 ASC LIMIT 1 gmysql-get-order-before-query=SELECT ordername, name FROM powerdns_records WHERE ordername <= ? AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 desc LIMIT 1 gmysql-get-order-first-query=SELECT ordername FROM powerdns_records WHERE domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 ASC LIMIT 1 gmysql-get-order-last-query=SELECT ordername, name FROM powerdns_records WHERE ordername != '' AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 desc LIMIT 1 gmysql-get-tsig-key-query=SELECT algorithm, secret FROM powerdns_tsigkeys WHERE name=? gmysql-get-tsig-keys-query=SELECT name,algorithm, secret FROM powerdns_tsigkeys gmysql-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND type=? AND name=? AND domain_id=? gmysql-info-all-master-query=SELECT d.id, d.name, d.notified_serial, r.content FROM powerdns_records r join powerdns_domains d on r.name=d.name WHERE r.type='SOA' AND r.disabled=0 AND d.type='MASTER' gmysql-info-all-slaves-query=SELECT id,name,master,last_check FROM powerdns_domains WHERE type='SLAVE' gmysql-info-zone-query=SELECT id,name,master,last_check,notified_serial,type,account FROM powerdns_domains WHERE name=? gmysql-insert-comment-query=INSERT INTO powerdns_comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?) gmysql-insert-empty-non-terminal-order-query=INSERT INTO powerdns_records (type,domain_id,disabled,name,ordername,auth,content,ttl,prio) values (null,?,0,?,?,?,NULL,NULL,NULL) gmysql-insert-record-query=INSERT INTO powerdns_records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (?,?,?,?,?,?,?,?,?) gmysql-insert-zone-query=INSERT INTO powerdns_domains (type,name,master,account,last_check,notified_serial) values(?,?,?,?,NULL,NULL) gmysql-list-comments-query=SELECT domain_id,name,type,modified_at,account,comment FROM powerdns_comments WHERE domain_id=? gmysql-list-domain-keys-query=SELECT powerdns_cryptokeys.id, flags, active, content FROM powerdns_domains, powerdns_cryptokeys WHERE powerdns_cryptokeys.domain_id=powerdns_domains.id AND name=? gmysql-list-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE (disabled=0 OR ?) AND domain_id=? ORDER BY name, type gmysql-list-subzone-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND (name=? OR name like ?) AND domain_id=? gmysql-nullify-ordername-and-update-auth-query=UPDATE powerdns_records SET ordername=NULL,auth=? WHERE domain_id=? AND name=? AND disabled=0 gmysql-nullify-ordername-and-update-auth-type-query=UPDATE powerdns_records SET ordername=NULL,auth=? WHERE domain_id=? AND name=? AND type=? AND disabled=0 gmysql-remove-domain-key-query=delete FROM powerdns_cryptokeys WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_cryptokeys.id=? gmysql-remove-empty-non-terminals-from-zone-query=delete FROM powerdns_records WHERE domain_id=? AND type is null gmysql-search-comments-query=SELECT domain_id,name,type,modified_at,account,comment FROM powerdns_comments WHERE name LIKE ? OR comment LIKE ? LIMIT ? gmysql-search-records-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE name LIKE ? OR content LIKE ? LIMIT ? gmysql-set-domain-metadata-query=INSERT INTO powerdns_domain_settings (domain_id, kind, content) SELECT id, ?, ? FROM powerdns_domains WHERE name=? gmysql-set-tsig-key-query=REPLACE INTO powerdns_tsigkeys (name,algorithm,secret) values(?,?,?) gmysql-supermaster-query=SELECT account FROM powerdns_masters WHERE ip=? AND nameserver=? gmysql-update-account-query=UPDATE powerdns_domains SET account=? WHERE name=? gmysql-update-kind-query=UPDATE powerdns_domains SET type=? WHERE name=? gmysql-update-lastcheck-query=UPDATE powerdns_domains SET last_check=? WHERE id=? gmysql-update-master-query=UPDATE powerdns_domains SET master=? WHERE name=? gmysql-update-ordername-and-auth-query=UPDATE powerdns_records SET ordername=?,auth=? WHERE domain_id=? AND name=? AND disabled=0 gmysql-update-ordername-and-auth-type-query=UPDATE powerdns_records SET ordername=?,auth=? WHERE domain_id=? AND name=? AND type=? AND disabled=0 gmysql-update-serial-query=UPDATE powerdns_domains SET notified_serial=? WHERE id=? diff --git a/docker/tests/init.sh b/docker/tests/init.sh index 8d0c083f..9e0cad1e 100755 --- a/docker/tests/init.sh +++ b/docker/tests/init.sh @@ -1,80 +1,81 @@ #!/bin/bash #set -e +rm -rf /src/kolabsrc sudo cp -a /src/kolabsrc.orig /src/kolabsrc sudo chmod 777 -R /src/kolabsrc cd /src/kolabsrc sudo rm -rf vendor/ composer.lock php -dmemory_limit=-1 $(command -v composer) install sudo rm -rf node_modules mkdir node_modules npm install find bootstrap/cache/ -type f ! -name ".gitignore" -delete ./artisan key:generate ./artisan clear-compiled ./artisan cache:clear ./artisan horizon:install if [ ! -f storage/oauth-public.key -o ! -f storage/oauth-private.key ]; then ./artisan passport:keys --force fi cat >> .env << EOF PASSPORT_PRIVATE_KEY="$(cat storage/oauth-private.key)" PASSPORT_PUBLIC_KEY="$(cat storage/oauth-public.key)" EOF if rpm -qv chromium 2>/dev/null; then chver=$(rpmquery --queryformat="%{VERSION}" chromium | awk -F'.' '{print $1}') ./artisan dusk:chrome-driver ${chver} fi if [ ! -f 'resources/countries.php' ]; then ./artisan data:countries fi npm run dev # /usr/bin/chromium-browser --no-sandbox --headless --disable-gpu --remote-debugging-port=9222 http://localhost & rm -rf database/database.sqlite ./artisan db:ping --wait php -dmemory_limit=512M ./artisan migrate:refresh --seed ./artisan data:import || : ./artisan queue:work --stop-when-empty ./artisan octane:start --host=$(grep OCTANE_HTTP_HOST .env | tail -n1 | sed "s/OCTANE_HTTP_HOST=//") >/dev/null 2>&1 & if [ "$1" == "testsuite" ]; then php \ -dmemory_limit=-1 \ vendor/bin/phpunit \ --exclude-group skipci \ --verbose \ --stop-on-defect \ --stop-on-error \ --stop-on-failure \ --testsuite Unit php \ -dmemory_limit=-1 \ vendor/bin/phpunit \ --exclude-group skipci \ --verbose \ --stop-on-defect \ --stop-on-error \ --stop-on-failure \ --testsuite Functional php \ -dmemory_limit=-1 \ vendor/bin/phpunit \ --exclude-group skipci,coinbase,mollie,stripe,meet,dns \ --verbose \ --stop-on-defect \ --stop-on-error \ --stop-on-failure \ --testsuite Feature fi if [ "$1" == "shell" ]; then exec /bin/bash fi diff --git a/docker/webapp/init.sh b/docker/webapp/init.sh index 7c4c879a..5c893297 100755 --- a/docker/webapp/init.sh +++ b/docker/webapp/init.sh @@ -1,39 +1,29 @@ #!/bin/bash set -e rm -rf /src/kolabsrc cp -a /src/kolabsrc.orig /src/kolabsrc cd /src/kolabsrc rm -rf vendor/ composer.lock .npm storage/framework mkdir -p storage/framework/{sessions,views,cache} php -dmemory_limit=-1 $(command -v composer) install npm install find bootstrap/cache/ -type f ! -name ".gitignore" -delete ./artisan key:generate ./artisan storage:link ./artisan clear-compiled ./artisan cache:clear ./artisan horizon:install -if [ ! -f storage/oauth-public.key -o ! -f storage/oauth-private.key ]; then - ./artisan passport:keys --force -fi - -cat >> .env << EOF -PASSPORT_PRIVATE_KEY="$(cat storage/oauth-private.key)" -PASSPORT_PUBLIC_KEY="$(cat storage/oauth-public.key)" -EOF - if [ ! -f 'resources/countries.php' ]; then ./artisan data:countries fi npm run dev -rm -rf database/database.sqlite ./artisan db:ping --wait php -dmemory_limit=512M ./artisan migrate:refresh --seed ./artisan data:import || : nohup ./artisan horizon >/dev/null 2>&1 & ./artisan octane:start --host=$(grep OCTANE_HTTP_HOST .env | tail -n1 | sed "s/OCTANE_HTTP_HOST=//") diff --git a/src/.gitignore b/src/.gitignore index 9be9b340..53f771ba 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,27 +1,29 @@ *.swp database/database.sqlite node_modules/ package-lock.json public/css/*.css public/hot public/js/*.js public/storage/ storage/*.key storage/*.log storage/*-????-??-??* storage/export/ tests/report/ vendor .env .env.backup .env.local .env.testing .phpunit.result.cache Homestead.json Homestead.yaml npm-debug.log yarn-error.log composer.lock resources/countries.php resources/build/js/ +database/seeds/ +src/public/themes/active cache diff --git a/src/.s2i/bin/assemble b/src/.s2i/bin/assemble index 30ca417c..f6248547 100755 --- a/src/.s2i/bin/assemble +++ b/src/.s2i/bin/assemble @@ -1,45 +1,38 @@ #!/bin/bash set -x set -e function composer_install () { echo "--->> Detected composer.json, running install" php -dmemory_limit=${COMPOSER_MEMORY_LIMIT:--1} /usr/bin/composer install ${COMPOSER_ARGS} rm -rf ~/.cache/composer/ } shopt -s dotglob echo "--->> $(rm -vrf vendor/ composer.lock)" echo "---> Installing application source..." rm -fR /tmp/src/.git mv /tmp/src/* ./ pushd /opt/app-root/src fix-permissions ./ -if [ -f ".env.local" ]; then - # Ensure there's a line ending - echo "---->> Append .env.local" - echo "" >> .env - cat .env.local >> .env -fi - if [ -f "composer.json" ]; then echo "--->> Detected composer.json, running install" composer_install fi ./artisan horizon:install if [ ! -z "${OPENEXCHANGERATES_API_KEY}" ]; then ./artisan data:import:open-exchange-rates fi echo "---->> Run npm run prod" npm install npm run ${LARAVEL_ENV:=prod} && rm -rf ~/.npm/ fix-permissions ./ diff --git a/src/database/migrations/2021_04_08_150000_signup_code_headers.php b/src/database/migrations/2021_04_08_150000_signup_code_headers.php index 037b8298..8f857b13 100644 --- a/src/database/migrations/2021_04_08_150000_signup_code_headers.php +++ b/src/database/migrations/2021_04_08_150000_signup_code_headers.php @@ -1,40 +1,39 @@ text('headers')->nullable(); } ); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table( 'signup_codes', function (Blueprint $table) { $table->dropColumn('headers'); } ); } } diff --git a/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php b/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php index 46a6d3bc..32073fc2 100644 --- a/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php +++ b/src/database/migrations/2021_07_12_100000_create_tenant_settings_table.php @@ -1,45 +1,44 @@ bigIncrements('id'); $table->unsignedBigInteger('tenant_id'); $table->string('key'); $table->text('value'); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent(); $table->foreign('tenant_id')->references('id')->on('tenants') ->onDelete('cascade')->onUpdate('cascade'); $table->unique(['tenant_id', 'key']); } ); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('tenant_settings'); } } diff --git a/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php b/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php index ac3ea22d..16544c3b 100644 --- a/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php +++ b/src/database/migrations/2022_05_13_100000_permissions_and_room_subscriptions.php @@ -1,156 +1,121 @@ string('id', 36)->primary(); $table->bigInteger('permissible_id'); $table->string('permissible_type'); $table->integer('rights')->default(0); $table->string('user'); $table->timestamps(); $table->index('user'); $table->index(['permissible_id', 'permissible_type']); } ); Schema::table( 'openvidu_rooms', function (Blueprint $table) { $table->bigInteger('tenant_id')->unsigned()->nullable(); $table->string('description')->nullable(); $table->softDeletes(); $table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('set null'); } ); // Create the new SKUs - if (!\App\Sku::where('title', 'room')->first()) { - $sku = \App\Sku::create([ - 'title' => 'group-room', - 'name' => 'Group conference room', - 'description' => 'Shareable audio & video conference room', - 'cost' => 0, - 'units_free' => 0, - 'period' => 'monthly', - 'handler_class' => 'App\Handlers\GroupRoom', - 'active' => true, - ]); + $sku = \App\Sku::where('title', 'room')->first(); - $sku = \App\Sku::create([ - 'title' => 'room', - 'name' => 'Standard conference room', - 'description' => 'Audio & video conference room', - 'cost' => 0, - 'units_free' => 0, - 'period' => 'monthly', - 'handler_class' => 'App\Handlers\Room', - 'active' => true, - ]); + // Create the entitlement for every existing room + foreach (\App\Meet\Room::get() as $room) { + $user = \App\User::find($room->user_id); // @phpstan-ignore-line + if (!$user) { + $room->forceDelete(); + continue; + } - // Create the entitlement for every existing room - foreach (\App\Meet\Room::get() as $room) { - $user = \App\User::find($room->user_id); // @phpstan-ignore-line - if (!$user) { - $room->forceDelete(); - continue; - } - - // Set tenant_id - if ($user->tenant_id) { - $room->tenant_id = $user->tenant_id; - $room->save(); - } - - $wallet = $user->wallets()->first(); - - \App\Entitlement::create([ - 'wallet_id' => $wallet->id, - 'sku_id' => $sku->id, - 'cost' => 0, - 'fee' => 0, - 'entitleable_id' => $room->id, - 'entitleable_type' => \App\Meet\Room::class - ]); + // Set tenant_id + if ($user->tenant_id) { + $room->tenant_id = $user->tenant_id; + $room->save(); } + + $wallet = $user->wallets()->first(); + + \App\Entitlement::create([ + 'wallet_id' => $wallet->id, + 'sku_id' => $sku->id, + 'cost' => 0, + 'fee' => 0, + 'entitleable_id' => $room->id, + 'entitleable_type' => \App\Meet\Room::class + ]); } // Remove 'meet' SKU/entitlements \App\Sku::where('title', 'meet')->delete(); Schema::table( 'openvidu_rooms', function (Blueprint $table) { $table->dropForeign('openvidu_rooms_user_id_foreign'); $table->dropColumn('user_id'); } ); } /** * Reverse the migrations. */ public function down(): void { Schema::table( 'openvidu_rooms', function (Blueprint $table) { $table->dropForeign('openvidu_rooms_tenant_id_foreign'); $table->dropColumn('tenant_id'); $table->dropColumn('description'); $table->bigInteger('user_id')->nullable(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); } ); // Set user_id back foreach (\App\Meet\Room::get() as $room) { $wallet = $room->wallet(); if (!$wallet) { $room->forceDelete(); continue; } $room->user_id = $wallet->user_id; // @phpstan-ignore-line $room->save(); } Schema::table( 'openvidu_rooms', function (Blueprint $table) { $table->dropSoftDeletes(); } ); \App\Entitlement::where('entitleable_type', \App\Meet\Room::class)->forceDelete(); - \App\Sku::where('title', 'room')->delete(); - \App\Sku::where('title', 'group-room')->delete(); - - \App\Sku::create([ - 'title' => 'meet', - 'name' => 'Voice & Video Conferencing (public beta)', - 'description' => 'Video conferencing tool', - 'cost' => 0, - 'units_free' => 0, - 'period' => 'monthly', - 'handler_class' => 'App\Handlers\Meet', - 'active' => true, - ]); Schema::dropIfExists('permissions'); } }; diff --git a/src/database/migrations/2022_09_08_100000_plans_free_months.php b/src/database/migrations/2022_09_08_100000_plans_free_months.php index 06ef6881..846a5a09 100644 --- a/src/database/migrations/2022_09_08_100000_plans_free_months.php +++ b/src/database/migrations/2022_09_08_100000_plans_free_months.php @@ -1,41 +1,38 @@ tinyInteger('free_months')->unsigned()->default(0); } ); - - DB::table('plans')->update(['free_months' => 1]); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table( 'plans', function (Blueprint $table) { $table->dropColumn('free_months'); } ); } }; diff --git a/src/database/seeds/DatabaseSeeder.php b/src/database/seeds/DatabaseSeeder.php deleted file mode 100644 index eaec2738..00000000 --- a/src/database/seeds/DatabaseSeeder.php +++ /dev/null @@ -1,44 +0,0 @@ - $name) { - $class = "Database\\Seeds\\$env\\$name"; - $seeders[$idx] = class_exists($class) ? $class : null; - } - - $seeders = array_filter($seeders); - - $this->call($seeders); - } -} diff --git a/src/database/seeds/production/DiscountSeeder.php b/src/database/seeds/production/DiscountSeeder.php deleted file mode 100644 index d5b83872..00000000 --- a/src/database/seeds/production/DiscountSeeder.php +++ /dev/null @@ -1,33 +0,0 @@ - 'Free Account', - 'discount' => 100, - 'active' => true, - ] - ); - - Discount::create( - [ - 'description' => 'Student or Educational Institution', - 'discount' => 30, - 'active' => true, - ] - ); - } -} diff --git a/src/database/seeds/production/DomainSeeder.php b/src/database/seeds/production/DomainSeeder.php deleted file mode 100644 index 59f8ee5e..00000000 --- a/src/database/seeds/production/DomainSeeder.php +++ /dev/null @@ -1,52 +0,0 @@ - $domain, - 'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE, - 'type' => Domain::TYPE_PUBLIC - ] - ); - } - } -} diff --git a/src/database/seeds/production/PackageSeeder.php b/src/database/seeds/production/PackageSeeder.php deleted file mode 100644 index 2500aa23..00000000 --- a/src/database/seeds/production/PackageSeeder.php +++ /dev/null @@ -1,86 +0,0 @@ - 'activesync']); - $skuGroupware = Sku::firstOrCreate(['title' => 'groupware']); - $skuMailbox = Sku::firstOrCreate(['title' => 'mailbox']); - $skuStorage = Sku::firstOrCreate(['title' => 'storage']); - - $package = Package::create( - [ - 'title' => 'kolab', - 'name' => 'Groupware Account', - 'description' => 'A fully functional groupware account.', - 'discount_rate' => 0 - ] - ); - - $skus = [ - $skuMailbox, - $skuGroupware, - $skuStorage, - $skuActiveSync - ]; - - $package->skus()->saveMany($skus); - - // This package contains 2 units of the storage SKU, which just so happens to also - // be the number of SKU free units. - $package->skus()->updateExistingPivot( - $skuStorage, - ['qty' => 2], - false - ); - - $package = Package::create( - [ - 'title' => 'lite', - 'name' => 'Lite Account', - 'description' => 'Just mail and no more.', - 'discount_rate' => 0 - ] - ); - - $skus = [ - $skuMailbox, - $skuStorage - ]; - - $package->skus()->saveMany($skus); - - $package->skus()->updateExistingPivot( - Sku::firstOrCreate(['title' => 'storage']), - ['qty' => 2], - false - ); - - $package = Package::create( - [ - 'title' => 'domain-hosting', - 'name' => 'Domain Hosting', - 'description' => 'Use your own, existing domain.', - 'discount_rate' => 0 - ] - ); - - $skus = [ - Sku::firstOrCreate(['title' => 'domain-hosting']) - ]; - - $package->skus()->saveMany($skus); - } -} diff --git a/src/database/seeds/production/PlanSeeder.php b/src/database/seeds/production/PlanSeeder.php deleted file mode 100644 index 35c35ef8..00000000 --- a/src/database/seeds/production/PlanSeeder.php +++ /dev/null @@ -1,76 +0,0 @@ -Everything you need to get started or try Kolab Now, including:

-
    -
  • Perfect for anyone wanting to move to Kolab Now
  • -
  • Suite of online apps: Secure email, calendar, address book, files and more
  • -
  • Access for anywhere: Sync all your devices to your Kolab Now account
  • -
  • Secure hosting: Managed right here on our own servers in Switzerland
  • -
  • Start protecting your data today, no ads, no crawling, no compromise
  • -
  • An ideal replacement for services like Gmail, Office 365, etc…
  • -
-EOD; - - $plan = Plan::create( - [ - 'title' => 'individual', - 'name' => 'Individual Account', - 'description' => $description, - 'free_months' => 1, - 'discount_qty' => 0, - 'discount_rate' => 0 - ] - ); - - $packages = [ - Package::firstOrCreate(['title' => 'kolab']) - ]; - - $plan->packages()->saveMany($packages); - - $description = <<<'EOD' -

All the features of the Individual Account, with the following extras:

-
    -
  • Perfect for anyone wanting to move a group or small business to Kolab Now
  • -
  • Recommended to support users from 1 to 100
  • -
  • Use your own personal domains with Kolab Now
  • -
  • Manage and add users through our online admin area
  • -
  • Flexible pricing based on user count
  • -
-EOD; - - $plan = Plan::create( - [ - 'title' => 'group', - 'name' => 'Group Account', - 'description' => $description, - 'free_months' => 1, - 'discount_qty' => 0, - 'discount_rate' => 0 - ] - ); - - $packages = [ - Package::firstOrCreate(['title' => 'domain-hosting']), - Package::firstOrCreate(['title' => 'kolab']), - ]; - - $plan->packages()->saveMany($packages); - } -}