initial commit

Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-10-24 16:28:53 +02:00
padre 0a41e47b03
commit e0f561de09
Se han modificado 23 ficheros con 2874 adiciones y 6624 borrados

138
lib/ip-monitor-service.js Archivo normal
Ver fichero

@@ -0,0 +1,138 @@
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
const configPath = join(process.cwd(), 'config.json');
export class IPMonitorService {
constructor() {
this.loadConfig();
}
loadConfig() {
try {
this.config = JSON.parse(readFileSync(configPath, 'utf8'));
} catch (error) {
console.error('Error loading config:', error);
this.config = { ipProviders: [], currentIPs: {} };
}
}
async fetchIPFromProvider(provider, type = 'ipv4') {
const url = type === 'ipv4' ? provider.ipv4Url : provider.ipv6Url;
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; OVH-DNS-Manager/1.0)'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const text = await response.text();
const ip = text.trim();
// Basic validation
if (type === 'ipv4') {
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipv4Regex.test(ip)) {
throw new Error('Invalid IPv4 format');
}
} else if (type === 'ipv6') {
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
if (!ipv6Regex.test(ip)) {
throw new Error('Invalid IPv6 format');
}
}
return ip;
} catch (error) {
console.error(`Error fetching ${type} from ${provider.name}:`, error);
throw error;
}
}
async getCurrentIPs() {
const enabledProviders = this.config.ipProviders?.filter(p => p.enabled) || [];
if (enabledProviders.length === 0) {
throw new Error('No IP providers enabled');
}
let ipv4 = null;
let ipv6 = null;
// Try each provider until we get valid IPs
for (const provider of enabledProviders) {
if (!ipv4) {
try {
ipv4 = await this.fetchIPFromProvider(provider, 'ipv4');
} catch {
console.error(`Failed to get IPv4 from ${provider.name}`);
}
}
if (!ipv6) {
try {
ipv6 = await this.fetchIPFromProvider(provider, 'ipv6');
} catch {
console.error(`Failed to get IPv6 from ${provider.name}`);
}
}
if (ipv4 && ipv6) break;
}
// Update config with new IPs
if (ipv4 || ipv6) {
this.updateCurrentIPs({ ipv4, ipv6 });
}
return { ipv4, ipv6 };
}
updateCurrentIPs(ips) {
try {
this.config.currentIPs = {
...this.config.currentIPs,
...ips,
lastUpdate: new Date().toISOString()
};
writeFileSync(configPath, JSON.stringify(this.config, null, 2), 'utf8');
} catch (error) {
console.error('Error updating current IPs in config:', error);
}
}
getStoredIPs() {
return this.config.currentIPs || { ipv4: null, ipv6: null, lastUpdate: null };
}
async checkAndUpdateIPs() {
try {
const newIPs = await this.getCurrentIPs();
const storedIPs = this.getStoredIPs();
const changed = {
ipv4: newIPs.ipv4 && newIPs.ipv4 !== storedIPs.ipv4,
ipv6: newIPs.ipv6 && newIPs.ipv6 !== storedIPs.ipv6
};
return {
changed: changed.ipv4 || changed.ipv6,
newIPs,
oldIPs: storedIPs,
details: changed
};
} catch (error) {
console.error('Error checking IPs:', error);
throw error;
}
}
}
const ipMonitorServiceInstance = new IPMonitorService();
export default ipMonitorServiceInstance;

200
lib/ovh-service.js Archivo normal
Ver fichero

