#!/bin/bash set -e # WISP.gg install script - ran with: # bash <(wget -qO- https://wisp.gg/install?license_key=&instance_url=) # # NOTE: This also installs a CLI utility from https://wisp.gg/install-wisp-cli to /usr/local/bin/wisp (and its dependency, jq) if [[ "$EUID" -ne 0 ]]; then echo -e "\e[31m[FATAL]\e[39m Currently this script requires being ran as root user - please try again as root." exit 1 fi echo -e "\n\nINSTALL LOG FOR WISP: $(date --rfc-3339=seconds)\n" >> /var/log/wisp-install.log info() { echo -e "\e[34m[INFO]\e[39m $1" echo "[INFO] $1" >> /var/log/wisp-install.log } debug() { if [[ ! -z "$DEBUG" ]]; then echo -e "\e[96m[DEBUG]\e[39m $1" fi echo "[DEBUG] $1" >> /var/log/wisp-install.log } warn() { echo -e "\e[33m[WARNING]\e[39m $1" echo "[WARNING] $1" >> /var/log/wisp-install.log } fatal() { echo -e "\e[31m[FATAL]\e[39m $1" echo "[FATAL] $1" >> /var/log/wisp-install.log exit 1 } prompt() { # (question, variable, max len, min len, nice attribute name) echo "" local valid=0 while [ "$valid" = 0 ]; do read -ep "$1`echo $'\n> '`" "$2" local value="${!2}" local len="${#value}" if [ ! -z "$4" ]; then if [ $len -lt $4 ] || [ $len -gt $3 ]; then echo "Invalid $5." echo "" else valid=1 fi else valid=1 fi done echo "" } prompt_yn() { # (question, variable) echo "" local TMP_YN local valid=0 while [ "$valid" = 0 ]; do read -ep "$1 [Y/n]`echo $'\n> '`" "TMP_YN" case "$TMP_YN" in y|Y ) declare -g "$2=1" valid=1;; n|N ) declare -g "$2=0" valid=1;; "" ) declare -g "$2=1" valid=1;; * ) echo "Invalid option, only y/n is accepted." echo "";; esac done echo "" } check_for_wings() { debug "Checking for wings presence..." if [[ -d /srv/daemon ]]; then debug "Default installation location for wings 0.7 present." WORKING_DIR="/srv/daemon" fi if [[ -d /etc/pterodactyl ]]; then debug "Default installation location for wings 1.0 present." WORKING_DIR="/etc/pterodactyl" fi local WORKING_DIR if [[ -f /etc/systemd/system/wings.service ]]; then debug "Wings systemd service present." local TMP_WORKING_DIR=$(cat /etc/systemd/system/wings.service | grep WorkingDirectory) WORKING_DIR=${TMP_WORKING_DIR:17} fi if [[ ! -z "$WORKING_DIR" ]]; then debug "The working directory for the daemon is $WORKING_DIR." if [[ -f "$WORKING_DIR/config.yml" ]]; then debug "Files are intact for 1.0, deciding that daemon is installed." declare -g "$1=$WORKING_DIR" elif [[ -f "$WORKING_DIR/src/index.js" ]]; then debug "Files are intact for 0.7, deciding that daemon is installed." declare -g "$1=$WORKING_DIR" else debug "Files are not intact, assuming no wings present on the system." fi else debug "Couldn't find wings directory, assuming no wings present on the system." fi } stop_wings() { debug "Checking if wings is running..." if [[ "$(ps aux | grep "node $WINGS_DIR" | wc -l)" -gt "1" || "$(ps aux | grep "wings" | wc -l)" -gt "1" ]]; then debug "Wings is running, finding the method it runs with..." if [[ -f "/etc/systemd/system/wings.service" ]]; then info "Stopping wings systemd service..." systemctl stop wings systemctl disable wings else fatal "Couldn't find out how wings is running, please stop it and run this script again." fi else debug "Wings isn't running." fi } stop_sftp() { debug "Checking if sftp-server is running..." if [[ "$(ps aux | grep sftp-server | wc -l)" -gt "1" ]]; then debug "Sftp-server is running, finding the method it runs with..." if [[ -f "/etc/systemd/system/pterosftp.service" ]]; then info "Stopping sftp-server systemd service..." systemctl stop pterosftp systemctl disable pterosftp else if [[ "$(netstat -tulpn | grep :2022 | wc -l)" -gt "0" ]]; then fatal "Couldn't find out how sftp-server is running, please stop it and run this script again." fi fi else debug "Sftp-server isn't running." fi } get_wings_data_dir() { if [[ -f "$WINGS_DIR/config.yml" ]]; then RES=$(cat $WINGS_DIR/config.yml | egrep "data: [\"]?(.*?)[\"]?" | egrep -o "/(.*?)") declare -g "$1=$RES" elif [[ -f "$WINGS_DIR/config/core.json" ]]; then RES=$(cat $WINGS_DIR/config/core.json | egrep "\"path\": \"/(.*?)\"" | egrep -o "/(.*?)" | sed "s/\",//") declare -g "$1=$RES" else fatal "Couldn't figure out wings' data directory from $WINGS_DIR." fi } check_for_wisp() { if [[ -d "/var/lib/wisp" ]]; then declare -g "$1=1" else declare -g "$1=0" fi } get_timezone_file() { if [[ -f "/etc/timezone" ]]; then declare -g "$1=/etc/timezone" elif [[ -f "/etc/localtime" ]]; then declare -g "$1=/etc/localtime" else fatal "Couldn't find any supported timezone file. Contact WISP support." fi } install_wisp_cli() { info "Installing dependencies for CLI utility..." if [[ ! -x "$(command -v jq)" ]]; then wget -qO- https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 > /usr/local/bin/jq chmod +x /usr/local/bin/jq ln -sf /usr/local/bin/jq /usr/bin fi info "Installing the CLI utility..." wget -qO- https://wisp.gg/install-wisp-cli > /usr/local/bin/wisp chmod +x /usr/local/bin/wisp ln -sf /usr/local/bin/wisp /usr/bin } install_wisp() { # TODO: Should install curl if not present debug "Checking if docker is installed..." if [[ ! -x "$(command -v docker)" ]]; then info "Missing dependency Docker, running its install script..." curl -sSL https://get.docker.com/ | CHANNEL=stable bash else debug "Docker is already installed, skipping installation of it." fi # TODO: Check kernel version (and if virtualization packages are installed) info "Starting and enabling docker..." systemctl start docker systemctl enable docker info "Making sure Docker works... (if this script suddenly stops, try 'docker run --rm hello-world' manually)" docker run --rm hello-world > /dev/null 2>&1 if [[ "$?" -ne 0 ]]; then fatal "Failed verifying docker support of this machine. The command 'docker run hello-world' fails, please look into this and re-run the script after it works." fi if [[ ! -x "$(command -v docker-compose)" ]]; then info "Missing dependency docker-compose, running its install script..." curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose > /dev/null 2>&1 if [[ "$?" -ne 0 ]]; then fatal "Failed installing docker-compose possibly due to unsupported system." fi chmod +x /usr/local/bin/docker-compose ln -sf /usr/local/bin/docker-compose /usr/bin else debug "docker-compose is already installed, skipping installation of it." fi info "Pulling required docker images (this may take a while)..." debug "Pulling bootstrapper..." docker pull quay.io/wisp/bootstrapper:latest debug "Pulling modmanager..." docker pull quay.io/wisp/modmanager:latest info "Creating required files for running WISP..." mkdir -p /var/lib/wisp/daemon/config /home/wisp debug "Retrieving timezone file..." get_timezone_file "TIMEZONE_FILE" debug "Timezone file is located in $TIMEZONE_FILE." debug "Creating docker-compose.yml..." cat < /var/lib/wisp/docker-compose.yml version: '3' services: bootstrapper: image: quay.io/wisp/bootstrapper:latest volumes: - /var/lib/wisp:/app - /var/run/docker.sock:/var/run/docker.sock - /var/lib/docker/containers:/var/lib/docker/containers - /home/wisp:/home/wisp - /etc/hostname:/etc/orig_hostname:ro - /tmp:/tmp - $TIMEZONE_FILE:$TIMEZONE_FILE:ro ports: - '8080:8080' - '2022:2022' environment: - MIGRATE_PTERO=$MIGRATE_PTERO - NODE_ENV=production EOT debug "Creating projects.json..." cat < /var/lib/wisp/projects.json { "daemon": { "type": "nodejs", "existency": "daemon.wisp", "startup": "bash -c 'node daemon.wisp \"\$@\" | node_modules/bunyan/bin/bunyan -o short' -- --license-key $LICENSE_KEY --panel-url $INSTANCE_URL", "running": "WISP Daemon is now listening for ", "timeout": 60000, "fatal": true }, "sftp-server": { "type": "golang", "existency": "sftp-server", "startup": "./sftp-server -config-path=../daemon/config/wisp.yaml", "running": "server listener registered", "timeout": 15000, "fatal": false } } EOT debug "Creating wisp.service..." cat < /etc/systemd/system/wisp.service [Unit] Description=WISP Bootstrapper (https://wisp.gg) Requires=docker.service After=docker.service [Service] Restart=always User=root WorkingDirectory=/var/lib/wisp ExecStartPre=$(which docker-compose) pull ExecStart=$(which docker-compose) up ExecStop=$(which docker-compose) down -v [Install] WantedBy=multi-user.target EOT info "Reloading systemd and enabling WISP..." systemctl daemon-reload systemctl enable wisp info "Starting WISP..." systemctl start wisp } main() { debug "Script loaded, starting the install process..." if [[ ! -x "$(command -v curl)" ]]; then fatal "Couldn't find curl installed on the system - please install it first and rerun the script." fi check_for_wings "WINGS_DIR" if [[ ! -z "$WINGS_DIR" ]]; then prompt_yn "We detected Pterodactyl present on the system. Should all the data be migrated onto WISP? (All servers will be stopped, configs and server data will be moved to WISP's folders, wings will be stopped)" "MIGRATE_PTERO" if [[ "$MIGRATE_PTERO" = 1 ]]; then prompt_yn "Have you ran the migrator in the panel's admin area? Otherwise the daemon will not start up properly and will not work." "MIGRATE_PTERO_CONFIRMATION" if [[ "$MIGRATE_PTERO_CONFIRMATION" = 1 ]]; then info "Preparing everything for migration: stopping wings, all server containers, and moving their server configs..." stop_wings stop_sftp get_wings_data_dir "OLD_WINGS_DATA_DIR" if [[ "$(ls $OLD_WINGS_DATA_DIR | egrep "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" | wc -l)" -gt "0" ]]; then mkdir -p /var/lib/wisp/daemon/config/servers /home/wisp/daemon-data SERVERS=$(ls $OLD_WINGS_DATA_DIR | egrep "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") for server in $SERVERS do debug "Handling $server for migration..." if [[ "$(docker ps -q -f name="^$server$")" ]]; then debug "Stopping the server..." docker stop $server > /dev/null 2>&1 fi if [[ "$(docker ps -aq -f name="^$server$")" ]]; then debug "Removing the container..." docker rm -f $server > /dev/null 2>&1 fi if [[ -d "$WINGS_DIR/config/servers/$server" ]]; then debug "Moving the config directory from $WINGS_DIR/config/servers/$server to /var/lib/wisp/daemon/config/servers..." mv $WINGS_DIR/config/servers/$server /var/lib/wisp/daemon/config/servers fi if [[ -d "$OLD_WINGS_DATA_DIR/$server" ]]; then debug "Moving the data directory from $OLD_WINGS_DATA_DIR/$server to /home/wisp/daemon-data..." mv $OLD_WINGS_DATA_DIR/$server /home/wisp/daemon-data fi done info "All pterodactyl servers will be migrated to WISP on initial boot up." else MIGRATE_PTERO=0 fatal "There are no Pterodactyl servers installed on the daemon - unable to migrate." fi else fatal "Aborting installation due to not being sure about running panel migration." fi else info "Pterodactyl servers will not be migrated to WISP." fi else MIGRATE_PTERO=0 fi install_wisp install_wisp_cli info "WISP is now installed, install script finished. It may take couple of minutes for everything to boot up, and after that it'll automatically register with the panel." info "You can follow real-time logs with the 'wisp tail' command. If there is an error, you can gather all required logs with the 'wisp logs' command." } check_for_wisp "WISP_EXISTS" if [[ "$WISP_EXISTS" = 1 ]]; then warn "WISP is already installed on the system. Only updating the WISP CLI utility..." install_wisp_cli info "WISP CLI utility is updated, install script finished." exit 0 fi prompt "What is the URL of the instance you want to run the daemon for? (e.g. https://mycool.panel.gg)" "INSTANCE_URL" 255 1 "instance url" info "Using the instance URL '$INSTANCE_URL' for installation..." warn "This script should be used only after you've deployed the instance on wisp.gg's dashboard." prompt "What license key should be used for the daemon?" "LICENSE_KEY" 32 32 "license key" #info "Using the license key '$LICENSE_KEY' for installation..." main