Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.25:
f2e6302 fix(zdeploy): build docker stacks that come from a Dockerfile, instead of restarting the old image (#81)
19 KiB
Changelog
Notable changes to the Evomedia.net Token Savers.
Unreleased
v1.0.0.0.26 - 2026-09-14
Fixed
zdeployon a docker stack built from source shipped nothing after the first deploy. The docker kind randocker compose pulland thendocker compose up -d, which is right for a stack of published images and wrong for one built from aDockerfilein the tree: there is nothing to pull, andup -dbuilds only when the image is missing. So the first deploy worked and every one after it uploaded the new code, started the old image, and reported success — worse than an error, because the deploy is green and the container is healthy. A project now opts into building with"deploy": { "build": true }, which runsdocker compose build --pullso the base image is refreshed at the same time. Stacks that pull are unaffected.
v1.0.0.0.25 - 2026-09-12
Added
zmerge— merge every pull request across the org that is genuinely ready (MERGEABLE/CLEAN, not a draft), re-checking each one immediately before and after every merge, because merging into a default branch can conflict a sibling PR in the same repository. Dry run by default;-Execute(or-e) merges.zpull—zmerge, thengit pull --ff-onlyin every checkout the merges affected. Skips a checkout that is dirty or is not on its default branch rather than guessing.
Changed
-eis an alias for-Executeon both of the above, the way-salready works for-Scan.zmergediscovers repositories instead of listing them. It asked a hand-kept list, which had fallen well behind the org - so a scan covered about half of it and reported "Nothing open to merge" while a ready pull request sat in a repository the list had never heard of. It now asks GitHub, and throws rather than returning an empty list if that fails: a tool that quietly scans nothing prints the same reassuring line as one that scanned everything, and the two must not be confusable.
v1.0.0.0.24 - 2026-09-08
Added
zdeploycan lay the release tag it already knows the number for. A versioning scheme that asks every release to carry an annotated tag needs something to enforce it, and for a project whose build number lives outside git - in a database, say - nothing did: one project reached thirty-eight builds with four tags, and the missing ones were unrecoverable because the number had never existed anywhere else. Withdeploy.tagOnDeploy, the deployed commit is tagged with its build number and pushed, but only after the live build has been verified - a tag is a claim about what is running. Opt-in, because a project that already tags releases through a pull request must not also collect a tag per deploy. It can never fail a deploy: an existing tag is left alone, a failed push keeps the tag local and prints the command to finish it, and a missing repo just says so.
Fixed
- The mirror stopped publishing current product names. Its own denylist never saw the current spellings (a dot or hyphen breaks the word, an underscore hides the boundary), so twelve references went out while the suite ran green. The patterns learn the spellings, planted cases prove it, and the references read generically now.
zstartno longer aborts on a pull that succeeded. git reports ordinary fetch progress (From https://...) on stderr, and under Windows PowerShell 5.1 the script's2>&1turned that into a terminating error - so a pull that had worked stopped the dev server from starting, before the script's own "Auto-pull skipped" branch could run. The pull now lives inInvoke-StartGitPull, which never throws, never switches branch, and never touches a dirty tree: it fast-forwards when it can, reports when it can't, andzstartcarries on either way - the opposite failure mode fromInvoke-DeployGitPull, on purpose. Fourteen tests drive real git underStopon 5.1, the host the defect lives on (#130).
v1.0.0.0.23 - 2026-08-31
Changed
- Every release since 1.0.0 has its own section again - 21 entries had
piled up under
Unreleasedwhile 22 builds shipped, so this file said nothing had been released since 1.0.0 and a reader at any tag found no section for the version they were holding. Which release carried which entry was derived from git, not guessed: for every line, the commit that introduced it, then the earliest tag containing that commit. No entry text changed - only headings were added and whole entries moved under the release that carried them. scripts/readme_txt.pyis nowscripts/plaintext_twins.pyand covers every markdown file that owes a twin,CHANGELOG.txtincluded. It was kept by hand, so it drifted the moment this file was reorganised. The renderer also drops<!-- -->markers, which are invisible in markdown and read as stray punctuation in a text file.
Fixed
- The
--checkthat keeps the twins honest is actually run now -readme_txt.pyshipped one and its docstring claimed "the test suite runs --check", but nothing invoked it, so a twin could disagree with its markdown indefinitely.tests/PlainTextTwins.Tests.ps1runs it, and reports inconclusive rather than passing when python is unavailable.
v1.0.0.0.22 - 2026-08-31
Changed
- The sanitization denylist moved to
tests/sanitization-patterns.psd1, so the test suite here and the publisher in the private toolkit read one list instead of keeping two. They had two, and they disagreed: the publisher's scan looked only for secrets, while these rules are about identity — internal project names, product domains, private-only script names, operator paths. It therefore reported "clean" on files this suite rejects. No rule changed; only where they live.
v1.0.0.0.21 - 2026-08-31
Added
-
tests/VerifyPlan.Tests.ps1— the verification channel-selection rules are pure functions inZHelpers.ps1(Get-VerifyAttempts,Get-VerifyTimeout) and Pester pins them, including "a project with no domain must never produce an edge attempt" and the PowerShell 5.1 one-element-unroll trap. -
scripts/readme_txt.pyandREADME.txt— a generated plain-text twin of the README for terminals and pagers.README.txtis generated, never edited by hand.
Changed
-
zdeployverification picks its channel on every retry, and never asks the bare IP — the check used to choose its channel once, before the wait loop, by probing; the probes raced the app restart the check exists to wait through, so the whole window went to the edge fallback. For a project with nodomainthat fallback had noHostheader, and the proxy can then only answer from its default vhost — a different product: that is how one deploy's check compared another app's build number against its own. Now channels are re-resolved each retry in trust order (docker-networkviaProxy→localhost:<port>→ edge with the project'sHost), the edge is skipped entirely when there is no host to route by, and a project with no trustworthy channel is reported as unverifiable instead of guessed at. The final warning also says which failure happened: a version that never matched (stale/failed build) reads differently from channels that never answered (probably still booting).verify.timeoutSecondsjoins the config so a project that is slow to boot — e.g. one that runs database migrations in its entrypoint — can widen its own window instead of warning on every routine success. -
An interrupted deploy can no longer destroy server-side
.envfiles — the preserve/restore of operator files is transactional: the restore comes from a tarball taken before the tree is replaced, so a deploy that dies mid-flight leaves the previous files in place instead of an empty directory. -
A successful deploy no longer reports failure —
docker compose restartwrites routine progress to stderr, which PowerShell 5.1 turns into a terminating error under$ErrorActionPreference = 'Stop'; four ssh calls bypassed the wrapper that flattens this. All remote steps now run through it and are judged by exit code alone.
v1.0.0.0.20 - 2026-08-30
Changed
zversion bumpis once per release, not once per PR — the usage text and thebumphelp line both said "one per PR, one per defect fix". The build number names something that shipped, so a release carrying five PRs moves it by one; PRs that never shipped on their own were never separate builds. Help text only here, but it is the wording people follow: it stamped a single evo.www release as two builds. The historical entry below, which records what the rule was whenzversionshipped, is deliberately left as written.
v1.0.0.0.19 - 2026-08-30
Added
-
Read a live build from inside the docker network, not through the public proxy —
zdeploy,zec2andzec2onlinenow preferdocker exec <viaProxy> curl http://<upstream>/api/build-versionwhen a project setsverify.viaProxyandverify.upstream.Two problems it closes. A build stamp is something many sites deliberately do not serve publicly, and a checker that reads it over the public URL stops working the moment that endpoint is blocked — reporting "unknown", which is indistinguishable from "could not reach it". And the proxy answers from whichever vhost matches the Host header, so a container with no public route was getting another site's version back and failing deploys that had worked.
Reading it from a container on the shared network also exercises the real HTTP path, so it proves the app is serving rather than that its database knows a version. Purely additive: projects without those two keys behave exactly as before.
v1.0.0.0.14 - 2026-08-20
Fixed
scpno longer receives an ssh-only flag — the stdin-hang fix added-ntoGet-Ec2SshOpts, and the deploy path splats that same array intoscpas well asssh. OpenSSH'sscphas no-n: it exits 1 withunknown option -- nand prints its usage block, so every upload failed.Get-Ec2ScpOptsnow supplies the shared connection options with-nfiltered out, derived fromGet-Ec2SshOptsrather than duplicated so the timeouts cannot drift apart between the two transports. All fourscpcall sites use it, including the recursive directory upload. The upload failure message also asserted "Likely server disk space" without checking; it now points atscp's own output, where the real diagnosis already was.
v1.0.0.0.8 - 2026-08-12
Fixed
-
Deploys can no longer hang forever on an ssh prompt — every deploy-path
ssh/scpnow carriesBatchMode=yesplus connect and keepalive timeouts (Get-Ec2SshOptsinZHelpers.ps1). WithoutBatchMode, ssh prompts for a passphrase or password and waits indefinitely; because the deploy pipes stderr through the pipeline, the prompt never reaches the screen and the run just stops under whatever step label printed last, with no explanation. Now it fails immediately — there is no prompt on this path worth answering.ServerAlive*bounds a session that dies mid-command (dropped VPN, sleeping laptop, rebooting host) to about a minute instead of hanging. -
Vendored archives survive the archive filter — files under a
vendor/directory are exempt from the "no archives in the zip" rule. A project that vendors a dependency asvendor/*.tgzneeds it in the deploy zip; dropping it makes a Dockerfile'sCOPY vendor ./vendorfail at image build, a confusing way to learn the filter ate a build input. -
unzipinstall is idempotent — the remote step ranapt-get update && apt-get install -y unzipon every deploy; it now checkscommand -v unzipfirst and skips the apt round-trip when the binary is already there. -
zdeployedge 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 exceptnginx-logs/and.git/now ships recursively, and theensure edge dirchown is recursive so scp into docker-created root-owned dirs cannot fail.
v1.0.0.0.0 - 2026-07-28
Added
-
Versioned releases:
zversion,zrelease,releases/— the toolkit now carries one version in a 5-segment scheme,v{major}.{rc}.{beta}.{alpha}.{build}.zversion bump(one per PR / defect fix) andzversion bump-stage release|rc|beta|alpha(zeroes every lower segment) rewritebuild-version.json, stamp# Version:into every script header — so a lone copied script still says which release it came from — and regenerateCHECKSUMS.txtin the same step.zreleasepackages the current version asreleases/zscripts-<version>.zipwith a.sha256beside it: one hash verifies the download, the bundledCHECKSUMS.txtverifies the extracted contents, so nobody needs to clone the repo to get a verifiable copy. Released zips are immutable —zreleaserefuses to overwrite one. -
zchecksums+CHECKSUMS.txt— a SHA-256 manifest covering every.ps1and.cmd, so a download can be verified before anything is run.zchecksumschecks them;zchecksums -Updateregenerates after an intentional edit. The manifest issha256sumformat, sosha256sum -c CHECKSUMS.txtworks on Linux/macOS/WSL too, and the hashes match on every platform because.gitattributespins these files to CRLF everywhere. Flags changed files, missing files, and scripts present on disk but absent from the manifest. It's an integrity check, not a signature — the manifest sits in the same repo as the code, so it catches corruption and accidental drift, not a compromised repo. A Pester test fails if the manifest ever goes stale. -
Test suite (Pester) — the toolkit now has automated coverage of its own pure logic:
Get-ArchiveExcludes(including the deploy-vs-backup rule that keeps.env/uploadsout of deploys but in backups), config and project lookups,remote.composeDirfallback, EC2 target composition, and build-label formatting. Run withInvoke-Pester .\tests(Pester 5+). Verified by mutation testing — reintroducing each historical bug turns the suite red. -
ZCONFIGenvironment variable — overrides the path tozconfig.json, so a run can target an alternate config. Also gives the test suite a seam for injecting a fixture. -
zec2_rotatekeys— safely rotate/reset server-side secrets — a new tool for when a secret leaks or a deploy overwrites a production.envwith dev values.-Rotate KEYregenerates a key on the server (openssl rand -hex 32) so the new value never leaves the box;-Set KEYtakes an operator-known value (e.g.DATABASE_URL,ADMIN_EMAIL) from a masked prompt and streams it over SSH stdin — never a command argument, never echoed. Backs the server.envup to a timestamped.bakfirst, updates the key atomically (matches or appends), auto-detectsbackend/.envfromdeploy.preserve, and with-Restartrecreates the container (up -d --force-recreate, so the new values actually load — a plain restart keeps the old environment).-WhatIfpreviews the plan without touching anything. -
zkill all—zkillnow acceptsall, stopping the dev server of every project that has aports.dev(edge/docker stacks with no local dev server are skipped). Brings it in line withzdeploy all/zbackup all; the one-shot "stop everything I've got running locally". -
zdeployserver-side health verification (verifyblock) — projects not published through the edge proxy can declare"verify": { "port": ..., "path": "/health", "expect": "..." }and the deploy is checked from the server itself (curl localhost:<port><path>over SSH) instead of hitting the public IP. Fixes a false PASS where the proxy's default vhost answered for apps that never started; projects with neitherdomainnorverifyare now reported as NOT verified. -
zdeployoptionaldeploy.gitPull—git pull --ff-onlyin the project root before zipping.zdeployzips the working tree and doesn't otherwise pull, so a checkout left behindoriginafter a merged PR would deploy stale code while still bumping the build number — success that changes nothing. A failed pull aborts the deploy instead. -
Per-project
startconfig block —zstarthonors optional pre-start steps fromzconfig.json:"gitPull": truerunsgit pull --ff-onlyin the project root before starting (never boot a stale checkout), and"env": { ... }sets environment variables for the dev-server process. Example added tozconfig.example.json. -
Switch-style argument tolerance — a leading dash on a project key is ignored everywhere (
zdeploy -myapp==zdeploy myapp), for hands that grew up on per-project switches.
Changed
-
zbackup/zbackup_and_syncrequire an explicit target — running them bare now shows usage instead of quietly backing up every project;alldoes what bare invocation used to (matchingzdeploy). The scheduled task created bysetup_backup_schedule.ps1passesall— re-run it if your task was registered before this change. -
zbackupparses moreDATABASE_URLstyles — double/single-quoted values (Prisma convention),postgres://andpostgresql+driver://schemes, and URLs without an explicit port (defaults to 5432) all work; previously these skipped the Postgres dump with "Could not parse DATABASE_URL". -
zkill/ port cleanup kills the whole process tree — listeners on a project's port are now terminated children-first. Auto-reloading servers (uvicorn/watchfiles, nodemon) spawn workers that inherit the listening socket; killing only the parent left orphans serving stale code. -
zbackupfindsDATABASE_URLinbackend\.envtoo — projects with a frontend/backend split get their Postgres dump bundled without needing a root-level.env.
Fixed
zdeployno 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 newdeploy.preservearray (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.
1.0.0
Initial public release: zstart / zkill / zrestart (local dev servers),
zdeploy (zip → upload → compose build → live build-version verification,
with handlers for python / vite / nextjs / edge / docker project kinds),
zec2 / zec2online / zrepair (health checks and recovery), zbackup /
zbackup_ec2 / zsync (local, server-side, and offsite backups), all driven
by a single gitignored zconfig.json.