diff --git a/changelog.md b/changelog.md index 821811e..446f38f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-03-25 - 1.10.0 - feat(core) +Add update checking and version logging across startup components + +- In daemon.ts, log version info on startup and check for updates in the background using npm registry response +- In nupst.ts, implement getVersion, checkForUpdates, getUpdateStatus, and compareVersions functions with update notifications +- Establish bidirectional reference between Nupst and NupstSnmp to support version logging +- Update systemd service status output to include version information + ## 2025-03-25 - 1.9.0 - feat(cli) Add update command to CLI to update NUPST from repository and refresh the systemd service diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index de1dd36..901138b 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/nupst', - version: '1.9.0', + version: '1.10.0', description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' } diff --git a/ts/daemon.ts b/ts/daemon.ts index a29c51f..199c067 100644 --- a/ts/daemon.ts +++ b/ts/daemon.ts @@ -152,6 +152,21 @@ export class NupstDaemon { await this.loadConfig(); this.logConfigLoaded(); + // Log version information + this.snmp.getNupst().logVersionInfo(false); // Don't check for updates immediately on startup + + // Check for updates in the background + this.snmp.getNupst().checkForUpdates().then(updateAvailable => { + if (updateAvailable) { + const updateStatus = this.snmp.getNupst().getUpdateStatus(); + console.log('┌─ Update Available ───────────────────────┐'); + console.log(`│ Current Version: ${updateStatus.currentVersion}`); + console.log(`│ Latest Version: ${updateStatus.latestVersion}`); + console.log('│ Run "sudo nupst update" to update'); + console.log('└──────────────────────────────────────────┘'); + } + }).catch(() => {}); // Ignore errors checking for updates + // Start UPS monitoring this.isRunning = true; await this.monitor(); diff --git a/ts/nupst.ts b/ts/nupst.ts index 6daab77..976abfb 100644 --- a/ts/nupst.ts +++ b/ts/nupst.ts @@ -1,6 +1,9 @@ import { NupstSnmp } from './snmp.js'; import { NupstDaemon } from './daemon.js'; import { NupstSystemd } from './systemd.js'; +import { commitinfo } from './00_commitinfo_data.js'; +import { spawn } from 'child_process'; +import * as https from 'https'; /** * Main Nupst class that coordinates all components @@ -10,12 +13,15 @@ export class Nupst { private readonly snmp: NupstSnmp; private readonly daemon: NupstDaemon; private readonly systemd: NupstSystemd; + private updateAvailable: boolean = false; + private latestVersion: string = ''; /** * Create a new Nupst instance with all necessary components */ constructor() { this.snmp = new NupstSnmp(); + this.snmp.setNupst(this); // Set up bidirectional reference this.daemon = new NupstDaemon(this.snmp); this.systemd = new NupstSystemd(this.daemon); } @@ -40,4 +46,144 @@ export class Nupst { public getSystemd(): NupstSystemd { return this.systemd; } + + /** + * Get the current version of NUPST + * @returns The current version string + */ + public getVersion(): string { + return commitinfo.version; + } + + /** + * Check if an update is available + * @returns Promise resolving to true if an update is available + */ + public async checkForUpdates(): Promise { + try { + const latestVersion = await this.getLatestVersion(); + const currentVersion = this.getVersion(); + + // Compare versions + this.updateAvailable = this.compareVersions(latestVersion, currentVersion) > 0; + this.latestVersion = latestVersion; + + return this.updateAvailable; + } catch (error) { + console.error(`Error checking for updates: ${error.message}`); + return false; + } + } + + /** + * Get update status information + * @returns Object with update status information + */ + public getUpdateStatus(): { + currentVersion: string, + latestVersion: string, + updateAvailable: boolean + } { + return { + currentVersion: this.getVersion(), + latestVersion: this.latestVersion || this.getVersion(), + updateAvailable: this.updateAvailable + }; + } + + /** + * Get the latest version from npm registry + * @returns Promise resolving to the latest version string + */ + private async getLatestVersion(): Promise { + return new Promise((resolve, reject) => { + const options = { + hostname: 'registry.npmjs.org', + path: '/@serve.zone/nupst', + method: 'GET', + headers: { + 'Accept': 'application/json', + 'User-Agent': `nupst/${this.getVersion()}` + } + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + try { + const response = JSON.parse(data); + if (response['dist-tags'] && response['dist-tags'].latest) { + resolve(response['dist-tags'].latest); + } else { + reject(new Error('Failed to parse version from npm registry response')); + } + } catch (error) { + reject(error); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.end(); + }); + } + + /** + * Compare two semantic version strings + * @param versionA First version + * @param versionB Second version + * @returns -1 if versionA < versionB, 0 if equal, 1 if versionA > versionB + */ + private compareVersions(versionA: string, versionB: string): number { + const partsA = versionA.split('.').map(part => parseInt(part, 10)); + const partsB = versionB.split('.').map(part => parseInt(part, 10)); + + for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { + const partA = i < partsA.length ? partsA[i] : 0; + const partB = i < partsB.length ? partsB[i] : 0; + + if (partA > partB) return 1; + if (partA < partB) return -1; + } + + return 0; // Versions are equal + } + + /** + * Log the current version and update status + */ + public logVersionInfo(checkForUpdates: boolean = true): void { + const version = this.getVersion(); + console.log('┌─ NUPST Version ────────────────────────┐'); + console.log(`│ Current Version: ${version}`); + + if (this.updateAvailable && this.latestVersion) { + console.log(`│ Update Available: ${this.latestVersion}`); + console.log('│ Run "sudo nupst update" to update'); + } else if (checkForUpdates) { + console.log('│ Checking for updates...'); + this.checkForUpdates().then(updateAvailable => { + if (updateAvailable) { + console.log(`│ Update Available: ${this.latestVersion}`); + console.log('│ Run "sudo nupst update" to update'); + } else { + console.log('│ You are running the latest version'); + } + console.log('└──────────────────────────────────────────┘'); + }).catch(() => { + console.log('│ Could not check for updates'); + console.log('└──────────────────────────────────────────┘'); + }); + } else { + console.log('└──────────────────────────────────────────┘'); + } + } } diff --git a/ts/snmp/manager.ts b/ts/snmp/manager.ts index 5dbdb20..1f4b07d 100644 --- a/ts/snmp/manager.ts +++ b/ts/snmp/manager.ts @@ -15,6 +15,8 @@ const execAsync = promisify(exec); export class NupstSnmp { // Active OID set private activeOIDs: OIDSet; + // Reference to the parent Nupst instance + private nupst: any; // Type 'any' to avoid circular dependency // Default SNMP configuration private readonly DEFAULT_CONFIG: SnmpConfig = { @@ -43,6 +45,21 @@ export class NupstSnmp { this.activeOIDs = UpsOidSets.getOidSet('cyberpower'); } + /** + * Set reference to the main Nupst instance + * @param nupst Reference to the main Nupst instance + */ + public setNupst(nupst: any): void { + this.nupst = nupst; + } + + /** + * Get reference to the main Nupst instance + */ + public getNupst(): any { + return this.nupst; + } + /** * Set active OID set based on UPS model * @param config SNMP configuration diff --git a/ts/systemd.ts b/ts/systemd.ts index 9820e3f..5da0e34 100644 --- a/ts/systemd.ts +++ b/ts/systemd.ts @@ -129,6 +129,9 @@ WantedBy=multi-user.target */ public async getStatus(): Promise { try { + // Display version information + this.daemon.getNupstSnmp().getNupst().logVersionInfo(); + // Check if config exists first try { await this.checkConfigExists();