forked from livecycle/preevy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
149 lines (129 loc) · 4.87 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import fs from 'fs'
import path from 'path'
import Docker from 'dockerode'
import { inspect } from 'node:util'
import http from 'node:http'
import { rimraf } from 'rimraf'
import pino from 'pino'
import pinoPretty from 'pino-pretty'
import { EOL } from 'os'
import {
requiredEnv,
formatPublicKey,
parseSshUrl,
SshConnectionConfig,
tunnelNameResolver,
MachineStatusCommand,
} from '@preevy/common'
import createDockerClient from './src/docker'
import createApiServerHandler from './src/http/api-server'
import { sshClient as createSshClient } from './src/ssh'
import { createDockerProxyHandlers } from './src/http/docker-proxy'
import { tryHandler, tryUpgradeHandler } from './src/http/http-server-helpers'
import { httpServerHandlers } from './src/http'
import { runMachineStatusCommand } from './src/machine-status'
import { envMetadata } from './src/metadata'
import { readAllFiles } from './src/files'
const homeDir = process.env.HOME || '/root'
const dockerSocket = '/var/run/docker.sock'
const sshConnectionConfigFromEnv = async (): Promise<{ connectionConfig: SshConnectionConfig; sshUrl: string }> => {
const sshUrl = requiredEnv('SSH_URL')
const parsed = parseSshUrl(sshUrl)
const clientPrivateKey = process.env.SSH_PRIVATE_KEY || fs.readFileSync(
path.join(homeDir, '.ssh', 'id_rsa'),
{ encoding: 'utf8' },
)
const knownServerPublicKeys = await readAllFiles(path.join(homeDir, 'known_server_keys'))
return {
sshUrl,
connectionConfig: {
...parsed,
clientPrivateKey,
username: requiredEnv('PREEVY_ENV_ID'),
knownServerPublicKeys,
insecureSkipVerify: Boolean(process.env.INSECURE_SKIP_VERIFY),
tlsServerName: process.env.TLS_SERVERNAME || undefined,
},
}
}
const writeLineToStdout = (s: string) => [s, EOL].forEach(d => process.stdout.write(d))
const machineStatusCommand = process.env.MACHINE_STATUS_COMMAND
? JSON.parse(process.env.MACHINE_STATUS_COMMAND) as MachineStatusCommand
: undefined
const log = pino({
level: process.env.DEBUG || process.env.DOCKER_PROXY_DEBUG ? 'debug' : 'info',
}, pinoPretty({ destination: pino.destination(process.stderr) }))
const main = async () => {
const { connectionConfig, sshUrl } = await sshConnectionConfigFromEnv()
log.debug('ssh config: %j', {
...connectionConfig,
clientPrivateKey: '*** REDACTED ***',
clientPublicKey: formatPublicKey(connectionConfig.clientPrivateKey),
})
const docker = new Docker({ socketPath: dockerSocket })
const dockerClient = createDockerClient({ log: log.child({ name: 'docker' }), docker, debounceWait: 500 })
const sshLog = log.child({ name: 'ssh' })
const sshClient = await createSshClient({
connectionConfig,
tunnelNameResolver: tunnelNameResolver({ envId: requiredEnv('PREEVY_ENV_ID') }),
log: sshLog,
onError: err => {
log.error(err)
process.exit(1)
},
})
sshLog.info('ssh client connected to %j', sshUrl)
let currentTunnels = dockerClient.getRunningServices().then(services => sshClient.updateTunnels(services))
void dockerClient.startListening({
onChange: async services => {
currentTunnels = sshClient.updateTunnels(services)
void currentTunnels.then(ssh => writeLineToStdout(JSON.stringify(ssh)))
},
})
const apiListenAddress = process.env.PORT ?? 3000
if (typeof apiListenAddress === 'string' && Number.isNaN(Number(apiListenAddress))) {
await rimraf(apiListenAddress)
}
const { handler, upgradeHandler } = httpServerHandlers({
log: log.child({ name: 'http' }),
apiHandler: createApiServerHandler({
log: log.child({ name: 'api' }),
currentSshState: async () => (await currentTunnels),
machineStatus: machineStatusCommand
? async () => await runMachineStatusCommand({ log, docker })(machineStatusCommand)
: undefined,
envMetadata: await envMetadata({ env: process.env, log }),
composeModelPath: '/preevy/docker-compose.yaml',
}),
dockerProxyHandlers: createDockerProxyHandlers({
log: log.child({ name: 'docker-proxy' }),
dockerSocket,
docker,
}),
dockerProxyPrefix: '/docker/',
})
const httpLog = log.child({ name: 'http' })
const httpServer = http.createServer(tryHandler({ log: httpLog }, async (req, res) => {
httpLog.debug('request %s %s', req.method, req.url)
return await handler(req, res)
}))
.on('upgrade', tryUpgradeHandler({ log: httpLog }, async (req, socket, head) => {
httpLog.debug('upgrade %s %s', req.method, req.url)
return await upgradeHandler(req, socket, head)
}))
.listen(apiListenAddress, () => {
httpLog.info(`API server listening on ${inspect(httpServer.address())}`)
})
.on('error', err => {
httpLog.error(err)
process.exit(1)
})
.unref()
}
void main();
['SIGTERM', 'SIGINT'].forEach(signal => {
process.once(signal, async () => {
log.info(`shutting down on ${signal}`)
process.exit(0)
})
})