Conversation
… not start (AP-7)
Windows had no way to run Caddy: `lt dev install` said "not supported", and
every other command then advised running `lt dev install`. Decision D1: no
Windows service (it would run as SYSTEM with a CA the user's browser does not
trust). Instead `lt dev install` and `lt dev up` start Caddy on demand:
`caddy run --config <ours>` via `spawnDetached`, logging to
~/.lenneTech/caddy.log. Not `caddy start`, which hands the background child
the caller's stdio.
Ownership rule, on every platform: someone who runs Caddy runs it for
something. Reloading our config into it would silently reroute or cut off those
sites. `detectCaddyOwner` decides by the loaded config, not by the process:
- `ours` when an inert `log lt-dev-owner { output discard }` global option is
loaded. Comments do not survive adaptation, so the block markers cannot serve
as the marker.
- `ours` for pre-marker instances when the loaded config equals `caddy adapt`
of our file. Callers therefore ask before they rewrite it.
- `foreign` for anything else answering on :2019, `none` when nothing answers.
Measured on this Mac: the live pre-marker instance reads as ours, a second
instance with another config as foreign.
`ensureOwnCaddy` is the one gate for up, test, tunnel and the test session. A
foreign Caddy gets our Caddyfile path plus `caddy reload` / `caddy run`, never
a stop or a reload. down, test teardown, install and uninstall ask first too.
doctor and status state "not started by lt dev" without advice.
Also fixed: `lt dev install` rewrote the Caddyfile stub on every run, dropping
the block of every project that was up. It now only adds the owner marker.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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
Windows had no way to run Caddy.
lt dev installanswered "not supported", and every other command (up,test,tunnel,status,doctor) then advised runninglt dev install. That was advice into the void.Ownership rule, on every platform. Someone who runs Caddy runs it for something, possibly a client project on :443. Reloading our config into that Caddy would silently reroute or cut off its sites, and whoever is affected would look for the fault everywhere but here. So
lt devnever touches a Caddy it did not start. It stops, says what it would have done, and puts the tools next to that. It never stops the other Caddy and never reloads it. The same direction as the registry fix in #116: when in doubt, pick the one that destroys nothing.What changed
Ownership: by what is loaded, not by which process (
src/lib/caddy.ts#detectCaddyOwner)none: nothing answers on :2019.ours: the loaded config carries an inertlog lt-dev-owner { output discard }global option. Comments do not survivecaddy adapt, so our# >>> lt-dev:<slug> >>>block markers never reachGET /config/. A named logger does, even in a Caddyfile without sites, and the default logger keeps logging as before (measured, caddy v2.11.3).ourswithout a marker: the loaded config equalscaddy adaptof our Caddyfile. That is how every existing installation is recognised. Its next reload carries the marker. Because of that, callers ask before they rewrite the Caddyfile, and every call site is ordered that way.foreign: anything else that answers, including an error status or a non-JSON reply. Something holds the port, and it is not us.One gate for every command (
src/lib/dev-caddy-gate.ts#ensureOwnCaddy), used byup,test,tunneland the test session:ours: ok.foreign: refused.foreignCaddyLinesprints the path of our Caddyfile,caddy reload --config … --adapter caddyfile(with the note that it replaces that Caddy's whole config) andcaddy run --config …for once :2019 is free. It does not suggest stopping anything.none: on Windows (withstartIfDown) our Caddy is started, and it counts only once it identifies as ours. macOS/Linux point atlt dev install, because the service owns the lifecycle there.down, test teardown,installanduninstallaskdetectCaddyOwnerfirst.downstill stops our processes and removes our block from our file, but does not reload a foreign Caddy.doctorandstatusstate "not started by lt dev". In doctor that counts as a FAIL, becauselt devcannot route through it, but it comes without a call to action.Windows start (D1) (
src/lib/dev-service.ts)caddyLaunchMode()returns:serviceon macOS/Linuxon-demandon WindowsmanualelsewherestartCaddyOnDemandrunscaddy run --config <ours> --adapter caddyfileviaspawnDetachedand logs to~/.lenneTech/caddy.log. I did not usecaddy start: it hands the background child the caller's stdout/stderr, and that pipe closes when the CLI exits.lt dev installon Windows: ownership check, then Caddyfile, then start, then thecaddy trusthint. The order is start first, trust second, becausecaddy trustwas measured to fail against a Caddy that is not running.isMachinePreparedon Windows means "our Caddyfile exists", solt dev initchains intoinstallexactly once.lt dev uninstallon Windows stops our Caddy (caddy stop), and only when it is ours.Also fixed:
lt dev installreset the Caddyfile on every run. It wrote the empty stub unconditionally and dropped the block of every project that was up.ensureCaddyfilenow creates the file only when it is missing, and otherwise only adds the marker. If ours is already running, it reloads so the marker loads. Without that reload, the config would no longer match the file and the next check would call our own Caddy foreign. That was a migration bug, and I caught it before the first run.Measured on this Mac (read-only against the live instance)
caddy adapt: comments in the configcaddy adapt:log lt-dev-ownerwithout sites{"logging":{"logs":{"lt-dev-owner":…}}}ensureOwnerMarker(new block, and inserted into an existing global block)detectCaddyOwner()against the live, pre-marker lt-dev CaddyoursforeignThe live Caddyfile was not modified (mtime 10:02, before this work). Every write-side test runs against
LT_DEV_CADDYFILEin a temp dir with injected owner answers.Mutation check (reverted from a backup)
oursnoneforeigndownwithout an owner checkensureCaddyfilealways resetscaddy startinstead ofcaddy runThe first two mutations first went "red" because of a compile error (an unused function), i.e. for the wrong reason. I redid them with mutations that compile.
npm test: 79 suites, 1236 tests.lintandcompileare clean. In one fullnpm run build, a test infullstack-add-commandshit the 60 s timeout at a load average of 175. Run alone it is green (14/14), and it does not touch Caddy.Not measured yet (Windows laptop)
PowerShell, in a project, with an
ltbuilt from this branch. Every step checks a return value, not an output line.If step 2 shows
False, the detached child does not survive the terminal, and on-demand then needs a different start (for example a scheduled task at logon). That is the one open question behind D1.Not covered by this PR (AP-6): the remaining doctor messages (
brew install caddyas the only install hint,/etc/hosts, Caddyfile validation before the first start).🤖 Generated with Claude Code