Compare commits
35 Commits
b06e2b2273
...
v2.6.17
Author | SHA1 | Date | |
---|---|---|---|
bd3042de25 | |||
456351ca34 | |||
00afa317ef | |||
45ee8208b5 | |||
39bf3e2239 | |||
f3de3f0618 | |||
03056d279d | |||
f860f39e59 | |||
fa4516de3b | |||
539547beb8 | |||
6eb92959ec | |||
4af9af0845 | |||
f7e12cdcbb | |||
002498b91b | |||
459911fe5f | |||
9859a02ea2 | |||
65444b6d25 | |||
d049e8741f | |||
1123a99aea | |||
d01e878310 | |||
588aeabf4b | |||
87005e72f1 | |||
f799c2ee66 | |||
1a029ba493 | |||
5b756dd223 | |||
4cac599a58 | |||
be6a7314c3 | |||
83ba9c2611 | |||
22ab472e58 | |||
9a77030377 | |||
ceff285ff5 | |||
d8bfbf0be3 | |||
3e6b883b38 | |||
47ef918128 | |||
5951638967 |
113
changelog.md
113
changelog.md
@ -1,5 +1,118 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.17 - fix(logger)
|
||||||
|
Preserve logbox width after logBoxEnd so that subsequent logBoxLine calls continue using the set width.
|
||||||
|
|
||||||
|
- Removed the reset of currentBoxWidth in logBoxEnd to allow persistent width across logbox calls.
|
||||||
|
- Ensures that logBoxLine uses the previously set width when no new width is provided.
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.16 - fix(cli)
|
||||||
|
Improve CLI logging consistency by replacing direct console output with unified logger calls.
|
||||||
|
|
||||||
|
- Replaced console.log and console.error with logger.log and logger.error in CLI commands
|
||||||
|
- Standardized debug, error, and status messages using logger's logbox utilities
|
||||||
|
- Enhanced consistency of log output throughout the ts/cli.ts file
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.15 - fix(logger)
|
||||||
|
Replace direct console logging with unified logger interface for consistent formatting
|
||||||
|
|
||||||
|
- Substitute console.log, console.error, and related calls with logger methods in cli, daemon, systemd, nupst, and index modules
|
||||||
|
- Integrate logBox formatting for structured output and consistent log presentation
|
||||||
|
- Update test expectations in test.logger.ts to check for standardized error messages
|
||||||
|
- Refactor logging calls throughout the codebase for improved clarity and maintainability
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.14 - fix(systemd)
|
||||||
|
Shorten closing log divider in systemd service installation output for consistent formatting.
|
||||||
|
|
||||||
|
- Replaced the overly long footer with a shorter one in ts/systemd.ts.
|
||||||
|
- This change improves log readability without affecting functionality.
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.13 - fix(cli)
|
||||||
|
Fix CLI update output box formatting
|
||||||
|
|
||||||
|
- Adjusted the closing box line in the update process log messages for consistent visual formatting
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.12 - fix(systemd)
|
||||||
|
Adjust logging border in systemd service installation output
|
||||||
|
|
||||||
|
- Updated the closing border line for consistent output formatting in ts/systemd.ts
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.11 - fix(cli, systemd)
|
||||||
|
Adjust log formatting for consistent output in CLI and systemd commands
|
||||||
|
|
||||||
|
- Fixed spacing issues in service installation and status log messages in the systemd module.
|
||||||
|
- Revised output formatting in the CLI to improve message clarity.
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.10 - fix(daemon)
|
||||||
|
Adjust console log box formatting for consistent output in daemon status messages
|
||||||
|
|
||||||
|
- Updated closing box borders to align properly in configuration error, periodic updates, and UPS status logs
|
||||||
|
- Improved visual consistency in log messages
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.9 - fix(cli)
|
||||||
|
Improve console output formatting for status banners and logging messages
|
||||||
|
|
||||||
|
- Standardize banner messages in daemon status updates
|
||||||
|
- Refine version information banner in nupst logging
|
||||||
|
- Update UPS connection and status banners in systemd
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.8 - fix(cli)
|
||||||
|
Improve CLI formatting, refine debug option filtering, and remove unused dgram import in SNMP manager
|
||||||
|
|
||||||
|
- Standardize whitespace and formatting in ts/cli.ts for consistency
|
||||||
|
- Refine argument filtering for debug mode and prompt messages
|
||||||
|
- Remove unused 'dgram' import from ts/snmp/manager.ts
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.7 - fix(setup.sh)
|
||||||
|
Clarify net-snmp dependency installation message in setup.sh
|
||||||
|
|
||||||
|
- Updated echo statement to indicate installation of net-snmp along with 2 subdependencies
|
||||||
|
- Improves clarity on dependency installation during setup
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.6 - fix(setup.sh)
|
||||||
|
Improve setup script to detect and execute npm-cli.js directly using the Node.js binary
|
||||||
|
|
||||||
|
- Replace use of the npm binary with direct execution of npm-cli.js
|
||||||
|
- Add fallback logic to locate npm-cli.js when not found at the expected path
|
||||||
|
- Simplify cleanup by removing unnecessary PATH modifications
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.5 - fix(daemon, setup)
|
||||||
|
Improve shutdown command detection and fallback logic; update setup script to use absolute Node/npm paths
|
||||||
|
|
||||||
|
- Use execFileAsync to execute shutdown commands reliably
|
||||||
|
- Add multiple fallback alternatives for shutdown and emergency shutdown handling
|
||||||
|
- Update setup.sh to log the Node and NPM versions using absolute paths without modifying PATH
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.4 - fix(setup)
|
||||||
|
Improve installation process in setup script by cleaning up package files and ensuring a minimal net-snmp dependency installation.
|
||||||
|
|
||||||
|
- Remove existing package-lock.json along with node_modules to prevent stale artifacts.
|
||||||
|
- Back up the original package.json before modifying it.
|
||||||
|
- Create a minimal package.json with only the net-snmp dependency based on the backed-up version.
|
||||||
|
- Use a clean install to guarantee that only net-snmp is installed.
|
||||||
|
- Restore the original package.json if the installation fails.
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.3 - fix(setup)
|
||||||
|
Update setup script to install only net-snmp dependency and create a minimal package-lock.json for better dependency control.
|
||||||
|
|
||||||
|
- Removed full production dependency install in favor of installing only net-snmp@3.20.0
|
||||||
|
- Added verification step to confirm net-snmp installation
|
||||||
|
- Generate a minimal package-lock.json if one does not exist
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.2 - fix(setup/readme)
|
||||||
|
Improve force update instructions and dependency installation process in setup.sh and readme.md
|
||||||
|
|
||||||
|
- Clarify force update commands with explicit paths in readme.md
|
||||||
|
- Remove existing node_modules before installing dependencies in setup.sh
|
||||||
|
- Switch from 'npm ci --only=production' to 'npm install --omit=dev' with updated error instructions
|
||||||
|
|
||||||
|
## 2025-03-26 - 2.6.1 - fix(setup)
|
||||||
|
Update setup.sh to temporarily add vendor Node.js binary to PATH for dependency installation, log Node and npm versions, and restore the original PATH afterwards.
|
||||||
|
|
||||||
|
- Temporarily prepend vendor Node.js binary directory to PATH to ensure proper npm execution.
|
||||||
|
- Log Node.js and npm versions for debugging purposes.
|
||||||
|
- Restore the original PATH after installing dependencies.
|
||||||
|
|
||||||
## 2025-03-26 - 2.6.0 - feat(setup)
|
## 2025-03-26 - 2.6.0 - feat(setup)
|
||||||
Add --force update flag to setup script and update installation instructions
|
Add --force update flag to setup script and update installation instructions
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "2.6.0",
|
"version": "2.6.17",
|
||||||
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -56,5 +56,6 @@
|
|||||||
"mongodb-memory-server",
|
"mongodb-memory-server",
|
||||||
"puppeteer"
|
"puppeteer"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||||
}
|
}
|
||||||
|
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@ -9,7 +9,7 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
net-snmp:
|
net-snmp:
|
||||||
specifier: ^3.20.0
|
specifier: 3.20.0
|
||||||
version: 3.20.0
|
version: 3.20.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
|
@ -234,7 +234,11 @@ This will:
|
|||||||
You can also manually run the setup script with the force flag to update Node.js and dependencies without updating the application code:
|
You can also manually run the setup script with the force flag to update Node.js and dependencies without updating the application code:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash setup.sh --force
|
# If you're in the nupst directory:
|
||||||
|
bash ./setup.sh --force
|
||||||
|
|
||||||
|
# If you're in another directory, specify the full path:
|
||||||
|
bash /opt/nupst/setup.sh --force
|
||||||
```
|
```
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
91
setup.sh
91
setup.sh
@ -239,25 +239,90 @@ echo "dist_ts directory successfully downloaded from npm registry."
|
|||||||
# Make launcher script executable
|
# Make launcher script executable
|
||||||
chmod +x "$SCRIPT_DIR/bin/nupst"
|
chmod +x "$SCRIPT_DIR/bin/nupst"
|
||||||
|
|
||||||
# Install production dependencies
|
# Set up Node.js binary path
|
||||||
echo "Installing production dependencies..."
|
NODE_BIN_DIR="$SCRIPT_DIR/vendor/$NODE_DIR/bin"
|
||||||
"$SCRIPT_DIR/vendor/$NODE_DIR/bin/npm" --prefix "$SCRIPT_DIR" ci --only=production --no-audit --no-fund
|
NODE_BIN="$NODE_BIN_DIR/node"
|
||||||
|
NPM_CLI_JS="$NODE_BIN_DIR/../lib/node_modules/npm/bin/npm-cli.js"
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
# Ensure we have executable permissions
|
||||||
echo "Warning: Failed to install dependencies with 'npm ci'. Trying 'npm install'..."
|
chmod +x "$NODE_BIN"
|
||||||
"$SCRIPT_DIR/vendor/$NODE_DIR/bin/npm" --prefix "$SCRIPT_DIR" install --only=production --no-audit --no-fund
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
# Make sure the npm-cli.js exists
|
||||||
echo "Error: Failed to install dependencies. NUPST may not function correctly."
|
if [ ! -f "$NPM_CLI_JS" ]; then
|
||||||
echo "You can try to install dependencies manually by running:"
|
# Try to find npm-cli.js
|
||||||
echo "cd $SCRIPT_DIR && npm install --only=production"
|
NPM_CLI_JS=$(find "$NODE_BIN_DIR/.." -name "npm-cli.js" | head -1)
|
||||||
|
|
||||||
|
if [ -z "$NPM_CLI_JS" ]; then
|
||||||
|
echo "Warning: Could not find npm-cli.js, npm commands may fail"
|
||||||
|
# Set to a fallback value so code can continue
|
||||||
|
NPM_CLI_JS="$NODE_BIN_DIR/npm"
|
||||||
else
|
else
|
||||||
echo "Dependencies installed successfully with 'npm install'."
|
echo "Found npm-cli.js at: $NPM_CLI_JS"
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
echo "Dependencies installed successfully with 'npm ci'."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Display which binaries we're using
|
||||||
|
echo "Using Node binary: $NODE_BIN"
|
||||||
|
echo "Using NPM CLI JS: $NPM_CLI_JS"
|
||||||
|
|
||||||
|
# Remove existing node_modules directory and package files
|
||||||
|
echo "Cleaning up existing installation..."
|
||||||
|
rm -rf "$SCRIPT_DIR/node_modules"
|
||||||
|
rm -f "$SCRIPT_DIR/package-lock.json"
|
||||||
|
|
||||||
|
# Back up existing package.json if it exists
|
||||||
|
if [ -f "$SCRIPT_DIR/package.json" ]; then
|
||||||
|
echo "Backing up existing package.json..."
|
||||||
|
cp "$SCRIPT_DIR/package.json" "$SCRIPT_DIR/package.json.bak"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a clean minimal package.json with ONLY net-snmp dependency
|
||||||
|
echo "Creating minimal package.json with only net-snmp dependency..."
|
||||||
|
VERSION=$(grep -o '"version": "[^"]*"' "$SCRIPT_DIR/package.json.bak" | head -1 | cut -d'"' -f4 || echo "2.6.3")
|
||||||
|
echo '{
|
||||||
|
"name": "@serve.zone/nupst",
|
||||||
|
"version": "'$VERSION'",
|
||||||
|
"description": "Node.js UPS Shutdown Tool for SNMP-enabled UPS devices",
|
||||||
|
"main": "dist_ts/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"nupst": "bin/nupst"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"net-snmp": "3.20.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}' > "$SCRIPT_DIR/package.json"
|
||||||
|
|
||||||
|
# Install ONLY net-snmp
|
||||||
|
echo "Installing ONLY net-snmp dependency (+ 2 subdependencies)..."
|
||||||
|
echo "Node version: $("$NODE_BIN" --version)"
|
||||||
|
echo "Executing NPM directly with Node.js"
|
||||||
|
|
||||||
|
# Execute npm-cli.js directly with our Node.js binary
|
||||||
|
"$NODE_BIN" "$NPM_CLI_JS" --prefix "$SCRIPT_DIR" install --no-audit --no-fund
|
||||||
|
|
||||||
|
INSTALL_STATUS=$?
|
||||||
|
if [ $INSTALL_STATUS -ne 0 ]; then
|
||||||
|
echo "Error: Failed to install net-snmp dependency. NUPST may not function correctly."
|
||||||
|
echo "Restoring original package.json..."
|
||||||
|
mv "$SCRIPT_DIR/package.json.bak" "$SCRIPT_DIR/package.json"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "net-snmp dependency installed successfully."
|
||||||
|
# Show what's actually installed
|
||||||
|
echo "Installed modules:"
|
||||||
|
find "$SCRIPT_DIR/node_modules" -maxdepth 1 -type d | grep -v "^$SCRIPT_DIR/node_modules$" | sort
|
||||||
|
|
||||||
|
# Remove backup if successful
|
||||||
|
rm -f "$SCRIPT_DIR/package.json.bak"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# No temporary files to clean up
|
||||||
|
|
||||||
echo "NUPST setup completed successfully."
|
echo "NUPST setup completed successfully."
|
||||||
echo "You can now run NUPST using: $SCRIPT_DIR/bin/nupst"
|
echo "You can now run NUPST using: $SCRIPT_DIR/bin/nupst"
|
||||||
echo "To install NUPST globally, run: sudo ln -s $SCRIPT_DIR/bin/nupst /usr/local/bin/nupst"
|
echo "To install NUPST globally, run: sudo ln -s $SCRIPT_DIR/bin/nupst /usr/local/bin/nupst"
|
||||||
|
147
test/test.logger.ts
Normal file
147
test/test.logger.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
import { Logger } from '../ts/logger.js';
|
||||||
|
|
||||||
|
// Create a Logger instance for testing
|
||||||
|
const logger = new Logger();
|
||||||
|
|
||||||
|
tap.test('should create a logger instance', async () => {
|
||||||
|
expect(logger instanceof Logger).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should log messages with different log levels', async () => {
|
||||||
|
// We're not testing console output directly, just ensuring no errors
|
||||||
|
logger.log('Regular log message');
|
||||||
|
logger.error('Error message');
|
||||||
|
logger.warn('Warning message');
|
||||||
|
logger.success('Success message');
|
||||||
|
|
||||||
|
// Just assert that the test runs without errors
|
||||||
|
expect(true).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should create a logbox with title, content, and end', async () => {
|
||||||
|
// Just ensuring no errors occur
|
||||||
|
logger.logBoxTitle('Test Box', 40);
|
||||||
|
logger.logBoxLine('This is a test line');
|
||||||
|
logger.logBoxEnd();
|
||||||
|
|
||||||
|
// Just assert that the test runs without errors
|
||||||
|
expect(true).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle width persistence between logbox calls', async () => {
|
||||||
|
logger.logBoxTitle('Width Test', 45);
|
||||||
|
|
||||||
|
// These should use the width from the title
|
||||||
|
logger.logBoxLine('Line 1');
|
||||||
|
logger.logBoxLine('Line 2');
|
||||||
|
logger.logBoxEnd();
|
||||||
|
|
||||||
|
let errorThrown = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This should work fine after the reset in logBoxEnd
|
||||||
|
logger.logBoxTitle('New Box', 30);
|
||||||
|
logger.logBoxLine('New line');
|
||||||
|
logger.logBoxEnd();
|
||||||
|
} catch (error) {
|
||||||
|
errorThrown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(errorThrown).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should throw error when using logBoxLine without width', async () => {
|
||||||
|
let errorThrown = false;
|
||||||
|
let errorMessage = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Should throw because no width is set
|
||||||
|
logger.logBoxLine('This should fail');
|
||||||
|
} catch (error) {
|
||||||
|
errorThrown = true;
|
||||||
|
errorMessage = (error as Error).message;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(errorThrown).toBeTruthy();
|
||||||
|
expect(errorMessage).toBeTruthy();
|
||||||
|
expect(errorMessage.includes('No box width')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should create a complete logbox in one call', async () => {
|
||||||
|
// Just ensuring no errors occur
|
||||||
|
logger.logBox('Complete Box', [
|
||||||
|
'Line 1',
|
||||||
|
'Line 2',
|
||||||
|
'Line 3'
|
||||||
|
], 40);
|
||||||
|
|
||||||
|
// Just assert that the test runs without errors
|
||||||
|
expect(true).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle content that exceeds box width', async () => {
|
||||||
|
// Just ensuring no errors occur when content is too long
|
||||||
|
logger.logBox('Truncation Test', [
|
||||||
|
'This line is way too long and should be truncated because it exceeds the available space'
|
||||||
|
], 30);
|
||||||
|
|
||||||
|
// Just assert that the test runs without errors
|
||||||
|
expect(true).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should create dividers with custom characters', async () => {
|
||||||
|
// Just ensuring no errors occur
|
||||||
|
logger.logDivider(30);
|
||||||
|
logger.logDivider(20, '*');
|
||||||
|
|
||||||
|
// Just assert that the test runs without errors
|
||||||
|
expect(true).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('Logger Demo', async () => {
|
||||||
|
console.log('\n=== LOGGER DEMO ===\n');
|
||||||
|
|
||||||
|
// Basic logging
|
||||||
|
logger.log('Regular log message');
|
||||||
|
logger.error('Error message');
|
||||||
|
logger.warn('Warning message');
|
||||||
|
logger.success('Success message');
|
||||||
|
|
||||||
|
// Logbox with title, content lines, and end
|
||||||
|
logger.logBoxTitle('Configuration Loaded', 50);
|
||||||
|
logger.logBoxLine('SNMP Settings:');
|
||||||
|
logger.logBoxLine(' Host: 127.0.0.1');
|
||||||
|
logger.logBoxLine(' Port: 161');
|
||||||
|
logger.logBoxLine(' Version: 1');
|
||||||
|
logger.logBoxEnd();
|
||||||
|
|
||||||
|
// Complete logbox in one call
|
||||||
|
logger.logBox('UPS Status', [
|
||||||
|
'Power Status: onBattery',
|
||||||
|
'Battery Capacity: 75%',
|
||||||
|
'Runtime Remaining: 30 minutes'
|
||||||
|
], 45);
|
||||||
|
|
||||||
|
// Logbox with content that's too long for the width
|
||||||
|
logger.logBox('Truncation Example', [
|
||||||
|
'This line is short enough to fit within the box width',
|
||||||
|
'This line is way too long and will be truncated because it exceeds the available space for content within the logbox'
|
||||||
|
], 40);
|
||||||
|
|
||||||
|
// Demonstrating logbox width being remembered
|
||||||
|
logger.logBoxTitle('Width Persistence Example', 60);
|
||||||
|
logger.logBoxLine('These lines use the width from the title');
|
||||||
|
logger.logBoxLine('No need to specify the width again');
|
||||||
|
logger.logBoxEnd();
|
||||||
|
|
||||||
|
// Divider example
|
||||||
|
logger.log('\nDivider example:');
|
||||||
|
logger.logDivider(30);
|
||||||
|
logger.logDivider(30, '*');
|
||||||
|
|
||||||
|
expect(true).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export the default tap object
|
||||||
|
export default tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/nupst',
|
name: '@serve.zone/nupst',
|
||||||
version: '2.6.0',
|
version: '2.6.17',
|
||||||
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices'
|
||||||
}
|
}
|
||||||
|
511
ts/cli.ts
511
ts/cli.ts
@ -3,6 +3,7 @@ import { promises as fs } from 'fs';
|
|||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { Nupst } from './nupst.js';
|
import { Nupst } from './nupst.js';
|
||||||
|
import { logger } from './logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for handling CLI commands
|
* Class for handling CLI commands
|
||||||
@ -26,7 +27,7 @@ export class NupstCli {
|
|||||||
// Extract debug flag from any position
|
// Extract debug flag from any position
|
||||||
const debugOptions = this.extractDebugOptions(args);
|
const debugOptions = this.extractDebugOptions(args);
|
||||||
if (debugOptions.debugMode) {
|
if (debugOptions.debugMode) {
|
||||||
console.log('Debug mode enabled');
|
logger.log('Debug mode enabled');
|
||||||
// Enable debug mode in the SNMP client
|
// Enable debug mode in the SNMP client
|
||||||
this.nupst.getSnmp().enableDebug();
|
this.nupst.getSnmp().enableDebug();
|
||||||
}
|
}
|
||||||
@ -46,7 +47,7 @@ export class NupstCli {
|
|||||||
private extractDebugOptions(args: string[]): { debugMode: boolean; cleanedArgs: string[] } {
|
private extractDebugOptions(args: string[]): { debugMode: boolean; cleanedArgs: string[] } {
|
||||||
const debugMode = args.includes('--debug') || args.includes('-d');
|
const debugMode = args.includes('--debug') || args.includes('-d');
|
||||||
// Remove debug flags from args
|
// Remove debug flags from args
|
||||||
const cleanedArgs = args.filter(arg => arg !== '--debug' && arg !== '-d');
|
const cleanedArgs = args.filter((arg) => arg !== '--debug' && arg !== '-d');
|
||||||
|
|
||||||
return { debugMode, cleanedArgs };
|
return { debugMode, cleanedArgs };
|
||||||
}
|
}
|
||||||
@ -119,7 +120,7 @@ export class NupstCli {
|
|||||||
private async enable(): Promise<void> {
|
private async enable(): Promise<void> {
|
||||||
this.checkRootAccess('This command must be run as root.');
|
this.checkRootAccess('This command must be run as root.');
|
||||||
await this.nupst.getSystemd().install();
|
await this.nupst.getSystemd().install();
|
||||||
console.log('NUPST service has been installed. Use "nupst start" to start the service.');
|
logger.log('NUPST service has been installed. Use "nupst start" to start the service.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,12 +128,12 @@ export class NupstCli {
|
|||||||
* @param debugMode Whether to enable debug mode
|
* @param debugMode Whether to enable debug mode
|
||||||
*/
|
*/
|
||||||
private async daemonStart(debugMode: boolean = false): Promise<void> {
|
private async daemonStart(debugMode: boolean = false): Promise<void> {
|
||||||
console.log('Starting NUPST daemon...');
|
logger.log('Starting NUPST daemon...');
|
||||||
try {
|
try {
|
||||||
// Enable debug mode for SNMP if requested
|
// Enable debug mode for SNMP if requested
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
this.nupst.getSnmp().enableDebug();
|
this.nupst.getSnmp().enableDebug();
|
||||||
console.log('SNMP debug mode enabled');
|
logger.log('SNMP debug mode enabled');
|
||||||
}
|
}
|
||||||
await this.nupst.getDaemon().start();
|
await this.nupst.getDaemon().start();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -148,10 +149,10 @@ export class NupstCli {
|
|||||||
try {
|
try {
|
||||||
// Use exec with spawn to properly follow logs in real-time
|
// Use exec with spawn to properly follow logs in real-time
|
||||||
const { spawn } = await import('child_process');
|
const { spawn } = await import('child_process');
|
||||||
console.log('Tailing nupst service logs (Ctrl+C to exit)...\n');
|
logger.log('Tailing nupst service logs (Ctrl+C to exit)...\n');
|
||||||
|
|
||||||
const journalctl = spawn('journalctl', ['-u', 'nupst.service', '-n', '50', '-f'], {
|
const journalctl = spawn('journalctl', ['-u', 'nupst.service', '-n', '50', '-f'], {
|
||||||
stdio: ['ignore', 'inherit', 'inherit']
|
stdio: ['ignore', 'inherit', 'inherit'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Forward signals to child process
|
// Forward signals to child process
|
||||||
@ -165,7 +166,7 @@ export class NupstCli {
|
|||||||
journalctl.on('exit', () => resolve());
|
journalctl.on('exit', () => resolve());
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to retrieve logs:', error);
|
logger.error(`Failed to retrieve logs: ${error}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,7 +213,7 @@ export class NupstCli {
|
|||||||
*/
|
*/
|
||||||
private checkRootAccess(errorMessage: string): void {
|
private checkRootAccess(errorMessage: string): void {
|
||||||
if (process.getuid && process.getuid() !== 0) {
|
if (process.getuid && process.getuid() !== 0) {
|
||||||
console.error(errorMessage);
|
logger.error(errorMessage);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,19 +226,21 @@ export class NupstCli {
|
|||||||
try {
|
try {
|
||||||
// Debug mode is now handled in parseAndExecute
|
// Debug mode is now handled in parseAndExecute
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log('┌─ Debug Mode ─────────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ SNMP debugging enabled - detailed logs will be shown');
|
logger.logBoxTitle('Debug Mode', boxWidth);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine('SNMP debugging enabled - detailed logs will be shown');
|
||||||
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load the configuration
|
// Try to load the configuration
|
||||||
try {
|
try {
|
||||||
await this.nupst.getDaemon().loadConfig();
|
await this.nupst.getDaemon().loadConfig();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('┌─ Configuration Error ─────────────────────┐');
|
const errorBoxWidth = 45;
|
||||||
console.error('│ No configuration found.');
|
logger.logBoxTitle('Configuration Error', errorBoxWidth);
|
||||||
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
logger.logBoxLine('No configuration found.');
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxLine("Please run 'nupst setup' first to create a configuration.");
|
||||||
|
logger.logBoxEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +250,7 @@ export class NupstCli {
|
|||||||
this.displayTestConfig(config);
|
this.displayTestConfig(config);
|
||||||
await this.testConnection(config);
|
await this.testConnection(config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Test failed: ${error.message}`);
|
logger.error(`Test failed: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,44 +259,45 @@ export class NupstCli {
|
|||||||
* @param config Current configuration
|
* @param config Current configuration
|
||||||
*/
|
*/
|
||||||
private displayTestConfig(config: any): void {
|
private displayTestConfig(config: any): void {
|
||||||
console.log('┌─ Testing Configuration ─────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ SNMP Settings:');
|
logger.logBoxTitle('Testing Configuration', boxWidth);
|
||||||
console.log(`│ Host: ${config.snmp.host}`);
|
logger.logBoxLine('SNMP Settings:');
|
||||||
console.log(`│ Port: ${config.snmp.port}`);
|
logger.logBoxLine(` Host: ${config.snmp.host}`);
|
||||||
console.log(`│ Version: ${config.snmp.version}`);
|
logger.logBoxLine(` Port: ${config.snmp.port}`);
|
||||||
console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
logger.logBoxLine(` Version: ${config.snmp.version}`);
|
||||||
|
logger.logBoxLine(` UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
||||||
|
|
||||||
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
||||||
console.log(`│ Community: ${config.snmp.community}`);
|
logger.logBoxLine(` Community: ${config.snmp.community}`);
|
||||||
} else if (config.snmp.version === 3) {
|
} else if (config.snmp.version === 3) {
|
||||||
console.log(`│ Security Level: ${config.snmp.securityLevel}`);
|
logger.logBoxLine(` Security Level: ${config.snmp.securityLevel}`);
|
||||||
console.log(`│ Username: ${config.snmp.username}`);
|
logger.logBoxLine(` Username: ${config.snmp.username}`);
|
||||||
|
|
||||||
// Show auth and privacy details based on security level
|
// Show auth and privacy details based on security level
|
||||||
if (config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv') {
|
if (config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv') {
|
||||||
console.log(`│ Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
|
logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.snmp.securityLevel === 'authPriv') {
|
if (config.snmp.securityLevel === 'authPriv') {
|
||||||
console.log(`│ Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
|
logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show timeout value
|
// Show timeout value
|
||||||
console.log(`│ Timeout: ${config.snmp.timeout / 1000} seconds`);
|
logger.logBoxLine(` Timeout: ${config.snmp.timeout / 1000} seconds`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show OIDs if custom model is selected
|
// Show OIDs if custom model is selected
|
||||||
if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) {
|
if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) {
|
||||||
console.log('│ Custom OIDs:');
|
logger.logBoxLine('Custom OIDs:');
|
||||||
console.log(`│ Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`);
|
logger.logBoxLine(` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`);
|
||||||
console.log(`│ Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`);
|
logger.logBoxLine(` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`);
|
||||||
console.log(`│ Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
logger.logBoxLine(` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
||||||
}
|
}
|
||||||
console.log('│ Thresholds:');
|
logger.logBoxLine('Thresholds:');
|
||||||
console.log(`│ Battery: ${config.thresholds.battery}%`);
|
logger.logBoxLine(` Battery: ${config.thresholds.battery}%`);
|
||||||
console.log(`│ Runtime: ${config.thresholds.runtime} minutes`);
|
logger.logBoxLine(` Runtime: ${config.thresholds.runtime} minutes`);
|
||||||
console.log(`│ Check Interval: ${config.checkInterval / 1000} seconds`);
|
logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -301,32 +305,34 @@ export class NupstCli {
|
|||||||
* @param config Current configuration
|
* @param config Current configuration
|
||||||
*/
|
*/
|
||||||
private async testConnection(config: any): Promise<void> {
|
private async testConnection(config: any): Promise<void> {
|
||||||
console.log('\nTesting connection to UPS...');
|
logger.log('\nTesting connection to UPS...');
|
||||||
try {
|
try {
|
||||||
// Create a test config with a short timeout
|
// Create a test config with a short timeout
|
||||||
const testConfig = {
|
const testConfig = {
|
||||||
...config.snmp,
|
...config.snmp,
|
||||||
timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for testing
|
timeout: Math.min(config.snmp.timeout, 10000), // Use at most 10 seconds for testing
|
||||||
};
|
};
|
||||||
|
|
||||||
const status = await this.nupst.getSnmp().getUpsStatus(testConfig);
|
const status = await this.nupst.getSnmp().getUpsStatus(testConfig);
|
||||||
|
|
||||||
console.log('┌─ Connection Successful! ─────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ UPS Status:');
|
logger.logBoxTitle('Connection Successful!', boxWidth);
|
||||||
console.log(`│ Power Status: ${status.powerStatus}`);
|
logger.logBoxLine('UPS Status:');
|
||||||
console.log(`│ Battery Capacity: ${status.batteryCapacity}%`);
|
logger.logBoxLine(` Power Status: ${status.powerStatus}`);
|
||||||
console.log(`│ Runtime Remaining: ${status.batteryRuntime} minutes`);
|
logger.logBoxLine(` Battery Capacity: ${status.batteryCapacity}%`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine(` Runtime Remaining: ${status.batteryRuntime} minutes`);
|
||||||
|
logger.logBoxEnd();
|
||||||
|
|
||||||
// Check status against thresholds if on battery
|
// Check status against thresholds if on battery
|
||||||
if (status.powerStatus === 'onBattery') {
|
if (status.powerStatus === 'onBattery') {
|
||||||
this.analyzeThresholds(status, config);
|
this.analyzeThresholds(status, config);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('┌─ Connection Failed! ───────────────────────┐');
|
const errorBoxWidth = 45;
|
||||||
console.error(`│ Error: ${error.message}`);
|
logger.logBoxTitle('Connection Failed!', errorBoxWidth);
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`Error: ${error.message}`);
|
||||||
console.log('\nPlease check your settings and run \'nupst setup\' to reconfigure.');
|
logger.logBoxEnd();
|
||||||
|
logger.log("\nPlease check your settings and run 'nupst setup' to reconfigure.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,34 +342,43 @@ export class NupstCli {
|
|||||||
* @param config Current configuration
|
* @param config Current configuration
|
||||||
*/
|
*/
|
||||||
private analyzeThresholds(status: any, config: any): void {
|
private analyzeThresholds(status: any, config: any): void {
|
||||||
console.log('┌─ Threshold Analysis ───────────────────────┐');
|
const boxWidth = 45;
|
||||||
|
logger.logBoxTitle('Threshold Analysis', boxWidth);
|
||||||
|
|
||||||
if (status.batteryCapacity < config.thresholds.battery) {
|
if (status.batteryCapacity < config.thresholds.battery) {
|
||||||
console.log('│ ⚠️ WARNING: Battery capacity below threshold');
|
logger.logBoxLine('⚠️ WARNING: Battery capacity below threshold');
|
||||||
console.log(`│ Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`);
|
logger.logBoxLine(
|
||||||
console.log('│ System would initiate shutdown');
|
` Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`
|
||||||
|
);
|
||||||
|
logger.logBoxLine(' System would initiate shutdown');
|
||||||
} else {
|
} else {
|
||||||
console.log('│ ✓ Battery capacity above threshold');
|
logger.logBoxLine('✓ Battery capacity above threshold');
|
||||||
console.log(`│ Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`);
|
logger.logBoxLine(
|
||||||
|
` Current: ${status.batteryCapacity}% | Threshold: ${config.thresholds.battery}%`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.batteryRuntime < config.thresholds.runtime) {
|
if (status.batteryRuntime < config.thresholds.runtime) {
|
||||||
console.log('│ ⚠️ WARNING: Runtime below threshold');
|
logger.logBoxLine('⚠️ WARNING: Runtime below threshold');
|
||||||
console.log(`│ Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`);
|
logger.logBoxLine(
|
||||||
console.log('│ System would initiate shutdown');
|
` Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`
|
||||||
|
);
|
||||||
|
logger.logBoxLine(' System would initiate shutdown');
|
||||||
} else {
|
} else {
|
||||||
console.log('│ ✓ Runtime above threshold');
|
logger.logBoxLine('✓ Runtime above threshold');
|
||||||
console.log(`│ Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`);
|
logger.logBoxLine(
|
||||||
|
` Current: ${status.batteryRuntime} min | Threshold: ${config.thresholds.runtime} min`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display help message
|
* Display help message
|
||||||
*/
|
*/
|
||||||
private showHelp(): void {
|
private showHelp(): void {
|
||||||
console.log(`
|
logger.log(`
|
||||||
NUPST - Node.js UPS Shutdown Tool
|
NUPST - Node.js UPS Shutdown Tool
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@ -393,10 +408,13 @@ Options:
|
|||||||
private async update(): Promise<void> {
|
private async update(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Check if running as root
|
// Check if running as root
|
||||||
this.checkRootAccess('This command must be run as root to update NUPST and refresh the systemd service.');
|
this.checkRootAccess(
|
||||||
|
'This command must be run as root to update NUPST and refresh the systemd service.'
|
||||||
|
);
|
||||||
|
|
||||||
console.log('┌─ NUPST Update Process ──────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ Updating NUPST from repository...');
|
logger.logBoxTitle('NUPST Update Process', boxWidth);
|
||||||
|
logger.logBoxLine('Updating NUPST from repository...');
|
||||||
|
|
||||||
// Determine the installation directory (assuming it's either /opt/nupst or the current directory)
|
// Determine the installation directory (assuming it's either /opt/nupst or the current directory)
|
||||||
const { existsSync } = await import('fs');
|
const { existsSync } = await import('fs');
|
||||||
@ -406,60 +424,70 @@ Options:
|
|||||||
// If not installed in /opt/nupst, use the current directory
|
// If not installed in /opt/nupst, use the current directory
|
||||||
const { dirname } = await import('path');
|
const { dirname } = await import('path');
|
||||||
installDir = dirname(dirname(process.argv[1])); // Go up two levels from the executable
|
installDir = dirname(dirname(process.argv[1])); // Go up two levels from the executable
|
||||||
console.log(`│ Using local installation directory: ${installDir}`);
|
logger.logBoxLine(`Using local installation directory: ${installDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Update the repository
|
// 1. Update the repository
|
||||||
console.log('│ Pulling latest changes from git repository...');
|
logger.logBoxLine('Pulling latest changes from git repository...');
|
||||||
execSync(`cd ${installDir} && git fetch origin && git reset --hard origin/main`, { stdio: 'pipe' });
|
execSync(`cd ${installDir} && git fetch origin && git reset --hard origin/main`, {
|
||||||
|
stdio: 'pipe',
|
||||||
|
});
|
||||||
|
|
||||||
// 2. Run the install.sh script
|
// 2. Run the install.sh script
|
||||||
console.log('│ Running install.sh to update NUPST...');
|
logger.logBoxLine('Running install.sh to update NUPST...');
|
||||||
execSync(`cd ${installDir} && bash ./install.sh`, { stdio: 'pipe' });
|
execSync(`cd ${installDir} && bash ./install.sh`, { stdio: 'pipe' });
|
||||||
|
|
||||||
// 3. Run the setup.sh script with force flag to update Node.js and dependencies
|
// 3. Run the setup.sh script with force flag to update Node.js and dependencies
|
||||||
console.log('│ Running setup.sh to update Node.js and dependencies...');
|
logger.logBoxLine('Running setup.sh to update Node.js and dependencies...');
|
||||||
execSync(`cd ${installDir} && bash ./setup.sh --force`, { stdio: 'pipe' });
|
execSync(`cd ${installDir} && bash ./setup.sh --force`, { stdio: 'pipe' });
|
||||||
|
|
||||||
// 4. Refresh the systemd service
|
// 4. Refresh the systemd service
|
||||||
console.log('│ Refreshing systemd service...');
|
logger.logBoxLine('Refreshing systemd service...');
|
||||||
|
|
||||||
// First check if service exists
|
// First check if service exists
|
||||||
const serviceExists = execSync('systemctl list-unit-files | grep nupst.service').toString().includes('nupst.service');
|
let serviceExists = false;
|
||||||
|
try {
|
||||||
|
const output = execSync('systemctl list-unit-files | grep nupst.service').toString();
|
||||||
|
serviceExists = output.includes('nupst.service');
|
||||||
|
} catch (error) {
|
||||||
|
// If grep fails (service not found), serviceExists remains false
|
||||||
|
serviceExists = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (serviceExists) {
|
if (serviceExists) {
|
||||||
// Stop the service if it's running
|
// Stop the service if it's running
|
||||||
const isRunning = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
const isRunning =
|
||||||
|
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
console.log('│ Stopping nupst service...');
|
logger.logBoxLine('Stopping nupst service...');
|
||||||
execSync('systemctl stop nupst.service');
|
execSync('systemctl stop nupst.service');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reinstall the service
|
// Reinstall the service
|
||||||
console.log('│ Reinstalling systemd service...');
|
logger.logBoxLine('Reinstalling systemd service...');
|
||||||
await this.nupst.getSystemd().install();
|
await this.nupst.getSystemd().install();
|
||||||
|
|
||||||
// Restart the service if it was running
|
// Restart the service if it was running
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
console.log('│ Restarting nupst service...');
|
logger.logBoxLine('Restarting nupst service...');
|
||||||
execSync('systemctl start nupst.service');
|
execSync('systemctl start nupst.service');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('│ Systemd service not installed, skipping service refresh.');
|
logger.logBoxLine('Systemd service not installed, skipping service refresh.');
|
||||||
console.log('│ Run "nupst enable" to install the service.');
|
logger.logBoxLine('Run "nupst enable" to install the service.');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('│ Update completed successfully!');
|
logger.logBoxLine('Update completed successfully!');
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('│ Error during update process:');
|
logger.logBoxLine('Error during update process:');
|
||||||
console.error(`│ ${error.message}`);
|
logger.logBoxLine(`${error.message}`);
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Update failed: ${error.message}`);
|
logger.error(`Update failed: ${error.message}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,7 +502,7 @@ Options:
|
|||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper function to prompt for input
|
// Helper function to prompt for input
|
||||||
@ -492,7 +520,7 @@ Options:
|
|||||||
rl.close();
|
rl.close();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Setup error:', error.message);
|
logger.error(`Setup error: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,9 +529,9 @@ Options:
|
|||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
*/
|
*/
|
||||||
private async runSetupProcess(prompt: (question: string) => Promise<string>): Promise<void> {
|
private async runSetupProcess(prompt: (question: string) => Promise<string>): Promise<void> {
|
||||||
console.log('\nNUPST Interactive Setup');
|
logger.log('\nNUPST Interactive Setup');
|
||||||
console.log('======================\n');
|
logger.log('======================\n');
|
||||||
console.log('This will guide you through configuring your UPS SNMP settings.\n');
|
logger.log('This will guide you through configuring your UPS SNMP settings.\n');
|
||||||
|
|
||||||
// Try to load existing config if available
|
// Try to load existing config if available
|
||||||
let config;
|
let config;
|
||||||
@ -513,7 +541,7 @@ Options:
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If config doesn't exist, use default config
|
// If config doesn't exist, use default config
|
||||||
config = this.nupst.getDaemon().getConfig();
|
config = this.nupst.getDaemon().getConfig();
|
||||||
console.log('No existing configuration found. Creating a new configuration.');
|
logger.log('No existing configuration found. Creating a new configuration.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather SNMP settings
|
// Gather SNMP settings
|
||||||
@ -536,7 +564,7 @@ Options:
|
|||||||
// Check if service is running and restart it if needed
|
// Check if service is running and restart it if needed
|
||||||
await this.restartServiceIfRunning();
|
await this.restartServiceIfRunning();
|
||||||
|
|
||||||
console.log('\nSetup complete!');
|
logger.log('\nSetup complete!');
|
||||||
await this.optionallyEnableService(prompt);
|
await this.optionallyEnableService(prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,7 +574,10 @@ Options:
|
|||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
* @returns Updated configuration
|
* @returns Updated configuration
|
||||||
*/
|
*/
|
||||||
private async gatherSnmpSettings(config: any, prompt: (question: string) => Promise<string>): Promise<any> {
|
private async gatherSnmpSettings(
|
||||||
|
config: any,
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<any> {
|
||||||
// SNMP IP Address
|
// SNMP IP Address
|
||||||
const defaultHost = config.snmp.host;
|
const defaultHost = config.snmp.host;
|
||||||
const host = await prompt(`UPS IP Address [${defaultHost}]: `);
|
const host = await prompt(`UPS IP Address [${defaultHost}]: `);
|
||||||
@ -556,7 +587,7 @@ Options:
|
|||||||
const defaultPort = config.snmp.port;
|
const defaultPort = config.snmp.port;
|
||||||
const portInput = await prompt(`SNMP Port [${defaultPort}]: `);
|
const portInput = await prompt(`SNMP Port [${defaultPort}]: `);
|
||||||
const port = parseInt(portInput, 10);
|
const port = parseInt(portInput, 10);
|
||||||
config.snmp.port = (portInput.trim() && !isNaN(port)) ? port : defaultPort;
|
config.snmp.port = portInput.trim() && !isNaN(port) ? port : defaultPort;
|
||||||
|
|
||||||
// SNMP Version
|
// SNMP Version
|
||||||
const defaultVersion = config.snmp.version;
|
const defaultVersion = config.snmp.version;
|
||||||
@ -566,7 +597,10 @@ Options:
|
|||||||
console.log(' 3) SNMPv3 (with security features)');
|
console.log(' 3) SNMPv3 (with security features)');
|
||||||
const versionInput = await prompt(`Select SNMP version [${defaultVersion}]: `);
|
const versionInput = await prompt(`Select SNMP version [${defaultVersion}]: `);
|
||||||
const version = parseInt(versionInput, 10);
|
const version = parseInt(versionInput, 10);
|
||||||
config.snmp.version = (versionInput.trim() && (version === 1 || version === 2 || version === 3)) ? version : defaultVersion;
|
config.snmp.version =
|
||||||
|
versionInput.trim() && (version === 1 || version === 2 || version === 3)
|
||||||
|
? version
|
||||||
|
: defaultVersion;
|
||||||
|
|
||||||
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
||||||
// SNMP Community String (for v1/v2c)
|
// SNMP Community String (for v1/v2c)
|
||||||
@ -587,7 +621,10 @@ Options:
|
|||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
* @returns Updated configuration
|
* @returns Updated configuration
|
||||||
*/
|
*/
|
||||||
private async gatherSnmpV3Settings(config: any, prompt: (question: string) => Promise<string>): Promise<any> {
|
private async gatherSnmpV3Settings(
|
||||||
|
config: any,
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<any> {
|
||||||
console.log('\nSNMPv3 Security Settings:');
|
console.log('\nSNMPv3 Security Settings:');
|
||||||
|
|
||||||
// Security Level
|
// Security Level
|
||||||
@ -595,9 +632,13 @@ Options:
|
|||||||
console.log(' 1) noAuthNoPriv (No Authentication, No Privacy)');
|
console.log(' 1) noAuthNoPriv (No Authentication, No Privacy)');
|
||||||
console.log(' 2) authNoPriv (Authentication, No Privacy)');
|
console.log(' 2) authNoPriv (Authentication, No Privacy)');
|
||||||
console.log(' 3) authPriv (Authentication and Privacy)');
|
console.log(' 3) authPriv (Authentication and Privacy)');
|
||||||
const defaultSecLevel = config.snmp.securityLevel ?
|
const defaultSecLevel = config.snmp.securityLevel
|
||||||
(config.snmp.securityLevel === 'noAuthNoPriv' ? 1 :
|
? config.snmp.securityLevel === 'noAuthNoPriv'
|
||||||
config.snmp.securityLevel === 'authNoPriv' ? 2 : 3) : 3;
|
? 1
|
||||||
|
: config.snmp.securityLevel === 'authNoPriv'
|
||||||
|
? 2
|
||||||
|
: 3
|
||||||
|
: 3;
|
||||||
const secLevelInput = await prompt(`Select Security Level [${defaultSecLevel}]: `);
|
const secLevelInput = await prompt(`Select Security Level [${defaultSecLevel}]: `);
|
||||||
const secLevel = parseInt(secLevelInput, 10) || defaultSecLevel;
|
const secLevel = parseInt(secLevelInput, 10) || defaultSecLevel;
|
||||||
|
|
||||||
@ -639,7 +680,9 @@ Options:
|
|||||||
|
|
||||||
// Allow customizing the timeout value
|
// Allow customizing the timeout value
|
||||||
const defaultTimeout = config.snmp.timeout / 1000; // Convert from ms to seconds for display
|
const defaultTimeout = config.snmp.timeout / 1000; // Convert from ms to seconds for display
|
||||||
console.log('\nSNMPv3 operations with authentication and privacy may require longer timeouts.');
|
console.log(
|
||||||
|
'\nSNMPv3 operations with authentication and privacy may require longer timeouts.'
|
||||||
|
);
|
||||||
const timeoutInput = await prompt(`SNMP Timeout in seconds [${defaultTimeout}]: `);
|
const timeoutInput = await prompt(`SNMP Timeout in seconds [${defaultTimeout}]: `);
|
||||||
const timeout = parseInt(timeoutInput, 10);
|
const timeout = parseInt(timeoutInput, 10);
|
||||||
if (timeoutInput.trim() && !isNaN(timeout)) {
|
if (timeoutInput.trim() && !isNaN(timeout)) {
|
||||||
@ -656,13 +699,18 @@ Options:
|
|||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
* @returns Updated configuration
|
* @returns Updated configuration
|
||||||
*/
|
*/
|
||||||
private async gatherAuthenticationSettings(config: any, prompt: (question: string) => Promise<string>): Promise<any> {
|
private async gatherAuthenticationSettings(
|
||||||
|
config: any,
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<any> {
|
||||||
// Authentication protocol
|
// Authentication protocol
|
||||||
console.log('\nAuthentication Protocol:');
|
console.log('\nAuthentication Protocol:');
|
||||||
console.log(' 1) MD5');
|
console.log(' 1) MD5');
|
||||||
console.log(' 2) SHA');
|
console.log(' 2) SHA');
|
||||||
const defaultAuthProtocol = config.snmp.authProtocol === 'SHA' ? 2 : 1;
|
const defaultAuthProtocol = config.snmp.authProtocol === 'SHA' ? 2 : 1;
|
||||||
const authProtocolInput = await prompt(`Select Authentication Protocol [${defaultAuthProtocol}]: `);
|
const authProtocolInput = await prompt(
|
||||||
|
`Select Authentication Protocol [${defaultAuthProtocol}]: `
|
||||||
|
);
|
||||||
const authProtocol = parseInt(authProtocolInput, 10) || defaultAuthProtocol;
|
const authProtocol = parseInt(authProtocolInput, 10) || defaultAuthProtocol;
|
||||||
config.snmp.authProtocol = authProtocol === 2 ? 'SHA' : 'MD5';
|
config.snmp.authProtocol = authProtocol === 2 ? 'SHA' : 'MD5';
|
||||||
|
|
||||||
@ -680,7 +728,10 @@ Options:
|
|||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
* @returns Updated configuration
|
* @returns Updated configuration
|
||||||
*/
|
*/
|
||||||
private async gatherPrivacySettings(config: any, prompt: (question: string) => Promise<string>): Promise<any> {
|
private async gatherPrivacySettings(
|
||||||
|
config: any,
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<any> {
|
||||||
// Privacy protocol
|
// Privacy protocol
|
||||||
console.log('\nPrivacy Protocol:');
|
console.log('\nPrivacy Protocol:');
|
||||||
console.log(' 1) DES');
|
console.log(' 1) DES');
|
||||||
@ -704,32 +755,42 @@ Options:
|
|||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
* @returns Updated configuration
|
* @returns Updated configuration
|
||||||
*/
|
*/
|
||||||
private async gatherThresholdSettings(config: any, prompt: (question: string) => Promise<string>): Promise<any> {
|
private async gatherThresholdSettings(
|
||||||
|
config: any,
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<any> {
|
||||||
console.log('\nShutdown Thresholds:');
|
console.log('\nShutdown Thresholds:');
|
||||||
|
|
||||||
// Battery threshold
|
// Battery threshold
|
||||||
const defaultBatteryThreshold = config.thresholds.battery;
|
const defaultBatteryThreshold = config.thresholds.battery;
|
||||||
const batteryThresholdInput = await prompt(`Battery percentage threshold [${defaultBatteryThreshold}%]: `);
|
const batteryThresholdInput = await prompt(
|
||||||
|
`Battery percentage threshold [${defaultBatteryThreshold}%]: `
|
||||||
|
);
|
||||||
const batteryThreshold = parseInt(batteryThresholdInput, 10);
|
const batteryThreshold = parseInt(batteryThresholdInput, 10);
|
||||||
config.thresholds.battery = (batteryThresholdInput.trim() && !isNaN(batteryThreshold))
|
config.thresholds.battery =
|
||||||
? batteryThreshold
|
batteryThresholdInput.trim() && !isNaN(batteryThreshold)
|
||||||
: defaultBatteryThreshold;
|
? batteryThreshold
|
||||||
|
: defaultBatteryThreshold;
|
||||||
|
|
||||||
// Runtime threshold
|
// Runtime threshold
|
||||||
const defaultRuntimeThreshold = config.thresholds.runtime;
|
const defaultRuntimeThreshold = config.thresholds.runtime;
|
||||||
const runtimeThresholdInput = await prompt(`Runtime minutes threshold [${defaultRuntimeThreshold} minutes]: `);
|
const runtimeThresholdInput = await prompt(
|
||||||
|
`Runtime minutes threshold [${defaultRuntimeThreshold} minutes]: `
|
||||||
|
);
|
||||||
const runtimeThreshold = parseInt(runtimeThresholdInput, 10);
|
const runtimeThreshold = parseInt(runtimeThresholdInput, 10);
|
||||||
config.thresholds.runtime = (runtimeThresholdInput.trim() && !isNaN(runtimeThreshold))
|
config.thresholds.runtime =
|
||||||
? runtimeThreshold
|
runtimeThresholdInput.trim() && !isNaN(runtimeThreshold)
|
||||||
: defaultRuntimeThreshold;
|
? runtimeThreshold
|
||||||
|
: defaultRuntimeThreshold;
|
||||||
|
|
||||||
// Check interval
|
// Check interval
|
||||||
const defaultInterval = config.checkInterval / 1000; // Convert from ms to seconds for display
|
const defaultInterval = config.checkInterval / 1000; // Convert from ms to seconds for display
|
||||||
const intervalInput = await prompt(`Check interval in seconds [${defaultInterval}]: `);
|
const intervalInput = await prompt(`Check interval in seconds [${defaultInterval}]: `);
|
||||||
const interval = parseInt(intervalInput, 10);
|
const interval = parseInt(intervalInput, 10);
|
||||||
config.checkInterval = (intervalInput.trim() && !isNaN(interval))
|
config.checkInterval =
|
||||||
? interval * 1000 // Convert to ms
|
intervalInput.trim() && !isNaN(interval)
|
||||||
: defaultInterval * 1000;
|
? interval * 1000 // Convert to ms
|
||||||
|
: defaultInterval * 1000;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@ -740,7 +801,10 @@ Options:
|
|||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
* @returns Updated configuration
|
* @returns Updated configuration
|
||||||
*/
|
*/
|
||||||
private async gatherUpsModelSettings(config: any, prompt: (question: string) => Promise<string>): Promise<any> {
|
private async gatherUpsModelSettings(
|
||||||
|
config: any,
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<any> {
|
||||||
console.log('\nUPS Model Selection:');
|
console.log('\nUPS Model Selection:');
|
||||||
console.log(' 1) CyberPower');
|
console.log(' 1) CyberPower');
|
||||||
console.log(' 2) APC');
|
console.log(' 2) APC');
|
||||||
@ -749,12 +813,20 @@ Options:
|
|||||||
console.log(' 5) Liebert/Vertiv');
|
console.log(' 5) Liebert/Vertiv');
|
||||||
console.log(' 6) Custom (Advanced)');
|
console.log(' 6) Custom (Advanced)');
|
||||||
|
|
||||||
const defaultModelValue = config.snmp.upsModel === 'cyberpower' ? 1 :
|
const defaultModelValue =
|
||||||
config.snmp.upsModel === 'apc' ? 2 :
|
config.snmp.upsModel === 'cyberpower'
|
||||||
config.snmp.upsModel === 'eaton' ? 3 :
|
? 1
|
||||||
config.snmp.upsModel === 'tripplite' ? 4 :
|
: config.snmp.upsModel === 'apc'
|
||||||
config.snmp.upsModel === 'liebert' ? 5 :
|
? 2
|
||||||
config.snmp.upsModel === 'custom' ? 6 : 1;
|
: config.snmp.upsModel === 'eaton'
|
||||||
|
? 3
|
||||||
|
: config.snmp.upsModel === 'tripplite'
|
||||||
|
? 4
|
||||||
|
: config.snmp.upsModel === 'liebert'
|
||||||
|
? 5
|
||||||
|
: config.snmp.upsModel === 'custom'
|
||||||
|
? 6
|
||||||
|
: 1;
|
||||||
|
|
||||||
const modelInput = await prompt(`Select UPS model [${defaultModelValue}]: `);
|
const modelInput = await prompt(`Select UPS model [${defaultModelValue}]: `);
|
||||||
const modelValue = parseInt(modelInput, 10) || defaultModelValue;
|
const modelValue = parseInt(modelInput, 10) || defaultModelValue;
|
||||||
@ -783,7 +855,7 @@ Options:
|
|||||||
config.snmp.customOIDs = {
|
config.snmp.customOIDs = {
|
||||||
POWER_STATUS: powerStatusOID.trim(),
|
POWER_STATUS: powerStatusOID.trim(),
|
||||||
BATTERY_CAPACITY: batteryCapacityOID.trim(),
|
BATTERY_CAPACITY: batteryCapacityOID.trim(),
|
||||||
BATTERY_RUNTIME: batteryRuntimeOID.trim()
|
BATTERY_RUNTIME: batteryRuntimeOID.trim(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -795,13 +867,18 @@ Options:
|
|||||||
* @param config Current configuration
|
* @param config Current configuration
|
||||||
*/
|
*/
|
||||||
private displayConfigSummary(config: any): void {
|
private displayConfigSummary(config: any): void {
|
||||||
console.log('\n┌─ Configuration Summary ─────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log(`│ SNMP Host: ${config.snmp.host}:${config.snmp.port}`);
|
logger.log('');
|
||||||
console.log(`│ SNMP Version: ${config.snmp.version}`);
|
logger.logBoxTitle('Configuration Summary', boxWidth);
|
||||||
console.log(`│ UPS Model: ${config.snmp.upsModel}`);
|
logger.logBoxLine(`SNMP Host: ${config.snmp.host}:${config.snmp.port}`);
|
||||||
console.log(`│ Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`);
|
logger.logBoxLine(`SNMP Version: ${config.snmp.version}`);
|
||||||
console.log(`│ Check Interval: ${config.checkInterval/1000} seconds`);
|
logger.logBoxLine(`UPS Model: ${config.snmp.upsModel}`);
|
||||||
console.log('└──────────────────────────────────────────┘\n');
|
logger.logBoxLine(
|
||||||
|
`Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`
|
||||||
|
);
|
||||||
|
logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`);
|
||||||
|
logger.logBoxEnd();
|
||||||
|
logger.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -809,29 +886,38 @@ Options:
|
|||||||
* @param config Current configuration
|
* @param config Current configuration
|
||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
*/
|
*/
|
||||||
private async optionallyTestConnection(config: any, prompt: (question: string) => Promise<string>): Promise<void> {
|
private async optionallyTestConnection(
|
||||||
const testConnection = await prompt('Would you like to test the connection to your UPS? (y/N): ');
|
config: any,
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<void> {
|
||||||
|
const testConnection = await prompt(
|
||||||
|
'Would you like to test the connection to your UPS? (y/N): '
|
||||||
|
);
|
||||||
if (testConnection.toLowerCase() === 'y') {
|
if (testConnection.toLowerCase() === 'y') {
|
||||||
console.log('\nTesting connection to UPS...');
|
logger.log('\nTesting connection to UPS...');
|
||||||
try {
|
try {
|
||||||
// Create a test config with a short timeout
|
// Create a test config with a short timeout
|
||||||
const testConfig = {
|
const testConfig = {
|
||||||
...config.snmp,
|
...config.snmp,
|
||||||
timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for testing
|
timeout: Math.min(config.snmp.timeout, 10000), // Use at most 10 seconds for testing
|
||||||
};
|
};
|
||||||
|
|
||||||
const status = await this.nupst.getSnmp().getUpsStatus(testConfig);
|
const status = await this.nupst.getSnmp().getUpsStatus(testConfig);
|
||||||
console.log('\n┌─ Connection Successful! ─────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ UPS Status:');
|
logger.log('');
|
||||||
console.log(`│ ✓ Power Status: ${status.powerStatus}`);
|
logger.logBoxTitle('Connection Successful!', boxWidth);
|
||||||
console.log(`│ ✓ Battery Capacity: ${status.batteryCapacity}%`);
|
logger.logBoxLine('UPS Status:');
|
||||||
console.log(`│ ✓ Runtime Remaining: ${status.batteryRuntime} minutes`);
|
logger.logBoxLine(`✓ Power Status: ${status.powerStatus}`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`✓ Battery Capacity: ${status.batteryCapacity}%`);
|
||||||
|
logger.logBoxLine(`✓ Runtime Remaining: ${status.batteryRuntime} minutes`);
|
||||||
|
logger.logBoxEnd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('\n┌─ Connection Failed! ───────────────────────┐');
|
const errorBoxWidth = 45;
|
||||||
console.error('│ Error: ' + error.message);
|
logger.log('');
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxTitle('Connection Failed!', errorBoxWidth);
|
||||||
console.log('\nPlease check your settings and try again.');
|
logger.logBoxLine(`Error: ${error.message}`);
|
||||||
|
logger.logBoxEnd();
|
||||||
|
logger.log('\nPlease check your settings and try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -843,31 +929,33 @@ Options:
|
|||||||
private async restartServiceIfRunning(): Promise<void> {
|
private async restartServiceIfRunning(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Check if the service is active
|
// Check if the service is active
|
||||||
const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
const isActive =
|
||||||
|
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
// Service is running, restart it
|
// Service is running, restart it
|
||||||
console.log('┌─ Service Update ─────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ Configuration has changed.');
|
logger.logBoxTitle('Service Update', boxWidth);
|
||||||
console.log('│ Restarting NUPST service to apply changes...');
|
logger.logBoxLine('Configuration has changed.');
|
||||||
|
logger.logBoxLine('Restarting NUPST service to apply changes...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (process.getuid && process.getuid() === 0) {
|
if (process.getuid && process.getuid() === 0) {
|
||||||
// We have root access, restart directly
|
// We have root access, restart directly
|
||||||
execSync('systemctl restart nupst.service');
|
execSync('systemctl restart nupst.service');
|
||||||
console.log('│ Service restarted successfully.');
|
logger.logBoxLine('Service restarted successfully.');
|
||||||
} else {
|
} else {
|
||||||
// No root access, show instructions
|
// No root access, show instructions
|
||||||
console.log('│ Please restart the service with:');
|
logger.logBoxLine('Please restart the service with:');
|
||||||
console.log('│ sudo systemctl restart nupst.service');
|
logger.logBoxLine(' sudo systemctl restart nupst.service');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`│ Error restarting service: ${error.message}`);
|
logger.logBoxLine(`Error restarting service: ${error.message}`);
|
||||||
console.log('│ You may need to restart the service manually:');
|
logger.logBoxLine('You may need to restart the service manually:');
|
||||||
console.log('│ sudo systemctl restart nupst.service');
|
logger.logBoxLine(' sudo systemctl restart nupst.service');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore errors checking service status
|
// Ignore errors checking service status
|
||||||
@ -878,18 +966,24 @@ Options:
|
|||||||
* Optionally enable and start systemd service
|
* Optionally enable and start systemd service
|
||||||
* @param prompt Function to prompt for user input
|
* @param prompt Function to prompt for user input
|
||||||
*/
|
*/
|
||||||
private async optionallyEnableService(prompt: (question: string) => Promise<string>): Promise<void> {
|
private async optionallyEnableService(
|
||||||
|
prompt: (question: string) => Promise<string>
|
||||||
|
): Promise<void> {
|
||||||
if (process.getuid && process.getuid() !== 0) {
|
if (process.getuid && process.getuid() !== 0) {
|
||||||
console.log('\nNote: Run "sudo nupst enable" to set up NUPST as a system service.');
|
console.log('\nNote: Run "sudo nupst enable" to set up NUPST as a system service.');
|
||||||
} else {
|
} else {
|
||||||
const setupService = await prompt('Would you like to enable NUPST as a system service? (y/N): ');
|
const setupService = await prompt(
|
||||||
|
'Would you like to enable NUPST as a system service? (y/N): '
|
||||||
|
);
|
||||||
if (setupService.toLowerCase() === 'y') {
|
if (setupService.toLowerCase() === 'y') {
|
||||||
try {
|
try {
|
||||||
await this.nupst.getSystemd().install();
|
await this.nupst.getSystemd().install();
|
||||||
console.log('Service installed and enabled to start on boot.');
|
console.log('Service installed and enabled to start on boot.');
|
||||||
|
|
||||||
// Ask if the user wants to start the service now
|
// Ask if the user wants to start the service now
|
||||||
const startService = await prompt('Would you like to start the NUPST service now? (Y/n): ');
|
const startService = await prompt(
|
||||||
|
'Would you like to start the NUPST service now? (Y/n): '
|
||||||
|
);
|
||||||
if (startService.toLowerCase() !== 'n') {
|
if (startService.toLowerCase() !== 'n') {
|
||||||
await this.nupst.getSystemd().start();
|
await this.nupst.getSystemd().start();
|
||||||
console.log('NUPST service started successfully.');
|
console.log('NUPST service started successfully.');
|
||||||
@ -912,80 +1006,89 @@ Options:
|
|||||||
try {
|
try {
|
||||||
await this.nupst.getDaemon().loadConfig();
|
await this.nupst.getDaemon().loadConfig();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('┌─ Configuration Error ─────────────────────┐');
|
const errorBoxWidth = 45;
|
||||||
console.error('│ No configuration found.');
|
logger.logBoxTitle('Configuration Error', errorBoxWidth);
|
||||||
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
logger.logBoxLine('No configuration found.');
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxLine("Please run 'nupst setup' first to create a configuration.");
|
||||||
|
logger.logBoxEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current configuration
|
// Get current configuration
|
||||||
const config = this.nupst.getDaemon().getConfig();
|
const config = this.nupst.getDaemon().getConfig();
|
||||||
|
|
||||||
console.log('┌─ NUPST Configuration ──────────────────────┐');
|
const boxWidth = 50;
|
||||||
|
logger.logBoxTitle('NUPST Configuration', boxWidth);
|
||||||
|
|
||||||
// SNMP Settings
|
// SNMP Settings
|
||||||
console.log('│ SNMP Settings:');
|
logger.logBoxLine('SNMP Settings:');
|
||||||
console.log(`│ Host: ${config.snmp.host}`);
|
logger.logBoxLine(` Host: ${config.snmp.host}`);
|
||||||
console.log(`│ Port: ${config.snmp.port}`);
|
logger.logBoxLine(` Port: ${config.snmp.port}`);
|
||||||
console.log(`│ Version: ${config.snmp.version}`);
|
logger.logBoxLine(` Version: ${config.snmp.version}`);
|
||||||
console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
logger.logBoxLine(` UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
||||||
|
|
||||||
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
if (config.snmp.version === 1 || config.snmp.version === 2) {
|
||||||
console.log(`│ Community: ${config.snmp.community}`);
|
logger.logBoxLine(` Community: ${config.snmp.community}`);
|
||||||
} else if (config.snmp.version === 3) {
|
} else if (config.snmp.version === 3) {
|
||||||
console.log(`│ Security Level: ${config.snmp.securityLevel}`);
|
logger.logBoxLine(` Security Level: ${config.snmp.securityLevel}`);
|
||||||
console.log(`│ Username: ${config.snmp.username}`);
|
logger.logBoxLine(` Username: ${config.snmp.username}`);
|
||||||
|
|
||||||
// Show auth and privacy details based on security level
|
// Show auth and privacy details based on security level
|
||||||
if (config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv') {
|
if (
|
||||||
console.log(`│ Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
|
config.snmp.securityLevel === 'authNoPriv' ||
|
||||||
|
config.snmp.securityLevel === 'authPriv'
|
||||||
|
) {
|
||||||
|
logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.snmp.securityLevel === 'authPriv') {
|
if (config.snmp.securityLevel === 'authPriv') {
|
||||||
console.log(`│ Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
|
logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show timeout value
|
// Show timeout value
|
||||||
console.log(`│ Timeout: ${config.snmp.timeout / 1000} seconds`);
|
logger.logBoxLine(` Timeout: ${config.snmp.timeout / 1000} seconds`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show OIDs if custom model is selected
|
// Show OIDs if custom model is selected
|
||||||
if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) {
|
if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) {
|
||||||
console.log('│ Custom OIDs:');
|
logger.logBoxLine('Custom OIDs:');
|
||||||
console.log(`│ Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`);
|
logger.logBoxLine(` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`);
|
||||||
console.log(`│ Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`);
|
logger.logBoxLine(
|
||||||
console.log(`│ Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`
|
||||||
|
);
|
||||||
|
logger.logBoxLine(` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thresholds
|
// Thresholds
|
||||||
console.log('│ Thresholds:');
|
logger.logBoxLine('Thresholds:');
|
||||||
console.log(`│ Battery: ${config.thresholds.battery}%`);
|
logger.logBoxLine(` Battery: ${config.thresholds.battery}%`);
|
||||||
console.log(`│ Runtime: ${config.thresholds.runtime} minutes`);
|
logger.logBoxLine(` Runtime: ${config.thresholds.runtime} minutes`);
|
||||||
console.log(`│ Check Interval: ${config.checkInterval / 1000} seconds`);
|
logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`);
|
||||||
|
|
||||||
// Configuration file location
|
// Configuration file location
|
||||||
console.log('│');
|
logger.logBoxLine('');
|
||||||
console.log('│ Configuration File Location:');
|
logger.logBoxLine('Configuration File Location:');
|
||||||
console.log('│ /etc/nupst/config.json');
|
logger.logBoxLine(' /etc/nupst/config.json');
|
||||||
|
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
|
|
||||||
// Show service status
|
// Show service status
|
||||||
try {
|
try {
|
||||||
const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
const isActive =
|
||||||
const isEnabled = execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled';
|
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
||||||
|
const isEnabled =
|
||||||
|
execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled';
|
||||||
|
|
||||||
console.log('┌─ Service Status ─────────────────────────┐');
|
const statusBoxWidth = 45;
|
||||||
console.log(`│ Service Active: ${isActive ? 'Yes' : 'No'}`);
|
logger.logBoxTitle('Service Status', statusBoxWidth);
|
||||||
console.log(`│ Service Enabled: ${isEnabled ? 'Yes' : 'No'}`);
|
logger.logBoxLine(`Service Active: ${isActive ? 'Yes' : 'No'}`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`Service Enabled: ${isEnabled ? 'Yes' : 'No'}`);
|
||||||
|
logger.logBoxEnd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore errors checking service status
|
// Ignore errors checking service status
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to display configuration: ${error.message}`);
|
logger.error(`Failed to display configuration: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1002,7 +1105,7 @@ Options:
|
|||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper function to prompt for input
|
// Helper function to prompt for input
|
||||||
@ -1019,7 +1122,9 @@ Options:
|
|||||||
console.log('This will completely remove NUPST from your system.\n');
|
console.log('This will completely remove NUPST from your system.\n');
|
||||||
|
|
||||||
// Ask about removing configuration
|
// Ask about removing configuration
|
||||||
const removeConfig = await prompt('Do you want to remove the NUPST configuration files? (y/N): ');
|
const removeConfig = await prompt(
|
||||||
|
'Do you want to remove the NUPST configuration files? (y/N): '
|
||||||
|
);
|
||||||
|
|
||||||
// Find the uninstall.sh script location
|
// Find the uninstall.sh script location
|
||||||
let uninstallScriptPath: string;
|
let uninstallScriptPath: string;
|
||||||
@ -1036,10 +1141,7 @@ Options:
|
|||||||
await fs.access(uninstallScriptPath);
|
await fs.access(uninstallScriptPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If we can't find it in the expected location, try common installation paths
|
// If we can't find it in the expected location, try common installation paths
|
||||||
const commonPaths = [
|
const commonPaths = ['/opt/nupst/uninstall.sh', join(process.cwd(), 'uninstall.sh')];
|
||||||
'/opt/nupst/uninstall.sh',
|
|
||||||
join(process.cwd(), 'uninstall.sh')
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const path of commonPaths) {
|
for (const path of commonPaths) {
|
||||||
try {
|
try {
|
||||||
@ -1068,16 +1170,15 @@ Options:
|
|||||||
const env = {
|
const env = {
|
||||||
...process.env,
|
...process.env,
|
||||||
REMOVE_CONFIG: removeConfig.toLowerCase() === 'y' ? 'yes' : 'no',
|
REMOVE_CONFIG: removeConfig.toLowerCase() === 'y' ? 'yes' : 'no',
|
||||||
REMOVE_REPO: 'yes', // Always remove repo as requested
|
REMOVE_REPO: 'yes', // Always remove repo as requested
|
||||||
NUPST_CLI_CALL: 'true' // Flag to indicate this is being called from CLI
|
NUPST_CLI_CALL: 'true', // Flag to indicate this is being called from CLI
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the uninstall script with sudo
|
// Run the uninstall script with sudo
|
||||||
execSync(`sudo bash ${uninstallScriptPath}`, {
|
execSync(`sudo bash ${uninstallScriptPath}`, {
|
||||||
env,
|
env,
|
||||||
stdio: 'inherit' // Show output in the terminal
|
stdio: 'inherit', // Show output in the terminal
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Uninstall failed: ${error.message}`);
|
console.error(`Uninstall failed: ${error.message}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
249
ts/daemon.ts
249
ts/daemon.ts
@ -1,11 +1,13 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { exec } from 'child_process';
|
import { exec, execFile } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { NupstSnmp } from './snmp/manager.js';
|
import { NupstSnmp } from './snmp/manager.js';
|
||||||
import type { ISnmpConfig } from './snmp/types.js';
|
import type { ISnmpConfig } from './snmp/types.js';
|
||||||
|
import { logger } from './logger.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration interface for the daemon
|
* Configuration interface for the daemon
|
||||||
@ -124,7 +126,7 @@ export class NupstDaemon {
|
|||||||
console.error('┌─ Configuration Error ─────────────────────┐');
|
console.error('┌─ Configuration Error ─────────────────────┐');
|
||||||
console.error(`│ ${message}`);
|
console.error(`│ ${message}`);
|
||||||
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
||||||
console.error('└──────────────────────────────────────────┘');
|
console.error('└───────────────────────────────────────────┘');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,11 +148,11 @@ export class NupstDaemon {
|
|||||||
*/
|
*/
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
if (this.isRunning) {
|
if (this.isRunning) {
|
||||||
console.log('Daemon is already running');
|
logger.log('Daemon is already running');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Starting NUPST daemon...');
|
logger.log('Starting NUPST daemon...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load configuration - this will throw an error if config doesn't exist
|
// Load configuration - this will throw an error if config doesn't exist
|
||||||
@ -164,11 +166,12 @@ export class NupstDaemon {
|
|||||||
this.snmp.getNupst().checkForUpdates().then(updateAvailable => {
|
this.snmp.getNupst().checkForUpdates().then(updateAvailable => {
|
||||||
if (updateAvailable) {
|
if (updateAvailable) {
|
||||||
const updateStatus = this.snmp.getNupst().getUpdateStatus();
|
const updateStatus = this.snmp.getNupst().getUpdateStatus();
|
||||||
console.log('┌─ Update Available ───────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log(`│ Current Version: ${updateStatus.currentVersion}`);
|
logger.logBoxTitle('Update Available', boxWidth);
|
||||||
console.log(`│ Latest Version: ${updateStatus.latestVersion}`);
|
logger.logBoxLine(`Current Version: ${updateStatus.currentVersion}`);
|
||||||
console.log('│ Run "sudo nupst update" to update');
|
logger.logBoxLine(`Latest Version: ${updateStatus.latestVersion}`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine('Run "sudo nupst update" to update');
|
||||||
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
}).catch(() => {}); // Ignore errors checking for updates
|
}).catch(() => {}); // Ignore errors checking for updates
|
||||||
|
|
||||||
@ -177,7 +180,7 @@ export class NupstDaemon {
|
|||||||
await this.monitor();
|
await this.monitor();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
console.error(`Daemon failed to start: ${error.message}`);
|
logger.error(`Daemon failed to start: ${error.message}`);
|
||||||
process.exit(1); // Exit with error
|
process.exit(1); // Exit with error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,23 +189,24 @@ export class NupstDaemon {
|
|||||||
* Log the loaded configuration settings
|
* Log the loaded configuration settings
|
||||||
*/
|
*/
|
||||||
private logConfigLoaded(): void {
|
private logConfigLoaded(): void {
|
||||||
console.log('┌─ Configuration Loaded ─────────────────────┐');
|
const boxWidth = 50;
|
||||||
console.log('│ SNMP Settings:');
|
logger.logBoxTitle('Configuration Loaded', boxWidth);
|
||||||
console.log(`│ Host: ${this.config.snmp.host}`);
|
logger.logBoxLine('SNMP Settings:');
|
||||||
console.log(`│ Port: ${this.config.snmp.port}`);
|
logger.logBoxLine(` Host: ${this.config.snmp.host}`);
|
||||||
console.log(`│ Version: ${this.config.snmp.version}`);
|
logger.logBoxLine(` Port: ${this.config.snmp.port}`);
|
||||||
console.log('│ Thresholds:');
|
logger.logBoxLine(` Version: ${this.config.snmp.version}`);
|
||||||
console.log(`│ Battery: ${this.config.thresholds.battery}%`);
|
logger.logBoxLine('Thresholds:');
|
||||||
console.log(`│ Runtime: ${this.config.thresholds.runtime} minutes`);
|
logger.logBoxLine(` Battery: ${this.config.thresholds.battery}%`);
|
||||||
console.log(`│ Check Interval: ${this.config.checkInterval / 1000} seconds`);
|
logger.logBoxLine(` Runtime: ${this.config.thresholds.runtime} minutes`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`Check Interval: ${this.config.checkInterval / 1000} seconds`);
|
||||||
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the monitoring daemon
|
* Stop the monitoring daemon
|
||||||
*/
|
*/
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
console.log('Stopping NUPST daemon...');
|
logger.log('Stopping NUPST daemon...');
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +214,7 @@ export class NupstDaemon {
|
|||||||
* Monitor the UPS status and trigger shutdown when necessary
|
* Monitor the UPS status and trigger shutdown when necessary
|
||||||
*/
|
*/
|
||||||
private async monitor(): Promise<void> {
|
private async monitor(): Promise<void> {
|
||||||
console.log('Starting UPS monitoring...');
|
logger.log('Starting UPS monitoring...');
|
||||||
|
|
||||||
let lastStatus: 'online' | 'onBattery' | 'unknown' = 'unknown';
|
let lastStatus: 'online' | 'onBattery' | 'unknown' = 'unknown';
|
||||||
let lastLogTime = 0; // Track when we last logged status
|
let lastLogTime = 0; // Track when we last logged status
|
||||||
@ -225,20 +229,22 @@ export class NupstDaemon {
|
|||||||
|
|
||||||
// Log status changes
|
// Log status changes
|
||||||
if (status.powerStatus !== lastStatus) {
|
if (status.powerStatus !== lastStatus) {
|
||||||
console.log('┌──────────────────────────────────────────┐');
|
const statusBoxWidth = 45;
|
||||||
console.log(`│ Power status changed: ${lastStatus} → ${status.powerStatus}`);
|
logger.logBoxTitle('Power Status Change', statusBoxWidth);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`Status changed: ${lastStatus} → ${status.powerStatus}`);
|
||||||
|
logger.logBoxEnd();
|
||||||
lastStatus = status.powerStatus;
|
lastStatus = status.powerStatus;
|
||||||
lastLogTime = currentTime; // Reset log timer when status changes
|
lastLogTime = currentTime; // Reset log timer when status changes
|
||||||
}
|
}
|
||||||
// Log status periodically (at least every 5 minutes)
|
// Log status periodically (at least every 5 minutes)
|
||||||
else if (shouldLogStatus) {
|
else if (shouldLogStatus) {
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
console.log('┌──────────────────────────────────────────┐');
|
const periodicBoxWidth = 45;
|
||||||
console.log(`│ [${timestamp}] Periodic Status Update`);
|
logger.logBoxTitle('Periodic Status Update', periodicBoxWidth);
|
||||||
console.log(`│ Power Status: ${status.powerStatus}`);
|
logger.logBoxLine(`Timestamp: ${timestamp}`);
|
||||||
console.log(`│ Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`);
|
logger.logBoxLine(`Power Status: ${status.powerStatus}`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`);
|
||||||
|
logger.logBoxEnd();
|
||||||
lastLogTime = currentTime;
|
lastLogTime = currentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,8 +272,8 @@ export class NupstDaemon {
|
|||||||
batteryCapacity: number,
|
batteryCapacity: number,
|
||||||
batteryRuntime: number
|
batteryRuntime: number
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
console.log('┌─ UPS Status ───────────────────────────────┐');
|
console.log('┌─ UPS Status ─────────────────────────────┐');
|
||||||
console.log(`│ Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min │`);
|
console.log(`│ Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
console.log('└──────────────────────────────────────────┘');
|
||||||
|
|
||||||
// Check battery threshold
|
// Check battery threshold
|
||||||
@ -292,29 +298,107 @@ export class NupstDaemon {
|
|||||||
* @param reason Reason for shutdown
|
* @param reason Reason for shutdown
|
||||||
*/
|
*/
|
||||||
public async initiateShutdown(reason: string): Promise<void> {
|
public async initiateShutdown(reason: string): Promise<void> {
|
||||||
console.log(`Initiating system shutdown due to: ${reason}`);
|
logger.log(`Initiating system shutdown due to: ${reason}`);
|
||||||
|
|
||||||
// Set a longer delay for shutdown to allow VMs and services to close
|
// Set a longer delay for shutdown to allow VMs and services to close
|
||||||
const shutdownDelayMinutes = 5;
|
const shutdownDelayMinutes = 5;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Execute shutdown command with delay to allow for VM graceful shutdown
|
// Find shutdown command in common system paths
|
||||||
const { stdout } = await execAsync(`shutdown -h +${shutdownDelayMinutes} "UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes"`);
|
const shutdownPaths = [
|
||||||
console.log('Shutdown initiated:', stdout);
|
'/sbin/shutdown',
|
||||||
console.log(`Allowing ${shutdownDelayMinutes} minutes for VMs to shut down safely`);
|
'/usr/sbin/shutdown',
|
||||||
|
'/bin/shutdown',
|
||||||
|
'/usr/bin/shutdown'
|
||||||
|
];
|
||||||
|
|
||||||
|
let shutdownCmd = '';
|
||||||
|
for (const path of shutdownPaths) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
shutdownCmd = path;
|
||||||
|
logger.log(`Found shutdown command at: ${shutdownCmd}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Continue checking other paths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shutdownCmd) {
|
||||||
|
// Execute shutdown command with delay to allow for VM graceful shutdown
|
||||||
|
logger.log(`Executing: ${shutdownCmd} -h +${shutdownDelayMinutes} "UPS battery critical..."`);
|
||||||
|
const { stdout } = await execFileAsync(shutdownCmd, [
|
||||||
|
'-h',
|
||||||
|
`+${shutdownDelayMinutes}`,
|
||||||
|
`UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes`
|
||||||
|
]);
|
||||||
|
logger.log(`Shutdown initiated: ${stdout}`);
|
||||||
|
logger.log(`Allowing ${shutdownDelayMinutes} minutes for VMs to shut down safely`);
|
||||||
|
} else {
|
||||||
|
// Try using the PATH to find shutdown
|
||||||
|
try {
|
||||||
|
logger.log('Shutdown command not found in common paths, trying via PATH...');
|
||||||
|
const { stdout } = await execAsync(`shutdown -h +${shutdownDelayMinutes} "UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes"`, {
|
||||||
|
env: process.env // Pass the current environment
|
||||||
|
});
|
||||||
|
logger.log(`Shutdown initiated: ${stdout}`);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Shutdown command not found: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Monitor UPS during shutdown and force immediate shutdown if battery gets too low
|
// Monitor UPS during shutdown and force immediate shutdown if battery gets too low
|
||||||
console.log('Monitoring UPS during shutdown process...');
|
logger.log('Monitoring UPS during shutdown process...');
|
||||||
await this.monitorDuringShutdown();
|
await this.monitorDuringShutdown();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initiate shutdown:', error);
|
logger.error(`Failed to initiate shutdown: ${error}`);
|
||||||
// Try a different method if first one fails
|
|
||||||
try {
|
// Try alternative shutdown methods
|
||||||
console.log('Trying alternative shutdown method...');
|
const alternatives = [
|
||||||
await execAsync('poweroff --force');
|
{ cmd: 'poweroff', args: ['--force'] },
|
||||||
} catch (innerError) {
|
{ cmd: 'halt', args: ['-p'] },
|
||||||
console.error('All shutdown methods failed:', innerError);
|
{ cmd: 'systemctl', args: ['poweroff'] },
|
||||||
|
{ cmd: 'reboot', args: ['-p'] } // Some systems allow reboot -p for power off
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const alt of alternatives) {
|
||||||
|
try {
|
||||||
|
// First check if command exists in common system paths
|
||||||
|
const paths = [
|
||||||
|
`/sbin/${alt.cmd}`,
|
||||||
|
`/usr/sbin/${alt.cmd}`,
|
||||||
|
`/bin/${alt.cmd}`,
|
||||||
|
`/usr/bin/${alt.cmd}`
|
||||||
|
];
|
||||||
|
|
||||||
|
let cmdPath = '';
|
||||||
|
for (const path of paths) {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
cmdPath = path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmdPath) {
|
||||||
|
logger.log(`Trying alternative shutdown method: ${cmdPath} ${alt.args.join(' ')}`);
|
||||||
|
await execFileAsync(cmdPath, alt.args);
|
||||||
|
return; // Exit if successful
|
||||||
|
} else {
|
||||||
|
// Try using PATH environment
|
||||||
|
logger.log(`Trying alternative via PATH: ${alt.cmd} ${alt.args.join(' ')}`);
|
||||||
|
await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, {
|
||||||
|
env: process.env // Pass the current environment
|
||||||
|
});
|
||||||
|
return; // Exit if successful
|
||||||
|
}
|
||||||
|
} catch (altError) {
|
||||||
|
logger.error(`Alternative method ${alt.cmd} failed: ${altError}`);
|
||||||
|
// Continue to next method
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.error('All shutdown methods failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,10 +430,79 @@ export class NupstDaemon {
|
|||||||
console.log('└──────────────────────────────────────────┘');
|
console.log('└──────────────────────────────────────────┘');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await execAsync('shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"');
|
// Find shutdown command in common system paths
|
||||||
|
const shutdownPaths = [
|
||||||
|
'/sbin/shutdown',
|
||||||
|
'/usr/sbin/shutdown',
|
||||||
|
'/bin/shutdown',
|
||||||
|
'/usr/bin/shutdown'
|
||||||
|
];
|
||||||
|
|
||||||
|
let shutdownCmd = '';
|
||||||
|
for (const path of shutdownPaths) {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
shutdownCmd = path;
|
||||||
|
console.log(`Found shutdown command at: ${shutdownCmd}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shutdownCmd) {
|
||||||
|
console.log(`Executing emergency shutdown: ${shutdownCmd} -h now`);
|
||||||
|
await execFileAsync(shutdownCmd, ['-h', 'now', 'EMERGENCY: UPS battery critically low, shutting down NOW']);
|
||||||
|
} else {
|
||||||
|
// Try using the PATH to find shutdown
|
||||||
|
console.log('Shutdown command not found in common paths, trying via PATH...');
|
||||||
|
await execAsync('shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"', {
|
||||||
|
env: process.env // Pass the current environment
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Emergency shutdown failed, trying alternative method...');
|
console.error('Emergency shutdown failed, trying alternative methods...');
|
||||||
await execAsync('poweroff --force');
|
|
||||||
|
// Try alternative shutdown methods in sequence
|
||||||
|
const alternatives = [
|
||||||
|
{ cmd: 'poweroff', args: ['--force'] },
|
||||||
|
{ cmd: 'halt', args: ['-p'] },
|
||||||
|
{ cmd: 'systemctl', args: ['poweroff'] }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const alt of alternatives) {
|
||||||
|
try {
|
||||||
|
// Check common paths
|
||||||
|
const paths = [
|
||||||
|
`/sbin/${alt.cmd}`,
|
||||||
|
`/usr/sbin/${alt.cmd}`,
|
||||||
|
`/bin/${alt.cmd}`,
|
||||||
|
`/usr/bin/${alt.cmd}`
|
||||||
|
];
|
||||||
|
|
||||||
|
let cmdPath = '';
|
||||||
|
for (const path of paths) {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
cmdPath = path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmdPath) {
|
||||||
|
console.log(`Emergency: using ${cmdPath} ${alt.args.join(' ')}`);
|
||||||
|
await execFileAsync(cmdPath, alt.args);
|
||||||
|
return; // Exit if successful
|
||||||
|
} else {
|
||||||
|
// Try using PATH
|
||||||
|
console.log(`Emergency: trying ${alt.cmd} via PATH`);
|
||||||
|
await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, {
|
||||||
|
env: process.env
|
||||||
|
});
|
||||||
|
return; // Exit if successful
|
||||||
|
}
|
||||||
|
} catch (altError) {
|
||||||
|
// Continue to next method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('All emergency shutdown methods failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop monitoring after initiating emergency shutdown
|
// Stop monitoring after initiating emergency shutdown
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { NupstCli } from './cli.js';
|
import { NupstCli } from './cli.js';
|
||||||
|
import { logger } from './logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point for NUPST
|
* Main entry point for NUPST
|
||||||
@ -13,6 +14,6 @@ async function main() {
|
|||||||
|
|
||||||
// Run the main function and handle any errors
|
// Run the main function and handle any errors
|
||||||
main().catch(error => {
|
main().catch(error => {
|
||||||
console.error('Error:', error);
|
logger.error(`Error: ${error}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
144
ts/logger.ts
Normal file
144
ts/logger.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* A simple logger class that provides consistent formatting for log messages
|
||||||
|
* including support for logboxes with title, lines, and closing
|
||||||
|
*/
|
||||||
|
export class Logger {
|
||||||
|
private currentBoxWidth: number | null = null;
|
||||||
|
private static instance: Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Logger instance
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.currentBoxWidth = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton logger instance
|
||||||
|
* @returns The singleton logger instance
|
||||||
|
*/
|
||||||
|
public static getInstance(): Logger {
|
||||||
|
if (!Logger.instance) {
|
||||||
|
Logger.instance = new Logger();
|
||||||
|
}
|
||||||
|
return Logger.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a message
|
||||||
|
* @param message Message to log
|
||||||
|
*/
|
||||||
|
public log(message: string): void {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an error message
|
||||||
|
* @param message Error message to log
|
||||||
|
*/
|
||||||
|
public error(message: string): void {
|
||||||
|
console.error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a warning message with a warning emoji
|
||||||
|
* @param message Warning message to log
|
||||||
|
*/
|
||||||
|
public warn(message: string): void {
|
||||||
|
console.warn(`⚠️ ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a success message with a checkmark
|
||||||
|
* @param message Success message to log
|
||||||
|
*/
|
||||||
|
public success(message: string): void {
|
||||||
|
console.log(`✓ ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a logbox title and set the current box width
|
||||||
|
* @param title Title of the logbox
|
||||||
|
* @param width Width of the logbox (including borders)
|
||||||
|
*/
|
||||||
|
public logBoxTitle(title: string, width: number): void {
|
||||||
|
this.currentBoxWidth = width;
|
||||||
|
|
||||||
|
// Create the title line with appropriate padding
|
||||||
|
const paddedTitle = ` ${title} `;
|
||||||
|
const remainingSpace = width - 3 - paddedTitle.length;
|
||||||
|
|
||||||
|
// Title line: ┌─ Title ───┐
|
||||||
|
const titleLine = `┌─${paddedTitle}${'─'.repeat(remainingSpace)}┐`;
|
||||||
|
|
||||||
|
console.log(titleLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a logbox line
|
||||||
|
* @param content Content of the line
|
||||||
|
* @param width Optional width override. If not provided, uses the current box width.
|
||||||
|
*/
|
||||||
|
public logBoxLine(content: string, width?: number): void {
|
||||||
|
const boxWidth = width || this.currentBoxWidth;
|
||||||
|
|
||||||
|
if (!boxWidth) {
|
||||||
|
throw new Error('No box width specified and no previous box width to use');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the available space for content
|
||||||
|
const availableSpace = boxWidth - 2; // Account for left and right borders
|
||||||
|
|
||||||
|
if (content.length <= availableSpace - 1) {
|
||||||
|
// If content fits with at least one space for the right border stripe
|
||||||
|
const padding = availableSpace - content.length - 1;
|
||||||
|
console.log(`│ ${content}${' '.repeat(padding)}│`);
|
||||||
|
} else {
|
||||||
|
// Content is too long, let it flow out of boundaries.
|
||||||
|
console.log(`│ ${content}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a logbox end
|
||||||
|
* @param width Optional width override. If not provided, uses the current box width.
|
||||||
|
*/
|
||||||
|
public logBoxEnd(width?: number): void {
|
||||||
|
const boxWidth = width || this.currentBoxWidth;
|
||||||
|
|
||||||
|
if (!boxWidth) {
|
||||||
|
throw new Error('No box width specified and no previous box width to use');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the bottom border: └────────┘
|
||||||
|
console.log(`└${'─'.repeat(boxWidth - 2)}┘`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a complete logbox with title, content lines, and ending
|
||||||
|
* @param title Title of the logbox
|
||||||
|
* @param lines Array of content lines
|
||||||
|
* @param width Width of the logbox
|
||||||
|
*/
|
||||||
|
public logBox(title: string, lines: string[], width: number): void {
|
||||||
|
this.logBoxTitle(title, width);
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
this.logBoxLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logBoxEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a divider line
|
||||||
|
* @param width Width of the divider
|
||||||
|
* @param character Character to use for the divider (default: ─)
|
||||||
|
*/
|
||||||
|
public logDivider(width: number, character: string = '─'): void {
|
||||||
|
console.log(character.repeat(width));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export a singleton instance for easy use
|
||||||
|
export const logger = Logger.getInstance();
|
32
ts/nupst.ts
32
ts/nupst.ts
@ -4,6 +4,7 @@ import { NupstSystemd } from './systemd.js';
|
|||||||
import { commitinfo } from './00_commitinfo_data.js';
|
import { commitinfo } from './00_commitinfo_data.js';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
|
import { logger } from './logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Nupst class that coordinates all components
|
* Main Nupst class that coordinates all components
|
||||||
@ -70,7 +71,7 @@ export class Nupst {
|
|||||||
|
|
||||||
return this.updateAvailable;
|
return this.updateAvailable;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error checking for updates: ${error.message}`);
|
logger.error(`Error checking for updates: ${error.message}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,28 +163,33 @@ export class Nupst {
|
|||||||
*/
|
*/
|
||||||
public logVersionInfo(checkForUpdates: boolean = true): void {
|
public logVersionInfo(checkForUpdates: boolean = true): void {
|
||||||
const version = this.getVersion();
|
const version = this.getVersion();
|
||||||
console.log('┌─ NUPST Version ────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log(`│ Current Version: ${version}`);
|
|
||||||
|
logger.logBoxTitle('NUPST Version', boxWidth);
|
||||||
|
logger.logBoxLine(`Current Version: ${version}`);
|
||||||
|
|
||||||
if (this.updateAvailable && this.latestVersion) {
|
if (this.updateAvailable && this.latestVersion) {
|
||||||
console.log(`│ Update Available: ${this.latestVersion}`);
|
logger.logBoxLine(`Update Available: ${this.latestVersion}`);
|
||||||
console.log('│ Run "sudo nupst update" to update');
|
logger.logBoxLine('Run "sudo nupst update" to update');
|
||||||
|
logger.logBoxEnd();
|
||||||
} else if (checkForUpdates) {
|
} else if (checkForUpdates) {
|
||||||
console.log('│ Checking for updates...');
|
logger.logBoxLine('Checking for updates...');
|
||||||
|
|
||||||
|
// We can't end the box yet since we're in an async operation
|
||||||
this.checkForUpdates().then(updateAvailable => {
|
this.checkForUpdates().then(updateAvailable => {
|
||||||
if (updateAvailable) {
|
if (updateAvailable) {
|
||||||
console.log(`│ Update Available: ${this.latestVersion}`);
|
logger.logBoxLine(`Update Available: ${this.latestVersion}`);
|
||||||
console.log('│ Run "sudo nupst update" to update');
|
logger.logBoxLine('Run "sudo nupst update" to update');
|
||||||
} else {
|
} else {
|
||||||
console.log('│ You are running the latest version');
|
logger.logBoxLine('You are running the latest version');
|
||||||
}
|
}
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
console.log('│ Could not check for updates');
|
logger.logBoxLine('Could not check for updates');
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import * as dgram from 'dgram';
|
|
||||||
import * as snmp from 'net-snmp';
|
import * as snmp from 'net-snmp';
|
||||||
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js';
|
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.js';
|
||||||
import { UpsOidSets } from './oid-sets.js';
|
import { UpsOidSets } from './oid-sets.js';
|
||||||
|
108
ts/systemd.ts
108
ts/systemd.ts
@ -1,6 +1,7 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { NupstDaemon } from './daemon.js';
|
import { NupstDaemon } from './daemon.js';
|
||||||
|
import { logger } from './logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for managing systemd service
|
* Class for managing systemd service
|
||||||
@ -47,10 +48,11 @@ WantedBy=multi-user.target
|
|||||||
try {
|
try {
|
||||||
await fs.access(configPath);
|
await fs.access(configPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('┌─ Configuration Error ─────────────────────┐');
|
const boxWidth = 50;
|
||||||
console.error(`│ No configuration file found at ${configPath}`);
|
logger.logBoxTitle('Configuration Error', boxWidth);
|
||||||
console.error('│ Please run \'nupst setup\' first to create a configuration.');
|
logger.logBoxLine(`No configuration file found at ${configPath}`);
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxLine("Please run 'nupst setup' first to create a configuration.");
|
||||||
|
logger.logBoxEnd();
|
||||||
throw new Error('Configuration not found');
|
throw new Error('Configuration not found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,23 +68,24 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
// Write the service file
|
// Write the service file
|
||||||
await fs.writeFile(this.serviceFilePath, this.serviceTemplate);
|
await fs.writeFile(this.serviceFilePath, this.serviceTemplate);
|
||||||
console.log('┌─ Service Installation ─────────────────────┐');
|
const boxWidth = 50;
|
||||||
console.log(`│ Service file created at ${this.serviceFilePath}`);
|
logger.logBoxTitle('Service Installation', boxWidth);
|
||||||
|
logger.logBoxLine(`Service file created at ${this.serviceFilePath}`);
|
||||||
|
|
||||||
// Reload systemd daemon
|
// Reload systemd daemon
|
||||||
execSync('systemctl daemon-reload');
|
execSync('systemctl daemon-reload');
|
||||||
console.log('│ Systemd daemon reloaded');
|
logger.logBoxLine('Systemd daemon reloaded');
|
||||||
|
|
||||||
// Enable the service
|
// Enable the service
|
||||||
execSync('systemctl enable nupst.service');
|
execSync('systemctl enable nupst.service');
|
||||||
console.log('│ Service enabled to start on boot');
|
logger.logBoxLine('Service enabled to start on boot');
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message === 'Configuration not found') {
|
if (error.message === 'Configuration not found') {
|
||||||
// Just rethrow the error as the message has already been displayed
|
// Just rethrow the error as the message has already been displayed
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
console.error('Failed to install systemd service:', error);
|
logger.error(`Failed to install systemd service: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,15 +100,16 @@ WantedBy=multi-user.target
|
|||||||
await this.checkConfigExists();
|
await this.checkConfigExists();
|
||||||
|
|
||||||
execSync('systemctl start nupst.service');
|
execSync('systemctl start nupst.service');
|
||||||
console.log('┌─ Service Status ─────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ NUPST service started successfully');
|
logger.logBoxTitle('Service Status', boxWidth);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine('NUPST service started successfully');
|
||||||
|
logger.logBoxEnd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message === 'Configuration not found') {
|
if (error.message === 'Configuration not found') {
|
||||||
// Exit with error code since configuration is required
|
// Exit with error code since configuration is required
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.error('Failed to start service:', error);
|
logger.error(`Failed to start service: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,9 +121,9 @@ WantedBy=multi-user.target
|
|||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
execSync('systemctl stop nupst.service');
|
execSync('systemctl stop nupst.service');
|
||||||
console.log('NUPST service stopped');
|
logger.success('NUPST service stopped');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to stop service:', error);
|
logger.error(`Failed to stop service: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,9 +136,10 @@ WantedBy=multi-user.target
|
|||||||
try {
|
try {
|
||||||
// Enable debug mode if requested
|
// Enable debug mode if requested
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.log('┌─ Debug Mode ─────────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log('│ SNMP debugging enabled - detailed logs will be shown');
|
logger.logBoxTitle('Debug Mode', boxWidth);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine('SNMP debugging enabled - detailed logs will be shown');
|
||||||
|
logger.logBoxEnd();
|
||||||
this.daemon.getNupstSnmp().enableDebug();
|
this.daemon.getNupstSnmp().enableDebug();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +157,7 @@ WantedBy=multi-user.target
|
|||||||
await this.displayServiceStatus();
|
await this.displayServiceStatus();
|
||||||
await this.displayUpsStatus();
|
await this.displayUpsStatus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to get status: ${error.message}`);
|
logger.error(`Failed to get status: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,13 +168,18 @@ WantedBy=multi-user.target
|
|||||||
private async displayServiceStatus(): Promise<void> {
|
private async displayServiceStatus(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const serviceStatus = execSync('systemctl status nupst.service').toString();
|
const serviceStatus = execSync('systemctl status nupst.service').toString();
|
||||||
console.log('┌─ Service Status ─────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log(serviceStatus.split('\n').map(line => `│ ${line}`).join('\n'));
|
logger.logBoxTitle('Service Status', boxWidth);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
// Process each line of the status output
|
||||||
|
serviceStatus.split('\n').forEach(line => {
|
||||||
|
logger.logBoxLine(line);
|
||||||
|
});
|
||||||
|
logger.logBoxEnd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('┌─ Service Status ─────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.error('│ Service is not running');
|
logger.logBoxTitle('Service Status', boxWidth);
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxLine('Service is not running');
|
||||||
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,22 +200,24 @@ WantedBy=multi-user.target
|
|||||||
timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for status check
|
timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for status check
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('┌─ Connecting to UPS... ────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.log(`│ Host: ${config.snmp.host}:${config.snmp.port}`);
|
logger.logBoxTitle('Connecting to UPS...', boxWidth);
|
||||||
console.log(`│ UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
logger.logBoxLine(`Host: ${config.snmp.host}:${config.snmp.port}`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
|
||||||
|
logger.logBoxEnd();
|
||||||
|
|
||||||
const status = await snmp.getUpsStatus(snmpConfig);
|
const status = await snmp.getUpsStatus(snmpConfig);
|
||||||
|
|
||||||
console.log('┌─ UPS Status ───────────────────────────────┐');
|
logger.logBoxTitle('UPS Status', boxWidth);
|
||||||
console.log(`│ Power Status: ${status.powerStatus}`);
|
logger.logBoxLine(`Power Status: ${status.powerStatus}`);
|
||||||
console.log(`│ Battery Capacity: ${status.batteryCapacity}%`);
|
logger.logBoxLine(`Battery Capacity: ${status.batteryCapacity}%`);
|
||||||
console.log(`│ Runtime Remaining: ${status.batteryRuntime} minutes`);
|
logger.logBoxLine(`Runtime Remaining: ${status.batteryRuntime} minutes`);
|
||||||
console.log('└──────────────────────────────────────────┘');
|
logger.logBoxEnd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('┌─ UPS Status ───────────────────────────────┐');
|
const boxWidth = 45;
|
||||||
console.error(`│ Failed to retrieve UPS status: ${error.message}`);
|
logger.logBoxTitle('UPS Status', boxWidth);
|
||||||
console.error('└──────────────────────────────────────────┘');
|
logger.logBoxLine(`Failed to retrieve UPS status: ${error.message}`);
|
||||||
|
logger.logBoxEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,10 +233,10 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
// Reload systemd daemon
|
// Reload systemd daemon
|
||||||
execSync('systemctl daemon-reload');
|
execSync('systemctl daemon-reload');
|
||||||
console.log('Systemd daemon reloaded');
|
logger.log('Systemd daemon reloaded');
|
||||||
console.log('NUPST service has been successfully uninstalled');
|
logger.success('NUPST service has been successfully uninstalled');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to disable and uninstall service:', error);
|
logger.error(`Failed to disable and uninstall service: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,11 +247,11 @@ WantedBy=multi-user.target
|
|||||||
*/
|
*/
|
||||||
private async stopService(): Promise<void> {
|
private async stopService(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log('Stopping NUPST service...');
|
logger.log('Stopping NUPST service...');
|
||||||
execSync('systemctl stop nupst.service');
|
execSync('systemctl stop nupst.service');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Service might not be running, that's okay
|
// Service might not be running, that's okay
|
||||||
console.log('Service was not running or could not be stopped');
|
logger.log('Service was not running or could not be stopped');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,10 +261,10 @@ WantedBy=multi-user.target
|
|||||||
*/
|
*/
|
||||||
private async disableService(): Promise<void> {
|
private async disableService(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log('Disabling NUPST service...');
|
logger.log('Disabling NUPST service...');
|
||||||
execSync('systemctl disable nupst.service');
|
execSync('systemctl disable nupst.service');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Service was not enabled or could not be disabled');
|
logger.log('Service was not enabled or could not be disabled');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,11 +274,11 @@ WantedBy=multi-user.target
|
|||||||
*/
|
*/
|
||||||
private async removeServiceFile(): Promise<void> {
|
private async removeServiceFile(): Promise<void> {
|
||||||
if (await fs.stat(this.serviceFilePath).catch(() => null)) {
|
if (await fs.stat(this.serviceFilePath).catch(() => null)) {
|
||||||
console.log(`Removing service file ${this.serviceFilePath}...`);
|
logger.log(`Removing service file ${this.serviceFilePath}...`);
|
||||||
await fs.unlink(this.serviceFilePath);
|
await fs.unlink(this.serviceFilePath);
|
||||||
console.log('Service file removed');
|
logger.log('Service file removed');
|
||||||
} else {
|
} else {
|
||||||
console.log('Service file did not exist');
|
logger.log('Service file did not exist');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user