mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-22 11:13:26 +00:00
4b8fdf2156
Previously, configuration created via the web UI was lost when containers were recreated or updated, requiring users to reconfigure each time. This was due to the .env file being stored inside the container filesystem. Changes: - Add persistent volume mount for configuration directory in docker-compose.yml - Update ConfigApi to auto-detect Docker vs development environment - Configure dotenv loading to use persistent config path when available - Update file watcher to monitor correct .env file location - Remove conflicting host .env file mount - Update README with new volume configuration instructions Configuration now persists across container updates, eliminating the need for users to reconfigure after updates. Fixes #94 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
271 lines
11 KiB
JavaScript
271 lines
11 KiB
JavaScript
const { URL } = require('url');
|
|
|
|
// Align placeholder values with install script and .env.example
|
|
const placeholderValues = [
|
|
// Hostname parts - case-insensitive matching might be better if OS env vars differ.
|
|
// For now, direct case-sensitive include check.
|
|
'your-proxmox-ip-or-hostname',
|
|
'proxmox_host', // Substring for https://proxmox_host:8006 or similar
|
|
'YOUR_PBS_IP_OR_HOSTNAME', // For PBS host
|
|
|
|
// Token ID parts - these are more specific to example/guidance values
|
|
'user@pam!your-token-name', // Matches common PVE example format
|
|
'user@pbs!your-token-name', // Matches common PBS example format
|
|
'your-api-token-id', // Generic part often seen in examples
|
|
'user@pam!tokenid', // From original install script comment
|
|
'user@pbs!tokenid', // PBS variant of install script comment
|
|
|
|
// Secret parts
|
|
'your-token-secret-uuid', // Common PVE secret example
|
|
'your-pbs-token-secret-uuid', // Common PBS secret example
|
|
'YOUR_API_SECRET_HERE', // From original install script comment
|
|
'secret-uuid', // Specific value used in config.test.js
|
|
'your-api-token-uuid', // Specific value used in config.test.js for PROXMOX_TOKEN_SECRET
|
|
'your-port' // Specific value used in config.test.js for PROXMOX_PORT
|
|
];
|
|
|
|
// Error class for configuration issues
|
|
class ConfigurationError extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'ConfigurationError';
|
|
}
|
|
}
|
|
|
|
// Function to load PBS configuration
|
|
function loadPbsConfig(index = null) {
|
|
const suffix = index ? `_${index}` : '';
|
|
const hostVar = `PBS_HOST${suffix}`;
|
|
const tokenIdVar = `PBS_TOKEN_ID${suffix}`;
|
|
const tokenSecretVar = `PBS_TOKEN_SECRET${suffix}`;
|
|
const nodeNameVar = `PBS_NODE_NAME${suffix}`;
|
|
const portVar = `PBS_PORT${suffix}`;
|
|
const selfSignedVar = `PBS_ALLOW_SELF_SIGNED_CERTS${suffix}`;
|
|
|
|
const pbsHostUrl = process.env[hostVar];
|
|
if (!pbsHostUrl) {
|
|
return false; // No more PBS configs if PBS_HOST is missing
|
|
}
|
|
|
|
let pbsHostname = pbsHostUrl;
|
|
try {
|
|
const parsedUrl = new URL(pbsHostUrl);
|
|
pbsHostname = parsedUrl.hostname;
|
|
} catch (e) {
|
|
// console.warn(`WARN: Could not parse PBS_HOST URL "${pbsHostUrl}". Using full value as fallback name.`);
|
|
}
|
|
|
|
const pbsTokenId = process.env[tokenIdVar];
|
|
const pbsTokenSecret = process.env[tokenSecretVar];
|
|
|
|
let config = null;
|
|
let idPrefix = index ? `pbs_endpoint_${index}` : 'pbs_primary';
|
|
|
|
if (pbsTokenId && pbsTokenSecret) {
|
|
const pbsPlaceholders = placeholderValues.filter(p =>
|
|
pbsHostUrl.includes(p) || pbsTokenId.includes(p) || pbsTokenSecret.includes(p)
|
|
);
|
|
if (pbsPlaceholders.length > 0) {
|
|
console.warn(`WARN: Skipping PBS configuration ${index || 'primary'} (Token). Placeholder values detected for: ${pbsPlaceholders.join(', ')}`);
|
|
} else {
|
|
config = {
|
|
id: `${idPrefix}_token`,
|
|
authMethod: 'token',
|
|
name: process.env[nodeNameVar] || pbsHostname,
|
|
host: pbsHostUrl,
|
|
port: process.env[portVar] || '8007',
|
|
tokenId: pbsTokenId,
|
|
tokenSecret: pbsTokenSecret,
|
|
nodeName: process.env[nodeNameVar], // Keep nodeName field
|
|
allowSelfSignedCerts: process.env[selfSignedVar] !== 'false',
|
|
enabled: true
|
|
};
|
|
console.log(`INFO: Found PBS configuration ${index || 'primary'} with ID: ${config.id}, name: ${config.name}, host: ${config.host}`);
|
|
}
|
|
} else {
|
|
console.warn(`WARN: Partial PBS configuration found for ${hostVar}. Please set (${tokenIdVar} + ${tokenSecretVar}) along with ${hostVar}.`);
|
|
}
|
|
|
|
if (config) {
|
|
return { found: true, config: config }; // Return config if found
|
|
}
|
|
// Return found:true if host was set, but config was invalid/partial
|
|
return { found: !!pbsHostUrl, config: null };
|
|
}
|
|
|
|
|
|
// Main function to load all configurations
|
|
function loadConfiguration() {
|
|
// Only load .env file if not in test environment
|
|
if (process.env.NODE_ENV !== 'test') {
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const configDir = path.join(__dirname, '../config');
|
|
const configEnvPath = path.join(configDir, '.env');
|
|
const projectEnvPath = path.join(__dirname, '../.env');
|
|
|
|
if (fs.existsSync(configEnvPath)) {
|
|
require('dotenv').config({ path: configEnvPath });
|
|
} else {
|
|
require('dotenv').config({ path: projectEnvPath });
|
|
}
|
|
}
|
|
|
|
let isConfigPlaceholder = false; // Add this flag
|
|
|
|
// --- Proxmox Primary Endpoint Validation ---
|
|
const primaryRequiredEnvVars = [
|
|
'PROXMOX_HOST',
|
|
'PROXMOX_TOKEN_ID',
|
|
'PROXMOX_TOKEN_SECRET'
|
|
];
|
|
let missingVars = [];
|
|
let placeholderVars = [];
|
|
|
|
primaryRequiredEnvVars.forEach(varName => {
|
|
const value = process.env[varName];
|
|
if (!value) {
|
|
missingVars.push(varName);
|
|
} else if (placeholderValues.some(placeholder => value.includes(placeholder) || placeholder.includes(value))) {
|
|
placeholderVars.push(varName);
|
|
}
|
|
});
|
|
|
|
// Throw error only if required vars are MISSING
|
|
if (missingVars.length > 0) {
|
|
console.warn('--- Configuration Warning ---');
|
|
console.warn(`Missing required environment variables: ${missingVars.join(', ')}.`);
|
|
console.warn('Pulse will start in setup mode. Please configure via the web interface.');
|
|
|
|
// Return minimal configuration to allow server to start
|
|
return {
|
|
endpoints: [],
|
|
pbsConfigs: [],
|
|
isConfigPlaceholder: true
|
|
};
|
|
}
|
|
|
|
// Set the flag if placeholders were detected (but don't throw error)
|
|
if (placeholderVars.length > 0) {
|
|
isConfigPlaceholder = true;
|
|
// Ensure token ID placeholder is included if missing
|
|
if (process.env.PROXMOX_TOKEN_ID && !placeholderVars.includes('PROXMOX_TOKEN_ID')) {
|
|
const hostIdx = placeholderVars.indexOf('PROXMOX_HOST');
|
|
if (hostIdx !== -1) placeholderVars.splice(hostIdx + 1, 0, 'PROXMOX_TOKEN_ID');
|
|
else placeholderVars.push('PROXMOX_TOKEN_ID');
|
|
}
|
|
console.warn(`WARN: Primary Proxmox environment variables seem to contain placeholder values: ${placeholderVars.join(', ')}. Pulse may not function correctly until configured.`);
|
|
}
|
|
|
|
// --- Load All Proxmox Endpoint Configurations ---
|
|
const endpoints = [];
|
|
|
|
function createProxmoxEndpointConfig(idPrefix, index, hostEnv, portEnv, tokenIdEnv, tokenSecretEnv, enabledEnv, selfSignedEnv, nodeNameEnv) {
|
|
const host = process.env[hostEnv];
|
|
const tokenId = process.env[tokenIdEnv];
|
|
const tokenSecret = process.env[tokenSecretEnv];
|
|
const nodeName = process.env[nodeNameEnv];
|
|
|
|
// Basic validation for additional endpoints (primary is validated earlier)
|
|
if (index !== null && (!tokenId || !tokenSecret)) {
|
|
// console.warn(`WARN: Skipping endpoint ${index || idPrefix} (Host: ${host}). Missing token ID or secret.`);
|
|
return null;
|
|
}
|
|
if (index !== null && placeholderValues.some(p => host.includes(p) || tokenId.includes(p) || tokenSecret.includes(p))) {
|
|
// console.warn(`WARN: Skipping endpoint ${index || idPrefix} (Host: ${host}). Environment variables seem to contain placeholder values.`);
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: index ? `${idPrefix}_${index}` : idPrefix,
|
|
name: nodeName || host,
|
|
host: host,
|
|
port: process.env[portEnv] || '8006',
|
|
tokenId: tokenId,
|
|
tokenSecret: tokenSecret,
|
|
enabled: process.env[enabledEnv] !== 'false',
|
|
allowSelfSignedCerts: process.env[selfSignedEnv] !== 'false',
|
|
};
|
|
}
|
|
|
|
// Load primary endpoint (index null for helper)
|
|
const primaryEndpoint = createProxmoxEndpointConfig(
|
|
'primary',
|
|
null, // No index for primary
|
|
'PROXMOX_HOST',
|
|
'PROXMOX_PORT',
|
|
'PROXMOX_TOKEN_ID',
|
|
'PROXMOX_TOKEN_SECRET',
|
|
'PROXMOX_ENABLED',
|
|
'PROXMOX_ALLOW_SELF_SIGNED_CERTS',
|
|
'PROXMOX_NODE_NAME'
|
|
);
|
|
if (primaryEndpoint) { // Should always exist due to earlier checks, but good practice
|
|
endpoints.push(primaryEndpoint);
|
|
}
|
|
|
|
// Load additional Proxmox endpoints
|
|
let i = 2;
|
|
while (process.env[`PROXMOX_HOST_${i}`]) {
|
|
const additionalEndpoint = createProxmoxEndpointConfig(
|
|
'endpoint',
|
|
i,
|
|
`PROXMOX_HOST_${i}`,
|
|
`PROXMOX_PORT_${i}`,
|
|
`PROXMOX_TOKEN_ID_${i}`,
|
|
`PROXMOX_TOKEN_SECRET_${i}`,
|
|
`PROXMOX_ENABLED_${i}`,
|
|
`PROXMOX_ALLOW_SELF_SIGNED_CERTS_${i}`,
|
|
`PROXMOX_NODE_NAME_${i}`
|
|
);
|
|
if (additionalEndpoint) {
|
|
endpoints.push(additionalEndpoint);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (endpoints.length > 1) {
|
|
// console.log(`INFO: Loaded configuration for ${endpoints.length} Proxmox endpoints.`);
|
|
}
|
|
|
|
// --- Load All PBS Configurations ---
|
|
const pbsConfigs = [];
|
|
// Load primary PBS config
|
|
const primaryPbsResult = loadPbsConfig();
|
|
/* istanbul ignore else */ // Ignore else path - tested by 'should not add primary PBS config if host is set but tokens are missing'
|
|
if (primaryPbsResult.config) {
|
|
pbsConfigs.push(primaryPbsResult.config);
|
|
}
|
|
|
|
// Load additional PBS configs
|
|
let pbsIndex = 2;
|
|
let pbsResult = loadPbsConfig(pbsIndex);
|
|
while (pbsResult.found) { // Continue as long as a PBS_HOST_n was found
|
|
if (pbsResult.config) {
|
|
pbsConfigs.push(pbsResult.config);
|
|
}
|
|
pbsIndex++;
|
|
pbsResult = loadPbsConfig(pbsIndex);
|
|
}
|
|
|
|
if (pbsConfigs.length > 0) {
|
|
// console.log(`INFO: Loaded configuration for ${pbsConfigs.length} PBS instances.`);
|
|
} else {
|
|
// console.log("INFO: No PBS instances configured.");
|
|
}
|
|
|
|
// --- Final Validation ---
|
|
const enabledEndpoints = endpoints.filter(e => e.enabled);
|
|
if (enabledEndpoints.length === 0 && pbsConfigs.length === 0) {
|
|
// Throw error instead of exiting
|
|
throw new ConfigurationError('\n--- Configuration Error ---\nNo enabled Proxmox VE or PBS endpoints could be configured. Please check your .env file and environment variables.\n');
|
|
}
|
|
|
|
// console.log('INFO: Configuration loaded successfully.');
|
|
// Return the flag along with endpoints and pbsConfigs
|
|
return { endpoints, pbsConfigs, isConfigPlaceholder };
|
|
}
|
|
|
|
module.exports = { loadConfiguration, ConfigurationError }; // Export the function and error class
|