| 1 | // Probe server for container → SQL DATABASE_URL reachability. Plain node, |
| 2 | // no deps. `/env` reports the URL the process received (and duplicate-key |
| 3 | // count from the raw environ — glibc getenv is first-match). `/probe` |
| 4 | // TCP-connects to that URL from inside the container. |
| 5 | // |
| 6 | // NOTE: this file is force-added past the repo's `*.js` ignore rule. |
| 7 | const fs = require("node:fs"); |
| 8 | const http = require("node:http"); |
| 9 | const net = require("node:net"); |
| 10 | |
| 11 | const countEnv = (name) => { |
| 12 | try { |
| 13 | return fs |
| 14 | .readFileSync("/proc/self/environ", "utf8") |
| 15 | .split("\0") |
| 16 | .filter((entry) => entry.startsWith(`${name}=`)).length; |
| 17 | } catch { |
| 18 | return undefined; |
| 19 | } |
| 20 | }; |
| 21 | |
| 22 | const defaultPort = (url) => { |
| 23 | if (url.port) return Number(url.port); |
| 24 | if (url.protocol.startsWith("mysql")) return 3306; |
| 25 | return 5432; |
| 26 | }; |
| 27 | |
| 28 | const tcpProbe = (hostname, port) => |
| 29 | new Promise((resolve) => { |
| 30 | const socket = net.connect({ host: hostname, port }, () => { |
| 31 | socket.end(); |
| 32 | resolve({ ok: true, host: hostname, port }); |
| 33 | }); |
| 34 | socket.setTimeout(8000, () => { |
| 35 | socket.destroy(); |
| 36 | resolve({ ok: false, host: hostname, port, error: "timeout" }); |
| 37 | }); |
| 38 | socket.on("error", (error) => |
| 39 | resolve({ ok: false, host: hostname, port, error: String(error) }), |
| 40 | ); |
| 41 | }); |
| 42 | |
| 43 | const server = http.createServer(async (req, res) => { |
| 44 | res.setHeader("content-type", "application/json"); |
| 45 | if (req.url === "/env") { |
| 46 | res.end( |
| 47 | JSON.stringify({ |
| 48 | DATABASE_URL: process.env.DATABASE_URL, |
| 49 | databaseUrlCount: countEnv("DATABASE_URL"), |
| 50 | }), |
| 51 | ); |
| 52 | return; |
| 53 | } |
| 54 | if (req.url === "/probe") { |
| 55 | const value = process.env.DATABASE_URL; |
| 56 | if (!value) { |
| 57 | res.end(JSON.stringify({ error: "missing DATABASE_URL" })); |
| 58 | return; |
| 59 | } |
| 60 | try { |
| 61 | const url = new URL(value); |
| 62 | res.end(JSON.stringify(await tcpProbe(url.hostname, defaultPort(url)))); |
| 63 | } catch (error) { |
| 64 | res.end(JSON.stringify({ error: String(error) })); |
| 65 | } |
| 66 | return; |
| 67 | } |
| 68 | res.end(JSON.stringify({ ok: true })); |
| 69 | }); |
| 70 | |
| 71 | server.listen(8080, () => { |
| 72 | console.log("sqlreach probe server listening on 8080"); |
| 73 | }); |