Transferring with tar meant pushing the full 16 MB on every deploy, most of it
unchanged source maps, over the link to Accra. rsync sends only what differs —
on a no-op deploy that is now zero bytes.
--delete matters as much as the speed. A staging directory without it quietly
accumulates every asset ever built, which is precisely how the stale chunks kept
reappearing: the existing staging area at /home/sema_gopa/pwa/dist holds 226
files against a build of 47, so copying from it restored everything that had
been pruned. The trailing slash on the source copies contents including
dotfiles, so .htaccess comes along; dist/* would skip it. tar remains as a
fallback where rsync is not installed, and the AppleDouble sweep stays for both.
The final hop still has to be docker cp. rsync is not installed in the
container, the live src directory is owned by root so sema_gopa cannot create or
delete entries in it, and there is no passwordless sudo — docker exec is the
only route that has root where the bind mount lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deploying by hand failed in two different ways. Once the files went to
/home/sema_gopa/pwa/dist, which nothing serves, so a fix looked live for a week
while the site ran an August build. Then pruning stale assets kept being undone:
that directory is not a git checkout, it has simply accumulated every asset ever
copied into it — 226 files against a build of 47 — so deploying from it restores
every stale chunk, including bundles still carrying the unrotated API token.
The script makes the served directory mirror a freshly built dist. It also
carries the things a plain copy keeps getting wrong here:
- The docroot is root-owned, so files go in through `docker cp`; the container
runs as root and /var/www/html is a bind mount to the host.
- macOS tar writes AppleDouble `._*` companions, and `.htaccess` once arrived
as `._.htaccess`. COPYFILE_DISABLE=1 plus a sweep handles that.
- `dist/*` silently skips `.htaccess`, which carries the rewrite rules, so
everything is copied with `.` and the transfer is refused unless the staged
file count matches the build and .htaccess survived.
It backs the docroot up before touching it, prunes assets/ to exactly the build,
and verifies over HTTP afterwards — including that the live service worker
version matches the one just built. Files outside assets/ are reported rather
than deleted, since someone may have put them there deliberately; --prune-all
removes them.
That caution paid for itself on the first run: it found a legacy icons/
directory, eleven files from May, left behind when the icons moved to
app-icons/. Apache had been 404ing them regardless, because Debian's
alias.conf maps /icons/ to /usr/share/apache2/icons/ for directory listings —
which is presumably why the directory was renamed. Removed with --prune-all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>