feat(hub): install the control box from the npm package, keep --ref for source#258
Merged
Conversation
…or source `agentbox hub deploy hetzner` cloned the monorepo onto the VPS and built 14 workspace packages plus the CLI there — ~2m40s of a ~4m45s deploy — to produce an artifact the published package already carries: `runtime/hub/apps/hub/server.js`, the same standalone bundle `agentbox hub` spawns locally. It now installs `@madarco/agentbox` pinned to the deploying CLI's exact version. The host ships the compose stack (the base docker-compose.yml verbatim, plus an override that swaps only the build block), so there is no repo on the VPS at all and no version-skew surface — that coupling is what made a nightly CLI's deploy 502 against a `main` hub listening on a different port. It also fixes base-image fingerprint parity by construction: the control box's runtime/ tree is now the same npm tarball the PC installed, rather than a ref that happens to match. `--ref`/`--repo` keep the clone-and-build path for deploying unreleased code, and a dev build (whose version was never published) falls back to it automatically. `--package <spec>` pins a different npm spec. deploy.json now records which build a control box is running, and `hub status` shows it.
… port A hub answering on the VPS but not over HTTPS was reported as one guess — "Caddy cannot reach it, so its upstream port does not match" — which is wrong whenever the certificate is the problem. Hit live: sslip.io derives the hostname from the IP, so a recycled Hetzner address arrived already at Let's Encrypt's 5-certs-per-identifier-per-week cap, and the deploy blamed a port that was perfectly correct. diagnoseUnhealthy now greps Caddy's own log when the hub is healthy locally, and names the actual cause — including the fix for the rate-limited case (redeploy for a different IP, or pass --domain).
…ntrol The sslip.io default derives the hostname from the IP, so it isn't really yours and Hetzner reuses released addresses — landing on one that already had five certificates issued this week leaves a healthy hub with no usable HTTPS until the 7-day window rolls. --domain is the way out (and what the new rate-limit diagnostic points at); the underlying deploy already accepted a domain, only the CLI never exposed it.
MDX parses the attribute value as JSX, so \" is an invalid attribute name character, not an escaped quote — `next build` failed to evaluate the page.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Owner
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 0563cca. Configure here.
… text Bugbot caught that the certificate check matched Caddy's SUCCESS lines: `tls.obtain` appears in "obtaining certificate", "lock acquired", and "certificate obtained successfully", so a healthy cert plus a genuine upstream mismatch would have been reported as a certificate problem. The verdict now comes from an HTTPS request made ON the VPS (--resolve pinned to loopback), which exercises the real certificate and the real reverse_proxy upstream: 200 means the break is outside the box (DNS / :443 rule), 000 means the TLS handshake never completed, any other status means Caddy answered and the upstream is wrong. The log is read only for the rate-limit detail, which has an unambiguous error signature. Split out as the pure `describeCaddyHop` so the three-way verdict is unit-tested, including the exact log that fooled the old check.
Owner
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 230d94e. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
agentbox hub deploy hetznercloned the monorepo onto the VPS and built 14 workspace packages plus the CLI there — ~2m40s of a ~4m45s deploy — to produce an artifact the published package already carries:runtime/hub/apps/hub/server.js, the same standalone bundleagentbox hubspawns locally (resolveHubServer, staged bystage-runtime.mjs).Building from a ref also couples the VPS artifact to a branch name rather than a version. That coupling is what produced the 502 fixed last week:
DEFAULT_DEPLOY_REFwasmainwhile the CLI was a nightly, somain's hub listened on:3000behind Postgres while the CLI wrotereverse_proxy app:8787.What changed
The VPS now runs
npm i -g @madarco/agentbox@<the deploying CLI's exact version>in a smallnode:22-slimimage, and the host ships the compose stack:apps/hub/docker-compose.ymlverbatim plusdocker-compose.package.yml, an override that replaces only thebuild:block. There is no repo on the VPS at all — cloud-init no longer clones.Sharing the base compose verbatim is deliberate: the service's
environment:/volumes:/ports:block keeps exactly one definition, so the two modes cannot drift.Consequences worth calling out:
runtime/tree is literally the same npm tarball the PC installed, rather than a ref that happens to correspond. (AGENTBOX_RUNTIME_ROOTstill has to be set, and nowAGENTBOX_CLI_RUNTIME_DIRtoo — a container has no parent CLI process to inherit it from.)NODE_ENV=productionis load-bearing in the image: without it Next takes the dev path and dies withCouldn't find any `pages` or `app` directory.--ref/--repokeep the clone-and-build path (a dev CLI, whose version was never published, falls back to it automatically);--package <spec>pins a different npm spec.deploy.jsonnow records which build a control box runs — nothing else on the host or the VPS did — andhub statusshows it.Also in here, both prompted by the live test:
diagnoseUnhealthyno longer reports a rate-limited certificate as "Caddy can't reach it, so the upstream port is wrong". It greps Caddy's log and names the real cause.--domain <host>onhub deploy hetzner(the deploy already accepted a domain; the CLI never exposed it) — the way out of a Let's Encrypt rate limit, since an sslip.io name is derived from a recyclable IP.Verification
Unit:
resolveHubDeploySource, the asset resolver, cloud-init with/without the clone, and a drift guard that the shipped compose still yieldshubContainerPort === 8787/isFullHubCompose. Fullpnpm build,typecheck,lint, and the web build all green.npm pack --dry-runconfirmsruntime/hub-deploy/**ships.Live on Hetzner:
/opt/agentboxholds no repo clone, only the six host-shipped files. In-container: version matches the deploying CLI exactly,gh 2.96.0present, all fourAGENTBOX_*pointers resolve under/opt/agentbox-cli, every provider's staged-runtime marker resolves (hetzner/scripts/install-box.sh,e2b/scripts/build-template.sh,vercel/scripts/provision.sh,digitalocean/…,_shared/ctl.cjs,docker/Dockerfile.box), and/healthzreturns{"ok":true,"ui":true,"cliEntry":true}.--ref nightlyregression — fully green end to end: healthy over public HTTPS,relay.controlPlaneUrlwired, and the e2b bake shared with the control box.One caveat, external to this change: two package-mode runs landed on a recycled Hetzner IP that had already hit Let's Encrypt's 5-certs-per-hostname-per-week cap, so their public HTTPS never came up (the hub itself was healthy on
127.0.0.1:8787throughout). That is what prompted the diagnostic fix and--domain. The--refrun on a fresh IP proves the shared Caddy path, which package mode does not modify.Note
Medium Risk
Changes the default production deploy path and VPS provisioning (cloud-init, compose files, env wiring); mistakes could break new control-box installs, though source mode and extensive tests preserve the escape hatch.
Overview
Hetzner control-box deploys now install
@madarco/agentboxfrom npm (pinned to the deploying CLI’s version) instead of cloning and building the monorepo on the VPS. Package mode scp’s a small compose stack (docker-compose.yml+docker-compose.package.yml+Dockerfile.package) fromruntime/hub-deploy/(staged bystage-runtime.mjs); cloud-init skips the git clone.--ref/--repostill use the old clone-and-build path; dev builds (0.0.0) auto-fall back to source.resolveHubDeploySourcecentralizes that choice; CLI flags add--packageand expose--domainonhub deploy hetzner.Observability and ops:
deploy.jsonrecordsHubDeploySource;hub statusshows the remote build line. Failed HTTPS health checks use an on-VPS curl probe (describeCaddyHop) so cert/rate-limit issues aren’t misreported as wrong Caddy upstream ports; recovery hints use the correct compose-flist per mode.Images: New
Dockerfile.packageglobal-installs the CLI and runs the staged hub bundle; sourceDockerfilegainsAGENTBOX_DOCKER_CONTEXT. Docs updated for default npm deploy, flags, and troubleshooting.Reviewed by Cursor Bugbot for commit 230d94e. Configure here.