@@ -0,0 +1,200 @@
import ovh from '@ovhcloud/node-ovh';
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
const configPath = join(process.cwd(), 'config.json');
export class OVHService {
constructor() {
this.clients = new Map();
this.loadConfig();
}
loadConfig() {
try {
const config = JSON.parse(readFileSync(configPath, 'utf8'));
this.config = config;
// Initialize OVH clients for each account
config.ovhAccounts.forEach(account => {
if (account.appKey && account.appSecret && account.consumerKey) {
this.clients.set(account.id, ovh({
appKey: account.appKey,
appSecret: account.appSecret,
consumerKey: account.consumerKey,
endpoint: account.endpoint || 'ovh-eu'
}));
}
});
} catch (error) {
console.error('Error loading config:', error);
this.config = { ovhAccounts: [], ipProviders: [], autoUpdate: { enabled: false }, currentIPs: {} };
}
}
getClientForDomain(domain) {
// Find which account manages this domain
for (const account of this.config.ovhAccounts) {
if (account.domains && account.domains.includes(domain)) {
return this.clients.get(account.id);
}
}
// Return first available client as fallback
return this.clients.values().next().value;
}
async getAllDomains() {
const allDomains = [];
for (const [accountId, client] of this.clients.entries()) {
try {
const domains = await client.requestPromised('GET', '/domain/zone');
const account = this.config.ovhAccounts.find(acc => acc.id === accountId);
allDomains.push(...domains.map(domain => ({
domain,
accountId,
accountName: account?.name || accountId
})));
} catch (error) {
console.error(`Error fetching domains from account ${accountId}:`, error);
}
}
return allDomains;
}
async getDNSRecords(zoneName) {
try {
const client = this.getClientForDomain(zoneName);
if (!client) {
throw new Error('No OVH client configured for this domain');
}
const recordIds = await client.requestPromised('GET', `/domain/zone/${zoneName}/record`);
const records = await Promise.all(
recordIds.map(async (id) => {
const record = await client.requestPromised('GET', `/domain/zone/${zoneName}/record/${id}`);
return { ...record, id };
})
);
return records;
} catch (error) {
console.error(`Failed to fetch DNS records for ${zoneName}:`, error);
throw error;
}
}
async createDNSRecord(zoneName, recordData) {
try {
const client = this.getClientForDomain(zoneName);
if (!client) {
throw new Error('No OVH client configured for this domain');
}
const record = await client.requestPromised('POST', `/domain/zone/${zoneName}/record`, recordData);
await this.refreshZone(zoneName);
return record;
} catch (error) {
console.error(`Failed to create DNS record in ${zoneName}:`, error);
throw error;
}
}
async updateDNSRecord(zoneName, recordId, recordData) {
try {
const client = this.getClientForDomain(zoneName);
if (!client) {
throw new Error('No OVH client configured for this domain');
}
const record = await client.requestPromised('PUT', `/domain/zone/${zoneName}/record/${recordId}`, recordData);
await this.refreshZone(zoneName);
return record;
} catch (error) {
console.error(`Failed to update DNS record ${recordId} in ${zoneName}:`, error);
throw error;
}
}
async deleteDNSRecord(zoneName, recordId) {
try {
const client = this.getClientForDomain(zoneName);
if (!client) {
throw new Error('No OVH client configured for this domain');
}
await client.requestPromised('DELETE', `/domain/zone/${zoneName}/record/${recordId}`);
await this.refreshZone(zoneName);
} catch (error) {
console.error(`Failed to delete DNS record ${recordId} from ${zoneName}:`, error);
throw error;
}
}
async refreshZone(domain) {
try {
const client = this.getClientForDomain(domain);
if (!client) {
throw new Error('No OVH client configured for this domain');
}
const result = await client.requestPromised('POST', `/domain/zone/${domain}/refresh`);
return result;
} catch (error) {
console.error(`Failed to refresh DNS zone: ${domain}`, error);
throw error;
}
}
async bulkUpdateRecords(zoneName, recordIds, updateData) {
const results = [];
for (const recordId of recordIds) {
try {
const result = await this.updateDNSRecord(zoneName, recordId, updateData);
results.push({ recordId, success: true, result });
} catch (error) {
results.push({ recordId, success: false, error: error.message });
}
}
return results;
}
getConfig() {
return this.config;
}
saveConfig(newConfig) {
try {
writeFileSync(configPath, JSON.stringify(newConfig, null, 2), 'utf8');
this.config = newConfig;
// Reinitialize clients
this.clients.clear();
newConfig.ovhAccounts.forEach(account => {
if (account.appKey && account.appSecret && account.consumerKey) {
this.clients.set(account.id, ovh({
appKey: account.appKey,
appSecret: account.appSecret,
consumerKey: account.consumerKey,
endpoint: account.endpoint || 'ovh-eu'
}));
}
});
return true;
} catch (error) {
console.error('Error saving config:', error);
throw error;
}
}
}
const ovhServiceInstance = new OVHService();
export default ovhServiceInstance;