build: resolve --remote as SSH config alias when it has no @

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.
This commit is contained in:
2026-09-08 18:35:23 -03:00
parent 3b0e6b4a3f
commit 38b22648f1
2 changed files with 105 additions and 10 deletions
+23
View File
@@ -22,6 +22,29 @@ It can be invoked from any directory: by default it resolves the project root
from the script's own location. Override with `--project-dir PATH` or the from the script's own location. Override with `--project-dir PATH` or the
`PROJECT_DIR` environment variable when needed. `PROJECT_DIR` environment variable when needed.
### SSH config aliases
`--remote` accepts either an explicit `user@host[:port]` or a bare alias that
is resolved from `~/.ssh/config`. The matching `Host` block supplies
`HostName`, `User`, `Port` and `IdentityFile` automatically:
```text
# ~/.ssh/config
Host prod
HostName prod.example.com
User deploy
Port 22
IdentityFile ~/.ssh/deploy_ed25519
```
```shell script
./scripts/build-and-push.sh --remote prod
# Resolves to deploy@prod.example.com:22 using ~/.ssh/deploy_ed25519
```
`--ssh-key` always overrides the config `IdentityFile`. Use `--ssh-config PATH`
to point at a non-default config file.
## Running the application in dev mode ## Running the application in dev mode
You can run your application in dev mode that enables live coding using: You can run your application in dev mode that enables live coding using:
+82 -10
View File
@@ -59,7 +59,9 @@ Build the native Quarkus image and push it to a remote Docker host using
unregistry (docker-pussh). unregistry (docker-pussh).
Required: Required:
--remote USER@HOST[:PORT] SSH destination (e.g. deploy@prod.example.com) --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): Image identity (defaults read from pom.xml):
--name NAME Image repository name --name NAME Image repository name
@@ -74,8 +76,8 @@ Build options:
(platform is fixed to ${DEFAULT_PLATFORM}) (platform is fixed to ${DEFAULT_PLATFORM})
SSH options forwarded to docker pussh: SSH options forwarded to docker pussh:
--ssh-key PATH Private key file --ssh-key PATH Private key file (overrides SSH config)
--ssh-config PATH SSH config file --ssh-config PATH SSH config file (default: ~/.ssh/config)
Post-push: Post-push:
--no-verify Skip the ssh/docker images verification step --no-verify Skip the ssh/docker images verification step
@@ -83,8 +85,15 @@ Post-push:
Other: Other:
-h, --help Show this help -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: Examples:
$(basename "$0") --remote deploy@prod.example.com $(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 ubuntu@1.2.3.4 --ssh-key ~/.ssh/deploy_ed25519
$(basename "$0") --remote deploy@prod --skip-build --tag 2.0.0 $(basename "$0") --remote deploy@prod --skip-build --tag 2.0.0
/absolute/path/to/test-quarkus/scripts/$(basename "$0") --remote deploy@prod /absolute/path/to/test-quarkus/scripts/$(basename "$0") --remote deploy@prod
@@ -109,6 +118,57 @@ require() {
command -v "$1" >/dev/null 2>&1 || die "Required command '$1' not found in PATH." 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 ---------- # ---------- arg parsing ----------
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
@@ -138,11 +198,23 @@ PROJECT_DIR="$(pwd)"
IMAGE_REF="${IMAGE_NAME}:${IMAGE_TAG}" 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 ---------- # ---------- pre-flight ----------
log "Image: ${IMAGE_REF}" log "Image: ${IMAGE_REF}"
log "Platform: ${DEFAULT_PLATFORM}" log "Platform: ${DEFAULT_PLATFORM}"
log "Dockerfile: ${DOCKERFILE}" log "Dockerfile: ${DOCKERFILE}"
log "Remote: ${REMOTE}" log "Remote: ${REMOTE_TARGET}"
require docker require docker
docker info >/dev/null 2>&1 \ docker info >/dev/null 2>&1 \
@@ -181,9 +253,9 @@ else
fi fi
# ---------- pussh ---------- # ---------- pussh ----------
log "Pusshing ${IMAGE_REF} to ${REMOTE} via unregistry…" log "Pusshing ${IMAGE_REF} to ${REMOTE_TARGET} via unregistry…"
pussh_args=( "$REMOTE" ) pussh_args=( "$REMOTE_TARGET" )
[[ -n "$SSH_KEY" ]] && pussh_args+=( -i "$SSH_KEY" ) [[ -n "$SSH_KEY" ]] && pussh_args+=( -i "$SSH_KEY" )
[[ -n "$SSH_CONFIG" ]] && pussh_args+=( -F "$SSH_CONFIG" ) [[ -n "$SSH_CONFIG" ]] && pussh_args+=( -F "$SSH_CONFIG" )
@@ -194,13 +266,13 @@ ok "Pussh complete."
if [[ "$SKIP_VERIFY" -eq 1 ]]; then if [[ "$SKIP_VERIFY" -eq 1 ]]; then
warn "Skipping post-push verification (--no-verify)." warn "Skipping post-push verification (--no-verify)."
else else
log "Verifying ${IMAGE_REF} on ${REMOTE}…" log "Verifying ${IMAGE_REF} on ${REMOTE_TARGET}…"
ssh_args=( "$REMOTE" ) ssh_args=( "$REMOTE_TARGET" )
[[ -n "$SSH_KEY" ]] && ssh_args=( -i "$SSH_KEY" "${ssh_args[@]}" ) [[ -n "$SSH_KEY" ]] && ssh_args=( -i "$SSH_KEY" "${ssh_args[@]}" )
[[ -n "$SSH_CONFIG" ]] && ssh_args=( -F "$SSH_CONFIG" "${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 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." die "Image ${IMAGE_REF} not found on ${REMOTE_TARGET} after pussh."
fi fi
ok "Image present on remote." ok "Image present on remote."
fi fi
@@ -208,4 +280,4 @@ fi
# ---------- next step hint ---------- # ---------- next step hint ----------
printf '\n%s→ To run on the remote:%s\n' "$C_OK" "$C_RESET" 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' \ printf ' ssh %s "docker run -d --name %s --restart unless-stopped -p 8080:8080 %s"\n\n' \
"$REMOTE" "$IMAGE_NAME" "$IMAGE_REF" "$REMOTE_TARGET" "$IMAGE_NAME" "$IMAGE_REF"