Goal
When `mirrorstack module init` writes `docker-compose.yml` (currently it doesn't — `mirrorstack dev` does on first run), check whether the proposed pg host port (5433) is already in use on localhost. If so, scan upward for the first free port and bake THAT into the generated compose file + the `MS_LOCAL_DB_URL` default in any scaffolded artifacts that reference it.
Why this is a sibling of #24
Issue #24 ("pre-flight TCP probe") is a dev-time gate — fail fast on collision. This one is scaffold-time generation — pick a port that won't collide. Both should land but they have different scope: scaffold once, gate every run.
Sketch
```rust
fn pick_free_pg_port(start: u16) -> Result {
for port in start..start + 100 {
if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() {
return Ok(port);
}
}
Err(anyhow!("couldn't find a free port in [{start}, {}]", start + 100))
}
```
Then substitute the chosen port into `docker-compose.yml.tmpl`'s `"5433:5432"` mapping at scaffold render time. The current scaffold lives in `mirrorstack module init`'s template substitution; this adds a new `MS_PG_HOST_PORT` placeholder.
Acceptance
- Init in a workspace where 5433 is free → compose maps `"5433:5432"`
- Init while another module's dev is running on 5433 → compose maps `"5434:5432"` (next free)
- The generated module's `MS_LOCAL_DB_URL` (if we ever bake one into a `.env.example` etc.) matches the chosen port
Caveat
A free port at scaffold time may be taken at `mirrorstack dev` time. #24's pre-flight probe still applies. #28's `--auto-port` is the runtime-flexible answer; this one is just for nicer first-run defaults.
Estimate
~50 LoC + a test using `TcpListener` to occupy a port. ~30 minutes.
Goal
When `mirrorstack module init` writes `docker-compose.yml` (currently it doesn't — `mirrorstack dev` does on first run), check whether the proposed pg host port (5433) is already in use on localhost. If so, scan upward for the first free port and bake THAT into the generated compose file + the `MS_LOCAL_DB_URL` default in any scaffolded artifacts that reference it.
Why this is a sibling of #24
Issue #24 ("pre-flight TCP probe") is a dev-time gate — fail fast on collision. This one is scaffold-time generation — pick a port that won't collide. Both should land but they have different scope: scaffold once, gate every run.
Sketch
```rust
fn pick_free_pg_port(start: u16) -> Result {
for port in start..start + 100 {
if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() {
return Ok(port);
}
}
Err(anyhow!("couldn't find a free port in [{start}, {}]", start + 100))
}
```
Then substitute the chosen port into `docker-compose.yml.tmpl`'s `"5433:5432"` mapping at scaffold render time. The current scaffold lives in `mirrorstack module init`'s template substitution; this adds a new `MS_PG_HOST_PORT` placeholder.
Acceptance
Caveat
A free port at scaffold time may be taken at `mirrorstack dev` time. #24's pre-flight probe still applies. #28's `--auto-port` is the runtime-flexible answer; this one is just for nicer first-run defaults.
Estimate
~50 LoC + a test using `TcpListener` to occupy a port. ~30 minutes.