Files
wg-manager/backend/src/index.js
T
AnranYus 8534441d59 update
2025-09-10 17:27:53 +08:00

158 lines
4.3 KiB
JavaScript

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}`);
});