#!/bin/bash set -o nounset errcho() { echo "$@" >&2 } show_help() { cat << EOM rocketchatctl command line tool to install and update RocketChat server Usage: $(basename "$0") [options] [--root-url=ROOT_URL --port=PORT --letsencrypt-email=EMAIL --webserver=WEBSERVER --version=VERSION --install-node --use-mongo] Installs node, mongo, RocketChat server and optionally a webserver (Caddy or Traefik), sets up directories and permissions to use Let's Encrypt certificates. In case node or mongo already installed, it uses already installed versions though confirmation is required. For node it set $NODE_VERSION as default in your system, for mongo wiredTiger storage engine and no authentication enabled is required during installation. If you wish this script to run unattended, provide extra flags to the install option, server URL is required (--root-url). OPTIONS -h help Display this message install Install latest RocketChat server version update Update RocketChat server from current version to latest version check-updates Check for updates of RocketChat server upgrade-rocketchatctl Upgrade the rocketchatctl command line tool configure Configures RocketChat server and Let's Encrypt backup Makes a rocketchat database backup FOR UNATTENDED INSTALLATION --root-url=ROOT_URL the public URL where RocketChat server will be accessible on the Internet (REQUIRED) --port=PORT port for the RocketChat server, default value 3000 --webserver=WEBSERVER webserver to install as reverse proxy for RocketChat server, options are caddy/traefik/none (REQUIRED) --letsencrypt-email=EMAIL e-mail address to use for SSL certificates (REQUIRED if webserver is not none) --version=VERSION RocketChat server version to install, default latest --install-node in case node installed, sets node to RocketChat server recommended version, default behavoir cancel RocketChat server installation --use-mongo in case mongo installed, and storage engine configured is wiredTiger, skip mongo installation but uses systems mongo for RocketChat server database, default database name rocketchat --mongo-version=4.x.x mongo 4 version, default value is latest (supported only for Debian and Ubuntu) --bind-loopback=value value=(true|false) set to false to prevent from bind RocketChat server to loopback interface when installing a webserver (default true) --reg-token=TOKEN This value can be obtained from https://cloud.rocket.chat to automatically register your workspace during startup FOR CONFIGURE OPTION --rocketchat --root-url=ROOT_URL --port=PORT --bind-loopback=value Reconfigures RocketChat server Site-URL and port (--root-url REQUIRED) --lets-encrypt --root-url=ROOT_URL --letsencrypt-email=EMAIL --bind-loopback=value Reconfigures webserver with Let's Encrypt and RocketChat server Site-URL (--root-url and letsencrypt-email REQUIRED) FOR BACKUP OPTION --backup-dir= sets the directory for storing the backup files, default backup directory is /tmp EOM } show_short_help() { errcho "Run '$(basename "$0") -h' to see the full list of available options." } print_run_as_root_error_and_exit() { errcho "This script must be run as root. Cancelling" show_short_help exit 1 } print_input_from_pipe_error_and_exit() { errcho "This script is interactive, it can't be run with stdin piped" exit 1 } print_incorrect_parameters_error_and_exit() { errcho "Incorrect parameters passed. Select one option." echo -e "" show_help exit 2 } error_with_no_value_specified() { echo "Command line option: '--${1}' must have an assigned value." show_short_help } print_distro_not_supported_error_and_exit() { errcho "The detected Linux distribution is not supported by rocketchatctl." exit 1 } print_mongo_version_not_supported_and_exit() { errcho "The detected Linux distribution does not support choosing mongo version." exit 1 } print_check_mongo_storage_and_exit() { errcho "IMPORTANT: Starting with Rocket.Chat version 4.X, a \"wiredTiger\" storage engine is required in MongoDB. Please migrate your existing database first: https://docs.rocket.chat/installation/manual-installation/mongodb-mmap-to-wiredtiger-migration" exit 1 } print_node_version_error_and_exit() { errcho "Different nodejs version already exists in the system. Cancelling" exit 1 } print_use_mongo_for_rocketchat_error_and_exit() { errcho "mongod already exists in the system. Cancelling" exit 1 } print_mongo_storage_engine_error_and_exit() { errcho "Storage engine from previous mongo installation in your system is not wiredTiger. Cancelling" exit 1 } print_mongo_connection_failed_error_and_exit() { errcho "Connection failed to previous installed mongo in your system. Cancelling" exit 1 } print_rocketchat_installed_error_and_exit() { errcho "RocketChat server already installed. Cancelling" exit 1 } print_root_url_error_and_exit() { errcho "root-url must have an assigned value. Use --root-url= for unattended install or the configure option. Cancelling" show_short_help exit 2 } print_wrong_webserver_error_and_set_none() { webserver=none errcho "webserver assigned value should be traefik, caddy or none. Use --webserver=(traefik/caddy/none) for unattended install. Skipping webserver installation" echo -e "" } print_email_error_and_exit() { errcho "letsencrypt-email must have an assigned value if decided to install a webserver or use configure option. Cancelling" show_short_help exit 2 } print_check_updates_error_and_exit() { errcho "Could not determine if updates available for RocketChat server." exit 1 } print_update_install_failed_error_and_exit() { errcho "Something went wrong backing up current RocketChat server version before update. Cancelling" exit 1 } print_upgrade_download_rocketchatctl_error_and_exit() { errcho "Error downloading rocketchatctl. Cancelling" exit 1 } print_update_install_failed_exit() { errcho "Error in updated RocketChat server health check, restoring backup." exit 1 } print_update_backup_failed_exit() { errcho "Update failed, can't get RocketChat server API in port $PORT. Cancelling" exit 1 } print_download_traefik_error_and_exit() { errcho "Error downloading traefik. Cancelling" exit 1 } print_rocketchat_not_running_error_and_exit() { errcho "RocketChat server not running. Cancelling" exit 1 } print_rocketchat_in_latest_version_and_exit() { echo "RocketChat server already in latest version." exit 2 } print_no_webserver_installed_and_exit() { echo "Looks like you don't have either Caddy or Traefik installed." exit 2 } print_make_backup() { echo "Updates could be risky, you can use the backup option # rocketchatctl backup, to create a backup of the rocketchat database first. Please check that you have enough space in your system to store the backup." } print_check_update_change_node_version() { echo "IMPORTANT: Update from version 2.x to 3.x requires node update, if you decide to update, your node version will change from 8.x to 12.x." } print_update_available_and_exit() { echo "Current update available for RocketChat server: from $current_rocketchat_version to $latest_rocketchat_version." } print_backup_ok() { echo "Backup done successfully, check for the backup file in $backup_dir/dump_$sufix.gz. You can restore using # mongorestore --host localhost:27017 --db rocketchat --archive --gzip < $backup_dir/dump_$sufix.gz" } print_backup_dir_error_and_exit() { echo "Can't write in the backup directory: $backup_dir" exit 1 } print_node_not_updated_error_and_exit() { echo "Error: node version was not updated correctly. Cancelling ..." exit 1 } print_done() { echo "Done :)" } command_exists() { command -v "$@" > /dev/null 2>&1 } get_os_distro() { if [ -r /etc/os-release ]; then distro="$(. /etc/os-release && echo "$ID")" distro_version="$(. /etc/os-release && echo "$VERSION_ID")" distro_codename="$(. /etc/os-release && echo ${VERSION_CODENAME:-})" fi } os_supported() { get_os_distro case "$distro" in ubuntu) [[ "$distro_version" =~ (("18.04"|"19.04"|"19.10"|"20.04"|"21.04"|"21.10")) ]] || print_distro_not_supported_error_and_exit ;; debian) [[ "$distro_version" =~ (("9"|"10"|"11")) ]] || print_distro_not_supported_error_and_exit ;; centos) [[ "$distro_version" =~ (("7"|"8")) ]] || print_distro_not_supported_error_and_exit ;; *) print_distro_not_supported_error_and_exit ;; esac } mongo_version_supported() { if [[ -n $MONGO_VERSION ]]; then { [[ $distro == ubuntu ]] || [[ $distro == debian ]]; } || print_mongo_version_not_supported_and_exit fi } interactive_mongo() { if [ ${use_mongo_arg} -eq 0 ]; then read -r -p "It appears you already have mongo installed, this script will skip mongo installation but can't assure successful RocketChat server installation. Database name for RocketChat server will be rocketchat. Would you like to use your mongo installation for the RocketChat server database? (y/n) " use_mongo_answer echo -e "" [ ${use_mongo_answer} == 'y' ] && use_mongo_arg=1 || print_use_mongo_for_rocketchat_error_and_exit fi } interactive_rocketchat() { if [ -z "${ROOT_URL}" ]; then read -r -p "Enter ROOT_URL for your RocketChat server installation (for example https://www.mydomain.com) : " ROOT_URL echo -e "" fi [ -z ${ROOT_URL} ] && print_root_url_error_and_exit ROOT_URL=$(echo "$ROOT_URL" | tr '[:upper:]' '[:lower:]') } interactive_webserver() { if [ ${webserver_arg} -eq 0 ]; then read -r -p "Select a webserver to use as a reverse proxy for your RocketChat server installation [none]: (traefik/caddy/none) " webserver echo -e "" fi webserver=$(echo "$webserver" | tr '[:upper:]' '[:lower:]') ! [[ ${webserver} =~ ((traefik)|(caddy)|(none)) ]] && print_wrong_webserver_error_and_set_none } interactive_mail() { if [ ${webserver} != "none" ]; then if [ "${rocket_mail_arg}" -eq 0 ]; then read -r -p "Enter e-mail for Let's Encrypt certificates: " rocket_mail echo -e "" fi [ -z ${rocket_mail} ] && print_email_error_and_exit rocket_mail=$(echo "$rocket_mail" | tr '[:upper:]' '[:lower:]') fi } check_arguments_unattended_install() { while [ $# -gt 0 ]; do IFS='=' read -r -a args <<< $1 case "${args[0]}" in --install-node) install_node_arg=1 ;; --use-mongo) use_mongo_arg=1 ;; --webserver) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi webserver="${args[1]}" webserver_arg=1 ;; --letsencrypt-email) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi rocket_mail="${args[1]}" rocket_mail_arg=1 ;; --root-url) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi ROOT_URL="${args[1]}" ;; --port) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi PORT="${args[1]}" ;; --version) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi VERSION="${args[1]}" ROCKETCHAT_DOWNLOAD_URL="https://releases.rocket.chat/$VERSION/download" ;; --mongo-version) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi MONGO_VERSION="${args[1]}" ;; --bind-loopback) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi bind_loopback="${args[1]}" ;; --reg-token) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi REG_TOKEN="${args[1]}" ;; *) show_help exit 2 ;; esac shift done } apt_configure_mongo() { local key_url="https://www.mongodb.org/static/pgp/server-$MONGO_VERSION_WITHOUT_PATCH.asc" local key_file="/usr/share/keyrings/mongodb-org-$MONGO_VERSION_WITHOUT_PATCH.gpg" local repo_file="/etc/apt/sources.list.d/mongodb-org-$MONGO_VERSION_WITHOUT_PATCH.list" declare -A repo=([ubuntu]=multiverse [debian]=main) local repo_url="deb [ arch=amd64 signed-by=$key_file ] https://repo.mongodb.org/apt/$distro $distro_codename/mongodb-org/$MONGO_VERSION_WITHOUT_PATCH ${repo[$distro]}" curl -fsSL "$key_url" | gpg --dearmor -o "$key_file" echo $repo_url > $repo_file } yum_configure_mongo() { local yum_mongo_url="https://repo.mongodb.org/yum/redhat/$distro_version/mongodb-org/$MONGO_VERSION_WITHOUT_PATCH/x86_64/" local yum_key="https://www.mongodb.org/static/pgp/server-$MONGO_VERSION_WITHOUT_PATCH.asc" cat << EOF | tee -a /etc/yum.repos.d/mongodb-org-$MONGO_VERSION_WITHOUT_PATCH.repo [mongodb-org-$MONGO_VERSION_WITHOUT_PATCH] name=MongoDB Repository baseurl=$yum_mongo_url gpgcheck=1 enabled=1 gpgkey=$yum_key EOF } apt_install_mongo() { apt-get update -qqq local pkg="mongodb-org" [[ -n $MONGO_VERSION ]] && pkg+="-$MONGO_VERSION" || true apt install $pkg -y printf "mongodb-org hold" | dpkg --set-selections printf "mongodb-org-server hold" | dpkg --set-selections printf "mongodb-org-shell hold" | dpkg --set-selections printf "mongodb-org-mongos hold" | dpkg --set-selections printf "mongodb-org-tools hold" | dpkg --set-selections } yum_install_mongo() { local pkg="mongodb-org" [[ -n $MONGO_VERSION ]] && pkg+="-$MONGO_VERSION" || true yum install -y $pkg } configure_mongo() { sed -i "s/^# engine:/ engine: wiredTiger/" /etc/mongod.conf sed -i "s/^#replication:/replication:\n replSetName: rs01/" /etc/mongod.conf if ! grep -Fq " fork: true" /etc/mongod.conf; then sed -i "/^processManagement:/a\ fork: true" /etc/mongod.conf fi } create_mongo_systemd_file() { mkdir /etc/systemd/system/mongod.service.d/ cat << EOF | tee -a /etc/systemd/system/mongod.service.d/mongod.conf [Service] Type=oneshot RemainAfterExit=yes EOF } initiate_and_start_mongo() { systemctl daemon-reload systemctl enable mongod systemctl start mongod if [[ -n $(pgrep mongod) ]]; then mongo --eval "printjson(rs.initiate())" >&2 fi } install_rocketchat() { curl -L $ROCKETCHAT_DOWNLOAD_URL -o /tmp/rocket.chat.tgz tar -xzf /tmp/rocket.chat.tgz -C /tmp cd /tmp/bundle/programs/server && npm install # add npm install node-gyp@5.0.1 to fix 4 moderate severity vulnerabilities mv /tmp/bundle $ROCKETCHAT_DIR rm /tmp/rocket.chat.tgz } set_rocketchat_user_and_permissions() { id -un rocketchat 1> /dev/null 2>&1 if [ $? -ne 0 ]; then useradd -M rocketchat && usermod -L rocketchat fi chown -R rocketchat:rocketchat $ROCKETCHAT_DIR } create_rocketchat_systemd_file() { if [ -f /lib/systemd/system/rocketchat.service ]; then true > /lib/systemd/system/rocketchat.service fi if [[ $webserver != "none" ]] && $bind_loopback; then BIND_IP="127.0.0.1" fi cat << EOF | tee -a /lib/systemd/system/rocketchat.service [Unit] Description=The Rocket.Chat server After=network.target remote-fs.target nss-lookup.target mongod.service [Service] ExecStart=$NODE_BIN /opt/Rocket.Chat/main.js StandardOutput=syslog StandardError=syslog SyslogIdentifier=rocketchat User=rocketchat Environment=MONGO_URL=$MONGO_URL Environment=MONGO_OPLOG_URL=$MONGO_OPLOG_URL Environment=ROOT_URL=$ROOT_URL Environment=PORT=$PORT Environment=BIND_IP=$BIND_IP Environment=DEPLOY_PLATFORM=rocketchatctl Environment=REG_TOKEN=$REG_TOKEN [Install] WantedBy=multi-user.target EOF } start_rocketchat() { systemctl daemon-reload systemctl enable rocketchat systemctl start rocketchat } restart_rocketchat() { systemctl daemon-reload systemctl restart rocketchat } get_rocketchat_domain() { rocket_domain=$(echo $ROOT_URL | awk -F[/:] '{print $4}') } get_mongo_storage_engine() { storage=$(mongo --eval "printjson(db.serverStatus().storageEngine)" | grep name | awk -F: '{print $2}' | tr -d , | tr -d \" | tr -d '[:space:]') } traefik_config_file() { if [ -f /etc/traefik/traefik.toml ]; then sufix=$(date +%s) cp /etc/traefik/traefik.toml /etc/traefik/traefik.toml.$sufix if [ $? == 0 ]; then true > /etc/traefik/traefik.toml fi fi cat << EOF | tee -a /etc/traefik/traefik.toml defaultEntryPoints = ["https","http"] [entryPoints] [entryPoints.http] address = ":80" [entryPoints.http.redirect] entryPoint = "https" [entryPoints.https] address = ":443" [entryPoints.https.tls] [file] [backends] [backends.rocketchat] [backends.rocketchat.servers.server1] url = "http://localhost:$PORT" [frontends] [frontends.rocketchat] backend = "rocketchat" passHostHeader = true [frontends.rocketchat.routes.route0] rule = "Host:$rocket_domain" [acme] email = "$rocket_mail" storage = "etc/traefik/acme/acme.json" caServer = "https://acme-v02.api.letsencrypt.org/directory" entryPoint = "https" [acme.httpChallenge] entryPoint = "http" [[acme.domains]] main = "$rocket_domain" EOF } traefik_systemd_file() { cat << EOF | tee -a /lib/systemd/system/traefik.service [Unit] Description=Traefik Documentation=https://docs.traefik.io After=network-online.target AssertFileIsExecutable=/usr/local/bin/traefik AssertPathExists=/etc/traefik/traefik.toml [Service] User=traefik Group=traefik AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE # configure service behavior Type=notify ExecStart=/usr/local/bin/traefik --configFile=/etc/traefik/traefik.toml Restart=on-abnormal WatchdogSec=1s # lock down system access # prohibit any operating system and configuration modification ProtectSystem=full # create separate, new (and empty) /tmp and /var/tmp filesystems PrivateTmp=true # make /home directories inaccessible ProtectHome=true # turns off access to physical devices (/dev/...) PrivateDevices=false # make kernel settings (procfs and sysfs) read-only #ProtectKernelTunables=true # make cgroups /sys/fs/cgroup read-only #ProtectControlGroups=true # allow writing of acme.json ReadWritePaths=/etc/traefik/acme/acme.json ReadWritePaths=/etc/traefik/acme # depending on log and entrypoint configuration, you may need to allow writing to other paths, too # limit number of processes in this unit #LimitNPROC=1 [Install] WantedBy=multi-user.target EOF } install_traefik() { curl -L $TRAEFIK_DOWNLOAD_URL -o /tmp/traefik if [[ $? == 0 ]]; then mv /tmp/traefik /usr/local/bin/ else print_download_traefik_error_and_exit fi useradd -r -s /bin/false -U -M traefik chown root:root /usr/local/bin/traefik chmod 755 /usr/local/bin/traefik setcap 'cap_net_bind_service=+ep' /usr/local/bin/traefik mkdir /etc/traefik mkdir /etc/traefik/acme touch /etc/traefik/acme/acme.json chown -R root:root /etc/traefik chown -R traefik:traefik /etc/traefik/acme chmod 600 /etc/traefik/acme/acme.json get_rocketchat_domain traefik_config_file chown root:root /etc/traefik/traefik.toml chmod 644 /etc/traefik/traefik.toml traefik_systemd_file chown root:root /lib/systemd/system/traefik.service chmod 644 /lib/systemd/system/traefik.service systemctl daemon-reload systemctl enable traefik.service systemctl start traefik.service } caddy_config_file() { if [ -f /etc/caddy/Caddyfile ]; then sufix=$(date +%s) cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.$sufix if [ $? == 0 ]; then true > /etc/caddy/Caddyfile fi fi cat << EOF | tee -a /etc/caddy/Caddyfile $ROOT_URL proxy / localhost:$PORT { websocket transparent } EOF } caddy_systemd_file() { if [ -f /lib/systemd/system/caddy.service ]; then sufix=$(date +%s) cp /lib/systemd/system/caddy.service /etc/caddy/caddy.service.$sufix if [ $? == 0 ]; then true > /lib/systemd/system/caddy.service fi fi cat << EOF | tee -a /lib/systemd/system/caddy.service [Unit] Description=Caddy HTTP/2 web server Documentation=https://caddyserver.com/docs After=network-online.target Wants=network-online.target systemd-networkd-wait-online.service [Service] Restart=on-abnormal ; User and group the process will run as. User=caddy Group=caddy ; Letsencrypt-issued certificates will be written to this directory. Environment=CADDYPATH=/etc/caddy/ssl ; Always set "-root" to something safe in case it gets forgotten in the Caddyfile. ExecStart=/usr/local/bin/caddy -log stdout -agree=true -conf=/etc/caddy/Caddyfile -email=$rocket_mail ExecReload=/bin/kill -USR1 \$MAINPID ; Use graceful shutdown with a reasonable timeout KillMode=mixed KillSignal=SIGQUIT TimeoutStopSec=5s ; Limit the number of file descriptors. LimitNOFILE=1048576 ; Unmodified caddy is not expected to use more than that. LimitNPROC=512 ; Use private /tmp and /var/tmp, which are discarded after caddy stops. PrivateTmp=true ; Use a minimal /dev (May bring additional security if switched to 'true', but it may not work on Raspberry Pi's or other devices, so it has been disabled in this dist.) PrivateDevices=false ; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. ProtectHome=true ; Make /usr, /boot, /etc and possibly some more folders read-only. ProtectSystem=full ; … except /etc/ssl/caddy, because we want Letsencrypt-certificates there. ; This merely retains r/w access rights, it does not add any new. Must still be writable on the host! ReadWriteDirectories=/etc/caddy/ssl ; The following additional security directives only work with systemd v229 or later. ; They further restrict privileges that can be gained by caddy. Uncomment if you like. ; Note that you may have to add capabilities required by any plugins in use. CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE ;NoNewPrivileges=true [Install] WantedBy=multi-user.target EOF } install_caddy() { curl -L $CADDY_DOWNLOAD_URL -o /tmp/caddy.tar.gz tar -xzf /tmp/caddy.tar.gz -C /tmp mv /tmp/caddy /usr/local/bin rm /tmp/caddy.tar.gz chown root:root /usr/local/bin/caddy chmod 755 /usr/local/bin/caddy setcap cap_net_bind_service=+ep $(which caddy) mkdir /etc/caddy mkdir /etc/caddy/ssl chown -R root:root /etc/caddy useradd -r -s /bin/false -U -M caddy chown -R caddy:caddy /etc/caddy/ssl caddy_config_file chown root:root /etc/caddy/Caddyfile chmod 644 /etc/caddy/Caddyfile caddy_systemd_file chown root:root /lib/systemd/system/caddy.service chmod 644 /lib/systemd/system/caddy.service systemctl daemon-reload systemctl enable caddy.service systemctl start caddy.service } get_rocketchat_latest_version() { if ! command_exists curl; then apt-get install -y curl fi latest_rocketchat_version=$(curl $ROCKET_VERSION_INFO_URL 2> /dev/null | grep -w tag | awk -F: '{print $2}' | awk -F'"' '{print $2}') } get_rocketchat_current_version() { if systemctl status rocketchat > /dev/null 2>&1; then PORT=$(cat /lib/systemd/system/rocketchat.service | grep PORT | awk -F= '{print $3}') current_rocketchat_version=$(curl http://localhost:$PORT/api/info 2> /dev/null | cut -d\" -f4) else print_rocketchat_not_running_error_and_exit fi } version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1" } rocketchat_health_check() { local count=0 while ! ([ $rocket_healthy -eq 1 ] || [ $count -eq 150 ]); do get_rocketchat_current_version [[ $current_rocketchat_version =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] && rocket_healthy=1 sleep 2 ((count++)) echo "Waiting up to 5 minutes for RocketChat server to be active ... $(($count * 2))" done } do_setup() { # checks system before install: node, mongo, rocketchat # assigns user's values # update environment and install necessary packages # TODO: add gnupg; find out the package name for deb & rpm case "$distro" in ubuntu | debian) apt update -qqq && apt install curl jq build-essential graphicsmagick -yqqq ;; centos) yum -y check-update || true yum install gcc-c++ make curl -y yum install -y epel-release yum install GraphicsMagick jq -y ;; esac # node get_supported_node_version # mongodb get_supported_mongo_version # mongo if command_exists mongod; then if [[ -n $(pgrep mongod) ]]; then if ! mongo --eval "printjson(db.getUsers())" | grep -q "Error: command usersInfo requires authentication"; then get_mongo_storage_engine if [ $storage == "wiredTiger" ]; then interactive_mongo else print_mongo_storage_engine_error_and_exit fi else print_mongo_connection_failed_error_and_exit fi else print_mongo_connection_failed_error_and_exit fi else # mongodb doesn't exist mongo_version_supported install_mongo=1 fi # rocketchat systemctl status rocketchat > /dev/null 2>&1 if [ $? -eq 4 ]; then interactive_rocketchat else print_rocketchat_installed_error_and_exit fi # webserver interactive_webserver interactive_mail } do_install_mongo() { if ((install_mongo)); then case "$distro" in ubuntu | debian) apt_configure_mongo apt_install_mongo configure_mongo create_mongo_systemd_file initiate_and_start_mongo ;; centos) yum_configure_mongo yum_install_mongo configure_mongo create_mongo_systemd_file initiate_and_start_mongo ;; *) print_distro_not_supported_error_and_exit ;; esac fi } do_install() { # install node out here since it doesn't depend on the distribution do_install_node do_install_mongo # rocketchat if ! [ -d ${ROCKETCHAT_DIR} ]; then install_rocketchat set_rocketchat_user_and_permissions create_rocketchat_systemd_file start_rocketchat else print_rocketchat_installed_error_and_exit fi # webserver case "$webserver" in traefik) install_traefik ;; caddy) install_caddy ;; esac print_done } do_check_updates() { get_rocketchat_latest_version get_rocketchat_current_version print_make_backup get_mongo_storage_engine if ([[ $storage == "mmapv1" ]] && [[ ${latest_rocketchat_version:0:1} -eq 4 ]]); then print_check_mongo_storage_and_exit elif version_gt $latest_rocketchat_version $current_rocketchat_version; then print_update_available_and_exit elif [ $latest_rocketchat_version == $current_rocketchat_version ]; then print_rocketchat_in_latest_version_and_exit else print_check_updates_error_and_exit fi } do_update() { do_update_node mv $ROCKETCHAT_DIR $ROCKETCHAT_DIR_UPDATE if [ $? == 0 ]; then systemctl stop rocketchat install_rocketchat set_rocketchat_user_and_permissions systemctl start rocketchat rocketchat_health_check if [ $rocket_healthy -eq 1 ]; then rm -rf $ROCKETCHAT_DIR_UPDATE echo "RocketChat server updated to latest version :)" exit 0 else rm -rf $ROCKETCHAT_DIR && mv $ROCKETCHAT_DIR_UPDATE $ROCKETCHAT_DIR systemctl restart rocketchat print_update_install_failed_exit fi else print_update_backup_failed_exit fi } check_node_major_version() { major_version=${NODE_VERSION%%.*} # Prefix up to the first dot [[ $(node -v) =~ ^v$major_version\.[0-9]+\.[0-9]+$ ]] } do_update_node() { get_supported_node_version local currently_used_node_path=$(awk -F'[= ]' '$1 == "ExecStart" {print $2}' /lib/systemd/system/rocketchat.service) [[ $($currently_used_node_path --version 2> /dev/null) == "v$NODE_VERSION" ]] && return do_install_node sed -Ei "s@^(ExecStart)=$currently_used_node_path {1,}(.+)\$@\1=$NODE_BIN \2@" /lib/systemd/system/rocketchat.service check_node_major_version || print_node_not_updated_error_and_exit } do_upgrade_rocketchatctl() { curl -L $ROCKETCHATCTL_DOWNLOAD_URL -o /tmp/rocketchatctl if [ $? != 0 ]; then print_upgrade_download_rocketchatctl_error_and_exit fi if cmp -s $ROCKETCHATCTL_DIRECTORY/rocketchatctl /tmp/rocketchatctl; then rm /tmp/rocketchatctl echo "rocketchatctl already in latest version." exit 0 else mv /tmp/rocketchatctl $ROCKETCHATCTL_DIRECTORY/ chmod 755 $ROCKETCHATCTL_DIRECTORY/rocketchatctl echo "rocketchatctl upgraded to latest version." exit 0 fi } check_arguments_configure() { case $1 in --rocketchat) configure_rocketchat=1 shift while [ $# -gt 0 ]; do IFS='=' read -r -a args <<< $1 case "${args[0]}" in --root-url) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi ROOT_URL="${args[1]}" root_url_arg=1 ;; --port) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi PORT="${args[1]}" ;; --bind-loopback) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi bind_loopback="${args[1]}" bind_loopback_arg=1 ;; *) show_help exit 2 ;; esac shift done ;; --lets-encrypt) configure_webserver=1 shift while [ $# -gt 0 ]; do IFS='=' read -r -a args <<< $1 case "${args[0]}" in --root-url) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi ROOT_URL="${args[1]}" root_url_arg=1 ;; --letsencrypt-email) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi rocket_mail="${args[1]}" rocket_mail_arg=1 ;; --bind-loopback) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi bind_loopback="${args[1]}" bind_loopback_arg=1 ;; *) show_help exit 2 ;; esac shift done ;; *) show_help exit 2 ;; esac } update_rocketchat_systemd_file() { ROOT_URL_REPLACED=$(echo ${ROOT_URL//\//\\/}) sed -i "/^Environment=ROOT_URL=/c\Environment=ROOT_URL=$ROOT_URL_REPLACED" /lib/systemd/system/rocketchat.service sed -i "/^Environment=PORT=/c\Environment=PORT=$PORT" /lib/systemd/system/rocketchat.service if [ "${bind_loopback_arg}" -eq 1 ] || [ "${configure_webserver}" -eq 1 ]; then [[ $bind_loopback == true ]] && BIND_IP=127.0.0.1 || BIND_IP=0.0.0.0 sed -i "/^Environment=BIND_IP=/c\Environment=BIND_IP=$BIND_IP" /lib/systemd/system/rocketchat.service fi } do_configure_rocketchat() { if [ "${root_url_arg}" -eq 1 ]; then siteurl_update_query="db.rocketchat_settings.update({\"_id\":\"Site_Url\"}, {\$set:{\"value\": \"${ROOT_URL}\"}});" mongo_cmd_str=$(echo "mongo rocketchat --eval '${siteurl_update_query}'") eval ${mongo_cmd_str} update_rocketchat_systemd_file restart_rocketchat else print_root_url_error_and_exit fi } get_webserver_installed() { if ([ -f /etc/caddy/Caddyfile ] && [ ! -f /etc/traefik/traefik.toml ]); then webserver=caddy elif ([ ! -f /etc/caddy/Caddyfile ] && [ -f /etc/traefik/traefik.toml ]); then webserver=traefik else print_no_webserver_installed_and_exit fi } do_configure() { if [ "${configure_rocketchat}" -eq 1 ]; then get_webserver_installed do_configure_rocketchat elif ([ "${configure_webserver}" -eq 1 ] && [ "${rocket_mail_arg}" -eq 1 ]); then get_webserver_installed PORT=$(cat /lib/systemd/system/rocketchat.service | grep PORT | awk -F= '{print $3}') do_configure_rocketchat case "$webserver" in traefik) get_rocketchat_domain traefik_config_file systemctl restart traefik.service ;; caddy) caddy_config_file caddy_systemd_file systemctl daemon-reload systemctl restart caddy.service ;; *) print_no_webserver_installed_and_exit ;; esac else print_email_error_and_exit fi } check_arguments_backup() { while [ $# -gt 0 ]; do IFS='=' read -r -a args <<< $1 case "${args[0]}" in --backup-dir) if [ ${#args[@]} -ne 2 ]; then error_with_no_value_specified "${args[0]}" exit 2 fi backup_dir="${args[1]}" ;; *) show_help exit 2 ;; esac shift done } check_bkup_dir() { touch "$backup_dir/testfile_$sufix.txt" /dev/null 2>&1 && rm "$backup_dir/testfile_$sufix.txt" || print_backup_dir_error_and_exit } do_backup() { check_bkup_dir systemctl stop rocketchat if mongodump --host localhost:27017 --db rocketchat --archive --gzip > "$backup_dir/dump_$sufix.gz"; then systemctl start rocketchat [[ -s $backup_dir/dump_$sufix.gz ]] && print_backup_ok || print_backup_error_and_exit else print_empty_backup_error_and_exit fi } do_install_nvm() { # if n not found, install nvm to manage node versions mkdir /opt/nvm local nvm_version=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq .tag_name -r) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh | NVM_DIR=/opt/nvm bash source /opt/nvm/nvm.sh # ensure nvm and other node commands are available for the current session } get_supported_node_version() { # if api returns nodeVersion, then use that # else it's pre node14 days local node_version=$(curl -s "$ROCKET_VERSION_INFO_URL" | jq -r '.nodeVersion // empty') NODE_VERSION=${node_version:-v12.22.8} } get_supported_mongo_version() { # if mongo_version was passed to the cli if [[ -n $MONGO_VERSION ]]; then local mongo_version=(${MONGO_VERSION/./ }) MONGO_VERSION_WITHOUT_PATCH="${mongo_version[0]}.${mongo_version[1]}" return fi # jq query takes the latest supported mongo version local mongo_version=$( curl -s "$ROCKET_VERSION_INFO_URL" | jq -r '.compatibleMongoVersions | sort_by(.) | reverse | .[0] // empty' ) # this is bashy - fyi # so don't do `sh rocketchatctl` # either `bash rocketchatctl` or `./rocketchatctl` mongo_version=(${mongo_version/./ }) MONGO_VERSION_MAJOR=${mongo_version[0]} MONGO_VERSION_MINOR=${mongo_version[1]} MONGO_VERSION_WITHOUT_PATCH="$MONGO_VERSION_MAJOR.$MONGO_VERSION_MINOR" } do_install_node() { # if called just to update, node version check's been already done if [[ ${FUNCNAME[1]} != "do_update_node" ]]; then if command_exists node; then if [[ $(node --version 2> /dev/null) == "v$NODE_VERSION" ]]; then NODE_BIN=$(which node) return else ((install_node_arg)) || print_node_version_error_and_exit fi fi fi if command_exists n; then # since previously this script used n n $NODE_VERSION NODE_BIN=$(n which $NODE_VERSION) else command_exists nvm || { test -s /opt/nvm/nvm.sh && source $_ || do_install_nvm; } nvm i $NODE_VERSION NODE_BIN=$(nvm which $NODE_VERSION) fi } main() { local VERSION="latest" # TODO: make this dynamic # local -r NODE_VERSION="v12.18.4" local NODE_VERSION="" local NODE_BIN='' # local -r NPM_VERSION="6.14.8" local MONGO_VERSION="" local -r MONGO_URL="mongodb://localhost:27017/rocketchat?replicaSet=rs01" local -r MONGO_OPLOG_URL="mongodb://localhost:27017/local?replicaSet=rs01" local ROCKETCHAT_DOWNLOAD_URL="https://releases.rocket.chat/$VERSION/download" local -r ROCKETCHAT_DIR="/opt/Rocket.Chat" local -r ROCKETCHAT_DIR_UPDATE="/opt/Rocket.Chat.bkup" local -r ROCKET_VERSION_INFO_URL="https://releases.rocket.chat/latest/info" local -r ROCKETCHATCTL_DOWNLOAD_URL="https://raw.githubusercontent.com/RocketChat/install.sh/master/rocketchatctl" local -r ROCKETCHATCTL_DIRECTORY="/usr/local/bin" local -r ROCKETCHATCTL_TEMP_DIRECTORY="/tmp" local -r TRAEFIK_DOWNLOAD_URL="https://github.com/containous/traefik/releases/download/v1.7.20/traefik" local -r CADDY_DOWNLOAD_URL="https://github.com/mholt/caddy/releases/download/v1.0.0/caddy_v1.0.0_linux_amd64.tar.gz" local distro="" local distro_version="" local install_mongo=0 local rocket_healthy=0 local dry_run=0 local ROOT_URL="" local PORT=3000 local BIND_IP="0.0.0.0" local REG_TOKEN="" local bind_loopback=true local webserver=none local use_mongo_arg=0 local webserver_arg=0 local rocket_mail_arg=0 local bind_loopback_arg=0 local rocket_domain="" local configure_rocketchat=0 local configure_webserver=0 local root_url_arg=0 local sufix=$(date +%s) local backup_dir="/tmp" [ ${EUID} -ne 0 ] && print_run_as_root_error_and_exit [ $# -lt 1 ] && print_incorrect_parameters_error_and_exit [[ -t 0 ]] || print_input_from_pipe_error_and_exit case $1 in --help | -h) show_help ;; install) os_supported shift check_arguments_unattended_install "$@" do_setup do_install ;; check-updates) do_check_updates ;; update) os_supported if do_check_updates; then do_update fi ;; upgrade-rocketchatctl) do_upgrade_rocketchatctl ;; configure) shift check_arguments_configure "$@" do_configure ;; backup) shift check_arguments_backup "$@" do_backup ;; *) show_help exit 2 ;; esac } main "$@"