diff --git a/changelog.md b/changelog.md index 8cd0a1e..3051ee3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-08-30 - 5.4.0 - feat(daemon) +Add CLI systemd service refresh on version mismatch and fix daemon memory leak; update dependencies + +- CLI: when client and daemon versions differ, prompt to refresh the systemd service and optionally disable/enable the service automatically +- Daemon: clear pidusage state for PIDs on process exit/stop to prevent memory leaks in long-running monitors +- Client: expose smartdaemon in client plugin exports and fix import path for tspm.servicemanager +- Package: tighten dependency ranges (set specific versions) and add @types for pidusage and ps-tree +- Misc: ensure IPC disconnects and PID/socket handling improvements were integrated alongside the above changes + ## 2025-08-30 - 5.3.2 - fix(daemon) Improve daemon log delivery and process monitor memory accounting; gate debug output and update tests to numeric ProcessId diff --git a/package.json b/package.json index 6dcce6d..27695fd 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "@push.rocks/smartinteract": "^2.0.16", "@push.rocks/smartipc": "^2.2.2", "@push.rocks/smartpath": "^6.0.0", + "@types/pidusage": "^2.0.5", + "@types/ps-tree": "^1.1.6", "pidusage": "^4.0.1", "ps-tree": "^1.2.0", "tsx": "^4.20.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28e4940..37c2ad8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,12 @@ importers: '@push.rocks/smartpath': specifier: ^6.0.0 version: 6.0.0 + '@types/pidusage': + specifier: ^2.0.5 + version: 2.0.5 + '@types/ps-tree': + specifier: ^1.1.6 + version: 1.1.6 pidusage: specifier: ^4.0.1 version: 4.0.1 @@ -1647,9 +1653,15 @@ packages: '@types/parse5@6.0.3': resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + '@types/pidusage@2.0.5': + resolution: {integrity: sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==} + '@types/ping@0.4.4': resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==} + '@types/ps-tree@1.1.6': + resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==} + '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -7592,8 +7604,12 @@ snapshots: '@types/parse5@6.0.3': {} + '@types/pidusage@2.0.5': {} + '@types/ping@0.4.4': {} + '@types/ps-tree@1.1.6': {} + '@types/qs@6.14.0': {} '@types/randomatic@3.1.5': {} diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 6bbb64e..8cc04c7 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tspm', - version: '5.3.2', + version: '5.4.0', description: 'a no fuzz process manager' } diff --git a/ts/cli/index.ts b/ts/cli/index.ts index b61ce98..0534667 100644 --- a/ts/cli/index.ts +++ b/ts/cli/index.ts @@ -2,6 +2,7 @@ import * as plugins from './plugins.js'; import { tspmIpcClient } from '../client/tspm.ipcclient.js'; import * as paths from '../paths.js'; import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js'; +import { TspmServiceManager } from '../client/tspm.servicemanager.js'; // Import command registration functions import { registerDefaultCommand } from './commands/default.js'; @@ -51,6 +52,38 @@ export const run = async (): Promise => { console.log( `Daemon: running v${status.version || 'unknown'} (pid ${status.pid})`, ); + // If versions mismatch, offer to refresh the systemd service + if (status.version && status.version !== cliVersion) { + console.log('\nVersion mismatch detected:'); + console.log(` CLI: v${cliVersion}`); + console.log(` Daemon: v${status.version}`); + console.log( + '\nThis can happen after upgrading tspm. The systemd service may still point to an older version.\n' + + 'You can refresh the service (equivalent to "tspm disable" then "tspm enable").', + ); + + // Ask the user for confirmation + const confirm = await plugins.smartinteract.SmartInteract.getCliConfirmation( + 'Refresh the systemd service now?', + true, + ); + if (confirm) { + try { + const sm = new TspmServiceManager(); + console.log('Refreshing TSPM system service...'); + await sm.disableService(); + await sm.enableService(); + console.log('✓ Service refreshed. Daemon restarted via systemd.'); + } catch (err: any) { + console.error( + 'Failed to refresh service automatically. You can try manually:\n tspm disable && tspm enable', + ); + console.error(err?.message || String(err)); + } + } else { + console.log('Skipped service refresh.'); + } + } } else { console.log('Daemon: not running'); } diff --git a/ts/client/plugins.ts b/ts/client/plugins.ts index e5a0207..fcba51b 100644 --- a/ts/client/plugins.ts +++ b/ts/client/plugins.ts @@ -1,6 +1,7 @@ // Minimal plugin set for lightweight client startup import * as path from 'node:path'; +import * as smartdaemon from '@push.rocks/smartdaemon'; import * as smartipc from '@push.rocks/smartipc'; -export { path, smartipc }; +export { path, smartdaemon, smartipc }; diff --git a/ts/client/tspm.servicemanager.ts b/ts/client/tspm.servicemanager.ts index d3306ce..63f197f 100644 --- a/ts/client/tspm.servicemanager.ts +++ b/ts/client/tspm.servicemanager.ts @@ -1,4 +1,4 @@ -import * as plugins from '../plugins.js'; +import * as plugins from './plugins.js'; import * as paths from '../paths.js'; /** diff --git a/ts/daemon/processmonitor.ts b/ts/daemon/processmonitor.ts index 9c6f8c3..3aa4bf8 100644 --- a/ts/daemon/processmonitor.ts +++ b/ts/daemon/processmonitor.ts @@ -139,6 +139,14 @@ export class ProcessMonitor extends EventEmitter { this.logger.info(exitMsg); this.log(exitMsg); + // Clear pidusage internal state for this PID to prevent memory leaks + try { + const pidToClear = this.processWrapper?.getPid(); + if (pidToClear) { + (plugins.pidusage as any)?.clear?.(pidToClear); + } + } catch {} + // Flush logs to disk on exit if (this.processId && this.logs.length > 0) { try { @@ -385,6 +393,13 @@ export class ProcessMonitor extends EventEmitter { clearInterval(this.intervalId); } if (this.processWrapper) { + // Clear pidusage state for current PID before stopping to avoid leaks + try { + const pidToClear = this.processWrapper.getPid(); + if (pidToClear) { + (plugins.pidusage as any)?.clear?.(pidToClear); + } + } catch {} this.processWrapper.stop(); } }