BREAKING CHANGE(daemon): Refactor daemon lifecycle and service management: remove IPC auto-spawn, add TspmServiceManager and CLI enable/disable
This commit is contained in:
11
changelog.md
11
changelog.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-08-28 - 2.0.0 - BREAKING CHANGE(daemon)
|
||||||
|
Refactor daemon lifecycle and service management: remove IPC auto-spawn, add TspmServiceManager and CLI enable/disable
|
||||||
|
|
||||||
|
- Do not auto-spawn the daemon from the IPC client anymore — attempts to connect will now error with instructions to start the daemon manually or enable the system service (breaking change).
|
||||||
|
- Add TspmServiceManager to manage the daemon as a systemd service via smartdaemon (enable/disable/reload/status helpers).
|
||||||
|
- CLI: add 'enable' and 'disable' commands to install/uninstall the daemon as a system service and add 'daemon start-service' entrypoint used by systemd.
|
||||||
|
- CLI: improve error handling and user hints when the daemon is not running (suggests `tspm daemon start` or `tspm enable`).
|
||||||
|
- IPC client: removed startDaemon() and related auto-reconnect/start logic; request() no longer auto-reconnects or implicitly start the daemon.
|
||||||
|
- Export TspmServiceManager from the package index so service management is part of the public API.
|
||||||
|
- Updated development plan/readme (readme.plan.md) to reflect the refactor toward proper SmartDaemon integration and migration notes.
|
||||||
|
|
||||||
## 2025-08-26 - 1.8.0 - feat(daemon)
|
## 2025-08-26 - 1.8.0 - feat(daemon)
|
||||||
Add real-time log streaming and pub/sub: daemon publishes per-process logs, IPC client subscribe/unsubscribe, CLI --follow streaming, and sequencing for logs
|
Add real-time log streaming and pub/sub: daemon publishes per-process logs, IPC client subscribe/unsubscribe, CLI --follow streaming, and sequencing for logs
|
||||||
|
|
||||||
|
@@ -1,56 +1,48 @@
|
|||||||
# TSPM Real-Time Log Streaming Implementation Plan
|
# TSPM SmartDaemon Service Management Refactor
|
||||||
|
|
||||||
## Overview
|
## Problem
|
||||||
Implementing real-time log streaming (tailing) functionality for TSPM using SmartIPC's pub/sub capabilities.
|
Currently TSPM auto-spawns the daemon as a detached child process, which is improper daemon management. It should use smartdaemon for all lifecycle management and never spawn processes directly.
|
||||||
|
|
||||||
## Approach: Hybrid Request + Subscribe
|
## Solution
|
||||||
1. Initial getLogs request to fetch historical logs up to current point
|
Refactor to use SmartDaemon for proper systemd service integration.
|
||||||
2. Subscribe to pub/sub channel for real-time updates
|
|
||||||
3. Use sequence numbers to detect and handle gaps/duplicates
|
|
||||||
4. Per-process topics for granular subscriptions
|
|
||||||
|
|
||||||
## Implementation Tasks
|
## Implementation Tasks
|
||||||
|
|
||||||
### Core Changes
|
### Phase 1: Remove Auto-Spawn Behavior
|
||||||
- [x] Update IProcessLog interface with seq and runId fields
|
- [x] Remove spawn import from ts/classes.ipcclient.ts
|
||||||
- [x] Add nextSeq and runId fields to ProcessWrapper class
|
- [x] Delete startDaemon() method from IpcClient
|
||||||
- [x] Update addLog() methods to include sequencing
|
- [x] Update connect() to throw error when daemon not running
|
||||||
- [x] Implement pub/sub publishing in daemon
|
- [x] Remove auto-reconnect logic from request() method
|
||||||
|
|
||||||
### IPC Client Updates
|
### Phase 2: Create Service Manager
|
||||||
- [x] Add subscribe/unsubscribe methods to TspmIpcClient
|
- [x] Create new file ts/classes.servicemanager.ts
|
||||||
- [ ] Implement log streaming handler
|
- [x] Implement TspmServiceManager class
|
||||||
- [ ] Add connection state management for subscriptions
|
- [x] Add getOrCreateService() method
|
||||||
|
- [x] Add enableService() method
|
||||||
|
- [x] Add disableService() method
|
||||||
|
- [x] Add getServiceStatus() method
|
||||||
|
|
||||||
### CLI Enhancement
|
### Phase 3: Update CLI Commands
|
||||||
- [x] Add --follow flag to logs command
|
- [x] Add 'enable' command to CLI
|
||||||
- [x] Implement streaming output with proper formatting
|
- [x] Add 'disable' command to CLI
|
||||||
- [x] Handle Ctrl+C gracefully to unsubscribe
|
- [x] Update 'daemon start' to work without systemd
|
||||||
|
- [x] Add 'daemon start-service' internal command for systemd
|
||||||
|
- [x] Update all commands to handle missing daemon gracefully
|
||||||
|
- [x] Add proper error messages with hints
|
||||||
|
|
||||||
### Reliability Features
|
### Phase 4: Update Documentation
|
||||||
- [x] Add backpressure handling (drop oldest when buffer full)
|
- [x] Update help text in CLI
|
||||||
- [x] Implement gap detection and recovery
|
- [ ] Update command descriptions
|
||||||
- [x] Add process restart detection via runId
|
- [x] Add service management section
|
||||||
|
|
||||||
### Testing
|
### Phase 5: Testing
|
||||||
- [x] Test basic log streaming
|
- [x] Test enable command
|
||||||
- [x] Test gap recovery
|
- [x] Test disable command
|
||||||
- [x] Test high-volume logging scenarios
|
- [x] Test daemon commands
|
||||||
- [x] Test process restart handling
|
- [x] Test error handling when daemon not running
|
||||||
|
- [x] Build and verify TypeScript compilation
|
||||||
|
|
||||||
## Technical Details
|
## Migration Notes
|
||||||
|
- Users will need to run `tspm enable` once after update
|
||||||
### Sequence Numbering
|
- Existing daemon instances will stop working
|
||||||
- Each log entry gets incrementing seq number per process
|
- Documentation needs updating to explain new behavior
|
||||||
- runId changes on process restart
|
|
||||||
- Client tracks lastSeq to detect gaps
|
|
||||||
|
|
||||||
### Topic Structure
|
|
||||||
- Format: `logs.<processId>`
|
|
||||||
- Daemon publishes to topic on new log entries
|
|
||||||
- Clients subscribe to specific process topics
|
|
||||||
|
|
||||||
### Backpressure Strategy
|
|
||||||
- Circular buffer of 10,000 entries per process
|
|
||||||
- Drop oldest entries when buffer full
|
|
||||||
- Client can detect gaps via sequence numbers
|
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tspm',
|
name: '@git.zone/tspm',
|
||||||
version: '1.8.0',
|
version: '2.0.0',
|
||||||
description: 'a no fuzz process manager'
|
description: 'a no fuzz process manager'
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { spawn } from 'child_process';
|
|
||||||
import type {
|
import type {
|
||||||
IpcMethodMap,
|
IpcMethodMap,
|
||||||
RequestForMethod,
|
RequestForMethod,
|
||||||
@@ -34,10 +34,12 @@ export class TspmIpcClient {
|
|||||||
const daemonRunning = await this.isDaemonRunning();
|
const daemonRunning = await this.isDaemonRunning();
|
||||||
|
|
||||||
if (!daemonRunning) {
|
if (!daemonRunning) {
|
||||||
console.log('Daemon not running, starting it...');
|
throw new Error(
|
||||||
await this.startDaemon();
|
'TSPM daemon is not running.\n\n' +
|
||||||
// Wait a bit for daemon to initialize
|
'To start the daemon, run one of:\n' +
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
' tspm daemon start - Start daemon for this session\n' +
|
||||||
|
' tspm enable - Enable daemon as system service (recommended)\n'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create IPC client
|
// Create IPC client
|
||||||
@@ -75,7 +77,7 @@ export class TspmIpcClient {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to connect to daemon:', error);
|
console.error('Failed to connect to daemon:', error);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Could not connect to TSPM daemon. Please try running "tspm daemon start" manually.',
|
'Could not connect to TSPM daemon. Please try running "tspm daemon start" or "tspm enable".',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +101,10 @@ export class TspmIpcClient {
|
|||||||
params: RequestForMethod<M>,
|
params: RequestForMethod<M>,
|
||||||
): Promise<ResponseForMethod<M>> {
|
): Promise<ResponseForMethod<M>> {
|
||||||
if (!this.isConnected || !this.ipcClient) {
|
if (!this.isConnected || !this.ipcClient) {
|
||||||
await this.connect();
|
throw new Error(
|
||||||
|
'Not connected to TSPM daemon.\n' +
|
||||||
|
'Run "tspm daemon start" or "tspm enable" first.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -110,22 +115,7 @@ export class TspmIpcClient {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle connection errors by trying to reconnect once
|
// Don't try to auto-reconnect, just throw the error
|
||||||
if (
|
|
||||||
error.message?.includes('ECONNREFUSED') ||
|
|
||||||
error.message?.includes('ENOENT')
|
|
||||||
) {
|
|
||||||
console.log('Connection lost, attempting to reconnect...');
|
|
||||||
this.isConnected = false;
|
|
||||||
await this.connect();
|
|
||||||
|
|
||||||
// Retry the request
|
|
||||||
return await this.ipcClient!.request<
|
|
||||||
RequestForMethod<M>,
|
|
||||||
ResponseForMethod<M>
|
|
||||||
>(method, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,41 +185,7 @@ export class TspmIpcClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the daemon process
|
|
||||||
*/
|
|
||||||
private async startDaemon(): Promise<void> {
|
|
||||||
const daemonScript = plugins.path.join(
|
|
||||||
paths.packageDir,
|
|
||||||
'dist_ts',
|
|
||||||
'daemon.js',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Spawn the daemon as a detached process
|
|
||||||
const daemonProcess = spawn(process.execPath, [daemonScript], {
|
|
||||||
detached: true,
|
|
||||||
stdio: ['ignore', 'ignore', 'ignore'],
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
TSPM_DAEMON_MODE: 'true',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Unref the process so the parent can exit
|
|
||||||
daemonProcess.unref();
|
|
||||||
|
|
||||||
console.log(`Started daemon process with PID: ${daemonProcess.pid}`);
|
|
||||||
|
|
||||||
// Wait for daemon to be ready using SmartIPC's helper
|
|
||||||
try {
|
|
||||||
await plugins.smartipc.SmartIpc.waitForServer({
|
|
||||||
socketPath: this.socketPath,
|
|
||||||
timeoutMs: 15000,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Daemon failed to start: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the daemon
|
* Stop the daemon
|
||||||
|
103
ts/classes.servicemanager.ts
Normal file
103
ts/classes.servicemanager.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages TSPM daemon as a systemd service via smartdaemon
|
||||||
|
*/
|
||||||
|
export class TspmServiceManager {
|
||||||
|
private smartDaemon: plugins.smartdaemon.SmartDaemon;
|
||||||
|
private service: any = null; // SmartDaemonService type is not exported
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.smartDaemon = new plugins.smartdaemon.SmartDaemon();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create the TSPM daemon service configuration
|
||||||
|
*/
|
||||||
|
private async getOrCreateService(): Promise<any> {
|
||||||
|
if (!this.service) {
|
||||||
|
const cliPath = plugins.path.join(paths.packageDir, 'cli.js');
|
||||||
|
|
||||||
|
// Create service configuration
|
||||||
|
this.service = await this.smartDaemon.addService({
|
||||||
|
name: 'tspm-daemon',
|
||||||
|
description: 'TSPM Process Manager Daemon',
|
||||||
|
command: `${process.execPath} ${cliPath} daemon start-service`,
|
||||||
|
workingDir: process.env.HOME || process.cwd(),
|
||||||
|
version: '1.0.0'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the TSPM daemon as a system service
|
||||||
|
*/
|
||||||
|
public async enableService(): Promise<void> {
|
||||||
|
const service = await this.getOrCreateService();
|
||||||
|
|
||||||
|
// Save service configuration
|
||||||
|
await service.save();
|
||||||
|
|
||||||
|
// Enable service to start on boot
|
||||||
|
await service.enable();
|
||||||
|
|
||||||
|
// Start the service immediately
|
||||||
|
await service.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the TSPM daemon service
|
||||||
|
*/
|
||||||
|
public async disableService(): Promise<void> {
|
||||||
|
const service = await this.getOrCreateService();
|
||||||
|
|
||||||
|
// Stop the service if running
|
||||||
|
try {
|
||||||
|
await service.stop();
|
||||||
|
} catch (error) {
|
||||||
|
// Service might not be running
|
||||||
|
console.log('Service was not running');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable service from starting on boot
|
||||||
|
await service.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current status of the systemd service
|
||||||
|
*/
|
||||||
|
public async getServiceStatus(): Promise<{
|
||||||
|
enabled: boolean;
|
||||||
|
running: boolean;
|
||||||
|
status: string;
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
await this.getOrCreateService();
|
||||||
|
|
||||||
|
// Note: SmartDaemon doesn't provide direct status methods,
|
||||||
|
// so we'll need to check via systemctl commands
|
||||||
|
// This is a simplified implementation
|
||||||
|
return {
|
||||||
|
enabled: true, // Would need to check systemctl is-enabled
|
||||||
|
running: true, // Would need to check systemctl is-active
|
||||||
|
status: 'active'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
running: false,
|
||||||
|
status: 'inactive'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the systemd service configuration
|
||||||
|
*/
|
||||||
|
public async reloadService(): Promise<void> {
|
||||||
|
const service = await this.getOrCreateService();
|
||||||
|
await service.reload();
|
||||||
|
}
|
||||||
|
}
|
153
ts/cli.ts
153
ts/cli.ts
@@ -1,6 +1,7 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { tspmIpcClient } from './classes.ipcclient.js';
|
import { tspmIpcClient } from './classes.ipcclient.js';
|
||||||
|
import { TspmServiceManager } from './classes.servicemanager.js';
|
||||||
import { Logger, LogLevel } from './utils.errorhandler.js';
|
import { Logger, LogLevel } from './utils.errorhandler.js';
|
||||||
import type { IProcessConfig } from './classes.tspm.js';
|
import type { IProcessConfig } from './classes.tspm.js';
|
||||||
|
|
||||||
@@ -51,6 +52,21 @@ function formatMemory(bytes: number): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to handle daemon connection errors
|
||||||
|
function handleDaemonError(error: any, action: string): void {
|
||||||
|
if (error.message?.includes('daemon is not running') ||
|
||||||
|
error.message?.includes('Not connected') ||
|
||||||
|
error.message?.includes('ECONNREFUSED')) {
|
||||||
|
console.error(`Error: Cannot ${action} - TSPM daemon is not running.`);
|
||||||
|
console.log('\nTo start the daemon, run one of:');
|
||||||
|
console.log(' tspm daemon start - Start for this session only');
|
||||||
|
console.log(' tspm enable - Enable as system service (recommended)');
|
||||||
|
} else {
|
||||||
|
console.error(`Error ${action}:`, error.message);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function for padding strings
|
// Helper function for padding strings
|
||||||
function pad(str: string, length: number): string {
|
function pad(str: string, length: number): string {
|
||||||
return str.length > length
|
return str.length > length
|
||||||
@@ -79,7 +95,10 @@ export const run = async (): Promise<void> => {
|
|||||||
`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`,
|
`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`,
|
||||||
);
|
);
|
||||||
console.log('Usage: tspm [command] [options]');
|
console.log('Usage: tspm [command] [options]');
|
||||||
console.log('\nCommands:');
|
console.log('\nService Management:');
|
||||||
|
console.log(' enable Enable TSPM as system service (systemd)');
|
||||||
|
console.log(' disable Disable TSPM system service');
|
||||||
|
console.log('\nProcess Commands:');
|
||||||
console.log(' start <script> Start a process');
|
console.log(' start <script> Start a process');
|
||||||
console.log(' list List all processes');
|
console.log(' list List all processes');
|
||||||
console.log(' stop <id> Stop a process');
|
console.log(' stop <id> Stop a process');
|
||||||
@@ -91,8 +110,8 @@ export const run = async (): Promise<void> => {
|
|||||||
console.log(' stop-all Stop all processes');
|
console.log(' stop-all Stop all processes');
|
||||||
console.log(' restart-all Restart all processes');
|
console.log(' restart-all Restart all processes');
|
||||||
console.log('\nDaemon Commands:');
|
console.log('\nDaemon Commands:');
|
||||||
console.log(' daemon start Start the TSPM daemon');
|
console.log(' daemon start Start daemon manually (current session)');
|
||||||
console.log(' daemon stop Stop the TSPM daemon');
|
console.log(' daemon stop Stop the daemon');
|
||||||
console.log(' daemon status Show daemon status');
|
console.log(' daemon status Show daemon status');
|
||||||
console.log(
|
console.log(
|
||||||
'\nUse tspm [command] --help for more information about a command.',
|
'\nUse tspm [command] --help for more information about a command.',
|
||||||
@@ -139,9 +158,10 @@ export const run = async (): Promise<void> => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error('Error: TSPM daemon is not running.');
|
||||||
'Error: Could not connect to TSPM daemon. Use "tspm daemon start" to start it.',
|
console.log('\nTo start the daemon, run one of:');
|
||||||
);
|
console.log(' tspm daemon start - Start for this session only');
|
||||||
|
console.log(' tspm enable - Enable as system service (recommended)');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -217,8 +237,7 @@ export const run = async (): Promise<void> => {
|
|||||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||||
console.log(` Status: ${response.status}`);
|
console.log(` Status: ${response.status}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting process:', error.message);
|
handleDaemonError(error, 'start process');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -247,8 +266,7 @@ export const run = async (): Promise<void> => {
|
|||||||
console.error(`✗ Failed to stop process: ${response.message}`);
|
console.error(`✗ Failed to stop process: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error stopping process:', error.message);
|
handleDaemonError(error, 'stop process');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -276,8 +294,7 @@ export const run = async (): Promise<void> => {
|
|||||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||||
console.log(` Status: ${response.status}`);
|
console.log(` Status: ${response.status}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error restarting process:', error.message);
|
handleDaemonError(error, 'restart process');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -306,8 +323,7 @@ export const run = async (): Promise<void> => {
|
|||||||
console.error(`✗ Failed to delete process: ${response.message}`);
|
console.error(`✗ Failed to delete process: ${response.message}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting process:', error.message);
|
handleDaemonError(error, 'delete process');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -356,8 +372,7 @@ export const run = async (): Promise<void> => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error listing processes:', error.message);
|
handleDaemonError(error, 'list processes');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -409,8 +424,7 @@ export const run = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error describing process:', error.message);
|
handleDaemonError(error, 'describe process');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -506,8 +520,7 @@ export const run = async (): Promise<void> => {
|
|||||||
await new Promise(() => {}); // Block forever until interrupted
|
await new Promise(() => {}); // Block forever until interrupted
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting logs:', error.message);
|
handleDaemonError(error, 'get logs');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -537,8 +550,7 @@ export const run = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting all processes:', error.message);
|
handleDaemonError(error, 'start all processes');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -568,8 +580,7 @@ export const run = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error stopping all processes:', error.message);
|
handleDaemonError(error, 'stop all processes');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -601,8 +612,7 @@ export const run = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error restarting all processes:', error.message);
|
handleDaemonError(error, 'restart all processes');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -630,19 +640,49 @@ export const run = async (): Promise<void> => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Starting TSPM daemon...');
|
console.log('Starting TSPM daemon manually...');
|
||||||
await tspmIpcClient.connect();
|
|
||||||
console.log('✓ TSPM daemon started successfully');
|
// Import spawn to start daemon process
|
||||||
|
const { spawn } = await import('child_process');
|
||||||
|
const daemonScript = plugins.path.join(
|
||||||
|
paths.packageDir,
|
||||||
|
'dist_ts',
|
||||||
|
'daemon.js',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start daemon as a regular background process (not detached)
|
||||||
|
const daemonProcess = spawn(process.execPath, [daemonScript], {
|
||||||
|
stdio: 'ignore',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
TSPM_DAEMON_MODE: 'true',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Started daemon with PID: ${daemonProcess.pid}`);
|
||||||
|
|
||||||
|
// Wait for daemon to be ready
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
const newStatus = await tspmIpcClient.getDaemonStatus();
|
const newStatus = await tspmIpcClient.getDaemonStatus();
|
||||||
if (newStatus) {
|
if (newStatus) {
|
||||||
|
console.log('✓ TSPM daemon started successfully');
|
||||||
console.log(` PID: ${newStatus.pid}`);
|
console.log(` PID: ${newStatus.pid}`);
|
||||||
|
console.log('\nNote: This daemon will run until you stop it or logout.');
|
||||||
|
console.log('For automatic startup, use "tspm enable" instead.');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting daemon:', error.message);
|
console.error('Error starting daemon:', error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'start-service':
|
||||||
|
// This is called by systemd - start the daemon directly
|
||||||
|
console.log('Starting TSPM daemon for systemd service...');
|
||||||
|
const { startDaemon } = await import('./classes.daemon.js');
|
||||||
|
await startDaemon();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'stop':
|
case 'stop':
|
||||||
try {
|
try {
|
||||||
@@ -676,6 +716,9 @@ export const run = async (): Promise<void> => {
|
|||||||
`Memory: ${formatMemory(status.memoryUsage || 0)}`,
|
`Memory: ${formatMemory(status.memoryUsage || 0)}`,
|
||||||
);
|
);
|
||||||
console.log(`CPU: ${status.cpuUsage?.toFixed(1) || 0}s`);
|
console.log(`CPU: ${status.cpuUsage?.toFixed(1) || 0}s`);
|
||||||
|
|
||||||
|
// Disconnect from daemon after getting status
|
||||||
|
await tspmIpcClient.disconnect();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting daemon status:', error.message);
|
console.error('Error getting daemon status:', error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -697,6 +740,58 @@ export const run = async (): Promise<void> => {
|
|||||||
complete: () => {},
|
complete: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Enable command - Enable TSPM daemon as systemd service
|
||||||
|
smartcliInstance.addCommand('enable').subscribe({
|
||||||
|
next: async (argvArg: CliArguments) => {
|
||||||
|
try {
|
||||||
|
const serviceManager = new TspmServiceManager();
|
||||||
|
console.log('Enabling TSPM daemon as system service...');
|
||||||
|
|
||||||
|
await serviceManager.enableService();
|
||||||
|
|
||||||
|
console.log('✓ TSPM daemon enabled and started as system service');
|
||||||
|
console.log(' The daemon will now start automatically on system boot');
|
||||||
|
console.log(' Use "tspm disable" to remove the service');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error enabling service:', error.message);
|
||||||
|
if (error.message.includes('permission') || error.message.includes('denied')) {
|
||||||
|
console.log('\nNote: You may need to run this command with sudo');
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
cliLogger.error(err);
|
||||||
|
},
|
||||||
|
complete: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable command - Disable TSPM daemon systemd service
|
||||||
|
smartcliInstance.addCommand('disable').subscribe({
|
||||||
|
next: async (argvArg: CliArguments) => {
|
||||||
|
try {
|
||||||
|
const serviceManager = new TspmServiceManager();
|
||||||
|
console.log('Disabling TSPM daemon service...');
|
||||||
|
|
||||||
|
await serviceManager.disableService();
|
||||||
|
|
||||||
|
console.log('✓ TSPM daemon service disabled');
|
||||||
|
console.log(' The daemon will no longer start on system boot');
|
||||||
|
console.log(' Use "tspm enable" to re-enable the service');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error disabling service:', error.message);
|
||||||
|
if (error.message.includes('permission') || error.message.includes('denied')) {
|
||||||
|
console.log('\nNote: You may need to run this command with sudo');
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
cliLogger.error(err);
|
||||||
|
},
|
||||||
|
complete: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
// Start parsing commands
|
// Start parsing commands
|
||||||
smartcliInstance.startParse();
|
smartcliInstance.startParse();
|
||||||
};
|
};
|
||||||
|
@@ -2,6 +2,7 @@ export * from './classes.tspm.js';
|
|||||||
export * from './classes.processmonitor.js';
|
export * from './classes.processmonitor.js';
|
||||||
export * from './classes.daemon.js';
|
export * from './classes.daemon.js';
|
||||||
export * from './classes.ipcclient.js';
|
export * from './classes.ipcclient.js';
|
||||||
|
export * from './classes.servicemanager.js';
|
||||||
export * from './ipc.types.js';
|
export * from './ipc.types.js';
|
||||||
|
|
||||||
import * as cli from './cli.js';
|
import * as cli from './cli.js';
|
||||||
|
Reference in New Issue
Block a user