Skip to content

refactor: ♻️ wire to tomgrv/scripts, restructure into one folder per script - #4

Merged
tomgrv merged 14 commits into
developfrom
claude/scripts-repo-devcontainer-pwvukn
Aug 27, 2026
Merged

refactor: ♻️ wire to tomgrv/scripts, restructure into one folder per script#4
tomgrv merged 14 commits into
developfrom
claude/scripts-repo-devcontainer-pwvukn

Conversation

@tomgrv

@tomgrv tomgrv commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • lib/common.sh's log/ok/warn/die and its ERR trap duplicated ANSI-color logging logic that now has a canonical home in zz_colors/zz_log at tomgrv/scripts — already shared with tomgrv/devcontainer-features' common-utils feature.
  • Restructured every feature from features/<name>/ to a top-level <name>/, alongside two new folders for what used to be lib/common.sh and lib/summary.sh: common/ and summary/ — laid out the same way (package.json + run.sh) so they're zz_use-able too, but excluded from dispatch.sh's feature discovery. This matches tomgrv/scripts' own one-folder-per-script layout, which is what makes zz_use itself usable directly against this repo: zz_use perspikapps/vps/rancher fetches and installs any one feature standalone. See the README section "Running a single feature via zz_use, without this repo at all".
  • Every folder's package.json declares "bin": {"<name>": "run.sh"} (matching tomgrv/scripts' convention — and what zz_use actually keys off, since it has no notion of "bin" at all) and "dependencies" includes "@tomgrv/vps-common" on every feature. One folder is named differently from its own feature for exactly this reason: vps-tailscale/, not tailscale/ — its run.sh calls the real tailscale CLI internally, so the bare name would let a zz_use-installed copy shadow the actual binary it depends on.
  • Renamed the npm scope from @vps/ to @tomgrv/vps- throughout, matching the org scope used elsewhere (tomgrv/scripts, tomgrv/devcontainer-features).
  • Centralized zz_use bootstrap in a root setup.sh, mirroring tomgrv/scripts' own: dispatch.sh now runs it exactly once, up front, instead of every feature's run.sh embedding its own curl ... | sh. Individual run.sh scripts no longer curl anything — they just fail fast with a one-line message pointing at setup.sh if zz_use somehow isn't already on PATH (e.g. run standalone, outside dispatch.sh).
  • New README section, "Replicating this pattern in another repo" — the general recipe (one-folder-per-script, root setup.sh run once by the dispatcher, shared common/, workspace package.json, tests), generic enough to copy into a different codebase.
  • Found and fixed a real bug this exposed under end-to-end testing: common/run.sh's VPS_SETUP_ROOT was computed from its own install location (BASH_SOURCE[0], e.g. /usr/local/bin/common) rather than the calling script's location — since zz_use perspikapps/vps/common always re-installs rather than reusing a checkout's sibling common/run.sh, this broke feature_package_json/net_port/all_network_ports (used by security/summary) even inside a normal dispatch.sh run from a full checkout. Fixed to use BASH_SOURCE[1] (the caller's location), matching the pattern net_port/net_access already used.
  • common/package.json's own two dependencies (zz_colors, zz_log) live in the separate tomgrv/scripts repo — documented under a non-npm "zzUse" field instead of "dependencies", since listing them there would break npm install.
  • dispatch.sh's feature discovery, checkout-detection, and usage text updated for the new top-level layout; feature_deps() excludes common/summary from auto-enable/--down-<step> refusal since neither is an installable step despite now being a real dependency.
  • package.json (workspaces) and package-lock.json (regenerated) updated for the new folder set and scope.
  • Added tests/ (bats) — this repo had no test suite before: 17 tests covering script syntax, the zz_use/common/setup.sh wiring (including that no run.sh curls setup.sh itself), package.json shape (bin, dependencies, the tailscale collision guard), and common/run.sh's pure logic against a fixture tree. The features themselves (apt/Helm/k3s installs) need a live root Ubuntu box to actually test, so that part has no automated coverage.
  • Found and fixed a real bug in zz_use itself along the way (tomgrv/scripts#2, companion PR): its cache-warm/download-validity checks hardcoded a zz_colors/run.sh sentinel, which incorrectly rejected every origin repo besides tomgrv/scripts itself.

Test plan

  • sh -n dispatch.sh setup.sh, bash -n */run.sh — all pass
  • shellcheck on every script — no new warnings (2 pre-existing, unrelated)
  • bats tests/ — 17/17 pass
  • npm install resolves the full workspace graph cleanly with the new @tomgrv/vps-* scope; node_modules/.bin/ shows the expected symlinks, including vps-tailscale (not tailscale)
  • Full end-to-end, against this real pushed branch, from a clean bin dir: sudo sh dispatch.sh --only-systemzz_use bootstraps exactly once (via setup.sh, not per-feature), system installs, and summary/run.sh runs to completion at the end printing the full connection-info block (this is what caught and proved the VPS_SETUP_ROOT/BASH_SOURCE[1] fix); also verified a feature run.sh invoked directly without zz_use on PATH fails fast with no network call, and the genuine-failure path (a nonexistent zz_use tool) still correctly errors

🤖 Generated with Claude Code

https://claude.ai/code/session_01XADVbmyNJaD8Qm6Gm6f5RF

lib/common.sh's log/ok/warn/die and its ERR trap duplicated ANSI-color
logging logic that now has a canonical home in zz_colors/zz_log at
https://github.com/tomgrv/scripts (already shared with
tomgrv/devcontainer-features' common-utils feature). Bootstrap zz_use
from there on first source and delegate to it instead, keeping every
existing call site (log/ok/warn/die) and die's exit-1 behavior unchanged.

Everything else in lib/common.sh (apt helpers, net_port/net_access,
dispatch_action, Helm/k3s helpers) stays local: it's vps-specific domain
logic, not duplicated anywhere. dispatch.sh's own standalone log/ok/warn/die
are intentionally left untouched too - it must stay a zero-dependency
POSIX sh entrypoint (curl | sudo sh) that works before the repo is even
cloned, so it can't assume network access to a second external repo.
@tomgrv
tomgrv force-pushed the claude/scripts-repo-devcontainer-pwvukn branch from 923dea9 to 6701e24 Compare August 27, 2026 06:55
@tomgrv tomgrv changed the title refactor(common): wire logging/colors to tomgrv/scripts refactor: wire logging/colors to tomgrv/scripts Aug 27, 2026

tomgrv commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

validate-pr-secret is failing org-wide, not because of this PR's diff: gitleaks-action reports [perspikapps] is an organization. License key is required. — the org has no GITLEAKS_LICENSE secret configured. I confirmed this predates this PR (PR #3, opened before this one, has no validate-pr-secret check run in its history at all — the workflow requiring the license is new). I don't have access to add org secrets, so this isn't something I can fix from here; an org admin needs to add GITLEAKS_LICENSE (see https://github.com/gitleaks/gitleaks-action#-announcement).

(Also fixed the validate-pr-title failure — the PR title's scope wasn't in this repo's allowed commitlint scope list, since this change isn't scoped to a single features/* package; dropped the scope to match this repo's own precedent for cross-cutting changes.)


Generated by Claude Code

@tomgrv
tomgrv marked this pull request as ready for review August 27, 2026 06:57
@tomgrv
tomgrv marked this pull request as draft August 27, 2026 06:57
@github-actions github-actions Bot changed the title refactor: wire logging/colors to tomgrv/scripts refactor: ♻️ wire logging/colors to tomgrv/scripts Aug 27, 2026
Every feature (system, security, tailscale, cockpit, k3s, rancher,
dockermanager, argocd, epinio) moves from features/<name>/ to a
top-level <name>/, alongside two new folders for what used to be
lib/common.sh and lib/summary.sh: common/ and summary/ - both excluded
from dispatch.sh's feature discovery, but laid out the same way
(package.json + run.sh) so they're zz_use-able too.

This matches https://github.com/tomgrv/scripts' own one-folder-per-script
layout, which is what makes zz_use itself usable directly against this
repo: `zz_use perspikapps/vps/rancher` now fetches and installs any one
feature standalone, without cloning this repo or running dispatch.sh -
zz_use's [org/repo/]<tool>[@ref] syntax resolves perspikapps/vps as the
origin. Every feature's run.sh now bootstraps zz_use (if not already on
PATH) and does `zz_use perspikapps/vps/common; . common` instead of a
relative `source ../../lib/common.sh`, so it works the same way whether
invoked by dispatch.sh, standalone from a checkout, or zz_use-installed
from anywhere.

dispatch.sh, package.json (workspaces), and README.md updated throughout
for the new paths; package-lock.json regenerated. Added tests/ (bats):
syntax checks on every run.sh/dispatch.sh, a check that every feature's
run.sh has the new zz_use/common wiring, and coverage of common/run.sh's
pure logic (net_port/net_access/all_network_ports/feature_package_json/
dispatch_action) against a fixture tree - this repo had no test suite
before.
@tomgrv tomgrv changed the title refactor: ♻️ wire logging/colors to tomgrv/scripts refactor: wire to tomgrv/scripts, restructure into one folder per script Aug 27, 2026
Every folder's package.json now has "bin": {"<name>": "run.sh"}, matching
the convention tomgrv/scripts uses for its own scripts and what
zz_use perspikapps/vps/<name> relies on - except tailscale/, which uses
"vps-tailscale" instead: tailscale/run.sh calls the real tailscale CLI
internally, so the bare name would let a zz_use-installed copy shadow the
actual binary it depends on.

Every feature's "dependencies" now also declares "@vps/common": "*" (a
real workspace dependency - every feature sources common/run.sh), on top
of any existing @vps/<feature> deps. Since common/ (and summary/) aren't
installable steps, dispatch.sh's feature_deps() now excludes them from
the dependencies it reads for auto-enable/--down-<step> refusal, so they
never get treated as toggleable steps despite being real dependencies.

common/package.json's own two dependencies (zz_colors, zz_log) live in
the separate tomgrv/scripts repo, not as workspace members here - listing
them as plain "dependencies" entries would make `npm install` try (and
fail) to resolve them from the registry. Documented instead under a new,
npm-ignored "zzUse" field.

Extended tests/test-syntax.bats to cover all of the above; README.md's
"One folder per feature" section and common/README.md updated to match.
@github-actions github-actions Bot changed the title refactor: wire to tomgrv/scripts, restructure into one folder per script refactor: ♻️ wire to tomgrv/scripts, restructure into one folder per script Aug 27, 2026
claude added 9 commits August 27, 2026 08:38
The previous commit's "bin": {"vps-tailscale": "run.sh"} in
tailscale/package.json didn't actually prevent the collision it was meant
to: zz_use has no awareness of package.json's "bin" field at all - it
always installs <name>/run.sh under the literal folder name <name> it
was asked for. zz_use perspikapps/vps/tailscale would still have
installed this feature's own script as "tailscale", shadowing the real
Tailscale CLI it calls internally.

Renamed the folder itself instead - the only thing zz_use actually keys
off. package.json's own "name" field stays "@vps/tailscale" (that's what
dispatch.sh reads for feature identity/CLI flags - --only-tailscale etc.
are unchanged), so only the folder differs from every other feature's own
name. Verified end-to-end against the real pushed branch: zz_use
perspikapps/vps/vps-tailscale now installs cleanly under that name, with
no "tailscale" binary ever created by it.

Simplified tests/test-syntax.bats accordingly (folder/bin names are
uniform again - no more special-casing) and added a guard test that no
package.json's bin ever uses the bare "tailscale" name.
1/ Every package.json's "name" and "dependencies" keys move from
@vps/<name> to @tomgrv/vps-<name>, matching the org scope used
elsewhere (tomgrv/scripts, tomgrv/devcontainer-features). Updated
dispatch.sh's feature_name()/pkg_deps() and common/run.sh's
feature_package_json() to match the new prefix; package-lock.json
regenerated; README and test fixtures updated throughout.

2/ Added a root setup.sh (mirroring tomgrv/scripts' own): a thin,
zero-prerequisite wrapper that installs zz_use by delegating to
tomgrv/scripts' setup.sh - the same "don't reimplement what you can
delegate to" reasoning that script itself uses for zz_use's bin-dir
logic. Every run.sh's bootstrap shrinks from a 3-line block embedding
the tomgrv/scripts URL directly to one line delegating to this repo's
own setup.sh instead:

    command -v zz_use >/dev/null 2>&1 || curl -fsSL "${VPS_SETUP_URL:-...}" | sh

so the tomgrv/scripts URL/env-var default now lives in exactly one
place. Documented the whole pattern in a new README section,
"Replicating this pattern in another repo", generic enough to copy into
a different codebase (this repo, tomgrv/devcontainer-features'
common-utils, and tomgrv/scripts itself already share this shape).

Extended tests/ to cover both: the new @tomgrv/vps-* dependency
declarations, and that every run.sh uses the centralized setup.sh
bootstrap rather than a duplicated tomgrv/scripts URL.
…patch.sh

Every feature's run.sh previously embedded its own
"command -v zz_use || curl ... setup.sh | sh" - one curl call per script.
Removed: dispatch.sh now runs setup.sh exactly once, up front (right
after the root check, before any feature runs), so zz_use is on PATH for
every feature's subprocess. Individual run.sh scripts no longer curl
anything themselves - they just fail fast with a one-line message
pointing at setup.sh if zz_use somehow still isn't there (e.g. run
standalone, outside dispatch.sh).

common/run.sh drops the bootstrap entirely: it's only ever reached by
sourcing from a caller that already passed this same check, so by the
time its own body runs, zz_use is guaranteed present.

Found and fixed a real bug this exposed under end-to-end testing:
common/run.sh's VPS_SETUP_ROOT was computed from its own BASH_SOURCE[0]
(wherever zz_use last installed it, e.g. /usr/local/bin/common) instead
of the calling script's location - since zz_use perspikapps/vps/common
always (re)installs rather than reusing a local checkout's sibling
common/run.sh (explicit org/repo requests never skip as "already
available"), this broke feature_package_json/net_port/all_network_ports
even inside a normal dispatch.sh run from a full checkout. Fixed to use
BASH_SOURCE[1] (the caller's location), matching the pattern net_port/
net_access already used.

Updated README.md's "Layout", standalone-run, and "Replicating this
pattern in another repo" sections to match; extended tests/ with two new
checks (no run.sh curls setup.sh itself; dispatch.sh bootstraps zz_use
exactly once).
Removed comments that only restated what the following line already
does, historical/rationale narration (why a decision was made, links to
external docs), and header "Env vars:" blocks duplicating README's env
var tables - keeping just a one-line pointer to README instead. Every
run.sh's header comment shrinks to a single line.

Kept: every runtime log/warn/die message an operator actually sees while
running the script (these are the usability payload, not noise) -
including the detailed failure diagnostics in argocd/epinio's helm
timeout handling and dispatch.sh's TAILSCALE_AUTHKEY guard. Kept the
handful of comments that explain a genuinely non-obvious gotcha
(random_password's SIGPIPE/pipefail interaction, the ERR trap, the
BASH_SOURCE[1] indirection in VPS_SETUP_ROOT) but compressed them to one
line each.

Also fixed a real doc gap this exposed: setup.sh's own trimmed comment
was the only place documenting its ZZ_SCRIPTS_REF/ZZ_SCRIPTS_SETUP_URL
override env vars - moved that one line to README's Layout section
instead of just dropping it.

~230 lines removed across dispatch.sh, setup.sh, common/run.sh, and
every feature's run.sh; behavior unchanged (bats/shellcheck/sh -n all
still pass).
Missed in the previous rationalization pass - the test name already
says what it checks.
Removes the log()/warn()/die() wrapper functions from common/run.sh
(ok() is kept - it's not a thin proxy the same way) and updates every
call site across the feature scripts to call zz_log i/w/e directly,
adding an explicit exit 1 where die() previously did. dispatch.sh's
own independent log/warn/die are untouched since it must stay a
zero-dependency POSIX sh entrypoint that runs before zz_use exists.
…ch.sh

setup.sh now execs package.json's "main" (dispatch.sh) after
installing zz_use, forwarding args through - so `curl setup.sh | sh
-s -- --only-rancher` behaves the same as calling dispatch.sh
directly. dispatch.sh's own zz_use bootstrap now forwards "$@" the
same way when it falls back to setup.sh, and the check moved earlier
(right after the OS check) so a fresh install doesn't re-run the
interactive menu twice. Matches the shared setup.sh pattern now used
across tomgrv/scripts and tomgrv/devcontainer-features.
Avoids colliding with other repos' own SETUP_DIR-shaped env vars by
keeping this script's local var under the zz_ namespace convention.
A curl | sh pipeline's exit status is sh's, not curl's, so a failed
download would go unnoticed under set -e. Download to a temp file
and run that instead - same fix applied to the scripting devcontainer
feature's copy of this template (tomgrv/devcontainer-features#139).
@tomgrv
tomgrv marked this pull request as ready for review August 27, 2026 17:07
@tomgrv
tomgrv requested a lite review from Copilot August 27, 2026 17:07

tomgrv commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

validate-pr-secret is failing, but not because of anything in this PR's diff:

[perspikapps] is an organization. License key is required.
🛑 missing gitleaks license. Go grab one at gitleaks.io and store it as a GitHub Secret named GITLEAKS_LICENSE.

This workflow only triggers on opened/ready_for_review (.github/workflows/validate-pr-secret.yml), so it's never run on this PR before now - it's not something my last push broke. tomgrv/actions/check-secret's gitleaks step requires a GITLEAKS_LICENSE org secret for organization accounts, which isn't set on perspikapps. There's no code fix on my side for this - it needs an org owner to add a GITLEAKS_LICENSE secret (from gitleaks.io) to the perspikapps org or this repo.


Generated by Claude Code

Same hardening as tomgrv/scripts's copy of this template: reject
absolute paths and ".." segments in "main" before using it to build
a file path, and invoke sh with "--" for defense in depth.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the repo into a tomgrv/scripts-compatible “one folder per script” layout so features can be installed standalone via zz_use, while also centralizing shared logging/helpers in common/ and moving the final reporting into summary/.

Changes:

  • Restructures features from features/<name>/ into top-level <name>/ folders and introduces common/ + summary/ as zz_use-installable helper folders excluded from dispatcher discovery.
  • Switches feature bootstrapping to a single root setup.sh (run once by dispatch.sh) and updates feature scripts to use zz_use .../common; . common.
  • Adds bats tests for syntax and wiring, and updates workspace metadata (package.json / package-lock.json) and README documentation for the new layout and scope rename.

Reviewed changes

Copilot reviewed 38 out of 39 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
dispatch.sh Updates discovery/dependency parsing and runs setup.sh once; calls summary/run.sh at end.
setup.sh Bootstraps zz_use via tomgrv/scripts and optionally execs the repo’s main.
common/run.sh New shared bash helper library (logging/traps/apt/net_port/etc).
common/package.json Adds workspace package for @tomgrv/vps-common and documents zzUse deps.
common/README.md Documents the new shared helper module and zz_use sourcing pattern.
summary/run.sh Migrates old summary logic to a top-level script sourcing common.
summary/package.json Adds workspace package for @tomgrv/vps-summary.
summary/README.md Documents re-running the summary output and that it’s excluded from feature discovery.
system/run.sh Migrates system step to top-level script using common + zz_log.
system/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common.
security/run.sh Migrates security step to top-level script using common + zz_log.
security/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common.
k3s/run.sh Migrates k3s step to top-level script using common + zz_log.
k3s/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common.
cockpit/run.sh Migrates cockpit step to top-level script using common + zz_log.
cockpit/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common.
dockermanager/run.sh Migrates dockermanager step to top-level script using common + zz_log.
dockermanager/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common.
argocd/run.sh Migrates argocd step to top-level script using common + zz_log.
argocd/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common (+ k3s).
epinio/run.sh Migrates epinio step to top-level script using common + zz_log.
epinio/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common (+ k3s).
rancher/run.sh Migrates rancher step to top-level script using common + zz_log.
rancher/package.json Renames scope and adds bin + dependency on @tomgrv/vps-common (+ k3s).
vps-tailscale/run.sh Migrates tailscale step to vps-tailscale/ to avoid shadowing the real tailscale CLI.
vps-tailscale/package.json Adds bin: vps-tailscale and dependency on @tomgrv/vps-common.
tests/test-syntax.bats Adds bats tests for script parsing and expected wiring patterns.
tests/test-common.bats Adds bats tests for common/run.sh “pure logic” helpers with fixtures.
README.md Updates docs for new layout, zz_use usage, and setup/dispatch flow.
package.json Adds main: dispatch.sh and enumerates workspaces explicitly (top-level folders).
package-lock.json Regenerates lockfile for new workspaces and renamed scopes.
lib/common.sh Removed (replaced by common/run.sh).
features/system/run.sh Removed (migrated to system/run.sh).
features/security/run.sh Removed (migrated to security/run.sh).
features/tailscale/run.sh Removed (migrated to vps-tailscale/run.sh).
features/tailscale/package.json Removed (migrated to vps-tailscale/package.json).
features/rancher/run.sh Removed (migrated to rancher/run.sh).
features/epinio/run.sh Removed (migrated to epinio/run.sh).
features/dockermanager/run.sh Removed (migrated to dockermanager/run.sh).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread common/run.sh
Comment thread common/run.sh Outdated
Comment thread common/run.sh
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md
- common/run.sh: feature_package_json()/all_network_ports() now only
  scan directories that have a run.sh alongside package.json, so a
  checked-out node_modules/ (thousands of unrelated package.json
  files) is no longer traversed/jq-parsed on every call.
- README: fix a leftover "tailscale/" reference (folder is
  vps-tailscale/), clarify that curl | sh only bootstraps zz_use
  without a local checkout to find package.json/dispatch.sh in, and
  note the jq prerequisite in the standalone zz_use example.
- tests: fixture dirs get a run.sh to match the new filter, plus a
  regression test asserting node_modules/* ports are skipped.
@tomgrv
tomgrv merged commit 288a4f5 into develop Aug 27, 2026
2 checks passed
@tomgrv
tomgrv deleted the claude/scripts-repo-devcontainer-pwvukn branch August 27, 2026 17:17
tomgrv added a commit that referenced this pull request Aug 27, 2026
* refactor: ♻️ wire logging/colors to tomgrv/scripts

lib/common.sh's log/ok/warn/die and its ERR trap duplicated ANSI-color
logging logic that now has a canonical home in zz_colors/zz_log at
https://github.com/tomgrv/scripts (already shared with
tomgrv/devcontainer-features' common-utils feature). Bootstrap zz_use
from there on first source and delegate to it instead, keeping every
existing call site (log/ok/warn/die) and die's exit-1 behavior unchanged.

Everything else in lib/common.sh (apt helpers, net_port/net_access,
dispatch_action, Helm/k3s helpers) stays local: it's vps-specific domain
logic, not duplicated anywhere. dispatch.sh's own standalone log/ok/warn/die
are intentionally left untouched too - it must stay a zero-dependency
POSIX sh entrypoint (curl | sudo sh) that works before the repo is even
cloned, so it can't assume network access to a second external repo.

* refactor: restructure into one top-level folder per script, zz_use-able

Every feature (system, security, tailscale, cockpit, k3s, rancher,
dockermanager, argocd, epinio) moves from features/<name>/ to a
top-level <name>/, alongside two new folders for what used to be
lib/common.sh and lib/summary.sh: common/ and summary/ - both excluded
from dispatch.sh's feature discovery, but laid out the same way
(package.json + run.sh) so they're zz_use-able too.

This matches https://github.com/tomgrv/scripts' own one-folder-per-script
layout, which is what makes zz_use itself usable directly against this
repo: `zz_use perspikapps/vps/rancher` now fetches and installs any one
feature standalone, without cloning this repo or running dispatch.sh -
zz_use's [org/repo/]<tool>[@ref] syntax resolves perspikapps/vps as the
origin. Every feature's run.sh now bootstraps zz_use (if not already on
PATH) and does `zz_use perspikapps/vps/common; . common` instead of a
relative `source ../../lib/common.sh`, so it works the same way whether
invoked by dispatch.sh, standalone from a checkout, or zz_use-installed
from anywhere.

dispatch.sh, package.json (workspaces), and README.md updated throughout
for the new paths; package-lock.json regenerated. Added tests/ (bats):
syntax checks on every run.sh/dispatch.sh, a check that every feature's
run.sh has the new zz_use/common wiring, and coverage of common/run.sh's
pure logic (net_port/net_access/all_network_ports/feature_package_json/
dispatch_action) against a fixture tree - this repo had no test suite
before.

* refactor: declare dependencies and bin entries in every package.json

Every folder's package.json now has "bin": {"<name>": "run.sh"}, matching
the convention tomgrv/scripts uses for its own scripts and what
zz_use perspikapps/vps/<name> relies on - except tailscale/, which uses
"vps-tailscale" instead: tailscale/run.sh calls the real tailscale CLI
internally, so the bare name would let a zz_use-installed copy shadow the
actual binary it depends on.

Every feature's "dependencies" now also declares "@vps/common": "*" (a
real workspace dependency - every feature sources common/run.sh), on top
of any existing @vps/<feature> deps. Since common/ (and summary/) aren't
installable steps, dispatch.sh's feature_deps() now excludes them from
the dependencies it reads for auto-enable/--down-<step> refusal, so they
never get treated as toggleable steps despite being real dependencies.

common/package.json's own two dependencies (zz_colors, zz_log) live in
the separate tomgrv/scripts repo, not as workspace members here - listing
them as plain "dependencies" entries would make `npm install` try (and
fail) to resolve them from the registry. Documented instead under a new,
npm-ignored "zzUse" field.

Extended tests/test-syntax.bats to cover all of the above; README.md's
"One folder per feature" section and common/README.md updated to match.

* fix: rename tailscale/ to vps-tailscale/ - zz_use has no notion of "bin"

The previous commit's "bin": {"vps-tailscale": "run.sh"} in
tailscale/package.json didn't actually prevent the collision it was meant
to: zz_use has no awareness of package.json's "bin" field at all - it
always installs <name>/run.sh under the literal folder name <name> it
was asked for. zz_use perspikapps/vps/tailscale would still have
installed this feature's own script as "tailscale", shadowing the real
Tailscale CLI it calls internally.

Renamed the folder itself instead - the only thing zz_use actually keys
off. package.json's own "name" field stays "@vps/tailscale" (that's what
dispatch.sh reads for feature identity/CLI flags - --only-tailscale etc.
are unchanged), so only the folder differs from every other feature's own
name. Verified end-to-end against the real pushed branch: zz_use
perspikapps/vps/vps-tailscale now installs cleanly under that name, with
no "tailscale" binary ever created by it.

Simplified tests/test-syntax.bats accordingly (folder/bin names are
uniform again - no more special-casing) and added a guard test that no
package.json's bin ever uses the bare "tailscale" name.

* refactor: rename @vps/* to @tomgrv/vps-*, add root setup.sh

1/ Every package.json's "name" and "dependencies" keys move from
@vps/<name> to @tomgrv/vps-<name>, matching the org scope used
elsewhere (tomgrv/scripts, tomgrv/devcontainer-features). Updated
dispatch.sh's feature_name()/pkg_deps() and common/run.sh's
feature_package_json() to match the new prefix; package-lock.json
regenerated; README and test fixtures updated throughout.

2/ Added a root setup.sh (mirroring tomgrv/scripts' own): a thin,
zero-prerequisite wrapper that installs zz_use by delegating to
tomgrv/scripts' setup.sh - the same "don't reimplement what you can
delegate to" reasoning that script itself uses for zz_use's bin-dir
logic. Every run.sh's bootstrap shrinks from a 3-line block embedding
the tomgrv/scripts URL directly to one line delegating to this repo's
own setup.sh instead:

    command -v zz_use >/dev/null 2>&1 || curl -fsSL "${VPS_SETUP_URL:-...}" | sh

so the tomgrv/scripts URL/env-var default now lives in exactly one
place. Documented the whole pattern in a new README section,
"Replicating this pattern in another repo", generic enough to copy into
a different codebase (this repo, tomgrv/devcontainer-features'
common-utils, and tomgrv/scripts itself already share this shape).

Extended tests/ to cover both: the new @tomgrv/vps-* dependency
declarations, and that every run.sh uses the centralized setup.sh
bootstrap rather than a duplicated tomgrv/scripts URL.

* refactor: centralize zz_use bootstrap in setup.sh, called once by dispatch.sh

Every feature's run.sh previously embedded its own
"command -v zz_use || curl ... setup.sh | sh" - one curl call per script.
Removed: dispatch.sh now runs setup.sh exactly once, up front (right
after the root check, before any feature runs), so zz_use is on PATH for
every feature's subprocess. Individual run.sh scripts no longer curl
anything themselves - they just fail fast with a one-line message
pointing at setup.sh if zz_use somehow still isn't there (e.g. run
standalone, outside dispatch.sh).

common/run.sh drops the bootstrap entirely: it's only ever reached by
sourcing from a caller that already passed this same check, so by the
time its own body runs, zz_use is guaranteed present.

Found and fixed a real bug this exposed under end-to-end testing:
common/run.sh's VPS_SETUP_ROOT was computed from its own BASH_SOURCE[0]
(wherever zz_use last installed it, e.g. /usr/local/bin/common) instead
of the calling script's location - since zz_use perspikapps/vps/common
always (re)installs rather than reusing a local checkout's sibling
common/run.sh (explicit org/repo requests never skip as "already
available"), this broke feature_package_json/net_port/all_network_ports
even inside a normal dispatch.sh run from a full checkout. Fixed to use
BASH_SOURCE[1] (the caller's location), matching the pattern net_port/
net_access already used.

Updated README.md's "Layout", standalone-run, and "Replicating this
pattern in another repo" sections to match; extended tests/ with two new
checks (no run.sh curls setup.sh itself; dispatch.sh bootstraps zz_use
exactly once).

* refactor: rationalize comments and logs, focus on usability

Removed comments that only restated what the following line already
does, historical/rationale narration (why a decision was made, links to
external docs), and header "Env vars:" blocks duplicating README's env
var tables - keeping just a one-line pointer to README instead. Every
run.sh's header comment shrinks to a single line.

Kept: every runtime log/warn/die message an operator actually sees while
running the script (these are the usability payload, not noise) -
including the detailed failure diagnostics in argocd/epinio's helm
timeout handling and dispatch.sh's TAILSCALE_AUTHKEY guard. Kept the
handful of comments that explain a genuinely non-obvious gotcha
(random_password's SIGPIPE/pipefail interaction, the ERR trap, the
BASH_SOURCE[1] indirection in VPS_SETUP_ROOT) but compressed them to one
line each.

Also fixed a real doc gap this exposed: setup.sh's own trimmed comment
was the only place documenting its ZZ_SCRIPTS_REF/ZZ_SCRIPTS_SETUP_URL
override env vars - moved that one line to README's Layout section
instead of just dropping it.

~230 lines removed across dispatch.sh, setup.sh, common/run.sh, and
every feature's run.sh; behavior unchanged (bats/shellcheck/sh -n all
still pass).

* refactor: trim leftover comment on the setup.sh-curl bats test

Missed in the previous rationalization pass - the test name already
says what it checks.

* refactor(common): migrate log/warn/die call sites onto zz_log directly

Removes the log()/warn()/die() wrapper functions from common/run.sh
(ok() is kept - it's not a thin proxy the same way) and updates every
call site across the feature scripts to call zz_log i/w/e directly,
adding an explicit exit 1 where die() previously did. dispatch.sh's
own independent log/warn/die are untouched since it must stay a
zero-dependency POSIX sh entrypoint that runs before zz_use exists.

* feat(setup): make setup.sh the single curl entrypoint, execing dispatch.sh

setup.sh now execs package.json's "main" (dispatch.sh) after
installing zz_use, forwarding args through - so `curl setup.sh | sh
-s -- --only-rancher` behaves the same as calling dispatch.sh
directly. dispatch.sh's own zz_use bootstrap now forwards "$@" the
same way when it falls back to setup.sh, and the check moved earlier
(right after the OS check) so a fresh install doesn't re-run the
interactive menu twice. Matches the shared setup.sh pattern now used
across tomgrv/scripts and tomgrv/devcontainer-features.

* refactor(setup): rename SETUP_DIR to ZZ_SCRIPTS_SETUP_DIR

Avoids colliding with other repos' own SETUP_DIR-shaped env vars by
keeping this script's local var under the zz_ namespace convention.

* fix(setup): 🐛 download zz_use bootstrap before running instead of piping

A curl | sh pipeline's exit status is sh's, not curl's, so a failed
download would go unnoticed under set -e. Download to a temp file
and run that instead - same fix applied to the scripting devcontainer
feature's copy of this template (tomgrv/devcontainer-features#139).

* fix(setup): 🐛 constrain package.json "main" to a safe relative path

Same hardening as tomgrv/scripts's copy of this template: reject
absolute paths and ".." segments in "main" before using it to build
a file path, and invoke sh with "--" for defense in depth.

* fix: address Copilot review feedback on #4

- common/run.sh: feature_package_json()/all_network_ports() now only
  scan directories that have a run.sh alongside package.json, so a
  checked-out node_modules/ (thousands of unrelated package.json
  files) is no longer traversed/jq-parsed on every call.
- README: fix a leftover "tailscale/" reference (folder is
  vps-tailscale/), clarify that curl | sh only bootstraps zz_use
  without a local checkout to find package.json/dispatch.sh in, and
  note the jq prerequisite in the standalone zz_use example.
- tests: fixture dirs get a run.sh to match the new filter, plus a
  regression test asserting node_modules/* ports are skipped.

* chore: allow git push/tag without prompting

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants