scripts/build-and-push.sh now parses ~/.ssh/config (or --ssh-config) when --remote is a bare alias like 'prod'. It extracts HostName, User, Port and IdentityFile from the matching Host block, falls back to the current user / port 22, and uses the config IdentityFile when --ssh-key is not given. --ssh-key always wins. The resolved target is shown in the logs before any docker/pussh call, and is the form passed to docker pussh and the verification ssh. Wildcards, Include and Match directives are intentionally not handled: ssh itself resolves them when the alias is passed through, but the script logs the expanded target first for visibility.
284 lines
9.3 KiB
Bash
Executable File
284 lines
9.3 KiB
Bash
Executable File
#!/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 <<EOF
|
|
Usage: $(basename "$0") --remote USER@HOST[:PORT] [options]
|
|
|
|
Build the native Quarkus image and push it to a remote Docker host using
|
|
unregistry (docker-pussh).
|
|
|
|
Required:
|
|
--remote TARGET SSH destination. Either 'user@host[:port]'
|
|
or a bare alias resolved from ~/.ssh/config
|
|
(e.g. 'prod'). See SSH config aliases below.
|
|
|
|
Image identity (defaults read from pom.xml):
|
|
--name NAME Image repository name
|
|
--tag TAG Image tag
|
|
|
|
Working directory (defaults to the parent of the script's directory):
|
|
--project-dir PATH Project root containing pom.xml
|
|
(override with env PROJECT_DIR)
|
|
|
|
Build options:
|
|
--skip-build Skip docker build, only pussh an existing image
|
|
(platform is fixed to ${DEFAULT_PLATFORM})
|
|
|
|
SSH options forwarded to docker pussh:
|
|
--ssh-key PATH Private key file (overrides SSH config)
|
|
--ssh-config PATH SSH config file (default: ~/.ssh/config)
|
|
|
|
Post-push:
|
|
--no-verify Skip the ssh/docker images verification step
|
|
|
|
Other:
|
|
-h, --help Show this help
|
|
|
|
SSH config aliases:
|
|
When --remote is a bare alias (no '@'), it is resolved from the SSH
|
|
config: HostName, User, Port and IdentityFile are pulled from the
|
|
matching Host block, with sensible fallbacks. --ssh-key always wins
|
|
over the config IdentityFile.
|
|
|
|
Examples:
|
|
$(basename "$0") --remote deploy@prod.example.com
|
|
$(basename "$0") --remote prod # uses 'Host prod' in ~/.ssh/config
|
|
$(basename "$0") --remote ubuntu@1.2.3.4 --ssh-key ~/.ssh/deploy_ed25519
|
|
$(basename "$0") --remote deploy@prod --skip-build --tag 2.0.0
|
|
/absolute/path/to/test-quarkus/scripts/$(basename "$0") --remote deploy@prod
|
|
PROJECT_DIR=/srv/proj $(basename "$0") --remote deploy@prod --skip-build
|
|
EOF
|
|
}
|
|
|
|
read_pom() {
|
|
local xpath="$1" fallback="$2"
|
|
local value=""
|
|
if command -v xmllint >/dev/null 2>&1; then
|
|
value=$(xmllint --xpath "string($xpath)" pom.xml 2>/dev/null || true)
|
|
fi
|
|
if [[ -z "$value" ]]; then
|
|
value=$(grep -E "<artifactId>|<version>" pom.xml | head -2 \
|
|
| sed -E 's:.*<(artifactId|version)>(.*)</\1>.*:\2:')
|
|
fi
|
|
printf '%s' "${value:-$fallback}"
|
|
}
|
|
|
|
require() {
|
|
command -v "$1" >/dev/null 2>&1 || die "Required command '$1' not found in PATH."
|
|
}
|
|
|
|
resolve_ssh_alias() {
|
|
local alias="$1"
|
|
local config="${SSH_CONFIG:-$HOME/.ssh/config}"
|
|
|
|
if [[ "$alias" == *@* ]]; then
|
|
printf '%s|%s\n' "$alias" ""
|
|
return 0
|
|
fi
|
|
|
|
[[ -f "$config" ]] \
|
|
|| die "Cannot resolve '$alias': $config not found and value has no '@'."
|
|
|
|
local in_block=0 hostname="" user="" port="" identity="" line key value p
|
|
local -a patterns
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
line="${line#"${line%%[![:space:]]*}"}"
|
|
[[ -z "$line" || "${line:0:1}" == "#" ]] && continue
|
|
key="${line%% *}"
|
|
if [[ "$key" == "Host" ]]; then
|
|
if (( in_block )); then break; fi
|
|
read -r -a patterns <<< "${line#Host }"
|
|
in_block=0
|
|
for p in "${patterns[@]}"; do
|
|
if [[ "$p" == "$alias" ]]; then
|
|
in_block=1
|
|
hostname=""; user=""; port=""; identity=""
|
|
break
|
|
fi
|
|
done
|
|
continue
|
|
fi
|
|
if (( in_block )); then
|
|
case "$key" in
|
|
HostName) hostname="${line#* }" ;;
|
|
User) user="${line#* }" ;;
|
|
Port) port="${line#* }" ;;
|
|
IdentityFile) identity="${line#* }" ;;
|
|
esac
|
|
fi
|
|
done < "$config"
|
|
|
|
(( in_block )) \
|
|
|| die "No matching Host entry in $config for alias '$alias'. Use user@host[:port] or check ~/.ssh/config."
|
|
[[ -z "$hostname" ]] && die "Host '$alias' has no HostName in $config."
|
|
[[ -z "$user" ]] && user="$(whoami)"
|
|
[[ -z "$port" ]] && port="22"
|
|
identity="${identity/#\~/$HOME}"
|
|
|
|
printf '%s@%s:%s|%s\n' "$user" "$hostname" "$port" "$identity"
|
|
}
|
|
|
|
# ---------- 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}"
|
|
|
|
# ---------- resolve SSH target ----------
|
|
resolved=$(resolve_ssh_alias "$REMOTE")
|
|
REMOTE_TARGET="${resolved%|*}"
|
|
REMOTE_IDENTITY="${resolved##*|}"
|
|
if [[ "$REMOTE" != *@* ]]; then
|
|
log "Resolved alias '${REMOTE}' → ${REMOTE_TARGET}"
|
|
fi
|
|
if [[ -z "$SSH_KEY" && -n "$REMOTE_IDENTITY" ]]; then
|
|
SSH_KEY="$REMOTE_IDENTITY"
|
|
log "Using IdentityFile from SSH config: ${SSH_KEY}"
|
|
fi
|
|
|
|
# ---------- pre-flight ----------
|
|
log "Image: ${IMAGE_REF}"
|
|
log "Platform: ${DEFAULT_PLATFORM}"
|
|
log "Dockerfile: ${DOCKERFILE}"
|
|
log "Remote: ${REMOTE_TARGET}"
|
|
|
|
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 <<EOF
|
|
|
|
'docker pussh' plugin not found. Install it with:
|
|
|
|
mkdir -p ${PLUGIN_DIR}
|
|
curl -fsSL ${UNREGISTRY_PLUGIN} -o ${PLUGIN_PATH}
|
|
chmod +x ${PLUGIN_PATH}
|
|
|
|
Then rerun this script.
|
|
EOF
|
|
exit 1
|
|
fi
|
|
|
|
[[ -f "$DOCKERFILE" ]] || die "Dockerfile not found at ${DOCKERFILE}."
|
|
[[ -f pom.xml ]] || die "pom.xml not found in current directory."
|
|
|
|
# ---------- build ----------
|
|
if [[ "$SKIP_BUILD" -eq 1 ]]; then
|
|
warn "Skipping build (--skip-build). Assuming ${IMAGE_REF} exists locally."
|
|
docker image inspect "$IMAGE_REF" >/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_TARGET} via unregistry…"
|
|
|
|
pussh_args=( "$REMOTE_TARGET" )
|
|
[[ -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_TARGET}…"
|
|
ssh_args=( "$REMOTE_TARGET" )
|
|
[[ -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_TARGET} 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_TARGET" "$IMAGE_NAME" "$IMAGE_REF"
|