Files
cpone_midleware/scripts/deploy.sh

190 lines
5.6 KiB
Bash
Executable File

#!/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"