CJ_Clippy 30be956029
Some checks failed
ci / build (push) Failing after 22m11s
ci / Tests & Checks (push) Failing after 7m31s
disable caddy
2025-02-21 23:32:48 -08:00

158 lines
4.2 KiB
TypeScript

import { Elysia, t, type Context } from 'elysia'
import { version } from './package.json';
import { basicAuth } from '@eelkevdbos/elysia-basic-auth'
import net from 'net'
import { appendFile } from "node:fs/promises";
if (!process.env.TRACKER_HELPER_USERNAME) throw new Error('TRACKER_HELPER_USERNAME missing in env');
if (!process.env.TRACKER_HELPER_PASSWORD) throw new Error('TRACKER_HELPER_PASSWORD missing in env');
const accesslistFilePath = process.env.TRACKER_HELPER_ACCESSLIST_PATH || "/var/lib/aquatic/accesslist"
const username = process.env.TRACKER_HELPER_USERNAME!
const password = process.env.TRACKER_HELPER_PASSWORD!
interface DockerContainer {
Id: string;
Command: string;
}
const authOpts = {
scope: [
"/accesslist",
"/version"
],
credentials: [
{
username: username,
password: password
}
]
}
const startupChecks = async function startupChecks() {
if (!process.env.TRACKER_HELPER_ACCESSLIST_PATH) {
console.warn(`TRACKER_HELPER_ACCESSLIST_PATH is missing in env. Using default ${accesslistFilePath}`)
}
}
const getAccesslist = async function getAccesslist(ctx: Context) {
const wl = Bun.file(accesslistFilePath); // relative to cwd
console.debug(`read from accesslist file at ${accesslistFilePath}. size=${wl.size}, type=${wl.type}`)
return wl.text()
}
async function findOpentrackerContainer(socketPath = "/var/run/docker.sock"): Promise<DockerContainer | null> {
return new Promise((resolve, reject) => {
console.debug(`opening net client at socketPath=${socketPath}`)
const client = net.createConnection(socketPath, () => {
const request = 'GET /containers/json HTTP/1.0\r\n\r\n';
client.write(request);
});
console.debug(`waiting for response from socket`)
let response = '';
client.on('data', (data) => {
console.debug(`client got data`)
response += data.toString();
});
console.debug(`waiting for connection end`)
client.on('end', () => {
console.debug(`client end detected`)
try {
const body = response.split('\r\n\r\n')[1];
const containers: DockerContainer[] = JSON.parse(body);
const container = containers.find(c => c.Command.includes('/bin/opentracker'));
resolve(container || null);
} catch (err) {
reject(err);
}
});
client.on('error', (err) => {
console.error(`net client encountered error ${err}`)
reject(err);
});
});
}
async function killContainer(socketPath = "/var/run/docker.sock", containerId: string, signal = "SIGTERM") {
const request = `POST /containers/${containerId}/kill?signal=${signal} HTTP/1.0\r\n\r\n`;
return new Promise((resolve, reject) => {
const client = net.createConnection(socketPath, () => {
client.write(request);
});
client.on('data', (data: any) => {
// console.log(data.toString());
client.end();
resolve(data.toString());
});
client.on('error', (err: any) => {
console.error('Error:', err);
reject(err);
});
});
}
const maybeKillContainer = async function maybeKillContainer(signal: string = "SIGUSR1") {
const sockFile = Bun.file('/var/run/docker.sock')
const sockFileExists = await sockFile.exists()
if (!sockFileExists) {
console.warn("⚠️ docker sock file not found. skipping.")
} else {
console.debug('looking for opentracker container')
const container = await findOpentrackerContainer()
if (!container) {
console.warn('⚠️ failed to find opentracker container');
} else {
await killContainer(undefined, container.Id, signal)
console.debug('sending SIGUSR1 to container ' + container.Id)
}
}
}
const postAccesslist = async function postAccesslist(ctx: Context) {
let body = ctx.body
console.debug('appending to accesslist at ' + accesslistFilePath)
await appendFile(accesslistFilePath, body + "\n");
await maybeKillContainer("SIGUSR1")
ctx.set.status = 201
return body
}
await startupChecks()
const app = new Elysia()
.use(basicAuth(authOpts))
.get('/health', () => 'OK')
.get('/version', () => `version ${version}`)
.get('/accesslist', getAccesslist)
.post('/accesslist', postAccesslist, {
body: t.String()
})
export default app