-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Expand file tree
/
Copy pathdetect.ts
More file actions
156 lines (144 loc) · 4.99 KB
/
Copy pathdetect.ts
File metadata and controls
156 lines (144 loc) · 4.99 KB
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
150
151
152
153
154
155
156
import { spawnSync } from 'node:child_process'
import net from 'node:net'
import os from 'node:os'
import { ROOT, readEnvFile } from './env-files.ts'
export const MANAGED_LABEL = 'managed-by=sim-setup'
export const DB_CONTAINER = 'sim-postgres'
export const REDIS_CONTAINER = 'sim-redis'
const SHELL_LLM_KEYS = [
'OPENAI_API_KEY',
'ANTHROPIC_API_KEY',
'GEMINI_API_KEY',
'XAI_API_KEY',
'MISTRAL_API_KEY',
] as const
export interface Detection {
dockerRunning: boolean
appPortOpen: boolean
realtimePortOpen: boolean
postgresPortOpen: boolean
redisPortOpen: boolean
envFiles: { sim: boolean; realtime: boolean; db: boolean; root: boolean }
dbContainer: { state: 'running' | 'stopped'; managed: boolean } | null
redisContainer: { state: 'running' | 'stopped'; managed: boolean } | null
shellLlmKeys: string[]
ollamaReachable: boolean
binaries: { kubectl: boolean; helm: boolean; kind: boolean }
kubeContext: string | null
specs: { hostMemGb: number; dockerMemGb: number | null; freeDiskGb: number | null }
}
export function portOpen(port: number, timeoutMs = 500): Promise<boolean> {
return new Promise((resolve) => {
const socket = net.connect({ port, host: '127.0.0.1' })
const done = (result: boolean) => {
socket.destroy()
resolve(result)
}
socket.setTimeout(timeoutMs, () => done(false))
socket.once('connect', () => done(true))
socket.once('error', () => done(false))
})
}
export interface PortOwnerInfo {
command: string
pid: number
isDocker: boolean
}
export function portOwner(port: number): PortOwnerInfo | null {
const result = spawnSync('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN'], { encoding: 'utf8' })
if (result.status !== 0) return null
const line = result.stdout.split('\n')[1]
if (!line) return null
const [command, pid] = line.split(/\s+/)
if (!command || !pid) return null
return { command, pid: Number(pid), isDocker: /^(com\.docke|docker)/i.test(command) }
}
function commandSucceeds(command: string, args: string[]): boolean {
return spawnSync(command, args, { stdio: 'ignore' }).status === 0
}
function commandOutput(command: string, args: string[]): string | null {
const result = spawnSync(command, args, { encoding: 'utf8' })
return result.status === 0 ? result.stdout.trim() : null
}
function detectContainer(dockerRunning: boolean, name: string): Detection['dbContainer'] {
if (!dockerRunning) return null
// Docker's `name=^x$` anchor matches against the internal `/x` form and often
// misses, so filter loosely (substring) and pin the exact name in code.
const out = commandOutput('docker', [
'ps',
'-a',
'--filter',
`name=${name}`,
'--format',
'{{.Names}}\t{{.State}}\t{{.Labels}}',
])
if (!out) return null
const row = out
.split('\n')
.map((line) => line.split('\t'))
.find(([containerName]) => containerName === name)
if (!row) return null
const [, state, labels = ''] = row
return {
state: state === 'running' ? 'running' : 'stopped',
managed: labels.includes(MANAGED_LABEL),
}
}
async function ollamaReachable(): Promise<boolean> {
try {
const res = await fetch('http://localhost:11434/api/tags', {
signal: AbortSignal.timeout(800),
})
return res.ok
} catch {
return false
}
}
function detectSpecs(dockerRunning: boolean): Detection['specs'] {
const dockerMem = dockerRunning
? commandOutput('docker', ['info', '--format', '{{.MemTotal}}'])
: null
const df = spawnSync('df', ['-k', ROOT], { encoding: 'utf8' })
const dfAvail =
df.status === 0 ? Number(df.stdout.trim().split('\n')[1]?.split(/\s+/)[3]) : Number.NaN
return {
hostMemGb: Math.round(os.totalmem() / 1024 ** 3),
dockerMemGb: dockerMem ? Math.round((Number(dockerMem) / 1024 ** 3) * 10) / 10 : null,
freeDiskGb: Number.isNaN(dfAvail) ? null : Math.round(dfAvail / 1024 ** 2),
}
}
export async function runDetection(): Promise<Detection> {
const dockerRunning = commandSucceeds('docker', ['info'])
const [appPortOpen, realtimePortOpen, postgresPortOpen, redisPortOpen, ollamaPortOpen] =
await Promise.all([
portOpen(3000),
portOpen(3002),
portOpen(5432),
portOpen(6379),
portOpen(11434),
])
return {
dockerRunning,
appPortOpen,
realtimePortOpen,
postgresPortOpen,
redisPortOpen,
envFiles: {
sim: readEnvFile('sim').exists,
realtime: readEnvFile('realtime').exists,
db: readEnvFile('db').exists,
root: readEnvFile('root').exists,
},
dbContainer: detectContainer(dockerRunning, DB_CONTAINER),
redisContainer: detectContainer(dockerRunning, REDIS_CONTAINER),
shellLlmKeys: SHELL_LLM_KEYS.filter((key) => process.env[key]),
ollamaReachable: ollamaPortOpen ? await ollamaReachable() : false,
binaries: {
kubectl: Bun.which('kubectl') !== null,
helm: Bun.which('helm') !== null,
kind: Bun.which('kind') !== null,
},
kubeContext: commandOutput('kubectl', ['config', 'current-context']),
specs: detectSpecs(dockerRunning),
}
}