fix(zdeploy): edge deploy ships asset subdirectories, not just top-level files

A project self-hosting assets in folders (fonts/, vendor/) lost them on
every deploy: docker created empty root-owned mount points and nginx
served 404s from them, so fonts fell back silently and vendored JS
never loaded. Every subdirectory except nginx-logs/ and .git/ now ships
recursively, and the ensure-dir chown is recursive so scp into
docker-created root-owned dirs cannot fail.

Closes #42
This commit is contained in:
KellyMichels 2026-08-06 22:56:55 -05:00
parent c8fcfee76c
commit 84764295b7
3 changed files with 26 additions and 3 deletions

View File

@ -11,6 +11,14 @@ Notable changes to the Evomedia.net Token Savers.
## Unreleased
### Fixed
- **`zdeploy` edge kind now ships asset subdirectories** (#42) — the edge
deploy uploaded top-level files only, so a project self-hosting assets
in folders (`fonts/`, `vendor/`) lost them on every deploy: docker
created empty root-owned mount points and nginx served 404s from them,
which shows up as fonts silently falling back and vendored JS never
loading. Every subdirectory except `nginx-logs/` and `.git/` now ships
recursively, and the `ensure edge dir` chown is recursive so scp into
docker-created root-owned dirs cannot fail.
- **`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

View File

@ -8,7 +8,7 @@ c336a61cb8563736bd6550671d7dc90b14702a43f25a97b1ac15ffd64605fbf6 zbackup_ec2.cm
20e6282901a919aa32ea717c739ba2dc9657426dff860f0e5fce201edefd99da zchecksums.cmd
692b7b0ff9e51e5d9b880358b33c54d2db4f967297a5350e154df1ce6f06ebc6 zchecksums.ps1
b56085112e7c573926a70a58872163b71416d58ce91ea7114b1f5de5fc96c982 zdeploy.cmd
c4189cef72368487af4524a00603967d7c8dc03c58ace5d4dd1e264ce1d22bd9 zdeploy.ps1
c847a5b8e679014e1d363c23966fcaf95e443e10cde4f76f197b27a0aa8b9746 zdeploy.ps1
fa817d3bd7bba98b03b411d3c5a82b210d3cca316ac0c9785afa5b4ac5fe0766 zec2.cmd
5f70432af23df3459a98d9ad6a0da723783f2269dd5f5ab8ecdd6366ebc84942 zec2.ps1
4df94eda29b6f5b2cb6bfa63e0add4d90ea24d936305f36a69988e8dd5d69154 zec2_rotatekeys.cmd

View File

@ -620,10 +620,13 @@ function Invoke-EdgeDeploy {
}
Invoke-Ec2Step "ensure shared web network" "sudo docker network create web 2>/dev/null || true"
Invoke-Ec2Step "ensure edge dir" "sudo mkdir -p $remotePath && sudo chown ${Ec2User}:${Ec2User} $remotePath"
# -R: docker creates mount-point subdirs (vendor/, fonts/) root-owned when
# they are missing at compose up; a non-recursive chown leaves those
# unwritable and every scp into them fails.
Invoke-Ec2Step "ensure edge dir" "sudo mkdir -p $remotePath && sudo chown -R ${Ec2User}:${Ec2User} $remotePath"
# Ship every top-level file in the edge folder — nginx.conf, compose, css,
# htpasswd, whatever the proxy serves. Subdirectories (logs, certs) stay put.
# htpasswd, whatever the proxy serves.
$files = @(Get-ChildItem -LiteralPath $root -File | Where-Object { $_.Name -ne 'nul' })
foreach ($f in $files) {
Write-Host " >> uploading $($f.Name)" -ForegroundColor DarkCyan
@ -631,6 +634,18 @@ function Invoke-EdgeDeploy {
if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($f.Name) (exit $LASTEXITCODE)" }
}
# Content subdirectories the proxy serves (fonts/, vendor/, ...) ship too —
# only server-side state stays put. Skipping them is how self-hosted assets
# silently never reach prod: docker creates empty mount-point dirs and nginx
# serves 404s from them, so fonts fall back and vendored JS never loads.
$skipDirs = @('nginx-logs', '.git')
$dirs = @(Get-ChildItem -LiteralPath $root -Directory | Where-Object { $skipDirs -notcontains $_.Name })
foreach ($d in $dirs) {
Write-Host " >> uploading $($d.Name)/ (recursive)" -ForegroundColor DarkCyan
scp -r -i $PEM_KEY $d.FullName "${SSH_TARGET}:$remotePath/"
if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($d.Name) (exit $LASTEXITCODE)" }
}
$certMount = if ($Proj.certsSource) { "-v $($Proj.certsSource):/etc/letsencrypt/:ro " } else { "" }
Invoke-Ec2Step "validate new nginx.conf" "sudo docker run --rm -v $remotePath/nginx.conf:/etc/nginx/nginx.conf:ro ${certMount}nginx:1.27-alpine nginx -t -c /etc/nginx/nginx.conf"