#!/usr/bin/env bash # Absolute DB v9.5.1 # Copyright (c) 2024-2026 D.H.Maree. All rights reserved. # Author/Creator: David H Maree # Owner: D.H.Maree (ABN 21 498 105 915) — sole IP holder # Licensed to: SupportCALL AU — primary distributor # SPDX-License-Identifier: BSL-1.1 # https://absolutedb.com/ # https://downloads.absolutedb.com/install-rhel.sh # Copyright 2024–2026 SupportCALL AU & D.H.Maree — SPDX-License-Identifier: BSL-1.1 # # Supported distros: # RHEL 8 / 9 # CentOS Stream 8 / 9 # AlmaLinux 8 / 9 # Rocky Linux 8 / 9 # Fedora 38 / 39 / 40 # # Usage: # bash install-rhel.sh [OPTIONS] # # Options: # --no-service Skip systemd service creation # --no-test Skip post-install verification tests # --uninstall Remove Absolute DB from this system # --prefix= Override installation prefix (default: /usr/local) # --branch= Clone a specific git branch (default: main) # --help Show this help message set -euo pipefail # --------------------------------------------------------------------------- # Global constants # --------------------------------------------------------------------------- VERSION="9.5.1" REPO="https://github.com/supportcall/AbsoluteDB.git" INSTALL_DIR="${HOME}/AbsoluteDB" PREFIX="/usr/local" BIN_DIR="${PREFIX}/bin" DATA_DIR="/var/lib/absdb" LOG_DIR="/var/log/absdb" SERVICE_USER="absdb" PG_PORT=5433 REST_PORT=8080 GRPC_PORT=9090 BRANCH="main" TOTAL_STEPS=11 # Flags (may be overridden by CLI args) DO_SERVICE=true DO_TEST=true DO_UNINSTALL=false # Package manager (detected at runtime: dnf or yum) PKG_MGR="" # --------------------------------------------------------------------------- # Colors and output helpers # --------------------------------------------------------------------------- RED='\033[0;31m' GRN='\033[0;32m' YLW='\033[0;33m' BLU='\033[0;34m' CYN='\033[0;36m' MAG='\033[0;35m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' # Disable colors when not a terminal if [ ! -t 1 ]; then RED=''; GRN=''; YLW=''; BLU=''; CYN=''; MAG=''; BOLD=''; DIM=''; NC='' fi info() { echo -e "${BLU}[INFO]${NC} $*"; } ok() { echo -e "${GRN}[ OK ]${NC} $*"; } warn() { echo -e "${YLW}[WARN]${NC} $*"; } step() { echo -e "\n${BOLD}${CYN}━━━ Step ${1}/${TOTAL_STEPS}: ${2} ${NC}"; } hdr() { echo -e "${BOLD}${MAG}$*${NC}"; } die() { echo -e "\n${RED}${BOLD}[FAIL]${NC}${RED} $* ${NC}" >&2 echo -e "\n${BOLD}${YLW}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}${YLW}║ TROUBLESHOOTING GUIDE ║${NC}" echo -e "${BOLD}${YLW}╚══════════════════════════════════════════════════════════════╝${NC}" cat >&2 <<'TROUBLE' 1. sudo not found → As root: dnf install -y sudo (RHEL 8/9, Fedora) → yum install -y sudo (RHEL 7 / older CentOS) → Grant access: usermod -aG wheel $USER && newgrp wheel → For passwordless sudo: echo "$USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/$USER 2. gcc not found / GCC too old (need ≥ 9) → RHEL 8/9 / AlmaLinux / Rocky: sudo dnf install -y gcc-toolset-11 scl enable gcc-toolset-11 bash # activate toolset → Or use devtoolset on older RHEL: sudo dnf install -y devtoolset-11-gcc devtoolset-11-gcc-c++ source /opt/rh/devtoolset-11/enable → Fedora: sudo dnf install -y gcc-c++ (ships GCC 13+) 3. git not found → sudo dnf install -y git (or yum install -y git) 4. DNS failure / no internet → ping -c1 8.8.8.8 # test network layer → cat /etc/resolv.conf # check DNS resolver → nmcli general connectivity # NetworkManager check → systemctl restart NetworkManager 5. SSL certificate error (git clone fails) → sudo dnf install -y ca-certificates && sudo update-ca-trust → Or: git -c http.sslVerify=false clone → Corporate proxy: export https_proxy=http://proxy.corp:3128/ 6. make not found → sudo dnf groupinstall -y "Development Tools" → sudo dnf install -y make 7. Permission denied for /usr/local/bin → Run with sudo or as root → Or: bash install-rhel.sh --prefix=$HOME/.local → echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc 8. Port 5433 already in use → sudo ss -tlnp | grep 5433 → sudo systemctl stop postgresql # stop conflicting PostgreSQL → Or: sudo firewall-cmd --add-port=5432/tcp --permanent --zone=public → sudo firewall-cmd --reload 9. systemd not available (Docker containers) → Use --no-service flag → Start manually: absdb-server --data-dir=/var/lib/absdb --daemon 10. libm.so.6 not found at runtime → sudo dnf install -y glibc (provides libm) → sudo ldconfig 11. Test failures / resource limits → ulimit -n 65536 → cat /proc/sys/fs/file-max # system-wide limit → sudo sysctl -w fs.file-max=200000 12. WSL2 specific notes → systemd in WSL2 requires Windows 11 Build 22000+ and WSL 0.67.6+ → Add to /etc/wsl.conf: [boot] \n systemd=true → Then: wsl --shutdown && re-open terminal → RHEL-based images on WSL2 may need: sudo dnf install -y systemd 13. Running as root (Docker containers) → SERVICE_USER will still be created; use --no-service → Docker: mount /var/lib/absdb as a volume for persistence 14. absdb command not found after install → echo $PATH | grep -q /usr/local/bin || echo "MISSING" → echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc → source ~/.bashrc → Or use full path: /usr/local/bin/absdb --version 15. Disk space issues → df -h /usr/local /var /tmp → Minimum: 512 MB free → du -sh /var/lib/absdb 16. SELinux blocking absdb → Check: getenforce (should be Permissive or Enforcing) → Temporary: sudo setenforce 0 → Permanent: sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config → Or add policy: sudo ausearch -c absdb --raw | audit2allow -M absdb → sudo semodule -i absdb.pp → Check denials: sudo ausearch -m avc -ts today | grep absdb → Restore context: sudo restorecon -Rv /usr/local/bin/absdb /var/lib/absdb 17. Firewall blocking ports → sudo firewall-cmd --state # check if running → Open PostgreSQL port: sudo firewall-cmd --add-port=5432/tcp --permanent --zone=public → Open REST port: sudo firewall-cmd --add-port=8080/tcp --permanent --zone=public → Open gRPC port: sudo firewall-cmd --add-port=9090/tcp --permanent --zone=public → Apply changes: sudo firewall-cmd --reload → Or disable temporarily: sudo systemctl stop firewalld 18. EPEL repository issues → RHEL 8: sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm → RHEL 9: sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm → AlmaLinux/Rocky: sudo dnf install -y epel-release → After installing EPEL: sudo dnf update -y → If subscription issues on RHEL: sudo subscription-manager repos --enable=codeready-builder-for-rhel-9-$(arch)-rpms 19. Proxy environment → export http_proxy=http://proxy.corp:3128/ → export https_proxy=http://proxy.corp:3128/ → export no_proxy=localhost,127.0.0.1,::1 → For dnf: /etc/dnf/dnf.conf → add: proxy=http://proxy.corp:3128/ → For git: git config --global http.proxy http://proxy.corp:3128/ TROUBLE echo -e "${DIM}Full log: /tmp/absdb-install-$$.log${NC}" >&2 echo -e "${DIM}Support: https://absolutedb.com/support | community@absolutedb.com${NC}" >&2 exit 1 } # Redirect all output to log file as well exec > >(tee -a /tmp/absdb-install-$$.log) 2>&1 # --------------------------------------------------------------------------- # Banner # --------------------------------------------------------------------------- print_banner() { echo -e "${BOLD}${CYN}" cat <<'BANNER' █████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ ██╗████████╗███████╗ ██████╗ ██████╗ ██╔══██╗██╔══██╗██╔════╝██╔═══██╗██║ ██║ ██║╚══██╔══╝██╔════╝ ██╔══██╗██╔══██╗ ███████║██████╔╝███████╗██║ ██║██║ ██║ ██║ ██║ █████╗ ██║ ██║██████╔╝ ██╔══██║██╔══██╗╚════██║██║ ██║██║ ██║ ██║ ██║ ██╔══╝ ██║ ██║██╔══██╗ ██║ ██║██████╔╝███████║╚██████╔╝███████╗╚██████╔╝ ██║ ███████╗ ██████╔╝██████╔╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝ BANNER echo -e "${NC}" echo -e "${BOLD} Absolute DB v${VERSION} — RHEL / CentOS / Fedora / AlmaLinux / Rocky Installer${NC}" echo -e "${DIM} Unified AI-native database platform | https://absolutedb.com${NC}" echo -e "${DIM} Copyright 2024–2026 SupportCALL AU & D.H.Maree | BSL-1.1${NC}" echo "" echo -e "${DIM} Install log: /tmp/absdb-install-$$.log${NC}" echo "" } # --------------------------------------------------------------------------- # Argument parsing # --------------------------------------------------------------------------- parse_args() { for arg in "$@"; do case "$arg" in --no-service) DO_SERVICE=false ;; --no-test) DO_TEST=false ;; --uninstall) DO_UNINSTALL=true ;; --prefix=*) PREFIX="${arg#*=}"; BIN_DIR="${PREFIX}/bin" ;; --branch=*) BRANCH="${arg#*=}" ;; --help|-h) echo "Usage: bash install-rhel.sh [OPTIONS]" echo "" echo "Options:" echo " --no-service Skip systemd service creation" echo " --no-test Skip post-install verification" echo " --uninstall Remove Absolute DB from this system" echo " --prefix= Override install prefix (default: /usr/local)" echo " --branch= Clone specific git branch (default: main)" echo " --help Show this help" exit 0 ;; *) warn "Unknown argument: $arg (ignored)" ;; esac done } # --------------------------------------------------------------------------- # Sudo detection and helper # --------------------------------------------------------------------------- SUDO="" setup_sudo() { if [ "$(id -u)" -eq 0 ]; then SUDO="" info "Running as root — sudo not required." return fi if ! command -v sudo >/dev/null 2>&1; then die "sudo is not installed. As root run: dnf install -y sudo && usermod -aG wheel $USER" fi if ! sudo -n true 2>/dev/null; then info "This installer requires sudo privileges. You may be prompted for your password." sudo -v || die "sudo authentication failed. Ensure your user is in the 'wheel' group." fi SUDO="sudo" ok "sudo access confirmed." } # --------------------------------------------------------------------------- # Detect package manager (dnf preferred; fall back to yum) # --------------------------------------------------------------------------- detect_pkg_manager() { if command -v dnf >/dev/null 2>&1; then PKG_MGR="dnf" ok "Package manager: dnf" elif command -v yum >/dev/null 2>&1; then PKG_MGR="yum" warn "dnf not found — falling back to yum." else die "Neither dnf nor yum found. This installer requires an RPM-based system." fi } # --------------------------------------------------------------------------- # Distro detection # --------------------------------------------------------------------------- DISTRO_ID="" DISTRO_VERSION="" DISTRO_VERSION_MAJOR=0 IS_FEDORA=false IS_RHEL_COMPATIBLE=false detect_distro() { if [ ! -f /etc/os-release ]; then warn "/etc/os-release not found — assuming RHEL-compatible." DISTRO_ID="rhel" DISTRO_VERSION="8" DISTRO_VERSION_MAJOR=8 return fi # shellcheck disable=SC1091 . /etc/os-release DISTRO_ID="${ID:-unknown}" DISTRO_VERSION="${VERSION_ID:-0}" DISTRO_VERSION_MAJOR=$(echo "$DISTRO_VERSION" | cut -d. -f1) case "$DISTRO_ID" in fedora) IS_FEDORA=true info "Distro: ${BOLD}Fedora ${DISTRO_VERSION}${NC}" ;; rhel|centos|almalinux|rocky|ol) IS_RHEL_COMPATIBLE=true info "Distro: ${BOLD}${NAME:-$DISTRO_ID} ${DISTRO_VERSION}${NC}" ;; *) # Check ID_LIKE if echo "${ID_LIKE:-}" | grep -qi "rhel\|fedora\|centos"; then IS_RHEL_COMPATIBLE=true warn "Unrecognised distro '${DISTRO_ID}' — treating as RHEL-compatible." else die "Unrecognised distro '${DISTRO_ID}'. This script supports RHEL/CentOS/Fedora/AlmaLinux/Rocky." fi ;; esac } # --------------------------------------------------------------------------- # Systemd availability check # --------------------------------------------------------------------------- SYSTEMD_AVAILABLE=false check_systemd() { if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then if systemctl is-system-running --quiet 2>/dev/null || \ systemctl status --quiet 2>/dev/null; then SYSTEMD_AVAILABLE=true fi fi if [ "$SYSTEMD_AVAILABLE" = false ]; then warn "systemd is not active on this system (Docker / minimal container?)." warn "Service will not be installed. Start manually: absdb-server --daemon" DO_SERVICE=false fi } # --------------------------------------------------------------------------- # SELinux detection # --------------------------------------------------------------------------- SELINUX_ENFORCING=false check_selinux() { if command -v getenforce >/dev/null 2>&1; then local mode mode=$(getenforce 2>/dev/null || echo "Disabled") info "SELinux mode: ${mode}" if [ "$mode" = "Enforcing" ]; then SELINUX_ENFORCING=true warn "SELinux is Enforcing. If absdb fails to start, see troubleshooting item 16." warn "Quick fix: sudo setenforce 0 (then configure policy — see troubleshooting)" fi fi } # --------------------------------------------------------------------------- # Firewalld detection # --------------------------------------------------------------------------- check_firewalld() { if command -v firewall-cmd >/dev/null 2>&1; then if systemctl is-active --quiet firewalld 2>/dev/null; then warn "firewalld is active. Ports ${PG_PORT}, ${REST_PORT}, ${GRPC_PORT} must be open." warn "Run after install:" warn " sudo firewall-cmd --add-port=${PG_PORT}/tcp --permanent --zone=public" warn " sudo firewall-cmd --add-port=${REST_PORT}/tcp --permanent --zone=public" warn " sudo firewall-cmd --add-port=${GRPC_PORT}/tcp --permanent --zone=public" warn " sudo firewall-cmd --reload" fi fi } # --------------------------------------------------------------------------- # Disk space check # --------------------------------------------------------------------------- check_disk_space() { local required_kb=1572864 # 1.5 GB (build objects + static lib) local available_kb available_kb=$(df -k "${HOME}" 2>/dev/null | awk 'NR==2{print $4}' || echo 0) if [ "$available_kb" -lt "$required_kb" ]; then die "Insufficient disk space. Need ≥ 1.5 GB free in ${HOME}. Have: $(( available_kb / 1024 )) MB." fi ok "Disk space: $(( available_kb / 1024 )) MB available — sufficient." } # --------------------------------------------------------------------------- # Network check (non-fatal) # --------------------------------------------------------------------------- check_network() { if ! ping -c1 -W3 8.8.8.8 >/dev/null 2>&1 && \ ! curl -s --connect-timeout 5 https://github.com >/dev/null 2>&1; then warn "Cannot reach the internet. Check connectivity or proxy settings." warn "Set: export https_proxy=http://proxy.corp:3128/" warn "For dnf proxy: add 'proxy=http://proxy.corp:3128/' to /etc/dnf/dnf.conf" else ok "Network connectivity confirmed." fi } # --------------------------------------------------------------------------- # Enable CodeReady Builder / PowerTools (provides devel packages on RHEL 8/9) # --------------------------------------------------------------------------- enable_crb_if_needed() { if [ "$IS_FEDORA" = true ]; then return # Fedora doesn't need CRB fi # Try to enable CodeReady Builder (RHEL 9) or PowerTools (RHEL 8 / CentOS) if command -v subscription-manager >/dev/null 2>&1 && \ subscription-manager status --timeout=5 >/dev/null 2>&1; then # Registered RHEL if [ "$DISTRO_VERSION_MAJOR" -ge 9 ]; then $SUDO subscription-manager repos \ --enable="codeready-builder-for-rhel-9-$(uname -m)-rpms" 2>/dev/null || true else $SUDO subscription-manager repos \ --enable="codeready-builder-for-rhel-8-$(uname -m)-rpms" 2>/dev/null || true fi else # Community distros: use dnf config-manager if $SUDO $PKG_MGR install -y dnf-plugins-core 2>/dev/null; then if [ "$DISTRO_VERSION_MAJOR" -ge 9 ]; then $SUDO $PKG_MGR config-manager --set-enabled crb 2>/dev/null || \ $SUDO $PKG_MGR config-manager --set-enabled powertools 2>/dev/null || true else $SUDO $PKG_MGR config-manager --set-enabled powertools 2>/dev/null || \ $SUDO $PKG_MGR config-manager --set-enabled PowerTools 2>/dev/null || true fi fi fi } # --------------------------------------------------------------------------- # Step 1 — System update and prerequisites # --------------------------------------------------------------------------- install_prerequisites() { step 1 "Installing prerequisites (${PKG_MGR})" # Update package cache info "Updating package cache..." $SUDO $PKG_MGR makecache -q 2>&1 || warn "$PKG_MGR makecache failed — continuing with cached metadata." # Enable CRB / PowerTools for devel headers on RHEL-compatible distros enable_crb_if_needed # Install Development Tools group info "Installing 'Development Tools' group..." $SUDO $PKG_MGR groupinstall -y "Development Tools" 2>&1 \ || $SUDO $PKG_MGR install -y gcc gcc-c++ make 2>&1 \ || warn "Development Tools group install had issues — attempting individual packages." # Core packages local pkgs=( gcc gcc-c++ make git curl wget ca-certificates glibc-devel libgcc util-linux procps-ng lsb_release nmap-ncat policycoreutils-python-utils ) # Fedora vs RHEL-compatible differences if [ "$IS_FEDORA" = true ]; then pkgs+=(redhat-lsb-core) else # RHEL 8+ uses redhat-lsb-core from EPEL or equivalent $SUDO $PKG_MGR install -y redhat-lsb-core 2>/dev/null || true fi info "Installing packages: ${pkgs[*]}" # Install each package individually to avoid one missing package killing the whole install for pkg in "${pkgs[@]}"; do $SUDO $PKG_MGR install -y "$pkg" 2>/dev/null || warn "Package '$pkg' not available — skipping." done ok "Base packages installed." } # --------------------------------------------------------------------------- # Step 2 — Ensure GCC ≥ 9 # --------------------------------------------------------------------------- ensure_gcc() { step 2 "Verifying GCC version (need ≥ 9)" local gcc_ver=0 if command -v gcc >/dev/null 2>&1; then gcc_ver=$(gcc -dumpversion 2>/dev/null | cut -d. -f1 || echo 0) if ! echo "${gcc_ver}" | grep -qE '^[0-9]+$'; then gcc_ver=0; fi fi if [ "$gcc_ver" -lt 9 ]; then warn "GCC $gcc_ver is too old (need ≥ 9). Installing GCC 11 via toolset..." if [ "$IS_FEDORA" = true ]; then $SUDO $PKG_MGR install -y gcc-c++ \ || die "Failed to install GCC on Fedora." else # RHEL 8/9, AlmaLinux, Rocky — use gcc-toolset if $SUDO $PKG_MGR install -y gcc-toolset-11 gcc-toolset-11-gcc-c++ 2>/dev/null; then # Activate the toolset in current shell and for the build if [ -f /opt/rh/gcc-toolset-11/enable ]; then # shellcheck disable=SC1091 source /opt/rh/gcc-toolset-11/enable # Re-assert strict mode after source (sourced scripts may reset it) set -euo pipefail ok "gcc-toolset-11 activated." fi elif $SUDO $PKG_MGR install -y devtoolset-11-gcc devtoolset-11-gcc-c++ 2>/dev/null; then if [ -f /opt/rh/devtoolset-11/enable ]; then # shellcheck disable=SC1091 source /opt/rh/devtoolset-11/enable set -euo pipefail ok "devtoolset-11 activated." fi else warn "Could not install gcc-toolset-11. Build may fail with old GCC." fi fi fi # Final check command -v gcc >/dev/null 2>&1 || die "gcc not found after installation." gcc_ver=$(gcc -dumpversion 2>/dev/null | cut -d. -f1 || echo 0) if ! echo "${gcc_ver}" | grep -qE '^[0-9]+$'; then gcc_ver=0; fi ok "GCC ${gcc_ver} ready." } # --------------------------------------------------------------------------- # Step 3 — Verify all tools are present # --------------------------------------------------------------------------- verify_tools() { step 3 "Verifying build tools" local tools=(gcc make git curl) for tool in "${tools[@]}"; do if ! command -v "$tool" >/dev/null 2>&1; then die "$tool is not available after installation. See troubleshooting." fi ok "$tool: $(command -v "$tool")" done } # --------------------------------------------------------------------------- # Branch detection: prefer 'main', fall back to 'master' # --------------------------------------------------------------------------- _detect_branch() { local remote="${1:-origin}" if git ls-remote --heads "${remote}" main 2>/dev/null | grep -q "refs/heads/main"; then echo "main" elif git ls-remote --heads "${remote}" master 2>/dev/null | grep -q "refs/heads/master"; then echo "master" else echo "main" fi } # Git clone with 3 attempts and exponential backoff (4s, 8s) _git_clone_with_retry() { local branch="$1" dest="$2" repo="$3" local attempt=1 delay=4 while [ "${attempt}" -le 3 ]; do # Remove any partial clone directory before each attempt if [ -d "${dest}" ] && [ ! -f "${dest}/.git/HEAD" ]; then warn "Removing incomplete directory ${dest} before retry..." rm -rf "${dest}" || true fi if git clone --depth=1 --branch="${branch}" "${repo}" "${dest}" 2>&1; then return 0 fi if [ "${attempt}" -lt 3 ]; then warn "Clone attempt ${attempt}/3 failed — retrying in ${delay}s..." sleep "${delay}"; delay=$(( delay * 2 )) fi attempt=$(( attempt + 1 )) done die "git clone failed after 3 attempts. Check: internet, DNS, firewall, proxy, EPEL (troubleshooting #18)." } # --------------------------------------------------------------------------- # Step 4 — Clone or update repository # --------------------------------------------------------------------------- clone_repository() { step 4 "Cloning Absolute DB v${VERSION} repository" # If --branch was not explicitly overridden via CLI, detect from remote if [ "${BRANCH}" = "main" ]; then local detected detected="$( _detect_branch "${REPO}" )" if [ "${detected}" != "${BRANCH}" ]; then info "Remote branch detected: ${detected}" BRANCH="${detected}" fi fi if [ -d "$INSTALL_DIR/.git" ]; then info "Existing installation found at ${INSTALL_DIR} — updating..." cd "${INSTALL_DIR}" info "Fetching latest source (branch: ${BRANCH})..." if git fetch --depth=1 origin "${BRANCH}" 2>&1; then git reset --hard FETCH_HEAD ok "Updated to latest ${BRANCH}" else warn "git fetch failed — removing and recloning..." cd /; rm -rf "${INSTALL_DIR}" _git_clone_with_retry "${BRANCH}" "${INSTALL_DIR}" "${REPO}" ok "Recloned to ${INSTALL_DIR}" fi cd "${INSTALL_DIR}" else if [ -d "$INSTALL_DIR" ]; then warn "Directory ${INSTALL_DIR} exists but is not a git repo — removing." rm -rf "$INSTALL_DIR" || die "Failed to remove existing directory: $INSTALL_DIR" fi info "Cloning ${REPO} (branch: ${BRANCH}) → ${INSTALL_DIR}" _git_clone_with_retry "${BRANCH}" "${INSTALL_DIR}" "${REPO}" ok "Repository cloned to ${INSTALL_DIR}." fi } # --------------------------------------------------------------------------- # Step 5 — Build # --------------------------------------------------------------------------- build_absdb() { step 5 "Building Absolute DB v${VERSION}" cd "$INSTALL_DIR" || die "Cannot enter ${INSTALL_DIR}" info "Running make clean..." make clean 2>&1 || warn "make clean failed — continuing anyway." # Check available memory and limit parallelism on low-memory hosts local mem_kb=0 mem_kb=$(awk '/^MemTotal/{print $2}' /proc/meminfo 2>/dev/null || echo 0) local swap_kb=0 swap_kb=$(awk '/^SwapTotal/{print $2}' /proc/meminfo 2>/dev/null || echo 0) local total_mem_mb=$(( (mem_kb + swap_kb) / 1024 )) if [ "${total_mem_mb}" -lt 1024 ]; then warn "Low memory detected (${total_mem_mb} MB RAM+swap). Limiting to single-threaded build." export MAKEFLAGS="-j1" fi info "Running make release..." make release 2>&1 || die "Build failed. Review errors above. Check troubleshooting for GCC/SELinux hints." # Verify mandatory binaries were produced for bin in absdb absdb-server; do local found found=$(find "$INSTALL_DIR" -maxdepth 3 -name "$bin" -type f 2>/dev/null | head -1 || true) if [ -z "$found" ]; then die "Binary '${bin}' not found after build. Build may have failed silently." fi done # Optional binaries — warn but don't abort for bin in absdb-bench adb_admin; do local found found=$(find "$INSTALL_DIR" -maxdepth 3 -name "$bin" -type f 2>/dev/null | head -1 || true) if [ -z "$found" ]; then warn "Optional binary '${bin}' not found — skipping." fi done ok "Build completed successfully." } # --------------------------------------------------------------------------- # Step 6 — Install binaries # --------------------------------------------------------------------------- install_binaries() { step 6 "Installing binaries to ${BIN_DIR}" $SUDO mkdir -p "$BIN_DIR" || die "Cannot create ${BIN_DIR}" for bin in absdb absdb-server absdb-bench adb_admin; do local src="" for loc in \ "${INSTALL_DIR}/${bin}" \ "${INSTALL_DIR}/build/${bin}" \ "${INSTALL_DIR}/out/${bin}" \ "${INSTALL_DIR}/dist/${bin}"; do if [ -f "$loc" ] && [ -x "$loc" ]; then src="$loc" break fi done if [ -z "$src" ]; then warn "Binary '${bin}' not found — skipping." continue fi $SUDO install -m 755 "$src" "${BIN_DIR}/${bin}" \ || die "Failed to install ${bin} to ${BIN_DIR}. See troubleshooting #7." ok "Installed: ${BIN_DIR}/${bin}" done # SELinux context restoration for installed binaries if command -v restorecon >/dev/null 2>&1; then $SUDO restorecon -v "${BIN_DIR}/absdb" "${BIN_DIR}/absdb-server" 2>/dev/null || true ok "SELinux context restored on binaries." fi # Ensure BIN_DIR is on PATH if ! echo "$PATH" | grep -q "${BIN_DIR}"; then warn "${BIN_DIR} is not in PATH. Adding to ~/.bashrc..." if ! grep -q "${BIN_DIR}" "${HOME}/.bashrc" 2>/dev/null; then echo "" >> "${HOME}/.bashrc" echo "# Absolute DB — added by installer" >> "${HOME}/.bashrc" echo "export PATH=\"${BIN_DIR}:\$PATH\"" >> "${HOME}/.bashrc" fi export PATH="${BIN_DIR}:${PATH}" ok "Added ${BIN_DIR} to PATH (effective after: source ~/.bashrc)" fi } # --------------------------------------------------------------------------- # Step 7 — Create data and log directories, service user # --------------------------------------------------------------------------- setup_directories_and_user() { step 7 "Creating data directories and service user" # Create service user (system account, no login shell) if ! id "$SERVICE_USER" >/dev/null 2>&1; then $SUDO useradd \ --system \ --no-create-home \ --shell /sbin/nologin \ --comment "Absolute DB service account" \ "$SERVICE_USER" \ || die "Failed to create service user '${SERVICE_USER}'." ok "Service user '${SERVICE_USER}' created." else ok "Service user '${SERVICE_USER}' already exists." fi for dir in "$DATA_DIR" "$LOG_DIR"; do $SUDO mkdir -p "$dir" || die "Cannot create directory: $dir" $SUDO chown "${SERVICE_USER}:${SERVICE_USER}" "$dir" \ || warn "Cannot chown ${dir}" $SUDO chmod 750 "$dir" ok "Directory: $dir" done # Set SELinux file contexts for data and log directories if command -v semanage >/dev/null 2>&1 && [ "$SELINUX_ENFORCING" = true ]; then $SUDO semanage fcontext -a -t var_lib_t "${DATA_DIR}(/.*)?" 2>/dev/null || true $SUDO semanage fcontext -a -t var_log_t "${LOG_DIR}(/.*)?" 2>/dev/null || true $SUDO restorecon -Rv "$DATA_DIR" "$LOG_DIR" 2>/dev/null || true ok "SELinux file contexts configured for data and log directories." fi } # --------------------------------------------------------------------------- # Step 8 — Systemd service # --------------------------------------------------------------------------- install_service() { step 8 "Installing systemd service" if [ "$DO_SERVICE" = false ]; then info "Skipping service installation (--no-service or systemd unavailable)." return fi if [ "$SYSTEMD_AVAILABLE" = false ]; then warn "systemd is not running — skipping service installation." info "To start manually: ${BIN_DIR}/absdb-server --data-dir=${DATA_DIR} --log-dir=${LOG_DIR} --daemon" return fi local service_file="/etc/systemd/system/absdb.service" $SUDO tee "$service_file" > /dev/null </dev/null 2>&1; then $SUDO restorecon -v "$service_file" 2>/dev/null || true fi $SUDO systemctl daemon-reload \ || warn "systemctl daemon-reload failed." $SUDO systemctl enable absdb.service \ || warn "Failed to enable absdb.service." ok "systemd service installed: ${service_file}" ok "Service enabled (will start on boot)." # Pre-flight: check ports are not already bound by another process for _port in ${PG_PORT} ${REST_PORT}; do local _port_in_use=false if (ss -tlnp 2>/dev/null | grep -qE ":${_port}[[:space:]]|:${_port}$") 2>/dev/null || \ (netstat -tlnp 2>/dev/null | grep -qE ":${_port}[[:space:]]|:${_port}$") 2>/dev/null; then _port_in_use=true fi if [ "${_port_in_use}" = true ]; then warn "Port ${_port} is already in use by another process." warn "The service may fail to start. Check with: sudo ss -tlnp | grep :${_port}" fi done # Start the service info "Starting absdb.service..." if $SUDO systemctl start absdb.service 2>&1; then ok "absdb.service started." # Confirm it didn't immediately crash sleep 2 if $SUDO systemctl is-failed --quiet absdb.service 2>/dev/null; then warn "absdb.service entered failed state immediately after start." $SUDO journalctl -u absdb.service -n 30 --no-pager 2>/dev/null || true if [ "$SELINUX_ENFORCING" = true ]; then warn "SELinux may be blocking. Run: sudo ausearch -m avc -ts recent | grep absdb" fi fi else warn "Service failed to start immediately. Diagnostics:" warn " sudo systemctl status absdb.service" warn " journalctl -u absdb.service -n 50" warn " cat ${LOG_DIR}/absdb-error.log" if [ "$SELINUX_ENFORCING" = true ]; then warn "SELinux may be blocking the process. See troubleshooting #16." fi fi } # --------------------------------------------------------------------------- # Step 9 — Protocol Selection Wizard # --------------------------------------------------------------------------- prompt_protocol_selection() { [ -t 0 ] && [ -t 1 ] || return 0 step 9 "Protocol Selection" echo "" echo -e " ${BOLD}Adaptive Protocol Manager — select which ports to open${NC}" echo -e " ${DIM}Only enabled ports are opened (least-protocol principle).${NC}" echo "" echo -e " ${CYN}1)${NC} Minimal — PostgreSQL wire + REST ${DIM}(default)${NC}" echo -e " ${CYN}2)${NC} Web App — PostgreSQL + REST + Redis" echo -e " ${CYN}3)${NC} Microservices — PostgreSQL + REST + gRPC" echo -e " ${CYN}4)${NC} Enterprise — All protocols" echo -e " ${CYN}5)${NC} Custom — Choose individually" echo "" local _pc="" while true; do read -rp " Choice [1-5, default=1]: " _raw _raw="${_raw:-1}" case "${_raw}" in 1|minimal) _pc="minimal"; break ;; 2|webapp) _pc="webapp"; break ;; 3|microservices) _pc="microservices"; break ;; 4|enterprise) _pc="enterprise"; break ;; 5|custom) _pc="custom"; break ;; *) warn "Enter 1–5." ;; esac done local CONF_DIR="/etc/absdb" local CONF_FILE="${CONF_DIR}/absdb.conf" $SUDO mkdir -p "${CONF_DIR}" 2>/dev/null || true # Build per-key on/off values (one key = value per line — required by absdb config parser) local _pg_wire=on _rest=on _grpc=off _redis=off _prometheus=off _cluster=off _craid=off case "${_pc}" in webapp) _redis=on ;; microservices) _grpc=on ;; enterprise) _grpc=on; _redis=on; _prometheus=on; _cluster=on; _craid=on ;; custom) for P in "pg_wire:5433" "rest:8080" "grpc:9090" "redis:6379" \ "prometheus:9093" "cluster:9091" "craid:9092"; do local PN="${P%%:*}"; local PP="${P##*:}" read -rp " Enable ${PN} (port ${PP})? [y/N]: " _ans case "${_ans:-n}" in y|Y|yes|YES) eval "_${PN}=on" ;; *) eval "_${PN}=off" ;; esac done ;; esac { echo "# Absolute DB v${VERSION} — generated by installer (RHEL/CentOS)" echo "[protocols]" echo "shadow = on" echo "pg_wire = ${_pg_wire}" echo "rest = ${_rest}" echo "grpc = ${_grpc}" echo "redis = ${_redis}" echo "prometheus = ${_prometheus}" echo "cluster = ${_cluster}" echo "craid = ${_craid}" } | $SUDO tee "${CONF_FILE}" > /dev/null ok "Protocol config saved: ${CONF_FILE}" echo -e " ${DIM}Change later: adb_admin --protocol enable ${NC}" } # --------------------------------------------------------------------------- # Step 10 — Verify installation # --------------------------------------------------------------------------- # ── Production vs Testing wizard ───────────────────────────────────────────── prompt_install_type() { # Only prompt in an interactive terminal [ -t 0 ] && [ -t 1 ] || return 0 step 9 "Installation Type" echo "" echo -e " ${BOLD}Is this installation for Production or Testing/Evaluation?${NC}" echo "" echo -e " ${BOLD}${RED}┌─────────────────────────────────────────────────────────────────┐${NC}" echo -e " ${BOLD}${RED}│${NC} ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${CYN}1)${NC} ${BOLD}Production${NC} — clean start, no sample data ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${CYN}2)${NC} ${BOLD}Testing${NC} — optionally load tier-sized sample data ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}└─────────────────────────────────────────────────────────────────┘${NC}" echo "" local INSTALL_TYPE="" while true; do read -rp " Enter choice [1/2] (default: 1): " _raw _raw="${_raw:-1}" case "${_raw}" in 1|production|prod) INSTALL_TYPE="production"; break ;; 2|testing|test) INSTALL_TYPE="testing"; break ;; *) warn "Enter 1 (Production) or 2 (Testing)." ;; esac done if [ "${INSTALL_TYPE}" = "production" ]; then info "Production installation — server starts clean." info "To generate test data later:" info " bash ${INSTALL_DIR}/tools/gen_test_data.sh" return 0 fi # ── Testing branch ──────────────────────────────────────────────────────── echo "" info "Testing installation selected." echo "" read -rp " Generate sample/test data now? [y/N]: " _want _want="${_want:-n}" if [[ "${_want,,}" != "y" && "${_want,,}" != "yes" ]]; then info "Skipping sample data. Run later:" info " bash ${INSTALL_DIR}/tools/gen_test_data.sh" return 0 fi echo "" echo -e " ${BOLD}Select the tier size for test data:${NC}" echo "" echo -e " ${BOLD}${RED}┌─────────────────────────────────────────────────────────────────┐${NC}" echo -e " ${BOLD}${RED}│${NC} ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${CYN}1)${NC} ${BOLD}Community${NC} — ${YLW}2 GB${NC} 10 connections · 10 GB limit ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${CYN}2)${NC} ${BOLD}SME${NC} — ${YLW}3 GB${NC} 500 connections · 5 GB limit ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${CYN}3)${NC} ${BOLD}Professional${NC} — ${YLW}10 GB${NC} Unlimited connections ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${CYN}4)${NC} ${BOLD}Enterprise${NC} — ${YLW}20 GB${NC} Unlimited + cluster + C-RAID ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${DIM}Fixed seed — same data every run, ±0.5% of target size ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}│${NC} ${BOLD}${RED}│${NC}" echo -e " ${BOLD}${RED}└─────────────────────────────────────────────────────────────────┘${NC}" echo "" local DATA_TIER="" while true; do read -rp " Enter tier [1-4 or name]: " _t _t="${_t,,}" case "${_t}" in 1|community) DATA_TIER="community"; break ;; 2|sme) DATA_TIER="sme"; break ;; 3|pro|professional) DATA_TIER="pro"; break ;; 4|enterprise) DATA_TIER="enterprise"; break ;; *) warn "Enter 1–4 or community/sme/pro/enterprise." ;; esac done # Confirm disk space warning case "${DATA_TIER}" in enterprise) warn "Enterprise tier: 20 GB will be generated. Ensure sufficient disk space." ;; pro) warn "Professional tier: 10 GB will be generated. Ensure sufficient disk space." ;; esac echo "" info "Generating ${DATA_TIER} test data — this may take several minutes..." info "Data is deterministic: same dataset is produced every run." echo "" local GEN_SH="${INSTALL_DIR}/tools/gen_test_data.sh" local GEN_PY="${INSTALL_DIR}/tools/gen_test_data.py" if [ -f "${GEN_SH}" ]; then bash "${GEN_SH}" \ --tier "${DATA_TIER}" \ --host "127.0.0.1" \ --port "${PG_PORT}" \ --dbname "absdb" \ --drop-existing \ || warn "Sample data generation encountered errors — check output above." elif [ -f "${GEN_PY}" ] && command -v python3 &>/dev/null; then # Install psycopg2 if needed python3 -c "import psycopg2" &>/dev/null 2>&1 \ || python3 -m pip install --quiet psycopg2-binary \ || warn "pip install psycopg2-binary failed — install manually and rerun." python3 "${GEN_PY}" \ --tier "${DATA_TIER}" \ --host "127.0.0.1" \ --port "${PG_PORT}" \ --dbname "absdb" \ --drop-existing \ || warn "Sample data generation encountered errors — check output above." else warn "gen_test_data.sh/py not found at ${INSTALL_DIR}/tools/" warn "Run manually after install:" warn " bash ${INSTALL_DIR}/tools/gen_test_data.sh --tier ${DATA_TIER}" fi } verify_install() { step 9 "Verifying installation" if [ "$DO_TEST" = false ]; then info "Skipping verification (--no-test)." return fi if command -v absdb >/dev/null 2>&1; then local ver_out ver_out=$(absdb --version 2>&1 || true) ok "absdb CLI: ${ver_out}" else warn "absdb not found in PATH. Try: source ~/.bashrc" fi if command -v absdb-server >/dev/null 2>&1; then local sver_out sver_out=$(absdb-server --version 2>&1 || true) ok "absdb-server: ${sver_out}" else warn "absdb-server not found in PATH." fi if [ "$DO_SERVICE" = true ] && [ "$SYSTEMD_AVAILABLE" = true ]; then local attempts=6 local port_open=false info "Waiting for server to accept connections on port ${PG_PORT}..." for i in $(seq 1 $attempts); do if (nc -z 127.0.0.1 "$PG_PORT" 2>/dev/null) || \ (ss -tlnp 2>/dev/null | grep -qE ":${PG_PORT}[[:space:]]|:${PG_PORT}$") || \ (bash -c "cat /dev/null > /dev/tcp/127.0.0.1/${PG_PORT}" 2>/dev/null); then port_open=true break fi sleep 2 done if [ "$port_open" = true ]; then ok "Server is accepting connections on port ${PG_PORT}." else warn "Server does not appear to be listening on port ${PG_PORT} yet." warn "Check: sudo systemctl status absdb && journalctl -u absdb -n 30" if [ "$SELINUX_ENFORCING" = true ]; then warn "SELinux may be blocking. Run: sudo ausearch -m avc -ts recent | grep absdb" fi fi fi } # --------------------------------------------------------------------------- # Step 10 — Final summary # --------------------------------------------------------------------------- print_summary() { step 10 "Installation complete" echo "" echo -e "${BOLD}${GRN}╔══════════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}${GRN}║ Absolute DB v${VERSION} — Successfully Installed! ║${NC}" echo -e "${BOLD}${GRN}╚══════════════════════════════════════════════════════════════════════╝${NC}" echo "" hdr " Installation Summary" echo -e " ${DIM}Binary (CLI): ${NC}${BIN_DIR}/absdb" echo -e " ${DIM}Binary (Server):${NC}${BIN_DIR}/absdb-server" echo -e " ${DIM}Data directory: ${NC}${DATA_DIR}" echo -e " ${DIM}Log directory: ${NC}${LOG_DIR}" echo -e " ${DIM}Service user: ${NC}${SERVICE_USER}" echo -e " ${DIM}PostgreSQL port:${NC}${PG_PORT}" echo -e " ${DIM}REST API port: ${NC}${REST_PORT}" echo -e " ${DIM}gRPC port: ${NC}${GRPC_PORT}" echo "" hdr " Quick-Start Commands" echo -e " ${CYN}# Connect with psql${NC}" echo -e " psql -h 127.0.0.1 -p ${PG_PORT} -U absdb" echo "" echo -e " ${CYN}# Run an inline SQL query${NC}" echo -e " absdb --exec \"SELECT version();\"" echo "" echo -e " ${CYN}# REST API${NC}" echo -e " curl http://127.0.0.1:${REST_PORT}/api/v1/query -d '{\"sql\":\"SELECT 1\"}'" echo "" echo -e " ${CYN}# Web management console${NC}" echo -e " open http://127.0.0.1:${REST_PORT}/console" echo "" if [ "$DO_SERVICE" = true ] && [ "$SYSTEMD_AVAILABLE" = true ]; then hdr " Service Management" echo -e " ${CYN}sudo systemctl start absdb${NC} # start" echo -e " ${CYN}sudo systemctl stop absdb${NC} # stop" echo -e " ${CYN}sudo systemctl restart absdb${NC} # restart" echo -e " ${CYN}sudo systemctl status absdb${NC} # status" echo -e " ${CYN}journalctl -u absdb -f ${NC} # follow logs" echo "" else hdr " Manual Start" echo -e " ${CYN}absdb-server --data-dir=${DATA_DIR} --log-dir=${LOG_DIR} --daemon${NC}" echo "" fi if [ "$SELINUX_ENFORCING" = true ]; then hdr " SELinux Notes" echo -e " ${YLW}SELinux is Enforcing. If issues occur, see troubleshooting item 16.${NC}" echo -e " ${YLW}Quick fix: sudo setenforce 0${NC}" echo "" fi hdr " Next Steps" echo -e " ${YLW}1. Open firewall ports for remote access:${NC}" if command -v firewall-cmd >/dev/null 2>&1; then echo -e " ${CYN}sudo firewall-cmd --permanent --add-port=${PG_PORT}/tcp${NC} ${DIM}# PostgreSQL wire${NC}" echo -e " ${CYN}sudo firewall-cmd --permanent --add-port=${REST_PORT}/tcp${NC} ${DIM}# REST API + Web console${NC}" echo -e " ${CYN}sudo firewall-cmd --permanent --add-port=${GRPC_PORT}/tcp${NC} ${DIM}# gRPC / HTTP/2${NC}" echo -e " ${CYN}sudo firewall-cmd --permanent --add-port=6379/tcp${NC} ${DIM}# Redis RESP3 (if enabled)${NC}" echo -e " ${CYN}sudo firewall-cmd --reload${NC}" else echo -e " ${CYN}sudo iptables -A INPUT -p tcp --dport ${PG_PORT} -j ACCEPT${NC} ${DIM}# PostgreSQL wire${NC}" echo -e " ${CYN}sudo iptables -A INPUT -p tcp --dport ${REST_PORT} -j ACCEPT${NC} ${DIM}# REST API + Web console${NC}" echo -e " ${CYN}sudo iptables -A INPUT -p tcp --dport ${GRPC_PORT} -j ACCEPT${NC} ${DIM}# gRPC / HTTP/2${NC}" echo -e " ${CYN}sudo iptables -A INPUT -p tcp --dport 6379 -j ACCEPT${NC} ${DIM}# Redis RESP3 (if enabled)${NC}" fi echo "" echo -e " ${YLW}2. Open the web management console:${NC}" echo -e " ${CYN}http://localhost:${REST_PORT}/console${NC}" echo "" echo -e " ${YLW}3. Enable TLS for production:${NC}" echo -e " See ${DIM}https://absolutedb.com/docs/tls${NC}" echo "" hdr " Documentation & Support" echo -e " ${DIM}Docs: ${NC}https://absolutedb.com/docs" echo -e " ${DIM}Discord: ${NC}https://discord.gg/absolutedb" echo -e " ${DIM}Support: ${NC}community@absolutedb.com" echo -e " ${DIM}GitHub: ${NC}${REPO}" echo "" if ! echo "$PATH" | grep -q "${BIN_DIR}"; then echo -e " ${YLW}NOTE: Run${NC} source ~/.bashrc ${YLW}to update your PATH in this shell session.${NC}" echo "" fi echo -e "${BOLD}${GRN} Enjoy Absolute DB v${VERSION}!${NC}" echo "" } # --------------------------------------------------------------------------- # Uninstall # --------------------------------------------------------------------------- do_uninstall() { hdr "Uninstalling Absolute DB v${VERSION}..." if [ "$SYSTEMD_AVAILABLE" = true ] && \ systemctl is-active --quiet absdb.service 2>/dev/null; then $SUDO systemctl stop absdb.service || true $SUDO systemctl disable absdb.service || true $SUDO rm -f /etc/systemd/system/absdb.service $SUDO systemctl daemon-reload || true ok "systemd service removed." fi for bin in absdb absdb-server absdb-bench adb_admin; do if [ -f "${BIN_DIR}/${bin}" ]; then $SUDO rm -f "${BIN_DIR}/${bin}" ok "Removed ${BIN_DIR}/${bin}" fi done if [ -d "$INSTALL_DIR" ]; then rm -rf "$INSTALL_DIR" ok "Removed ${INSTALL_DIR}" fi warn "Data and logs NOT removed. To also remove them:" warn " sudo rm -rf ${DATA_DIR} ${LOG_DIR}" warn " sudo userdel ${SERVICE_USER}" ok "Uninstall complete." exit 0 } # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- main() { parse_args "$@" print_banner detect_pkg_manager detect_distro if [ "$DO_UNINSTALL" = true ]; then setup_sudo check_systemd do_uninstall fi info "Starting Absolute DB v${VERSION} installation..." info "Install dir: ${INSTALL_DIR}" info "Prefix: ${PREFIX}" info "Branch: ${BRANCH}" info "Service: ${DO_SERVICE}" echo "" check_systemd check_selinux check_firewalld check_disk_space check_network setup_sudo install_prerequisites ensure_gcc verify_tools clone_repository build_absdb install_binaries setup_directories_and_user prompt_protocol_selection install_service verify_install prompt_install_type print_summary } main "$@"