import express from 'express'; import cors from 'cors'; import bodyParser from 'body-parser'; import { createRequire } from 'module'; import wgService from './services/wgService.js'; import auth from './middleware/auth.js'; const require = createRequire(import.meta.url); const app = express(); app.use(cors()); app.use(bodyParser.json({ limit: '2mb' })); // Health app.get('/api/health', (_req, res) => { res.json({ ok: true }); }); // Auth app.post('/api/login', (req, res) => { const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD; const { password } = req.body || {}; if (!ADMIN_PASSWORD) { return res.status(500).json({ error: '未配置 ADMIN_PASSWORD 环境变量' }); } if (password !== ADMIN_PASSWORD) { return res.status(401).json({ error: '密码错误' }); } const token = auth.issueToken(); res.json({ token }); }); // 保护以下接口 app.use('/api', auth.verifyToken); // Server config app.get('/api/server/config', async (req, res) => { try { const iface = req.query.interface || 'wg0'; const content = await wgService.readServerConfig(iface); res.json({ interface: iface, content }); } catch (err) { res.status(500).json({ error: String(err?.message || err) }); } }); app.put('/api/server/config', async (req, res) => { try { const { interface: iface = 'wg0', content } = req.body || {}; if (typeof content !== 'string') { return res.status(400).json({ error: 'content must be string' }); } const result = await wgService.writeServerConfig(iface, content); res.json({ ok: true, backupPath: result.backupPath, path: result.path }); } catch (err) { res.status(500).json({ error: String(err?.message || err) }); } }); // Server control app.post('/api/server/control', async (req, res) => { try { const { interface: iface = 'wg0', action } = req.body || {}; if (!['start', 'stop', 'restart'].includes(action)) { return res.status(400).json({ error: 'action must be start|stop|restart' }); } const output = await wgService.controlService(iface, action); res.json({ ok: true, output }); } catch (err) { res.status(500).json({ error: String(err?.message || err) }); } }); // Server status app.get('/api/server/status', async (req, res) => { try { const iface = req.query.interface || 'wg0'; const status = await wgService.getStatus(iface); res.json(status); } catch (err) { res.status(500).json({ error: String(err?.message || err) }); } }); // Keys app.post('/api/keys/generate', async (req, res) => { try { const { type = 'pair' } = req.body || {}; if (type === 'pair') { const pair = await wgService.generateKeyPair(); res.json(pair); } else if (type === 'psk') { const psk = await wgService.generatePresharedKey(); res.json({ presharedKey: psk }); } else { res.status(400).json({ error: 'type must be pair|psk' }); } } catch (err) { res.status(500).json({ error: String(err?.message || err) }); } }); // Client config app.post('/api/clients/generate', async (req, res) => { try { const { interface: iface = 'wg0', name, clientAddress, // optional; auto-assign if omitted dns = '1.1.1.1', allowedIps = '0.0.0.0/0, ::/0', keepalive = 25, endpointHost, endpointPort, usePresharedKey = false, } = req.body || {}; if (!name) return res.status(400).json({ error: 'name is required' }); if (!endpointHost || !endpointPort) { return res.status(400).json({ error: 'endpointHost and endpointPort are required' }); } const result = await wgService.generateClientConfig({ interfaceName: iface, clientName: name, clientAddress, dns, allowedIps, keepalive, endpointHost, endpointPort, usePresharedKey, }); // 生成后自动写入服务端Peer await wgService.appendPeerToServerConfig({ interfaceName: iface, clientName: name, clientPublicKey: result.publicKey, clientAddress: result.address, presharedKey: result.presharedKey, allowedIps, }); res.json(result); } catch (err) { res.status(500).json({ error: String(err?.message || err) }); } }); const PORT = process.env.PORT || 51821; app.listen(PORT, () => { // eslint-disable-next-line no-console console.log(`wg-easy backend listening on :${PORT}`); });