fix(P04): deploy.sh — UAT branch lookup strips patch segment; SSH-key auth preferred

---ci---
phase: 4
milestone: v0.1
status: execute
persona: ci-lead-developer
task: T-4.2
requirements:
  covered: [REQ-52]
  partial: []
---/ci---

UAT variant now resolves the milestone branch by milestone number
(v0.1) not full tag version (v0.1.4), matching branch-strategy
labeling. DEPLOY_KEY (path in .env.secrets) preferred over passwords —
survives sshd PermitRootLogin prohibit-password (Ubuntu 24.04 default
that rejects root password auth; observed on the LXC host). Deployed
v0.1.4-uat for manual UAT: http://10.0.0.114:3000.
This commit is contained in:
coreci
2026-09-27 02:48:41 +00:00
parent 2ffc71cff9
commit 0fa0b07429
+40 -22
View File
@@ -44,31 +44,41 @@ command -v ssh >/dev/null || die "ssh not found" 2
DEPLOY_HOST=$(grep -E "^DEPLOY_HOST=" "$SECRETS_FILE" | cut -d= -f2- | tr -d '\r')
DEPLOY_USER=$(grep -E "^DEPLOY_USER=" "$SECRETS_FILE" | cut -d= -f2- | tr -d '\r')
DEPLOY_PASSWORD=$(grep -E "^DEPLOY_PASSWORD=" "$SECRETS_FILE" | cut -d= -f2- | tr -d '\r')
[ -n "$DEPLOY_HOST" ] && [ -n "$DEPLOY_USER" ] && [ -n "$DEPLOY_PASSWORD" ] \
|| die "DEPLOY_HOST/DEPLOY_USER/DEPLOY_PASSWORD not set in $SECRETS_FILE" 2
DEPLOY_KEY=$(grep -E "^DEPLOY_KEY=" "$SECRETS_FILE" | cut -d= -f2- | tr -d '\r')
[ -n "$DEPLOY_HOST" ] && [ -n "$DEPLOY_USER" ] \
|| die "DEPLOY_HOST/DEPLOY_USER not set in $SECRETS_FILE" 2
# Password auth transport + file-sync transport.
# sshpass when available; otherwise OpenSSH's native SSH_ASKPASS
# (no extra binary). rsync when available; otherwise tar-over-ssh.
# Auth: an SSH key (DEPLOY_KEY=path in .env.secrets) is preferred —
# it survives sshd 'PermitRootLogin prohibit-password' policies that
# reject root passwords. Password auth (DEPLOY_PASSWORD) is fallback.
SSH_AUTH_DIR=$(mktemp -d)
trap 'rm -rf "$SSH_AUTH_DIR"' EXIT
if command -v sshpass >/dev/null; then
SSH_TRANSPORT="sshpass"
SSH() { sshpass -p "$DEPLOY_PASSWORD" ssh -o StrictHostKeyChecking=accept-new "$DEPLOY_USER@$DEPLOY_HOST" "$@"; }
if [ -n "$DEPLOY_KEY" ] && [ -f "$DEPLOY_KEY" ]; then
SSH_TRANSPORT="ssh-key:${DEPLOY_KEY##*/}"
SSH() { ssh -i "$DEPLOY_KEY" -o IdentitiesOnly=yes \
-o StrictHostKeyChecking=accept-new "$DEPLOY_USER@$DEPLOY_HOST" "$@"; }
else
SSH_TRANSPORT="ssh-askpass"
cat > "$SSH_AUTH_DIR/askpass.sh" <<ASKPASS
[ -n "$DEPLOY_PASSWORD" ] \
|| die "neither DEPLOY_KEY (existing file) nor DEPLOY_PASSWORD set in $SECRETS_FILE" 2
if command -v sshpass >/dev/null; then
SSH_TRANSPORT="sshpass"
SSH() { sshpass -p "$DEPLOY_PASSWORD" ssh -o StrictHostKeyChecking=accept-new "$DEPLOY_USER@$DEPLOY_HOST" "$@"; }
else
SSH_TRANSPORT="ssh-askpass"
cat > "$SSH_AUTH_DIR/askpass.sh" <<ASKPASS
#!/usr/bin/env bash
printf '%s\n' "\$DEPLOY_PASSWORD"
ASKPASS
chmod 700 "$SSH_AUTH_DIR/askpass.sh"
SSH() {
DEPLOY_PASSWORD="$DEPLOY_PASSWORD" SSH_ASKPASS="$SSH_AUTH_DIR/askpass.sh" \
SSH_ASKPASS_REQUIRE=force DISPLAY=:0 \
ssh -o StrictHostKeyChecking=accept-new "$DEPLOY_USER@$DEPLOY_HOST" "$@"
}
chmod 700 "$SSH_AUTH_DIR/askpass.sh"
SSH() {
DEPLOY_PASSWORD="$DEPLOY_PASSWORD" SSH_ASKPASS="$SSH_AUTH_DIR/askpass.sh" \
SSH_ASKPASS_REQUIRE=force DISPLAY=:0 \
ssh -o StrictHostKeyChecking=accept-new "$DEPLOY_USER@$DEPLOY_HOST" "$@"
}
fi
fi
log "password transport: $SSH_TRANSPORT"
log "ssh transport: $SSH_TRANSPORT"
if command -v rsync >/dev/null; then
SYNC_TRANSPORT="rsync"
@@ -77,11 +87,16 @@ else
fi
# Sync the build context to the host (excludes live in the Dockerfile's
# .dockerignore; we only prune heavy dirs here).
# .dockerignore; we only prune heavy dirs here). rsync uses the SSH
# function's transport implicitly via -e; tar-over-ssh uses SSH directly.
sync_source() {
local src="$1" dst="$2"
if [ "$SYNC_TRANSPORT" = "rsync" ]; then
if [ "$SSH_TRANSPORT" = "sshpass" ]; then
if [[ "$SSH_TRANSPORT" == ssh-key:* ]]; then
rsync -az --delete --exclude node_modules --exclude .next --exclude .git \
-e "ssh -i '$DEPLOY_KEY' -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \
"$src" "$DEPLOY_USER@$DEPLOY_HOST:$dst/"
elif [ "$SSH_TRANSPORT" = "sshpass" ]; then
sshpass -p "$DEPLOY_PASSWORD" rsync -az --delete \
--exclude node_modules --exclude .next --exclude .git \
-e "ssh -o StrictHostKeyChecking=accept-new" "$src" "$DEPLOY_USER@$DEPLOY_HOST:$dst/"
@@ -117,9 +132,12 @@ git -C "$REPO_ROOT" rev-parse -q --verify "refs/tags/$BASE_VERSION" >/dev/null \
|| die "tag $BASE_VERSION not found locally or on origin" 2
if [ "$UAT_BUILD" = true ]; then
# Deploy the milestone branch HEAD carrying this tag (review staging)
MILESTONE_BRANCH=$(git -C "$REPO_ROOT" branch -r --list "origin/milestone/${BASE_VERSION#v}*" | head -1 | tr -d ' ')
[ -n "$MILESTONE_BRANCH" ] || die "no milestone branch found for $BASE_VERSION" 2
# Deploy the milestone branch HEAD carrying this tag (review staging).
# Branch labels use the MILESTONE number (v0.1), tags the full patch
# version (v0.1.4) — strip the patch segment for the branch lookup.
MILESTONE_VER="${BASE_VERSION%.*}" # v0.1.4 -> v0.1
MILESTONE_BRANCH=$(git -C "$REPO_ROOT" branch -r --list "origin/milestone/${MILESTONE_VER}-*" | head -1 | tr -d ' ')
[ -n "$MILESTONE_BRANCH" ] || die "no milestone branch found for $BASE_VERSION (looked for origin/milestone/${MILESTONE_VER}-*)" 2
TAG_COMMIT=$(git -C "$REPO_ROOT" rev-parse "${MILESTONE_BRANCH#origin/}" 2>/dev/null || git -C "$REPO_ROOT" rev-parse "$MILESTONE_BRANCH")
log "UAT build: milestone HEAD $TAG_COMMIT (tag $BASE_VERSION + review infra)"
else