#!/usr/bin/env bash # # Build the native Quarkus image and push it to a remote Docker host # via docker-pussh (unregistry): https://github.com/psviderski/unregistry # # - Image name/tag default to pom.xml properties (quarkus.application.*) # - Builds for linux/amd64 by default (cross-compile from arm64 hosts) # - Transfers only missing layers over SSH (no external registry required) set -euo pipefail IFS=$'\n\t' readonly DOCKERFILE="src/main/docker/Dockerfile" readonly DEFAULT_PLATFORM="linux/amd64" readonly UNREGISTRY_PLUGIN="https://raw.githubusercontent.com/psviderski/unregistry/main/docker-pussh" readonly PLUGIN_DIR="${HOME}/.docker/cli-plugins" readonly PLUGIN_PATH="${PLUGIN_DIR}/docker-pussh" readonly SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)" REMOTE="" IMAGE_NAME="" IMAGE_TAG="" SSH_KEY="" SSH_CONFIG="" PROJECT_DIR="${PROJECT_DIR:-${SCRIPT_DIR}/..}" SKIP_BUILD=0 SKIP_VERIFY=0 # ---------- logging ---------- if [[ -t 1 ]]; then readonly C_INFO=$'\033[0;36m' readonly C_OK=$'\033[0;32m' readonly C_WARN=$'\033[0;33m' readonly C_ERR=$'\033[0;31m' readonly C_RESET=$'\033[0m' else readonly C_INFO="" C_OK="" C_WARN="" C_ERR="" C_RESET="" fi log() { printf '%s[INFO]%s %s\n' "$C_INFO" "$C_RESET" "$*"; } ok() { printf '%s[OK]%s %s\n' "$C_OK" "$C_RESET" "$*"; } warn() { printf '%s[WARN]%s %s\n' "$C_WARN" "$C_RESET" "$*" >&2; } err() { printf '%s[ERROR]%s %s\n' "$C_ERR" "$C_RESET" "$*" >&2; } die() { err "$*"; exit 1; } on_error() { local exit_code=$? err "Script aborted on line ${BASH_LINENO[0]} (exit ${exit_code})." exit "$exit_code" } trap on_error ERR # ---------- helpers ---------- usage() { cat </dev/null 2>&1; then value=$(xmllint --xpath "string($xpath)" pom.xml 2>/dev/null || true) fi if [[ -z "$value" ]]; then value=$(grep -E "|" pom.xml | head -2 \ | sed -E 's:.*<(artifactId|version)>(.*).*:\2:') fi printf '%s' "${value:-$fallback}" } require() { command -v "$1" >/dev/null 2>&1 || die "Required command '$1' not found in PATH." } # ---------- arg parsing ---------- while [[ $# -gt 0 ]]; do case "$1" in --remote) REMOTE="$2"; shift 2 ;; --name) IMAGE_NAME="$2"; shift 2 ;; --tag) IMAGE_TAG="$2"; shift 2 ;; --ssh-key) SSH_KEY="$2"; shift 2 ;; --ssh-config) SSH_CONFIG="$2"; shift 2 ;; --project-dir) PROJECT_DIR="$2"; shift 2 ;; --skip-build) SKIP_BUILD=1; shift ;; --no-verify) SKIP_VERIFY=1; shift ;; -h|--help) usage; exit 0 ;; *) die "Unknown argument: $1 (use --help)" ;; esac done # ---------- resolve project root ---------- if ! cd "${PROJECT_DIR}" 2>/dev/null; then die "Invalid --project-dir: ${PROJECT_DIR}" fi PROJECT_DIR="$(pwd)" # ---------- defaults ---------- [[ -z "$IMAGE_NAME" ]] && IMAGE_NAME=$(read_pom "/*[local-name()='project']/*[local-name()='artifactId']" "test-quarkus") [[ -z "$IMAGE_TAG" ]] && IMAGE_TAG=$(read_pom "/*[local-name()='project']/*[local-name()='version']" "1.0.0-SNAPSHOT") [[ -z "$REMOTE" ]] && die "Missing --remote USER@HOST[:PORT] (use --help)." IMAGE_REF="${IMAGE_NAME}:${IMAGE_TAG}" # ---------- pre-flight ---------- log "Image: ${IMAGE_REF}" log "Platform: ${DEFAULT_PLATFORM}" log "Dockerfile: ${DOCKERFILE}" log "Remote: ${REMOTE}" require docker docker info >/dev/null 2>&1 \ || die "Docker daemon is not reachable. Start Docker Desktop (or your daemon) and retry." if ! docker pussh --help >/dev/null 2>&1; then cat >&2 </dev/null 2>&1 \ || die "Image ${IMAGE_REF} not present locally; remove --skip-build to build it." else log "Building ${IMAGE_REF} for ${DEFAULT_PLATFORM} (this may take several minutes)…" docker build \ --platform "$DEFAULT_PLATFORM" \ -f "$DOCKERFILE" \ -t "$IMAGE_REF" \ . ok "Build finished." fi # ---------- pussh ---------- log "Pusshing ${IMAGE_REF} to ${REMOTE} via unregistry…" pussh_args=( "$REMOTE" ) [[ -n "$SSH_KEY" ]] && pussh_args+=( -i "$SSH_KEY" ) [[ -n "$SSH_CONFIG" ]] && pussh_args+=( -F "$SSH_CONFIG" ) docker pussh "${IMAGE_REF}" "${pussh_args[@]}" ok "Pussh complete." # ---------- verify ---------- if [[ "$SKIP_VERIFY" -eq 1 ]]; then warn "Skipping post-push verification (--no-verify)." else log "Verifying ${IMAGE_REF} on ${REMOTE}…" ssh_args=( "$REMOTE" ) [[ -n "$SSH_KEY" ]] && ssh_args=( -i "$SSH_KEY" "${ssh_args[@]}" ) [[ -n "$SSH_CONFIG" ]] && ssh_args=( -F "$SSH_CONFIG" "${ssh_args[@]}" ) if ! ssh "${ssh_args[@]}" "docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep -E '^${IMAGE_NAME}:${IMAGE_TAG} '"; then die "Image ${IMAGE_REF} not found on ${REMOTE} after pussh." fi ok "Image present on remote." fi # ---------- next step hint ---------- printf '\n%s→ To run on the remote:%s\n' "$C_OK" "$C_RESET" printf ' ssh %s "docker run -d --name %s --restart unless-stopped -p 8080:8080 %s"\n\n' \ "$REMOTE" "$IMAGE_NAME" "$IMAGE_REF"