Skip to content

First-Run Image Pull (minutes → seconds), Serve Background+SSE, Gemini Agent, Patchright Chromium Extensions (CapSolver pinned), Docker Disk Reclaim, Login Cookie Freshness, Claude Code 2.1.158#37

Merged
DimmKirr merged 1 commit into
mainfrom
feature/wip
Jun 2, 2026

Conversation

@DimmKirr
Copy link
Copy Markdown
Owner

@DimmKirr DimmKirr commented May 19, 2026

Changes

  • feat(cmd): cell <agent> default acquisition flips from "build whatever's needed" to "pull pure → pull impure → build pure → build impure" — fresh installs no longer wait through a multi-minute docker build whose output the orchestrator would discard on the next step; --impure opts back into the legacy Dockerfile path, --debian is a deprecated alias kept for one release, --pure is a silent no-op for back-compat
  • feat(cmd): cell build defaults to the pure (nix2container) path; same flag set as agents (--impure, --debian, --pure) plus --update for flake refresh and --no-generate to skip build-context regen
  • feat(cmd): cell build --stack <name> and DEVCELL_STACK=<name> env override [cell].stack for a single build — precedence flag > env > TOML > "base" — no more editing .devcell.toml to test a different stack image
  • feat(cmd): new cell build df — read-only Docker disk-usage table ranked by reclaimable bytes; rows tag pinned-by-running-container vs reclaimable, surface per-row reclaim commands as hints, never execute them; --top/--all/--kind/--json for narrowing and scripting
  • feat(cmd): new cell build prune — composes an ordered cleanup plan for the current host (GOOS), docker vs nix path, and rootless-aware on Linux; default sequence is conservative, --force opts into nuclear cleanup (Docker Desktop wipe / qcow nuke / aggressive nix GC); confirms before executing
  • feat(cmd): new cell gemini agent alongside claude/codex/opencode — same flag-stripping shell, same per-agent config layout; new --nix-daemon flag (cross-agent) keeps the in-container nix-daemon running so users can nix profile install ad-hoc fixes from a live cell
  • feat(cmd): cell login flushes the Chrome Cookies sqlite WAL between phase 1 (interactive login) and phase 2 (CDP extraction) so phase 2 reads a consistent snapshot, warns the user when the Cookies mtime is older than the session start ("did you actually log in?"), and SIGTERMs mcp-server-patchright in every running cell sharing the same cell-home bind mount so Claude/etc. pick up the fresh storage-state.json on the next browser tool call — no more stale-cookie surprises after relog
  • feat(cmd): cell login writes storage state to $HOME/.playwright/storage-state.json (DIMM-208 layout) and pre-creates the parent dir so first-run can't race
  • feat(cmd): root-cmd persistent pre-run loads [cell].stack/modules/per_session_image into runner globals BEFORE any subcommand's RunE — cell build now tags images with the project's actual stack instead of always devcell-user:base-pure
  • feat(serve): cell serve POST /v1/responses accepts "background": true → returns 202 + job stub; clients poll GET /v1/responses/{id} until status is terminal (completed/failed/cancelled); in-memory JobStore tracks lifecycle and exposes a CancelFunc per job for future POST /v1/responses/{id}/cancel
  • feat(serve): cell serve POST /v1/responses accepts "stream": true for the claude agent and returns Server-Sent Events with token-level deltas (opencode still buffers); combining stream + background returns 400 with unsupported_combination — clients pick one mode
  • feat(serve): new GET /v1/responses/{id} handler — same ResponsesObject shape whether the underlying job is still in-progress (empty Output) or terminal (fully populated, identical to a sync POST response)
  • feat(runner): new AcquireImage orchestrator walks the action sequence from DecideLaunchActionsPure, invokes one closure per effective action (PullPure/PullImpure/BuildPure/BuildImpure), stops at first success, and surfaces errors.Join-ed chain on total failure — every fallback attempted is visible to the user, no silent reverts
  • feat(runner): DecideLaunchActionsPure returns an ordered sequence (replaces the singular DecideLaunchActionPure) and models fallback as data (ActionPullPure/ActionPullImpure/ActionBuildPure/ActionBuildImpure/ActionUseLocal/ActionDryRun) so the launcher's decision logic is unit-testable without invoking docker or nix
  • feat(runner): new PullAndTagImpure dual-tags an impure pull as both UserImageTag() and UserImageTagPure() — a subsequent default-path launch finds the image locally without retrying the pull chain
  • feat(runner): new UserImageTagPure() (devcell-user:<stack>-pure), StackImageTagPure(stack), StackImageTagImpure(stack) registry tags; deprecated StackImageTagDebian aliases impure for one release; PickImageTag(impure bool) is the single seam every runtime caller uses to choose a local image variant
  • feat(runner): new PreflightNixBuilder (+ PreflightNixBuilderFromProbe testable seam) extracted from the BuildImagePure body — callers probe host nix usability before deciding to attempt a pure build; pull-only cold starts on Mac no longer trigger the linux-builder gate
  • feat(runner): new pure-build path (pure_build.go) invokes nix2container's copyToDockerDaemon via skopeo's nix: transport; supports both local-flake (path:) and remote-flake (github:) refs; tees progress to a heartbeat goroutine; no docker-build fallback inside the pure path (failures surface, do not silently revert)
  • feat(runner): new pure-nixhome-resolver — resolves a local path: flake from the project tree, falls back to the upstream github:DimmKirr/devcell?dir=nixhome flake when no local nixhome exists (mirrors scaffold's Dockerfile FROM-image fallback)
  • feat(runner): new disk-space introspection (df.go + df_collect.go) — docker system df -v JSON parsed into ranked entries, image dedup via unique-blob accounting, pin-count via running-container join; emits stable JSON schema for scripting
  • feat(runner): new prune planner — per-(GOOS × Force × Pure × Rootless) command plan composed in pure builders, executed by RunDockerPrune/RunNixPrune after confirmation; rootless detected via docker info at the call site
  • feat(runner): new build-debug helper — cell build --debug tees output to a file and a ring buffer for post-mortem artifact capture
  • feat(runner): new registry probe — best-effort ECR/GHCR reachability check that powers the pull-pure → pull-impure decision (offline hosts skip pulls and go straight to local build)
  • feat(runner): warm-boot path no longer pays for mise reshim + recursive chowns over the persistent ~/.local/share/mise (~17k entries) when ~/.tool-versions is unchanged — drops macOS launches from ~57s to ~10s
  • feat(runner): kick-mcp helper SIGTERMs mcp-server-patchright in every running cell that shares the current cell-home bind mount; best-effort, never errors out the calling command
  • feat(runner): registry pull falls back to docker-build when ECR is unreachable, and first-run no longer wastes minutes on a docker build the next step ignores
  • feat(scaffold): SyncNixhome runs git init + git add . inside the synced copy so nix's flake source filter (which uses git ls-files) sees every file — fixes the case where dest lives inside a gitignored dir (.devcell/) and nix would otherwise see an empty tree
  • feat(scaffold): scaffolding the first-run .devcell.toml/.devcell/ no longer eagerly invokes buildImageWithSpinner — image acquisition is owned by the unified orchestrator below it
  • feat(nix): new nixhome/packages/image.nix (911 lines) — nix2container OCI image builder; content-addressed; bakes home-manager activationPackage into the image filesystem without running activation at runtime; per-stack outputs keyed on HOST system (not target) so IFD helpers run on the right arch; optional includeNix (default on) keeps the runtime-installable nix profile capability
  • feat(nix): nix2container flake input added; skopeo-nix2container overlay forces unstable's skopeo 1.21+ for the nix: transport while keeping nix2container-bin on the main nixpkgs pin (avoids a 0-byte closure-graph.json regression from a wholesale pkgs swap)
  • feat(nix): flake lib.mkHome now threads self into extraSpecialArgs so downstream modules can resolve the flake source path for content-addressed staging
  • feat(nix): new nixhome/entrypoint.sh for the pure-image runtime — reads /etc/devcell/metadata.json for stack/module/package counts and prefers runner-injected DEVCELL_BUILD_DATE/DEVCELL_BUILD_REV (sourced from OCI labels) so post-2026-05-16 cells display real provenance instead of placeholders; pre-2026 image fall-through preserved
  • feat(nix): base.nix exports NIX_LD pointing at the real nix glibc loader (ld-linux-aarch64.so.1 or ld-linux-x86-64.so.2 per arch) so non-nix binaries (mise-installed node/go, pip wheels, downloaded gpg keychains) load against a consistent loader on both pure and impure images
  • feat(nix): base.nix skips the impure stageEntrypoints rsync activation when /etc/devcell/.image-built-with-nix2container marker is present — pure images already have fragments staged at build time
  • feat(nix): new entrypoint fragment 04-nix-daemon.sh boots a per-cell nix-daemon when the agent flag --nix-daemon is passed, enabling runtime nix profile install / home-manager switch for ad-hoc fixes
  • feat(nix): new entrypoint fragment 22-chromium-singleton.sh sweeps stale SingletonLock/SingletonCookie/SingletonSocket on container boot — handles cross-container-restart cleanup that the mid-session DIMM-222 sweep can't catch
  • feat(nix): new entrypoint fragment 30-gemini.sh merges nix-staged MCP servers into ~/.gemini/settings.json (mirrors the claude/codex/opencode pattern), with backup-before-merge, corrupt-file recovery, and stale-server eviction by /opt/devcell/ prefix
  • feat(nix): user-writable nix profile ($HOME/.local/state/nix/profiles/profile) replaces the read-only /opt/devcell/.nix-profile symlink — nix profile add nixpkgs#htop now succeeds from inside the cell; user-profile PATH precedes the stack profile so user installs override defaults while stack tools remain reachable
  • feat(nix): LD_LIBRARY_PATH (35 KB, 546 paths, fragile under ARG_MAX) replaced by a merged /opt/devcell/.nix-ld-libs/ directory pointed to by NIX_LD_LIBRARY_PATH and consulted only by the nix-ld shim — nix-built tools keep using RPATH untouched (closes the nixpkgs#327854 RPATH-override class of bugs)
  • feat(scraping): new devcell.scraping.extensions.<name> registry — attrsOf submodule keyed by short name, each with url, hash (SRI), and enable; gives any cell a one-line opt-in to a third-party chromium extension without redefining the URL/hash pin
  • feat(scraping): patchright MCP and the interactive chromium wrapper now fold enabled extensions into --load-extension=<csv> and --disable-extensions-except=<csv>; both flags omitted when no extension is enabled — no empty-arg surprises for Chromium's flag parser
  • feat(scraping): per-extension derivation fetches the upstream zip at image build time via pkgs.fetchurl and unpacks via runCommandLocal — no zip vendored in the repo, store path content-addressed by SRI sha256 so two clean builds of the same commit produce byte-identical extension directories; manifest-at-root guard fails the build loudly with a diagnostic if upstream ever wraps everything in an inner dir
  • feat(scraping): CapSolver v.1.17.0 registered with the official GitHub release URL + sha256-luDxNlNoLYHWG3EHuqXTAxAzqkYnDpZt7eFFbqwRQT8=, enable = mkDefault false — users opt in with devcell.scraping.extensions.capsolver.enable = true and configure the API key via the popup on first run (persistent $HOME/.chrome profile keeps the setting)
  • feat(scraping): nixhome/modules/scraping/default.nix reworked end-to-end — patchright config / wrapper / interactive chromium wrapper share one extensionArgs source of truth
  • feat(media): new media.nix module ships Plex MCP (plex-mcp-server) — Plex library/playlist/session/admin control over MCP
  • feat(graphics): inkscape-mcp gains INKS_WORKSPACE = "./" so the agent reads and writes SVGs anywhere in the project tree (instead of being trapped in the default "inkspace" sandbox); potrace + mkbitmap added for bitmap → SVG tracing
  • feat(llm): new nixhome/modules/llm/gemini.nix mirrors claude/codex/opencode (config staging, MCP entry, runtime settings.json merge); default.nix imports it
  • feat(cfg): new [cell].mac_address TOML key — pinned NIC MAC for stable bot-protection identity across container restarts; honored on docker user-defined bridge (default), ignored on --network=host, vagrant wiring still TODO
  • feat(cfg): new [ports].publish_ip TOML key — host interface docker publishes container ports on; default "0.0.0.0" (LAN-reachable regardless of dockerd bind default), override to "127.0.0.1" for loopback-only or a specific NIC IP; applies to VNC, RDP, and every forward entry
  • feat(cfg): [[volumes]] entries now resolve ~ and absolute host paths uniformly so cross-cell shared mounts (/Users/…/.devcell/PTC etc.) behave the same on macOS and Linux
  • feat(ci): new pure-build GHA matrix job builds + pushes per-arch per-stack pure images (base + ultimate × amd64 + arm64) to GHCR then mirrors to ECR Public; cache-nix-action keeps /nix/store warm between runs with a 5 GB GC cap; pure-manifest job stitches per-arch tags into multi-arch manifests
  • feat(ci): impure buildx bake adds oci-mediatypes=true so dev-build images use OCI media types and play nicer with skopeo / nix2container loaders downstream
  • chore(nix): nixpkgs-edge flake lock bumped from 591688422… (2026-04-24) to a4421cecfb7c… (2026-06-02) — every cell claude session picks up claude-code 2.1.118 → 2.1.158 after the next image rebuild, 40 patch versions of upstream Claude Code fixes, features, and MCP improvements
  • chore(nix): nix2container input added with its nixpkgs follow pinned to the main input — no separate nixpkgs evaluation
  • chore(infra): Taskfile.yml +326 lines — image:pure:* tasks (build/push/mirror per-stack), image:mirror skopeo wrapper, nix:validate parse + attr-check, refreshed image: umbrella targets
  • chore(infra): Dockerfile +52 lines — minor adjustments aligning with the new fragment layout and the pure-default flip
  • chore(infra): .nixhome-tmp/ staging tree created from a rename of images/entrypoint.sh plus a mirror of nixhome/ — transitional layout that lets the impure docker context keep its own bind during the pure-default flip
  • chore(deps): .node-version pinned at repo root; go.mod / go.sum updated for the new orchestration packages (heartbeat, df parser, prune planner)
  • docs(scraping): extensions.nix header documents distribution policy (no vendored zips, no md5, no floating "latest" URLs) and bump workflow (nix-prefetch-urlnix hash converttask nix:validate) — future bumps don't need to rediscover the playbook
  • test(runner): acquire_image_test.go covers sequence walk, fallback fallthrough, build-failure surfacing, and joined chain errors; preflight_test.go covers Linux-OK, env-skip, Darwin-no-builder error contract
  • test(runner): launch_decision_test.go rewritten as 6 table-driven cases covering every (HasNix × ExplicitBuild × LocalExists × DryRun) branch of the new sequence decision
  • test(runner): new df_test.go (363 lines — ranks, dedup, JSON schema, kind filtering), prune_test.go (758 lines — every (GOOS, force, pure, rootless) combo), humanbytes_test.go, build_debug_test.go, pure_build_test.go, pure_build_heartbeat_test.go, pure_nixhome_resolver_test.go, registry_test.go, tag_variants_test.go
  • test(serve): responses_background_test.go (407 lines) — background job lifecycle, polling, cancellation, error states; responses_test.go updated for the new handler signature with *JobStore
  • test(cmd): new build_df_test.go, build_stack_override_test.go, chrome_cookiedb_test.go, chrome_kick_mcp_test.go, chrome_paths_test.go, gemini_test.go, root_persistent_prerun_test.go, strip_test.go — cover new build subcommands, gemini cmd, kick-mcp helper, cookie-db flush, root persistent pre-run reading project config
  • test(integration): new test/ package suite — gui_test.go, image_nix_provenance_test.go, image_test.go, managed_mcp_staging_test.go, mise_node_install_test.go, mise_test.go (586 lines), playwright_singleton_lock_test.go, stealth_test.go, sudo_integration_test.go, sudo_test.go, usr_bin_env_test.go, variant_test.go — covers image provenance via /etc/devcell/.image-built-with-nix2container marker, mise reshim short-circuit, playwright singleton recovery, stealth init.js regressions, sudo rights, /usr/bin/env shim, pure-vs-impure runtime variant equivalence
  • test(rdp): resolution_probe_test.go (207 lines) — RDP resolution probing for dynamic screen sizing
  • test(cfg): cfg_test.go (157 lines) — coverage for new mac_address, publish_ip, stack-override resolution

…i Agent, Patchright Chromium Extensions (CapSolver pinned), Docker Disk Reclaim, Login Cookie Freshness, Claude Code 2.1.158

- feat(cmd): `cell <agent>` default acquisition flips from "build whatever's needed" to "pull pure → pull impure → build pure → build impure" — fresh installs no longer wait through a multi-minute docker build whose output the orchestrator would discard on the next step; `--impure` opts back into the legacy Dockerfile path, `--debian` is a deprecated alias kept for one release, `--pure` is a silent no-op for back-compat
- feat(cmd): `cell build` defaults to the pure (nix2container) path; same flag set as agents (`--impure`, `--debian`, `--pure`) plus `--update` for flake refresh and `--no-generate` to skip build-context regen
- feat(cmd): `cell build --stack <name>` and `DEVCELL_STACK=<name>` env override `[cell].stack` for a single build — precedence flag > env > TOML > "base" — no more editing `.devcell.toml` to test a different stack image
- feat(cmd): new `cell build df` — read-only Docker disk-usage table ranked by reclaimable bytes; rows tag pinned-by-running-container vs reclaimable, surface per-row reclaim commands as hints, never execute them; `--top`/`--all`/`--kind`/`--json` for narrowing and scripting
- feat(cmd): new `cell build prune` — composes an ordered cleanup plan for the current host (GOOS), docker vs nix path, and rootless-aware on Linux; default sequence is conservative, `--force` opts into nuclear cleanup (Docker Desktop wipe / qcow nuke / aggressive nix GC); confirms before executing
- feat(cmd): new `cell gemini` agent alongside claude/codex/opencode — same flag-stripping shell, same per-agent config layout; new `--nix-daemon` flag (cross-agent) keeps the in-container nix-daemon running so users can `nix profile install` ad-hoc fixes from a live cell
- feat(cmd): `cell login` flushes the Chrome Cookies sqlite WAL between phase 1 (interactive login) and phase 2 (CDP extraction) so phase 2 reads a consistent snapshot, warns the user when the Cookies mtime is older than the session start ("did you actually log in?"), and SIGTERMs `mcp-server-patchright` in every running cell sharing the same cell-home bind mount so Claude/etc. pick up the fresh storage-state.json on the next browser tool call — no more stale-cookie surprises after relog
- feat(cmd): `cell login` writes storage state to `$HOME/.playwright/storage-state.json` (DIMM-208 layout) and pre-creates the parent dir so first-run can't race
- feat(cmd): root-cmd persistent pre-run loads `[cell].stack`/`modules`/`per_session_image` into runner globals BEFORE any subcommand's RunE — `cell build` now tags images with the project's actual stack instead of always `devcell-user:base-pure`
- feat(serve): `cell serve` POST `/v1/responses` accepts `"background": true` → returns 202 + job stub; clients poll GET `/v1/responses/{id}` until status is terminal (`completed`/`failed`/`cancelled`); in-memory `JobStore` tracks lifecycle and exposes a `CancelFunc` per job for future POST `/v1/responses/{id}/cancel`
- feat(serve): `cell serve` POST `/v1/responses` accepts `"stream": true` for the claude agent and returns Server-Sent Events with token-level deltas (opencode still buffers); combining `stream + background` returns 400 with `unsupported_combination` — clients pick one mode
- feat(serve): new GET `/v1/responses/{id}` handler — same `ResponsesObject` shape whether the underlying job is still in-progress (empty Output) or terminal (fully populated, identical to a sync POST response)
- feat(runner): new `AcquireImage` orchestrator walks the action sequence from `DecideLaunchActionsPure`, invokes one closure per effective action (`PullPure`/`PullImpure`/`BuildPure`/`BuildImpure`), stops at first success, and surfaces `errors.Join`-ed chain on total failure — every fallback attempted is visible to the user, no silent reverts
- feat(runner): `DecideLaunchActionsPure` returns an ordered sequence (replaces the singular `DecideLaunchActionPure`) and models fallback as data (`ActionPullPure`/`ActionPullImpure`/`ActionBuildPure`/`ActionBuildImpure`/`ActionUseLocal`/`ActionDryRun`) so the launcher's decision logic is unit-testable without invoking docker or nix
- feat(runner): new `PullAndTagImpure` dual-tags an impure pull as both `UserImageTag()` and `UserImageTagPure()` — a subsequent default-path launch finds the image locally without retrying the pull chain
- feat(runner): new `UserImageTagPure()` (`devcell-user:<stack>-pure`), `StackImageTagPure(stack)`, `StackImageTagImpure(stack)` registry tags; deprecated `StackImageTagDebian` aliases impure for one release; `PickImageTag(impure bool)` is the single seam every runtime caller uses to choose a local image variant
- feat(runner): new `PreflightNixBuilder` (+ `PreflightNixBuilderFromProbe` testable seam) extracted from the BuildImagePure body — callers probe host nix usability before deciding to attempt a pure build; pull-only cold starts on Mac no longer trigger the linux-builder gate
- feat(runner): new pure-build path (`pure_build.go`) invokes `nix2container`'s `copyToDockerDaemon` via skopeo's `nix:` transport; supports both local-flake (`path:`) and remote-flake (`github:`) refs; tees progress to a heartbeat goroutine; no docker-build fallback inside the pure path (failures surface, do not silently revert)
- feat(runner): new pure-nixhome-resolver — resolves a local `path:` flake from the project tree, falls back to the upstream `github:DimmKirr/devcell?dir=nixhome` flake when no local nixhome exists (mirrors scaffold's Dockerfile FROM-image fallback)
- feat(runner): new disk-space introspection (`df.go` + `df_collect.go`) — `docker system df -v` JSON parsed into ranked entries, image dedup via unique-blob accounting, pin-count via running-container join; emits stable JSON schema for scripting
- feat(runner): new prune planner — per-(GOOS × Force × Pure × Rootless) command plan composed in pure builders, executed by `RunDockerPrune`/`RunNixPrune` after confirmation; rootless detected via `docker info` at the call site
- feat(runner): new build-debug helper — `cell build --debug` tees output to a file and a ring buffer for post-mortem artifact capture
- feat(runner): new registry probe — best-effort ECR/GHCR reachability check that powers the pull-pure → pull-impure decision (offline hosts skip pulls and go straight to local build)
- feat(runner): warm-boot path no longer pays for `mise reshim` + recursive chowns over the persistent `~/.local/share/mise` (~17k entries) when `~/.tool-versions` is unchanged — drops macOS launches from ~57s to ~10s
- feat(runner): kick-mcp helper SIGTERMs `mcp-server-patchright` in every running cell that shares the current cell-home bind mount; best-effort, never errors out the calling command
- feat(runner): registry pull falls back to docker-build when ECR is unreachable, and first-run no longer wastes minutes on a docker build the next step ignores
- feat(scaffold): `SyncNixhome` runs `git init` + `git add .` inside the synced copy so nix's flake source filter (which uses git ls-files) sees every file — fixes the case where dest lives inside a gitignored dir (`.devcell/`) and nix would otherwise see an empty tree
- feat(scaffold): scaffolding the first-run `.devcell.toml`/`.devcell/` no longer eagerly invokes `buildImageWithSpinner` — image acquisition is owned by the unified orchestrator below it
- feat(nix): new `nixhome/packages/image.nix` (911 lines) — nix2container OCI image builder; content-addressed; bakes home-manager `activationPackage` into the image filesystem without running activation at runtime; per-stack outputs keyed on HOST system (not target) so IFD helpers run on the right arch; optional `includeNix` (default on) keeps the runtime-installable nix profile capability
- feat(nix): `nix2container` flake input added; `skopeo-nix2container` overlay forces unstable's skopeo 1.21+ for the `nix:` transport while keeping `nix2container-bin` on the main nixpkgs pin (avoids a 0-byte closure-graph.json regression from a wholesale pkgs swap)
- feat(nix): flake `lib.mkHome` now threads `self` into `extraSpecialArgs` so downstream modules can resolve the flake source path for content-addressed staging
- feat(nix): new `nixhome/entrypoint.sh` for the pure-image runtime — reads `/etc/devcell/metadata.json` for stack/module/package counts and prefers runner-injected `DEVCELL_BUILD_DATE`/`DEVCELL_BUILD_REV` (sourced from OCI labels) so post-2026-05-16 cells display real provenance instead of placeholders; pre-2026 image fall-through preserved
- feat(nix): `base.nix` exports `NIX_LD` pointing at the real nix glibc loader (`ld-linux-aarch64.so.1` or `ld-linux-x86-64.so.2` per arch) so non-nix binaries (mise-installed node/go, pip wheels, downloaded gpg keychains) load against a consistent loader on both pure and impure images
- feat(nix): `base.nix` skips the impure `stageEntrypoints` rsync activation when `/etc/devcell/.image-built-with-nix2container` marker is present — pure images already have fragments staged at build time
- feat(nix): new entrypoint fragment `04-nix-daemon.sh` boots a per-cell `nix-daemon` when the agent flag `--nix-daemon` is passed, enabling runtime `nix profile install` / `home-manager switch` for ad-hoc fixes
- feat(nix): new entrypoint fragment `22-chromium-singleton.sh` sweeps stale `SingletonLock`/`SingletonCookie`/`SingletonSocket` on container boot — handles cross-container-restart cleanup that the mid-session DIMM-222 sweep can't catch
- feat(nix): new entrypoint fragment `30-gemini.sh` merges nix-staged MCP servers into `~/.gemini/settings.json` (mirrors the claude/codex/opencode pattern), with backup-before-merge, corrupt-file recovery, and stale-server eviction by `/opt/devcell/` prefix
- feat(nix): user-writable nix profile (`$HOME/.local/state/nix/profiles/profile`) replaces the read-only `/opt/devcell/.nix-profile` symlink — `nix profile add nixpkgs#htop` now succeeds from inside the cell; user-profile PATH precedes the stack profile so user installs override defaults while stack tools remain reachable
- feat(nix): `LD_LIBRARY_PATH` (35 KB, 546 paths, fragile under ARG_MAX) replaced by a merged `/opt/devcell/.nix-ld-libs/` directory pointed to by `NIX_LD_LIBRARY_PATH` and consulted only by the nix-ld shim — nix-built tools keep using RPATH untouched (closes the nixpkgs#327854 RPATH-override class of bugs)
- feat(scraping): new `devcell.scraping.extensions.<name>` registry — attrsOf submodule keyed by short name, each with `url`, `hash` (SRI), and `enable`; gives any cell a one-line opt-in to a third-party chromium extension without redefining the URL/hash pin
- feat(scraping): patchright MCP and the interactive chromium wrapper now fold enabled extensions into `--load-extension=<csv>` and `--disable-extensions-except=<csv>`; both flags omitted when no extension is enabled — no empty-arg surprises for Chromium's flag parser
- feat(scraping): per-extension derivation fetches the upstream zip at image build time via `pkgs.fetchurl` and unpacks via `runCommandLocal` — no zip vendored in the repo, store path content-addressed by SRI sha256 so two clean builds of the same commit produce byte-identical extension directories; manifest-at-root guard fails the build loudly with a diagnostic if upstream ever wraps everything in an inner dir
- feat(scraping): CapSolver v.1.17.0 registered with the official GitHub release URL + `sha256-luDxNlNoLYHWG3EHuqXTAxAzqkYnDpZt7eFFbqwRQT8=`, `enable = mkDefault false` — users opt in with `devcell.scraping.extensions.capsolver.enable = true` and configure the API key via the popup on first run (persistent `$HOME/.chrome` profile keeps the setting)
- feat(scraping): `nixhome/modules/scraping/default.nix` reworked end-to-end — patchright config / wrapper / interactive chromium wrapper share one `extensionArgs` source of truth
- feat(media): new `media.nix` module ships Plex MCP (`plex-mcp-server`) — Plex library/playlist/session/admin control over MCP
- feat(graphics): inkscape-mcp gains `INKS_WORKSPACE = "./"` so the agent reads and writes SVGs anywhere in the project tree (instead of being trapped in the default "inkspace" sandbox); `potrace` + `mkbitmap` added for bitmap → SVG tracing
- feat(llm): new `nixhome/modules/llm/gemini.nix` mirrors claude/codex/opencode (config staging, MCP entry, runtime settings.json merge); `default.nix` imports it
- feat(cfg): new `[cell].mac_address` TOML key — pinned NIC MAC for stable bot-protection identity across container restarts; honored on docker user-defined bridge (default), ignored on `--network=host`, vagrant wiring still TODO
- feat(cfg): new `[ports].publish_ip` TOML key — host interface docker publishes container ports on; default `"0.0.0.0"` (LAN-reachable regardless of dockerd bind default), override to `"127.0.0.1"` for loopback-only or a specific NIC IP; applies to VNC, RDP, and every forward entry
- feat(cfg): `[[volumes]]` entries now resolve `~` and absolute host paths uniformly so cross-cell shared mounts (`/Users/…/.devcell/PTC` etc.) behave the same on macOS and Linux
- feat(ci): new `pure-build` GHA matrix job builds + pushes per-arch per-stack pure images (base + ultimate × amd64 + arm64) to GHCR then mirrors to ECR Public; `cache-nix-action` keeps `/nix/store` warm between runs with a 5 GB GC cap; `pure-manifest` job stitches per-arch tags into multi-arch manifests
- feat(ci): impure buildx bake adds `oci-mediatypes=true` so dev-build images use OCI media types and play nicer with skopeo / nix2container loaders downstream
- chore(nix): `nixpkgs-edge` flake lock bumped from `591688422…` (2026-04-24) to `a4421cecfb7c…` (2026-06-02) — every `cell claude` session picks up claude-code 2.1.118 → 2.1.158 after the next image rebuild, 40 patch versions of upstream Claude Code fixes, features, and MCP improvements
- chore(nix): `nix2container` input added with its `nixpkgs` follow pinned to the main input — no separate nixpkgs evaluation
- chore(infra): `Taskfile.yml` +326 lines — `image:pure:*` tasks (build/push/mirror per-stack), `image:mirror` skopeo wrapper, `nix:validate` parse + attr-check, refreshed `image:` umbrella targets
- chore(infra): `Dockerfile` +52 lines — minor adjustments aligning with the new fragment layout and the pure-default flip
- chore(infra): `.nixhome-tmp/` staging tree created from a rename of `images/entrypoint.sh` plus a mirror of `nixhome/` — transitional layout that lets the impure docker context keep its own bind during the pure-default flip
- chore(deps): `.node-version` pinned at repo root; `go.mod` / `go.sum` updated for the new orchestration packages (heartbeat, df parser, prune planner)
- docs(scraping): `extensions.nix` header documents distribution policy (no vendored zips, no md5, no floating "latest" URLs) and bump workflow (`nix-prefetch-url` → `nix hash convert` → `task nix:validate`) — future bumps don't need to rediscover the playbook
- test(runner): `acquire_image_test.go` covers sequence walk, fallback fallthrough, build-failure surfacing, and joined chain errors; `preflight_test.go` covers Linux-OK, env-skip, Darwin-no-builder error contract
- test(runner): `launch_decision_test.go` rewritten as 6 table-driven cases covering every (HasNix × ExplicitBuild × LocalExists × DryRun) branch of the new sequence decision
- test(runner): new `df_test.go` (363 lines — ranks, dedup, JSON schema, kind filtering), `prune_test.go` (758 lines — every (GOOS, force, pure, rootless) combo), `humanbytes_test.go`, `build_debug_test.go`, `pure_build_test.go`, `pure_build_heartbeat_test.go`, `pure_nixhome_resolver_test.go`, `registry_test.go`, `tag_variants_test.go`
- test(serve): `responses_background_test.go` (407 lines) — background job lifecycle, polling, cancellation, error states; `responses_test.go` updated for the new handler signature with `*JobStore`
- test(cmd): new `build_df_test.go`, `build_stack_override_test.go`, `chrome_cookiedb_test.go`, `chrome_kick_mcp_test.go`, `chrome_paths_test.go`, `gemini_test.go`, `root_persistent_prerun_test.go`, `strip_test.go` — cover new build subcommands, gemini cmd, kick-mcp helper, cookie-db flush, root persistent pre-run reading project config
- test(integration): new `test/` package suite — `gui_test.go`, `image_nix_provenance_test.go`, `image_test.go`, `managed_mcp_staging_test.go`, `mise_node_install_test.go`, `mise_test.go` (586 lines), `playwright_singleton_lock_test.go`, `stealth_test.go`, `sudo_integration_test.go`, `sudo_test.go`, `usr_bin_env_test.go`, `variant_test.go` — covers image provenance via `/etc/devcell/.image-built-with-nix2container` marker, mise reshim short-circuit, playwright singleton recovery, stealth init.js regressions, sudo rights, `/usr/bin/env` shim, pure-vs-impure runtime variant equivalence
- test(rdp): `resolution_probe_test.go` (207 lines) — RDP resolution probing for dynamic screen sizing
- test(cfg): `cfg_test.go` (157 lines) — coverage for new `mac_address`, `publish_ip`, stack-override resolution
@DimmKirr DimmKirr changed the title nix profile add works inside cells, browser state shares one profile across cell login + chromium + MCP, and RDP-into-cell works on first launch First-Run Image Pull (minutes → seconds), Serve Background+SSE, Gemini Agent, Patchright Chromium Extensions (CapSolver pinned), Docker Disk Reclaim, Login Cookie Freshness, Claude Code 2.1.158 Jun 2, 2026
@DimmKirr DimmKirr merged commit f6a2c02 into main Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant