zbr-server / src /server.ts
itszubariel
add per-user bot limit
6f5f964
Raw
History Blame Contribute Delete
3.83 kB
import express, { Request, Response, NextFunction } from 'express';
import { spawn, execSync, ChildProcess } from 'child_process';
import fs from 'fs';
import path from 'path';
interface BotProcess {
child: ChildProcess;
tempDir: string;
startedAt: number;
}
interface Command {
filename: string;
content: string;
}
interface RunRequestBody {
botId: string;
discordId: string;
envFile: string;
zbrJson: string;
commands: Command[];
dbBase64: string;
}
interface StopRequestBody {
botId: string;
discordId: string;
}
const app = express();
app.use(express.json({ limit: '10mb' }));
const ZBR_SECRET = process.env.ZBR_SECRET;
const processes = new Map<string, BotProcess>();
try {
const version = execSync('/usr/local/bin/zbr --version').toString().trim();
const size = execSync('wc -c < /usr/local/bin/zbr').toString().trim();
console.log('ZBR binary version:', version, '| size:', size, 'bytes');
} catch (e) {
console.error("Failed to execute ZBR binary:", e);
}
const auth = (req: Request, res: Response, next: NextFunction): void | Response => {
if (req.path === '/health') return next();
if (req.headers['x-zbr-secret'] !== ZBR_SECRET) {
return res.status(401).send('Unauthorized');
}
next();
};
app.use(auth);
app.post('/run', (req: Request<{}, any, RunRequestBody>, res: Response) => {
const { botId, discordId, envFile, zbrJson, commands, dbBase64 } = req.body;
const key = `${discordId}-${botId}`;
const userHasRunningBot = Array.from(processes.keys()).some(k => k.startsWith(`${discordId}-`));
if (userHasRunningBot) return res.status(400).send('You already have a bot running. Stop it before starting another.');
if (processes.has(key)) return res.status(400).send('Bot already running');
const tempDir = `/tmp/zbr-${discordId}-${botId}`;
fs.mkdirSync(tempDir, { recursive: true });
fs.writeFileSync(path.join(tempDir, '.env'), envFile);
fs.writeFileSync(path.join(tempDir, 'zbr.json'), zbrJson);
const commandsDir = path.join(tempDir, 'commands');
fs.mkdirSync(commandsDir, { recursive: true });
commands.forEach(cmd => {
fs.writeFileSync(path.join(commandsDir, cmd.filename), cmd.content);
});
fs.writeFileSync(path.join(tempDir, 'zbr.db'), Buffer.from(dbBase64, 'base64'));
const child = spawn('/usr/local/bin/zbr', [], {
cwd: tempDir,
stdio: ['ignore', 'pipe', 'pipe']
});
if (child.stdout) {
child.stdout.on('data', (data) => console.log(data.toString()));
}
if (child.stderr) {
child.stderr.on('data', (data) => console.error(data.toString()));
}
processes.set(key, { child, tempDir, startedAt: Date.now() });
setTimeout(() => {
if (processes.has(key)) stopBot(key);
}, 2 * 60 * 60 * 1000);
res.json({ success: true, status: 'running' });
});
function stopBot(key: string): boolean {
const proc = processes.get(key);
if (!proc) return false;
proc.child.kill();
fs.rmSync(proc.tempDir, { recursive: true, force: true });
processes.delete(key);
return true;
}
app.post('/stop', (req: Request<{}, any, StopRequestBody>, res: Response) => {
const { botId, discordId } = req.body;
stopBot(`${discordId}-${botId}`);
res.json({ success: true, status: 'stopped' });
});
app.get('/status/:discordId/:botId', (req: Request, res: Response) => {
const { discordId, botId } = req.params;
const proc = processes.get(`${discordId}-${botId}`);
if (proc) {
res.json({ status: 'running', startedAt: proc.startedAt });
} else {
res.json({ status: 'stopped' });
}
});
app.get('/health', (req: Request, res: Response) => res.json({ ok: true }));
app.listen(7860, () => console.log('Server running on port 7860'));