From be70809bf0c72fc12d923342727696d9e57d29cf Mon Sep 17 00:00:00 2001 From: elpatron Date: Fri, 19 Jun 2026 17:29:22 +0200 Subject: [PATCH] Fix Windows deploy by copying remote script via scp instead of piping. Co-authored-by: Cursor --- scripts/deploy-remote.sh | 70 ++++++++++++++++++++ scripts/deploy.ps1 | 136 +++++++++++++++------------------------ scripts/deploy.sh | 77 ++-------------------- 3 files changed, 127 insertions(+), 156 deletions(-) create mode 100644 scripts/deploy-remote.sh diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh new file mode 100644 index 0000000..27bb215 --- /dev/null +++ b/scripts/deploy-remote.sh @@ -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 diff --git a/scripts/deploy.ps1 b/scripts/deploy.ps1 index 51b330c..3b21491 100644 --- a/scripts/deploy.ps1 +++ b/scripts/deploy.ps1 @@ -12,6 +12,46 @@ function Fail([string]$Message) { 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 if (-not $RepoRoot) { Fail "Not inside a git repository." } 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") $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 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 Write-Step "Local branch: $Branch ($LocalSha)" -Write-Step "Pushing $Branch to origin…" +Write-Step "Pushing $Branch to origin..." git push origin $Branch if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Write-Step "Deploying to ${DeployHost}:${DeployDir}" -$RemoteScript = @' -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 -'@ - -# 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 } +$exitCode = Invoke-RemoteDeploy ` + -SshOptions $SshOpts ` + -TargetHost $DeployHost ` + -ScriptPath $RemoteScriptPath ` + -RemoteDir $DeployDir ` + -Branch $Branch ` + -ExpectedSha $LocalSha ` + -HealthUrl $HealthUrl ` + -HealthRetries $HealthRetries ` + -HealthInterval $HealthInterval +if ($exitCode -ne 0) { exit $exitCode } Write-Step "Deployment finished successfully." diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 7cc48c7..95505cf 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -4,6 +4,8 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + REMOTE_HOST="${DEPLOY_HOST:-root@10.0.0.5}" REMOTE_DIR="${DEPLOY_DIR:-/opt/apps/Idle-Fantasy-Save-Viewer}" 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…" git push origin "$BRANCH" -REMOTE_SHA="$(git rev-parse HEAD)" - info "Deploying to $REMOTE_HOST:$REMOTE_DIR" ssh "${SSH_OPTS[@]}" "$REMOTE_HOST" bash -s -- \ "$REMOTE_DIR" \ "$BRANCH" \ - "$REMOTE_SHA" \ + "$LOCAL_SHA" \ "$HEALTH_URL" \ "$HEALTH_RETRIES" \ - "$HEALTH_INTERVAL" <<'REMOTE_SCRIPT' -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 + "$HEALTH_INTERVAL" < "$SCRIPT_DIR/deploy-remote.sh" info "Deployment finished successfully."