refactor: ♻️ wire to tomgrv/scripts, restructure into one folder per script - #4
Conversation
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.
923dea9 to
6701e24
Compare
|
(Also fixed the Generated by Claude Code |
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.
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.
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).
|
This workflow only triggers on 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.
There was a problem hiding this comment.
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 introducescommon/+summary/aszz_use-installable helper folders excluded from dispatcher discovery. - Switches feature bootstrapping to a single root
setup.sh(run once bydispatch.sh) and updates feature scripts to usezz_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.
- 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.
* 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>
Summary
lib/common.sh'slog/ok/warn/dieand its ERR trap duplicated ANSI-color logging logic that now has a canonical home inzz_colors/zz_logattomgrv/scripts— already shared withtomgrv/devcontainer-features' common-utils feature.features/<name>/to a top-level<name>/, alongside two new folders for what used to belib/common.shandlib/summary.sh:common/andsummary/— laid out the same way (package.json+run.sh) so they'rezz_use-able too, but excluded fromdispatch.sh's feature discovery. This matchestomgrv/scripts' own one-folder-per-script layout, which is what makeszz_useitself usable directly against this repo:zz_use perspikapps/vps/rancherfetches and installs any one feature standalone. See the README section "Running a single feature viazz_use, without this repo at all".package.jsondeclares"bin": {"<name>": "run.sh"}(matchingtomgrv/scripts' convention — and whatzz_useactually 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/, nottailscale/— itsrun.shcalls the realtailscaleCLI internally, so the bare name would let azz_use-installed copy shadow the actual binary it depends on.@vps/to@tomgrv/vps-throughout, matching the org scope used elsewhere (tomgrv/scripts,tomgrv/devcontainer-features).zz_usebootstrap in a rootsetup.sh, mirroringtomgrv/scripts' own:dispatch.shnow runs it exactly once, up front, instead of every feature'srun.shembedding its owncurl ... | sh. Individualrun.shscripts no longer curl anything — they just fail fast with a one-line message pointing atsetup.shifzz_usesomehow isn't already onPATH(e.g. run standalone, outsidedispatch.sh).setup.shrun once by the dispatcher, sharedcommon/, workspacepackage.json, tests), generic enough to copy into a different codebase.common/run.sh'sVPS_SETUP_ROOTwas computed from its own install location (BASH_SOURCE[0], e.g./usr/local/bin/common) rather than the calling script's location — sincezz_use perspikapps/vps/commonalways re-installs rather than reusing a checkout's siblingcommon/run.sh, this brokefeature_package_json/net_port/all_network_ports(used bysecurity/summary) even inside a normaldispatch.shrun from a full checkout. Fixed to useBASH_SOURCE[1](the caller's location), matching the patternnet_port/net_accessalready used.common/package.json's own two dependencies (zz_colors,zz_log) live in the separatetomgrv/scriptsrepo — documented under a non-npm"zzUse"field instead of"dependencies", since listing them there would breaknpm install.dispatch.sh's feature discovery, checkout-detection, and usage text updated for the new top-level layout;feature_deps()excludescommon/summaryfrom auto-enable/--down-<step>refusal since neither is an installable step despite now being a real dependency.package.json(workspaces) andpackage-lock.json(regenerated) updated for the new folder set and scope.tests/(bats) — this repo had no test suite before: 17 tests covering script syntax, thezz_use/common/setup.shwiring (including that norun.shcurlssetup.shitself),package.jsonshape (bin,dependencies, thetailscalecollision guard), andcommon/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.zz_useitself along the way (tomgrv/scripts#2, companion PR): its cache-warm/download-validity checks hardcoded azz_colors/run.shsentinel, which incorrectly rejected every origin repo besidestomgrv/scriptsitself.Test plan
sh -n dispatch.sh setup.sh,bash -n */run.sh— all passshellcheckon every script — no new warnings (2 pre-existing, unrelated)bats tests/— 17/17 passnpm installresolves the full workspace graph cleanly with the new@tomgrv/vps-*scope;node_modules/.bin/shows the expected symlinks, includingvps-tailscale(nottailscale)sudo sh dispatch.sh --only-system—zz_usebootstraps exactly once (viasetup.sh, not per-feature),systeminstalls, andsummary/run.shruns to completion at the end printing the full connection-info block (this is what caught and proved theVPS_SETUP_ROOT/BASH_SOURCE[1]fix); also verified a featurerun.shinvoked directly withoutzz_useonPATHfails fast with no network call, and the genuine-failure path (a nonexistentzz_usetool) still correctly errors🤖 Generated with Claude Code
https://claude.ai/code/session_01XADVbmyNJaD8Qm6Gm6f5RF