#!/usr/bin/env bash # ============================================================================= # prepare-host.sh — siapkan server Ubuntu 24.04 (noble) untuk instalasi SiCABE # ----------------------------------------------------------------------------- # Menyiapkan satu host sebelum `install.sh` dijalankan: # 1. paket apt yang benar-benar dipakai script deploy (bukan container) # 2. Docker Engine + plugin compose v2 (opsional --no-docker) # 3. sysctl vm.max_map_count=262144 → SYARAT KERAS OpenSearch # 4. (opsional --swap-off) matikan swap → rekomendasi OpenSearch # 5. prometheus-node-exporter (:9100) → PRA-SYARAT dashboard Host # (opsional --no-node-exporter) # 6. precheck: AVX2, jam, RAM, disk, port yang akan dipakai # # Pemakaian: # sudo ./scripts/prepare-host.sh --dry-run # lihat rencana, TIDAK mengubah apa pun # sudo ./scripts/prepare-host.sh # jalankan # sudo ./scripts/prepare-host.sh --no-docker # bila Docker sudah dipasang manual # sudo ./scripts/prepare-host.sh --swap-off # ikut mematikan swap # sudo ./scripts/prepare-host.sh --no-node-exporter # jangan pasang node_exporter # # Sifat: IDEMPOTEN — aman dijalankan berulang. # ============================================================================= set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" DRY_RUN=0 WITH_DOCKER=1 SWAP_OFF=0 WITH_NODE_EXPORTER=1 RED=$'\e[31m'; GRN=$'\e[32m'; YLW=$'\e[33m'; BLD=$'\e[1m'; NC=$'\e[0m' ok() { echo -e " ${GRN}[OK]${NC} $*"; } inf() { echo -e " ${YLW}[..]${NC} $*"; } warn() { echo -e " ${YLW}[WARN]${NC} $*"; } err() { echo -e " ${RED}[!!]${NC} $*" >&2; } # Jalankan perintah; pada --dry-run cukup dicetak. run() { if [[ ${DRY_RUN} -eq 1 ]]; then echo " [dry-run] $*" return 0 fi "$@" } usage() { sed -n '2,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' exit 0 } parse_args() { while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=1 ;; --no-docker) WITH_DOCKER=0 ;; --swap-off) SWAP_OFF=1 ;; --no-node-exporter) WITH_NODE_EXPORTER=0 ;; -h|--help) usage ;; *) err "Argumen tidak dikenal: $1"; exit 2 ;; esac shift done } # ─── 0. OS & hak akses ────────────────────────────────────────────────────── check_os() { echo -e "${BLD}A. Sistem${NC}" if [[ ! -r /etc/os-release ]]; then err "/etc/os-release tidak terbaca — script ini untuk Ubuntu."; exit 1 fi # shellcheck disable=SC1091 . /etc/os-release echo " OS: ${PRETTY_NAME:-?} (codename: ${VERSION_CODENAME:-?})" if [[ "${ID:-}" != "ubuntu" ]]; then warn "Bukan Ubuntu (ID=${ID:-?}) — script ini diuji pada Ubuntu 24.04; lanjutkan hati-hati." elif [[ "${VERSION_CODENAME:-}" != "noble" ]]; then warn "Bukan 24.04/noble (codename=${VERSION_CODENAME:-?}) — nama paket bisa berbeda." else ok "Ubuntu 24.04 (noble) — sesuai target" fi if [[ ${EUID} -ne 0 && ${DRY_RUN} -eq 0 ]]; then err "Perlu root: jalankan dengan sudo." exit 1 fi [[ ${EUID} -ne 0 ]] && inf "berjalan tanpa root (dry-run) — hanya memeriksa" return 0 } # ─── 1. Paket apt ─────────────────────────────────────────────────────────── # Daftar ini berasal dari pemakaian nyata di deploy/, bukan tebakan: # htpasswd (apache2-utils) → password.db Trino (bootstrap/install) # ldapsearch (ldap-utils) → bootstrap step 3 + verify-phase1 # mysql (client) → seed/reapply StarRocks + verify-phase4 # openssl → keystore Trino # curl → hampir semua script # python3 + PyYAML → lib/cluster/*.py + bootstrap # ca-certificates/gnupg → repo & TLS PKGS=(ca-certificates curl gnupg openssl apache2-utils ldap-utils python3 python3-yaml git unzip) install_packages() { echo echo -e "${BLD}B. Paket apt${NC}" # Klien MySQL: nama paket berbeda antar rilis → coba berurutan, laporkan yang dipakai. local mysql_candidates=(default-mysql-client mysql-client mariadb-client) picked="" run apt-get update -qq run apt-get install -y --no-install-recommends "${PKGS[@]}" for cand in "${mysql_candidates[@]}"; do if apt-cache show "${cand}" >/dev/null 2>&1; then run apt-get install -y --no-install-recommends "${cand}" picked="${cand}" break fi done if [[ -n "${picked}" ]]; then ok "paket terpasang (klien MySQL: ${picked})" else warn "klien MySQL tidak ditemukan di repo — seed/reapply StarRocks butuh binary 'mysql'" fi # MinIO client (mc) dipakai verify-phase2 & bootstrap, tapi tidak selalu ada di repo. if command -v mc &>/dev/null; then ok "mc tersedia" else warn "mc (MinIO client) belum ada — sebagian cek Fase 2 & bootstrap akan dilewati." echo " Pasang manual bila diinginkan, mis.:" echo " curl -sSL https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc && chmod +x /usr/local/bin/mc" fi # node_exporter (:9100) — PRA-SYARAT dashboard Host (job `node` di Prometheus). # Best-effort: paket ada di repo Ubuntu (universe). Bila tak tersedia, dashboard # Host hanya menampilkan target DOWN — tidak fatal. if [[ ${WITH_NODE_EXPORTER} -eq 0 ]]; then inf "node_exporter dilewati (--no-node-exporter)" elif command -v prometheus-node-exporter &>/dev/null; then ok "node_exporter sudah tersedia (:9100)" elif apt-cache show prometheus-node-exporter >/dev/null 2>&1; then run apt-get install -y --no-install-recommends prometheus-node-exporter ok "node_exporter terpasang & listen :9100 (di-scrape Prometheus job 'node')" else warn "paket prometheus-node-exporter tidak ada di repo — dashboard Host akan kosong" fi } # ─── 2. Docker ────────────────────────────────────────────────────────────── install_docker() { echo echo -e "${BLD}C. Docker${NC}" if [[ ${WITH_DOCKER} -eq 0 ]]; then inf "--no-docker: dilewati" elif command -v docker &>/dev/null && docker compose version &>/dev/null; then ok "Docker + compose v2 sudah tersedia ($(docker --version))" else inf "memasang Docker Engine + plugin compose dari repo resmi Docker" run install -m 0755 -d /etc/apt/keyrings # kunci GPG repo Docker if [[ ${DRY_RUN} -eq 0 ]]; then curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg # shellcheck disable=SC1091 . /etc/os-release echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" \ > /etc/apt/sources.list.d/docker.list apt-get update -qq apt-get install -y --no-install-recommends \ docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin else echo " [dry-run] tambah repo Docker + pasang docker-ce, docker-compose-plugin, …" fi fi # Pengguna non-root: supaya `docker` bisa dipakai tanpa sudo setelah re-login. local target_user="${SUDO_USER:-}" if [[ -n "${target_user}" && "${target_user}" != "root" && ${DRY_RUN} -eq 0 ]]; then if id -nG "${target_user}" | grep -qw docker; then ok "pengguna ${target_user} sudah di grup docker" else run usermod -aG docker "${target_user}" warn "pengguna ${target_user} ditambahkan ke grup docker — LOGOUT/LOGIN agar berlaku" fi fi if [[ ${DRY_RUN} -eq 0 ]] && command -v docker &>/dev/null; then if systemctl is-active --quiet docker; then ok "layanan docker aktif" else run systemctl enable --now docker fi fi } # ─── 3. sysctl (SYARAT KERAS OpenSearch) ──────────────────────────────────── configure_sysctl() { echo echo -e "${BLD}D. sysctl — vm.max_map_count${NC}" local want=262144 have have="$(sysctl -n vm.max_map_count 2>/dev/null || echo 0)" if [[ "${have}" -ge "${want}" ]]; then ok "vm.max_map_count=${have} (cukup)" return 0 fi warn "vm.max_map_count=${have} < ${want} — OpenSearch GAGAL start bila kurang (syarat keras)" local f=/etc/sysctl.d/99-sicabe.conf if [[ ${DRY_RUN} -eq 1 ]]; then echo " [dry-run] tulis 'vm.max_map_count=${want}' ke ${f} lalu sysctl --system" return 0 fi echo "vm.max_map_count=${want}" > "${f}" sysctl --system >/dev/null 2>&1 || true ok "ditetapkan vm.max_map_count=$(sysctl -n vm.max_map_count 2>/dev/null) (persist: ${f})" } # ─── 4. Swap (opsional, rekomendasi OpenSearch) ───────────────────────────── configure_swap() { echo echo -e "${BLD}E. Swap${NC}" if [[ ${SWAP_OFF} -eq 0 ]]; then inf "--swap-off tidak diberikan: swap dibiarkan (OpenSearch hanya merekomendasikan)" return 0 fi if [[ -z "$(swapon --show 2>/dev/null)" ]]; then ok "swap sudah mati" return 0 fi if [[ ${DRY_RUN} -eq 1 ]]; then echo " [dry-run] swapoff -a + nonaktifkan entri swap di /etc/fstab (dengan backup)" return 0 fi cp -a /etc/fstab "/etc/fstab.sicabe-bak-$(date +%Y%m%d%H%M%S)" swapoff -a sed -i 's|^\([^#].*[[:space:]]swap[[:space:]].*\)$|# \1|' /etc/fstab ok "swap dimatikan (fstab dibackup; entri swap dikomentari)" warn "pastikan RAM cukup — tanpa swap, OOM langsung membunuh proses" } # ─── 5. Precheck ──────────────────────────────────────────────────────────── precheck() { echo echo -e "${BLD}F. Precheck${NC}" # AVX2: syarat keras starrocks-cn (tanpa ini container gagal SIGILL) if grep -qo -m1 avx2 /proc/cpuinfo 2>/dev/null; then ok "CPU mendukung AVX2 (syarat starrocks-cn)" else warn "AVX2 TIDAK terdeteksi — container starrocks-cn akan gagal (SIGILL)." echo " Jalankan install tanpa Fase 4, atau pindahkan starrocks-cn ke host ber-AVX2." fi # Jam: OIDC/JWT sensitif terhadap skew (preflight juga memeriksanya) if command -v timedatectl &>/dev/null; then local ntp ntp="$(timedatectl show -p NTPSynchronized --value 2>/dev/null || echo "")" case "${ntp}" in yes) ok "jam tersinkronisasi (NTP aktif)" ;; no) warn "jam BELUM tersinkron — aktifkan NTP (timedatectl set-ntp true) sebelum install" ;; *) inf "status NTP tidak dapat dibaca di lingkungan ini — verifikasi manual di server nyata" ;; esac fi # RAM & disk local ram_gb disk_gb ram_gb="$(awk '/MemTotal/{printf "%d", $2/1024/1024}' /proc/meminfo 2>/dev/null || echo 0)" disk_gb="$(df -BG --output=avail /var/lib/docker 2>/dev/null | tail -1 | tr -dc '0-9')" [[ -z "${disk_gb}" ]] && disk_gb="$(df -BG --output=avail / | tail -1 | tr -dc '0-9')" echo " RAM: ${ram_gb} GB · disk tersedia: ${disk_gb:-?} GB" [[ "${ram_gb}" -lt 8 ]] 2>/dev/null && warn "RAM < 8 GB: sesuaikan penempatan komponen (lihat reviews/sicabe-09…)" [[ "${disk_gb:-0}" -lt 20 ]] 2>/dev/null && warn "disk < 20 GB: image + volume bisa tidak cukup (siapkan ±20 GB untuk image)" # Port yang akan dipakai: dibaca dari config/environment.defaults.sh agar sinkron local ports="" if [[ -f "${DEPLOY_DIR}/config/environment.defaults.sh" ]]; then ports="$(grep -oE ':-(12[0-9]{3})\}' "${DEPLOY_DIR}/config/environment.defaults.sh" \ | tr -dc '0-9\n' | sort -un | paste -sd' ' -)" fi if [[ -n "${ports}" ]]; then local busy="" p for p in ${ports}; do if (exec 3<>"/dev/tcp/127.0.0.1/${p}") 2>/dev/null; then busy+=" ${p}"; fi exec 3<&- 2>/dev/null done if [[ -z "${busy}" ]]; then ok "semua port 12xxx yang dibutuhkan masih bebas" else warn "port sudah terpakai:${busy}" echo " Bila itu stack SiCABE yang lama: matikan dulu (RAM tidak cukup untuk dua stack)." fi else inf "daftar port tidak terbaca dari environment.defaults.sh — lewati pemeriksaan port" fi } main() { parse_args "$@" echo -e "${BLD}Persiapan host untuk SiCABE${NC}" echo " deploy dir : ${DEPLOY_DIR}" [[ ${DRY_RUN} -eq 1 ]] && echo -e " ${YLW}MODE DRY-RUN — tidak ada perubahan sistem${NC}" echo check_os install_packages install_docker configure_sysctl configure_swap precheck echo echo -e "${BLD}Ringkasan${NC}" echo " Langkah berikutnya di host ini:" echo " 1. salin direktori deploy/ ke host ini" echo " 2. ./scripts/prefetch-images.sh # tarik semua image lebih dulu" echo " 3. ./install.sh # atau ./install.sh --host " echo " 4. ./install.sh --preflight (cek keterjangkauan lintas host)" } main "$@"