This also makes the noop backend more useful because it will dump things in data directory and user can back it up as they see fit.
308 lines
12 KiB
Bash
Executable File
308 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -eu -o pipefail
|
|
|
|
echo "==> Cloudron Start"
|
|
|
|
readonly USER="yellowtent"
|
|
readonly HOME_DIR="/home/${USER}"
|
|
readonly BOX_SRC_DIR="${HOME_DIR}/box"
|
|
readonly PLATFORM_DATA_DIR="${HOME_DIR}/platformdata" # platform data
|
|
readonly APPS_DATA_DIR="${HOME_DIR}/appsdata" # app data
|
|
readonly BOX_DATA_DIR="${HOME_DIR}/boxdata" # box data
|
|
readonly CONFIG_DIR="${HOME_DIR}/configs"
|
|
readonly SETUP_PROGRESS_JSON="${HOME_DIR}/setup/website/progress.json"
|
|
|
|
readonly curl="curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max-time 2400"
|
|
|
|
readonly script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
source "${script_dir}/argparser.sh" "$@" # this injects the arg_* variables used below
|
|
|
|
set_progress() {
|
|
local percent="$1"
|
|
local message="$2"
|
|
|
|
echo "==> ${percent} - ${message}"
|
|
(echo "{ \"update\": { \"percent\": \"${percent}\", \"message\": \"${message}\" }, \"backup\": {} }" > "${SETUP_PROGRESS_JSON}") 2> /dev/null || true # as this will fail in non-update mode
|
|
}
|
|
|
|
set_progress "20" "Configuring host"
|
|
sed -e 's/^#NTP=/NTP=0.ubuntu.pool.ntp.org 1.ubuntu.pool.ntp.org 2.ubuntu.pool.ntp.org 3.ubuntu.pool.ntp.org/' -i /etc/systemd/timesyncd.conf
|
|
timedatectl set-ntp 1
|
|
timedatectl set-timezone UTC
|
|
hostnamectl set-hostname "${arg_fqdn}"
|
|
|
|
echo "==> 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
|
|
# preserve the existing storage driver (user might be using overlay2)
|
|
storage_driver=$(docker info | grep "Storage Driver" | sed 's/.*: //')
|
|
[[ -n "${storage_driver}" ]] || storage_driver="overlay2" # if the above command fails
|
|
|
|
temp_file=$(mktemp)
|
|
# create systemd drop-in. some apps do not work with aufs
|
|
echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=${storage_driver}" > "${temp_file}"
|
|
|
|
systemctl enable docker
|
|
# restart docker if options changed
|
|
if [[ ! -f /etc/systemd/system/docker.service.d/cloudron.conf ]] || ! diff -q /etc/systemd/system/docker.service.d/cloudron.conf "${temp_file}" >/dev/null; then
|
|
mkdir -p /etc/systemd/system/docker.service.d
|
|
mv "${temp_file}" /etc/systemd/system/docker.service.d/cloudron.conf
|
|
systemctl daemon-reload
|
|
systemctl restart docker
|
|
fi
|
|
docker network create --subnet=172.18.0.0/16 cloudron || true
|
|
|
|
# caas has ssh on port 202 and we disable password login
|
|
if [[ "${arg_provider}" == "caas" ]]; then
|
|
# https://stackoverflow.com/questions/4348166/using-with-sed on why ? must be escaped
|
|
sed -e 's/^#\?PermitRootLogin .*/PermitRootLogin without-password/g' \
|
|
-e 's/^#\?PermitEmptyPasswords .*/PermitEmptyPasswords no/g' \
|
|
-e 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/g' \
|
|
-e 's/^#\?Port .*/Port 202/g' \
|
|
-i /etc/ssh/sshd_config
|
|
|
|
# required so we can connect to this machine since port 22 is blocked by iptables by now
|
|
systemctl reload sshd
|
|
fi
|
|
|
|
mkdir -p "${BOX_DATA_DIR}"
|
|
mkdir -p "${APPS_DATA_DIR}"
|
|
|
|
# keep these in sync with paths.js
|
|
echo "==> Ensuring directories"
|
|
|
|
mkdir -p "${PLATFORM_DATA_DIR}/graphite"
|
|
mkdir -p "${PLATFORM_DATA_DIR}/mysql"
|
|
mkdir -p "${PLATFORM_DATA_DIR}/postgresql"
|
|
mkdir -p "${PLATFORM_DATA_DIR}/mongodb"
|
|
mkdir -p "${PLATFORM_DATA_DIR}/addons/mail"
|
|
mkdir -p "${PLATFORM_DATA_DIR}/collectd/collectd.conf.d"
|
|
mkdir -p "${PLATFORM_DATA_DIR}/logrotate.d"
|
|
mkdir -p "${PLATFORM_DATA_DIR}/acme"
|
|
|
|
mkdir -p "${BOX_DATA_DIR}/appicons"
|
|
mkdir -p "${BOX_DATA_DIR}/certs"
|
|
mkdir -p "${BOX_DATA_DIR}/acme" # acme keys
|
|
mkdir -p "${BOX_DATA_DIR}/mail/dkim"
|
|
|
|
# ensure backups folder exists and is writeable
|
|
mkdir -p /var/backups
|
|
chmod 777 /var/backups
|
|
|
|
echo "==> Migrating mail data"
|
|
if [[ -d "${PLATFORM_DATA_DIR}/mail" ]]; then
|
|
find "${PLATFORM_DATA_DIR}/mail" -mindepth 1 -maxdepth 1 -exec mv --target-directory="${BOX_DATA_DIR}/mail" '{}' +
|
|
fi
|
|
|
|
echo "==> 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
|
|
|
|
# Give user access to system logs
|
|
usermod -a -G systemd-journal ${USER}
|
|
mkdir -p /var/log/journal # in some images, this directory is not created making system log to /run/systemd instead
|
|
chown root:systemd-journal /var/log/journal
|
|
systemctl daemon-reload
|
|
systemctl restart systemd-journald
|
|
setfacl -n -m u:${USER}:r /var/log/journal/*/system.journal
|
|
|
|
echo "==> Creating config directory"
|
|
rm -rf "${CONFIG_DIR}" && mkdir "${CONFIG_DIR}"
|
|
|
|
echo "==> Setting up unbound"
|
|
# DO uses Google nameservers by default. This causes RBL queries to fail (host 2.0.0.127.zen.spamhaus.org)
|
|
# We do not use dnsmasq because it is not a recursive resolver and defaults to the value in the interfaces file (which is Google DNS!)
|
|
# We listen on 0.0.0.0 because there is no way control ordering of docker (which creates the 172.18.0.0/16) and unbound
|
|
echo -e "server:\n\tinterface: 0.0.0.0\n\tdo-ip6: no\n\taccess-control: 127.0.0.1 allow\n\taccess-control: 172.18.0.1/16 allow\n\tcache-max-negative-ttl: 30\n\tcache-max-ttl: 300" > /etc/unbound/unbound.conf.d/cloudron-network.conf
|
|
# update the root anchor after a out-of-disk-space situation (see #269)
|
|
unbound-anchor -a /var/lib/unbound/root.key
|
|
|
|
echo "==> Adding systemd services"
|
|
cp -r "${script_dir}/start/systemd/." /etc/systemd/system/
|
|
systemctl daemon-reload
|
|
systemctl enable unbound
|
|
systemctl enable cloudron.target
|
|
systemctl enable cloudron-firewall
|
|
|
|
# update firewall rules
|
|
systemctl restart cloudron-firewall
|
|
|
|
# For logrotate
|
|
systemctl enable --now cron
|
|
|
|
# ensure unbound runs
|
|
systemctl restart unbound
|
|
|
|
echo "==> Configuring sudoers"
|
|
rm -f /etc/sudoers.d/${USER}
|
|
cp "${script_dir}/start/sudoers" /etc/sudoers.d/${USER}
|
|
|
|
echo "==> Configuring collectd"
|
|
rm -rf /etc/collectd
|
|
ln -sfF "${PLATFORM_DATA_DIR}/collectd" /etc/collectd
|
|
cp "${script_dir}/start/collectd/collectd.conf" "${PLATFORM_DATA_DIR}/collectd/collectd.conf"
|
|
systemctl restart collectd
|
|
|
|
echo "==> 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
|
|
|
|
echo "==> Adding motd message for admins"
|
|
cp "${script_dir}/start/cloudron-motd" /etc/update-motd.d/92-cloudron
|
|
|
|
echo "==> 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"
|
|
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"
|
|
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
|
|
systemctl daemon-reload
|
|
fi
|
|
systemctl start nginx
|
|
|
|
# bookkeep the version as part of data
|
|
echo "{ \"version\": \"${arg_version}\", \"apiServerOrigin\": \"${arg_api_server_origin}\" }" > "${BOX_DATA_DIR}/version"
|
|
|
|
# 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
|
|
echo "Waiting for mysql jobs..."
|
|
sleep 1
|
|
done
|
|
systemctl restart mysql
|
|
else
|
|
systemctl start mysql
|
|
fi
|
|
|
|
readonly mysql_root_password="password"
|
|
mysqladmin -u root -ppassword password password # reset default root password
|
|
mysql -u root -p${mysql_root_password} -e 'CREATE DATABASE IF NOT EXISTS box'
|
|
|
|
if [[ -n "${arg_restore_url}" ]]; then
|
|
set_progress "30" "Downloading restore data"
|
|
|
|
decrypt=""
|
|
if [[ "${arg_restore_url}" == *.tar.gz.enc || -n "${arg_restore_key}" ]]; then
|
|
echo "==> Downloading encrypted backup: ${arg_restore_url} and key: ${arg_restore_key}"
|
|
decrypt=(openssl aes-256-cbc -d -nosalt -pass "pass:${arg_restore_key}")
|
|
else
|
|
echo "==> Downloading backup: ${arg_restore_url}"
|
|
decrypt=(cat -)
|
|
fi
|
|
|
|
while true; do
|
|
if $curl -L "${arg_restore_url}" | "${decrypt[@]}" \
|
|
| tar -zxf - --overwrite -C "${BOX_DATA_DIR}"; then break; fi
|
|
echo "Failed to download data, trying again"
|
|
done
|
|
|
|
set_progress "35" "Setting up MySQL"
|
|
if [[ -f "${BOX_DATA_DIR}/box.mysqldump" ]]; then
|
|
echo "==> Importing existing database into MySQL"
|
|
mysql -u root -p${mysql_root_password} box < "${BOX_DATA_DIR}/box.mysqldump"
|
|
fi
|
|
fi
|
|
|
|
set_progress "40" "Migrating data"
|
|
sudo -u "${USER}" -H bash <<EOF
|
|
set -eu
|
|
cd "${BOX_SRC_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
|
|
EOF
|
|
|
|
echo "==> Creating cloudron.conf"
|
|
cat > "${CONFIG_DIR}/cloudron.conf" <<CONF_END
|
|
{
|
|
"version": "${arg_version}",
|
|
"token": "${arg_token}",
|
|
"apiServerOrigin": "${arg_api_server_origin}",
|
|
"webServerOrigin": "${arg_web_server_origin}",
|
|
"fqdn": "${arg_fqdn}",
|
|
"zoneName": "${arg_zone_name}",
|
|
"isCustomDomain": ${arg_is_custom_domain},
|
|
"provider": "${arg_provider}",
|
|
"isDemo": ${arg_is_demo},
|
|
"database": {
|
|
"hostname": "127.0.0.1",
|
|
"username": "root",
|
|
"password": "${mysql_root_password}",
|
|
"port": 3306,
|
|
"name": "box"
|
|
},
|
|
"appBundle": ${arg_app_bundle}
|
|
}
|
|
CONF_END
|
|
# pass these out-of-band because they have new lines which interfere with json
|
|
if [[ -n "${arg_tls_cert}" && -n "${arg_tls_key}" ]]; then
|
|
echo "${arg_tls_cert}" > "${CONFIG_DIR}/host.cert"
|
|
echo "${arg_tls_key}" > "${CONFIG_DIR}/host.key"
|
|
fi
|
|
|
|
echo "==> Creating config.json for webadmin"
|
|
cat > "${BOX_SRC_DIR}/webadmin/dist/config.json" <<CONF_END
|
|
{
|
|
"webServerOrigin": "${arg_web_server_origin}"
|
|
}
|
|
CONF_END
|
|
|
|
echo "==> Changing ownership"
|
|
chown "${USER}:${USER}" -R "${CONFIG_DIR}"
|
|
chown "${USER}:${USER}" -R "${PLATFORM_DATA_DIR}/nginx" "${PLATFORM_DATA_DIR}/collectd" "${PLATFORM_DATA_DIR}/logrotate.d" "${PLATFORM_DATA_DIR}/addons" "${PLATFORM_DATA_DIR}/acme"
|
|
chown "${USER}:${USER}" -R "${BOX_DATA_DIR}"
|
|
chown "${USER}:${USER}" -R "${BOX_DATA_DIR}/mail/dkim" # this is owned by box currently since it generates the keys
|
|
chown "${USER}:${USER}" "${PLATFORM_DATA_DIR}/INFRA_VERSION" 2>/dev/null || true
|
|
chown "${USER}:${USER}" "${PLATFORM_DATA_DIR}"
|
|
|
|
echo "==> Adding automated configs"
|
|
if [[ ! -z "${arg_backup_config}" ]]; then
|
|
mysql -u root -p${mysql_root_password} \
|
|
-e "REPLACE INTO settings (name, value) VALUES (\"backup_config\", '$arg_backup_config')" box
|
|
fi
|
|
|
|
if [[ ! -z "${arg_dns_config}" ]]; then
|
|
mysql -u root -p${mysql_root_password} \
|
|
-e "REPLACE INTO settings (name, value) VALUES (\"dns_config\", '$arg_dns_config')" box
|
|
fi
|
|
|
|
if [[ ! -z "${arg_update_config}" ]]; then
|
|
mysql -u root -p${mysql_root_password} \
|
|
-e "REPLACE INTO settings (name, value) VALUES (\"update_config\", '$arg_update_config')" box
|
|
fi
|
|
|
|
if [[ ! -z "${arg_tls_config}" ]]; then
|
|
mysql -u root -p${mysql_root_password} \
|
|
-e "REPLACE INTO settings (name, value) VALUES (\"tls_config\", '$arg_tls_config')" box
|
|
fi
|
|
|
|
echo "==> Generating dhparams (takes forever)"
|
|
if [[ ! -f "${BOX_DATA_DIR}/dhparams.pem" ]]; then
|
|
openssl dhparam -out "${BOX_DATA_DIR}/dhparams.pem" 2048
|
|
fi
|
|
|
|
set_progress "60" "Starting Cloudron"
|
|
systemctl start cloudron.target
|
|
|
|
sleep 2 # give systemd sometime to start the processes
|
|
|
|
set_progress "90" "Almost done"
|