249 lines
9.6 KiB
Bash
Executable File
249 lines
9.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -eu -o pipefail
|
|
|
|
function exitHandler() {
|
|
rm -f /etc/update-motd.d/91-cloudron-install-in-progress
|
|
}
|
|
|
|
trap exitHandler EXIT
|
|
|
|
# change this to a hash when we make a upgrade release
|
|
readonly LOG_FILE="/var/log/cloudron-setup.log"
|
|
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 rootfs_type=$(LC_ALL=C df --output=fstype / | tail -n1)
|
|
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))
|
|
|
|
readonly RED='\033[31m'
|
|
readonly GREEN='\033[32m'
|
|
readonly DONE='\033[m'
|
|
|
|
# verify the system has minimum requirements met
|
|
if [[ "${rootfs_type}" != "ext4" ]]; then
|
|
echo "Error: Cloudron requires '/' to be ext4" # see #364
|
|
exit 1
|
|
fi
|
|
|
|
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
|
|
|
|
if [[ "$(uname -m)" != "x86_64" ]]; then
|
|
echo "Error: Cloudron only supports amd64/x86_64"
|
|
exit 1
|
|
fi
|
|
|
|
# do not use is-active in case box service is down and user attempts to re-install
|
|
if systemctl cat box.service >/dev/null 2>&1; then
|
|
echo "Error: Cloudron is already installed. To reinstall, start afresh"
|
|
exit 1
|
|
fi
|
|
|
|
provider="generic"
|
|
requestedVersion=""
|
|
installServerOrigin="https://api.cloudron.io"
|
|
apiServerOrigin="https://api.cloudron.io"
|
|
webServerOrigin="https://cloudron.io"
|
|
sourceTarballUrl=""
|
|
rebootServer="true"
|
|
setupToken="" # this is a OTP for securing an installation (https://forum.cloudron.io/topic/6389/add-password-for-initial-configuration)
|
|
appstoreSetupToken=""
|
|
redo="false"
|
|
|
|
args=$(getopt -o "" -l "help,provider:,version:,env:,skip-reboot,generate-setup-token,setup-token:,redo" -n "$0" -- "$@")
|
|
eval set -- "${args}"
|
|
|
|
while true; do
|
|
case "$1" in
|
|
--help) echo "See https://docs.cloudron.io/installation/ on how to install Cloudron"; exit 0;;
|
|
--provider) provider="$2"; shift 2;;
|
|
--version) requestedVersion="$2"; shift 2;;
|
|
--env)
|
|
if [[ "$2" == "dev" ]]; then
|
|
apiServerOrigin="https://api.dev.cloudron.io"
|
|
webServerOrigin="https://dev.cloudron.io"
|
|
installServerOrigin="https://api.dev.cloudron.io"
|
|
elif [[ "$2" == "staging" ]]; then
|
|
apiServerOrigin="https://api.staging.cloudron.io"
|
|
webServerOrigin="https://staging.cloudron.io"
|
|
installServerOrigin="https://api.staging.cloudron.io"
|
|
elif [[ "$2" == "unstable" ]]; then
|
|
installServerOrigin="https://api.dev.cloudron.io"
|
|
fi
|
|
shift 2;;
|
|
--skip-reboot) rebootServer="false"; shift;;
|
|
--redo) redo="true"; shift;;
|
|
--setup-token) appstoreSetupToken="$2"; shift 2;;
|
|
--generate-setup-token) setupToken="$(openssl rand -hex 10)"; shift;;
|
|
--) break;;
|
|
*) echo "Unknown option $1"; exit 1;;
|
|
esac
|
|
done
|
|
|
|
# Only --help works as non-root
|
|
if [[ ${EUID} -ne 0 ]]; then
|
|
echo "This script should be run as root." > /dev/stderr
|
|
exit 1
|
|
fi
|
|
|
|
# Only --help works with mismatched ubuntu
|
|
ubuntu_version=$(lsb_release -rs)
|
|
if [[ "${ubuntu_version}" != "16.04" && "${ubuntu_version}" != "18.04" && "${ubuntu_version}" != "20.04" ]]; then
|
|
echo "Cloudron requires Ubuntu 16.04, 18.04 or 20.04" > /dev/stderr
|
|
exit 1
|
|
fi
|
|
|
|
if which nginx >/dev/null || which docker >/dev/null || which node > /dev/null; then
|
|
if [[ "${redo}" == "false" ]]; then
|
|
echo "Error: Some packages like nginx/docker/nodejs are already installed. Cloudron requires specific versions of these packages and will install them as part of it's installation. Please start with a fresh Ubuntu install and run this script again." > /dev/stderr
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Install MOTD file for stack script style installations. this is removed by the trap exit handler. Heredoc quotes prevents parameter expansion
|
|
cat > /etc/update-motd.d/91-cloudron-install-in-progress <<'EOF'
|
|
#!/bin/bash
|
|
|
|
printf "**********************************************************************\n\n"
|
|
|
|
printf "\t\t\tWELCOME TO CLOUDRON\n"
|
|
printf "\t\t\t-------------------\n"
|
|
|
|
printf '\n\e[1;32m%-6s\e[m\n\n' "Cloudron is installing. Run 'tail -f /var/log/cloudron-setup.log' to view progress."
|
|
|
|
printf "Cloudron overview - https://docs.cloudron.io/ \n"
|
|
printf "Cloudron setup - https://docs.cloudron.io/installation/#setup \n"
|
|
|
|
printf "\nFor help and more information, visit https://forum.cloudron.io\n\n"
|
|
|
|
printf "**********************************************************************\n"
|
|
EOF
|
|
chmod +x /etc/update-motd.d/91-cloudron-install-in-progress
|
|
|
|
# Can only write after we have confirmed script has root access
|
|
echo "Running cloudron-setup with args : $@" > "${LOG_FILE}"
|
|
|
|
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://forum.cloudron.io for any questions."
|
|
echo ""
|
|
|
|
echo "=> Updating apt and installing script dependencies"
|
|
if ! apt-get update &>> "${LOG_FILE}"; then
|
|
echo "Could not update package repositories. See ${LOG_FILE}"
|
|
exit 1
|
|
fi
|
|
|
|
if ! DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y install --no-install-recommends curl python3 ubuntu-standard software-properties-common -y &>> "${LOG_FILE}"; then
|
|
echo "Could not install setup dependencies (curl). See ${LOG_FILE}"
|
|
exit 1
|
|
fi
|
|
|
|
echo "=> Checking version"
|
|
if ! releaseJson=$($curl -s "${installServerOrigin}/api/v1/releases?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
|
|
|
|
echo "=> Downloading Cloudron 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
|
|
|
|
echo -n "=> Installing base dependencies and downloading docker images (this takes some time) ..."
|
|
if ! /bin/bash "${box_src_tmp_dir}/baseimage/initializeBaseUbuntuImage.sh" &>> "${LOG_FILE}"; then
|
|
echo "Init script failed. See ${LOG_FILE} for details"
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
# The provider flag is still used for marketplace images
|
|
echo "=> Installing Cloudron version ${version} (this takes some time) ..."
|
|
mkdir -p /etc/cloudron
|
|
echo "${provider}" > /etc/cloudron/PROVIDER
|
|
[[ ! -z "${setupToken}" ]] && echo "${setupToken}" > /etc/cloudron/SETUP_TOKEN
|
|
|
|
if ! /bin/bash "${box_src_tmp_dir}/scripts/installer.sh" &>> "${LOG_FILE}"; then
|
|
echo "Failed to install cloudron. See ${LOG_FILE} for details"
|
|
exit 1
|
|
fi
|
|
|
|
mysql -uroot -ppassword -e "REPLACE INTO box.settings (name, value) VALUES ('api_server_origin', '${apiServerOrigin}');" 2>/dev/null
|
|
mysql -uroot -ppassword -e "REPLACE INTO box.settings (name, value) VALUES ('web_server_origin', '${webServerOrigin}');" 2>/dev/null
|
|
|
|
if [[ -n "${appstoreSetupToken}" ]]; then
|
|
if ! setupResponse=$(curl -X POST -H "Content-type: application/json" --data "{\"setupToken\": \"${appstoreSetupToken}\"}" "${apiServerOrigin}/api/v1/cloudron_setup_done"); then
|
|
echo "Could not complete setup. See ${LOG_FILE} for details"
|
|
exit 1
|
|
fi
|
|
|
|
cloudronId=$(echo "${setupResponse}" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["cloudronId"])')
|
|
mysql -uroot -ppassword -e "REPLACE INTO box.settings (name, value) VALUES ('cloudron_id', '${cloudronId}');" 2>/dev/null
|
|
|
|
appstoreApiToken=$(echo "${setupResponse}" | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["cloudronToken"])')
|
|
mysql -uroot -ppassword -e "REPLACE INTO box.settings (name, value) VALUES ('appstore_api_token', '${appstoreApiToken}');" 2>/dev/null
|
|
fi
|
|
|
|
echo -n "=> Waiting for cloudron to be ready (this takes some time) ..."
|
|
while true; do
|
|
echo -n "."
|
|
if status=$($curl -s -f "http://localhost:3000/api/v1/cloudron/status" 2>/dev/null); then
|
|
break # we are up and running
|
|
fi
|
|
sleep 10
|
|
done
|
|
|
|
if ! ip=$(curl -s --fail --connect-timeout 2 --max-time 2 https://ipv4.api.cloudron.io/api/v1/helper/public_ip | sed -n -e 's/.*"ip": "\(.*\)"/\1/p'); then
|
|
ip='<IP>'
|
|
fi
|
|
if [[ -z "${setupToken}" ]]; then
|
|
url="https://${ip}"
|
|
else
|
|
url="https://${ip}/?setupToken=${setupToken}"
|
|
fi
|
|
echo -e "\n\n${GREEN}After reboot, visit ${url} and accept the self-signed certificate to finish setup.${DONE}\n"
|
|
|
|
if [[ "${rebootServer}" == "true" ]]; then
|
|
systemctl stop box mysql # sometimes mysql ends up having corrupt privilege tables
|
|
|
|
read -p "The server has to be rebooted to apply all the settings. Reboot now ? [Y/n] " yn
|
|
yn=${yn:-y}
|
|
case $yn in
|
|
[Yy]* ) exitHandler; systemctl reboot;;
|
|
* ) exit;;
|
|
esac
|
|
fi
|