#!/usr/bin/env bash # Shared helpers for deploy/bin/*.sh — sourced, not executed. # Targets: data = BHS128 (public via BHS64 gateway, stores on 10.68.0.1) · crawl = BHS64b (workers, 10.68.0.2) set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" REMOTE_DIR="${DCI_REMOTE_DIR:-/srv/dci/app}" BACKUP_DIR="${DCI_BACKUP_DIR:-/srv/dci/backups}" OFFSITE_DIR="${DCI_OFFSITE_DIR:-/srv/dci-backups}" DATA_WG_IP="${DATA_WG_IP:-10.67.0.60}" DATA_PRIVATE_IP="${DATA_PRIVATE_IP:-10.68.0.1}" CRAWL_PRIVATE_IP="${CRAWL_PRIVATE_IP:-10.68.0.2}" PUBLIC_URL="${DCI_PUBLIC_URL:-https://www.datacenterindex.io}" SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15 -o ServerAliveInterval=30) c_bold=$'\033[1m'; c_dim=$'\033[2m'; c_red=$'\033[31m'; c_grn=$'\033[32m'; c_ylw=$'\033[33m'; c_rst=$'\033[0m' log() { printf '%s▸ %s%s\n' "$c_bold" "$*" "$c_rst"; } ok() { printf '%s✓ %s%s\n' "$c_grn" "$*" "$c_rst"; } warn() { printf '%s! %s%s\n' "$c_ylw" "$*" "$c_rst" >&2; } die() { printf '%s✗ %s%s\n' "$c_red" "$*" "$c_rst" >&2; exit 1; } # target → ssh alias host_for() { case "$1" in data) echo "${DCI_DATA_HOST:-BHS128}" ;; crawl) echo "${DCI_CRAWL_HOST:-BHS64b}" ;; *) die "unknown target '$1' (data|crawl)" ;; esac } check_target() { case "${1:-}" in data|crawl) ;; *) die "usage: $(basename "$0") data|crawl" ;; esac; } # service → target (for logs.sh / status.sh convenience) target_for_service() { case "$1" in worker|worker-b) echo crawl ;; *) echo data ;; esac } remote() { # remote local t="$1"; shift ssh "${SSH_OPTS[@]}" "$(host_for "$t")" "$@" } # The compose command as executed ON the remote host (docker or sudo docker, whichever works). compose_prefix() { # compose_prefix local t="$1" echo "cd $REMOTE_DIR && TSX_VERSION=$(tsx_version) \$(docker info >/dev/null 2>&1 && echo docker || echo 'sudo docker') compose -f deploy/compose.$t.yml --env-file deploy/.env.$t" } compose() { # compose local t="$1"; shift remote "$t" "$(compose_prefix "$t") $*" } tsx_version() { sed -nE 's/^ tsx@([0-9]+\.[0-9]+\.[0-9]+):.*/\1/p' "$REPO_ROOT/pnpm-lock.yaml" | head -1 | grep . || echo 4.23.13 } require_env_file() { # require_env_file local f="$REPO_ROOT/deploy/.env.$1" if [[ ! -f "$f" ]]; then if remote "$1" "test -f $REMOTE_DIR/deploy/.env.$1"; then warn "no local $f — keeping the existing remote copy" return 0 fi die "missing $f (copy deploy/.env.$1.example and fill the secrets)" fi if grep -qE '^(POSTGRES_PASSWORD|REDIS_PASSWORD|S3_SECRET_KEY)=$' "$f"; then die "$f still has empty secrets" fi } ensure_remote_layout() { # ensure_remote_layout local t="$1" # clickhouse (uid 101 in its container) writes BACKUP files into $BACKUP_DIR/clickhouse → sticky world-writable remote "$t" "sudo mkdir -p $REMOTE_DIR $BACKUP_DIR/pg $BACKUP_DIR/config $BACKUP_DIR/minio $BACKUP_DIR/clickhouse /srv/dci/out \ && sudo chown -R \$USER:\$USER /srv/dci && sudo chmod 1777 $BACKUP_DIR/clickhouse /srv/dci/out \ && (docker info >/dev/null 2>&1 || sudo usermod -aG docker \$USER)" } sync_repo() { # sync_repo local t="$1" host; host="$(host_for "$t")" log "rsync repo → $host:$REMOTE_DIR" rsync -az --delete \ --exclude-from="$REPO_ROOT/deploy/rsync-exclude.txt" \ -e "ssh ${SSH_OPTS[*]}" \ "$REPO_ROOT/" "$host:$REMOTE_DIR/" if [[ -f "$REPO_ROOT/deploy/.env.$t" ]]; then scp -q "${SSH_OPTS[@]}" "$REPO_ROOT/deploy/.env.$t" "$host:$REMOTE_DIR/deploy/.env.$t" && remote "$t" "chmod 600 $REMOTE_DIR/deploy/.env.$t" fi remote "$t" "chmod +x $REMOTE_DIR/deploy/bin/*.sh $REMOTE_DIR/deploy/bin/remote/*.sh $REMOTE_DIR/deploy/docker/entrypoint.sh $REMOTE_DIR/deploy/minio/init.sh" ok "synced" } wait_http() { # wait_http [attempts] [sleep] local t="$1" url="$2" n="${3:-60}" s="${4:-5}" i for ((i = 1; i <= n; i++)); do if remote "$t" "curl -fsS -m 5 -o /dev/null $url"; then ok "healthy: $url"; return 0; fi printf '%s waiting for %s (%d/%d)%s\n' "$c_dim" "$url" "$i" "$n" "$c_rst" sleep "$s" done return 1 }