#!/usr/bin/env bash # # WardPane installer, https://wardpane.com # # curl -fsSL https://wardpane.com/install | sudo bash # curl -fsSL https://wardpane.com/install | sudo bash -s -- --php # # What it does: # 1. Checks the system: Ubuntu 24.04, root, curl, tar. # 2. Asks for your email and your agreement to the Terms of use, the Privacy # policy and the Consent to the processing of personal data. # 3. Sends a 6-digit code to that email and asks for it. # 4. Downloads the release archive with a one-time link and checks its SHA-256. # 5. Unpacks it and runs the panel's own install.sh with the arguments you # gave this script (for example --php, --email, -y; see that file). # # Questions are read from the terminal, so this needs an interactive session. # --force skips the Ubuntu version check, here and in the panel installer. set -euo pipefail umask 077 SITE="https://wardpane.com" CDN="https://cdn.wardpane.com" say() { printf '%s\n' "$*"; } die() { printf '\nError: %s\n' "$*" >&2; exit 1; } # --- checks ------------------------------------------------------------------- [[ $(id -u) -eq 0 ]] || die "run as root: curl -fsSL $SITE/install | sudo bash" FORCE=0 for a in "$@"; do [[ $a == "--force" ]] && FORCE=1; done OS_ID=""; OS_VERSION="" if [[ -r /etc/os-release ]]; then # shellcheck disable=SC1091 OS_ID=$(. /etc/os-release && printf '%s' "${ID:-}") # shellcheck disable=SC1091 OS_VERSION=$(. /etc/os-release && printf '%s' "${VERSION_ID:-}") fi if [[ $OS_ID != "ubuntu" || $OS_VERSION != "24.04" ]]; then [[ $FORCE -eq 1 ]] || die "WardPane needs Ubuntu 24.04 (found: ${OS_ID:-unknown} ${OS_VERSION:-}). Use --force to try anyway." say "Warning: not Ubuntu 24.04, continuing because of --force." fi for cmd in curl tar sha256sum mktemp; do command -v "$cmd" >/dev/null 2>&1 || die "$cmd is required: apt-get install -y $cmd" done # The script itself comes in on stdin, so answers are read from the terminal. if ! { exec 3/dev/null; then die "no terminal to ask questions on. Run it in an interactive shell: curl -fsSL $SITE/install | sudo bash" fi ask() { local reply="" printf '%s' "$1" >/dev/tty IFS= read -r reply <&3 || true printf '%s' "$reply" } yes_no() { local reply reply=$(ask "$1 [y/N] ") [[ $reply =~ ^([Yy]|[Yy][Ee][Ss])$ ]] } CURL=(curl --proto '=https' --tlsv1.2 -sS --connect-timeout 15 --max-time 60) TMP=$(mktemp -d /root/wardpane-install.XXXXXX) trap 'rm -rf "$TMP"' EXIT # --- email and consent -------------------------------------------------------- LANG_CODE="en"; PREFIX="" case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in ru*) LANG_CODE="ru"; PREFIX="/ru" ;; esac say "" say "WardPane installer" say "" EMAIL_RE='^[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,63}$' EMAIL="" for _ in 1 2 3; do EMAIL=$(ask "Your email (the install code is sent there): ") EMAIL=$(printf '%s' "$EMAIL" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') if [[ ${#EMAIL} -le 254 && $EMAIL =~ $EMAIL_RE ]]; then break; fi say "That does not look like an email address." EMAIL="" done [[ -n $EMAIL ]] || die "no valid email given" say "" say "Please read these documents:" say " Terms of use: $SITE$PREFIX/legal/terms" say " Privacy policy: $SITE$PREFIX/legal/privacy" say " Consent to the processing of personal data: $SITE$PREFIX/legal/consent" say "" yes_no "Do you accept the Terms of use and the Privacy policy?" || die "the installation needs your acceptance of the Terms of use and the Privacy policy" yes_no "Do you consent to the processing of personal data?" || die "the installation needs your consent to the processing of personal data" NEWS=false if yes_no "Send you news about releases and security fixes?"; then NEWS=true; fi # --- code --------------------------------------------------------------------- # Strings here are checked above (email) or fixed, so they are safe in JSON. START_JSON=$(printf '{"email":"%s","lang":"%s","acceptTerms":true,"consent":true,"newsletter":%s}' "$EMAIL" "$LANG_CODE" "$NEWS") post() { # post URL JSON OUTFILE ACCEPT -> prints the HTTP status local status status=$("${CURL[@]}" -o "$3" -w '%{http_code}' -X POST \ -H 'Content-Type: application/json' -H "Accept: $4" --data "$2" "$1") || status="000" printf '%s' "$status" } STATUS=$(post "$SITE/api/install/start" "$START_JSON" "$TMP/start.out" "application/json") case "$STATUS" in 200) ;; 429) die "too many requests from this server or for this email. Wait a while and run the installer again." ;; 503) die "downloads are not available right now. Try again later." ;; 000) die "could not reach $SITE" ;; *) die "the server refused the request (HTTP $STATUS)" ;; esac say "" say "If the address is valid, a 6-digit code is on its way to $EMAIL." say "It is valid for 15 minutes. Check the spam folder if it does not arrive." URL=""; FILE=""; VERSION=""; SHA256=""; SHA256_URL="" for attempt in 1 2 3 4 5; do CODE=$(ask "Code: ") CODE=$(printf '%s' "$CODE" | tr -d '[:space:]') if [[ ! $CODE =~ ^[0-9]{6}$ ]]; then say "The code is 6 digits." continue fi VERIFY_JSON=$(printf '{"email":"%s","code":"%s"}' "$EMAIL" "$CODE") STATUS=$(post "$SITE/api/install/verify" "$VERIFY_JSON" "$TMP/verify.out" "text/plain") if [[ $STATUS == 200 ]]; then while IFS='=' read -r key value; do case "$key" in url) URL=$value ;; file) FILE=$value ;; version) VERSION=$value ;; sha256) SHA256=$value ;; sha256Url) SHA256_URL=$value ;; esac done < "$TMP/verify.out" break fi case "$STATUS" in 429) die "too many attempts. Wait 15 minutes and run the installer again." ;; 000) die "could not reach $SITE" ;; esac say "Wrong or expired code ($attempt of 5)." done [[ -n $URL ]] || die "no valid code entered. Run the installer again to get a new one." # Only accept what this installer expects to see. [[ $FILE =~ ^wardpane-[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz$ ]] || die "unexpected file name from the server" [[ $URL == "$CDN/d/"* && ${URL#"$CDN/d/"} =~ ^[A-Za-z0-9_-]{43}$ ]] || die "unexpected download address from the server" [[ $SHA256 =~ ^[0-9a-f]{64}$ ]] || die "unexpected checksum from the server" [[ $SHA256_URL == "$SITE/releases/$FILE.sha256" ]] || die "unexpected checksum address from the server" # --- download and verify ------------------------------------------------------ say "" say "Downloading WardPane $VERSION ..." OK=0 for _ in 1 2 3 4 5; do # -C - resumes from where a broken transfer stopped (same IP, same link). if "${CURL[@]}" --max-time 1800 -f -C - -o "$TMP/$FILE" "$URL"; then OK=1; break; fi say "Download interrupted, resuming ..." sleep 2 done [[ $OK -eq 1 ]] || die "the download failed. Run the installer again to get a new link." "${CURL[@]}" -f -o "$TMP/$FILE.sha256" "$SHA256_URL" || die "could not download the checksum" EXPECTED=$(awk 'NR==1 {print $1}' "$TMP/$FILE.sha256") ACTUAL=$(sha256sum "$TMP/$FILE" | awk '{print $1}') [[ $EXPECTED == "$SHA256" ]] || die "the checksum file does not match the server's answer" [[ $ACTUAL == "$EXPECTED" ]] || die "checksum mismatch: the archive is damaged or was changed" say "SHA-256 ok: $ACTUAL" # --- install ------------------------------------------------------------------ # Our umask 077 is for the temp files ($TMP itself stays 0700). The panel # copies its files from here, and other users must be able to read them. umask 022 mkdir "$TMP/src" tar -xzf "$TMP/$FILE" -C "$TMP/src" --no-same-owner [[ -f "$TMP/src/wardpane/install.sh" ]] || die "the archive has no wardpane/install.sh" say "" say "Starting the WardPane installer ..." say "" cd "$TMP/src/wardpane" bash ./install.sh "$@" <&3