From dfe9f6e93e2c3bc6b19279728d69423ec37aa1b1 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 2 Jun 2026 14:25:38 +0300 Subject: [PATCH] fixup! :sparkles: Document and stabilise the parallel-workspace CLI; wire AI agents --- .serena/memories/devenv/core.md | 4 +-- docker/devenv/defaults.env | 13 ++++----- docs/technical-guide/developer/devenv.md | 29 +++++++++++++++++++- manage.sh | 35 ++++++++++++++++++------ 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/.serena/memories/devenv/core.md b/.serena/memories/devenv/core.md index 5ed52be06e..2017155628 100644 --- a/.serena/memories/devenv/core.md +++ b/.serena/memories/devenv/core.md @@ -37,8 +37,8 @@ Container-internal ports fixed; host side offset `10000·N`. | 3449/udp | 13449/udp | … | 3449/udp | HTTP/3 | | 4401 | 14401 | … | 4401 | MCP HTTP stream | | 4403 | 14403 | … | 4403 | MCP REPL | -| 14281 | 24281 | … | 14281 | Serena MCP | -| 14282 | 24282 | … | 24282 | Serena dashboard | +| 14181 | 24181 | … | 14281 | Serena MCP | +| 14182 | 24182 | … | 24282 | Serena dashboard | Everything else (frontend dev, backend API, exporter, storybook, REPLs, plugin dev, MCP inspector/WebSocket) is in-process or same-origin via Caddy/nginx. Infra publishes: mailer 1080, ldap 10389/10636 (singletons, not offset). diff --git a/docker/devenv/defaults.env b/docker/devenv/defaults.env index 2af19fbcbf..9dd4b6c042 100644 --- a/docker/devenv/defaults.env +++ b/docker/devenv/defaults.env @@ -6,11 +6,6 @@ # Backend runtime defaults that compose does not care about live in # backend/scripts/_env. -# Compose project name (replaces the -p flag). Set per instance; ws0 default -# here. Infra is launched under penpotdev-infra by manage.sh's infra-compose -# helper; ws1+ overlays override this to penpotdev-wsN. -COMPOSE_PROJECT_NAME=penpotdev-ws0 - # Container names and volume names. Volumes are pinned by explicit name # (rather than relying on COMPOSE_PROJECT_NAME prefixing) so the physical # volumes survive project renames without a data migration. ws0 reuses the @@ -51,9 +46,11 @@ PENPOT_PUBLIC_HTTP_PORT=3449 PENPOT_MCP_SERVER_PORT=4401 PENPOT_MCP_REPL_PORT=4403 -# Serena (agentic devenv). Internal ports are fixed by Serena itself. -SERENA_EXTERNAL_PORT=14281 -SERENA_DASHBOARD_EXTERNAL_PORT=14282 +# Serena (agentic devenv). These are the published host ports for ws0; ws1+ +# offset them by 10000*N. The container-internal ports (Serena MCP 14281, +# dashboard 24282) are fixed by Serena and mapped to these in compose. +SERENA_EXTERNAL_PORT=14181 +SERENA_DASHBOARD_EXTERNAL_PORT=14182 # Backend worker (scheduled + async tasks). ws0 only; per-instance overlays # for ws1+ override this to false. See mem:devenv/core. diff --git a/docs/technical-guide/developer/devenv.md b/docs/technical-guide/developer/devenv.md index a2837b960d..04e66a3630 100644 --- a/docs/technical-guide/developer/devenv.md +++ b/docs/technical-guide/developer/devenv.md @@ -99,7 +99,7 @@ Host ports are offset by `10000 × N`: |---|---|---|---| | Penpot UI (HTTPS) | `https://localhost:3449` | `https://localhost:13449` | `https://localhost:23449` | | MCP HTTP stream | `http://localhost:4401/mcp` | `http://localhost:14401/mcp` | `http://localhost:24401/mcp` | -| Serena MCP | `http://localhost:14281` | `http://localhost:24281` | `http://localhost:34281` | +| Serena MCP | `http://localhost:14181` | `http://localhost:24181` | `http://localhost:34181` | Container-internal ports stay fixed. Target a specific instance with `--ws N` on `attach-devenv`, `run-devenv-agentic`, `stop-devenv`, @@ -110,6 +110,33 @@ ws0-only and takes no workspace flag. `run-devenv-agentic` also accepts `--serena-context CTX` and `--git-user-name NAME` / `--git-user-email EMAIL` (see below). +### Per-instance configuration + +Instance configuration is layered, with two sources: + +- `docker/devenv/defaults.env` — the baseline, tracked in git. `ws0` uses it + directly, and it supplies every value that does not vary per instance + (database/object-storage settings, shared-infra hostnames, the worker + default, …). +- `docker/devenv/instances/wsN.env` — a generated overlay for each `ws1+` + instance. `manage.sh` writes it on every start/reconciler pass with only the + values that differ per instance: the offset host ports, the per-instance + container/volume names, and the Redis/public URIs. Compose loads it *after* + `defaults.env` (`--env-file defaults.env --env-file instances/wsN.env`), so + these override the baseline; everything else falls through to `defaults.env`. + +Two things to keep in mind: + +- **The overlays are auto-generated and gitignored** (`docker/devenv/instances/` + is in `.gitignore`). They are regenerated from scratch on each run, so hand + edits do not survive — change `defaults.env` (or the port/naming logic in + `manage.sh`) instead of editing an overlay. +- **The overlay lives in the control checkout, not in the workspace it + configures.** `wsN`'s overlay sits at `docker/devenv/instances/wsN.env` inside + the repo you run `manage.sh` from, while the workspace clone it configures + lives separately at `${PENPOT_WORKSPACES_DIR}/wsN/`. Compose is invoked from + that control checkout, so the relative `--env-file` path has to resolve there. + ### Git identity inside the container `run-devenv-agentic` wires a Git author identity into the container's diff --git a/manage.sh b/manage.sh index b29c266943..70987bc06e 100755 --- a/manage.sh +++ b/manage.sh @@ -34,16 +34,19 @@ export PENPOT_SOURCE_PATH="${PENPOT_SOURCE_PATH:-$PWD}" export PENPOT_WORKSPACES_DIR="${PENPOT_WORKSPACES_DIR:-$HOME/.penpot/penpot_workspaces}" # Port allocation for parallel instances. Each wsN reserves a stride-wide port -# block starting at N*stride; per-service base ports sit at well-known offsets -# inside ws0's block. Single source of truth for both write-instance-env (the -# values baked into the per-instance compose env file) and print-instance-info -# (the URLs printed at startup). +# block starting at N*stride; ws0 sits at offset 0, so a per-service base port +# IS ws0's published port. To keep a single source of truth, the bases are +# derived from the ws0 values sourced from defaults.env above rather than +# duplicated here -- this makes it impossible for ws0's compose substitution and +# the ws1+ overlay arithmetic to drift apart. `:?` aborts loudly if defaults.env +# is missing one. Consumed by write-instance-env (the values baked into the +# per-instance compose env file) and print-instance-info (the startup URLs). PENPOT_INSTANCE_PORT_STRIDE=10000 -PENPOT_PORT_BASE_PUBLIC=3449 -PENPOT_PORT_BASE_MCP=4401 -PENPOT_PORT_BASE_MCP_REPL=4403 -PENPOT_PORT_BASE_SERENA=14181 -PENPOT_PORT_BASE_SERENA_DASHBOARD=14182 +PENPOT_PORT_BASE_PUBLIC=${PENPOT_PUBLIC_HTTP_PORT:?missing in defaults.env} +PENPOT_PORT_BASE_MCP=${PENPOT_MCP_SERVER_PORT:?missing in defaults.env} +PENPOT_PORT_BASE_MCP_REPL=${PENPOT_MCP_REPL_PORT:?missing in defaults.env} +PENPOT_PORT_BASE_SERENA=${SERENA_EXTERNAL_PORT:?missing in defaults.env} +PENPOT_PORT_BASE_SERENA_DASHBOARD=${SERENA_DASHBOARD_EXTERNAL_PORT:?missing in defaults.env} # Per-instance values like PENPOT_REDIS_URI must live in each instance's env # file (not in this shell), because docker compose's --env-file mechanism @@ -285,6 +288,20 @@ function instance-port { # Generate (or refresh) the per-instance Compose env-file overlay. Idempotent; # safe to call on every reconciler pass. +# For ws0, this is a no-op since it uses the baseline defaults.env directly. +# +# The overlay is layered on top of defaults.env by instance-compose's +# `--env-file defaults.env --env-file instances/wsN.env` (compose applies the +# later file last), so this writes only the values that differ per instance -- +# ports, container/volume names, the Redis/public URIs, worker flag. Everything +# else falls through to defaults.env. +# +# Note the split: the overlay lives in the *control checkout* at +# docker/devenv/instances/wsN.env (gitignored, regenerated each pass), keyed by +# instance name -- NOT inside the wsN workspace clone it configures (that lives +# at $PENPOT_WORKSPACES_DIR/wsN and holds only source). It has to live here +# because compose runs from $PWD, so the relative --env-file path resolves +# against this repo. Hand edits do not survive a reconciler pass. function write-instance-env { local instance="$1" if [[ "$instance" == "ws0" ]]; then