(options: RunContainerOptions)
| 83 | * same way the boot did. |
| 84 | */ |
| 85 | export const runSelfhostContainer = async (options: RunContainerOptions): Promise<void> => { |
| 86 | const name = selfhostDockerContainerName(options.port); |
| 87 | const volume = selfhostDockerVolumeName(options.port); |
| 88 | const args = [ |
| 89 | "run", |
| 90 | "--detach", |
| 91 | "--name", |
| 92 | name, |
| 93 | "--network", |
| 94 | "host", |
| 95 | "--volume", |
| 96 | `${volume}:/data`, |
| 97 | "-e", |
| 98 | `PORT=${options.port}`, |
| 99 | "-e", |
| 100 | "BETTER_AUTH_SECRET=executor-selfhost-e2e-secret-0123456789", |
| 101 | "-e", |
| 102 | `EXECUTOR_BOOTSTRAP_ADMIN_EMAIL=${options.admin.email}`, |
| 103 | "-e", |
| 104 | `EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD=${options.admin.password}`, |
| 105 | "-e", |
| 106 | `EXECUTOR_WEB_BASE_URL=${options.webBaseUrl}`, |
| 107 | // Same rationale as the dev target: the harness boots loopback MCP/OAuth |
| 108 | // test servers and points the instance at them. |
| 109 | "-e", |
| 110 | "EXECUTOR_ALLOW_LOCAL_NETWORK=true", |
| 111 | options.image, |
| 112 | ]; |
| 113 | log(options.logFile, `docker ${args.join(" ")}`); |
| 114 | await exec("docker", args).catch((error: { stderr?: string }) => { |
| 115 | throw new Error(`selfhost-docker: docker run failed: ${String(error.stderr ?? error)}`); |
| 116 | }); |
| 117 | |
| 118 | try { |
| 119 | await waitForHttp(`${options.webBaseUrl}/api/health`, { timeoutMs: 120_000 }); |
| 120 | } catch (error) { |
| 121 | const { stdout } = await exec("docker", ["logs", "--tail", "100", name]).catch(() => ({ |
| 122 | stdout: "(docker logs unavailable)", |
| 123 | })); |
| 124 | log(options.logFile, String(stdout)); |
| 125 | await exec("docker", ["rm", "-f", name]).catch(() => {}); |
| 126 | throw error; |
| 127 | } |
| 128 | }; |
| 129 | |
| 130 | /** |
| 131 | * A deployment shutdown, as orchestrators do it: SIGTERM (docker stop's |
no test coverage detected