#!/usr/bin/env bash # ============================================================================= # KoaPanel — installer online. # # curl -fsSL https://get.koapanel.app | sudo bash # # Su un server Ubuntu 24.04 o 26.04 LTS (o AlmaLinux / Rocky Linux 9) appena creato (x86_64 o arm64), come root: # 1. controlla sistema, architettura e che il server sia "pulito"; # 2. scarica dal canale ufficiale (console.koapanel.app/updates) il # manifest firmato, ne verifica la firma ed25519 con la chiave pubblica # scritta qui sotto, poi scarica il pannello e lo script di installazione # e ne verifica lo SHA-256; # 3. esegue l'installazione (deploy/install.sh): nginx, PHP-FPM, MariaDB, # certbot, firewall, phpMyAdmin, strumenti WordPress, ClamAV, pannello; # 4. collega il pannello a console.koapanel.app (licenze e aggiornamenti). # Parte con il piano Gratuito (fino a 3 siti, uso personale). # # KoaPanel è software proprietario di Prime Software S.r.l.s.: installandolo # accetti i termini di licenza, https://koapanel.app/termini/ # (in inglese https://koapanel.app/en/terms/). I componenti open source # inclusi sono elencati in https://IP:8443/third-party-notices.txt. # # Variabili facoltative: # Con sudo le variabili si perdono: meglio i parametri (dalla 0.90.0) # # curl -fsSL https://get.koapanel.app | sudo bash -s -- \\ # --hostname pannello.miodominio.it --email tu@dominio.it --koabilling # # --hostname NOME nome del pannello: certificato Let's Encrypt in automatico # (il nome deve già puntare al server, porta 80 aperta) # --email EMAIL email per Let's Encrypt # --koabilling crea la chiave API per KoaBilling e la mostra alla fine # --license CODICE attiva la licenza: senza, il pannello risponde (chiave API # compresa) solo dopo aver collegato l'account # --mail attiva subito anche la posta # --isolation installa subito anche «Isolamento e limiti» # --port PORTA porta del pannello # # Variabili facoltative (ognuna vale come il parametro corrispondente): # KOAPANEL_HOSTNAME=NOME come --hostname # KOAPANEL_KOABILLING=1 come --koabilling # KOAPANEL_EMAIL=tu@dominio.it email per Let's Encrypt # KOAPANEL_MAIL=1 attiva subito anche la posta # KOAPANEL_ISOLATION=1 installa subito anche «Isolamento e limiti» # KOAPANEL_LINK_GRACE_DAYS=14 immagini dei marketplace cloud: piano Gratuito # senza account per N giorni (massimo 30) # KOAPANEL_PORT=8443 porta del pannello # KOAPANEL_FORCE=1 installa anche se il server non sembra nuovo # KOAPANEL_DOWNLOAD_ONLY=1 scarica e verifica soltanto (nessuna modifica) # KOAPANEL_CHANNEL=URL canale (solo per prove) # KOAPANEL_LANG=it|en lingua dei messaggi (get.koapanel.app/it = italiano) # KOAPANEL_NO_REPORT=1 non mandare il resoconto anonimo dell'installazione # # Resoconto anonimo (1.25.0): all'inizio e alla fine l'installer manda a # console.koapanel.app come è andata (avviata, bloccata da un controllo, # installata, fallita e a quale passo), con versione di Ubuntu, architettura, # memoria e lingua. Nessun dato personale, nessun indirizzo IP conservato. # ============================================================================= set -Eeuo pipefail umask 077 CHANNEL="${KOAPANEL_CHANNEL:-https://console.koapanel.app/updates/stable.json}" CONSOLE="https://console.koapanel.app" URL_PREFIX="${CONSOLE}/updates/" if [[ -n "${KOAPANEL_CHANNEL:-}" ]]; then URL_PREFIX="${KOAPANEL_CHANNEL%/stable.json}/"; fi # Chiave pubblica degli aggiornamenti (agent/pkg/licproto UpdatePublicKeys). UPDATE_PUBKEY='-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAlxlyG0YNNSkuMDAf4Hh8dit7MXc01J+PNfP5BNvrA0U= -----END PUBLIC KEY-----' if [[ -t 1 ]]; then B=$'\033[1m'; G=$'\033[1;32m'; Y=$'\033[1;33m'; R=$'\033[1;31m'; N=$'\033[0m'; else B= G= Y= R= N=; fi say() { printf '%s==>%s %s\n' "$G" "$N" "$*"; } warn() { printf '%s[!]%s %s\n' "$Y" "$N" "$*" >&2; } # ------------------------------------------------------------- lingua # get.koapanel.app/it pubblica questo file con DEFAULT_LANG=it. DEFAULT_LANG=en case "${KOAPANEL_LANG:-}" in it | en) L="$KOAPANEL_LANG" ;; *) case "${LC_ALL:-${LANG:-}}" in it_* | it) L=it ;; *) L="$DEFAULT_LANG" ;; esac ;; esac t() { if [[ "$L" == "it" ]]; then printf '%s' "$1"; else printf '%s' "$2"; fi; } # ------------------------------------------------------------- resoconto anonimo RUN_ID="$(od -An -N8 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n' || true)" PHASE=checks OS_NAME="" ARCH="" MEM_MB=0 VERSION_INST="" SERVER_ID="" report() { # stage [reason] [[ "${KOAPANEL_NO_REPORT:-0}" == "1" || -z "$RUN_ID" ]] && return 0 command -v curl >/dev/null 2>&1 || return 0 local body body=$(printf '{"runId":"%s","stage":"%s","reason":"%s","version":"%s","arch":"%s","os":"%s","memMb":%d,"lang":"%s","serverId":"%s"}' \ "$RUN_ID" "$1" "$(printf '%s' "${2:-}" | tr -d '"\\' | head -c 200)" "$VERSION_INST" "$ARCH" "$(printf '%s' "$OS_NAME" | tr -d '"\\')" "${MEM_MB:-0}" "$L" "$SERVER_ID") curl -fsS --max-time 5 --proto '=https' -o /dev/null -H 'Content-Type: application/json' -d "$body" "${CONSOLE}/api/v1/installs" 2>/dev/null || true } # die REASON_EN MESSAGE: stop, and tell the console why (blocked by a check / failed) die() { local reason="$1"; shift printf '%s[x]%s %s\n' "$R" "$N" "$*" >&2 if [[ "$PHASE" == "checks" ]]; then report blocked "$reason"; else report failed "$reason"; fi exit 1 } main() { printf '\n%sKoaPanel%s — %s\n\n' "$B" "$N" "$(t installazione installation)" # ------------------------------------------------------------- controlli [[ -r /etc/os-release ]] && . /etc/os-release OS_NAME="${PRETTY_NAME:-unknown}" MEM_MB=$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo 2>/dev/null || echo 0) case "$(uname -m)" in x86_64 | amd64) ARCH=amd64 ;; aarch64 | arm64) ARCH=arm64 ;; *) ARCH="$(uname -m)" ;; esac report started [[ $EUID -eq 0 ]] || die "not root" "$(t "serve root: curl -fsSL https://get.koapanel.app/it | sudo bash" "run it as root: curl -fsSL https://get.koapanel.app | sudo bash")" [[ -n "${ID:-}" ]] || die "unknown system" "$(t "sistema non riconosciuto" "unknown operating system")" local rhel=0 [[ "${ID:-}" =~ ^(almalinux|rocky)$ && "${VERSION_ID:-}" =~ ^9(\.|$) ]] && rhel=1 if [[ $rhel == 0 && ( "${ID:-}" != "ubuntu" || ! "${VERSION_ID:-}" =~ ^(24|26)\.04$ ) ]]; then die "needs Ubuntu 24.04 or 26.04 (found ${PRETTY_NAME:-unknown})" "$(t "serve Ubuntu 24.04 o 26.04 LTS, AlmaLinux 9 o Rocky Linux 9 (trovato: ${PRETTY_NAME:-sconosciuto}). Crea un server nuovo con uno di questi sistemi e rilancia il comando: https://koapanel.app/documentazione/installazione/" "KoaPanel needs Ubuntu 24.04 or 26.04 LTS, AlmaLinux 9 or Rocky Linux 9 (found: ${PRETTY_NAME:-unknown}). Create a new server with one of these systems and run the command again: https://koapanel.app/en/docs/installation/")" fi local arch="$ARCH" [[ "$arch" == amd64 || "$arch" == arm64 ]] || die "architecture $(uname -m)" "$(t "architettura non supportata: $(uname -m) (servono x86_64 o arm64)" "unsupported architecture: $(uname -m) (x86_64 or arm64 needed)")" if [[ -f /etc/panel/agent.yaml ]]; then die "already installed" "$(t "KoaPanel è già installato su questo server (/etc/panel/agent.yaml). Per aggiornarlo usa la pagina Aggiornamenti del pannello." "KoaPanel is already installed on this server (/etc/panel/agent.yaml). To update it, use the Updates page of the panel.")" fi local found=() [[ -d /usr/local/cpanel ]] && found+=("cPanel") [[ -d /usr/local/psa ]] && found+=("Plesk") [[ -d /usr/local/directadmin ]] && found+=("DirectAdmin") command -v apache2 >/dev/null 2>&1 && found+=("Apache") [[ -d /etc/nginx/sites-enabled ]] && [[ -n "$(ls -A /etc/nginx/sites-enabled 2>/dev/null | grep -v '^default$' || true)" ]] && found+=("siti nginx") command -v mysqld >/dev/null 2>&1 && found+=("MySQL/MariaDB") if ((${#found[@]})); then warn "$(t "Questo server non sembra nuovo: ${found[*]}." "This server doesn't look new: ${found[*]}.")" warn "$(t "KoaPanel va installato su un server appena creato: potrebbe sovrascrivere configurazioni esistenti." "KoaPanel must be installed on a freshly created server: it could overwrite existing configuration.")" [[ "${KOAPANEL_FORCE:-0}" == "1" ]] || die "server not new: ${found[*]}" "$(t "installazione annullata (per procedere comunque: KOAPANEL_FORCE=1)" "installation cancelled (to go ahead anyway: KOAPANEL_FORCE=1)")" fi local mem_mb="$MEM_MB" if ((mem_mb > 0 && mem_mb < 1800)); then warn "$(t "Memoria: ${mem_mb} MB. Consigliati almeno 2 GB (sotto ~1,5 GB l'antivirus ClamAV non viene installato)." "Memory: ${mem_mb} MB. At least 2 GB recommended (below ~1.5 GB the ClamAV antivirus is not installed).")" fi # ------------------------------------------------------------- strumenti local need=() for c in curl openssl sha256sum python3 base64; do command -v "$c" >/dev/null 2>&1 || need+=("$c"); done if ((${#need[@]})); then say "$(t "Installo gli strumenti mancanti" "Installing missing tools"): ${need[*]}" if [[ $rhel == 1 ]]; then dnf -y -q install curl ca-certificates openssl coreutils python3 >/dev/null else export DEBIAN_FRONTEND=noninteractive apt-get update -q >/dev/null apt-get install -y -q --no-install-recommends curl ca-certificates openssl coreutils python3 >/dev/null fi fi # ------------------------------------------------------------- download WORKDIR="$(mktemp -d /tmp/koapanel.XXXXXX)" trap 'rm -rf "${WORKDIR:-}"' EXIT cd "$WORKDIR" PHASE=download say "$(t "Scarico il manifest firmato da" "Downloading the signed manifest from") ${CHANNEL}" curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -o stable.json "$CHANNEL" || die "channel unreachable" "$(t "canale non raggiungibile" "update channel unreachable"): $CHANNEL" curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -o stable.json.sig "${CHANNEL}.sig" || die "signature unreachable" "$(t "firma del manifest non raggiungibile" "manifest signature unreachable")" say "$(t "Verifico la firma ed25519 del manifest" "Checking the ed25519 signature of the manifest")" printf '%s\n' "$UPDATE_PUBKEY" > update.pub base64 -d stable.json.sig > stable.sig 2>/dev/null || die "signature unreadable" "$(t "firma del manifest non leggibile" "manifest signature unreadable")" openssl pkeyutl -verify -pubin -inkey update.pub -rawin -in stable.json -sigfile stable.sig >/dev/null 2>&1 \ || die "bad signature" "$(t "FIRMA NON VALIDA: il manifest non è stato firmato da KoaPanel. Installazione annullata." "INVALID SIGNATURE: the manifest was not signed by KoaPanel. Installation cancelled.")" local info version bin_url bin_sha bin_size inst_url inst_sha inst_size info="$(python3 - "$arch" "$URL_PREFIX" <<'PY' import json, sys arch, prefix = sys.argv[1], sys.argv[2] m = json.load(open("stable.json")) if m.get("product") != "panel": sys.exit("prodotto inatteso") arts = m.get("artifacts", {}) out = [m["version"]] for key in ("linux-" + arch, "linux-installer"): a = arts.get(key) if not a: sys.exit("il manifest non contiene " + key) if not a["url"].startswith(prefix): sys.exit("indirizzo inatteso: " + a["url"]) out += [a["url"], a["sha256"].lower(), str(a["size"])] print(" ".join(out)) PY )" || die "bad manifest" "$(t "manifest non valido" "invalid manifest")" read -r version bin_url bin_sha bin_size inst_url inst_sha inst_size <<<"$info" [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "bad manifest version" "$(t "versione non valida nel manifest" "invalid version in the manifest")" VERSION_INST="$version" say "$(t Versione Version) ${B}${version}${N} (${arch})" local bar=(-s) [[ -t 2 ]] && bar=(--progress-bar) fetch() { # url sha size out curl -fSL "${bar[@]}" --proto '=https' --tlsv1.2 --retry 3 -o "$4" "$1" || die "download failed" "$(t "download non riuscito" "download failed"): $1" [[ "$(stat -c %s "$4")" == "$3" ]] || die "wrong size" "$(t "dimensione errata" "wrong size"): $1" echo "$2 $4" | sha256sum -c --status - || die "bad sha256" "$(t "SHA-256 errato: $1 (file alterato o corrotto)" "wrong SHA-256: $1 (file altered or corrupted)")" } say "$(t "Scarico il pannello" "Downloading the panel")" fetch "$bin_url" "$bin_sha" "$bin_size" panel-agent say "$(t "Scarico lo script di installazione" "Downloading the installation script")" fetch "$inst_url" "$inst_sha" "$inst_size" install.sh chmod 755 panel-agent install.sh say "$(t "File verificati: firma del manifest e SHA-256 corretti." "Files verified: manifest signature and SHA-256 are correct.")" if [[ "${KOAPANEL_DOWNLOAD_ONLY:-0}" == "1" ]]; then ./panel-agent version 2>/dev/null || true say "$(t "Solo download (KOAPANEL_DOWNLOAD_ONLY=1): nessuna modifica al server." "Download only (KOAPANEL_DOWNLOAD_ONLY=1): nothing changed on the server.")" KOAPANEL_NO_REPORT=1 return 0 fi # ------------------------------------------------------------- installazione local log=/root/koapanel-install.log say "$(t "Installando KoaPanel accetti i termini di licenza: https://koapanel.app/termini/" "By installing KoaPanel you accept the licence terms: https://koapanel.app/en/terms/")" say "$(t "Installo KoaPanel ${version}: servono di solito 5-10 minuti." "Installing KoaPanel ${version}: it usually takes 5-10 minutes.")" PHASE=install : > "$log" chmod 600 "$log" if ! PANEL_BINARY=./panel-agent PANEL_LE_EMAIL="${KOAPANEL_EMAIL:-}" PANEL_MAIL="${KOAPANEL_MAIL:-0}" PANEL_ISOLATION="${KOAPANEL_ISOLATION:-0}" PANEL_PORT="${KOAPANEL_PORT:-8443}" PANEL_HOSTNAME="${KOAPANEL_HOSTNAME:-}" PANEL_KOABILLING="${KOAPANEL_KOABILLING:-0}" PANEL_LICENSE="${KOAPANEL_LICENSE:-}" \ bash ./install.sh &1 | tee -a "$log"; then local last last="$(grep -a '\[x\]' "$log" | tail -1 | sed 's/\x1b\[[0-9;]*m//g; s/^.*\[x\] *//' | cut -c1-150)" die "install.sh: ${last:-failed}" "$(t "installazione non riuscita: guarda $log. Ti aiutiamo noi: supporto@koapanel.app (allega le ultime righe del file)." "installation failed: see $log. We can help: supporto@koapanel.app (attach the last lines of the file).")" fi # ------------------------------------------------------------- collegamento alla console say "$(t "Collego il pannello a console.koapanel.app (licenze e aggiornamenti)" "Connecting the panel to console.koapanel.app (licences and updates)")" local st=/var/lib/panel install -d -m 700 "$st/license" "$st/update" if [[ ! -s "$st/license/settings.json" ]]; then printf '{"checkoutUrl":"%s/checkout","adminUpgradeUrl":"","licenseServerUrl":"%s"}\n' "$CONSOLE" "$CONSOLE" > "$st/license/settings.json" chmod 600 "$st/license/settings.json" fi if [[ ! -s "$st/update/settings.json" ]]; then printf '{"mode":"notify","windowStart":"03:00","windowEnd":"05:00","channelUrl":"%s"}\n' "$CHANNEL" > "$st/update/settings.json" chmod 600 "$st/update/settings.json" fi systemctl restart panel-agent.service 2>/dev/null || true # ------------------------------------------------------------- marketplace: prova senza account local grace="${KOAPANEL_LINK_GRACE_DAYS:-}" graceok=0 if [[ -n "$grace" ]]; then [[ "$grace" =~ ^[0-9]+$ ]] || die "bad KOAPANEL_LINK_GRACE_DAYS" "$(t "KOAPANEL_LINK_GRACE_DAYS deve essere un numero di giorni" "KOAPANEL_LINK_GRACE_DAYS must be a number of days")" # l'agente registra l'installazione al primo avvio: qualche secondo for _ in $(seq 1 30); do if /usr/local/bin/panel-agent license link-grace "$grace" >/dev/null 2>&1; then graceok=1; break; fi sleep 2 done [[ "$graceok" == "1" ]] || warn "$(t "periodo senza account non impostato: il pannello chiederà subito di collegare l'account" "no-account period not set: the panel will ask to connect the account right away")" fi SERVER_ID="$(cat /var/lib/panel/server-id 2>/dev/null || true)" SERVER_ID="$(printf '%s' "$SERVER_ID" | tr -d ' \n')" report installed local ip port # 0.90.0: la stessa scelta di install.sh, che l'ha già scritta nel resoconto ip="$(sed 's/\x1b\[[0-9;]*m//g' "$log" | sed -n "s#.*Waiting for the Let's Encrypt certificate of https://\([^:]*\):.*#\1#p" | tail -1)" [[ -n "$ip" ]] || ip="$(hostname -I 2>/dev/null | awk '{print $1}')" port="${KOAPANEL_PORT:-8443}" printf '\n%s============================================================%s\n' "$G" "$N" printf ' %sKoaPanel %s %s%s\n\n' "$B" "$version" "$(t "è installato." "is installed.")" "$N" local certnote certnote="$(t "(certificato autofirmato: il pannello continua a chiedere quello di Let's Encrypt)" "(self-signed certificate: the panel keeps asking Let's Encrypt for a valid one)")" local name="${KOAPANEL_HOSTNAME:-}" shown="$ip" if [[ -n "$name" ]]; then name="${name,,}" shown="$name" certnote="$(t "(certificato non ancora pronto: controlla che il nome punti al server e che la porta 80 sia aperta)" "(certificate not ready yet: check that the name points to the server and port 80 is open)")" if curl -fsS --noproxy '*' --max-time 5 --resolve "${name}:${port}:127.0.0.1" -o /dev/null "https://${name}:${port}/api/v1/health" 2>/dev/null; then certnote="$(t "(certificato Let's Encrypt valido)" "(valid Let's Encrypt certificate)")" fi elif curl -fsS --noproxy '*' --max-time 5 --resolve "${ip}:${port}:127.0.0.1" -o /dev/null "https://${ip}:${port}/api/v1/health" 2>/dev/null; then certnote="$(t "(certificato Let's Encrypt valido)" "(valid Let's Encrypt certificate)")" fi printf ' %s https://%s:%s/ %s\n' "$(t "Pannello: " "Panel: ")" "$shown" "$port" "$certnote" printf ' %s\n' "$(t "Un nome tuo (es. pannello.miodominio.it): Server › Sicurezza › Certificato del pannello" "Your own name (e.g. panel.mydomain.com): Server › Security › Panel certificate")" printf ' %s %s\n' "$(t "Credenziali:" "Login: ")" "$(t "nome utente e password dell'amministratore sono qui sopra" "the administrator username and password are shown above")" printf ' %s %s %s\n' "$(t "e in" "and in")" "$log" "$(t "(leggibile solo da root):" "(readable by root only):")" printf ' %s\n' "$(t "salvale in un gestore di password, poi cancella il file." "save them in a password manager, then delete the file.")" if grep -aq 'License activated' "$log"; then printf ' %s %s\n' "$(t "Licenza: " "Licence: ")" "$(t "attivata." "activated.")" elif [[ "$graceok" == "1" ]]; then printf ' %s %s\n' "$(t "Piano: " "Plan: ")" "$(t "Gratuito (fino a 3 siti), senza account per $grace giorni." "Free (up to 3 sites), no account needed for $grace days.")" else printf '\n %s%s%s\n' "$B" "$(t "Ultimo passo (2 minuti): collega il tuo account KoaPanel gratuito" "Last step (2 minutes): connect your free KoaPanel account")" "$N" printf ' %s\n' "$(t "Accedi al pannello e premi «Collega l'account» (Piano e licenza):" "Sign in to the panel and press \"Connect the account\" (Plan and licence):")" printf ' %s\n' "$(t "sblocchi il piano Gratuito, fino a 3 siti. Finché non lo colleghi non puoi creare siti." "it unlocks the Free plan, up to 3 sites. Until then you cannot create sites.")" fi printf ' %s %s\n' "$(t "Più siti: " "More sites: ")" "$(t "nel pannello, Piano e licenza › Passa a…" "in the panel, Plan and licence › Upgrade to…")" printf ' %s %s\n' "$(t "Manuale: " "Manual: ")" "$(t "https://koapanel.app/documentazione/" "https://koapanel.app/en/docs/")" printf ' %s %s\n' "$(t "Aiuto: " "Help: ")" "supporto@koapanel.app" printf '%s============================================================%s\n\n' "$G" "$N" } # ------------------------------------------------------------- parametri while (($#)); do case "$1" in --hostname) KOAPANEL_HOSTNAME="${2:-}"; shift 2 || shift ;; --hostname=*) KOAPANEL_HOSTNAME="${1#*=}"; shift ;; --email) KOAPANEL_EMAIL="${2:-}"; shift 2 || shift ;; --email=*) KOAPANEL_EMAIL="${1#*=}"; shift ;; --license) KOAPANEL_LICENSE="${2:-}"; shift 2 || shift ;; --license=*) KOAPANEL_LICENSE="${1#*=}"; shift ;; --port) KOAPANEL_PORT="${2:-}"; shift 2 || shift ;; --port=*) KOAPANEL_PORT="${1#*=}"; shift ;; --koabilling) KOAPANEL_KOABILLING=1; shift ;; --mail) KOAPANEL_MAIL=1; shift ;; --isolation) KOAPANEL_ISOLATION=1; shift ;; *) printf '%s[x]%s %s: %s\n' "$R" "$N" "$(t "parametro sconosciuto" "unknown option")" "$1" >&2 printf ' --hostname NAME --email EMAIL --license CODE --port PORT --koabilling --mail --isolation\n' >&2 exit 2 ;; esac done main