#!/bin/bash set -eu -o pipefail # This script is run after the box code is switched. This means that this script # should pretty much always succeed. No network logic/download code here. function log() { echo -e "$(date +'%Y-%m-%dT%H:%M:%S')" "==> start: $1" } log "Cloudron Start" readonly USER="yellowtent" readonly HOME_DIR="/home/${USER}" readonly BOX_SRC_DIR="${HOME_DIR}/box" readonly PLATFORM_DATA_DIR="${HOME_DIR}/platformdata" readonly APPS_DATA_DIR="${HOME_DIR}/appsdata" readonly BOX_DATA_DIR="${HOME_DIR}/boxdata/box" readonly MAIL_DATA_DIR="${HOME_DIR}/boxdata/mail" readonly script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly ubuntu_version=$(lsb_release -rs) vergte() { greater_version=$(echo -e "$1\n$2" | sort -rV | head -n1) [[ "$1" == "${greater_version}" ]] && return 0 || return 1 } cp -f "${script_dir}/../scripts/cloudron-support" /usr/bin/cloudron-support cp -f "${script_dir}/../scripts/cloudron-translation-update" /usr/bin/cloudron-translation-update rm -f /usr/bin/cloudron-logs # legacy script # this needs to match the cloudron/base:4.2.0 gid if ! getent group media; then addgroup --gid 500 --system media fi log "Configuring docker" cp "${script_dir}/start/docker-cloudron-app.apparmor" /etc/apparmor.d/docker-cloudron-app systemctl enable apparmor systemctl restart apparmor usermod ${USER} -a -G docker if ! grep -q ip6tables /etc/systemd/system/docker.service.d/cloudron.conf; then log "Adding ip6tables flag to docker" # https://github.com/moby/moby/pull/41622 echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=overlay2 --experimental --ip6tables --userland-proxy=false" > /etc/systemd/system/docker.service.d/cloudron.conf systemctl daemon-reload systemctl restart docker fi if ! grep -q userland-proxy /etc/systemd/system/docker.service.d/cloudron.conf; then log "Adding userland-proxy=false to docker" # https://github.com/moby/moby/pull/41622 echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=overlay2 --experimental --ip6tables --userland-proxy=false" > /etc/systemd/system/docker.service.d/cloudron.conf systemctl daemon-reload systemctl restart docker fi mkdir -p "${BOX_DATA_DIR}" "${APPS_DATA_DIR}" "${MAIL_DATA_DIR}" # keep these in sync with paths.js log "Ensuring directories" mkdir -p "${PLATFORM_DATA_DIR}/"{graphite,mysql,postgresql,mongodb,redis,tls,logrotate.d,acme,backup,update,firewall,sshfs,cifs,oidc,diskusage} mkdir -p "${PLATFORM_DATA_DIR}/addons/mail/"{banner,dkim} mkdir -p "${PLATFORM_DATA_DIR}/logs/"{backup,updater,tasks} mkdir -p "${PLATFORM_DATA_DIR}/sftp/ssh" # sftp keys # ensure backups folder exists and is writeable mkdir -p /var/backups chmod 777 /var/backups log "Configuring journald" sed -e "s/^#SystemMaxUse=.*$/SystemMaxUse=100M/" \ -e "s/^#ForwardToSyslog=.*$/ForwardToSyslog=no/" \ -i /etc/systemd/journald.conf # When rotating logs, systemd kills journald too soon sometimes # See https://github.com/systemd/systemd/issues/1353 (this is upstream default) sed -e "s/^WatchdogSec=.*$/WatchdogSec=3min/" \ -i /lib/systemd/system/systemd-journald.service usermod -a -G systemd-journal ${USER} # Give user access to system logs if [[ ! -d /var/log/journal ]]; then # in some images, this directory is not created making system log to /run/systemd instead mkdir -p /var/log/journal chown root:systemd-journal /var/log/journal chmod g+s /var/log/journal # sticky bit for group propagation fi systemctl daemon-reload systemctl restart systemd-journald # Give user access to nginx logs (uses adm group) usermod -a -G adm ${USER} log "Setting up unbound" rm -f /etc/unbound/unbound.conf.d/prefer-ip4.conf # old config file cp -f "${script_dir}/start/unbound/unbound.conf" /etc/unbound/unbound.conf.d/cloudron-network.conf if [[ "${ubuntu_version}" == "20.04" || "${ubuntu_version}" == "22.04" ]]; then # on older ubuntu, prefer-ip4 option does not exist. do-ip6 has to be disabled because SpamHaus rejects IPv6 queries # this means we cannot support IPv6 only servers on older ubuntu sed -e 's/do-ip6: yes/do-ip6: no/' -e 's/prefer-ip4:/# prefer-ip4:/' -i /etc/unbound/unbound.conf.d/cloudron-network.conf fi rm -f /etc/unbound/unbound.conf.d/remote-control.conf # on ubuntu 24 log "Adding systemd services" cp -r "${script_dir}/start/systemd/." /etc/systemd/system/ systemctl daemon-reload systemctl enable unbound systemctl enable box systemctl enable cloudron-firewall systemctl enable --now cloudron-disable-thp # update firewall rules. this must be done after docker created it's rules systemctl restart cloudron-firewall # For logrotate systemctl enable --now cron # ensure unbound runs systemctl restart unbound # nfs-common depends on rpcbind which is only needed for NFS v2/v3 . can be removed after 8.0.1 . systemctl list-sockets | grep 111 systemctl disable rpcbind.socket rpcbind.service || true systemctl stop rpcbind.socket rpcbind.service || true log "Configuring sudoers" rm -f /etc/sudoers.d/${USER} /etc/sudoers.d/cloudron cp "${script_dir}/start/sudoers" /etc/sudoers.d/cloudron # can be removed after 9.0 log "Unconfiguring collectd" rm -rf "${PLATFORM_DATA_DIR}/collectd" systemctl disable collectd || true systemctl stop collectd || true log "Configuring logrotate" if ! grep -q "^include ${PLATFORM_DATA_DIR}/logrotate.d" /etc/logrotate.conf; then echo -e "\ninclude ${PLATFORM_DATA_DIR}/logrotate.d\n" >> /etc/logrotate.conf fi cp "${script_dir}/start/logrotate/"* "${PLATFORM_DATA_DIR}/logrotate.d/" # logrotate files have to be owned by root, this is here to fixup existing installations where we were resetting the owner to yellowtent chown root:root "${PLATFORM_DATA_DIR}/logrotate.d/" log "Adding motd message for admins" cp "${script_dir}/start/cloudron-motd" /etc/update-motd.d/92-cloudron log "Configuring nginx" # link nginx config to system config unlink /etc/nginx 2>/dev/null || rm -rf /etc/nginx ln -s "${PLATFORM_DATA_DIR}/nginx" /etc/nginx mkdir -p "${PLATFORM_DATA_DIR}/nginx/applications/dashboard" mkdir -p "${PLATFORM_DATA_DIR}/nginx/cert" cp "${script_dir}/start/nginx/nginx.conf" "${PLATFORM_DATA_DIR}/nginx/nginx.conf" cp "${script_dir}/start/nginx/mime.types" "${PLATFORM_DATA_DIR}/nginx/mime.types" touch "${PLATFORM_DATA_DIR}/nginx/trusted.ips" if ! grep -q "^Restart=" /etc/systemd/system/multi-user.target.wants/nginx.service; then # default nginx service file does not restart on crash echo -e "\n[Service]\nRestart=always\n" >> /etc/systemd/system/multi-user.target.wants/nginx.service fi # worker_rlimit_nofile in nginx config can be max this number mkdir -p /etc/systemd/system/nginx.service.d if ! grep -q "^LimitNOFILE=" /etc/systemd/system/nginx.service.d/cloudron.conf 2>/dev/null; then echo -e "[Service]\nLimitNOFILE=16384\n" > /etc/systemd/system/nginx.service.d/cloudron.conf fi systemctl daemon-reload systemctl start nginx # restart mysql to make sure it has latest config if [[ ! -f /etc/mysql/mysql.cnf ]] || ! diff -q "${script_dir}/start/mysql.cnf" /etc/mysql/mysql.cnf >/dev/null; then # wait for all running mysql jobs cp "${script_dir}/start/mysql.cnf" /etc/mysql/mysql.cnf while true; do if ! systemctl list-jobs | grep mysql; then break; fi log "Waiting for mysql jobs..." sleep 1 done log "Stopping mysql" systemctl stop mysql while mysqladmin ping 2>/dev/null; do log "Waiting for mysql to stop..." sleep 1 done fi # the start/stop of mysql is separate to make sure it got reloaded with latest config and it's up and running before we start the new box code # when using 'system restart mysql', it seems to restart much later and the box code loses connection during platform startup (dangerous!) log "Starting mysql" systemctl start mysql while ! mysqladmin ping 2>/dev/null; do log "Waiting for mysql to start..." sleep 1 done readonly mysql_root_password="password" mysqladmin -u root -ppassword password password # reset default root password readonly mysqlVersion=$(mysql -NB -u root -p${mysql_root_password} -e 'SELECT VERSION()' 2>/dev/null) if [[ "${mysqlVersion}" == "8.0."* ]]; then # mysql 8 added a new caching_sha2_password scheme which mysqljs does not support mysql -u root -p${mysql_root_password} -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '${mysql_root_password}';" fi mysql -u root -p${mysql_root_password} -e 'CREATE DATABASE IF NOT EXISTS box' # set HOME explicity, because it's not set when the installer calls it. this is done because # paths.js uses this env var and some of the migrate code requires box code log "Migrating data" cd "${BOX_SRC_DIR}" if ! HOME=${HOME_DIR} BOX_ENV=cloudron DATABASE_URL=mysql://root:${mysql_root_password}@127.0.0.1/box "${BOX_SRC_DIR}/node_modules/.bin/db-migrate" up; then log "DB migration failed" exit 1 fi # migrate disk usage cache file [[ -f "${PLATFORM_DATA_DIR}/diskusage.json" ]] && mv "${PLATFORM_DATA_DIR}/diskusage.json" "${PLATFORM_DATA_DIR}/diskusage/cache.json" # this file is obsolete now, moved to apps database rm -f "${PLATFORM_DATA_DIR}/update/updatechecker.json" log "Changing ownership" # note, change ownership after db migrate. this allow db migrate to move files around as root and then we can fix it up here # be careful of what is chown'ed here. subdirs like mysql,redis etc are owned by the containers and will stop working if perms change chown -R "${USER}" /etc/cloudron chown "${USER}:${USER}" -R "${PLATFORM_DATA_DIR}/"{nginx,addons,acme,backup,logs,update,sftp,firewall,sshfs,cifs,tls,oidc,diskusage} chown "${USER}:${USER}" "${PLATFORM_DATA_DIR}/INFRA_VERSION" 2>/dev/null || true chown "${USER}:${USER}" "${PLATFORM_DATA_DIR}" chown "${USER}:${USER}" "${APPS_DATA_DIR}" chown "${USER}:${USER}" -R "${BOX_DATA_DIR}" # do not chown the boxdata/mail directory entirely; dovecot gets upset chown "${USER}:${USER}" "${MAIL_DATA_DIR}" # this require logs dir to have correct permissions log "Starting cloudron-syslog" systemctl enable cloudron-syslog systemctl start cloudron-syslog log "Starting Cloudron" systemctl start box sleep 2 # give systemd sometime to start the processes log "Almost done"