#!/bin/bash set -eu -o pipefail if [[ ${EUID} -ne 0 ]]; then echo "This script should be run as root." > /dev/stderr exit 1 fi if [[ $(lsb_release -rs) != "16.04" ]]; then echo "Cloudron requires Ubuntu 16.04" > /dev/stderr exit 1 fi # change this to a hash when we make a upgrade release readonly LOG_FILE="/var/log/cloudron-setup.log" readonly DATA_FILE="/root/cloudron-install-data.json" readonly MINIMUM_DISK_SIZE_GB="18" # this is the size of "/" and required to fit in docker images 18 is a safe bet for different reporting on 20GB min readonly MINIMUM_MEMORY="974" # this is mostly reported for 1GB main memory (DO 992, EC2 990, Linode 989, Serverdiscounter.com 974) readonly curl="curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max-time 2400" # copied from cloudron-resize-fs.sh readonly physical_memory=$(LC_ALL=C free -m | awk '/Mem:/ { print $2 }') readonly disk_size_bytes=$(LC_ALL=C df --output=size / | tail -n1) readonly disk_size_gb=$((${disk_size_bytes}/1024/1024)) # verify the system has minimum requirements met if [[ "${physical_memory}" -lt "${MINIMUM_MEMORY}" ]]; then echo "Error: Cloudron requires atleast 1GB physical memory" exit 1 fi if [[ "${disk_size_gb}" -lt "${MINIMUM_DISK_SIZE_GB}" ]]; then echo "Error: Cloudron requires atleast 20GB disk space (Disk space on / is ${disk_size_gb}GB)" exit 1 fi initBaseImage="true" # provisioning data domain="" provider="" encryptionKey="" restoreUrl="" dnsProvider="manual" tlsProvider="le-prod" requestedVersion="" apiServerOrigin="https://api.cloudron.io" webServerOrigin="https://cloudron.io" dataJson="" prerelease="false" sourceTarballUrl="" rebootServer="true" baseDataDir="" # TODO this is still there for the restore case, see other occasions below versionsUrl="https://s3.amazonaws.com/prod-cloudron-releases/versions.json" args=$(getopt -o "" -l "domain:,help,skip-baseimage-init,data:,data-dir:,provider:,encryption-key:,restore-url:,tls-provider:,version:,dns-provider:,env:,prerelease,skip-reboot,source-url:" -n "$0" -- "$@") eval set -- "${args}" while true; do case "$1" in --domain) domain="$2"; shift 2;; --help) echo "See https://cloudron.io/references/selfhosting.html on how to install Cloudron"; exit 0;; --provider) provider="$2"; shift 2;; --encryption-key) encryptionKey="$2"; shift 2;; --restore-url) restoreUrl="$2"; shift 2;; --tls-provider) tlsProvider="$2"; shift 2;; --dns-provider) dnsProvider="$2"; shift 2;; --version) requestedVersion="$2"; shift 2;; --env) if [[ "$2" == "dev" ]]; then versionsUrl="https://s3.amazonaws.com/dev-cloudron-releases/versions.json" apiServerOrigin="https://api.dev.cloudron.io" webServerOrigin="https://dev.cloudron.io" tlsProvider="le-staging" prerelease="true" elif [[ "$2" == "staging" ]]; then versionsUrl="https://s3.amazonaws.com/staging-cloudron-releases/versions.json" apiServerOrigin="https://api.staging.cloudron.io" webServerOrigin="https://staging.cloudron.io" tlsProvider="le-staging" prerelease="true" fi shift 2;; --skip-baseimage-init) initBaseImage="false"; shift;; --skip-reboot) rebootServer="false"; shift;; --data) dataJson="$2"; shift 2;; --prerelease) prerelease="true"; shift;; --source-url) sourceTarballUrl="$2"; version="0.0.1+custom"; shift 2;; --data-dir) baseDataDir=$(realpath "$2"); shift 2;; --) break;; *) echo "Unknown option $1"; exit 1;; esac done # validate arguments in the absence of data if [[ -z "${dataJson}" ]]; then if [[ -z "${provider}" ]]; then echo "--provider is required (azure, digitalocean, ec2, lightsail, linode, ovh, rosehosting, scaleway, vultr or generic)" exit 1 elif [[ \ "${provider}" != "ami" && \ "${provider}" != "azure" && \ "${provider}" != "digitalocean" && \ "${provider}" != "ec2" && \ "${provider}" != "lightsail" && \ "${provider}" != "linode" && \ "${provider}" != "ovh" && \ "${provider}" != "rosehosting" && \ "${provider}" != "scaleway" && \ "${provider}" != "vultr" && \ "${provider}" != "generic" \ ]]; then echo "--provider must be one of: azure, digitalocean, ec2, lightsail, linode, ovh, rosehosting, scaleway, vultr or generic" exit 1 fi if [[ "${tlsProvider}" != "fallback" && "${tlsProvider}" != "le-prod" && "${tlsProvider}" != "le-staging" ]]; then echo "--tls-provider must be one of: le-prod, le-staging, fallback" exit 1 fi if [[ -z "${dnsProvider}" ]]; then echo "--dns-provider is required (noop, manual)" exit 1 elif [[ "${dnsProvider}" != "noop" && "${dnsProvider}" != "manual" ]]; then echo "--dns-provider must be one of : manual, noop" exit 1 fi if [[ -n "${baseDataDir}" && ! -d "${baseDataDir}" ]]; then echo "${baseDataDir} does not exist" exit 1 fi fi echo "" echo "##############################################" echo " Cloudron Setup (${requestedVersion:-latest})" echo "##############################################" echo "" echo " Follow setup logs in a second terminal with:" echo " $ tail -f ${LOG_FILE}" echo "" echo " Join us at https://chat.cloudron.io for any questions." echo "" if [[ "${initBaseImage}" == "true" ]]; then echo "=> Updating apt and installing script dependencies" if ! apt-get update &>> "${LOG_FILE}"; then echo "Could not update package repositories" exit 1 fi if ! apt-get install curl python3 ubuntu-standard -y &>> "${LOG_FILE}"; then echo "Could not install setup dependencies (curl)" exit 1 fi fi echo "=> Checking version" if [[ "${sourceTarballUrl}" == "" ]]; then if ! releaseJson=$($curl -s "${apiServerOrigin}/api/v1/releases?prerelease=${prerelease}&boxVersion=${requestedVersion}"); then echo "Failed to get release information" exit 1 fi if [[ "$requestedVersion" == "" ]]; then version=$(echo "${releaseJson}" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["version"])') else version="${requestedVersion}" fi if ! sourceTarballUrl=$(echo "${releaseJson}" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["info"]["sourceTarballUrl"])'); then echo "No source code for version '${requestedVersion:-latest}'" exit 1 fi fi # Build data # TODO versionsUrl is still there for the cloudron restore case if [[ -z "${dataJson}" ]]; then if [[ -z "${restoreUrl}" ]]; then data=$(cat < Downloading version ${version} ..." box_src_tmp_dir=$(mktemp -dt box-src-XXXXXX) if ! $curl -sL "${sourceTarballUrl}" | tar -zxf - -C "${box_src_tmp_dir}"; then echo "Could not download source tarball. See ${LOG_FILE} for details" exit 1 fi if [[ "${initBaseImage}" == "true" ]]; then echo -n "=> Installing base dependencies and downloading docker images (this takes some time) ..." if ! /bin/bash "${box_src_tmp_dir}/baseimage/initializeBaseUbuntuImage.sh" "${provider}" "../src" &>> "${LOG_FILE}"; then echo "Init script failed. See ${LOG_FILE} for details" exit 1 fi echo "" fi echo "=> Installing version ${version} (this takes some time) ..." echo "${data}" > "${DATA_FILE}" # poor mans semver if [[ ${version} == "0.10"* ]]; then if ! /bin/bash "${box_src_tmp_dir}/scripts/installer.sh" --data-file "${DATA_FILE}" &>> "${LOG_FILE}"; then echo "Failed to install cloudron. See ${LOG_FILE} for details" exit 1 fi else if ! /bin/bash "${box_src_tmp_dir}/scripts/installer.sh" --data-file "${DATA_FILE}" --data-dir "${baseDataDir}" &>> "${LOG_FILE}"; then echo "Failed to install cloudron. See ${LOG_FILE} for details" exit 1 fi fi rm "${DATA_FILE}" echo -n "=> Waiting for cloudron to be ready (this takes some time) ..." while true; do echo -n "." if status=$($curl -q -f "http://localhost:3000/api/v1/cloudron/status" 2>/dev/null); then [[ -z "$domain" ]] && break # with no domain, we are up and running [[ "$status" == *"\"tls\": true"* ]] && break # with a domain, wait for the cert fi sleep 10 done if [[ -n "${domain}" ]]; then echo -e "\n\nVisit https://my.${domain} to finish setup once the server has rebooted.\n" else echo -e "\n\nVisit https:// to finish setup once the server has rebooted.\n" fi if [[ "${rebootServer}" == "true" ]]; then echo -e "\n\nRebooting this server now to let bootloader changes take effect.\n" systemctl reboot fi