UNPKG

@monkeyscanjump/cloudflare-dyndns

Version:

A robust TypeScript application that automatically updates Cloudflare DNS records when your public IP address changes. Perfect for maintaining consistent domain names for home servers, WireGuard VPN, self-hosted services, or any system with a dynamic IP a

301 lines 13.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigManager = void 0; const dotenv = __importStar(require("dotenv")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); /** * Manages application configuration from multiple sources * Loads from .env files, environment variables and direct configuration * with priority handling and OS-specific paths */ class ConfigManager { /** * Creates a new configuration manager * @param directConfig Optional configuration values to override environment variables */ constructor(directConfig = {}) { /** * Required configuration parameters for the application to function * These must be provided or the validation will fail */ this.requiredEnvVars = [ 'API_TOKEN', 'DOMAIN', 'SUBDOMAIN' ]; this.loadEnvironmentVariables(); // Initialize configuration with defaults - support both prefixed and non-prefixed env vars this.config = { API_TOKEN: process.env.API_TOKEN || process.env.CLOUDFLARE_API_TOKEN || '', ZONE_ID: process.env.ZONE_ID || process.env.CLOUDFLARE_ZONE_ID || '', RECORD_ID: process.env.RECORD_ID || process.env.CLOUDFLARE_RECORD_ID || '', DOMAIN: process.env.DOMAIN || process.env.CLOUDFLARE_DOMAIN || '', SUBDOMAIN: process.env.SUBDOMAIN || process.env.CLOUDFLARE_SUBDOMAIN || '', TTL: parseInt(process.env.TTL || process.env.CLOUDFLARE_TTL || '120', 10), PROXIED: process.env.PROXIED === 'true' || process.env.CLOUDFLARE_PROXIED === 'true', RETRY_ATTEMPTS: parseInt(process.env.RETRY_ATTEMPTS || process.env.CLOUDFLARE_RETRY_ATTEMPTS || '3', 10), RETRY_DELAY: parseInt(process.env.RETRY_DELAY || process.env.CLOUDFLARE_RETRY_DELAY || '5000', 10), LOG_FILE: process.env.LOG_FILE || process.env.CLOUDFLARE_LOG_FILE || this.getDefaultLogPath(), LAST_IP_FILE: process.env.LAST_IP_FILE || process.env.CLOUDFLARE_LAST_IP_FILE || this.getDefaultIpStoragePath(), IP_SERVICES: (process.env.IP_SERVICES || process.env.CLOUDFLARE_IP_SERVICES || 'ipify,ifconfig,ipinfo,seeip').split(','), FQDN: this.constructFqdn(process.env.SUBDOMAIN || process.env.CLOUDFLARE_SUBDOMAIN, process.env.DOMAIN || process.env.CLOUDFLARE_DOMAIN), API_VERSION: process.env.CLOUDFLARE_API_VERSION || '', API_URL: process.env.CLOUDFLARE_API_URL || '', AUTO_DETECT_API: process.env.CLOUDFLARE_AUTO_DETECT_API === 'true' }; // Apply direct configuration overrides if (directConfig) { if (directConfig.API_TOKEN !== undefined) this.config.API_TOKEN = directConfig.API_TOKEN; if (directConfig.ZONE_ID !== undefined) this.config.ZONE_ID = directConfig.ZONE_ID; if (directConfig.RECORD_ID !== undefined) this.config.RECORD_ID = directConfig.RECORD_ID; if (directConfig.DOMAIN !== undefined) this.config.DOMAIN = directConfig.DOMAIN; if (directConfig.SUBDOMAIN !== undefined) this.config.SUBDOMAIN = directConfig.SUBDOMAIN; if (directConfig.TTL !== undefined) this.config.TTL = directConfig.TTL; if (directConfig.PROXIED !== undefined) this.config.PROXIED = directConfig.PROXIED; if (directConfig.RETRY_ATTEMPTS !== undefined) this.config.RETRY_ATTEMPTS = directConfig.RETRY_ATTEMPTS; if (directConfig.RETRY_DELAY !== undefined) this.config.RETRY_DELAY = directConfig.RETRY_DELAY; if (directConfig.LOG_FILE !== undefined) this.config.LOG_FILE = directConfig.LOG_FILE; if (directConfig.LAST_IP_FILE !== undefined) this.config.LAST_IP_FILE = directConfig.LAST_IP_FILE; if (directConfig.IP_SERVICES !== undefined) this.config.IP_SERVICES = directConfig.IP_SERVICES; if (directConfig.FQDN !== undefined) this.config.FQDN = directConfig.FQDN; // Recalculate FQDN if domain or subdomain was provided directly if (directConfig.DOMAIN !== undefined || directConfig.SUBDOMAIN !== undefined) { this.config.FQDN = this.constructFqdn(directConfig.SUBDOMAIN || this.config.SUBDOMAIN, directConfig.DOMAIN || this.config.DOMAIN); } } this.ensureDirectoriesExist(); } /** * Sets a configuration value and updates dependent values * @param key Configuration key to set * @param value New value for the configuration key */ set(key, value) { this.config[key] = value; // Update FQDN when domain or subdomain changes if (key === 'DOMAIN' || key === 'SUBDOMAIN') { this.config.FQDN = this.constructFqdn(this.config.SUBDOMAIN, this.config.DOMAIN); } } /** * Builds a fully qualified domain name from subdomain and domain parts * @param subdomain Subdomain component * @param domain Domain component * @returns Formatted FQDN or empty string if insufficient parts */ constructFqdn(subdomain, domain) { if (!subdomain || subdomain.trim() === '') { return domain || ''; } return `${subdomain}.${domain || ''}`; } /** * Loads environment variables from multiple locations in priority order * Tries several common paths for .env files */ loadEnvironmentVariables() { // Possible config locations in order of precedence const configLocations = [ // 1. Local .env file (current directory) path.join(process.cwd(), '.env'), // 2. User home directory config path.join(os.homedir(), '.cloudflare-dyndns', '.env'), // 3. System config locations ...(process.platform === 'win32' ? [path.join(process.env.ProgramData || 'C:\\ProgramData', 'cloudflare-dyndns', '.env')] : ['/etc/cloudflare-dyndns/.env']), // 4. Package directory path.join(__dirname, '..', '..', '.env') ]; // Try loading from each location let loaded = false; for (const location of configLocations) { if (fs.existsSync(location)) { dotenv.config({ path: location }); console.log(`Loaded configuration from ${location}`); loaded = true; break; // Stop after first found config } } if (!loaded) { console.warn('No .env file found. Using environment variables or direct configuration.'); } } /** * Creates directories needed for logs and IP storage if they don't exist */ ensureDirectoriesExist() { const logDir = path.dirname(this.config.LOG_FILE); const ipDir = path.dirname(this.config.LAST_IP_FILE); [logDir, ipDir].forEach(dir => { if (!fs.existsSync(dir)) { try { fs.mkdirSync(dir, { recursive: true }); console.log(`Created directory: ${dir}`); } catch (error) { console.error(`Error creating directory ${dir}: ${error.message}`); } } }); } /** * Determines the appropriate log file path based on the current OS * @returns OS-specific default log path */ getDefaultLogPath() { if (process.platform === 'win32') { return path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'cloudflare-dyndns', 'logs', 'cloudflare_dyndns.log'); } else if (process.platform === 'darwin') { return path.join('/Library/Logs', 'cloudflare-dyndns', 'cloudflare_dyndns.log'); } else { // Try to use /var/log but fall back to user directory if permissions don't allow try { const varLogDir = '/var/log/cloudflare-dyndns'; if (!fs.existsSync(varLogDir)) { fs.mkdirSync(varLogDir, { recursive: true }); } return path.join(varLogDir, 'cloudflare_dyndns.log'); } catch (error) { // Fall back to user's home directory return path.join(os.homedir(), '.cloudflare-dyndns', 'logs', 'cloudflare_dyndns.log'); } } } /** * Determines the appropriate IP storage file path based on the current OS * @returns OS-specific default path for storing the last known IP */ getDefaultIpStoragePath() { if (process.platform === 'win32') { return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'cloudflare-dyndns', 'last_ip.txt'); } else if (process.platform === 'darwin') { return path.join(os.homedir(), 'Library', 'Application Support', 'cloudflare-dyndns', 'last_ip.txt'); } else { // Try to use /var/lib but fall back to user directory if permissions don't allow try { const varLibDir = '/var/lib/cloudflare-dyndns'; if (!fs.existsSync(varLibDir)) { fs.mkdirSync(varLibDir, { recursive: true }); } return path.join(varLibDir, 'last_ip.txt'); } catch (error) { // Fall back to user's home directory return path.join(os.homedir(), '.cloudflare-dyndns', 'last_ip.txt'); } } } /** * Validates that all required configuration parameters are present * @throws Error if required configuration is missing */ validate() { const missing = this.requiredEnvVars.filter(envVar => { const value = this.config[envVar]; return !value || (typeof value === 'string' && value.trim() === ''); }); if (missing.length > 0) { if (process.env.DEVELOPMENT_MODE === 'true') { console.error('\n🔧 DEVELOPMENT ERROR: Missing configuration parameters'); console.error('The following required parameters are missing or empty:'); missing.forEach(param => { console.error(` - ${param}`); }); console.error('\nTo fix this:'); console.error('1. Delete your .env file: rm .env'); console.error('2. Run the setup again: npm run dev:setup'); console.error('3. Make sure you enter values for ALL required fields\n'); } throw new Error(`Missing or empty required configuration: ${missing.join(', ')}\n\nPlease run 'cloudflare-dyndns-setup' to configure the application or provide the required parameters.`); } if (this.config.TTL < 60) { console.warn('WARNING: TTL less than 60 seconds may cause issues. Recommended minimum is 120 seconds.'); } } /** * Gets a specific configuration value * @param key Configuration key to retrieve * @returns Value for the specified configuration key */ get(key) { return this.config[key]; } /** * Gets a copy of the entire configuration object * @returns Complete configuration object */ getAll() { return { ...this.config }; } /** * Checks if all required configuration exists * @returns True if all required config parameters are present and non-empty */ configExists() { try { return this.requiredEnvVars.every(envVar => { const value = this.config[envVar]; return value && (typeof value !== 'string' || value.trim() !== ''); }); } catch (error) { return false; } } } exports.ConfigManager = ConfigManager; //# sourceMappingURL=ConfigManager.js.map