Skip to content

fix(cli): ship lucli-native module, version stamps, and tar dep in Linux packages#2701

Merged
bpamiri merged 2 commits into
developfrom
fix/bot-2700-rpm-packaging-v4-0-0-ships-legacy-wheels-cli-modul
May 15, 2026
Merged

fix(cli): ship lucli-native module, version stamps, and tar dep in Linux packages#2701
bpamiri merged 2 commits into
developfrom
fix/bot-2700-rpm-packaging-v4-0-0-ships-legacy-wheels-cli-modul

Conversation

@wheels-bot
Copy link
Copy Markdown
Contributor

@wheels-bot wheels-bot Bot commented May 15, 2026

Summary

The v4.0.0 .deb / .rpm Linux packages diverged from the brew formula in three ways that broke wheels start on Rocky Linux during the paiindustries/titan production cutover on 2026-05-13. This PR aligns the Linux packaging staging pipeline with the brew formula and the Scoop manifest so all three distributions ship the same lucli-native module under the same dispatch model.

Three sub-fixes + one runtime-dependency fix:

  1. Right module. tools/distribution-drafts/linux-packages/build-linux-packages.sh now untars wheels-module-${WHEELS_VERSION}.tar.gz into /opt/wheels/module/ instead of unzipping the legacy CommandBox-shaped wheels-cli-${WHEELS_VERSION}.zip. The lucli-native artifact (~24 MB, with Module.cfc at top) is the one the brew formula and the Scoop manifest stage.
  2. Right dispatch. The LuCLI binary is staged as /opt/wheels/wheels so basename(argv[0]) is wheels when the wrapper execs it — mirroring the brew formula's libexec.install resource("lucli") => "wheels". LuCLI routes via argv[0], so the rename is what makes wheels start reach the bundled wheels module.
  3. Version + channel stamps. nfpm-wheels.yaml and nfpm-wheels-be.yaml now declare /opt/wheels/.version and /opt/wheels/.channel under contents:. Without them, wheels --version returned unknown (stable).
  4. tar dependency. Rocky Linux 10 minimal cloud images do not ship tar, and any role that unpacks /opt/wheels/module/ (or other tarball payloads) fails silently without it. Both yamls now declare tar under overrides.{rpm,deb}.depends.

The paiindustries/titan Ansible wheels-cli role has been carrying an overlay workaround for all three sub-bugs since 2026-05-13. With this fix, that workaround is no longer needed — and every titan VM (plus every future rpm consumer) gets a working wheels start straight from the package.

Related Issue

Fixes #2700

Type of Change

  • Bug fix
  • New feature
  • Enhancement to existing feature
  • Documentation update
  • Refactoring

Feature Completeness Checklist

  • DCO sign-off -- Commit carries Signed-off-by: (git commit -s)
  • Tests -- 8 new specs under vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc pin all four sub-fixes against the source of build-linux-packages.sh, nfpm-wheels.yaml, and nfpm-wheels-be.yaml. Pattern follows buildArtifactLicenseSpec.cfc and ConfigRoutesStaleDocUrlSpec.cfc — structural assertions against the packaging-file source, since end-to-end execution of nfpm-built rpms requires a Rocky/Debian sandbox and signed-key access that the test runner doesn't have.
  • Framework Docs -- left unchecked; bot-update-docs.yml will follow up
  • AI Reference Docs -- left unchecked; bot-update-docs.yml will follow up
  • CLAUDE.md -- left unchecked; bot-update-docs.yml will follow up
  • CHANGELOG.md -- entry under [Unreleased]### Fixed
  • Test runner passes -- cli layer: 42 pass, 0 fail, 0 error (was 34 pass, 8 fail before the implementation; the 8 failing specs are the new ones in this PR). Full core suite (Lucee 7 + SQLite): 3553 pass, 0 fail, 0 error, 16 skipped — no regressions.

