const express = require('express'); const fs = require('fs'); const https = require('https'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 8095; const DATA_FILE = path.join(__dirname, 'data', 'servers.json'); app.use(express.json()); app.use(express.static(__dirname)); app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'vps_manager.html')); }); if (!fs.existsSync(path.join(__dirname, 'data'))) { fs.mkdirSync(path.join(__dirname, 'data')); } function readData() { try { return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); } catch { return []; } } function writeData(data) { fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2)); } function geocode(location) { return new Promise((resolve) => { const q = encodeURIComponent(location); const options = { hostname: 'nominatim.openstreetmap.org', path: `/search?q=${q}&format=json&limit=1`, headers: { 'User-Agent': 'vps-fleet-manager/1.0' } }; https.get(options, (res) => { let data = ''; res.on('data', c => data += c); res.on('end', () => { try { const r = JSON.parse(data); resolve(r[0] ? { lat: parseFloat(r[0].lat), lng: parseFloat(r[0].lon) } : null); } catch { resolve(null); } }); }).on('error', () => resolve(null)); }); } app.get('/api/servers', (req, res) => { res.json(readData()); }); app.post('/api/servers', async (req, res) => { const servers = readData(); const server = { ...req.body }; if (!server.id) server.id = 'srv_' + Date.now(); const existing = servers.find(s => s.id === server.id); if (server.location && (!existing || existing.location !== server.location || !existing._lat)) { const coords = await geocode(server.location); if (coords) { server._lat = coords.lat; server._lng = coords.lng; } } else if (existing && existing._lat) { server._lat = existing._lat; server._lng = existing._lng; } const idx = servers.findIndex(s => s.id === server.id); if (idx > -1) servers[idx] = server; else servers.push(server); writeData(servers); res.json(server); }); app.delete('/api/servers/:id', (req, res) => { writeData(readData().filter(s => s.id !== req.params.id)); res.json({ ok: true }); }); app.listen(PORT, '0.0.0.0', () => { const { networkInterfaces } = require('os'); const nets = networkInterfaces(); const localIPs = Object.values(nets).flat() .filter(n => n.family === 'IPv4' && !n.internal) .map(n => n.address); console.log(`\nvps/fleet running`); console.log(` local → http://localhost:${PORT}`); localIPs.forEach(ip => console.log(` network → http://${ip}:${PORT}`)); console.log(''); });