Fix Windows deploy by copying remote script via scp instead of piping.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Remote deployment steps (run on the server via bash -s or scp + bash).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REMOTE_DIR="$1"
|
||||||
|
BRANCH="$2"
|
||||||
|
EXPECTED_SHA="$3"
|
||||||
|
HEALTH_URL="$4"
|
||||||
|
HEALTH_RETRIES="$5"
|
||||||
|
HEALTH_INTERVAL="$6"
|
||||||
|
|
||||||
|
info() { printf '==> [remote] %s\n' "$*"; }
|
||||||
|
die() { printf 'ERROR: [remote] %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
command -v docker >/dev/null 2>&1 || die "docker not found on remote host"
|
||||||
|
docker compose version >/dev/null 2>&1 || die "docker compose not available on remote host"
|
||||||
|
command -v curl >/dev/null 2>&1 || die "curl not found on remote host"
|
||||||
|
|
||||||
|
[[ -d "$REMOTE_DIR/.git" ]] || die "Directory is not a git repo: $REMOTE_DIR"
|
||||||
|
|
||||||
|
cd "$REMOTE_DIR"
|
||||||
|
|
||||||
|
if [[ -n "$(git status --porcelain)" ]]; then
|
||||||
|
die "Remote working tree is dirty. Resolve local changes on the server first."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Fetching origin…"
|
||||||
|
git fetch origin
|
||||||
|
|
||||||
|
REMOTE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
|
if [[ "$REMOTE_BRANCH" != "$BRANCH" ]]; then
|
||||||
|
info "Checking out branch $BRANCH"
|
||||||
|
git checkout "$BRANCH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Fast-forwarding to origin/$BRANCH"
|
||||||
|
git pull --ff-only origin "$BRANCH"
|
||||||
|
|
||||||
|
ACTUAL_SHA="$(git rev-parse HEAD)"
|
||||||
|
if [[ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]]; then
|
||||||
|
die "Remote SHA mismatch after pull (expected $EXPECTED_SHA, got $ACTUAL_SHA)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Rebuilding and starting containers…"
|
||||||
|
docker compose up -d --build --remove-orphans
|
||||||
|
|
||||||
|
info "Waiting for health check ($HEALTH_URL)…"
|
||||||
|
ok=0
|
||||||
|
for ((i = 1; i <= HEALTH_RETRIES; i++)); do
|
||||||
|
if curl -fsS -o /dev/null "$HEALTH_URL" 2>/dev/null; then
|
||||||
|
ok=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep "$HEALTH_INTERVAL"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$ok" -ne 1 ]]; then
|
||||||
|
die "Health check failed after $((HEALTH_RETRIES * HEALTH_INTERVAL))s."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Health check OK"
|
||||||
|
|
||||||
|
info "Pruning stopped containers…"
|
||||||
|
docker container prune -f >/dev/null
|
||||||
|
|
||||||
|
info "Pruning dangling images…"
|
||||||
|
docker image prune -f >/dev/null
|
||||||
|
|
||||||
|
info "Service status:"
|
||||||
|
docker compose ps
|
||||||
+53
-83
@@ -12,6 +12,46 @@ function Fail([string]$Message) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Invoke-RemoteDeploy {
|
||||||
|
param(
|
||||||
|
[string[]]$SshOptions,
|
||||||
|
[string]$TargetHost,
|
||||||
|
[string]$ScriptPath,
|
||||||
|
[string]$RemoteDir,
|
||||||
|
[string]$Branch,
|
||||||
|
[string]$ExpectedSha,
|
||||||
|
[string]$HealthUrl,
|
||||||
|
[int]$HealthRetries,
|
||||||
|
[int]$HealthInterval
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Get-Command scp -ErrorAction SilentlyContinue)) {
|
||||||
|
Fail "scp not found. Install OpenSSH client or use Git Bash with scripts/deploy.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
$remoteScript = "/tmp/if-viewer-deploy-$PID.sh"
|
||||||
|
$quoted = @(
|
||||||
|
$RemoteDir,
|
||||||
|
$Branch,
|
||||||
|
$ExpectedSha,
|
||||||
|
$HealthUrl,
|
||||||
|
"$HealthRetries",
|
||||||
|
"$HealthInterval"
|
||||||
|
) | ForEach-Object { "'" + ($_ -replace "'", "'\\''") + "'" }
|
||||||
|
|
||||||
|
$remoteArgs = $quoted -join " "
|
||||||
|
$remoteRun = "bash $remoteScript $remoteArgs; rc=`$?; rm -f $remoteScript; exit `$rc"
|
||||||
|
|
||||||
|
& scp @SshOptions $ScriptPath "${TargetHost}:${remoteScript}"
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
|
||||||
|
& ssh @SshOptions $TargetHost $remoteRun
|
||||||
|
return $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$RemoteScriptPath = Join-Path $ScriptDir "deploy-remote.sh"
|
||||||
|
|
||||||
$RepoRoot = git rev-parse --show-toplevel 2>$null
|
$RepoRoot = git rev-parse --show-toplevel 2>$null
|
||||||
if (-not $RepoRoot) { Fail "Not inside a git repository." }
|
if (-not $RepoRoot) { Fail "Not inside a git repository." }
|
||||||
Set-Location $RepoRoot
|
Set-Location $RepoRoot
|
||||||
@@ -32,7 +72,7 @@ $HealthInterval = if ($env:DEPLOY_HEALTH_INTERVAL) { [int]$env:DEPLOY_HEALTH_INT
|
|||||||
$SshOpts = @("-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new")
|
$SshOpts = @("-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new")
|
||||||
|
|
||||||
$Branch = git rev-parse --abbrev-ref HEAD
|
$Branch = git rev-parse --abbrev-ref HEAD
|
||||||
if ($Branch -eq "HEAD") { Fail "Detached HEAD – checkout a branch before deploying." }
|
if ($Branch -eq "HEAD") { Fail "Detached HEAD - checkout a branch before deploying." }
|
||||||
|
|
||||||
$Status = git status --porcelain
|
$Status = git status --porcelain
|
||||||
if ($Status) { Fail "Working tree is not clean. Commit or stash changes before deploying." }
|
if ($Status) { Fail "Working tree is not clean. Commit or stash changes before deploying." }
|
||||||
@@ -42,92 +82,22 @@ if (-not $Upstream) { Fail "Branch '$Branch' has no upstream. Run: git push -u o
|
|||||||
|
|
||||||
$LocalSha = git rev-parse HEAD
|
$LocalSha = git rev-parse HEAD
|
||||||
Write-Step "Local branch: $Branch ($LocalSha)"
|
Write-Step "Local branch: $Branch ($LocalSha)"
|
||||||
Write-Step "Pushing $Branch to origin…"
|
Write-Step "Pushing $Branch to origin..."
|
||||||
git push origin $Branch
|
git push origin $Branch
|
||||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
|
||||||
Write-Step "Deploying to ${DeployHost}:${DeployDir}"
|
Write-Step "Deploying to ${DeployHost}:${DeployDir}"
|
||||||
|
|
||||||
$RemoteScript = @'
|
$exitCode = Invoke-RemoteDeploy `
|
||||||
set -euo pipefail
|
-SshOptions $SshOpts `
|
||||||
REMOTE_DIR="$1"
|
-TargetHost $DeployHost `
|
||||||
BRANCH="$2"
|
-ScriptPath $RemoteScriptPath `
|
||||||
EXPECTED_SHA="$3"
|
-RemoteDir $DeployDir `
|
||||||
HEALTH_URL="$4"
|
-Branch $Branch `
|
||||||
HEALTH_RETRIES="$5"
|
-ExpectedSha $LocalSha `
|
||||||
HEALTH_INTERVAL="$6"
|
-HealthUrl $HealthUrl `
|
||||||
|
-HealthRetries $HealthRetries `
|
||||||
info() { printf '==> [remote] %s\n' "$*"; }
|
-HealthInterval $HealthInterval
|
||||||
die() { printf 'ERROR: [remote] %s\n' "$*" >&2; exit 1; }
|
if ($exitCode -ne 0) { exit $exitCode }
|
||||||
|
|
||||||
command -v docker >/dev/null 2>&1 || die "docker not found on remote host"
|
|
||||||
docker compose version >/dev/null 2>&1 || die "docker compose not available on remote host"
|
|
||||||
command -v curl >/dev/null 2>&1 || die "curl not found on remote host"
|
|
||||||
|
|
||||||
[[ -d "$REMOTE_DIR/.git" ]] || die "Directory is not a git repo: $REMOTE_DIR"
|
|
||||||
|
|
||||||
cd "$REMOTE_DIR"
|
|
||||||
|
|
||||||
if [[ -n "$(git status --porcelain)" ]]; then
|
|
||||||
die "Remote working tree is dirty. Resolve local changes on the server first."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Fetching origin…"
|
|
||||||
git fetch origin
|
|
||||||
|
|
||||||
REMOTE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
||||||
if [[ "$REMOTE_BRANCH" != "$BRANCH" ]]; then
|
|
||||||
info "Checking out branch $BRANCH"
|
|
||||||
git checkout "$BRANCH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Fast-forwarding to origin/$BRANCH"
|
|
||||||
git pull --ff-only origin "$BRANCH"
|
|
||||||
|
|
||||||
ACTUAL_SHA="$(git rev-parse HEAD)"
|
|
||||||
if [[ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]]; then
|
|
||||||
die "Remote SHA mismatch after pull (expected $EXPECTED_SHA, got $ACTUAL_SHA)."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Rebuilding and starting containers…"
|
|
||||||
docker compose up -d --build --remove-orphans
|
|
||||||
|
|
||||||
info "Waiting for health check ($HEALTH_URL)…"
|
|
||||||
ok=0
|
|
||||||
for ((i = 1; i <= HEALTH_RETRIES; i++)); do
|
|
||||||
if curl -fsS -o /dev/null "$HEALTH_URL" 2>/dev/null; then
|
|
||||||
ok=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep "$HEALTH_INTERVAL"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$ok" -ne 1 ]]; then
|
|
||||||
die "Health check failed after $((HEALTH_RETRIES * HEALTH_INTERVAL))s."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Health check OK"
|
|
||||||
|
|
||||||
info "Pruning stopped containers…"
|
|
||||||
docker container prune -f >/dev/null
|
|
||||||
|
|
||||||
info "Pruning dangling images…"
|
|
||||||
docker image prune -f >/dev/null
|
|
||||||
|
|
||||||
info "Service status:"
|
|
||||||
docker compose ps
|
|
||||||
'@
|
|
||||||
|
|
||||||
# PowerShell here-strings use CRLF on Windows; strip CR before sending to remote bash.
|
|
||||||
$RemoteScript = ($RemoteScript -replace "`r`n", "`n") -replace "`r", ""
|
|
||||||
|
|
||||||
$RemoteScript | & ssh @SshOpts $DeployHost bash -s -- `
|
|
||||||
$DeployDir `
|
|
||||||
$Branch `
|
|
||||||
$LocalSha `
|
|
||||||
$HealthUrl `
|
|
||||||
"$HealthRetries" `
|
|
||||||
"$HealthInterval"
|
|
||||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
||||||
|
|
||||||
Write-Step "Deployment finished successfully."
|
Write-Step "Deployment finished successfully."
|
||||||
|
|||||||
+4
-73
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
REMOTE_HOST="${DEPLOY_HOST:-root@10.0.0.5}"
|
REMOTE_HOST="${DEPLOY_HOST:-root@10.0.0.5}"
|
||||||
REMOTE_DIR="${DEPLOY_DIR:-/opt/apps/Idle-Fantasy-Save-Viewer}"
|
REMOTE_DIR="${DEPLOY_DIR:-/opt/apps/Idle-Fantasy-Save-Viewer}"
|
||||||
HEALTH_URL="${DEPLOY_HEALTH_URL:-http://127.0.0.1:5000/}"
|
HEALTH_URL="${DEPLOY_HEALTH_URL:-http://127.0.0.1:5000/}"
|
||||||
@@ -49,85 +51,14 @@ info "Local branch: $BRANCH ($LOCAL_SHA)"
|
|||||||
info "Pushing $BRANCH to origin…"
|
info "Pushing $BRANCH to origin…"
|
||||||
git push origin "$BRANCH"
|
git push origin "$BRANCH"
|
||||||
|
|
||||||
REMOTE_SHA="$(git rev-parse HEAD)"
|
|
||||||
|
|
||||||
info "Deploying to $REMOTE_HOST:$REMOTE_DIR"
|
info "Deploying to $REMOTE_HOST:$REMOTE_DIR"
|
||||||
|
|
||||||
ssh "${SSH_OPTS[@]}" "$REMOTE_HOST" bash -s -- \
|
ssh "${SSH_OPTS[@]}" "$REMOTE_HOST" bash -s -- \
|
||||||
"$REMOTE_DIR" \
|
"$REMOTE_DIR" \
|
||||||
"$BRANCH" \
|
"$BRANCH" \
|
||||||
"$REMOTE_SHA" \
|
"$LOCAL_SHA" \
|
||||||
"$HEALTH_URL" \
|
"$HEALTH_URL" \
|
||||||
"$HEALTH_RETRIES" \
|
"$HEALTH_RETRIES" \
|
||||||
"$HEALTH_INTERVAL" <<'REMOTE_SCRIPT'
|
"$HEALTH_INTERVAL" < "$SCRIPT_DIR/deploy-remote.sh"
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
REMOTE_DIR="$1"
|
|
||||||
BRANCH="$2"
|
|
||||||
EXPECTED_SHA="$3"
|
|
||||||
HEALTH_URL="$4"
|
|
||||||
HEALTH_RETRIES="$5"
|
|
||||||
HEALTH_INTERVAL="$6"
|
|
||||||
|
|
||||||
info() { printf '==> [remote] %s\n' "$*"; }
|
|
||||||
die() { printf 'ERROR: [remote] %s\n' "$*" >&2; exit 1; }
|
|
||||||
|
|
||||||
command -v docker >/dev/null 2>&1 || die "docker not found on remote host"
|
|
||||||
docker compose version >/dev/null 2>&1 || die "docker compose not available on remote host"
|
|
||||||
command -v curl >/dev/null 2>&1 || die "curl not found on remote host"
|
|
||||||
|
|
||||||
[[ -d "$REMOTE_DIR/.git" ]] || die "Directory is not a git repo: $REMOTE_DIR"
|
|
||||||
|
|
||||||
cd "$REMOTE_DIR"
|
|
||||||
|
|
||||||
if [[ -n "$(git status --porcelain)" ]]; then
|
|
||||||
die "Remote working tree is dirty. Resolve local changes on the server first."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Fetching origin…"
|
|
||||||
git fetch origin
|
|
||||||
|
|
||||||
REMOTE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
||||||
if [[ "$REMOTE_BRANCH" != "$BRANCH" ]]; then
|
|
||||||
info "Checking out branch $BRANCH"
|
|
||||||
git checkout "$BRANCH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Fast-forwarding to origin/$BRANCH"
|
|
||||||
git pull --ff-only origin "$BRANCH"
|
|
||||||
|
|
||||||
ACTUAL_SHA="$(git rev-parse HEAD)"
|
|
||||||
if [[ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]]; then
|
|
||||||
die "Remote SHA mismatch after pull (expected $EXPECTED_SHA, got $ACTUAL_SHA)."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Rebuilding and starting containers…"
|
|
||||||
docker compose up -d --build --remove-orphans
|
|
||||||
|
|
||||||
info "Waiting for health check ($HEALTH_URL)…"
|
|
||||||
ok=0
|
|
||||||
for ((i = 1; i <= HEALTH_RETRIES; i++)); do
|
|
||||||
if curl -fsS -o /dev/null "$HEALTH_URL" 2>/dev/null; then
|
|
||||||
ok=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep "$HEALTH_INTERVAL"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$ok" -ne 1 ]]; then
|
|
||||||
die "Health check failed after $((HEALTH_RETRIES * HEALTH_INTERVAL))s."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Health check OK"
|
|
||||||
|
|
||||||
info "Pruning stopped containers…"
|
|
||||||
docker container prune -f >/dev/null
|
|
||||||
|
|
||||||
info "Pruning dangling images…"
|
|
||||||
docker image prune -f >/dev/null
|
|
||||||
|
|
||||||
info "Service status:"
|
|
||||||
docker compose ps
|
|
||||||
REMOTE_SCRIPT
|
|
||||||
|
|
||||||
info "Deployment finished successfully."
|
info "Deployment finished successfully."
|
||||||
|
|||||||
Reference in New Issue
Block a user