Test Plan

  • Spec lives at vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc.
  • The failing → passing transition was confirmed by running the cli layer before and after the implementation:
    • Before: 34 pass, 8 fail — all 8 are the new specs:
      • build-linux-packages.sh — consumes the lucli-native wheels-module tarball, not the legacy wheels-cli zip
      • build-linux-packages.sh — emits a wrapper that routes lucli through the wheels module
      • nfpm-wheels.yaml — ships /opt/wheels/.version
      • nfpm-wheels.yaml — ships /opt/wheels/.channel
      • nfpm-wheels.yaml — declares tar as an rpm runtime dependency
      • nfpm-wheels-be.yaml — ships /opt/wheels/.version
      • nfpm-wheels-be.yaml — ships /opt/wheels/.channel
      • nfpm-wheels-be.yaml — declares tar as an rpm runtime dependency
    • After: 42 pass, 0 fail.
  • Full core suite (http://localhost:60007/wheels/core/tests?db=sqlite&format=json) re-run: 3553 passed, 0 failed, 0 errors, 16 skipped — no regressions outside the cli layer.
  • End-to-end validation requires a Rocky / Debian sandbox + nfpm install + signed key. The structural test pins what nfpm pkg will see in the contents block and the wrapper exec form; the actual built artifact's correctness on a Rocky 10 VM will need to be validated by re-running the paiindustries/titan cutover sequence (or a clean reproduction of it) once the rpm is published from release.yml.

Screenshots / Output

Before (failing spec output on the cli layer):
  Failed: consumes the lucli-native wheels-module tarball, not the legacy wheels-cli zip
  Failed: emits a wrapper that routes lucli through the wheels module
  Failed: nfpm-wheels.yaml — ships /opt/wheels/.version
  Failed: nfpm-wheels.yaml — ships /opt/wheels/.channel
  Failed: nfpm-wheels.yaml — declares tar as an rpm runtime dependency
  Failed: nfpm-wheels-be.yaml — ships /opt/wheels/.version
  Failed: nfpm-wheels-be.yaml — ships /opt/wheels/.channel
  Failed: nfpm-wheels-be.yaml — declares tar as an rpm runtime dependency
  → 34 pass, 8 fail

After:
  → 42 pass, 0 fail, 0 error

…nux packages

The v4.0.0 .deb / .rpm Linux packages diverged from the brew formula in
three ways that broke `wheels start` on Rocky Linux during the titan
production cutover on 2026-05-13:

1. `build-linux-packages.sh` unzipped `wheels-cli-${WHEELS_VERSION}.zip`
   (the legacy CommandBox-shaped artifact built from `cli/src/`) into
   `/opt/wheels/module/` instead of untarring
   `wheels-module-${WHEELS_VERSION}.tar.gz` (the lucli-native module built
   from `cli/lucli/`, with `Module.cfc` at top). LuCLI couldn't resolve
   any `wheels` verbs against the CommandBox-shaped module.
2. The wrapper ended with `exec /opt/wheels/lucli "$@"`, so basename(argv[0])
   was `lucli` and LuCLI had no module context — `wheels start` became
   `lucli start` and hit `Unknown command: 'start'`.
3. `nfpm-wheels.yaml` and `nfpm-wheels-be.yaml` never declared the
   `.version` / `.channel` stamps under `contents:`, so `wheels --version`
   returned `unknown (stable)` regardless of installed version.

Plus Rocky Linux 10 minimal cloud images don't ship `tar`, and any role
that unpacks the module payload fails silently without it.

This change:
- Stages `wheels-module-${WHEELS_VERSION}.tar.gz` into `/opt/wheels/module/`.
- Renames the LuCLI binary to `wheels` at stage time (mirroring the brew
  formula's `libexec.install resource("lucli") => "wheels"`), so
  `basename(argv[0])` is `wheels` and LuCLI's module dispatcher routes
  through the bundled module.
- Adds `/opt/wheels/.version` and `/opt/wheels/.channel` to the nfpm
  `contents:` block in both yamls.
- Adds `tar` to the rpm + deb runtime dependency list.

Regression test in `vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc`
pins all four sub-fixes against the packaging file source.

Fixes #2700

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
…nary rename

The .deb/.rpm installation page described the package contents as including
/opt/wheels/lucli (the LuCLI launcher). PR #2701 renamed the staged binary
to /opt/wheels/wheels (mirroring the brew formula's argv[0]-based dispatch)
and added .version/.channel stamp files. Update both v4-0-0 and v4-0-1-snapshot
installation pages to reflect the corrected on-disk layout.

Fixes #2700

Signed-off-by: wheels-bot[bot] <wheels-bot[bot]@users.noreply.github.com>
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
@wheels-bot
Copy link
Copy Markdown
Contributor Author

wheels-bot Bot commented May 15, 2026

Wheels Bot — Docs updated

Added a doc commit to this PR:

  • web/sites/guides/src/content/docs/v4-0-0/command-line-tools/installation.mdx — corrected the Linux package layout description: /opt/wheels/lucli/opt/wheels/wheels (renamed binary), and listed the new .version/.channel stamp files that ship in the package
  • web/sites/guides/src/content/docs/v4-0-1-snapshot/command-line-tools/installation.mdx — same correction in the v4-0-1-snapshot page

The installation guide described the package contents with the old /opt/wheels/lucli path; after this fix the binary on disk is /opt/wheels/wheels. The stamp files (/opt/wheels/.version, /opt/wheels/.channel) were also not listed previously.

Copy link
Copy Markdown
Contributor Author

@wheels-bot wheels-bot Bot left a comment

Choose a reason for hiding this comment

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

Wheels Bot — Reviewer A

TL;DR: This PR correctly fixes four v4.0.0 Linux packaging regressions (wrong module artifact, missing argv[0] rename, missing version/channel stamps, missing tar dep) that broke wheels start on Rocky Linux during the titan production cutover. The implementation changes are all correct and the CHANGELOG entry and DCO sign-off are present. Two minor test-quality nits, neither of which warrant blocking the merge — verdict: comment.


Correctness

The four sub-fixes are mechanically correct:

  1. tar -xzf wheels-module-${WHEELS_VERSION}.tar.gz replacing unzip wheels-cli-${WHEELS_VERSION}.zip — correct artifact, correct extraction tool.
  2. Binary staged as build/wheels (not build/lucli) so basename(argv[0]) is wheels when the wrapper execs it — mirrors the brew formula's libexec.install resource("lucli") => "wheels".
  3. .version / .channel stamps declared under contents: in both nfpm yamls — the old code wrote the files to staging but never added the contents: entries, so nfpm silently dropped them.
  4. tar declared as a runtime dep for both rpm and deb — defensive and correct even though the wrapper uses cp -r at runtime, since downstream Ansible roles consuming the package may call tar.

One observation on the tar point: the runtime wrapper at build-linux-packages.sh:142 uses cp -r /opt/wheels/module/. "${MODULE_DIR}/" (not tar), so the installed package itself doesn't call tar at runtime. The PR body mentions "any role that unpacks /opt/wheels/module/" as the motivating case. That rationale is defensible, but a comment in the nfpm yaml would make the "why" clearer to future maintainers — the current comment in nfpm-wheels.yaml:76-78 mentions unpacking specifically, which is good.


Tests

Nit 1 — test title says "rpm" but regex is file-wide

vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc:148:

it("declares tar as an rpm runtime dependency", () => {
    var hasTarDep = reFindNoCase(
        "-[[:space:]]+tar([[:space:]]|$)",
        src
    ) > 0;

The regex matches - tar anywhere in the file. In the current implementation tar appears in both the deb.depends and rpm.depends sections — so the test passes correctly. But if a future change removed tar from rpm.depends while leaving it in deb.depends, this test would falsely pass. The test title says "rpm runtime dependency" and should either (a) use a regex that anchors the check to the rpm: block, or (b) be renamed to "declares tar as an rpm and deb runtime dependency" so the scope matches the regex. Suggested anchor approach:

// Split the yaml at the rpm: overrides block and check only that segment
var rpmBlock = reFindNoCase("rpm:[^\n]+\n(.*)", src, 1, true);
// then check hasTarDep within rpmBlock.match[2]

This is a test reliability concern, not a blocking correctness issue. The actual fix (both rpm and deb) is correct.

Nit 2 — nfpm binary path rename not covered

The spec verifies that build-linux-packages.sh uses exec /opt/wheels/wheels "$@" (not exec /opt/wheels/lucli "$@"), but there is no corresponding check that the nfpm yamls changed src: ./build/luclisrc: ./build/wheels and dst: /opt/wheels/luclidst: /opt/wheels/wheels. If the yaml binary path were inadvertently reverted while the build script remained fixed, nfpm would fail at package-build time with a "file not found" error — caught by CI — but the spec would still pass green. A one-liner reFindNoCase("dst:[[:space:]]+/opt/wheels/lucli", src) == 0 assertion on each yaml would close the gap.


Cross-engine

No CFML model/controller/view code is touched. The new spec uses the same expandPath("/wheels/../..") + IIFE-for-loop-closure pattern established in ConfigRoutesStaleDocUrlSpec.cfc (which ships and passes today), so there are no new cross-engine concerns.


Commits

Single commit edd39be74fix(cli): ship lucli-native module, version stamps, and tar dep in Linux packages. Type fix, subject under 100 chars, not ALL-CAPS. DCO sign-off present: Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> matches the commit author identity. Conformant.


Docs

CHANGELOG entry under [Unreleased] → Fixed is present. Framework and AI reference docs deferred to bot-update-docs.yml — acceptable for a packaging-only fix with no framework API changes.

Copy link
Copy Markdown
Contributor Author

@wheels-bot wheels-bot Bot left a comment

Choose a reason for hiding this comment

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

Wheels Bot — Reviewer A

TL;DR: This PR fixes four v4.0.0 Linux packaging regressions (wrong module artifact, wrong binary name, missing version/channel stamps, missing tar dep) that broke wheels start on Rocky Linux. The implementation commit (edd39be74) was reviewed at the previous SHA; the new commit (15ba26138) is a clean docs follow-up that correctly syncs the installation pages with the binary rename. Two test-quality nits from the prior review cycle remain unaddressed, but neither is blocking. Verdict: comment.


Correctness

All four sub-fixes remain mechanically correct:

  1. Untarring wheels-module-VERSION.tar.gz correctly stages the lucli-native artifact instead of the legacy CommandBox zip.
  2. Binary staged as build/wheels and installed to /opt/wheels/wheels; wrapper ends with exec /opt/wheels/wheels "$@" so argv[0] dispatch works.
  3. /opt/wheels/.version and /opt/wheels/.channel declared under contents: in both nfpm yamls — wheels --version no longer returns unknown (stable).
  4. tar declared in overrides.rpm.depends and overrides.deb.depends in both yamls.

The new docs commit (15ba26138) is factually accurate: the installation description now names /opt/wheels/wheels, /opt/wheels/.version, and /opt/wheels/.channel, matching the nfpm contents blocks exactly.


Tests

Two nits from the prior review cycle remain unaddressed. Neither blocks the merge, but both are worth a follow-up.

Nit 1 — tar-dep assertion is file-wide, not rpm-anchored

vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc line 148 — the regex -[[:space:]]+tar([[:space:]]|$) matches - tar anywhere in the file. Since tar appears in both the deb.depends and rpm.depends sections, the test passes correctly today. But if a future change removed tar from only the rpm block while leaving it in deb, this test would false-pass while its title claims "rpm runtime dependency". Either anchor the check to the rpm section, or rename the test to "declares tar as an rpm and deb runtime dependency" to match the regex's actual scope.

Nit 2 — no nfpm binary-path assertion

The spec verifies that the build script wrapper uses exec /opt/wheels/wheels "$@" (not the old lucli form), but there is no corresponding check that the nfpm yamls changed dst: /opt/wheels/lucli to dst: /opt/wheels/wheels. If the yaml were inadvertently reverted, nfpm would fail at package-build time (caught by CI), but the spec would stay green. A one-liner per yaml would close the gap:

expect(reFindNoCase("dst:[[:space:]]+/opt/wheels/lucli", src)).toBe(0,
    t.label & " must not reference the old /opt/wheels/lucli path");

Docs

Both installation pages (v4-0-0 and v4-0-1-snapshot) are correctly updated. No other installation pages for these versions exist in the repo. The updated sentence correctly lists all five on-disk paths: /usr/bin/wheels, /opt/wheels/wheels, /opt/wheels/module/, /opt/wheels/.version + /opt/wheels/.channel, and /opt/wheels/sqlite-jdbc.jar.


Commits

Both commits conform to commitlint: type from the allowlist, subjects under 100 chars, not ALL-CAPS. edd39be74 carries one DCO sign-off line; 15ba26138 carries two (wheels-bot[bot] and claude[bot]) — DCO is satisfied by either.


Security

No CFML model/controller/view code changed. No secrets in the diff. No user-facing input paths introduced.

@wheels-bot
Copy link
Copy Markdown
Contributor Author

wheels-bot Bot commented May 15, 2026

Wheels Bot — Reviewer B (round 1)

A's review is solid. Both nits are real, all correctness claims check out, no sycophancy, no false positives. One inaccuracy in A's framing of Nit 2, but it does not change the verdict. Aligning on approve.

Sycophancy

None detected. A's TL;DR is backed by line-specific analysis for all four sub-fixes, and A did not approve — chose COMMENTED explicitly.

False positives

None detected. I verified every claim A made against the diff:

  • The four sub-fix assessments (artifact rename, argv[0] dispatch, stamp files, tar dep) are all accurate.
  • Nit 1 regex scope observation is correct: LinuxPackageStagingSpec.cfc:156 uses "-[[:space:]]+tar([[:space:]]|$)" which matches anywhere in the file. Both nfpm yamls now carry tar under both deb.depends and rpm.depends, so a future removal from the rpm-only block would not trip the test. The title says "rpm runtime dependency" but the regex covers deb too — A's read is accurate.
  • Nit 2 observation (no assertion that dst: /opt/wheels/lucli was replaced in the nfpm yamls) is also accurate. The eight new specs cover the build script wrapper exec form, the stamp files, and the tar dep, but no spec checks that the binary destination was renamed from lucli to wheels in the yamls themselves.

One inaccuracy in A's Nit 2 framing: A writes the gap "would be caught by CI" as mitigation. That is not right. The PR body itself states end-to-end validation requires a Rocky/Debian sandbox and nfpm install — that is release.yml, not the test matrix. A regression here would be caught at release-build time, not in CI. This makes Nit 2's coverage gap slightly more important than A's language ("nit") implies — it is the only automated safety net for that path — though it still does not rise to blocking.

Missed issues

None detected. The docs changes in the second commit (15ba26138) are two one-line path renames (/opt/wheels/lucli/opt/wheels/wheels plus stamp file list) in the installation guide; both are accurate and consistent with the nfpm yaml changes.

Verdict alignment

A's COMMENTED (non-blocking) verdict is consistent with the findings. Two test-quality nits on a packaging fix are not merge blockers.

Convergence

Aligned on approve. The four sub-fixes are correct, the CHANGELOG and DCO are in order, and the two nits A raised are real but non-blocking quality improvements to new test coverage. A's verdict is appropriate. The CI inaccuracy on Nit 2 is worth noting but does not change the overall recommendation.

@wheels-bot
Copy link
Copy Markdown
Contributor Author

wheels-bot Bot commented May 15, 2026

Wheels Bot — Reviewer B (round 1)

A's review is accurate and consistent. No sycophancy, no false positives, and the two nits A flagged are real — but A explicitly characterized both as non-blocking follow-ups, not required changes. The implementation is correct on all four sub-fixes, and the tests do pin the regressions they claim to pin. I'm aligning on approve.

Sycophancy

None detected. A chose "comment" rather than "approve" precisely because the nits were unresolved — that is the more conservative call, not the easier one.

False positives

None detected. I re-read the diff against each of A's correctness claims:

  • Untarring wheels-module-${WHEELS_VERSION}.tar.gz vs the old unzip wheels-cli-...: correct per the build-linux-packages.sh diff.
  • Binary staged as build/wheels installed to /opt/wheels/wheels: both nfpm yamls show src: ./build/wheels / dst: /opt/wheels/wheels and the wrapper heredoc ends with exec /opt/wheels/wheels "$@".
  • .version and .channel declared under contents: in both yamls: confirmed.
  • tar in overrides.rpm.depends and overrides.deb.depends in both yamls: confirmed — A's statement was accurate; both sections in both files were updated.
  • Docs and commit conformance claims: verified against the diff and commitlint.config.js rules.

Missed issues

None that would change the verdict.

One observation worth noting but not blocking: the spec constructs repoRoot via expandPath("/wheels/../..") which relies on the /wheels Lucee mapping being configured identically across all test engines. If Adobe CF or BoxLang configures this mapping differently, the fileExists(buildScript) guard would surface the failure explicitly — the test would fail with "Missing file", not silently pass. This is the same pattern used elsewhere in the core test suite, so it is presumably stable, but worth keeping in mind if these tests start failing on non-Lucee engines.

Verdict alignment

A's "comment" verdict is consistent with having two concrete but explicitly non-blocking nits. Either "comment" or "approve" would be defensible here; A's more conservative choice is reasonable, not misaligned.

Convergence

Aligned. A's two nits (tar-dep assertion scope, missing nfpm binary-path assertion) are real and worth addressing in a follow-up — but A called them non-blocking and described them as "worth a follow-up," not required changes to this PR. The implementation is mechanically correct on all four regressions. Recommending approve.

@bpamiri bpamiri marked this pull request as ready for review May 15, 2026 18:55
@bpamiri bpamiri merged commit cf0d169 into develop May 15, 2026
14 of 15 checks passed
@bpamiri bpamiri deleted the fix/bot-2700-rpm-packaging-v4-0-0-ships-legacy-wheels-cli-modul branch May 15, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rpm packaging: v4.0.0 ships legacy wheels-cli module, missing .version / .channel files (titan cutover regression)

1 participant