|
1 | 1 | import { spawnSync } from 'node:child_process' |
2 | | -import { |
3 | | - DB_CONTAINER, |
4 | | - type Detection, |
5 | | - LOOPBACK_HOST, |
6 | | - type PortBinding, |
7 | | - publishedBinding, |
8 | | - targetsLocalPort, |
9 | | -} from './detect' |
| 2 | +import { DB_CONTAINER, type Detection, LOOPBACK_HOST } from './detect' |
10 | 3 | import { ensureDocker } from './docker' |
11 | 4 | import { generateSecret } from './env-files' |
12 | 5 | import { SetupError } from './errors' |
@@ -43,8 +36,6 @@ function dockerOutput(args: string[]): string | null { |
43 | 36 | interface ManagedContainer { |
44 | 37 | running: boolean |
45 | 38 | dsn: string |
46 | | - password: string |
47 | | - binding: PortBinding |
48 | 39 | } |
49 | 40 |
|
50 | 41 | /** |
@@ -72,16 +63,21 @@ function inspectManagedContainer(): ManagedContainer | null { |
72 | 63 | ?.slice('POSTGRES_PASSWORD='.length) |
73 | 64 | if (!password) return null |
74 | 65 |
|
75 | | - const binding = publishedBinding(DB_CONTAINER, '5432/tcp') |
76 | | - if (!binding) return null |
| 66 | + // `docker port` only reports a published port while the container runs; the |
| 67 | + // static config carries it either way. |
| 68 | + const hostPort = dockerOutput([ |
| 69 | + 'inspect', |
| 70 | + DB_CONTAINER, |
| 71 | + '--format', |
| 72 | + '{{(index .HostConfig.PortBindings "5432/tcp" 0).HostPort}}', |
| 73 | + ]) |
| 74 | + if (!hostPort) return null |
77 | 75 |
|
78 | 76 | return { |
79 | 77 | running: running === 'true', |
80 | 78 | // Read back from the container env verbatim, so it may be a password the |
81 | 79 | // user supplied for an existing volume — encode it like any other. |
82 | | - dsn: buildDsn(password, binding.hostPort), |
83 | | - password, |
84 | | - binding, |
| 80 | + dsn: buildDsn(password, hostPort), |
85 | 81 | } |
86 | 82 | } |
87 | 83 |
|
@@ -253,14 +249,6 @@ async function startManagedContainer(detection: Detection): Promise<string> { |
253 | 249 | const password = volumeInitialized() |
254 | 250 | ? await resolveExistingVolume() |
255 | 251 | : generateSecret().slice(0, 24) |
256 | | - return launchManagedContainer(password, hostPort) |
257 | | -} |
258 | | - |
259 | | -/** Runs the managed container on `LOOPBACK_HOST` and waits until it accepts the password. */ |
260 | | -async function launchManagedContainer( |
261 | | - password: string, |
262 | | - hostPort: string | number |
263 | | -): Promise<string> { |
264 | 252 | // A user-supplied password can contain @ : / # — raw interpolation would |
265 | 253 | // re-parse the DSN into a different host and fail a password that is correct. |
266 | 254 | const dsn = buildDsn(password, hostPort) |
@@ -321,41 +309,13 @@ async function launchManagedContainer( |
321 | 309 | return dsn |
322 | 310 | } |
323 | 311 |
|
324 | | -/** |
325 | | - * Offers to re-publish a managed container that an earlier release bound to |
326 | | - * every interface, keeping its password, port, and data volume. Returns the new |
327 | | - * DSN when `existingDsn` pointed at that container (or nothing was configured), |
328 | | - * so the env files move to the loopback address with it. |
329 | | - */ |
330 | | -async function rebindExposedContainer(existingDsn?: string): Promise<string | null> { |
331 | | - const container = inspectManagedContainer() |
332 | | - if (!container || container.binding.hostIp === LOOPBACK_HOST) return null |
333 | | - const { hostPort } = container.binding |
334 | | - const rebind = await p.confirm({ |
335 | | - message: `${DB_CONTAINER} publishes Postgres on every network interface (set up by an earlier sim-setup). Recreate it on ${LOOPBACK_HOST} only? Its data and password are kept.`, |
336 | | - initialValue: true, |
337 | | - }) |
338 | | - if (!rebind) { |
339 | | - p.log.warn(`${DB_CONTAINER} stays reachable from your network on :${hostPort}.`) |
340 | | - return null |
341 | | - } |
342 | | - docker(['rm', '-f', DB_CONTAINER]) |
343 | | - const dsn = await launchManagedContainer(container.password, hostPort) |
344 | | - return !existingDsn || targetsLocalPort(existingDsn, hostPort) ? dsn : null |
345 | | -} |
346 | | - |
347 | 312 | /** |
348 | 313 | * The mode-B database ladder: reuse a working DSN, offer (never silently adopt) |
349 | 314 | * a Postgres already on 5432, start/reuse the wizard-managed pgvector |
350 | 315 | * container, or take an external DSN. Adopting an existing database is always |
351 | 316 | * an explicit choice — migrations run against whatever is chosen here. |
352 | 317 | */ |
353 | 318 | export async function resolveDatabase(detection: Detection, existingDsn?: string): Promise<string> { |
354 | | - if (detection.dbContainer?.managed) { |
355 | | - const rebound = await rebindExposedContainer(existingDsn) |
356 | | - if (rebound) return rebound |
357 | | - } |
358 | | - |
359 | 319 | if (existingDsn && (await probeWithSpinner(existingDsn, 'Testing existing DATABASE_URL…'))) { |
360 | 320 | return existingDsn |
361 | 321 | } |
|
0 commit comments