Files
mcp-vultr/services/mikrotikService.js

208 lines
8.0 KiB
JavaScript

const axios = require('axios');
const https = require('https');
class MikrotikService {
constructor() {
this.client = null;
}
getConfiguredRouterIds() {
const ids = [1]; // Default router always exists (or assumed ID 1)
// Scan env for MIKROTIK_HOST_X
for (const key in process.env) {
if (key.startsWith('MIKROTIK_HOST_')) {
const id = parseInt(key.replace('MIKROTIK_HOST_', ''));
if (!isNaN(id) && !ids.includes(id)) {
ids.push(id);
}
}
}
return ids;
}
getConfig(routerId = 1) {
// Default to Router 1 if not specified or if routerId is 1.
// If routerId is provided (e.g. 3), look for MIKROTIK_HOST_3
let host;
if (routerId && routerId != 1) {
// STRICT MODE: Secondary routers MUST have their own config.
// Do NOT fall back to main router, or we'll connect to the wrong device.
host = process.env[`MIKROTIK_HOST_${routerId}`];
if (!host) {
throw new Error(`Configuration for Router ${routerId} (MIKROTIK_HOST_${routerId}) not found in .env`);
}
} else {
// Default Router 1
host = process.env.MIKROTIK_HOST;
if (!host) throw new Error("Mikrotik Host (Router 1) not configured in .env");
}
const suffix = (routerId && routerId != 1) ? `_${routerId}` : '';
// Only fallback to default credentials if specifically Router 1.
// For Router 3+, we expect specific creds OR we can allow fallback for creds but NOT host.
// Actually, safer to rely on suffix or default global ONLY if logic permits.
// Let's allow fallback for User/Pass/Port (common to reuse creds) but Host was the critical one.
const user = process.env[`MIKROTIK_USER${suffix}`] || process.env.MIKROTIK_USER || 'admin';
const password = process.env[`MIKROTIK_PASSWORD${suffix}`] || process.env.MIKROTIK_PASSWORD || '';
let port = parseInt(process.env[`MIKROTIK_PORT${suffix}`] || process.env.MIKROTIK_PORT || 80);
if (port === 8728) port = 80;
return {
host,
user,
password,
port,
protocol: (port === 443) ? 'https' : 'http'
};
}
getClient(routerId = 1) {
const config = this.getConfig(routerId);
const baseURL = `${config.protocol}://${config.host}:${config.port}/rest`;
return axios.create({
baseURL,
auth: {
username: config.user,
password: config.password
},
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
timeout: 10000
});
}
async execute(method, url, data = null, routerId = 1) {
try {
const client = this.getClient(routerId);
const response = await client.request({
method,
url,
data
});
return response.data;
} catch (error) {
if (error.response) {
// throw new Error(`Mikrotik API Error: ${error.response.status} ${JSON.stringify(error.response.data)}`);
// Return empty for 404 on list calls?
throw new Error(`Mikrotik Error (Router ${routerId}): ${error.response.status} - ${JSON.stringify(error.response.data)}`);
} else if (error.request) {
console.error(`[Mikrotik Router ${routerId} No Response]`, error.message);
throw new Error(`Mikrotik Router ${routerId} Unreachable`);
} else {
throw error;
}
}
}
async getSystemResource(routerId = 1) {
const data = await this.execute('GET', '/system/resource', null, routerId);
return Array.isArray(data) ? data[0] : data;
}
// --- HOTSPOT ---
async getHotspotUsers(routerId = 1) {
return this.execute('GET', '/ip/hotspot/user', null, routerId);
}
async getActiveHotspot(routerId = 1) {
return this.execute('GET', '/ip/hotspot/active', null, routerId);
}
async getActiveUsers(routerId = 1) { // Alias for consistency
return this.getActiveHotspot(routerId);
}
async enableUser(name, routerId = 1) {
const users = await this.execute('GET', `/ip/hotspot/user?name=${encodeURIComponent(name)}`, null, routerId);
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
const id = users[0]['.id'];
await this.execute('PATCH', `/ip/hotspot/user/${id}`, { disabled: "false" }, routerId);
}
async disableUser(name, routerId = 1) {
const users = await this.execute('GET', `/ip/hotspot/user?name=${encodeURIComponent(name)}`, null, routerId);
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
const id = users[0]['.id'];
await this.execute('PATCH', `/ip/hotspot/user/${id}`, { disabled: "true" }, routerId);
// Kick
try {
const active = await this.execute('GET', `/ip/hotspot/active?user=${encodeURIComponent(name)}`, null, routerId);
if (active && active.length > 0) {
await this.execute('DELETE', `/ip/hotspot/active/${active[0]['.id']}`, null, routerId);
}
} catch (e) { }
}
// --- PPPoE ---
async getPppoeSecrets(routerId = 1) {
return this.execute('GET', '/ppp/secret', null, routerId);
}
async getActivePppoe(routerId = 1) {
return this.execute('GET', '/ppp/active', null, routerId);
}
async disablePppoeUser(name, routerId = 1) {
const users = await this.execute('GET', `/ppp/secret?name=${encodeURIComponent(name)}`, null, routerId);
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
const id = users[0]['.id'];
await this.execute('PATCH', `/ppp/secret/${id}`, { disabled: "true" }, routerId);
try {
const active = await this.execute('GET', `/ppp/active?name=${encodeURIComponent(name)}`, null, routerId);
if (active && active.length > 0) {
await this.execute('DELETE', `/ppp/active/${active[0]['.id']}`, null, routerId);
}
} catch (e) { }
return true;
}
async enablePppoeUser(name, routerId = 1) {
const users = await this.execute('GET', `/ppp/secret?name=${encodeURIComponent(name)}`, null, routerId);
if (!users || users.length === 0) throw new Error(`User ${name} not found on Router ${routerId}`);
const id = users[0]['.id'];
await this.execute('PATCH', `/ppp/secret/${id}`, { disabled: "false" }, routerId);
return true;
}
async getPppoeSecret(name, routerId = 1) {
const users = await this.execute('GET', `/ppp/secret?name=${encodeURIComponent(name)}`, null, routerId);
return (users && users.length > 0) ? users[0] : null;
}
async getInterfaces(routerId = 1) {
return this.execute('GET', '/interface', null, routerId);
}
async monitorInterfaceTraffic(interfaceName, routerId = 1) {
const result = await this.execute('POST', '/interface/monitor-traffic', {
interface: interfaceName,
once: 'true'
}, routerId);
return Array.isArray(result) ? result[0] : result;
}
async findInterface(partialName, routerId = 1) {
const interfaces = await this.getInterfaces(routerId);
const exact = interfaces.find(i => i.name === partialName);
if (exact) return exact;
const cleanInput = partialName.toLowerCase().replace(/[^a-z0-9]/g, '');
const fuzzy = interfaces.find(i => {
const cleanName = i.name.toLowerCase().replace(/[^a-z0-9]/g, '');
return cleanName.includes(cleanInput);
});
return fuzzy || null;
}
}
module.exports = new MikrotikService();