Commit Graph

35 Commits

Author SHA1 Message Date
b26be3cd2a
chore: write the site name as evomedia.net, lowercase (#92)
* chore: write the site name as evomedia.net, lowercase

The name is a domain and is written as one. Script headers, the README,
CHANGELOG and elevator pitch, their .txt twins, and the site page --
matching the same sweep in the private evo.scripts so the mirror does not
drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: refresh CHECKSUMS.txt for the lowercase sweep

Every script's header changed, so every hash did. The repo's own
Checksums test caught it -- which is what it is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 13:40:03 -05:00
1c7d1d639b
chore(release): v1.0.0.0.27 (#86)
Build stamp catch-up for 2 merged PR(s) since v1.0.0.0.26:
  c317b13 docs(readme): give the download-and-verify path a PowerShell form (#85)
  d4cfa98 feat(site): track the public page this project serves (#84)
2026-09-15 19:59:46 -05:00
05b771e64a
chore(release): v1.0.0.0.26 (#83)
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)
2026-09-14 14:16:58 -05:00
f2e6302d00
fix(zdeploy): build docker stacks that come from a Dockerfile, instead of restarting the old image (#81)
* fix(zdeploy): build docker stacks that come from a Dockerfile

Mirrors the fix in the private scripts repo; the code is identical in both, only
the config differs.

The docker kind ran `docker compose pull` then `docker compose up -d`. That is
right for a stack of published images and wrong for one built from a Dockerfile
in the tree, where there is nothing to pull. `up -d` builds only when the image
is MISSING, so the first deploy works and every one after it uploads the new
code, starts the old image, and reports success.

A project opts into building with deploy.build, which runs
`docker compose build --pull` so the base image is refreshed at the same time.
Stacks that pull are unaffected.

The example config documents the flag on the docker project, next to the
existing note about startApp, because the failure is silent and nobody goes
looking for a setting they do not know exists.

CHECKSUMS.txt regenerated, since two covered scripts changed.

286 tests pass, 1 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(checksums): hash the scripts as git checks them out, not as a tool wrote them

CI failed on the two files this branch touches while the same suite passed here.
The manifest was right about the wrong bytes.

.gitattributes pins *.ps1 to eol=crlf, and its comment says why: it makes these
files byte-identical on every platform, which is what lets CHECKSUMS.txt hold
one hash per file rather than one per OS. The edit that added Get-DockerImageStep
was applied by a script that wrote LF, so the working copy stopped matching the
pin. zchecksums then faithfully recorded the LF hashes, and every checkout that
honours .gitattributes - including CI - disagreed.

Nothing was wrong with the committed content: git normalises on the way in, so
the objects were always correct. Only the local working copy and the manifest
taken from it were off.

Re-materialised both files through git so they carry the endings the attribute
pins, then regenerated the manifest from those.

286 tests pass, 1 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 12:32:20 -05:00
0e7b80b4ae
chore(release): v1.0.0.0.25 (#80)
Some checks failed
tests / test (push) Has been cancelled
Build stamp catch-up for 6 merged PR(s) since v1.0.0.0.24:
  732a448 feat: add zmerge and zpull — fleet-wide PR merging and checkout sync (#79)
  573e010 docs(security): security notes, and a twin for every root .md (#78)
  3b0194a perf(tests): run each child-process invocation once (#77)
  f27f9dc fix(deploy): read the string build stamp, and never compare two unreadable labels (#76)
  16c56dc fix(ci): push trigger names master, this repo's default branch, so the workflow runs after a merge (#75)
  32e8d74 chore(ci): add a GitHub Actions workflow that lints with PSScriptAnalyzer and runs the Pester suite on Windows (#74)
2026-09-12 16:05:20 -05:00
f27f9dc5bc
fix(deploy): read the string build stamp, and never compare two unreadable labels (#76)
Get-LabelFromBuildJsonObj knew two stamp shapes and not the third - the string
form the versioning scheme specifies and zbump writes. It fell through to the
legacy branch, where productVersion is null ("") and buildNumber is null (0),
so every string stamp became "v.0".

Deploy verification runs BOTH sides through this function: the local stamp and
the one read back from the running container. So it did not fail loudly - it
collapsed both to "v.0", compared them equal, and printed PASS:

  local 'v1.0.0.0.0' -> 'v.0'   remote 'v9.9.9.9.9' -> 'v.0'   equal? True

A check that cannot fail is worse than no check, because it is believed. It
would report PASS against a container serving a build from weeks ago, which is
the exact case it exists to catch.

- The string form is read first: a stamp that states its version means it,
  even if it also carries stray numeric fields from a half-migration. A
  missing leading v is tolerated so all three shapes stay comparable.
- The legacy branch returns $null when there is nothing to build a label from,
  so a caller sees "no label" instead of a label matching every other
  unreadable stamp.
- zdeploy refuses to verify an unreadable local label, and never treats a null
  remote label as a match - otherwise $null -eq $null restores the same
  vacuous pass one level up.

CHECKSUMS.txt regenerated, since both covered files changed.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 13:47:25 -05:00
fc52501999
chore(release): v1.0.0.0.24 (#73)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:44:23 -05:00
4ebb596176
chore(mirror): mirror tagOnDeploy and the zstart gitPull fix, and stop publishing current product names (#72)
The mirror carries the tagOnDeploy feature, its tests, and the zstart
gitPull fix from the private tree. Twelve published references to
current product names and internal issue numbers are reworded
generically, the denylist learns the current spellings (a dot or hyphen
broke the word, an underscore hid the boundary), and planted cases prove
the suite now sees them. CHECKSUMS.txt regenerated.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:40:45 -05:00
f1b1fd6264
chore(release): v1.0.0.0.23 (#69)
Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.22:
  135c585 docs(changelog): give every shipped release its own section, and generate the twin (#68)
2026-08-31 18:39:46 -05:00
560a2064a2
chore(release): v1.0.0.0.22 (#67)
Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.21:
  40fa250 refactor(tests): move the sanitization denylist to a data file both sides can read (#66)
2026-08-31 17:56:24 -05:00
af929f73ba
chore(release): v1.0.0.0.21 (#65)
Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.20:
  ac95c47 chore(sync): bring zdeploy/ZHelpers up to the private tree, sanitized (#64)
2026-08-31 15:01:27 -05:00
ac95c47255
chore(sync): bring zdeploy/ZHelpers up to the private tree, sanitized (#64)
The mirror's deploy pair had drifted ~280 lines behind: it lacked the
transactional .env preserve/restore (an interrupted deploy could destroy
server-side env files), the stderr-flattening step wrapper (a successful
deploy reported failure and skipped its own verification), and the
verification rework (channel re-picked every retry, edge only with a Host
to route by, verify.timeoutSeconds, honest split of "stale build" vs "no
channel answered").

The sync is byte-faithful to the private tree except where the mirror's
own Sanitization suite demands otherwise - and it caught the first copy:
three failures for private project names, a private domain, and a
private-only script name that rode along in comments. Each war story keeps
its lesson and loses its cast, per the convention already in the file
("EvoCivilCode: deploy/" was already published as "(deploy/, infra/,
...)"). That suite going red on an unsanitized copy is exactly what it
exists for.

Also in this change:

- tests/VerifyPlan.Tests.ps1 - the channel-selection rules are pure
  functions and Pester pins them (no domain => no edge attempt; the PS 5.1
  one-element-unroll trap). First verification tests in the mirror.
- README: the verify block now documents viaProxy/upstream (they shipped
  in the docker-network read but were never in the README),
  timeoutSeconds, and the channel order with why it re-resolves per retry.
- README.txt: generated plain-text twin, via scripts/readme_txt.py
  (vendored from the fleet's reference implementation; the file is
  generated, never edited by hand).
- CHANGELOG.md/.txt: entries merged into the existing Unreleased sections.

CHECKSUMS.txt refreshed (42 entries). Pester: 240 passed, 0 failed.
Both synced files parse clean.

Observed, untouched: the Unreleased section carries duplicate "### Fixed"
headings from earlier appends; folding them risks reordering entries whose
prose references their neighbours, so it is left for the next release cut
(zbump #110 rolls Unreleased into the version being cut).
2026-08-31 14:44:26 -05:00
471789f530
chore(release): v1.0.0.0.20 (#62)
Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.19:
  e4c563d fix(zversion): help text says one bump per release, not one per PR (#61)
2026-08-30 21:06:11 -05:00
e4c563d1d5
fix(zversion): help text says one bump per release, not one per PR (#61)
zversion's usage block and its bump help line both said 'one per PR, one per
defect fix'. The build counter advances once per release: the number names
something that shipped, so a release carrying five PRs moves it by one, and
PRs that never shipped on their own were never separate builds.

This is help text rather than behaviour, but it is the wording that gets
followed - it is what stamped a single evo.www release as two builds. The
matching comments in ZHelpers.ps1 and zdeploy.ps1 are corrected with it.

CHECKSUMS.txt regenerated for the three edited scripts, since the manifest
tests fail the moment it drifts. CHANGELOG entry added under Unreleased, with
its .txt twin. The older CHANGELOG entry recording what the rule was when
zversion shipped is deliberately left as written - a changelog describes what
happened, not what is currently true.

Pester: 231 passed, 0 failed.
2026-08-30 20:08:26 -05:00
c32fa8537b
chore(release): v1.0.0.0.19 (#60)
Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.18:
  e738b62 feat(version): read a live build from inside the docker network, not the public proxy (#59)
2026-08-30 15:48:25 -05:00
e738b62cdf
feat(version): read a live build from inside the docker network, not the public proxy (#59)
zdeploy, zec2 and zec2online now prefer

    docker exec <viaProxy> curl http://<upstream>/api/build-version

when a project sets verify.viaProxy and verify.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: a project without those two keys behaves
exactly as before.

zec2 gains $PemKey and $SshTarget, which it had no need of until now -- the
read is inside a try/catch, so without them it would throw, be swallowed,
and fall through silently.

CHECKSUMS.txt regenerated (zchecksums -Update), zconfig.example.json
documents both shapes of the verify block, and CHANGELOG.md carries its
plain-text twin.

Pester: 231 passed, 0 failed -- including the sanitization suite.
2026-08-30 15:44:42 -05:00
58c888aa56
chore(release): v1.0.0.0.18 (#58)
Build stamp catch-up for 4 merged PR(s) since v1.0.0.0.14:
  4230566 feat: two blank lines after every z-script run (#57)
  432f210 fix(zdeploy): the pre-zip line states the label verification will require (#56)
  fcbad44 feat(zdeploy): add the static deploy kind, and a test that keeps this repo sanitized (#55)
  7b32bfa fix(zdeploy): docker kind now ships config subdirectories (#54)
2026-08-25 15:51:58 -05:00
4230566e47
feat: two blank lines after every z-script run (#57)
zdeploy ended with two blank lines and nothing else did, so its output
was the only one that did not butt up against the next prompt. Applied
everywhere.

Implemented in Stop-ZTracking rather than per script, because every
tracked script ends by calling it - including the usage and guard paths
that do `Stop-ZTracking; exit 1`. One place therefore covers every exit
of sixteen scripts.

  * Write-ZTrailer emits the two lines. Stop-ZTracking calls it on both
    paths, including the early return when tracking never started - a
    script that printed output still deserves the separation.
  * -FinalNote prints one last line AFTER the tracking footer and BEFORE
    the blanks. zdeploy's "Last deployed at ..." now goes through it, so
    it stays the last thing on screen instead of being followed by the
    token count.
  * The standalone tools (zchecksums, zversion, zrelease) get a local
    three-line copy at each exit rather than dot-sourcing ZHelpers -
    they are deliberately dependency-free so they run from an extracted
    release zip with nothing beside them.
  * Redundant trailing blanks were removed where a script already
    printed one before exiting, so the count is exactly two, not three.

Also fixes a related gap found while testing: the error exits inside
Get-ZConfig and Get-ZProject bypassed Stop-ZTracking entirely, so a run
that died on a missing zconfig.json printed no trailer AND no token
footer. Those three exits now route through Stop-ZTracking.

zkill/zrestart are one-line wrappers around scripts that already trail,
so they are untouched - adding it would double the blanks.

Verified by running each script and counting the trailing blank lines in
its captured output: 2 on every success path, every usage path, and the
config-error path. Suite 231/231. CHECKSUMS.txt refreshed.
2026-08-25 15:37:45 -05:00
KellyMichels
77d7c2f1f5 chore(release): v1.0.0.0.14
Build stamp catch-up for 3 merged PR(s) since v1.0.0.0.11:
  6c30f40 feat(zdeploy): sync deploy hardening from the private toolkit (#53)
  8959d9f fix(zdeploy): stop passing ssh -n to scp, which rejects it (#52)
  d99f8a1 fix(zdeploy): stop mirroring the build stamp into the local checkout (#51)
2026-08-20 11:45:19 -05:00
6c30f400ad
feat(zdeploy): sync deploy hardening from the private toolkit (#53)
Nine fixes that had accumulated only in the private copy. Each one is a
production failure that already happened:

  * ssh -n on the deploy path. Without it ssh reads its stdin, inherits
    the console handle under PowerShell, and can block forever. The
    existing timeouts cannot catch it - they bound a connection that is
    dying, and this one was never established. Get-Ec2ScpOpts derives
    from the same list minus -n, because scp rejects it with a usage
    error that reads like an unrelated failure.

  * Invoke-DeployGitPull now switches TO the default branch instead of
    pulling whatever is checked out. Pulling the current branch breaks
    as soon as the remote deletes branches on merge, and worse, could
    ship a feature branch to production. Refuses over local changes,
    excluding the files the deploy itself stamps.

  * nextjs handler resolves composeDir. It ran compose against
    remote.path, so a project whose compose lives in a subdirectory
    recycled whatever docker-compose.yml sat at the project root -
    usually the local dev one shipped in the same archive. Two deploys
    in a row exited 0 having shipped nothing, and verification passed
    because the untouched old container still answered.

  * BUILD BEFORE DOWN. The old order took the site offline for the whole
    build and left it offline if the build failed - one deploy served
    502 for 19 hours with no container running. The old image now keeps
    serving until the new one is ready.

  * compose build takes the named service, so a compose file that does
    not define it fails loudly instead of succeeding with nothing to do.

  * deploy.verifyHost overrides domain for verification only, for when a
    domain is retired ahead of its replacement.

  * deploy.stampCmd writes the build version from git before zipping,
    then reverts the tree so the stamp cannot block the next deploy.

  * Optional ztokens pseudo-project and passive usage records. Both
    degrade to a printed skip when no sibling ztokens checkout exists.

Sanitized on the way across, per the public-repo rule: the war stories
that make these comments worth reading are kept, the private product
names, domains, internal script names, outage dates and host figures are
not. Also dropped a "See issue #28" pointer that resolves to an
unrelated PR in this repo, and two references to scripts that exist only
in the private toolkit.

CHECKSUMS.txt refreshed alongside, per the manifest rule.

Tests: 222/222 (the 4 failures before the refresh were the checksum
suite correctly flagging both edited files).
2026-08-20 11:44:42 -05:00
8959d9fdb6
fix(zdeploy): stop passing ssh -n to scp, which rejects it (#52)
Mirrors evo.scripts #66, which added -n to Get-Ec2SshOpts so a deploy step
cannot block on inherited stdin, plus the follow-up that keeps that flag away
from scp. OpenSSH's scp has no -n: it exits 1 with 'unknown option -- n' and
prints its usage block, so every upload failed once the shared option array
reached it.

Get-Ec2ScpOpts is derived from Get-Ec2SshOpts with -n filtered out rather than
duplicated, so the connect and keepalive timeouts cannot drift apart between
the two transports. All four scp call sites here use it - three plain uploads
and the recursive directory upload, which the private tree does not have.

The upload failure message asserted 'Likely server disk space' without checking
anything; it now points at scp's own output, where the real diagnosis already
was.

Verified: both files parse clean, Get-Ec2ScpOpts returns the five -o pairs with
no -n, and the added lines carry no real hosts, keys, or paths.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:21:04 -05:00
KellyMichels
91bbaca0e7 chore(release): v1.0.0.0.11
Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.10:
  a1dd642 chore(changelog): drop the product name from the versioning entry; add the plain-text twin (#50)
2026-08-14 12:47:12 -05:00
KellyMichels
bc86162170 chore(release): v1.0.0.0.10
Build stamp catch-up for 1 merged PR(s) since v1.0.0.0.9:
  eedb95b fix(zdeploy): sync via explicit fetch + ff-only merge, not git pull (#49)
2026-08-13 19:53:00 -05:00
eedb95bbf1
fix(zdeploy): sync via explicit fetch + ff-only merge, not git pull (#49)
With deploy.gitPull set, Invoke-DeployGitPull ran `git pull --ff-only`.
Pull merges whatever .git/FETCH_HEAD marks "for merge", and a
concurrent fetch in the same repo - an editor's background auto-fetch
racing the deploy - can leave duplicate for-merge lines. Pull then dies
with "Cannot fast-forward to multiple branches" even when both lines
name the same commit. Reproduced live on 2026-08-13: the deploy failed
at step 0, and by inspection time the same pull succeeded, with the
duplicated FETCH_HEAD entries as the only evidence.

Now the function fetches explicitly and fast-forwards against the
remote-tracking ref:

  git fetch origin
  git merge --ff-only origin/$branch

Identical fast-forward-or-refuse semantics, but nothing shared and
mutable in the path. A failed fetch and a failed merge now also throw
distinct messages, so the operator knows which half broke.

CHECKSUMS.txt refreshed alongside, per the manifest rule.

Tested: PS 5.1 parse clean; full Pester suite 222/222 after the
manifest refresh (the 3 pre-refresh failures were the checksum suite
correctly flagging the edited file). The same fix pattern was
exercised against live and scratch repos for the private twin
(evomedia-net/evo.scripts#58): behind -> fast-forwards, diverged ->
refuses and aborts the deploy.

Closes #48
2026-08-13 19:52:19 -05:00
KellyMichels
a2fafcc6d0 chore(release): v1.0.0.0.9
Reconcile to the version build-version.json already committed. No increment.
2026-08-13 18:36:09 -05:00
KellyMichels
5701dff60a chore(release): v1.0.0.0.8
Build stamp catch-up for 8 merged PR(s) since v1.0.0.0.0:
  6c03138 fix(zdeploy): deploy edge first for any project list, not just 'all' (#46)
  976eb6d chore: point repo URLs at evomedia-net/evo.* (#44)
  0e4d9c3 fix(zdeploy): ship edge asset subdirectories, stop deploys hanging on ssh prompts, keep vendored archives (#43)
  c8fcfee fix(zdeploy): verify Next.js deploys on the server instead of probing the public IP (stops the bogus security-group warning) (#41)
  b84d24b fix(zdeploy): report where a domain-less project deployed to (#40)
  4668404 fix(zdeploy): re-land the two blank lines after the timestamp (#38)
  a868973 fix(zdeploy): verify against the committed build stamp, and read 5-segment versions (#37)
  0ddffc1 feat(zdeploy): print 'Last deployed at' timestamp as the last line of a run (#36)
2026-08-12 16:29:36 -05:00
kellymichels
976eb6d95e
chore: point repo URLs at evomedia-net/evo.* (#44)
All 16 repos moved to the evomedia-net org and were renamed into the evo.*
namespace, so every github.com/kellymichels/<old-name> reference in source
headers, CI badges, security links and docs pointed at a redirect.

Mechanical URL-only rewrite, applied longest-name-first so smartplantehs-docs
could not be clobbered by the smartplantehs rule. Nothing else changes: no
code, no product names, no behaviour. smartplantehs -> evo.ehs here is the
REPO url only; the product rename is separate and still pending.
2026-08-09 11:31:43 -05:00
kellymichels
0e4d9c3cca
fix(zdeploy): ship edge asset subdirectories, stop deploys hanging on ssh prompts, keep vendored archives (#43)
* 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

* fix(zdeploy): stop deploys hanging on ssh prompts, keep vendored archives, make unzip install idempotent

- Get-Ec2SshOpts: every deploy-path ssh/scp now carries BatchMode=yes
  plus connect/keepalive timeouts. Without BatchMode ssh prompts and
  waits forever, and because the deploy pipes stderr through the
  pipeline the prompt never reaches the screen - the run just stops
  under the last step label with no explanation.
- Archive filter exempts files under vendor/: a vendored *.tgz is a
  build input, and dropping it fails a Dockerfile COPY at image build.
- unzip install checks command -v first instead of an apt round-trip
  on every deploy.

Ported from the private script; three tests cover the vendor/ exemption.
2026-08-06 23:29:33 -05:00
kellymichels
a8689732ba
fix(zdeploy): verify against the committed build stamp, and read 5-segment versions (#37)
Deploy verification expected buildNumber + 1, which was only correct while
a prebuild hook self-incremented the stamp during the image build. That hook
is gone (it produced versions matching no commit and left every build with a
dirty tree), so the check waited out its timeout and warned on every deploy
even when the deploy had succeeded.

Both verifier call sites now expect the committed label, and
Get-LabelFromBuildJsonObj understands the 5-segment scheme
v{major}.{rc}.{beta}.{alpha}.{build} while still reading the legacy
{productVersion, buildNumber} stamp for projects that haven't migrated.

Verified: both files parse; label fn returns v0.0.0.1.8 (5-segment) and
v1.0.0.70 (legacy).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 19:22:34 -05:00
kellymichels
91b638ac31
feat: checksums, toolkit versioning (v{major}.{rc}.{beta}.{alpha}.{build}), and downloadable release zips (#35)
* feat(zchecksums): SHA-256 manifest so a download can be verified before it's run

CHECKSUMS.txt lists a SHA-256 for every top-level .ps1 and .cmd - the files a
user actually executes. zchecksums verifies them; zchecksums -Update
regenerates after an intentional edit.

The manifest is sha256sum format, so 'sha256sum -c CHECKSUMS.txt' works on
Linux/macOS/WSL as well as the PowerShell path on Windows. Hashes are identical
on every platform because .gitattributes pins .ps1/.cmd to CRLF everywhere -
that pin is now load-bearing, so it is commented as such.

Beyond changed and missing files it also reports a script that is on disk but
NOT in the manifest, so something added outside a commit still gets noticed.
Exits non-zero on any of the three.

Honest about its limits, in the header and the README: the manifest lives in
the same repo as the code, so it is an integrity check rather than a signature.
It catches a truncated clone, a forgotten local edit, or an unlisted file - not
a compromised repo.

CHECKSUMS.txt is pinned to LF: sha256sum treats a trailing CR as part of the
filename and would report every entry as missing on Linux.

tests/Checksums.Tests.ps1 keeps it from rotting - a stale manifest is worse
than none, since it either cries wolf until people ignore it or quietly stops
covering a new script. The tests assert the format, LF endings, sort order,
full coverage of on-disk scripts, current hashes, and that zchecksums itself
exits 1 on a tampered file (proved by appending a byte and restoring it).

* feat(zversion, zrelease): toolkit versioning + downloadable release zips

Implements the versioning rule (SmartPlant's 5-segment scheme, now the global
standard; currently only sp and zscripts are on it at v1.x):

    v{major}.{rc}.{beta}.{alpha}.{build}

zversion: get / bump / bump-stage / set. A stage bump zeroes every lower
segment including build. 'bump' is one per PR and one per defect fix, not per
file. Any write rewrites three things together, because they are only useful
when they agree: build-version.json (source of truth), a '# Version:' line in
all 42 script headers (a lone copied script still says which release it came
from), and CHECKSUMS.txt (stamping changes every file).

zrelease: packages the current version as releases/zscripts-<version>.zip with
a sibling .sha256, for people who want the toolkit without cloning. One hash
verifies the download; the bundled CHECKSUMS.txt verifies the extracted
contents. Refuses to overwrite an existing version's zip (released = immutable;
bump instead), and refuses to package when zchecksums fails. tests/ excluded
from the zip; releases/ never packages itself.

First release included: releases/zscripts-v1.0.0.0.0.zip (42 scripts + 7
support files) and its .sha256.

.gitattributes: releases/*.sha256 pinned LF (sha256sum treats a trailing CR as
part of the filename), releases/*.zip marked binary.

Verified end-to-end as a downloader would experience it, in WSL: sha256sum -c
on the zip passes, unzip, sha256sum -c CHECKSUMS.txt inside gives 42 OK / 0
FAILED, and the extracted zdeploy.ps1 header and build-version.json both read
v1.0.0.0.0. Double-release guard and -Verify mode exercised. Full Pester suite
219/219 (the checksum tests absorb the new files automatically).
2026-07-28 13:47:25 -05:00
kellymichels
c20e82e209
chore: hold the bash port back from the public repo until it is tested (#34)
The bash port and its bats suite are removed from the tree while they get more
testing. Everything they were referenced from is cleaned up so nothing dangles:

  - README drops the 'Linux / macOS / WSL' pointer to bash/README.md (a dead
    link once the folder is gone) and the aside that ZCONFIG is honoured by
    both ports.
  - CHANGELOG drops the two bash mentions.
  - ZHelpers.ps1's ZCONFIG comment no longer cites zhelpers.sh.
  - .gitattributes drops the now-dead bash/**, tests/bash/** and *.bats rules,
    keeping the PowerShell CRLF rules.

No behaviour change to the PowerShell scripts; the Pester suite is untouched
and still passes 169/169.

Deliberately NOT a history rewrite: the port stays in this repo's history and
in full in the private mirror, so it can be restored with a revert when the
testing is done. Nothing here is secret - it is unfinished, not sensitive.
2026-07-26 13:24:23 -05:00
kellymichels
d4980b3086
test: add Pester suite for ZHelpers pure logic + ZCONFIG seam (phases 1-2) (#27)
Part of #26. The toolkit had no automated tests at all - including for the
functions that decide what goes into a deploy zip, which is where a dev .env
reached production (#23).

Phase 1 - the seam. Get-ZConfig read a hardcoded $PSScriptRoot\zconfig.json,
so nothing config-dependent could be tested without touching the real config.
Adds Get-ZConfigPath honoring $env:ZCONFIG (the bash port has always had this,
so it also closes a parity gap) and Reset-ZConfigCache to drop the memoised
config between fixtures. Deliberately did NOT convert the exit 1 paths to
throw: that changes observed CLI output, and the pure functions don't need it.

Phase 2 - 61 tests over the functions with no side effects: Get-ArchiveExcludes
(common/python/vite/nextjs lists, deploy.exclude merging, dedupe, array shape),
Get-ZConfig / Get-ZConfigPath / Get-ZProjectKeys / Get-ZProject (dash tolerance,
underscore-key filtering, memoisation), Get-ZEdgeProject, Get-RemoteComposeDir,
Get-Ec2Target / Get-Ec2Home, Get-LabelFromBuildJsonObj, Read-JsonBuildVersion.

The suite is verified by mutation testing rather than assumed useful - six
deliberate regressions were each introduced and confirmed to turn it red,
including reintroducing the exact #23 bug and its inverse (backups silently
dropping .env/uploads, which would produce restore points that cannot restore).

Runs off a fixture config injected via ZCONFIG, so it never reads a real
zconfig.json and passes on a machine that has never been configured.
2026-07-26 12:20:32 -05:00
kellymichels
50b7c81736
fix(ps): zbackup explicit target + robust DATABASE_URL parsing + real pg_dump error reporting (#10)
* fix(ps): zbackup explicit target + robust DATABASE_URL parsing; ssh-stderr deploy fix

Restores parked, previously-uncommitted PowerShell improvements:

- zbackup / zbackup_and_sync require an explicit target: bare invocation
  now prints usage instead of quietly backing up everything; 'all' does
  what bare used to (matching zdeploy). setup_backup_schedule.ps1 passes
  'all' to the scheduled task; both tolerate switch-style args.
- zbackup parses more DATABASE_URL styles: strips surrounding quotes
  (Prisma convention), accepts postgres:// and postgresql+driver://
  schemes, and treats the port as optional (defaults to 5432).
- Invoke-Ec2Step survives ssh stderr warnings: under ErrorActionPreference
  'Stop', PS 5.1 turns any native stderr line (e.g. Docker's COMPOSE_BAKE
  deprecation notice) into a terminating NativeCommandError, aborting a
  deploy that actually succeeded. Drop to Continue locally and flatten
  stderr so only the real exit code decides success.

* fix(ps): zbackup reports the real pg_dump failure, not "No DATABASE_URL"

Mirror of the bash fix. Invoke-LocalPgDump now owns all its messaging
(caller just captures success) and distinguishes the cases:

- no .env / no DATABASE_URL -> calm "No local DATABASE_URL".
- database not reachable (connection refused / could not connect / DNS /
  timeout) -> calm "Local database not running at host:port" - a stopped
  dev DB is a normal state.
- any other failure (version mismatch, auth, missing db) -> the loud,
  full pg_dump error plus the host:port/db it tried, instead of a bare
  "pg_dump failed (exit N)" followed by a misleading "No DATABASE_URL".

Captures pg_dump stderr (was 2>$null); drops ErrorActionPreference to
Continue locally so PS 5.1 doesn't turn that stderr into a terminating
NativeCommandError under the script's 'Stop' setting.
2026-07-25 22:16:48 -05:00
kellymichels
5844fc1614
fix: exclude .env secrets from python/vite deploy zips (not backups) (#23)
Only nextjs-kind excluded .env* from the deploy archive; python and vite
did not - so a project's local .env at its root got zipped and shipped to
the server on every deploy, planting local secrets over the server's own
(the operator-file restore only wins for files it preserved). Add
.env/.env.local/.env.production to the python and vite deploy excludes,
matching nextjs. Kept DEPLOY-only (like `uploads`): backups still capture
.env so a source backup stays complete. Bash + PowerShell.

Note: this covers a ROOT .env. A nested secret (e.g. DocketMail's
backend/.env) is handled separately via deploy.preserve in the project's
zconfig.
2026-07-25 15:22:04 -05:00
KellyMichels
3546a12564 Evomedia.net Token Savers - initial public release
Config-driven PowerShell scripts to run infrastructure tasks (deploy, restart, backup, diagnostics) yourself instead of having an AI agent orchestrate them, to save agent tokens. Environment specifics live in zconfig.json (gitignored).
2026-07-15 21:33:22 -05:00