mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
fix(zdeploy): stop deleting operator-managed files on deploy (#3)
The project-directory replacement preserved only ./.env, silently destroying every other server-side file (.env.db, staged signing keys, certs) on every deploy — and the vite kind preserved nothing at all. - preserve all .env* files at the project root by default - new deploy.preserve array for additional files/directories - implemented via tar to the home dir before the wipe, extract after the unzip; server-side copies win over zip contents (same semantics ./.env always had) - helpers deliberately avoid embedded quotes and $( ): PowerShell 5.1 strips embedded double quotes when passing args to ssh.exe, which silently corrupts remote commands (discovered when v1 of this fix failed exactly that way) Fixes #2
This commit is contained in:
parent
2cf83a74ab
commit
d046768c67
11
CHANGELOG.md
11
CHANGELOG.md
@ -10,6 +10,17 @@ Notable changes to the Evomedia.net Token Savers.
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **`zdeploy` no longer deletes operator-managed files on deploy** (#2) —
|
||||||
|
the project-directory replacement preserved only `./.env`, silently
|
||||||
|
destroying every other server-side file (`.env.db`, staged signing
|
||||||
|
keys, certs) on every deploy. All `.env*` files at the project root are
|
||||||
|
now preserved by default, plus anything listed in the new
|
||||||
|
`deploy.preserve` array (files or directories); the vite kind, which
|
||||||
|
previously preserved nothing, gets the same protection. Found the hard
|
||||||
|
way: a first production deploy of an auth service wiped its staged DB
|
||||||
|
credentials and RSA signing keys.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **`zdeploy` server-side health verification (`verify` block)** — projects
|
- **`zdeploy` server-side health verification (`verify` block)** — projects
|
||||||
not published through the edge proxy can declare
|
not published through the edge proxy can declare
|
||||||
|
|||||||
@ -175,7 +175,7 @@ zdeploy all [-Note "message"]
|
|||||||
|
|
||||||
The core workflow, per project kind (projects with `deploy.gitPull` first `git pull --ff-only` so a merged PR isn't left behind):
|
The core workflow, per project kind (projects with `deploy.gitPull` first `git pull --ff-only` so a merged PR isn't left behind):
|
||||||
|
|
||||||
- **python / vite / nextjs** — zip the local source (excluding `.git`, `node_modules`, envs, archives, junk, plus anything in `deploy.exclude`), free disk space on the server (docker prune; aborts if under 1.5 GB free), `scp` the zip up, unzip into `remote.path` preserving the server-side `.env`, `docker compose build` + `up -d`, then **verify the live site reports the new build version** (see [Enabling deploy verification](#enabling-deploy-verification)). nextjs additionally waits for Postgres (`db` block) and applies migrations (`migrations` field). Zips are always deleted locally afterward.
|
- **python / vite / nextjs** — zip the local source (excluding `.git`, `node_modules`, envs, archives, junk, plus anything in `deploy.exclude`), free disk space on the server (docker prune; aborts if under 1.5 GB free), `scp` the zip up, unzip into `remote.path` preserving all server-side `.env*` files plus anything listed in `deploy.preserve` (staged keys, certs, seed data — files or directories), `docker compose build` + `up -d`, then **verify the live site reports the new build version** (see [Enabling deploy verification](#enabling-deploy-verification)). nextjs additionally waits for Postgres (`db` block) and applies migrations (`migrations` field). Zips are always deleted locally afterward.
|
||||||
- **edge** — uploads *every top-level file* in the edge folder (nginx.conf, compose, css, htpasswd, …), validates the new config with `nginx -t` before switching over, then recreates the proxy.
|
- **edge** — uploads *every top-level file* in the edge folder (nginx.conf, compose, css, htpasswd, …), validates the new config with `nginx -t` before switching over, then recreates the proxy.
|
||||||
- **docker** — uploads the compose folder's files, `docker compose pull` + `up -d`. For stacks that run stock images (analytics, mail, etc.).
|
- **docker** — uploads the compose folder's files, `docker compose pull` + `up -d`. For stacks that run stock images (analytics, mail, etc.).
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,13 @@
|
|||||||
"path": "/health",
|
"path": "/health",
|
||||||
"expect": "\"status\":\"ok\""
|
"expect": "\"status\":\"ok\""
|
||||||
},
|
},
|
||||||
"deploy": { "zipName": "PyAppDeploy.zip", "gitPull": true, "exclude": ["docs"] }
|
"deploy": {
|
||||||
|
"zipName": "PyAppDeploy.zip",
|
||||||
|
"gitPull": true,
|
||||||
|
"exclude": ["docs"],
|
||||||
|
"_comment": "preserve: server-side files/dirs in the project dir that deploys must never delete (.env* files are always preserved)",
|
||||||
|
"preserve": ["keys", "seed-data"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"viteapp": {
|
"viteapp": {
|
||||||
|
|||||||
41
zdeploy.ps1
41
zdeploy.ps1
@ -16,8 +16,9 @@
|
|||||||
# zdeploy all -Note "weekly release"
|
# zdeploy all -Note "weekly release"
|
||||||
#
|
#
|
||||||
# Flow (python/vite/nextjs): zip source -> free server disk space -> scp up ->
|
# Flow (python/vite/nextjs): zip source -> free server disk space -> scp up ->
|
||||||
# unzip into remote.path (preserving server-side .env) -> docker compose build + up
|
# unzip into remote.path (preserving server-side .env* files and anything in
|
||||||
# -> verify the live site reports the new build version. Zips are always deleted.
|
# deploy.preserve) -> docker compose build + up -> verify the live site reports
|
||||||
|
# the new build version. Zips are always deleted.
|
||||||
#
|
#
|
||||||
# Compose service-name conventions (override with remote.appService):
|
# Compose service-name conventions (override with remote.appService):
|
||||||
# python kind: app service "app", db service "db"
|
# python kind: app service "app", db service "db"
|
||||||
@ -141,6 +142,32 @@ function Invoke-RemoteUnzip {
|
|||||||
Invoke-Ec2Step "unzip $ZipName" $bash
|
Invoke-Ec2Step "unzip $ZipName" $bash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Operator-file preservation (issue #2) ────────────────────────────────────
|
||||||
|
# Deploys replace the project directory wholesale, which used to destroy every
|
||||||
|
# operator-managed file except ./.env. These helpers preserve all .env* files
|
||||||
|
# at the project root PLUS any paths listed in deploy.preserve (files or
|
||||||
|
# directories), by tarring them to the home dir before the wipe and extracting
|
||||||
|
# them back after the unzip. Server-side copies win over anything shipped in
|
||||||
|
# the zip — the same semantics ./.env always had.
|
||||||
|
function Save-OperatorFiles {
|
||||||
|
param([string]$Key, $Proj, [string]$RemotePath)
|
||||||
|
$paths = @('.env*')
|
||||||
|
if ($Proj.deploy -and $Proj.deploy.preserve) { $paths += @($Proj.deploy.preserve) }
|
||||||
|
$spec = $paths -join ' '
|
||||||
|
$tarball = "$RemoteHome/preserve_${Key}.tgz"
|
||||||
|
# NOTE: no embedded quotes or $( ) here - PowerShell 5.1 strips embedded
|
||||||
|
# double quotes when passing args to ssh.exe, silently corrupting the
|
||||||
|
# remote command. Globs expand remotely; tar archives whatever exists
|
||||||
|
# and its nonzero exit for missing paths is deliberately swallowed.
|
||||||
|
Invoke-Ec2Step "preserve operator files ($spec)" "rm -f $tarball; cd $RemotePath && tar -czf $tarball $spec 2>/dev/null; true"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Restore-OperatorFiles {
|
||||||
|
param([string]$Key, [string]$RemotePath)
|
||||||
|
$tarball = "$RemoteHome/preserve_${Key}.tgz"
|
||||||
|
Invoke-Ec2Step "restore operator files" "test -f $tarball && tar -xzf $tarball -C $RemotePath; rm -f $tarball; true"
|
||||||
|
}
|
||||||
|
|
||||||
# ── Deploy verification (build-version match, not just HTTP 200 — a 200 can be
|
# ── Deploy verification (build-version match, not just HTTP 200 — a 200 can be
|
||||||
# a stale cached build; the version match proves the new build is live) ─────
|
# a stale cached build; the version match proves the new build is live) ─────
|
||||||
|
|
||||||
@ -284,10 +311,10 @@ function Invoke-PythonDeploy {
|
|||||||
Invoke-Ec2Step "apt-get install unzip" "sudo apt-get update -qq && sudo apt-get install -y unzip"
|
Invoke-Ec2Step "apt-get install unzip" "sudo apt-get update -qq && sudo apt-get install -y unzip"
|
||||||
Invoke-Ec2Step "ensure stack root" "sudo mkdir -p $STACK_ROOT && sudo chown ${Ec2User}:${Ec2User} $STACK_ROOT"
|
Invoke-Ec2Step "ensure stack root" "sudo mkdir -p $STACK_ROOT && sudo chown ${Ec2User}:${Ec2User} $STACK_ROOT"
|
||||||
Invoke-Ec2Step "ensure shared web network" "sudo docker network create web 2>/dev/null || true"
|
Invoke-Ec2Step "ensure shared web network" "sudo docker network create web 2>/dev/null || true"
|
||||||
Invoke-Ec2Step "backup .env if present" "if [ -f $remotePath/.env ]; then cp $remotePath/.env $RemoteHome/.env.${Key}_bak; fi"
|
Save-OperatorFiles -Key $Key -Proj $Proj -RemotePath $remotePath
|
||||||
Invoke-Ec2Step "replace project directory" "sudo rm -rf $remotePath && sudo mkdir -p $remotePath && sudo chown ${Ec2User}:${Ec2User} $remotePath"
|
Invoke-Ec2Step "replace project directory" "sudo rm -rf $remotePath && sudo mkdir -p $remotePath && sudo chown ${Ec2User}:${Ec2User} $remotePath"
|
||||||
Invoke-RemoteUnzip -ZipName $zipName -DestPath $remotePath
|
Invoke-RemoteUnzip -ZipName $zipName -DestPath $remotePath
|
||||||
Invoke-Ec2Step "restore .env from backup" "if [ -f $RemoteHome/.env.${Key}_bak ]; then cp $RemoteHome/.env.${Key}_bak $remotePath/.env; fi"
|
Restore-OperatorFiles -Key $Key -RemotePath $remotePath
|
||||||
Invoke-Ec2Step "require compose directory" "test -d $composeDir"
|
Invoke-Ec2Step "require compose directory" "test -d $composeDir"
|
||||||
Invoke-Ec2Step "docker compose build $appSvc" "cd $composeDir && sudo COMPOSE_BAKE=false docker compose build $appSvc"
|
Invoke-Ec2Step "docker compose build $appSvc" "cd $composeDir && sudo COMPOSE_BAKE=false docker compose build $appSvc"
|
||||||
Invoke-Ec2Step "docker compose up -d" "cd $composeDir && sudo COMPOSE_BAKE=false docker compose up -d"
|
Invoke-Ec2Step "docker compose up -d" "cd $composeDir && sudo COMPOSE_BAKE=false docker compose up -d"
|
||||||
@ -398,8 +425,10 @@ function Invoke-ViteDeploy {
|
|||||||
Invoke-Ec2Step "apt-get install unzip" "sudo apt-get update -qq && sudo apt-get install -y unzip"
|
Invoke-Ec2Step "apt-get install unzip" "sudo apt-get update -qq && sudo apt-get install -y unzip"
|
||||||
Invoke-Ec2Step "ensure stack root" "sudo mkdir -p $STACK_ROOT && sudo chown ${Ec2User}:${Ec2User} $STACK_ROOT"
|
Invoke-Ec2Step "ensure stack root" "sudo mkdir -p $STACK_ROOT && sudo chown ${Ec2User}:${Ec2User} $STACK_ROOT"
|
||||||
Invoke-Ec2Step "ensure shared web network" "sudo docker network create web 2>/dev/null || true"
|
Invoke-Ec2Step "ensure shared web network" "sudo docker network create web 2>/dev/null || true"
|
||||||
|
Save-OperatorFiles -Key $Key -Proj $Proj -RemotePath $remotePath
|
||||||
Invoke-Ec2Step "replace project directory" "sudo rm -rf $remotePath && sudo mkdir -p $remotePath && sudo chown ${Ec2User}:${Ec2User} $remotePath"
|
Invoke-Ec2Step "replace project directory" "sudo rm -rf $remotePath && sudo mkdir -p $remotePath && sudo chown ${Ec2User}:${Ec2User} $remotePath"
|
||||||
Invoke-RemoteUnzip -ZipName $zipName -DestPath $remotePath
|
Invoke-RemoteUnzip -ZipName $zipName -DestPath $remotePath
|
||||||
|
Restore-OperatorFiles -Key $Key -RemotePath $remotePath
|
||||||
Invoke-Ec2Step "require compose file" "test -f $remotePath/docker-compose.yml"
|
Invoke-Ec2Step "require compose file" "test -f $remotePath/docker-compose.yml"
|
||||||
Invoke-Ec2Step "docker compose build" "cd $remotePath && sudo COMPOSE_BAKE=false docker compose build"
|
Invoke-Ec2Step "docker compose build" "cd $remotePath && sudo COMPOSE_BAKE=false docker compose build"
|
||||||
Invoke-Ec2Step "docker compose up -d" "cd $remotePath && sudo COMPOSE_BAKE=false docker compose up -d"
|
Invoke-Ec2Step "docker compose up -d" "cd $remotePath && sudo COMPOSE_BAKE=false docker compose up -d"
|
||||||
@ -464,10 +493,10 @@ function Invoke-NextDeploy {
|
|||||||
Invoke-Ec2Step "ensure unzip installed" "sudo apt-get update -qq && sudo apt-get install -y unzip"
|
Invoke-Ec2Step "ensure unzip installed" "sudo apt-get update -qq && sudo apt-get install -y unzip"
|
||||||
Invoke-Ec2Step "ensure stack root" "sudo mkdir -p $STACK_ROOT && sudo chown ${Ec2User}:${Ec2User} $STACK_ROOT"
|
Invoke-Ec2Step "ensure stack root" "sudo mkdir -p $STACK_ROOT && sudo chown ${Ec2User}:${Ec2User} $STACK_ROOT"
|
||||||
Invoke-Ec2Step "ensure shared web network" "sudo docker network create web 2>/dev/null || true"
|
Invoke-Ec2Step "ensure shared web network" "sudo docker network create web 2>/dev/null || true"
|
||||||
Invoke-Ec2Step "backup .env if present" "if [ -f $remotePath/.env ]; then cp $remotePath/.env $RemoteHome/.env.${Key}_bak; fi"
|
Save-OperatorFiles -Key $Key -Proj $Proj -RemotePath $remotePath
|
||||||
Invoke-Ec2Step "replace project directory" "sudo rm -rf $remotePath && sudo mkdir -p $remotePath && sudo chown ${Ec2User}:${Ec2User} $remotePath"
|
Invoke-Ec2Step "replace project directory" "sudo rm -rf $remotePath && sudo mkdir -p $remotePath && sudo chown ${Ec2User}:${Ec2User} $remotePath"
|
||||||
Invoke-RemoteUnzip -ZipName $zipName -DestPath $remotePath
|
Invoke-RemoteUnzip -ZipName $zipName -DestPath $remotePath
|
||||||
Invoke-Ec2Step "restore .env from backup" "if [ -f $RemoteHome/.env.${Key}_bak ]; then cp $RemoteHome/.env.${Key}_bak $remotePath/.env; fi"
|
Restore-OperatorFiles -Key $Key -RemotePath $remotePath
|
||||||
|
|
||||||
Write-Host "`n--- [4] Docker compose rebuild ---" -ForegroundColor Cyan
|
Write-Host "`n--- [4] Docker compose rebuild ---" -ForegroundColor Cyan
|
||||||
Invoke-Ec2Step "docker compose down" "cd $remotePath && sudo COMPOSE_BAKE=false docker compose down"
|
Invoke-Ec2Step "docker compose down" "cd $remotePath && sudo COMPOSE_BAKE=false docker compose down"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user