feat(ansible): provision the fleet browser ladder via a standalone fleet_browsers profile - #35
Merged
Merged
Conversation
The ladder only ran under fleet_guards, whose shared-Supabase play refuses to proceed without the fixture volume, so a host without that fixture could not get the obscura tier at all. - profiles.fleet_browsers (default off) gates tasks/fleet-browsers.yml, tagged fleet_browsers; obscura resolves only for it, supabase only for fleet_guards; ladder units leave factory_fleet_units for their own lists, checked by verify under the new profile. - The ladder env moves into herdr.service.j2: the old lineinfile edit was overwritten by the herdr template on every apply, restarting Herdr, and its PATH line was shadowed by the template's own. - journald cap tasks are tagged journald so a ladder-only run can skip it. - fleet-browser finds bun on PATH; Code Factory installs it in ~/.local/bin, not ~/.bun/bin, so cookie sync never ran.
…wsers; document iproute2
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.
Intent
Drive his browser yourself when work needs something only his session can reach - a credential, a console setting, a third-party sign-in, a dashboard toggle. Do not hand him click-by-click instructions.
Context: that is the captain's standing order, and fleet rule says every browser use goes through the shared
fleet-browserladder (obscura CDP tier on 127.0.0.1:9222 by default, Chrome tier, noVNC tier where a human must see or drive it; one canonical cookie jar synced across tiers). His newer rule also requires before/after screenshots for every UI change. Since the fleet moved to this host on 2026-10-01 the ladder does not exist here: nofleet-browser*systemd user units, nothing listening on :9222, so a worker on 2026-10-02 could not take screenshots. Code-Factory'sansible/tasks/fleet-browsers.yml(imported fromansible/site.ymlunder thefleettag when thefleet_guardsprofile is on) owns the ladder: the obscura tier, sync and gc timers, and env injection.Applied on the fleet host (Ubuntu 26.04)
This branch's recipe was applied on the host the fleet moved to on 2026-10-01, using that host's
.local/host.ymlplusfleet_browsers: true, narrowed to the ladder:accountonly resolves the account facts the ladder needs (no change). Changed:~/oss-fleet/browsers/(scripts, latest Obscura release, symlinks), oneenv.shsource line in~/.profileand~/.bashrc, 7fleet-browser-*user units + 3 enable links, then obscura and the sync/gc timers started. No packages, nothing under/etc, no Herdr/tailscale/ssh/docker change. A second apply reportedchanged=0.Evidence:
fleet-browser-obscura.serviceactive, listening on127.0.0.1:9222;curl /json/versionreturns"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser".chrome-devtools-axi open https://example.com+screenshotfrom a fresh shell (CHROME_DEVTOOLS_AXI_BROWSER_URL=http://127.0.0.1:9222fromenv.sh) produced a screenshot through obscura.fleet-browser-sync.timer(2 min) andfleet-browser-gc.timer(5 min) active; firstfleet-browser-sync.servicerunResult=success(cookie-sync: canonical 0 cookies), which needed thebun-on-PATH fix.After merge, that host's
.local/host.ymlneedsprofiles.fleet_browsers: true(the current main schema rejects the key, so it is set after merge). Not yet applied there: the Herdr unit's ladder environment (needs a run with theherdrtag, which restarts Herdr) and the host-wide journald cap (journaldtag, restartssystemd-journald).What Changed
profiles.fleet_browsersflag (defaultfalse, added toconfig/default.ymlandschemas/factory.schema.json).factory_fleet_browsers_enabledis true when eitherfleet_browsersorfleet_guardsis on. It now gates the import oftasks/fleet-browsers.ymlinsite.yml(newfleet_browserstag) and the Obscura release resolution, so the ladder no longer needs the Supabase CLI. The fleet-browser unit lists moved fromfactory_fleet_units/factory_fleet_enabled_unitsintofactory_fleet_browser_units/factory_fleet_browser_enabled_units.fleet-browsers.ymlnow loops over those lists and creates thedefault.target.wantsandtimers.target.wantsdirectories itself.verify.ymlnow checks the browser scripts, units and enable symlinks.herdr.service.j2now sets the ladder environment when the ladder is enabled: the fleet browsers dir onPATH,CHROME_DEVTOOLS_AXI_BROWSER_URL=http://127.0.0.1:9222andFLEET_BROWSER_TIER=obscura. This replaces thelineinfileedit of the installedherdr.service.fleet/browsers/fleet-browsernow defaultsBUNtobunfromPATHinstead of$HOME/.bun/bin/bun. The journald rate-limit tasks are taggedjournald.test_configuration.pynow checks thatobscurais resolved when either profile is on andsupabaseonly forfleet_guards.CONTRIBUTING.mdand the docs (configuration,dependencies,fleet-guards,capacity,agent-host-move) are updated for the new profile.Risk Assessment
✅ Low: The change is a bounded Ansible and config refactor that adds a
fleet_browsersprofile. Prior-round decisions are implemented consistently,fleet_guardshosts keep the ladder, and I found no concrete failing path.Testing
I applied the real playbook with the ladder tags into a disposable home, using a fake-sudo shim and the real Obscura 0.2.3 download. I checked all three profile combinations, idempotency, the
verifyassertions, and a live Obscura tier driven over CDP, then removed the sandbox. All scenarios passed. The one side effect on the operator's real Obscura unit is listed in the last scenario below.--tags fleet_browsers) leaves herdr.service untouched;--tags herdr,fleet_browsersrenders it with the ladder env (matches the new docs/fleet-guards.md text)--skip-tags journaldapplies the ladder without touching /etc/systemd/journald.conf.d--skip-tags journald; no journald tasks ranfleet-browser statusgoes from down to up, and a worker navigates and takes a screenshot (the failure the int…BUN=bunchange) when the home has no ~/.bunfleet-browser syncexited 0 with bun found at ~/.local/bin/bununshare -Urnnamespace isolates the network but not the user systemd bus. Myfleet-browser synctherefore restarted the operator's realfleet-browser-obscura.serviceonce (ActiveEnterTimesta…Evidence: Live Obscura ladder transcript: :9222 down before and up after, env.sh values, CDP navigate, evaluate and screenshot
Evidence: Apply run 1 and run 2 with fleet_browsers only (run 2 reports changed=0)
Evidence: Apply with fleet_guards only (existing host, no fleet_browsers key)
~/.no-mistakes/evidence/01M3X2T219BX49BYET2918N27W/apply-neither-profile.log)Evidence: Per-profile ladder files, units and Herdr unit environment
Evidence: verify names the deleted ladder outputs (env.sh, fleet-browser-gc.timer)
Evidence: fleet-browser sync with bun from PATH
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed (2) ✅
ansible/site.yml:105- The change gates the ladder on a newprofiles.fleet_browsersflag (default false) instead of the existingfleet_guardsgate. The intent saysansible/tasks/fleet-browsers.ymlis imported fromsite.yml"when thefleet_guardsprofile is on". No requirement in the intent needs a second profile, so this is an unrequired component. It also has two side effects.(1) A host that already has
fleet_guards: trueand nofleet_browserskey stops provisioning the ladder on the next apply (site.yml:105).herdr.service.j2:15-20re-renders the Herdr unit withoutCHROME_DEVTOOLS_AXI_BROWSER_URL,FLEET_BROWSER_TIERand the browsers PATH entry. Therestart herdrnotify inherdr.yml:36-37then restarts Herdr, so agents lose the ladder environment.verify.yml:93-105also stops checking the browser outputs on that host.(2) The diff changes no host config.
.local/host.ymlis local, so the ladder stays absent on the moved host until someone edits it by hand.docs/agent-host-move.md:21only documents that edit.The smallest honest remedy is to remove the new profile and keep the
fleet_guardsgate, with the Obscura/Supabase installer split and the unit-list refactor undone. If a separate profile is wanted, the author must say so. The remedy then needs an explicit decision on migrating existingfleet_guardshosts and on enabling the flag for this host. Thefleet_browsersandjournaldtags insite.yml:106andfleet-browsers.yml:11,26are also not required by the intent.Sites tied to the new profile:
ansible/group_vars/all.yml:212-213(installer_also split),config/default.yml:16,schemas/factory.schema.json:57,ansible/tasks/verify.yml:94-105,ansible/templates/herdr.service.j2:15-20,tests/test_configuration.py(test_each_fleet_profile_resolves_only_its_own_release), and the docs edits.docs/dependencies.md:81- The docs now sayfleet_browsers"Needs no other profile" (docs/configuration.md:84).docs/dependencies.md:81says only thedesktopprofile installsiproute2(ss), and that withoutssevery tier reports 0 clients and gc can stop a tier in use. Afleet_browsers-only host therefore relies oniproute2being present in the base image. Only the always-on obscura tier is provisioned there, so impact is low. Consider stating theiproute2dependency next to thefleet_browsersentry, or installing it with the profile. This only applies if F1's separate profile is kept.🔧 Fix applied.
1 warning still open:
ansible/templates/herdr.service.j2:15- The fix round moved the ladder's Herdr environment (CHROME_DEVTOOLS_AXI_BROWSER_URL,FLEET_BROWSER_TIER, the browsersPATHentry) intoherdr.service.j2. Before,fleet-browsers.ymlinjected it withlineinfile, so a run with--tags fleetcarried it. Now the template is rendered only bytasks/herdr.yml, whichsite.yml:56tags[herdr].An operator on the new host who runs
ansible-playbook --tags fleet_browsersgets the units andenv.sh, but the Herdr unit is not re-rendered andrestart herdris not notified. Herdr agents then still lack the ladder environment.That is the runbook
docs/fleet-guards.mdadvertises ("The playbook tagfleet_browsersnarrows a run to the ladder", in a paragraph that also says the ladder puts its environment in the Herdr unit).--tags fleethas regressed in the same way for existingfleet_guardshosts, and--tags fleet --skip-tags journaldis the other path the docs imply.Smallest fix: document that the Herdr unit needs the
herdrtag too (--tags herdr,fleet_browsers), or addherdrto the tags on the ladder's import insite.yml:106. This is not a defect in the template move itself. Keeping the template is correct, because the oldlineinfilewas reverted by the template on every apply.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
--tags fleet_browsers) leaves herdr.service untouched;--tags herdr,fleet_browsersrenders it with the ladder env (matches the new docs/fleet-guards.md text)--skip-tags journaldapplies the ladder without touching /etc/systemd/journald.conf.d--skip-tags journald; no journald tasks ranfleet-browser statusgoes from down to up, and a worker navigates and takes a screenshot (the failure the int…BUN=bunchange) when the home has no ~/.bunfleet-browser syncexited 0 with bun found at ~/.local/bin/bununshare -Urnnamespace isolates the network but not the user systemd bus. Myfleet-browser synctherefore restarted the operator's realfleet-browser-obscura.serviceonce (ActiveEnterTimesta…uv run pytest tests/test_configuration.py -q(56 passed, includestest_each_fleet_profile_resolves_only_its_own_release)uv run ansible-playbook -i ansible/inventory.yml ansible/site.yml --syntax-checkRealansible-playbook ansible/site.yml --tags herdr,fleet_browsers --skip-tags journaldwithfleet_browsers=true,fleet_guards=false, sandbox home,start_services=false; run twiceSame playbook withfleet_guards=true,fleet_browsers=falseSame playbook with both profiles off--tags verifyon the ladder host after deletingenv.shandfleet-browser-gc.timerRenderedfleet-browser-obscura.serviceExecStart run in aunshare -Urnnetwork namespace, thenfleet-browser status,env.shsourced in a shell, and a Bun CDP client navigating to a local page and callingPage.captureScreenshotfleet-browser syncwithbunfound on PATH and no~/.bunin the sandbox home✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.