Skip to content

Commit f4c46d8

Browse files
committed
fix: resolve project SSH through shared Docker network
1 parent d220d01 commit f4c46d8

1 file changed

Lines changed: 42 additions & 7 deletions

File tree

packages/api/src/services/terminal-sessions.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import { Effect, Either } from "effect"
1010
import { Buffer } from "node:buffer"
1111
import { spawn } from "node:child_process"
1212
import { randomUUID } from "node:crypto"
13+
import { existsSync } from "node:fs"
1314
import type { IncomingMessage, Server as HttpServer } from "node:http"
15+
import os from "node:os"
1416
import type { Duplex } from "node:stream"
1517
import { WebSocket, WebSocketServer, type RawData } from "ws"
1618

@@ -140,7 +142,13 @@ type ContainerNetworkEntry = {
140142
readonly name: string
141143
}
142144

143-
const dockerGitApiContainerName = (): string => process.env["DOCKER_GIT_API_CONTAINER_NAME"]?.trim() || "docker-git-api"
145+
const dockerGitApiContainerName = (): string =>
146+
process.env["DOCKER_GIT_API_CONTAINER_NAME"]?.trim() || os.hostname().trim() || "docker-git-api"
147+
148+
const isContainerizedController = (): boolean => {
149+
const configuredName = process.env["DOCKER_GIT_API_CONTAINER_NAME"]?.trim()
150+
return (configuredName !== undefined && configuredName.length > 0) || existsSync("/.dockerenv")
151+
}
144152

145153
const parseContainerNetworkEntries = (output: string): ReadonlyArray<ContainerNetworkEntry> =>
146154
output
@@ -150,9 +158,23 @@ const parseContainerNetworkEntries = (output: string): ReadonlyArray<ContainerNe
150158
.map(([name, ipAddress]) => ({ name, ipAddress }))
151159

152160
const selectReachableProjectNetwork = (
161+
projectEntries: ReadonlyArray<ContainerNetworkEntry>,
162+
controllerEntries: ReadonlyArray<ContainerNetworkEntry>
163+
): ContainerNetworkEntry | null =>
164+
projectEntries.find((entry) =>
165+
entry.name !== "bridge" && controllerEntries.some((controllerEntry) => controllerEntry.name === entry.name)
166+
) ??
167+
projectEntries.find((entry) =>
168+
controllerEntries.some((controllerEntry) => controllerEntry.name === entry.name)
169+
) ??
170+
null
171+
172+
const selectFallbackProjectNetwork = (
153173
entries: ReadonlyArray<ContainerNetworkEntry>
154174
): ContainerNetworkEntry | null =>
155-
entries.find((entry) => entry.name !== "bridge") ?? entries[0] ?? null
175+
isContainerizedController()
176+
? entries.find((entry) => entry.name === "bridge") ?? entries[0] ?? null
177+
: null
156178

157179
const inspectContainerNetworks = (
158180
containerName: string
@@ -177,7 +199,7 @@ const connectContainerToNetwork = (
177199
containerName: string
178200
) =>
179201
networkName === "bridge"
180-
? Effect.void
202+
? Effect.succeed(true)
181203
: runCommandCapture(
182204
{
183205
cwd: process.cwd(),
@@ -187,23 +209,36 @@ const connectContainerToNetwork = (
187209
[0],
188210
(exitCode) => new CommandFailedError({ command: `docker network connect ${networkName}`, exitCode })
189211
).pipe(
190-
Effect.asVoid,
191-
Effect.orElseSucceed(() => void 0)
212+
Effect.as(true),
213+
Effect.orElseSucceed(() => false)
192214
)
193215

194216
const resolveControllerReachableProject = (
195217
projectItem: ProjectItem
196218
) =>
197219
Effect.gen(function*(_) {
220+
const controllerContainer = dockerGitApiContainerName()
198221
const networkEntries = yield* _(inspectContainerNetworks(projectItem.containerName).pipe(Effect.orElseSucceed(() => [])))
222+
const controllerNetworks = yield* _(inspectContainerNetworks(controllerContainer).pipe(Effect.orElseSucceed(() => [])))
223+
const alreadyReachable = selectReachableProjectNetwork(networkEntries, controllerNetworks)
224+
if (alreadyReachable !== null) {
225+
return {
226+
...projectItem,
227+
ipAddress: alreadyReachable.ipAddress
228+
}
229+
}
199230
yield* _(
200231
Effect.forEach(
201232
networkEntries.filter((entry) => entry.name !== "bridge"),
202-
(entry) => connectContainerToNetwork(entry.name, dockerGitApiContainerName()),
233+
(entry) => connectContainerToNetwork(entry.name, controllerContainer),
203234
{ discard: true }
204235
)
205236
)
206-
const preferredNetwork = selectReachableProjectNetwork(networkEntries)
237+
const refreshedControllerNetworks = yield* _(
238+
inspectContainerNetworks(controllerContainer).pipe(Effect.orElseSucceed(() => []))
239+
)
240+
const preferredNetwork = selectReachableProjectNetwork(networkEntries, refreshedControllerNetworks) ??
241+
selectFallbackProjectNetwork(networkEntries)
207242
if (preferredNetwork === null) {
208243
return projectItem
209244
}

0 commit comments

Comments
 (0)