Files
sicabe-core/deploy/scripts/prepare-host.sh
T
2026-10-09 02:35:14 +07:00

317 lines
13 KiB
Bash
Executable File

#!/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 <nama>"
echo " 4. ./install.sh --preflight (cek keterjangkauan lintas host)"
}
main "$@"