2016-10-24 16:18:02 +02:00
#!/bin/bash
2016-10-25 12:00:54 -07:00
set -eu -o pipefail
2016-10-24 16:18:02 +02:00
2021-02-25 10:40:34 -08:00
function exitHandler( ) {
rm -f /etc/update-motd.d/91-cloudron-install-in-progress
}
trap exitHandler EXIT
2023-09-02 12:02:37 +05:30
vergte( ) {
greater_version = $( echo -e " $1 \n $2 " | sort -rV | head -n1)
[ [ " $1 " = = " ${ greater_version } " ] ] && return 0 || return 1
}
2016-10-24 14:52:30 -07:00
# change this to a hash when we make a upgrade release
2016-10-25 14:01:35 +02:00
readonly LOG_FILE = "/var/log/cloudron-setup.log"
2017-03-22 14:21:40 +01:00
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
2024-04-04 17:51:27 +02:00
readonly MINIMUM_MEMORY = "941" # this is mostly reported for 1GB main memory (DO 957, EC2 949, Linode 989, Lightsail 941)
2017-01-02 18:02:40 +01:00
2017-02-16 17:32:44 -08:00
readonly curl = "curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max-time 2400"
2017-01-02 18:02:40 +01:00
# copied from cloudron-resize-fs.sh
2017-07-24 18:14:51 -07:00
readonly rootfs_type = $( LC_ALL = C df --output= fstype / | tail -n1)
2017-03-13 10:32:27 -07:00
readonly physical_memory = $( LC_ALL = C free -m | awk '/Mem:/ { print $2 }' )
2017-03-27 13:51:34 +02:00
readonly disk_size_bytes = $( LC_ALL = C df --output= size / | tail -n1)
2017-03-22 14:21:40 +01:00
readonly disk_size_gb = $(( ${ disk_size_bytes } / 1024 / 1024 ))
2017-01-02 18:02:40 +01:00
2018-03-07 15:48:01 -08:00
readonly RED = '\033[31m'
readonly GREEN = '\033[32m'
2024-03-31 14:12:42 +02:00
readonly YELLOW = '\033[33m'
2018-03-07 15:48:01 -08:00
readonly DONE = '\033[m'
2017-01-02 18:02:40 +01:00
# verify the system has minimum requirements met
2022-06-08 10:32:25 -07:00
if [ [ " ${ rootfs_type } " != "ext4" && " ${ rootfs_type } " != "xfs" ] ] ; then
echo "Error: Cloudron requires '/' to be ext4 or xfs" # see #364
2017-07-24 18:14:51 -07:00
exit 1
fi
2017-01-02 18:02:40 +01:00
if [ [ " ${ physical_memory } " -lt " ${ MINIMUM_MEMORY } " ] ] ; then
2017-01-03 14:27:01 -08:00
echo "Error: Cloudron requires atleast 1GB physical memory"
2017-01-02 18:02:40 +01:00
exit 1
fi
if [ [ " ${ disk_size_gb } " -lt " ${ MINIMUM_DISK_SIZE_GB } " ] ] ; then
2017-03-27 13:51:34 +02:00
echo " Error: Cloudron requires atleast 20GB disk space (Disk space on / is ${ disk_size_gb } GB) "
2017-01-02 18:02:40 +01:00
exit 1
fi
2016-10-24 14:52:30 -07:00
2021-08-25 13:20:12 -07:00
if [ [ " $( uname -m) " != "x86_64" ] ] ; then
echo "Error: Cloudron only supports amd64/x86_64"
exit 1
fi
2022-04-26 14:59:26 -07:00
if cvirt = $( systemd-detect-virt --container) ; then
2022-04-26 08:24:36 -07:00
echo " Error: Cloudron does not support ${ cvirt } , only runs on bare metal or with full hardware virtualization "
exit 1
fi
2021-05-24 19:05:49 -07:00
# do not use is-active in case box service is down and user attempts to re-install
2021-06-17 10:03:59 -07:00
if systemctl cat box.service >/dev/null 2>& 1; then
2018-07-30 10:08:00 -07:00
echo "Error: Cloudron is already installed. To reinstall, start afresh"
exit 1
fi
2020-06-03 13:47:30 -07:00
provider = "generic"
2017-04-13 15:20:33 -07:00
requestedVersion = ""
2021-01-10 11:26:01 -08:00
installServerOrigin = "https://api.cloudron.io"
2017-01-11 12:35:31 -08:00
apiServerOrigin = "https://api.cloudron.io"
2017-06-08 10:30:23 -07:00
webServerOrigin = "https://cloudron.io"
2022-05-05 16:51:18 +02:00
consoleServerOrigin = "https://console.cloudron.io"
2017-02-01 14:06:18 -08:00
sourceTarballUrl = ""
2017-02-07 12:42:32 +01:00
rebootServer = "true"
2022-03-31 23:02:26 -07:00
setupToken = "" # this is a OTP for securing an installation (https://forum.cloudron.io/topic/6389/add-password-for-initial-configuration)
2022-03-31 23:38:54 -07:00
appstoreSetupToken = ""
2023-07-16 10:33:27 +05:30
cloudronId = ""
appstoreApiToken = ""
2022-03-25 10:33:47 -07:00
redo = "false"
2016-10-24 16:18:02 +02:00
2022-04-01 09:22:23 -07:00
args = $( getopt -o "" -l "help,provider:,version:,env:,skip-reboot,generate-setup-token,setup-token:,redo" -n " $0 " -- " $@ " )
2016-10-24 16:18:02 +02:00
eval set -- " ${ args } "
while true; do
case " $1 " in
2020-09-15 14:46:22 -07:00
--help) echo "See https://docs.cloudron.io/installation/ on how to install Cloudron" ; exit 0; ;
2016-10-24 16:18:02 +02:00
--provider) provider = " $2 " ; shift 2; ;
2016-12-30 13:12:22 -08:00
--version) requestedVersion = " $2 " ; shift 2; ;
2017-01-10 20:19:39 -08:00
--env)
if [ [ " $2 " = = "dev" ] ] ; then
2017-01-11 12:35:31 -08:00
apiServerOrigin = "https://api.dev.cloudron.io"
2017-06-08 10:30:23 -07:00
webServerOrigin = "https://dev.cloudron.io"
2022-05-05 16:51:18 +02:00
consoleServerOrigin = "https://console.dev.cloudron.io"
2021-01-10 11:31:25 -08:00
installServerOrigin = "https://api.dev.cloudron.io"
2017-01-10 20:19:39 -08:00
elif [ [ " $2 " = = "staging" ] ] ; then
2017-01-11 12:35:31 -08:00
apiServerOrigin = "https://api.staging.cloudron.io"
2017-06-08 10:30:23 -07:00
webServerOrigin = "https://staging.cloudron.io"
2022-05-05 16:51:18 +02:00
consoleServerOrigin = "https://console.staging.cloudron.io"
2021-01-10 11:31:25 -08:00
installServerOrigin = "https://api.staging.cloudron.io"
2021-01-10 11:26:01 -08:00
elif [ [ " $2 " = = "unstable" ] ] ; then
2021-01-10 11:31:25 -08:00
installServerOrigin = "https://api.dev.cloudron.io"
2017-01-10 20:19:39 -08:00
fi
shift 2; ;
2017-02-07 12:42:32 +01:00
--skip-reboot) rebootServer = "false" ; shift; ;
2022-03-25 10:33:47 -07:00
--redo) redo = "true" ; shift; ;
2022-03-31 23:38:54 -07:00
--setup-token) appstoreSetupToken = " $2 " ; shift 2; ;
2020-12-21 22:36:10 -08:00
--generate-setup-token) setupToken = " $( openssl rand -hex 10) " ; shift; ;
2016-10-24 16:18:02 +02:00
--) break; ;
*) echo " Unknown option $1 " ; exit 1; ;
esac
done
2018-03-09 10:37:18 -08:00
# 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
2018-10-05 10:27:34 -07:00
ubuntu_version = $( lsb_release -rs)
2024-04-28 11:31:39 +02:00
if [ [ " ${ ubuntu_version } " != "16.04" && " ${ ubuntu_version } " != "18.04" && " ${ ubuntu_version } " != "20.04" && " ${ ubuntu_version } " != "22.04" && " ${ ubuntu_version } " != "24.04" ] ] ; then
echo "Cloudron requires Ubuntu 18.04, 20.04, 22.04, 24.04" > /dev/stderr
2018-03-09 10:37:18 -08:00
exit 1
fi
2021-07-28 07:16:06 -07:00
if which nginx >/dev/null || which docker >/dev/null || which node > /dev/null; then
2022-03-25 10:33:47 -07:00
if [ [ " ${ redo } " = = "false" ] ] ; then
2023-12-13 09:32:38 +01:00
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 its installation. Please start with a fresh Ubuntu install and run this script again." > /dev/stderr
2022-03-25 10:33:47 -07:00
exit 1
fi
2021-07-28 07:16:06 -07:00
fi
2021-02-25 10:40:34 -08:00
# 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"
2021-03-03 14:34:55 -08:00
printf '\n\e[1;32m%-6s\e[m\n\n' "Cloudron is installing. Run 'tail -f /var/log/cloudron-setup.log' to view progress."
2021-02-25 10:40:34 -08:00
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
2024-05-04 17:36:26 +02:00
# workaround netcup setting immutable bit. required for installation pre 8.0
2024-01-31 17:24:29 +01:00
if lsattr -l /etc/resolv.conf 2>/dev/null | grep -q Immutable; then
chattr -i /etc/resolv.conf
fi
2018-10-25 11:31:39 -07:00
# Can only write after we have confirmed script has root access
echo " Running cloudron-setup with args : $@ " > " ${ LOG_FILE } "
2016-10-25 15:28:26 +02:00
echo ""
2016-10-25 14:07:28 +02:00
echo "##############################################"
2017-04-13 15:20:33 -07:00
echo " Cloudron Setup ( ${ requestedVersion :- latest } ) "
2016-10-25 14:07:28 +02:00
echo "##############################################"
echo ""
echo " Follow setup logs in a second terminal with:"
echo " $ tail -f ${ LOG_FILE } "
echo ""
2018-03-08 10:50:18 -08:00
echo " Join us at https://forum.cloudron.io for any questions."
2016-12-22 13:28:04 -08:00
echo ""
2016-10-25 14:07:28 +02:00
2022-04-01 09:22:23 -07:00
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
2017-01-05 14:03:30 -08:00
2022-04-01 09:22:23 -07:00
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
2017-01-05 14:03:30 -08:00
fi
2023-07-16 10:33:27 +05:30
echo "=> Validating setup token"
if [ [ -n " ${ appstoreSetupToken } " ] ] ; then
if ! httpCode = $( curl -sX POST -H "Content-type: application/json" -o /tmp/response.json -w "%{http_code}" --data " {\"setupToken\": \" ${ appstoreSetupToken } \"} " " ${ apiServerOrigin } /api/v1/cloudron_setup_done " ) ; then
echo " Could not reach ${ apiServerOrigin } to complete setup "
exit 1
fi
if [ [ " ${ httpCode } " != "200" ] ] ; then
echo -e " Failed to validate setup token.\n $( cat /tmp/response.json) "
exit 1
fi
setupResponse = $( cat /tmp/response.json)
cloudronId = $( echo " ${ setupResponse } " | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["cloudronId"])' )
appstoreApiToken = $( echo " ${ setupResponse } " | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["cloudronToken"])' )
fi
2016-10-25 13:05:24 -07:00
echo "=> Checking version"
2021-01-10 11:26:01 -08:00
if ! releaseJson = $( $curl -s " ${ installServerOrigin } /api/v1/releases?boxVersion= ${ requestedVersion } " ) ; then
2018-01-31 20:14:31 -08:00
echo "Failed to get release information"
exit 1
fi
2017-04-13 15:20:33 -07:00
2018-01-31 20:14:31 -08:00
if [ [ " $requestedVersion " = = "" ] ] ; then
version = $( echo " ${ releaseJson } " | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["version"])' )
else
version = " ${ requestedVersion } "
fi
2017-04-13 15:20:33 -07:00
2024-03-30 18:31:57 +01:00
# 7.6 dropped support for CPUs lacking AVX but this came back in 8.0
2024-03-31 14:12:42 +02:00
if ! grep -q avx /proc/cpuinfo; then
if vergte " ${ version } " "7.5.99" && vergte "7.8.0" " ${ version } " ; then
2023-09-02 12:02:37 +05:30
echo " Cloudron version ${ version } requires AVX support in the CPU. No avx found in /proc/cpuinfo "
exit 1
2024-03-31 14:12:42 +02:00
else
echo " ${ YELLOW } CPU has no AVX support. MongoDB will be disabled ${ DONE } "
2023-09-02 12:02:37 +05:30
fi
fi
2024-04-28 11:31:39 +02:00
if [ [ " ${ ubuntu_version } " = = "24.04" ] ] && ! vergte " ${ version } " "8.0.0" ; then
echo " Cloudron >= 8.0.0 required for installation on Ubuntu ${ ubuntu_version } "
exit 1
fi
2018-01-31 20:14:31 -08:00
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
2016-10-25 13:05:24 -07:00
fi
2022-02-18 13:38:52 -08:00
echo " => Downloading Cloudron version ${ version } ... "
2016-12-23 18:28:18 -08:00
box_src_tmp_dir = $( mktemp -dt box-src-XXXXXX)
2017-02-16 17:32:44 -08:00
if ! $curl -sL " ${ sourceTarballUrl } " | tar -zxf - -C " ${ box_src_tmp_dir } " ; then
2016-12-23 18:28:18 -08:00
echo " Could not download source tarball. See ${ LOG_FILE } for details "
exit 1
fi
2017-01-03 14:48:37 -08:00
2023-08-21 22:26:58 +05:30
echo -n "=> Installing base dependencies (this takes some time) ..."
2022-04-01 09:48:40 -07:00
init_ubuntu_script = $( test -f " ${ box_src_tmp_dir } /scripts/init-ubuntu.sh " && echo " ${ box_src_tmp_dir } /scripts/init-ubuntu.sh " || echo " ${ box_src_tmp_dir } /baseimage/initializeBaseUbuntuImage.sh " )
if ! /bin/bash " ${ init_ubuntu_script } " & >> " ${ LOG_FILE } " ; then
2022-04-01 09:22:23 -07:00
echo " Init script failed. See ${ LOG_FILE } for details "
exit 1
2017-01-03 14:48:37 -08:00
fi
2022-04-01 09:22:23 -07:00
echo ""
2017-01-03 14:48:37 -08:00
2020-06-25 11:07:49 -07:00
# The provider flag is still used for marketplace images
2019-04-25 19:38:24 -07:00
mkdir -p /etc/cloudron
2019-07-26 08:44:37 -07:00
echo " ${ provider } " > /etc/cloudron/PROVIDER
2020-12-21 22:36:10 -08:00
[ [ ! -z " ${ setupToken } " ] ] && echo " ${ setupToken } " > /etc/cloudron/SETUP_TOKEN
2018-10-26 09:49:52 -07:00
2023-08-22 16:26:44 +05:30
echo -n " => Installing Cloudron version ${ version } (this takes some time) ... "
2019-04-25 19:38:24 -07:00
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
2016-10-25 12:47:51 -07:00
fi
2023-08-22 16:26:44 +05:30
echo ""
2016-10-25 11:27:58 +02:00
2019-07-26 22:35:44 -07:00
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
2022-05-05 16:51:18 +02:00
mysql -uroot -ppassword -e " REPLACE INTO box.settings (name, value) VALUES ('console_server_origin', ' ${ consoleServerOrigin } '); " 2>/dev/null
2019-07-26 10:49:29 -07:00
2022-03-31 23:38:54 -07:00
if [ [ -n " ${ appstoreSetupToken } " ] ] ; then
mysql -uroot -ppassword -e " REPLACE INTO box.settings (name, value) VALUES ('cloudron_id', ' ${ cloudronId } '); " 2>/dev/null
mysql -uroot -ppassword -e " REPLACE INTO box.settings (name, value) VALUES ('appstore_api_token', ' ${ appstoreApiToken } '); " 2>/dev/null
fi
2016-12-30 11:35:03 +01:00
echo -n "=> Waiting for cloudron to be ready (this takes some time) ..."
2016-10-24 14:52:30 -07:00
while true; do
echo -n "."
2020-06-05 13:50:40 -07:00
if status = $( $curl -s -f "http://localhost:3000/api/v1/cloudron/status" 2>/dev/null) ; then
2018-01-30 19:08:06 -08:00
break # we are up and running
2016-10-24 14:52:30 -07:00
fi
sleep 10
done
2022-12-24 15:04:45 +01:00
ip4 = $( curl -s --fail --connect-timeout 10 --max-time 10 https://ipv4.api.cloudron.io/api/v1/helper/public_ip | sed -n -e 's/.*"ip": "\(.*\)"/\1/p' || true )
ip6 = $( curl -s --fail --connect-timeout 10 --max-time 10 https://ipv6.api.cloudron.io/api/v1/helper/public_ip | sed -n -e 's/.*"ip": "\(.*\)"/\1/p' || true )
2022-07-27 06:54:08 +02:00
url4 = ""
url6 = ""
fallbackUrl = ""
2020-12-21 22:36:10 -08:00
if [ [ -z " ${ setupToken } " ] ] ; then
2022-07-27 06:54:08 +02:00
[ [ -n " ${ ip4 } " ] ] && url4 = " https:// ${ ip4 } "
[ [ -n " ${ ip6 } " ] ] && url6 = " https://[ ${ ip6 } ] "
[ [ -z " ${ ip4 } " && -z " ${ ip6 } " ] ] && fallbackUrl = "https://<IP>"
2020-12-21 22:36:10 -08:00
else
2022-07-27 06:54:08 +02:00
[ [ -n " ${ ip4 } " ] ] && url4 = " https:// ${ ip4 } /?setupToken= ${ setupToken } "
[ [ -n " ${ ip6 } " ] ] && url6 = " https://[ ${ ip6 } ]/?setupToken= ${ setupToken } "
[ [ -z " ${ ip4 } " && -z " ${ ip6 } " ] ] && fallbackUrl = " https://<IP>?setupToken= ${ setupToken } "
2020-12-21 22:36:10 -08:00
fi
2022-07-27 06:54:08 +02:00
echo -e " \n\n ${ GREEN } After reboot, visit one of the following URLs and accept the self-signed certificate to finish setup. ${ DONE } \n "
[ [ -n " ${ url4 } " ] ] && echo -e " * ${ GREEN } ${ url4 } ${ DONE } "
[ [ -n " ${ url6 } " ] ] && echo -e " * ${ GREEN } ${ url6 } ${ DONE } "
[ [ -n " ${ fallbackUrl } " ] ] && echo -e " * ${ GREEN } ${ fallbackUrl } ${ DONE } "
2017-01-05 20:18:57 -08:00
2017-02-07 12:42:32 +01:00
if [ [ " ${ rebootServer } " = = "true" ] ] ; then
2018-12-07 10:41:13 -08:00
systemctl stop box mysql # sometimes mysql ends up having corrupt privilege tables
2022-09-27 11:25:11 +02:00
# https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#ANSI_002dC-Quoting
read -p $'\n' "The server has to be rebooted to apply all the settings. Reboot now ? [Y/n] " yn
2018-12-07 10:41:13 -08:00
yn = ${ yn :- y }
case $yn in
2021-03-02 13:01:19 -08:00
[ Yy] * ) exitHandler; systemctl reboot; ;
2018-12-07 10:41:13 -08:00
* ) exit; ;
esac
2017-02-07 12:42:32 +01:00
fi