feat: Add PBS docs, increase recent task limit, clean up logs

This commit is contained in:
courtmanr@gmail.com
2025-04-28 10:38:57 +01:00
parent efb7599723
commit 3845b140fa
2 changed files with 91 additions and 29 deletions
+59
View File
@@ -14,6 +14,7 @@ A lightweight monitoring application for Proxmox VE that displays real-time stat
- [Configuration](#-configuration)
- [Environment Variables](#environment-variables)
- [Creating a Proxmox API Token](#creating-a-proxmox-api-token)
- [Creating a Proxmox Backup Server API Token](#creating-a-proxmox-backup-server-api-token)
- [Required Permissions](#required-permissions)
- [Installation](#-installation)
- [Running the Application](#-running-the-application)
@@ -62,6 +63,36 @@ A lightweight monitoring application for Proxmox VE that displays real-time stat
If you only need to monitor a single Proxmox cluster or node, you only need to set the primary variables (`PROXMOX_HOST`, `PROXMOX_TOKEN_ID`, `PROXMOX_TOKEN_SECRET`).
**Proxmox Backup Server (PBS) Configuration (Optional):**
Pulse can also monitor backup status information from a Proxmox Backup Server instance. If you want to enable this feature, configure the following environment variables:
- `PBS_HOST`: URL of your Proxmox Backup Server (e.g., `https://your-pbs-ip-or-hostname:8007`).
- `PBS_TOKEN_ID`: Your PBS API Token ID (e.g., `user@pam!tokenid`). Create this in the PBS UI (see below).
- `PBS_TOKEN_SECRET`: Your PBS API Token Secret.
- `PBS_NODE_NAME`: (Potentially Required) The internal hostname of your PBS server. **Crucially, this might be different from the hostname used in `PBS_HOST`**. See detailed explanation below.
- `PBS_ALLOW_SELF_SIGNED_CERTS`: (Optional) Set to `true` if your PBS server uses self-signed SSL certificates. Defaults to `false`.
- `PBS_PORT`: (Optional) Port for the PBS API. Defaults to `8007`.
*Note: Currently, Pulse only supports monitoring a single PBS instance. Numbered variables like `PBS_HOST_2` are not yet supported.*
**Why `PBS_NODE_NAME` is Important:**
Pulse needs to query task lists specific to the PBS node (e.g., `/api2/json/nodes/{nodeName}/tasks`). While Pulse attempts to discover this node name automatically, this can fail due to API limitations or specific permission issues (like accessing `/api2/json/nodes`, which is not a standard PBS management endpoint).
If Pulse cannot fetch task data correctly (which might result in recent backups showing as 'stale'), you likely need to set `PBS_NODE_NAME` manually.
**How to find your PBS Node Name:**
1. **SSH:** Log into your PBS server via SSH and run the command `hostname`. The output is the value needed for `PBS_NODE_NAME`.
2. **UI:** Log into the PBS web interface. The hostname is typically displayed on the main Dashboard under Server Status.
3. **MOTD:** The hostname is often shown in the Message of the Day when you log in via SSH.
Example: If your PBS connects via `https://minipc-pbs.lan:8007` but its internal hostname is `proxmox-backup-server`, you would set:
```
PBS_HOST=https://minipc-pbs.lan:8007
PBS_NODE_NAME=proxmox-backup-server
```
### Creating a Proxmox API Token
An API token is recommended for connecting Pulse to Proxmox.
@@ -103,6 +134,34 @@ An API token is recommended for connecting Pulse to Proxmox.
5. **Update your `server/.env` file** with the `Token ID` (which looks like `user@realm!tokenid`, e.g., `pulse-monitor@pam!pulse`) and the `Secret` you saved.
### Creating a Proxmox Backup Server API Token
If you are configuring PBS monitoring, you need a separate API token created within PBS.
1. **Log in to the Proxmox Backup Server web interface**
2. **Create a dedicated user** (optional but recommended)
* Go to `Configuration` → `Access Control` → `User Management`.
* Click `Add`.
* Enter a `User ID` (e.g., "pulse-monitor@pam"), set other fields as needed, and click `Add`.
3. **Create an API token**
* Under `Configuration` → `Access Control`, select `API Token`.
* Click `Add`.
* Select the `User` (e.g., "pulse-monitor@pam") or `root@pam`.
* Enter a `Token Name` (e.g., "pulse").
* Leave `Privilege Separation` checked.
* Click `Add`.
* **Important:** Copy the displayed `Secret` value immediately.
4. **Assign permissions**
* Under `Configuration` → `Access Control`, select `Permissions`.
* Click `Add` → `API Token Permission`.
* Path: `/datastore` (This grants access to view datastores and their contents, including backup snapshots).
* API Token: Select the token you created (e.g., "pulse-monitor@pam!pulse").
* Role: `DatastoreAudit` (Provides read-only access to datastore contents and backups).
* Ensure `Propagate` is checked.
* Click `Add`.
* *(Note: While `DatastoreAudit` on `/datastore` is usually sufficient for backup status, if you still encounter issues fetching task lists, you might need broader permissions like `Audit` on path `/` for the token, although this has shown inconsistent behaviour with API tokens vs user sessions in some PBS versions).*
5. **Update your `server/.env` file** with the PBS `Token ID` (`PBS_TOKEN_ID`) and the `Secret` (`PBS_TOKEN_SECRET`).
### Required Permissions
The `PVEAuditor` role is recommended as it provides the necessary read-only permissions for Pulse to monitor your Proxmox environment:
+32 -29
View File
@@ -301,7 +301,7 @@ async function initializeAllPbsClients() {
});
clientData = { client: pbsAxiosInstance, config: config };
console.log(`INFO: [PBS Init] Successfully initialized client for instance '${config.name}' (Token Auth)`);
console.log(`INFO: Successfully initialized client for instance '${config.name}' (Token Auth)`);
} else {
// This case should not be reachable anymore if loadPbsConfig only creates 'token' authMethod configs
console.error(`ERROR: Unexpected authMethod '${config.authMethod}' found during PBS client initialization for: ${config.name}`);
@@ -311,12 +311,12 @@ async function initializeAllPbsClients() {
pbsApiClients[config.id] = clientData; // Store successful client keyed by config ID
}
} catch (error) {
console.error(`ERROR: Unhandled exception during PBS client initialization for ${config.name}: ${error.message}`);
console.error(`ERROR: Unhandled exception during PBS client initialization for ${config.name}: ${error.message}`, error.stack);
}
});
await Promise.allSettled(initPromises);
console.log(`INFO: [PBS Init] Finished initialization. ${Object.keys(pbsApiClients).length} / ${pbsConfigs.length} PBS clients initialized successfully.`);
console.log(`INFO: Finished initialization. ${Object.keys(pbsApiClients).length} / ${pbsConfigs.length} PBS clients initialized successfully.`);
}
if (Object.keys(apiClients).length === 0 && pbsConfigs.length === 0) {
@@ -1205,7 +1205,7 @@ async function fetchPbsNodeName(pbsClient) {
return 'localhost'; // Fallback
}
} catch (error) {
console.error(`ERROR: Failed to fetch PBS nodes list: ${error.message}`);
console.error(`ERROR: Failed to fetch PBS nodes list for ${pbsClient.config.name}: ${error.message}`, error.stack);
return 'localhost'; // Fallback on error
}
}
@@ -1225,21 +1225,22 @@ async function fetchAllPbsTasksForProcessing(pbsClient, nodeName) {
return { tasks: null, error: true };
}
try {
const sinceTimestamp = Math.floor((Date.now() - 7 * 24 * 60 * 60 * 1000) / 1000);
const response = await pbsClient.client.get(`/nodes/${nodeName}/tasks`, {
params: {
since: sinceTimestamp,
limit: 1000, // Fetch a larger number to cover 7 days of various tasks
errors: 1,
}
});
const sinceTimestamp = Math.floor((Date.now() - 7 * 24 * 60 * 60 * 1000) / 1000); // RESTORED
const requestUrl = `/nodes/${nodeName}/tasks`;
const requestParams = {
since: sinceTimestamp, // RESTORED
limit: 1000, // Kept (was 1000 after increasing from 20)
errors: 1, // RESTORED
};
const paramsToSend = Object.keys(requestParams).length > 0 ? { params: requestParams } : {};
const response = await pbsClient.client.get(requestUrl, paramsToSend);
const allTasks = response.data?.data ?? [];
console.log(`INFO: Fetched ${allTasks.length} tasks from PBS for processing.`);
return { tasks: allTasks, error: false };
} catch (error) {
console.error(`ERROR: Failed to fetch PBS task list for node ${nodeName}: ${error.message}`);
if (error.response?.status === 401) console.error("ERROR: PBS API authentication failed (401).");
else if (error.response?.status === 403) console.error("ERROR: PBS API authorization failed (403).");
console.error(`ERROR: Failed to fetch PBS task list for node ${nodeName} (${pbsClient.config.name}): ${error.message}`, error.stack);
if (error.response) { // Log more detail if available
console.error(`Error details: Status=${error.response.status}, Data=${JSON.stringify(error.response.data)}`);
}
return { tasks: null, error: true };
}
}
@@ -1305,10 +1306,10 @@ function processPbsTasks(allTasks) {
// Process and sort recent tasks for each category
const sortTasksDesc = (a, b) => (b.startTime || 0) - (a.startTime || 0);
const recentBackupTasks = taskResults.backup.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
const recentVerifyTasks = taskResults.verify.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
const recentSyncTasks = taskResults.sync.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
const recentPruneGcTasks = taskResults.pruneGc.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
const recentBackupTasks = taskResults.backup.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
const recentVerifyTasks = taskResults.verify.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
const recentSyncTasks = taskResults.sync.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
const recentPruneGcTasks = taskResults.pruneGc.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
console.log(`INFO: Processed PBS Tasks - Backup: ${taskResults.backup.list.length} (OK: ${taskResults.backup.ok}, Fail: ${taskResults.backup.failed}), Verify: ${taskResults.verify.list.length} (OK: ${taskResults.verify.ok}, Fail: ${taskResults.verify.failed}), Sync: ${taskResults.sync.list.length} (OK: ${taskResults.sync.ok}, Fail: ${taskResults.sync.failed}), Prune/GC: ${taskResults.pruneGc.list.length} (OK: ${taskResults.pruneGc.ok}, Fail: ${taskResults.pruneGc.failed})`);
@@ -1366,11 +1367,13 @@ async function fetchPbsTaskSummaryByType(pbsClient, nodeName, taskTypes) {
async function fetchPbsDatastoreData(pbsClient) {
// Fetches datastore usage details from PBS using the /status/datastore-usage endpoint
console.log("INFO: Fetching PBS datastore data...");
console.log(`INFO: Fetching PBS datastore data for ${pbsClient.config.name}...`);
let datastores = [];
const primaryUrl = '/status/datastore-usage';
const fallbackUrl = '/config/datastore';
try {
// Fetch usage stats for all datastores at once
const usageResponse = await pbsClient.client.get('/status/datastore-usage');
const usageResponse = await pbsClient.client.get(primaryUrl);
const usageData = usageResponse.data?.data ?? [];
if (usageData.length > 0) {
@@ -1391,14 +1394,15 @@ async function fetchPbsDatastoreData(pbsClient) {
}
} catch (usageError) {
console.error(`ERROR: Failed to fetch PBS datastore usage via /status/datastore-usage: ${usageError.message}. Trying fallback /config/datastore.`);
console.error(`ERROR: Failed to fetch PBS datastore usage via ${primaryUrl} for ${pbsClient.config.name}: ${usageError.message}. Trying fallback ${fallbackUrl}.`, usageError.stack);
// --- Fallback Logic ---
try {
const configResponse = await pbsClient.client.get('/config/datastore');
const configResponse = await pbsClient.client.get(fallbackUrl);
const datastoresConfig = configResponse.data?.data ?? [];
if (datastoresConfig.length > 0) {
console.log(`INFO: Fetched config for ${datastoresConfig.length} PBS datastores (fallback). Status unavailable.`);
datastores = datastoresConfig.map(dsConfig => ({
// Map the received data to the expected format
datastores = datastoresConfig.map(dsConfig => ({
name: dsConfig.name,
path: dsConfig.path,
total: null, // Usage/Status info unavailable from config
@@ -1410,13 +1414,12 @@ async function fetchPbsDatastoreData(pbsClient) {
console.warn("WARN: Fallback fetch of PBS datastore config also returned empty data.");
}
} catch (configError) {
console.error(`ERROR: Fallback fetch of PBS datastore config failed: ${configError.message}`);
if (configError.response?.status === 401 || configError.response?.status === 403) {
console.error("ERROR: PBS API authentication/authorization failed for datastore config access.");
console.error(`ERROR: Fallback fetch of PBS datastore config (${fallbackUrl}) for ${pbsClient.config.name}: ${configError.message}`, configError.stack);
if (configError.response) { // Log more detail if available
console.error(`Fallback error details: Status=${configError.response.status}, Data=${JSON.stringify(configError.response.data)}`);
}
// Keep datastores as empty array if both primary and fallback attempts fail
}
// --- End Fallback ---
}
console.log(`INFO: Finished fetching PBS datastore data. Found ${datastores.length} datastores.`);