#!/usr/bin/env bash set -Eeuo pipefail remote_target="${1:-}" app_name="${APP_NAME:-cpone-middleware}" build_binary="${BUILD_BINARY:-dist/${app_name}-linux-amd64}" requested_remote_dir="${REMOTE_DIR:-auto}" remote_binary_override="${REMOTE_BINARY:-}" service="${SERVICE:-${app_name}.service}" service_scope="${SERVICE_SCOPE:-auto}" restart_mode="${RESTART_MODE:-systemd-kill}" ssh_timeout="${SSH_TIMEOUT:-45s}" transfer_timeout="${TRANSFER_TIMEOUT:-120s}" if [[ -z "$remote_target" ]]; then printf 'Error: target server belum diisi.\n' >&2 exit 2 fi if [[ ! "$remote_target" =~ ^[A-Za-z0-9._-]+@[A-Za-z0-9._:-]+$ ]]; then printf 'Error: target harus berbentuk user@host (gunakan SSH_OPTIONS untuk port).\n' >&2 exit 2 fi if [[ ! -f "$build_binary" ]]; then printf 'Error: binary tidak ditemukan: %s\n' "$build_binary" >&2 printf 'Jalankan `make build` terlebih dahulu.\n' >&2 exit 2 fi case "$restart_mode" in systemd-kill|systemctl) ;; *) printf 'Error: RESTART_MODE harus systemd-kill atau systemctl.\n' >&2 exit 2 ;; esac case "$service_scope" in auto|user|system) ;; *) printf 'Error: SERVICE_SCOPE harus auto, user, atau system.\n' >&2 exit 2 ;; esac if ! command -v ssh >/dev/null 2>&1 || ! command -v scp >/dev/null 2>&1 || ! command -v timeout >/dev/null 2>&1; then printf 'Error: ssh, scp, dan timeout wajib tersedia di komputer lokal.\n' >&2 exit 2 fi read -r -a ssh_options <<< "${SSH_OPTIONS:-}" remote_metadata="$(timeout --foreground "$ssh_timeout" ssh "${ssh_options[@]}" "$remote_target" bash -s -- "$service" "$service_scope" <<'REMOTE_METADATA' set -Eeuo pipefail service="$1" requested_scope="$2" if [[ "$requested_scope" == "user" ]]; then systemctl_cmd=(systemctl --user) resolved_scope=user elif [[ "$requested_scope" == "system" ]]; then systemctl_cmd=(systemctl) resolved_scope=system elif systemctl --user show "$service" >/dev/null 2>&1; then systemctl_cmd=(systemctl --user) resolved_scope=user elif systemctl show "$service" >/dev/null 2>&1; then systemctl_cmd=(systemctl) resolved_scope=system else echo "Service tidak ditemukan: $service" >&2 exit 1 fi working_directory="$("${systemctl_cmd[@]}" show "$service" -p WorkingDirectory --value 2>/dev/null || true)" printf '%s\t%s\n' "$resolved_scope" "$working_directory" REMOTE_METADATA )" IFS=$'\t' read -r resolved_service_scope service_working_directory <<< "$remote_metadata" if [[ "$resolved_service_scope" != user && "$resolved_service_scope" != system ]]; then printf 'Error: scope service remote tidak valid: %s\n' "$resolved_service_scope" >&2 exit 1 fi if [[ "$requested_remote_dir" == "auto" ]]; then if [[ -n "$service_working_directory" && "$service_working_directory" != "-" ]]; then remote_dir="${service_working_directory%/}" else remote_home="$(timeout --foreground "$ssh_timeout" ssh "${ssh_options[@]}" "$remote_target" 'printf "%s" "$HOME"')" if [[ -z "$remote_home" ]]; then printf 'Error: tidak dapat menentukan HOME pada server remote.\n' >&2 exit 1 fi remote_dir="${remote_home%/}/apps" fi else remote_dir="$requested_remote_dir" fi remote_binary="${remote_binary_override:-${remote_dir}/${app_name}}" remote_tmp="${remote_dir}/.${app_name}.new.$$.$RANDOM" cleanup_needed=0 cleanup_remote_tmp() { if (( cleanup_needed )); then timeout --foreground "$ssh_timeout" ssh "${ssh_options[@]}" "$remote_target" bash -s -- "$remote_tmp" <<'REMOTE_CLEANUP' >/dev/null 2>&1 || true set -eu rm -f -- "$1" REMOTE_CLEANUP fi } trap cleanup_remote_tmp EXIT printf 'Preparing remote directory %s:%s\n' "$remote_target" "$remote_dir" timeout --foreground "$ssh_timeout" ssh "${ssh_options[@]}" "$remote_target" bash -s -- "$remote_dir" <<'REMOTE_PREPARE' set -eu install -d -- "$1" REMOTE_PREPARE printf 'Uploading %s -> %s:%s\n' "$build_binary" "$remote_target" "$remote_tmp" timeout --foreground "$transfer_timeout" scp "${ssh_options[@]}" "$build_binary" "$remote_target:$remote_tmp" cleanup_needed=1 printf 'Installing binary and restarting %s\n' "$service" timeout --foreground "$ssh_timeout" ssh "${ssh_options[@]}" "$remote_target" bash -s -- \ "$remote_binary" "$service" "$remote_tmp" "$restart_mode" "$resolved_service_scope" <<'REMOTE_INSTALL' set -Eeuo pipefail binary="$1" service="$2" tmp="$3" restart_mode="$4" service_scope="$5" main_pid="" if [[ "$service_scope" == "user" ]]; then systemctl_cmd=(systemctl --user) else systemctl_cmd=(systemctl) fi if [[ ! -s "$tmp" ]]; then echo "Uploaded binary kosong atau tidak ditemukan: $tmp" >&2 exit 1 fi if [[ "$restart_mode" == "systemd-kill" ]]; then main_pid="$("${systemctl_cmd[@]}" show "$service" -p MainPID --value 2>/dev/null || true)" if [[ ! "$main_pid" =~ ^[1-9][0-9]*$ ]]; then echo "Tidak dapat menemukan MainPID aktif untuk $service; binary belum diganti." >&2 exit 1 fi fi chmod 0755 "$tmp" if [[ -e "$binary" ]]; then backup="${binary}.before-$(date +%Y%m%d%H%M%S)-$$" cp -p -- "$binary" "$backup" echo "Backup: $backup" fi mv -f -- "$tmp" "$binary" if [[ "$restart_mode" == "systemctl" ]]; then "${systemctl_cmd[@]}" restart "$service" else kill -TERM "$main_pid" fi for attempt in {1..20}; do if "${systemctl_cmd[@]}" is-active --quiet "$service"; then new_pid="$("${systemctl_cmd[@]}" show "$service" -p MainPID --value 2>/dev/null || true)" if [[ "$restart_mode" == "systemctl" || "$new_pid" != "$main_pid" ]]; then echo "Service aktif: $service (MainPID=${new_pid:-unknown})" exit 0 fi fi sleep 1 done echo "Service tidak kembali aktif dalam 20 detik: $service" >&2 "${systemctl_cmd[@]}" status "$service" --no-pager >&2 || true exit 1 REMOTE_INSTALL cleanup_needed=0 printf 'Deploy selesai ke %s\n' "$remote_target"