Last active
April 7, 2026 02:15
-
-
Save yeungon/72accab21687616ab7df1b0fc66c5a85 to your computer and use it in GitHub Desktop.
kiwipanel updated install bash script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| ###################################################################### | |
| # KiwiPanel - a LOMP stack on Linux # | |
| # # | |
| # Author: Vuong Nguyen and contributors # | |
| # Website: https://kiwipanel.org # | |
| # @since: 2025 # | |
| # Please do not remove copyright. Thanks! # | |
| # Please do not copy under any circumstance for commercial reason! # | |
| ###################################################################### | |
| ###################################################################### | |
| # KiwiPanel Installer v0.1.5 | |
| # To install KiwiPanel copy the following instruction and paste to the terminal: | |
| # Quick Install (copy and paste to terminal): | |
| # bash <(curl -fsSL https://raw.githubusercontent.com/kiwipanel/install/main/install) | |
| # | |
| # Or download first: | |
| # curl -sLO https://raw.githubusercontent.com/kiwipanel/install/main/install && chmod +x install && sudo bash install | |
| ###################################################################### | |
| # Check for sudo/root privileges###################################### | |
| if [ "$(id -u)" -ne 0 ]; then | |
| echo "❌ You should install KiwiPanel as root or using sudo command." | |
| exit 1 | |
| fi | |
| clear | |
| date | |
| export LANG="${LANG:-en_US.UTF-8}" | |
| export LC_ALL="${LC_ALL:-en_US.UTF-8}" | |
| if command -v locale-gen >/dev/null 2>&1; then | |
| locale-gen en_US.UTF-8 2>/dev/null || true | |
| fi | |
| # Stop on error | |
| set -euo pipefail | |
| trap 'LogError "Failed: command=\"$BASH_COMMAND\" line=$LINENO"' ERR | |
| trap 'Cleanup 2>/dev/null || true' EXIT | |
| # Create log file | |
| KIWIPANEL_DIR="/opt/kiwipanel" | |
| LOG_DIR="$KIWIPANEL_DIR/logs" | |
| LOG_FILE="$LOG_DIR/install.log" | |
| function log() { | |
| local level="$1" | |
| shift | |
| local message="$*" | |
| local ts | |
| ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" | |
| echo "$ts [$level] $message" >> "$LOG_FILE" | |
| } | |
| LogInfo() { log INFO "$@"; } | |
| LogWarn() { log WARN "$@"; } | |
| LogError() { log ERROR "$@"; } | |
| LogDebug() { [[ "$DEBUG" == "1" ]] && log DEBUG "$@"; } | |
| # Utilities ########################################################### | |
| PrintGreen() { | |
| local GREEN=$'\e[0;32m' | |
| local NC=$'\e[0m' | |
| local content="$*" | |
| printf "%b%s%b\n" "$GREEN" "$content" "$NC" | |
| LogInfo "$content" | |
| } | |
| PrintInfo() { | |
| local CYAN=$'\e[0;36m' | |
| local NC=$'\e[0m' | |
| local content="$*" | |
| printf "\n%b%s%b\n" "$CYAN" "$content" "$NC" | |
| LogInfo "$content" | |
| } | |
| PrintWarn() { | |
| local YELLOW_BROWN=$'\e[38;5;179m' | |
| local NC=$'\e[0m' | |
| local content="$*" | |
| printf "%bWarning: %s%b\n" "$YELLOW_BROWN" "$content" "$NC" | |
| LogWarn "$content" | |
| } | |
| PrintRed() { | |
| local RED=$'\e[0;31m' | |
| local NC=$'\e[0m' | |
| local content="$*" | |
| printf "\n%bError: %s%b\n" "$RED" "$content" "$NC" | |
| LogError "$content" | |
| } | |
| DEBUG=${DEBUG:-0} | |
| AUTO_UPGRADE=${AUTO_UPGRADE:-0} | |
| mkdir -p "$LOG_DIR" || { | |
| echo "Failed to create log directory: $LOG_DIR" | |
| exit 1 | |
| } | |
| touch "$LOG_FILE" || { | |
| echo "Failed to create log file: $LOG_FILE" | |
| exit 1 | |
| } | |
| chmod 750 "$KIWIPANEL_DIR" "$LOG_DIR" # rwx for owner+group only; no access for others (security requirement) | |
| chmod 640 "$LOG_FILE" | |
| # ============================================================================== | |
| # Step Runner Framework | |
| # ============================================================================== | |
| # Adds resume-after-interruption capability. Each install step is wrapped with | |
| # run_step which tracks completion via marker files and verifies before skipping. | |
| # The existing install functions are called unchanged — this is a thin wrapper. | |
| # ============================================================================== | |
| INSTALL_STATE_DIR="/var/lib/kiwipanel/install.d" | |
| STEP_TOTAL=9 | |
| STEP_CURRENT=0 | |
| VERIFY_ONLY=0 | |
| STEPS_FILTER="" | |
| # Parse optional flags | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --verify-only) VERIFY_ONLY=1 ;; | |
| --steps=*) STEPS_FILTER="${arg#--steps=}" ;; | |
| esac | |
| done | |
| # run_step "step_name" execute_function verify_function | |
| run_step() { | |
| local name="$1" | |
| local execute_fn="$2" | |
| local verify_fn="$3" | |
| local state_file="$INSTALL_STATE_DIR/${name}.done" | |
| ((STEP_CURRENT++)) || true | |
| # If --steps filter is set, skip steps not in the list | |
| if [[ -n "$STEPS_FILTER" ]]; then | |
| if ! echo ",$STEPS_FILTER," | grep -q ",$name,"; then | |
| return 0 | |
| fi | |
| fi | |
| local prefix="[${STEP_CURRENT}/${STEP_TOTAL}] ${name}" | |
| # If marked done, verify before skipping | |
| if [[ -f "$state_file" ]]; then | |
| if "$verify_fn" 2>/dev/null; then | |
| PrintGreen "${prefix} ✔ verified (skipped)" | |
| LogInfo "Step '$name': verified, skipped" | |
| return 0 | |
| else | |
| PrintWarn "${prefix}: verification failed, re-running" | |
| LogWarn "Step '$name': was marked done but verification failed" | |
| rm -f "$state_file" | |
| fi | |
| fi | |
| # --verify-only mode: just report status, don't execute | |
| if [[ "$VERIFY_ONLY" == "1" ]]; then | |
| if "$verify_fn" 2>/dev/null; then | |
| PrintGreen "${prefix} ✔ verified" | |
| else | |
| PrintRed "${prefix} ✘ FAILED" | |
| fi | |
| return 0 | |
| fi | |
| # Execute | |
| PrintInfo "${prefix} ▶ running..." | |
| LogInfo "Step '$name': starting" | |
| local start_time | |
| start_time=$(date +%s) | |
| if "$execute_fn"; then | |
| # Verify after execution | |
| if "$verify_fn" 2>/dev/null; then | |
| # Atomic mark: write to tmp, then mv (survives power loss) | |
| local tmp_file="${state_file}.tmp" | |
| date -u +"%Y-%m-%dT%H:%M:%SZ" > "$tmp_file" | |
| mv "$tmp_file" "$state_file" | |
| local elapsed=$(( $(date +%s) - start_time )) | |
| PrintGreen "${prefix} ✔ completed (${elapsed}s)" | |
| LogInfo "Step '$name': completed and verified (${elapsed}s)" | |
| return 0 | |
| else | |
| PrintRed "${prefix}: executed but verification failed" | |
| LogError "Step '$name': executed but verification failed" | |
| return 1 | |
| fi | |
| else | |
| PrintRed "${prefix}: execution failed" | |
| LogError "Step '$name': execution failed" | |
| return 1 | |
| fi | |
| } | |
| # ============================================================================== | |
| # Idempotent Primitives (used by verify functions) | |
| # ============================================================================== | |
| ensure_package_installed() { | |
| local pkg="$1" | |
| rpm -q "$pkg" &>/dev/null || dpkg -l "$pkg" 2>/dev/null | grep -q '^ii' | |
| } | |
| ensure_service_active() { | |
| local svc="$1" | |
| systemctl is-active "$svc" &>/dev/null | |
| } | |
| ensure_user_exists() { | |
| local usr="$1" | |
| id "$usr" &>/dev/null | |
| } | |
| ensure_group_exists() { | |
| local grp="$1" | |
| getent group "$grp" >/dev/null | |
| } | |
| ensure_dir_exists() { | |
| local dir="$1" | |
| [[ -d "$dir" ]] | |
| } | |
| ensure_file_exists() { | |
| local file="$1" | |
| [[ -f "$file" ]] | |
| } | |
| function CheckFreshInstall() { | |
| local state_dir="$INSTALL_STATE_DIR" | |
| # Case 1: Fully completed install (finalize.done exists) | |
| if [[ -f "$state_dir/finalize.done" ]]; then | |
| PrintRed "KiwiPanel is already fully installed." | |
| PrintRed "To force re-install, remove the state directory:" | |
| PrintRed " rm -rf $state_dir && sudo bash install" | |
| exit 1 | |
| fi | |
| # Case 2: Partial install (state dir exists, some steps done) | |
| if [[ -d "$state_dir" ]] && compgen -G "$state_dir/*.done" >/dev/null 2>&1; then | |
| local completed | |
| completed=$(find "$state_dir" -name '*.done' | wc -l) | |
| PrintWarn "Found partial installation ($completed/9 steps completed). Resuming..." | |
| return 0 | |
| fi | |
| # Case 3: Fresh install | |
| PrintGreen "Welcome! Thanks for choosing KiwiPanel." | |
| } | |
| function PreflightChecks() { | |
| PrintInfo "==> Running pre-flight checks..." | |
| # Internet check | |
| if ! curl -s --max-time 5 https://google.com >/dev/null; then | |
| PrintRed "No internet connection" | |
| exit 1 | |
| fi | |
| local missing=() | |
| # Check binary dependencies | |
| for cmd in curl wget unzip systemctl jq gpg; do | |
| if ! command -v "$cmd" >/dev/null 2>&1; then | |
| missing+=("$cmd") | |
| fi | |
| done | |
| # Always ensure these packages are present (can't detect via command -v) | |
| # gnupg is called gnupg2 on RHEL/Rocky/Alma | |
| if command -v apt >/dev/null 2>&1; then | |
| missing+=("gnupg") | |
| else | |
| missing+=("gnupg2") | |
| fi | |
| missing+=("ca-certificates") | |
| if [ ${#missing[@]} -gt 0 ]; then | |
| PrintInfo "==> Installing missing dependencies: ${missing[*]}" | |
| if command -v apt >/dev/null 2>&1; then | |
| apt update -y | |
| apt install -y "${missing[@]}" | |
| elif command -v dnf >/dev/null 2>&1; then | |
| dnf install -y "${missing[@]}" | |
| elif command -v yum >/dev/null 2>&1; then | |
| yum install -y "${missing[@]}" | |
| else | |
| PrintRed "Unsupported package manager. Install manually: ${missing[*]}" | |
| exit 1 | |
| fi | |
| fi | |
| } | |
| # Run initial checks | |
| CheckFreshInstall | |
| PreflightChecks | |
| # Configuration######################################################## | |
| readonly KIWIPANEL_PORT=8443 | |
| kiwipanel_passcode="" | |
| INSTALL_DIR="/opt/kiwipanel" | |
| TMP_BINARY="/tmp/kiwipanel-binary" | |
| TMP_AGENT_BINARY="/tmp/kiwipanel-agent-binary" | |
| TMP_ZIP="/tmp/kiwipanel-scaffold.zip" | |
| INSTALL_MARKER="/opt/kiwipanel/meta/.installed" | |
| # OS Detection Variables (set by LoadOsRelease) | |
| OS_ID="" | |
| OS_VERSION_MAJOR="" | |
| OS_LIKE="" | |
| OS_NAME="" | |
| LSWS_SERVICE="" | |
| # ------------------------------------------------------------------------------ | |
| # Fetching install scripts | |
| # ------------------------------------------------------------------------------ | |
| function DownloadPackage() { | |
| # 1. Download architecture-independent scaffold (scripts, config, systemd, templates) | |
| PrintInfo "==> Downloading KiwiPanel scaffold" | |
| PrintInfo " URL: $KIWIPANEL_SCAFFOLD_URL" | |
| if ! curl -L --fail --retry 3 --retry-delay 2 "$KIWIPANEL_SCAFFOLD_URL" -o "$TMP_ZIP"; then | |
| PrintRed "Failed to download KiwiPanel scaffold" | |
| exit 1 | |
| fi | |
| if [[ ! -s "$TMP_ZIP" ]]; then | |
| PrintRed "Downloaded scaffold is empty" | |
| exit 1 | |
| fi | |
| # 2. Download architecture-specific binary | |
| PrintInfo "==> Downloading KiwiPanel binary (${KIWIPANEL_ARCH})" | |
| if [[ -z "${KIWIPANEL_DOWNLOAD_URL:-}" ]]; then | |
| PrintRed "KIWIPANEL_DOWNLOAD_URL is not set. Did ShowVersion() run?" | |
| exit 1 | |
| fi | |
| PrintInfo " URL: $KIWIPANEL_DOWNLOAD_URL" | |
| if ! curl -L --fail --retry 3 --retry-delay 2 "$KIWIPANEL_DOWNLOAD_URL" -o "$TMP_BINARY"; then | |
| PrintRed "Failed to download KiwiPanel binary" | |
| exit 1 | |
| fi | |
| if [[ ! -s "$TMP_BINARY" ]]; then | |
| PrintRed "Downloaded binary is empty" | |
| exit 1 | |
| fi | |
| # 3. Validate SHA-256 checksum of binary if provided | |
| if [[ -n "${KIWIPANEL_SHA256:-}" && "$KIWIPANEL_SHA256" != "Not provided" ]]; then | |
| PrintInfo "==> Verifying SHA-256 checksum..." | |
| local actual_sha256 | |
| actual_sha256=$(sha256sum "$TMP_BINARY" | cut -d' ' -f1) | |
| if [[ "$actual_sha256" != "$KIWIPANEL_SHA256" ]]; then | |
| PrintRed "Checksum mismatch!" | |
| PrintRed " Expected: $KIWIPANEL_SHA256" | |
| PrintRed " Actual: $actual_sha256" | |
| rm -f "$TMP_BINARY" | |
| exit 1 | |
| fi | |
| PrintGreen "✓ Checksum verified" | |
| else | |
| PrintWarn "No SHA-256 checksum available, skipping verification" | |
| fi | |
| # 4. Download architecture-specific agent binary | |
| PrintInfo "==> Downloading KiwiPanel agent binary (${KIWIPANEL_ARCH})" | |
| PrintInfo " URL: $KIWIPANEL_AGENT_DOWNLOAD_URL" | |
| if ! curl -L --fail --retry 3 --retry-delay 2 "$KIWIPANEL_AGENT_DOWNLOAD_URL" -o "$TMP_AGENT_BINARY"; then | |
| PrintRed "Failed to download KiwiPanel agent binary" | |
| exit 1 | |
| fi | |
| if [[ ! -s "$TMP_AGENT_BINARY" ]]; then | |
| PrintRed "Downloaded agent binary is empty" | |
| exit 1 | |
| fi | |
| PrintGreen "✓ All downloads complete" | |
| } | |
| #Make sure the meta data is removed during re-installation | |
| function RemoveMetaData(){ | |
| rm -f "/opt/kiwipanel/meta/passcode" | |
| rm -f "/opt/kiwipanel/meta/.installed" | |
| } | |
| function ExtractPackage() { | |
| # 1. Extract scaffold (scripts, config, systemd, templates) | |
| PrintInfo "==> Extracting scaffold" | |
| unzip -oq "$TMP_ZIP" -d /opt/ || { | |
| PrintRed "Failed to extract KiwiPanel scaffold" | |
| exit 1 | |
| } | |
| # 2. Install architecture-specific binaries (overwrite any from scaffold) | |
| PrintInfo "==> Installing KiwiPanel binaries (${KIWIPANEL_ARCH})" | |
| local bin_dir="$INSTALL_DIR/bin" | |
| mkdir -p "$bin_dir" | |
| cp "$TMP_BINARY" "$bin_dir/kiwipanel" | |
| chmod 750 "$bin_dir/kiwipanel" | |
| PrintGreen "✓ kiwipanel binary installed (${KIWIPANEL_ARCH})" | |
| cp "$TMP_AGENT_BINARY" "$bin_dir/kiwipanel-agent" | |
| chmod 755 "$bin_dir/kiwipanel-agent" | |
| PrintGreen "✓ kiwipanel-agent binary installed (${KIWIPANEL_ARCH})" | |
| RemoveMetaData | |
| } | |
| function CreateGroupAndUser() { | |
| PrintInfo "==> Creating kiwisecure group" | |
| getent group kiwisecure >/dev/null || groupadd kiwisecure | |
| PrintInfo "==> Creating kiwipanel user" | |
| if ! id kiwipanel >/dev/null 2>&1; then | |
| useradd --system \ | |
| --no-create-home \ | |
| --home-dir "$INSTALL_DIR" \ | |
| --shell /usr/sbin/nologin \ | |
| --gid kiwisecure \ | |
| kiwipanel | |
| fi | |
| # Ensure kiwipanel user is in kiwisecure group (for reading token file and socket access) | |
| usermod -aG kiwisecure kiwipanel 2>/dev/null || true | |
| # Add kiwipanel user to systemd-journal group (for reading agent logs via journalctl) | |
| if getent group systemd-journal >/dev/null 2>&1; then | |
| usermod -aG systemd-journal kiwipanel 2>/dev/null || true | |
| PrintGreen "✓ kiwipanel user added to systemd-journal group" | |
| fi | |
| } | |
| #====================================Start Installing KiwiPanel ==================================== | |
| function PrepareDirectories() { | |
| PrintInfo "==> Preparing directories" | |
| mkdir -p "$INSTALL_DIR" | |
| } | |
| function ShowVersion() { | |
| META_DIR="/opt/kiwipanel/meta" | |
| VERSION_FILE="$META_DIR/version" | |
| RELEASES_FILE="$META_DIR/releases.json" | |
| RELEASES_URL="https://raw.githubusercontent.com/kiwipanel/install/refs/heads/main/releases.json" | |
| # Default to stable channel (can be overridden by environment variable) | |
| CHANNEL="${KIWIPANEL_CHANNEL:-stable}" | |
| mkdir -p "$META_DIR" | |
| # Fetch releases catalog with error handling | |
| if ! RELEASES_JSON=$(curl -fsSL "$RELEASES_URL" 2>/dev/null); then | |
| PrintRed "Failed to fetch releases.json from GitHub" | |
| exit 1 | |
| fi | |
| # Validate JSON is not empty | |
| if [[ -z "$RELEASES_JSON" || "$RELEASES_JSON" == "null" ]]; then | |
| PrintRed "Empty or invalid releases.json received" | |
| exit 1 | |
| fi | |
| # Cache full releases.json locally (replaceable cache) | |
| echo "$RELEASES_JSON" > "$RELEASES_FILE" | |
| # Detect OS | |
| OS="linux" | |
| # Detect architecture | |
| ARCH=$(uname -m) | |
| case "$ARCH" in | |
| x86_64) ARCH="amd64" ;; | |
| aarch64|arm64) ARCH="arm64" ;; | |
| *) | |
| PrintRed "Unsupported architecture: $ARCH" | |
| exit 1 | |
| ;; | |
| esac | |
| # Get latest version string from the specified channel | |
| LATEST_VERSION=$(echo "$RELEASES_JSON" | jq -r --arg channel "$CHANNEL" '.channels[$channel].latest // empty') | |
| # Check if LATEST_VERSION is empty or null | |
| if [[ -z "$LATEST_VERSION" || "$LATEST_VERSION" == "null" ]]; then | |
| PrintRed "Failed to parse 'latest' version for channel '$CHANNEL' from releases.json" | |
| PrintInfo "Available channels:" | |
| echo "$RELEASES_JSON" | jq -r '.channels | keys[]' 2>/dev/null || true | |
| exit 1 | |
| fi | |
| PrintInfo "==> Channel: $CHANNEL" | |
| PrintInfo "==> Latest version: $LATEST_VERSION" | |
| # Select matching release (must match version, channel, os, and arch) | |
| RELEASE=$(echo "$RELEASES_JSON" | jq -r \ | |
| --arg os "$OS" \ | |
| --arg arch "$ARCH" \ | |
| --arg ver "$LATEST_VERSION" \ | |
| --arg channel "$CHANNEL" ' | |
| .releases[] | |
| | select(.version==$ver and .channel==$channel and .os==$os and .arch==$arch) | |
| ') | |
| if [[ -z "$RELEASE" || "$RELEASE" == "null" ]]; then | |
| PrintRed "No compatible release found for $OS/$ARCH (channel: $CHANNEL, version: $LATEST_VERSION)" | |
| PrintInfo "Available releases:" | |
| echo "$RELEASES_JSON" | jq -r '.releases[] | "\(.version) [\(.channel)] - \(.os)/\(.arch)"' 2>/dev/null || true | |
| exit 1 | |
| fi | |
| VERSION=$(echo "$RELEASE" | jq -r '.version') | |
| URL=$(echo "$RELEASE" | jq -r '.download_url') | |
| NOTES=$(echo "$RELEASE" | jq -r '.notes // "No release notes"') | |
| RELEASE_DATE=$(echo "$RELEASE" | jq -r '.release_date // "Unknown"') | |
| SHA256=$(echo "$RELEASE" | jq -r '.sha256 // "Not provided"') | |
| # Write plain-text version pointer (authoritative) | |
| echo "$VERSION" > "$VERSION_FILE" | |
| echo "Channel : $CHANNEL" | |
| echo "Latest Version : $VERSION" | |
| echo "Release Date : $RELEASE_DATE" | |
| echo "Architecture : $ARCH" | |
| echo "SHA256 : $SHA256" | |
| echo "Notes : $NOTES" | |
| # Export for installer/update steps | |
| export KIWIPANEL_VERSION="$VERSION" | |
| export KIWIPANEL_DOWNLOAD_URL="$URL" | |
| export KIWIPANEL_ARCH="$ARCH" | |
| export KIWIPANEL_OS="$OS" | |
| export KIWIPANEL_CHANNEL="$CHANNEL" | |
| export KIWIPANEL_SHA256="$SHA256" | |
| # Derive agent and scaffold URLs from same release tag | |
| # e.g. .../v0.4.2/kiwipanel-linux-amd64 → .../v0.4.2/kiwipanel-agent-linux-amd64 | |
| local base_url | |
| base_url=$(dirname "$URL") | |
| export KIWIPANEL_AGENT_DOWNLOAD_URL="${base_url}/kiwipanel-agent-linux-${ARCH}" | |
| export KIWIPANEL_SCAFFOLD_URL="${base_url}/kiwipanel-scaffold.zip" | |
| } | |
| #------------------------------------------------------------Fixing ownership and permissions (because root created files beforehand | |
| function FixPermissions() { | |
| PrintInfo "==> Setting proper ownership and permissions" | |
| # Ownership | |
| chown -R kiwipanel:kiwisecure "$INSTALL_DIR" | |
| # Directories: rwx for owner+group | |
| find "$INSTALL_DIR" -type d -exec chmod 750 {} \; | |
| # Files: rw for owner+group | |
| find "$INSTALL_DIR" -type f -exec chmod 640 {} \; | |
| # Real binaries: executable by owner+group only | |
| chmod 750 "$INSTALL_DIR/bin/kiwipanel" | |
| # Agent binary (runs as root, needs to be executable) | |
| if [[ -f "$INSTALL_DIR/bin/kiwipanel-agent" ]]; then | |
| chmod 755 "$INSTALL_DIR/bin/kiwipanel-agent" | |
| fi | |
| # Ensure config directory is readable by kiwipanel user | |
| if [[ -d "$INSTALL_DIR/config" ]]; then | |
| chown -R kiwipanel:kiwisecure "$INSTALL_DIR/config" | |
| chmod 750 "$INSTALL_DIR/config" | |
| find "$INSTALL_DIR/config" -type f -exec chmod 640 {} \; | |
| fi | |
| } | |
| function FixScriptPermissions() { | |
| PrintInfo "==> Setting script permissions in /opt/kiwipanel/scripts" | |
| local SCRIPT_DIR="$INSTALL_DIR/scripts" | |
| if [ -d "$SCRIPT_DIR" ]; then | |
| # Make scripts executable by owner and group only | |
| find "$SCRIPT_DIR" -type f -exec chmod 750 {} \; | |
| # Change ownership to kiwipanel user and kiwisecure group | |
| chown -R kiwipanel:kiwisecure "$SCRIPT_DIR" | |
| PrintGreen "✓ Scripts are now executable by kiwipanel user" | |
| else | |
| PrintInfo "==> No scripts directory found at $SCRIPT_DIR" | |
| fi | |
| } | |
| function CreateSystemDirectories() { | |
| PrintInfo "==> Creating KiwiPanel system directories" | |
| mkdir -p /etc/kiwipanel | |
| mkdir -p /var/log/kiwipanel | |
| mkdir -p /var/lib/kiwipanel/update | |
| mkdir -p /run/kiwipanel | |
| chown -R kiwipanel:kiwisecure /etc/kiwipanel | |
| chown -R kiwipanel:kiwisecure /var/log/kiwipanel | |
| chown -R kiwipanel:kiwisecure /var/lib/kiwipanel | |
| chown root:kiwisecure /run/kiwipanel | |
| chmod 750 /etc/kiwipanel | |
| chmod 750 /var/log/kiwipanel | |
| chmod 750 /var/lib/kiwipanel | |
| chmod 750 /var/lib/kiwipanel/update | |
| chmod 750 /run/kiwipanel | |
| } | |
| function FetchingInstallScripts(){ | |
| PrintInfo "Fetching KiwiPanel installation script." | |
| PrepareDirectories | |
| CreateGroupAndUser | |
| CreateSystemDirectories | |
| ShowVersion | |
| DownloadPackage | |
| ExtractPackage | |
| FixPermissions | |
| FixScriptPermissions | |
| } | |
| FetchingInstallScripts | |
| source "/opt/kiwipanel/scripts/kiwi" | |
| InstallFirstKiwiBinary | |
| InstallAgentBinary | |
| # Detect the system's architecture | |
| ARCHITECTURE=$(uname -m) | |
| # | |
| kiwipanel_d_start_time=$(date +%s) | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| NC='\033[0m' #NC: Not color: reset color to the default | |
| IPADDRESS=$(curl -s --connect-timeout 5 https://cpanel.net/showip.cgi || hostname -I | awk '{print $1}') | |
| [[ -z "$IPADDRESS" ]] && IPADDRESS="<YOUR_SERVER_IP>" | |
| DIR=$(pwd) | |
| source "/opt/kiwipanel/scripts/utility" | |
| source "/opt/kiwipanel/scripts/checksystem" | |
| source "/opt/kiwipanel/scripts/checkscript" | |
| CheckSystemRequirements | |
| ################################################################################### | |
| function CheckArchitecture() { | |
| PrintInfo "==> Checking architechture. Kiwipanel supports 64 bits system only!" | |
| # Ref: https://raw.githubusercontent.com/runtipi/runtipi/master/scripts/install.sh | |
| # Not supported on 32 bits systems. | |
| # Note: aarch64 [ARM 64-bit (also known as AArch64), armv7: 32-bit ARM] | |
| local ARCHITECTURE="$(uname -m)" | |
| if [[ "$ARCHITECTURE" == "armv7"* ]] || [[ "$ARCHITECTURE" == "i686" ]] || [[ "$ARCHITECTURE" == "i386" ]]; then | |
| PrintRed "KiwiPanel currently does not support the 32 bits systems" | |
| exit 1 | |
| fi | |
| } | |
| # ========================= | |
| # OS Detection (refactored) | |
| # ========================= | |
| function LoadOsRelease() { | |
| local file="${1:-/etc/os-release}" | |
| if [[ ! -f "$file" ]]; then | |
| echo "ERROR: Cannot detect operating system (missing /etc/os-release)" >&2 | |
| exit 1 | |
| fi | |
| # Method: Use grep and cut (most reliable) | |
| OS_ID=$(grep '^ID=' "$file" | cut -d'=' -f2 | tr -d '"' | tr -d "'" | xargs) | |
| VERSION_ID=$(grep '^VERSION_ID=' "$file" | cut -d'=' -f2 | tr -d '"' | tr -d "'" | xargs) | |
| OS_VERSION_MAJOR="${VERSION_ID%%.*}" | |
| OS_LIKE=$(grep '^ID_LIKE=' "$file" | cut -d'=' -f2 | tr -d '"' | tr -d "'" | xargs || echo "") | |
| OS_NAME=$(grep '^PRETTY_NAME=' "$file" | cut -d'=' -f2 | tr -d '"' | tr -d "'" | xargs) | |
| # Fallback for OS_NAME | |
| if [[ -z "$OS_NAME" ]]; then | |
| OS_NAME=$(grep '^NAME=' "$file" | cut -d'=' -f2 | tr -d '"' | tr -d "'" | xargs) | |
| fi | |
| # Validate critical variables | |
| if [[ -z "$OS_ID" ]]; then | |
| echo "ERROR: Failed to detect OS_ID from $file" >&2 | |
| echo "File contents:" >&2 | |
| cat "$file" >&2 | |
| exit 1 | |
| fi | |
| if [[ -z "$OS_VERSION_MAJOR" ]]; then | |
| echo "WARNING: Could not parse VERSION_ID, defaulting to 9" >&2 | |
| OS_VERSION_MAJOR="9" | |
| fi | |
| if [[ -z "$OS_NAME" ]]; then | |
| OS_NAME="$OS_ID $OS_VERSION_MAJOR" | |
| fi | |
| # Success message | |
| if declare -f PrintInfo >/dev/null 2>&1; then | |
| PrintInfo "==> Detected: $OS_NAME (ID: $OS_ID, Version: $OS_VERSION_MAJOR)" | |
| else | |
| echo "Detected: $OS_NAME (ID: $OS_ID, Version: $OS_VERSION_MAJOR)" | |
| fi | |
| # Debug logging | |
| if [[ "${DEBUG:-0}" == "1" ]]; then | |
| echo "DEBUG: OS_ID=[$OS_ID]" >&2 | |
| echo "DEBUG: OS_VERSION_MAJOR=[$OS_VERSION_MAJOR]" >&2 | |
| echo "DEBUG: OS_LIKE=[$OS_LIKE]" >&2 | |
| echo "DEBUG: OS_NAME=[$OS_NAME]" >&2 | |
| fi | |
| } | |
| function CheckOs() { | |
| PrintInfo "==> Checking operating system compatibility..." | |
| LoadOsRelease | |
| PrintInfo "==> Detected OS: ${OS_NAME}" | |
| PrintInfo "==> Kernel: $(uname -r)" | |
| # ---- Reject CentOS Stream explicitly ---- | |
| if [[ "$OS_ID" == "centos" && "$OS_NAME" =~ Stream ]]; then | |
| PrintRed "CentOS Stream is not supported." | |
| PrintRed "Please use Rocky Linux or AlmaLinux instead." | |
| exit 1 | |
| fi | |
| case "${OS_ID}" in | |
| debian) | |
| if (( OS_VERSION_MAJOR < 11 )); then | |
| PrintRed "Debian ${VERSION_ID} is not supported." | |
| PrintRed "Please use Debian 11, 12, or 13." | |
| exit 1 | |
| fi | |
| ;; | |
| ubuntu) | |
| if (( OS_VERSION_MAJOR < 22 )); then | |
| PrintRed "Ubuntu ${VERSION_ID} is not supported." | |
| PrintRed "Please use Ubuntu 22.04 or newer." | |
| exit 1 | |
| fi | |
| ;; | |
| rhel|rocky|almalinux|ol|cloudlinux|vzlinux) | |
| if (( OS_VERSION_MAJOR < 9 )); then | |
| PrintRed "${OS_NAME} (version ${OS_VERSION_MAJOR}) is not supported." | |
| PrintRed "KiwiPanel requires systemd 249+ which is not available on EL8 distributions." | |
| PrintRed "Please use version 9 or newer (Rocky Linux 9, AlmaLinux 9, RHEL 9, etc.)." | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| if [[ "${OS_LIKE}" == *"rhel"* ]]; then | |
| if (( OS_VERSION_MAJOR < 9 )); then | |
| PrintRed "${OS_NAME} (version ${OS_VERSION_MAJOR}) is not supported." | |
| PrintRed "KiwiPanel requires systemd 249+. Please use version 9 or newer." | |
| exit 1 | |
| fi | |
| else | |
| PrintRed "Unsupported operating system." | |
| PrintRed "Supported: Debian 11+, Ubuntu 22.04+, Rocky/Alma/RHEL 9+" | |
| exit 1 | |
| fi | |
| ;; | |
| esac | |
| PrintGreen "✔ Operating system is supported." | |
| } | |
| #Check port https://ping.eu/port-chk/ | |
| function CheckPortUsage() { | |
| local port="$1" | |
| PrintInfo "==> Checking port $port..." | |
| if ss -tuln | awk '{print $5}' | grep -q ":$port$"; then | |
| echo "❌ Port $port is already in use. KiwiPanel needs this port." | |
| return 1 | |
| fi | |
| PrintGreen "✔ Port $port is free." | |
| } | |
| function CheckSystemdVersion() { | |
| PrintInfo "==> Checking systemd version..." | |
| local MIN_SYSTEMD_VERSION=249 | |
| if ! command -v systemctl >/dev/null 2>&1; then | |
| PrintRed "systemctl not found. KiwiPanel requires systemd ${MIN_SYSTEMD_VERSION}+." | |
| exit 1 | |
| fi | |
| local systemd_ver | |
| systemd_ver=$(systemctl --version 2>/dev/null | head -1 | grep -oP 'systemd\s+\K\d+' || echo "0") | |
| if [[ -z "$systemd_ver" || "$systemd_ver" -eq 0 ]]; then | |
| PrintRed "Failed to detect systemd version." | |
| exit 1 | |
| fi | |
| if (( systemd_ver < MIN_SYSTEMD_VERSION )); then | |
| PrintRed "systemd ${systemd_ver} is too old. KiwiPanel requires systemd ${MIN_SYSTEMD_VERSION}+." | |
| PrintRed "Your system's systemd does not support required security features." | |
| PrintRed "Please upgrade to a newer OS (e.g., Rocky/AlmaLinux 9, Debian 12, Ubuntu 22.04)." | |
| exit 1 | |
| fi | |
| PrintGreen "✔ systemd ${systemd_ver} (>= ${MIN_SYSTEMD_VERSION})" | |
| } | |
| function CheckConditionBeforeInstall() { | |
| PrintInfo "==> Evaluating your VPS before installing KiwiPanel....." | |
| CheckOs | |
| CheckSystemdVersion | |
| CheckArchitecture | |
| CheckPanelScript | |
| CheckPortUsage 8443 || exit 1 | |
| } | |
| #------------------------------------------------------------Create passcode to protect login page | |
| function GenerateThePasscode() { | |
| local META_DIR="/opt/kiwipanel/meta" | |
| local PASSCODE_FILE="$META_DIR/passcode" | |
| local INSTALL_MARKER="$META_DIR/.installed" | |
| mkdir -p "$META_DIR" | |
| if [[ -f "$INSTALL_MARKER" && -f "$PASSCODE_FILE" ]]; then | |
| kiwipanel_passcode=$(tr -d '\n' < "$PASSCODE_FILE") | |
| PrintGreen "✔ Existing passcode preserved." | |
| return | |
| fi | |
| kiwipanel_passcode=$(GenerateRandomString) | |
| printf '%s\n' "$kiwipanel_passcode" > "$PASSCODE_FILE" | |
| chown kiwipanel:kiwisecure "$PASSCODE_FILE" | |
| chmod 640 "$PASSCODE_FILE" | |
| touch "$INSTALL_MARKER" | |
| chown kiwipanel:kiwisecure "$INSTALL_MARKER" | |
| chmod 640 "$INSTALL_MARKER" | |
| PrintGreen "✔ New passcode generated." | |
| } | |
| #------------------------------------------------------------Generate terminal token for global terminal access | |
| # This token is required to access the global (root) terminal via web UI | |
| # SECURITY: We store SHA-256 hash of the token, not the plain text | |
| function GenerateTerminalToken() { | |
| local META_DIR="/opt/kiwipanel/meta" | |
| local TOKEN_FILE="$META_DIR/terminal_token" | |
| mkdir -p "$META_DIR" | |
| # Always generate a new unique token (don't reuse from package) | |
| # Generate a secure random 32-character token | |
| terminal_token=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 32) | |
| # Hash the token using SHA-256 before storing | |
| # IMPORTANT: Use printf to avoid newline issues (echo -n behaves differently on some systems) | |
| # The plain token is shown to user only once during install | |
| local token_hash | |
| token_hash=$(printf '%s' "$terminal_token" | sha256sum | cut -d' ' -f1) | |
| printf '%s\n' "$token_hash" > "$TOKEN_FILE" | |
| # Set ownership and permissions | |
| # Token hash file should be readable by kiwipanel user (for web service) | |
| chown kiwipanel:kiwisecure "$TOKEN_FILE" | |
| chmod 640 "$TOKEN_FILE" | |
| PrintGreen "✔ Terminal token generated (stored as hash)." | |
| } | |
| # #------------------------------------------------------------Installing systemd service file | |
| #------------------------------------------------------------ | |
| # Helper: Installs a single unit file (copies, permissions, backup) | |
| #------------------------------------------------------------ | |
| function install_unit_file() { | |
| local unit_name="$1" | |
| local service_src="$INSTALL_DIR/systemd/$unit_name" | |
| local service_dst="/etc/systemd/system/$unit_name" | |
| # Validate source | |
| if [[ ! -f "$service_src" ]]; then | |
| PrintRed "Unit file not found: ${service_src}" | |
| return 1 | |
| fi | |
| # Backup existing | |
| if [[ -f "$service_dst" ]]; then | |
| cp "$service_dst" "${service_dst}.bak" | |
| fi | |
| # Copy and set permissions | |
| cp "$service_src" "$service_dst" | |
| chmod 644 "$service_dst" | |
| PrintInfo "Installed unit: $unit_name" | |
| } | |
| #------------------------------------------------------------ | |
| # Main Function: Installs and Starts Services | |
| #------------------------------------------------------------ | |
| function InstallSystemdService() { | |
| PrintInfo "==> Installing Systemd Services..." | |
| # 1. Install the unit files | |
| install_unit_file "kiwipanel.service" || return 1 | |
| # Only install update service/path if they exist in source | |
| if [[ -f "$INSTALL_DIR/systemd/kiwipanel-update.service" ]]; then | |
| install_unit_file "kiwipanel-update.service" || return 1 | |
| fi | |
| if [[ -f "$INSTALL_DIR/systemd/kiwipanel-update.path" ]]; then | |
| install_unit_file "kiwipanel-update.path" || return 1 | |
| fi | |
| # Install agent service and socket | |
| if [[ -f "$INSTALL_DIR/systemd/kiwipanel-agent.service" ]]; then | |
| install_unit_file "kiwipanel-agent.service" || return 1 | |
| fi | |
| if [[ -f "$INSTALL_DIR/systemd/kiwipanel-agent.socket" ]]; then | |
| install_unit_file "kiwipanel-agent.socket" || return 1 | |
| fi | |
| # 2. Reload Daemon (Do this once for all files) | |
| if ! systemctl daemon-reload; then | |
| PrintRed "Failed to reload systemd daemon" | |
| return 1 | |
| fi | |
| # 3. Enable Services | |
| PrintInfo "==> Enabling services..." | |
| if ! systemctl enable kiwipanel.service >/dev/null; then | |
| PrintRed "Failed to enable kiwipanel.service" | |
| return 1 | |
| fi | |
| if [[ -f "/etc/systemd/system/kiwipanel-update.service" ]]; then | |
| systemctl enable kiwipanel-update.service >/dev/null || true | |
| fi | |
| # Enable path unit (it monitors for changes) | |
| if [[ -f "/etc/systemd/system/kiwipanel-update.path" ]]; then | |
| if ! systemctl enable kiwipanel-update.path >/dev/null; then | |
| PrintWarn "Failed to enable kiwipanel-update.path" | |
| fi | |
| fi | |
| # Enable agent socket and service | |
| [[ -f "/etc/systemd/system/kiwipanel-agent.socket" ]] && \ | |
| systemctl enable kiwipanel-agent.socket >/dev/null | |
| [[ -f "/etc/systemd/system/kiwipanel-agent.service" ]] && \ | |
| systemctl enable kiwipanel-agent.service >/dev/null | |
| # 4. Start Main Service with Health Checks | |
| PrintInfo "==> Starting KiwiPanel service..." | |
| # Stop first to ensure clean restart | |
| systemctl stop kiwipanel.service 2>/dev/null | |
| if ! systemctl start kiwipanel.service; then | |
| PrintRed "Failed to start service" | |
| systemctl status kiwipanel.service --no-pager || true | |
| journalctl -u kiwipanel.service -n 20 --no-pager || true | |
| return 1 | |
| fi | |
| # 5. Start path unit to begin monitoring | |
| if [[ -f "/etc/systemd/system/kiwipanel-update.path" ]]; then | |
| PrintInfo "==> Starting update monitor..." | |
| if ! systemctl start kiwipanel-update.path; then | |
| PrintWarn "Failed to start kiwipanel-update.path (auto-updates may not work)" | |
| else | |
| PrintGreen "✓ Update monitor started" | |
| fi | |
| fi | |
| # Start agent socket first for socket activation | |
| if [[ -f "/etc/systemd/system/kiwipanel-agent.socket" ]]; then | |
| systemctl start kiwipanel-agent.socket && PrintGreen "✓ Agent socket started" | |
| fi | |
| # Start agent service | |
| if [[ -f "/etc/systemd/system/kiwipanel-agent.service" ]]; then | |
| systemctl start kiwipanel-agent.service && PrintGreen "✓ Agent service started" | |
| fi | |
| # 6. Wait for service to be ready | |
| PrintInfo "==> Waiting for service to become ready..." | |
| local timeout=60 | |
| local elapsed=0 | |
| while (( elapsed < timeout )); do | |
| if systemctl is-active --quiet kiwipanel.service; then | |
| # Double check: wait 1s and check again to ensure it didn't crash immediately | |
| sleep 1 | |
| if systemctl is-active --quiet kiwipanel.service; then | |
| PrintGreen "✓ KiwiPanel service started successfully (${elapsed}s)" | |
| return 0 | |
| fi | |
| fi | |
| sleep 1 | |
| ((elapsed++)) | |
| done | |
| # Timeout reached | |
| PrintWarn "⚠ Service did not start within ${timeout}s" | |
| systemctl status kiwipanel.service --no-pager || true | |
| PrintInfo "Recent logs:" | |
| journalctl -u kiwipanel.service -n 15 --no-pager || true | |
| return 1 | |
| } | |
| function Cleanup() { | |
| rm -f "$TMP_ZIP" | |
| rm -f "$TMP_BINARY" | |
| rm -f "$TMP_AGENT_BINARY" | |
| rm -rf /tmp/kiwipanel-* 2>/dev/null || true | |
| # Clean up any failed extraction attempts | |
| if [[ -d /opt/kiwipanel.tmp ]]; then | |
| rm -rf /opt/kiwipanel.tmp | |
| fi | |
| } | |
| function DisableSelinuxRuntime() { | |
| # Only applies to systems with SELinux tools | |
| if ! command -v getenforce >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| local mode | |
| mode="$(getenforce 2>/dev/null || echo Disabled)" | |
| case "$mode" in | |
| Enforcing|Permissive) | |
| PrintWarn "Disabling SELinux enforcement (runtime). Consider configuring SELinux policies for production." | |
| setenforce 0 || true | |
| ;; | |
| Disabled) | |
| PrintInfo "==> SELinux already disabled" | |
| ;; | |
| *) | |
| PrintWarn "Unknown SELinux mode: $mode (ignored)" | |
| ;; | |
| esac | |
| return 0 | |
| } | |
| function VerifyKiwipanel(){ | |
| PrintInfo "==> Verifying ownership" | |
| for path in /opt/kiwipanel /etc/kiwipanel /var/log/kiwipanel /var/lib/kiwipanel; do | |
| owner=$(stat -c "%U:%G" "$path") | |
| if [ "$owner" != "kiwipanel:kiwisecure" ]; then | |
| PrintWarn "$path ownership is $owner (expected kiwipanel:kiwisecure)" | |
| fi | |
| done | |
| } | |
| function SuccessMessage() { | |
| PrintGreen "✔ KiwiPanel has been successfully installed on your VPS!" | |
| kiwipanel_d_end_time=$(date +%s) | |
| elapsed=$((kiwipanel_d_end_time - kiwipanel_d_start_time)) | |
| printf " \n" | |
| echo "$(tput setaf 2)Total time installed: ${elapsed} seconds.$(tput sgr0)" | |
| printf " \n" | |
| echo "$(tput setaf 2)================================Install KiwiPanel Done!==============================$(tput sgr0)" | |
| echo "" | |
| echo " Login at : https://$IPADDRESS:$KIWIPANEL_PORT/$kiwipanel_passcode" | |
| echo " User: ${admin_user}" | |
| echo " Password: ${admin_pass}" | |
| echo "" | |
| echo "$(tput setaf 3) ⚠️ IMPORTANT - Save these credentials now!$(tput sgr0)" | |
| echo "" | |
| echo " Terminal Token: ${terminal_token}" | |
| echo " (This token is shown ONLY ONCE. Use it at Dashboard > Terminal)" | |
| echo " To rotate: kiwipanel terminal rotate" | |
| echo "" | |
| echo " Database information:" | |
| cat /etc/kiwipanel/secrets.env | |
| echo "" | |
| echo "$(tput setaf 2)=====================================================================================$(tput sgr0)" | |
| LogInfo "Installation completed successfully" | |
| } | |
| source "/opt/kiwipanel/scripts/wrapper" | |
| function InstallKiwipanel() { | |
| local config_file="/opt/kiwipanel/config/kiwipanel.toml" | |
| if [[ ! -f "$config_file" ]]; then | |
| PrintWarn "Config file not found, generating now..." | |
| WriteToConfig || { | |
| PrintRed "Failed to generate config file" | |
| return 1 | |
| } | |
| fi | |
| InstallWrapper | |
| DisableDefaultMotd | |
| GenerateThePasscode | |
| GenerateTerminalToken | |
| InstallSystemdService | |
| InstallLoginBanner | |
| } | |
| # ------------------------------------------------------------------------------ | |
| # Main flow | |
| # ------------------------------------------------------------------------------ | |
| function InstallLitespeedServer() { | |
| InstallingOpenLiteSpeedRepo | |
| #Updated: Using mask to prevent | |
| GuardOlsServiceDebianUbuntu | |
| InstallingOpenLiteSpeed | |
| InstallingLiteSpeedPhp | |
| EnsureAdminPassword | |
| SetupKiwiwebUser | |
| ConfigureLswsListener | |
| Disable7080 | |
| # NO restart here - just configure | |
| PrintGreen "OpenLiteSpeed configuration complete." | |
| } | |
| #================================= Fetching source file from github================================= | |
| source "/opt/kiwipanel/scripts/loginbanner" | |
| source "/opt/kiwipanel/scripts/welcome" | |
| source "/opt/kiwipanel/scripts/mariadb" | |
| source "/opt/kiwipanel/scripts/firewall" | |
| source "/opt/kiwipanel/scripts/kiwipanelssl" | |
| source "/opt/kiwipanel/scripts/base" | |
| source "/opt/kiwipanel/scripts/swap" | |
| source "/opt/kiwipanel/scripts/createadminuser" | |
| source "/opt/kiwipanel/scripts/lsws" | |
| function WriteToConfig(){ | |
| # 1. Set critical variables FIRST | |
| export KIWIPANEL_ADMIN_USER="admin" # or your generated value | |
| export KIWIPANEL_ADMIN_PASS="$kiwipanel_passcode" | |
| # Load the real DB password generated by InstallAndConfigureMariaDB | |
| # (stored in /etc/kiwipanel/secrets.env by the mariadb script) | |
| if [[ -f /etc/kiwipanel/secrets.env ]]; then | |
| DB_PASS="$(grep '^DB_PASS=' /etc/kiwipanel/secrets.env | cut -d'=' -f2- | tr -d '\n')" | |
| export DB_PASS | |
| fi | |
| if [[ -z "${DB_PASS:-}" ]]; then | |
| PrintRed "DB_PASS not found in /etc/kiwipanel/secrets.env. Did MariaDB install complete?" | |
| return 1 | |
| fi | |
| # 2. Source the scripts | |
| source "/opt/kiwipanel/scripts/writeconfigdata" || { | |
| PrintRed "Failed to source writeconfigdata" | |
| return 1 | |
| } | |
| source "/opt/kiwipanel/scripts/writeconfig" || { | |
| PrintRed "Failed to source writeconfig" | |
| return 1 | |
| } | |
| # 3. Generate config | |
| if ! GenerateInitialConfig; then | |
| PrintRed "Failed to generate configuration file" | |
| return 1 | |
| fi | |
| PrintGreen "✓ Configuration file created successfully" | |
| } | |
| # ============================================================================== | |
| # Main Phase: Step Functions + Verify Functions | |
| # ============================================================================== | |
| # Step 5: Base system | |
| step_base_system() { InstallAndConfigureBase; } | |
| verify_base_system() { | |
| # Cron must be enabled | |
| ensure_service_active "cron" || ensure_service_active "crond" || return 1 | |
| return 0 | |
| } | |
| # Step 6: Swap | |
| step_swap() { CreateSwap; } | |
| verify_swap() { | |
| # Either swap is active OR system has plenty of RAM (>4GB) | |
| if swapon --show 2>/dev/null | grep -q '/'; then | |
| return 0 | |
| fi | |
| local ram_mb | |
| ram_mb=$(free -m | awk '/Mem:/ {print $2}') | |
| if (( ram_mb > 4096 )); then | |
| return 0 # enough RAM, swap optional | |
| fi | |
| return 1 | |
| } | |
| # Step 7: Firewall | |
| step_firewall() { InstallAndConfigureFirewall; } | |
| verify_firewall() { | |
| if command -v ufw >/dev/null 2>&1; then | |
| ufw status | grep -q "Status: active" || return 1 | |
| ufw status | grep -q "8443" || return 1 | |
| elif command -v firewall-cmd >/dev/null 2>&1; then | |
| firewall-cmd --state &>/dev/null || return 1 | |
| firewall-cmd --list-ports 2>/dev/null | grep -q "8443" || \ | |
| firewall-cmd --list-all 2>/dev/null | grep -q "8443" || return 1 | |
| else | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Step 8: SSL | |
| step_ssl() { InstallAndConfigureSSL; } | |
| verify_ssl() { | |
| local cert="/opt/kiwipanel/ssl/panel/kiwipanel.crt" | |
| local key="/opt/kiwipanel/ssl/panel/kiwipanel.key" | |
| ensure_file_exists "$cert" || return 1 | |
| ensure_file_exists "$key" || return 1 | |
| # Not expired? | |
| openssl x509 -in "$cert" -checkend 0 &>/dev/null || return 1 | |
| return 0 | |
| } | |
| # Step 9: MariaDB | |
| step_mariadb() { InstallAndConfigureMariaDB; } | |
| verify_mariadb() { | |
| # Package installed? | |
| command -v mariadbd >/dev/null 2>&1 || command -v mysqld >/dev/null 2>&1 || return 1 | |
| # Service running? | |
| ensure_service_active "mariadb" || ensure_service_active "mysql" || return 1 | |
| # Secrets file exists with DB_PASS? | |
| [[ -f /etc/kiwipanel/secrets.env ]] || return 1 | |
| grep -q "^DB_PASS=" /etc/kiwipanel/secrets.env 2>/dev/null || return 1 | |
| return 0 | |
| } | |
| # Step 10: Config | |
| step_config() { WriteToConfig; } | |
| verify_config() { | |
| local config="/opt/kiwipanel/config/kiwipanel.toml" | |
| ensure_file_exists "$config" || return 1 | |
| [[ -s "$config" ]] || return 1 # non-empty | |
| return 0 | |
| } | |
| # Step 11: Panel (systemd, passcode, token, etc.) | |
| step_panel() { InstallKiwipanel; } | |
| verify_panel() { | |
| ensure_file_exists "/etc/systemd/system/kiwipanel.service" || return 1 | |
| ensure_file_exists "/opt/kiwipanel/meta/passcode" || return 1 | |
| ensure_file_exists "/opt/kiwipanel/meta/terminal_token" || return 1 | |
| ensure_service_active "kiwipanel" || return 1 | |
| return 0 | |
| } | |
| # Step 12: OLS | |
| step_ols() { | |
| InstallLitespeedServer | |
| VerifyLswsStopped | |
| UnguardOpenLiteSpeedService | |
| StartLswsOnce | |
| } | |
| verify_ols() { | |
| # OLS installed? | |
| ensure_dir_exists "/usr/local/lsws" || return 1 | |
| # Service active? | |
| ensure_service_active "lsws" || ensure_service_active "lshttpd" || return 1 | |
| # Port 80 responding? | |
| ss -tlnp 2>/dev/null | grep -q ':80 ' || return 1 | |
| return 0 | |
| } | |
| # Step 13: Finalize | |
| step_finalize() { | |
| VerifyKiwipanel | |
| PostInstallCheck | |
| CreateAdminUser | |
| } | |
| verify_finalize() { | |
| # Panel service must still be running after finalize | |
| ensure_service_active "kiwipanel" || return 1 | |
| # Admin user creation writes to the database; check the DB file exists | |
| ensure_file_exists "/opt/kiwipanel/data/kiwipanel.db" || return 1 | |
| return 0 | |
| } | |
| # ============================================================================== | |
| # Main | |
| # ============================================================================== | |
| function main() { | |
| Welcome | |
| CheckConditionBeforeInstall | |
| mkdir -p "$INSTALL_STATE_DIR" | |
| run_step "base_system" step_base_system verify_base_system | |
| run_step "swap" step_swap verify_swap | |
| run_step "firewall" step_firewall verify_firewall | |
| run_step "ssl" step_ssl verify_ssl | |
| run_step "mariadb" step_mariadb verify_mariadb | |
| run_step "config" step_config verify_config | |
| run_step "panel" step_panel verify_panel | |
| run_step "ols" step_ols verify_ols | |
| run_step "finalize" step_finalize verify_finalize | |
| SuccessMessage | |
| Cleanup | |
| } | |
| main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment