#!/usr/bin/env bash # MyPanel installer — https://mypanel.vn # Usage: curl -fsSL https://get.mypanel.vn | sudo bash # Options: MYPANEL_VERSION=v0.4.8 MYPANEL_PORT=8888 MYPANEL_SETUP=none MYPANEL_LANG=vi curl -fsSL https://get.mypanel.vn | sudo bash # MYPANEL_SETUP=full (default) auto-installs nginx/docker/certbot/nixpacks. # MYPANEL_SETUP=none installs only the panel binary (you provide dependencies). # MYPANEL_LANG=vi prints messages in Vietnamese (default: en). set -euo pipefail BASE_URL="${MYPANEL_BASE_URL:-https://get.mypanel.vn}" # minisign public key — build-release.ps1 replaces the placeholder with the real key # at release time. Placeholder/empty = release not signed, checksum-only verification. MINISIGN_PUBKEY="RWQceo/PUWw6x93NayjI2OSsdr+efzSW4FhTmCg9N9KobawpJdYToEgA" INSTALL_DIR="/usr/local/bin" DATA_DIR="/var/lib/mypanel" PORT="${MYPANEL_PORT:-8888}" SETUP="${MYPANEL_SETUP:-full}" # Ngôn ngữ thông báo: en (mặc định) | vi. L in ra chuỗi đúng ngôn ngữ. case "${MYPANEL_LANG:-en}" in vi|VI|vn|VN) LANG_SEL=vi ;; *) LANG_SEL=en ;; esac L() { if [ "$LANG_SEL" = vi ]; then printf '%s' "$2"; else printf '%s' "$1"; fi; } err() { echo "$(L 'ERROR' 'LỖI'): $*" >&2; exit 1; } # check_os verifies the OS can auto-install: Ubuntu 22.04+/Debian 12+ run directly; # other apt-based distros warn and continue; non-apt + SETUP=full stops early with a # clear hint. Skipped when MYPANEL_SETUP=none (user provides dependencies). check_os() { if [ "$SETUP" = "none" ]; then return 0; fi local id="" verid="" maj="" min="" if [ -r /etc/os-release ]; then id="$( . /etc/os-release 2>/dev/null; printf '%s' "${ID:-}" )" verid="$( . /etc/os-release 2>/dev/null; printf '%s' "${VERSION_ID:-}" )" fi maj="${verid%%.*}"; min="${verid#*.}" case "$maj" in ''|*[!0-9]*) maj=0 ;; esac case "$min" in ''|*[!0-9]*) min=0 ;; esac [ "$min" = "$maj" ] && min=0 # no decimal part (e.g. Debian "12") case "$id" in ubuntu) if [ "$(( 10#$maj * 100 + 10#$min ))" -ge 2204 ]; then return 0; fi echo "$(L "⚠ Ubuntu ${verid:-?} is older than 22.04 — trying anyway, may fail (22.04+ recommended)" "⚠ Ubuntu ${verid:-?} cũ hơn 22.04 — vẫn thử cài, có thể lỗi (khuyến nghị 22.04+)")" ;; debian) if [ "$(( 10#$maj ))" -ge 12 ]; then return 0; fi echo "$(L "⚠ Debian ${verid:-?} is older than 12 — trying anyway, may fail (Debian 12+ recommended)" "⚠ Debian ${verid:-?} cũ hơn 12 — vẫn thử cài, có thể lỗi (khuyến nghị Debian 12+)")" ;; *) if command -v apt-get >/dev/null 2>&1; then echo "$(L "⚠ Distro '${id:-unknown}' (apt-based, untested) — continuing anyway" "⚠ Distro '${id:-không rõ}' (nền apt, chưa kiểm thử) — vẫn tiếp tục cài")" else err "$(L "OS not supported for auto-install (ID='${id:-unknown}'). MyPanel needs Ubuntu 22.04+ or Debian 12+ (apt-based). To install only the panel binary and handle dependencies yourself, re-run with MYPANEL_SETUP=none curl -fsSL https://get.mypanel.vn | sudo bash" "OS chưa hỗ trợ tự cài (ID='${id:-không rõ}'). MyPanel cần Ubuntu 22.04+ hoặc Debian 12+ (nền apt). Chỉ cài binary panel và tự lo phụ thuộc: chạy lại với MYPANEL_SETUP=none curl -fsSL https://get.mypanel.vn | sudo bash")" fi ;; esac } [ "$(id -u)" -eq 0 ] || err "$(L "must run as root: curl -fsSL https://get.mypanel.vn | sudo bash" "cần chạy với quyền root: curl -fsSL https://get.mypanel.vn | sudo bash")" [ "$(uname -s)" = "Linux" ] || err "$(L "MyPanel supports Linux only (Ubuntu 22.04+/Debian 12+)" "MyPanel chỉ hỗ trợ Linux (Ubuntu 22.04+/Debian 12+)")" command -v curl >/dev/null 2>&1 || err "$(L "curl is required (apt install -y curl)" "cần cài curl trước (apt install -y curl)")" command -v sha256sum >/dev/null 2>&1 || err "$(L "sha256sum command missing" "thiếu lệnh sha256sum")" command -v systemctl >/dev/null 2>&1 || err "$(L "system needs systemd" "hệ thống cần systemd")" case "$(uname -m)" in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; *) err "$(L "unsupported CPU architecture: $(uname -m)" "kiến trúc CPU không hỗ trợ: $(uname -m)")" ;; esac check_os # setup_deps installs & configures every dependency so the panel is usable right away # (best-effort: a partial failure doesn't abort, panel still runs). Idempotent. setup_deps() { if [ "$SETUP" = "none" ]; then echo "$(L "==> Skipping dependency setup (MYPANEL_SETUP=none)" "==> Bỏ qua cài phụ thuộc (MYPANEL_SETUP=none)")" return 0 fi if ! command -v apt-get >/dev/null 2>&1; then echo "$(L "⚠ Not an apt system (Ubuntu/Debian) — skipping auto-setup. Install manually: nginx, Docker (curl -fsSL https://get.docker.com | sh), certbot" "⚠ Không phải hệ apt (Ubuntu/Debian) — bỏ qua tự cài. Hãy tự cài: nginx, Docker (curl -fsSL https://get.docker.com | sh), certbot")" return 0 fi export DEBIAN_FRONTEND=noninteractive # Per-step progress so the user doesn't think it hung on first install. TOTAL=6; STEP=0 step() { STEP=$((STEP + 1)); printf '\n==> [%s/%s] %s\n' "$STEP" "$TOTAL" "$1"; } ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; } warn() { printf ' \033[33m⚠\033[0m %s\n' "$1"; } echo "" echo "$(L "==> Installing & configuring dependencies (first run usually takes 1-3 min) — step by step:" "==> Cài & cấu hình phụ thuộc (lần đầu thường mất 1–3 phút tuỳ mạng) — báo từng bước:")" step "$(L "Update package list (apt update)" "Cập nhật danh sách gói (apt update)")" if apt-get update -qq; then ok "$(L "updated" "đã cập nhật")"; else warn "$(L "apt update had errors — continuing" "apt update gặp lỗi — vẫn tiếp tục")"; fi # Install packages one by one for progress. DB is NOT installed by default: the user # picks & installs an engine (MariaDB/MySQL/Postgres/Mongo) inside the panel when # needed (panel configures the docker-bridge bind via EnsureDockerAccess). step "$(L "Install system packages" "Cài gói hệ thống")" PKGS="nginx certbot git ca-certificates curl" TOT=$(echo "$PKGS" | wc -w); N=0 for p in $PKGS; do N=$((N + 1)); printf ' (%s/%s) %-22s ' "$N" "$TOT" "$p" if dpkg -s "$p" >/dev/null 2>&1; then printf '\033[2m— %s\033[0m\n' "$(L 'present' 'đã có')" elif apt-get install -y -qq "$p" >/dev/null 2>&1; then printf '\033[32m✓\033[0m\n' else printf '\033[33m⚠ %s\033[0m\n' "$(L 'failed (install manually later)' 'lỗi (cài tay sau)')"; fi done # Docker CE (official, newer than the distro docker.io package): install via the # official get.docker.com script (auto-detects distro + repo + compose/buildx). # Enable it first to create bridge 172.17.0.1 (WordPress containers reach host MySQL). step "$(L "Install & start Docker CE + wait for internal network (docker0)" "Cài & bật Docker CE + chờ mạng nội bộ (docker0)")" if command -v docker >/dev/null 2>&1; then ok "$(L "Docker present" "Docker đã có") ($(docker --version 2>/dev/null | awk '{print $3}' | tr -d ,))" elif curl -fsSL https://get.docker.com | sh >/dev/null 2>&1; then ok "$(L "Docker CE installed" "đã cài Docker CE")" else warn "$(L "get.docker.com failed — trying distro docker.io (fallback)" "get.docker.com lỗi — thử gói distro docker.io (dự phòng)")" apt-get install -y -qq docker.io >/dev/null 2>&1 && ok "$(L "docker.io installed" "đã cài docker.io")" || warn "$(L "Docker install failed — install manually: curl -fsSL https://get.docker.com | sh" "cài Docker thất bại — cài tay: curl -fsSL https://get.docker.com | sh")" fi systemctl enable --now docker >/dev/null 2>&1 || warn "$(L "could not start docker" "không bật được docker")" # Wait for docker0 bridge (172.17.0.1) before binding MySQL — otherwise on a blank # VPS MySQL will FAIL to start binding to an address that doesn't exist yet. i=0 while [ "$i" -lt 15 ]; do if ip -4 addr show docker0 2>/dev/null | grep -q '172\.17\.0\.1'; then break; fi i=$((i + 1)); sleep 1 done ok "$(L "Docker ready" "Docker sẵn sàng")" # Docker >= 23 routes `docker build` through buildx and Nixpacks emits # "RUN --mount=type=cache" (BuildKit-only), so a missing buildx breaks every site # build there. (Docker 20.10 still builds fine without it via the daemon's built-in # BuildKit — hence "ensure", not "require".) get.docker.com ships buildx, but the two # other paths above do NOT: a pre-existing docker (we skip install) or the distro # docker.io fallback. Package name differs per source — docker-buildx-plugin (Docker # CE repo) vs docker-buildx (Ubuntu universe, the companion to docker.io). step "$(L "Ensure Docker buildx (BuildKit) is available" "Bảo đảm có Docker buildx (BuildKit)")" if docker buildx version >/dev/null 2>&1; then ok "$(L "buildx present" "buildx đã có") ($(docker buildx version 2>/dev/null | awk '{print $2}'))" else # dpkg -s cũng trả về 0 cho gói ĐÃ GỠ mà còn file cấu hình ("deinstall ok # config-files") — phải soi đúng dòng Status, nếu không máy từng cài docker.io # rồi chuyển sang Docker CE sẽ bị đoán nhầm tên gói. if dpkg -s docker.io 2>/dev/null | grep -q '^Status: install ok installed'; then BX="docker-buildx"; BX2="docker-buildx-plugin" else BX="docker-buildx-plugin"; BX2="docker-buildx" fi BXOK=0 for p in "$BX" "$BX2"; do if apt-get install -y -qq "$p" >/dev/null 2>&1 && docker buildx version >/dev/null 2>&1; then ok "$(L "installed $p" "đã cài $p")"; BXOK=1; break fi done if [ "$BXOK" = 0 ]; then warn "$(L "buildx missing — nixpacks/Dockerfile builds will fail on Docker >= 23. Install manually: apt-get install -y $BX" "thiếu buildx — build nixpacks/Dockerfile sẽ lỗi trên Docker >= 23. Cài tay: apt-get install -y $BX")" fi fi # DB NOT installed by default — pick & install an engine in the panel (Databases); # the panel configures the docker-bridge bind. HERE we only configure it if the # server HAPPENS to already have MySQL/MariaDB (e.g. a prebuilt image) so WP works. step "$(L "Configure DB for containers (if already present)" "Cấu hình DB cho container (nếu đã có sẵn)")" DBSVC="" if systemctl list-unit-files 2>/dev/null | grep -q '^mariadb\.service'; then DBSVC=mariadb elif systemctl list-unit-files 2>/dev/null | grep -q '^mysql\.service'; then DBSVC=mysql fi if [ -z "$DBSVC" ]; then warn "$(L "No DB installed — open the panel Databases section to pick & install an engine (MariaDB/MySQL/Postgres/Mongo) when needed" "Chưa cài DB — vào panel mục Databases để chọn & cài engine (MariaDB/MySQL/Postgres/Mongo) khi cần")" else systemctl enable --now "$DBSVC" >/dev/null 2>&1 || warn "$(L "could not start $DBSVC" "không bật được $DBSVC")" CONFD="" for d in /etc/mysql/mysql.conf.d /etc/mysql/mariadb.conf.d /etc/mysql/conf.d; do [ -d "$d" ] && { CONFD="$d"; break; } done # "zz-" prefix so the file is read AFTER defaults (avoid default 127.0.0.1 override). if [ -n "$CONFD" ] && [ ! -f "$CONFD/zz-mypanel-docker.cnf" ]; then rm -f /etc/mysql/mysql.conf.d/mypanel.cnf 2>/dev/null || true # Many WP sites share one DB: raise global connection cap + reap idle connections. printf '[mysqld]\nbind-address = 127.0.0.1,172.17.0.1\nmax_connections = 500\nwait_timeout = 120\ninteractive_timeout = 120\n' > "$CONFD/zz-mypanel-docker.cnf" mkdir -p "/etc/systemd/system/$DBSVC.service.d" printf '[Unit]\nAfter=docker.service\nWants=docker.service\n' > "/etc/systemd/system/$DBSVC.service.d/mypanel.conf" systemctl daemon-reload systemctl restart "$DBSVC" >/dev/null 2>&1 || warn "$(L "$DBSVC did not accept bind 172.17.0.1 — check: systemctl status $DBSVC" "$DBSVC chưa nhận bind 172.17.0.1 — kiểm tra: systemctl status $DBSVC")" fi ok "$(L "$DBSVC ready (listens on 127.0.0.1 + docker bridge only)" "$DBSVC sẵn sàng (chỉ nghe 127.0.0.1 + docker bridge)")" fi step "$(L "Start Nginx" "Bật Nginx")" if systemctl enable --now nginx >/dev/null 2>&1; then ok "$(L "nginx running" "nginx đang chạy")"; else warn "$(L "could not start nginx" "không bật được nginx")"; fi # Nixpacks: build multi-language apps ('auto-detect' mode). Best-effort. step "$(L "Install Nixpacks (multi-language app builder — optional)" "Cài Nixpacks (build app đa ngôn ngữ — tuỳ chọn)")" if command -v nixpacks >/dev/null 2>&1; then ok "$(L "present" "đã có")" elif curl -fsSL https://nixpacks.com/install.sh | bash >/dev/null 2>&1; then ok "$(L "installed" "đã cài")" else warn "$(L "could not install Nixpacks (only affects 'auto-detect' builds)" "chưa cài được Nixpacks (chỉ ảnh hưởng build 'Tự nhận diện')")"; fi # Firewall: ONLY when ufw is active — open SSH + web + panel port to avoid lockout. if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "Status: active"; then for p in 22 80 443 "$PORT"; do ufw allow "$p"/tcp >/dev/null 2>&1 || true; done # DB only opened to the docker subnet (WordPress containers), NOT the Internet. ufw allow from 172.16.0.0/12 to any port 3306 proto tcp >/dev/null 2>&1 || true ok "$(L "Opened ports 22/80/443/$PORT (+3306 docker-internal) on ufw" "Mở cổng 22/80/443/$PORT (+3306 nội bộ docker) trên ufw")" fi # GeoIP (optional): download GeoLite2-Country.mmdb for per-country stats. Only when # missing (not refreshed each update). Skip with MYPANEL_GEOIP=skip. step "$(L "GeoIP for per-country stats (optional)" "GeoIP cho thống kê quốc gia (tuỳ chọn)")" if [ "${MYPANEL_GEOIP:-auto}" = "skip" ]; then ok "$(L "skipped (MYPANEL_GEOIP=skip)" "bỏ qua (MYPANEL_GEOIP=skip)")" elif [ -f "$DATA_DIR/GeoLite2-Country.mmdb" ]; then ok "$(L "present" "đã có")" elif curl -fsSL -o "$DATA_DIR/GeoLite2-Country.mmdb.tmp" "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb" 2>/dev/null; then mv "$DATA_DIR/GeoLite2-Country.mmdb.tmp" "$DATA_DIR/GeoLite2-Country.mmdb" ok "$(L "downloaded GeoLite2-Country.mmdb" "đã tải GeoLite2-Country.mmdb")" else rm -f "$DATA_DIR/GeoLite2-Country.mmdb.tmp" 2>/dev/null || true warn "$(L "could not download GeoIP (per-country stats off — you can place the file manually later)" "chưa tải được GeoIP (thống kê quốc gia tắt — đặt file thủ công sau cũng được)")" fi printf '\n==> \033[32m✓\033[0m %s\n' "$(L "Dependencies ready" "Phụ thuộc đã sẵn sàng")" } VERSION="${MYPANEL_VERSION:-$(curl -fsSL "$BASE_URL/latest.txt" | tr -d '[:space:]')}" [ -n "$VERSION" ] || err "$(L "could not read latest version from $BASE_URL/latest.txt" "không đọc được phiên bản mới nhất từ $BASE_URL/latest.txt")" echo "$(L "==> Installing MyPanel $VERSION (linux/$ARCH)" "==> Cài MyPanel $VERSION (linux/$ARCH)")" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT BIN="mypanel-linux-$ARCH" echo "$(L "==> Downloading" "==> Tải") $BASE_URL/releases/$VERSION/$BIN" curl -fSL --progress-bar "$BASE_URL/releases/$VERSION/$BIN" -o "$TMP/$BIN" curl -fsSL "$BASE_URL/releases/$VERSION/SHA256SUMS" -o "$TMP/SHA256SUMS" # Verify the minisign signature of SHA256SUMS (if the release is signed + pubkey embedded). # The signature authenticates the SHA256SUMS file; the checksum below authenticates the binary. if [ -n "$MINISIGN_PUBKEY" ] && [ "$MINISIGN_PUBKEY" != "RWQceo/PUWw6x93NayjI2OSsdr+efzSW4FhTmCg9N9KobawpJdYToEgA" ]; then echo "$(L "==> Verifying minisign signature" "==> Xác thực chữ ký minisign")" if curl -fsSL "$BASE_URL/releases/$VERSION/SHA256SUMS.minisig" -o "$TMP/SHA256SUMS.minisig"; then if ! command -v minisign >/dev/null 2>&1; then apt-get install -y -qq minisign >/dev/null 2>&1 || true fi if command -v minisign >/dev/null 2>&1; then ( cd "$TMP" && minisign -V -P "$MINISIGN_PUBKEY" -m SHA256SUMS ) \ || err "$(L "invalid minisign signature — file may be tampered, aborting" "chữ ký minisign KHÔNG hợp lệ — file có thể đã bị giả mạo, dừng cài đặt")" echo "$(L "==> ✓ Signature valid" "==> ✓ Chữ ký hợp lệ")" else echo "$(L "⚠ could not install minisign — skipping signature check (SHA256 checksum still verified)" "⚠ không cài được minisign — bỏ qua verify chữ ký (vẫn kiểm checksum SHA256)")" fi else echo "$(L "⚠ this release has no signature file — skipping signature check (SHA256 checksum still verified)" "⚠ release này chưa có file chữ ký — bỏ qua verify chữ ký (vẫn kiểm checksum SHA256)")" fi fi echo "$(L "==> Verifying SHA256 checksum" "==> Kiểm tra checksum SHA256")" ( cd "$TMP" && grep " $BIN\$" SHA256SUMS | sha256sum -c - ) \ || err "$(L "checksum MISMATCH — downloaded file may be tampered, aborting" "checksum KHÔNG khớp — file tải về có thể đã bị can thiệp, dừng cài đặt")" if systemctl is-active --quiet mypanel 2>/dev/null; then echo "$(L "==> MyPanel is running — upgrading binary and restarting" "==> Phát hiện MyPanel đang chạy — sẽ nâng cấp binary và khởi động lại")" systemctl stop mypanel fi install -m 0755 "$TMP/$BIN" "$INSTALL_DIR/mypanel" mkdir -p "$DATA_DIR" # Install & configure dependencies (before starting the panel so it's usable right after). setup_deps cat > /etc/systemd/system/mypanel.service </dev/null && [ -f "$DATA_DIR/admin-password.txt" ]; then break; fi if systemctl is-failed --quiet mypanel 2>/dev/null; then break; fi sleep 1 done systemctl is-active --quiet mypanel || err "$(L "mypanel service failed to start — see log: journalctl -u mypanel -n 50" "service mypanel không khởi động được — xem log: journalctl -u mypanel -n 50")" IP="$(hostname -I 2>/dev/null | awk '{print $1}')" echo "" echo "=================================================" echo " $(L "✓ MyPanel $VERSION installed — ready to use!" "✓ MyPanel $VERSION đã cài xong — sẵn sàng dùng ngay!")" echo "" echo " $(L "Access:" "Truy cập:") https://${IP:-}:$PORT" echo " $(L "(self-signed cert — browser warns, choose Advanced > Proceed)" "(chứng chỉ tự ký — trình duyệt cảnh báo, chọn Advanced > Proceed)")" echo " $(L "Account:" "Tài khoản:") admin" if [ -f "$DATA_DIR/admin-password.txt" ]; then echo " $(L "Password:" "Mật khẩu:") $(cat "$DATA_DIR/admin-password.txt")" fi echo "" echo " $(L "Do everything (websites/WordPress/databases, SSL…) inside the panel." "Mọi thao tác (tạo website/WordPress/database, SSL…) làm trong panel.")" echo " $(L "⚠ If your VPS has a Cloud Firewall (GCP/AWS/Vultr…): open ports 80, 443, $PORT to the Internet." "⚠ Nếu VPS có Cloud Firewall (GCP/AWS/Vultr…): mở cổng 80, 443, $PORT cho Internet.")" echo " $(L "Manage service:" "Quản lý service:") systemctl status|restart|stop mypanel" echo "================================================="