style: configure deno fmt to use single quotes
All checks were successful
CI / Build All Platforms (Tag/Main only) (push) Has been skipped
CI / Type Check & Lint (push) Successful in 6s
CI / Build Test (Current Platform) (push) Successful in 6s

- Add singleQuote: true to deno.json fmt configuration
- Reformat all files with single quotes using deno fmt
This commit is contained in:
2025-10-19 13:14:18 +00:00
parent b935087d50
commit 071ded9c41
24 changed files with 1094 additions and 672 deletions

View File

@@ -7,6 +7,7 @@ This directory contains automated workflows for NUPST's CI/CD pipeline.
### CI Workflow (`ci.yml`) ### CI Workflow (`ci.yml`)
**Triggers:** **Triggers:**
- Push to `main` branch - Push to `main` branch
- Push to `migration/**` branches - Push to `migration/**` branches
- Pull requests to `main` - Pull requests to `main`
@@ -30,6 +31,7 @@ This directory contains automated workflows for NUPST's CI/CD pipeline.
### Release Workflow (`release.yml`) ### Release Workflow (`release.yml`)
**Triggers:** **Triggers:**
- Push tags matching `v*` (e.g., `v4.0.0`) - Push tags matching `v*` (e.g., `v4.0.0`)
**Jobs:** **Jobs:**
@@ -115,12 +117,14 @@ cd ../..
### Version Mismatch Error ### Version Mismatch Error
If the release workflow fails with "Version mismatch": If the release workflow fails with "Version mismatch":
- Ensure `deno.json` version matches the git tag - Ensure `deno.json` version matches the git tag
- Example: tag `v4.0.0` requires `"version": "4.0.0"` in deno.json - Example: tag `v4.0.0` requires `"version": "4.0.0"` in deno.json
### Compilation Errors ### Compilation Errors
If compilation fails: If compilation fails:
1. Test locally: `bash scripts/compile-all.sh` 1. Test locally: `bash scripts/compile-all.sh`
2. Check Deno version compatibility 2. Check Deno version compatibility
3. Review TypeScript errors: `deno check mod.ts` 3. Review TypeScript errors: `deno check mod.ts`
@@ -128,6 +132,7 @@ If compilation fails:
### Upload Failures ### Upload Failures
If binary upload fails: If binary upload fails:
1. Check Gitea Actions permissions 1. Check Gitea Actions permissions
2. Verify `GITHUB_TOKEN` secret exists (auto-provided by Gitea) 2. Verify `GITHUB_TOKEN` secret exists (auto-provided by Gitea)
3. Try manual release creation 3. Try manual release creation

View File

@@ -7,7 +7,7 @@ language: typescript
# the encoding used by text files in the project # the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8" encoding: 'utf-8'
# whether to use the project's gitignore file to ignore files # whether to use the project's gitignore file to ignore files
# Added on 2025-04-07 # Added on 2025-04-07
@@ -66,6 +66,6 @@ excluded_tools: []
# initial prompt for the project. It will always be given to the LLM upon activating the project # initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand). # (contrary to the memories, which are loaded on demand).
initial_prompt: "" initial_prompt: ''
project_name: "nupst" project_name: 'nupst'

View File

@@ -4,27 +4,34 @@
**MAJOR RELEASE: NUPST v4.0 is a complete rewrite powered by Deno** **MAJOR RELEASE: NUPST v4.0 is a complete rewrite powered by Deno**
This release fundamentally changes NUPST's architecture from Node.js-based to Deno-based, distributed as pre-compiled binaries. This is a **breaking change** in terms of installation and distribution, but configuration files from v3.x are **fully compatible**. This release fundamentally changes NUPST's architecture from Node.js-based to Deno-based,
distributed as pre-compiled binaries. This is a **breaking change** in terms of installation and
distribution, but configuration files from v3.x are **fully compatible**.
### Breaking Changes ### Breaking Changes
**Installation & Distribution:** **Installation & Distribution:**
- **Removed**: Node.js runtime dependency - NUPST no longer requires Node.js - **Removed**: Node.js runtime dependency - NUPST no longer requires Node.js
- **Removed**: npm package distribution (no longer published to npmjs.org) - **Removed**: npm package distribution (no longer published to npmjs.org)
- **Removed**: `bin/nupst` wrapper script - **Removed**: `bin/nupst` wrapper script
- **Removed**: `setup.sh` dependency installation - **Removed**: `setup.sh` dependency installation
- **Removed**: All Node.js-related files (package.json, tsconfig.json, pnpm-lock.yaml, npmextra.json) - **Removed**: All Node.js-related files (package.json, tsconfig.json, pnpm-lock.yaml,
npmextra.json)
- **Changed**: Installation now downloads pre-compiled binaries instead of cloning repository - **Changed**: Installation now downloads pre-compiled binaries instead of cloning repository
- **Changed**: Binary-based distribution (~340MB self-contained executables) - **Changed**: Binary-based distribution (~340MB self-contained executables)
**CLI Structure (Backward Compatible):** **CLI Structure (Backward Compatible):**
- **Changed**: Commands now use subcommand structure (e.g., `nupst service enable` instead of `nupst enable`)
- **Changed**: Commands now use subcommand structure (e.g., `nupst service enable` instead of
`nupst enable`)
- **Maintained**: Old command format still works with deprecation warnings for smooth migration - **Maintained**: Old command format still works with deprecation warnings for smooth migration
- **Added**: Aliases for common commands (`nupst ls`, `nupst rm`) - **Added**: Aliases for common commands (`nupst ls`, `nupst rm`)
### New Features ### New Features
**Distribution & Installation:** **Distribution & Installation:**
- Pre-compiled binaries for 5 platforms: - Pre-compiled binaries for 5 platforms:
- Linux x86_64 - Linux x86_64
- Linux ARM64 - Linux ARM64
@@ -38,6 +45,7 @@ This release fundamentally changes NUPST's architecture from Node.js-based to De
- Cross-platform compilation from single codebase - Cross-platform compilation from single codebase
**CI/CD Automation:** **CI/CD Automation:**
- Gitea Actions workflows for continuous integration - Gitea Actions workflows for continuous integration
- Automated release workflow triggered by git tags - Automated release workflow triggered by git tags
- Automatic binary compilation for all platforms on release - Automatic binary compilation for all platforms on release
@@ -45,6 +53,7 @@ This release fundamentally changes NUPST's architecture from Node.js-based to De
- Build verification on every push - Build verification on every push
**CLI Improvements:** **CLI Improvements:**
- New hierarchical command structure with subcommands - New hierarchical command structure with subcommands
- `nupst service` - Service management (enable, disable, start, stop, restart, status, logs) - `nupst service` - Service management (enable, disable, start, stop, restart, status, logs)
- `nupst ups` - UPS device management (add, edit, remove, list, test) - `nupst ups` - UPS device management (add, edit, remove, list, test)
@@ -55,6 +64,7 @@ This release fundamentally changes NUPST's architecture from Node.js-based to De
- Backward compatibility maintained with deprecation warnings - Backward compatibility maintained with deprecation warnings
**Technical Improvements:** **Technical Improvements:**
- Deno runtime for modern TypeScript/JavaScript execution - Deno runtime for modern TypeScript/JavaScript execution
- Native TypeScript support without compilation step - Native TypeScript support without compilation step
- Faster startup and execution compared to Node.js - Faster startup and execution compared to Node.js
@@ -65,18 +75,22 @@ This release fundamentally changes NUPST's architecture from Node.js-based to De
### Migration Guide ### Migration Guide
**For Users:** **For Users:**
1. Stop existing v3.x service: `sudo nupst disable` 1. Stop existing v3.x service: `sudo nupst disable`
2. Install v4.0 using new installer: `curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y` 2. Install v4.0 using new installer:
`curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- -y`
3. Your configuration at `/etc/nupst/config.json` is preserved and fully compatible 3. Your configuration at `/etc/nupst/config.json` is preserved and fully compatible
4. Enable service with new CLI: `sudo nupst service enable && sudo nupst service start` 4. Enable service with new CLI: `sudo nupst service enable && sudo nupst service start`
5. Update systemd commands to use new syntax (old syntax still works with warnings) 5. Update systemd commands to use new syntax (old syntax still works with warnings)
**Configuration Compatibility:** **Configuration Compatibility:**
- All configuration files from v3.x work without modification - All configuration files from v3.x work without modification
- No changes to `/etc/nupst/config.json` format - No changes to `/etc/nupst/config.json` format
- All SNMP settings, thresholds, and group configurations preserved - All SNMP settings, thresholds, and group configurations preserved
**Command Mapping:** **Command Mapping:**
```bash ```bash
# Old (v3.x) → New (v4.0) # Old (v3.x) → New (v4.0)
nupst enable → nupst service enable nupst enable → nupst service enable
@@ -100,6 +114,7 @@ nupst config → nupst config show
### Technical Details ### Technical Details
**Commit History:** **Commit History:**
- `df6a44d`: Complete migration with Gitea Actions workflows and install.sh updates - `df6a44d`: Complete migration with Gitea Actions workflows and install.sh updates
- `9efcc4b`: CLI reorganization with subcommand structure - `9efcc4b`: CLI reorganization with subcommand structure
- `5903ae7`: Cross-platform compilation scripts - `5903ae7`: Cross-platform compilation scripts
@@ -107,13 +122,16 @@ nupst config → nupst config show
- `5f4f3ec`: Initial migration to Deno - `5f4f3ec`: Initial migration to Deno
**Files Changed:** **Files Changed:**
- Removed: 11 files (package.json, tsconfig.json, pnpm-lock.yaml, npmextra.json, bin/nupst, setup.sh)
- Removed: 11 files (package.json, tsconfig.json, pnpm-lock.yaml, npmextra.json, bin/nupst,
setup.sh)
- Added: 3 Gitea Actions workflows (ci.yml, release.yml, README.md) - Added: 3 Gitea Actions workflows (ci.yml, release.yml, README.md)
- Modified: 14 TypeScript files for Deno compatibility - Modified: 14 TypeScript files for Deno compatibility
- Updated: install.sh, .gitignore, readme.md - Updated: install.sh, .gitignore, readme.md
- Net reduction: -10,242 lines (93% reduction in repository size) - Net reduction: -10,242 lines (93% reduction in repository size)
**Dependencies:** **Dependencies:**
- Runtime: Deno v1.x (bundled in binary, no installation required) - Runtime: Deno v1.x (bundled in binary, no installation required)
- SNMP: npm:net-snmp@3.20.0 (bundled in binary via npm: specifier) - SNMP: npm:net-snmp@3.20.0 (bundled in binary via npm: specifier)
- Node.js built-ins: Accessed via node: specifier (node:fs, node:child_process, etc.) - Node.js built-ins: Accessed via node: specifier (node:fs, node:child_process, etc.)
@@ -121,7 +139,9 @@ nupst config → nupst config show
### Benefits ### Benefits
**For Users:** **For Users:**
- **Faster Installation**: Download single binary instead of cloning repo + installing Node.js + npm dependencies
- **Faster Installation**: Download single binary instead of cloning repo + installing Node.js + npm
dependencies
- **Zero Dependencies**: No Node.js or npm required on target system - **Zero Dependencies**: No Node.js or npm required on target system
- **Smaller Footprint**: Single binary vs repo + Node.js + node_modules - **Smaller Footprint**: Single binary vs repo + Node.js + node_modules
- **Easier Updates**: Download new binary instead of git pull + npm install - **Easier Updates**: Download new binary instead of git pull + npm install
@@ -129,6 +149,7 @@ nupst config → nupst config show
- **Platform Support**: Official binaries for all major platforms - **Platform Support**: Official binaries for all major platforms
**For Developers:** **For Developers:**
- **Modern Tooling**: Native TypeScript support without build configuration - **Modern Tooling**: Native TypeScript support without build configuration
- **Faster Development**: No compilation step during development - **Faster Development**: No compilation step during development
- **CI/CD Automation**: Automated releases and testing - **CI/CD Automation**: Automated releases and testing
@@ -143,22 +164,27 @@ nupst config → nupst config show
### Acknowledgments ### Acknowledgments
This release represents a complete modernization of NUPST's infrastructure while maintaining full backward compatibility for user configurations. Special thanks to the Deno team for creating an excellent runtime that made this migration possible. This release represents a complete modernization of NUPST's infrastructure while maintaining full
backward compatibility for user configurations. Special thanks to the Deno team for creating an
excellent runtime that made this migration possible.
--- ---
## 2025-03-28 - 3.1.2 - fix(cli/ups-handler) ## 2025-03-28 - 3.1.2 - fix(cli/ups-handler)
Improve UPS device listing table formatting for better column alignment Improve UPS device listing table formatting for better column alignment
- Adjusted header spacing for the Host column and overall table alignment in the UPS handler output. - Adjusted header spacing for the Host column and overall table alignment in the UPS handler output.
## 2025-03-28 - 3.1.1 - fix(cli) ## 2025-03-28 - 3.1.1 - fix(cli)
Improve table header formatting in group and UPS listings Improve table header formatting in group and UPS listings
- Adjusted column padding in group listing for proper alignment - Adjusted column padding in group listing for proper alignment
- Fixed UPS table header spacing for consistent CLI output - Fixed UPS table header spacing for consistent CLI output
## 2025-03-28 - 3.1.0 - feat(cli) ## 2025-03-28 - 3.1.0 - feat(cli)
Refactor CLI commands to use dedicated handlers for UPS, group, and service management Refactor CLI commands to use dedicated handlers for UPS, group, and service management
- Extracted UPS-related CLI logic into a new UpsHandler - Extracted UPS-related CLI logic into a new UpsHandler
@@ -168,28 +194,38 @@ Refactor CLI commands to use dedicated handlers for UPS, group, and service mana
- Exposed getters for the new handlers in the Nupst class - Exposed getters for the new handlers in the Nupst class
## 2025-03-28 - 3.0.1 - fix(cli) ## 2025-03-28 - 3.0.1 - fix(cli)
Simplify UPS ID generation by removing the redundant promptForUniqueUpsId function in the CLI module and replacing it with the shortId helper.
Simplify UPS ID generation by removing the redundant promptForUniqueUpsId function in the CLI module
and replacing it with the shortId helper.
- Deleted the unused promptForUniqueUpsId method from ts/cli.ts. - Deleted the unused promptForUniqueUpsId method from ts/cli.ts.
- Updated UPS configuration to generate a unique ID directly using helpers.shortId(). - Updated UPS configuration to generate a unique ID directly using helpers.shortId().
- Improved code clarity by removing unnecessary interactive prompts for UPS IDs. - Improved code clarity by removing unnecessary interactive prompts for UPS IDs.
## 2025-03-28 - 3.0.0 - BREAKING CHANGE(core) ## 2025-03-28 - 3.0.0 - BREAKING CHANGE(core)
Add multi-UPS support and group management; update CLI, configuration and documentation to support multiple UPS devices with group modes
- Implemented multi-UPS configuration with an array of UPS devices and groups in the configuration file Add multi-UPS support and group management; update CLI, configuration and documentation to support
- Added group management commands (group add, edit, delete, list) with redundant and non-redundant modes multiple UPS devices with group modes
- Revamped CLI command parsing for UPS management (add, edit, delete, list, setup) and group subcommands
- Implemented multi-UPS configuration with an array of UPS devices and groups in the configuration
file
- Added group management commands (group add, edit, delete, list) with redundant and non-redundant
modes
- Revamped CLI command parsing for UPS management (add, edit, delete, list, setup) and group
subcommands
- Updated readme and documentation to reflect new configuration structure and features - Updated readme and documentation to reflect new configuration structure and features
- Enhanced logging and status display for multiple UPS devices - Enhanced logging and status display for multiple UPS devices
## 2025-03-26 - 2.6.17 - fix(logger) ## 2025-03-26 - 2.6.17 - fix(logger)
Preserve logbox width after logBoxEnd so that subsequent logBoxLine calls continue using the set width.
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. - 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. - Ensures that logBoxLine uses the previously set width when no new width is provided.
## 2025-03-26 - 2.6.16 - fix(cli) ## 2025-03-26 - 2.6.16 - fix(cli)
Improve CLI logging consistency by replacing direct console output with unified logger calls. 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 - Replaced console.log and console.error with logger.log and logger.error in CLI commands
@@ -197,42 +233,51 @@ Improve CLI logging consistency by replacing direct console output with unified
- Enhanced consistency of log output throughout the ts/cli.ts file - Enhanced consistency of log output throughout the ts/cli.ts file
## 2025-03-26 - 2.6.15 - fix(logger) ## 2025-03-26 - 2.6.15 - fix(logger)
Replace direct console logging with unified logger interface for consistent formatting 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 - 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 - Integrate logBox formatting for structured output and consistent log presentation
- Update test expectations in test.logger.ts to check for standardized error messages - Update test expectations in test.logger.ts to check for standardized error messages
- Refactor logging calls throughout the codebase for improved clarity and maintainability - Refactor logging calls throughout the codebase for improved clarity and maintainability
## 2025-03-26 - 2.6.14 - fix(systemd) ## 2025-03-26 - 2.6.14 - fix(systemd)
Shorten closing log divider in systemd service installation output for consistent formatting. 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. - Replaced the overly long footer with a shorter one in ts/systemd.ts.
- This change improves log readability without affecting functionality. - This change improves log readability without affecting functionality.
## 2025-03-26 - 2.6.13 - fix(cli) ## 2025-03-26 - 2.6.13 - fix(cli)
Fix CLI update output box formatting Fix CLI update output box formatting
- Adjusted the closing box line in the update process log messages for consistent visual formatting - Adjusted the closing box line in the update process log messages for consistent visual formatting
## 2025-03-26 - 2.6.12 - fix(systemd) ## 2025-03-26 - 2.6.12 - fix(systemd)
Adjust logging border in systemd service installation output Adjust logging border in systemd service installation output
- Updated the closing border line for consistent output formatting in ts/systemd.ts - Updated the closing border line for consistent output formatting in ts/systemd.ts
## 2025-03-26 - 2.6.11 - fix(cli, systemd) ## 2025-03-26 - 2.6.11 - fix(cli, systemd)
Adjust log formatting for consistent output in CLI and systemd commands 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. - Fixed spacing issues in service installation and status log messages in the systemd module.
- Revised output formatting in the CLI to improve message clarity. - Revised output formatting in the CLI to improve message clarity.
## 2025-03-26 - 2.6.10 - fix(daemon) ## 2025-03-26 - 2.6.10 - fix(daemon)
Adjust console log box formatting for consistent output in daemon status messages 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 - Updated closing box borders to align properly in configuration error, periodic updates, and UPS
status logs
- Improved visual consistency in log messages - Improved visual consistency in log messages
## 2025-03-26 - 2.6.9 - fix(cli) ## 2025-03-26 - 2.6.9 - fix(cli)
Improve console output formatting for status banners and logging messages Improve console output formatting for status banners and logging messages
- Standardize banner messages in daemon status updates - Standardize banner messages in daemon status updates
@@ -240,19 +285,23 @@ Improve console output formatting for status banners and logging messages
- Update UPS connection and status banners in systemd - Update UPS connection and status banners in systemd
## 2025-03-26 - 2.6.8 - fix(cli) ## 2025-03-26 - 2.6.8 - fix(cli)
Improve CLI formatting, refine debug option filtering, and remove unused dgram import in SNMP manager
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 - Standardize whitespace and formatting in ts/cli.ts for consistency
- Refine argument filtering for debug mode and prompt messages - Refine argument filtering for debug mode and prompt messages
- Remove unused 'dgram' import from ts/snmp/manager.ts - Remove unused 'dgram' import from ts/snmp/manager.ts
## 2025-03-26 - 2.6.7 - fix(setup.sh) ## 2025-03-26 - 2.6.7 - fix(setup.sh)
Clarify net-snmp dependency installation message in setup.sh Clarify net-snmp dependency installation message in setup.sh
- Updated echo statement to indicate installation of net-snmp along with 2 subdependencies - Updated echo statement to indicate installation of net-snmp along with 2 subdependencies
- Improves clarity on dependency installation during setup - Improves clarity on dependency installation during setup
## 2025-03-26 - 2.6.6 - fix(setup.sh) ## 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 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 - Replace use of the npm binary with direct execution of npm-cli.js
@@ -260,14 +309,18 @@ Improve setup script to detect and execute npm-cli.js directly using the Node.js
- Simplify cleanup by removing unnecessary PATH modifications - Simplify cleanup by removing unnecessary PATH modifications
## 2025-03-26 - 2.6.5 - fix(daemon, setup) ## 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
Improve shutdown command detection and fallback logic; update setup script to use absolute Node/npm
paths
- Use execFileAsync to execute shutdown commands reliably - Use execFileAsync to execute shutdown commands reliably
- Add multiple fallback alternatives for shutdown and emergency shutdown handling - 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 - Update setup.sh to log the Node and NPM versions using absolute paths without modifying PATH
## 2025-03-26 - 2.6.4 - fix(setup) ## 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.
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. - Remove existing package-lock.json along with node_modules to prevent stale artifacts.
- Back up the original package.json before modifying it. - Back up the original package.json before modifying it.
@@ -276,13 +329,16 @@ Improve installation process in setup script by cleaning up package files and en
- Restore the original package.json if the installation fails. - Restore the original package.json if the installation fails.
## 2025-03-26 - 2.6.3 - fix(setup) ## 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.
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 - Removed full production dependency install in favor of installing only net-snmp@3.20.0
- Added verification step to confirm net-snmp installation - Added verification step to confirm net-snmp installation
- Generate a minimal package-lock.json if one does not exist - Generate a minimal package-lock.json if one does not exist
## 2025-03-26 - 2.6.2 - fix(setup/readme) ## 2025-03-26 - 2.6.2 - fix(setup/readme)
Improve force update instructions and dependency installation process in setup.sh and readme.md Improve force update instructions and dependency installation process in setup.sh and readme.md
- Clarify force update commands with explicit paths in readme.md - Clarify force update commands with explicit paths in readme.md
@@ -290,13 +346,16 @@ Improve force update instructions and dependency installation process in setup.s
- Switch from 'npm ci --only=production' to 'npm install --omit=dev' with updated error instructions - Switch from 'npm ci --only=production' to 'npm install --omit=dev' with updated error instructions
## 2025-03-26 - 2.6.1 - fix(setup) ## 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.
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. - Temporarily prepend vendor Node.js binary directory to PATH to ensure proper npm execution.
- Log Node.js and npm versions for debugging purposes. - Log Node.js and npm versions for debugging purposes.
- Restore the original PATH after installing dependencies. - 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
- Implemented --force option in setup.sh to force-update Node.js binary and dependencies - Implemented --force option in setup.sh to force-update Node.js binary and dependencies
@@ -304,27 +363,33 @@ Add --force update flag to setup script and update installation instructions
- Modified ts/cli.ts update command to pass the --force flag to setup.sh - Modified ts/cli.ts update command to pass the --force flag to setup.sh
## 2025-03-26 - 2.5.2 - fix(installer) ## 2025-03-26 - 2.5.2 - fix(installer)
Improve Node.js binary detection, dependency management, and SNMPv3 fallback logic Improve Node.js binary detection, dependency management, and SNMPv3 fallback logic
- Enhanced bin/nupst to detect OS and architecture (Linux and Darwin) and fall back to system Node.js for unsupported platforms - Enhanced bin/nupst to detect OS and architecture (Linux and Darwin) and fall back to system
Node.js for unsupported platforms
- Moved net-snmp from devDependencies to dependencies in package.json - Moved net-snmp from devDependencies to dependencies in package.json
- Updated setup.sh to install production dependencies and handle installation errors gracefully - Updated setup.sh to install production dependencies and handle installation errors gracefully
- Refined SNMPv3 user configuration and fallback mechanism in ts/snmp/manager.ts - Refined SNMPv3 user configuration and fallback mechanism in ts/snmp/manager.ts
- Revised README to clarify minimal runtime dependencies and secure SNMP features - Revised README to clarify minimal runtime dependencies and secure SNMP features
## 2025-03-25 - 2.5.1 - fix(snmp) ## 2025-03-25 - 2.5.1 - fix(snmp)
Fix Eaton UPS support by updating power status OID and adjusting battery runtime conversion. Fix Eaton UPS support by updating power status OID and adjusting battery runtime conversion.
- Updated Eaton UPS power status OID to '1.3.6.1.4.1.534.1.4.4.0' to correctly detect online/battery status. - Updated Eaton UPS power status OID to '1.3.6.1.4.1.534.1.4.4.0' to correctly detect online/battery
status.
- Added conversion for Eaton UPS battery runtime from seconds to minutes in SNMP manager. - Added conversion for Eaton UPS battery runtime from seconds to minutes in SNMP manager.
## 2025-03-25 - 2.5.0 - feat(cli) ## 2025-03-25 - 2.5.0 - feat(cli)
Automatically restart running NUPST service after configuration changes in interactive setup Automatically restart running NUPST service after configuration changes in interactive setup
- Added restartServiceIfRunning() to check and restart the service if it's active. - Added restartServiceIfRunning() to check and restart the service if it's active.
- Invoked the restart function post-setup to apply configuration changes immediately. - Invoked the restart function post-setup to apply configuration changes immediately.
## 2025-03-25 - 2.4.8 - fix(installer) ## 2025-03-25 - 2.4.8 - fix(installer)
Improve Git dependency handling and repository cloning in install.sh Improve Git dependency handling and repository cloning in install.sh
- Add explicit check for git installation and prompt the user interactively if git is missing. - Add explicit check for git installation and prompt the user interactively if git is missing.
@@ -332,23 +397,30 @@ Improve Git dependency handling and repository cloning in install.sh
- Ensure proper cloning of the repository when running the installer outside the repo. - Ensure proper cloning of the repository when running the installer outside the repo.
## 2025-03-25 - 2.4.7 - fix(readme) ## 2025-03-25 - 2.4.7 - fix(readme)
Update installation instructions to combine download and execution into a single command for clarity Update installation instructions to combine download and execution into a single command for clarity
- Method 1 now uses a unified one-line command to download and run the install script - Method 1 now uses a unified one-line command to download and run the install script
## 2025-03-25 - 2.4.6 - fix(installer) ## 2025-03-25 - 2.4.6 - fix(installer)
Improve installation instructions for interactive and non-interactive setups Improve installation instructions for interactive and non-interactive setups
- Changed install.sh to require explicit download of the install script and updated error messages for non-interactive modes - Changed install.sh to require explicit download of the install script and updated error messages
for non-interactive modes
- Updated readme.md to include three distinct installation methods with clear command examples - Updated readme.md to include three distinct installation methods with clear command examples
## 2025-03-25 - 2.4.5 - fix(install) ## 2025-03-25 - 2.4.5 - fix(install)
Improve interactive terminal detection and update installation instructions Improve interactive terminal detection and update installation instructions
- Enhanced install.sh to better detect non-interactive environments and provide clearer guidance for both interactive and non-interactive installations - Enhanced install.sh to better detect non-interactive environments and provide clearer guidance for
- Updated README.md quick install instructions to recommend process substitution and clarify auto-yes usage both interactive and non-interactive installations
- Updated README.md quick install instructions to recommend process substitution and clarify
auto-yes usage
## 2025-03-25 - 2.4.4 - fix(install) ## 2025-03-25 - 2.4.4 - fix(install)
Improve interactive mode detection and non-interactive installation handling in install.sh Improve interactive mode detection and non-interactive installation handling in install.sh
- Detect and warn when running without a controlling terminal - Detect and warn when running without a controlling terminal
@@ -357,86 +429,116 @@ Improve interactive mode detection and non-interactive installation handling in
- Clarify installation instructions in readme for interactive and non-interactive modes - Clarify installation instructions in readme for interactive and non-interactive modes
## 2025-03-25 - 2.4.3 - fix(readme) ## 2025-03-25 - 2.4.3 - fix(readme)
Update Quick Install command syntax in readme for auto-yes installation Update Quick Install command syntax in readme for auto-yes installation
- Changed installation command to use: curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -c "bash -s -- -y" - Changed installation command to use: curl -sSL
https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -c "bash -s --
-y"
## 2025-03-25 - 2.4.2 - fix(daemon) ## 2025-03-25 - 2.4.2 - fix(daemon)
Refactor shutdown initiation logic in daemon by moving the initiateShutdown and monitorDuringShutdown methods from the SNMP manager to the daemon, and update calls accordingly
Refactor shutdown initiation logic in daemon by moving the initiateShutdown and
monitorDuringShutdown methods from the SNMP manager to the daemon, and update calls accordingly
- Moved initiateShutdown and monitorDuringShutdown to the daemon class for improved cohesion - Moved initiateShutdown and monitorDuringShutdown to the daemon class for improved cohesion
- Updated references in the daemon to call its own shutdown method instead of the SNMP manager - Updated references in the daemon to call its own shutdown method instead of the SNMP manager
- Removed redundant initiateShutdown method from the SNMP manager - Removed redundant initiateShutdown method from the SNMP manager
## 2025-03-25 - 2.4.1 - fix(docs) ## 2025-03-25 - 2.4.1 - fix(docs)
Update readme with detailed legal and trademark guidance Update readme with detailed legal and trademark guidance
- Clarified legal section by adding trademark and company information - Clarified legal section by adding trademark and company information
- Ensured users understand that licensing terms do not imply endorsement by the company - Ensured users understand that licensing terms do not imply endorsement by the company
## 2025-03-25 - 2.4.0 - feat(installer) ## 2025-03-25 - 2.4.0 - feat(installer)
Add auto-yes flag to installer and update installation documentation Add auto-yes flag to installer and update installation documentation
- Enhance install.sh to parse -y/--yes and -h/--help options, automating git installation when auto-yes is provided - Enhance install.sh to parse -y/--yes and -h/--help options, automating git installation when
auto-yes is provided
- Improve user prompts for dependency installation and provide clearer instructions - Improve user prompts for dependency installation and provide clearer instructions
- Update readme.md to document new installer options and enhanced file system and service changes details - Update readme.md to document new installer options and enhanced file system and service changes
details
## 2025-03-25 - 2.3.0 - feat(installer/cli) ## 2025-03-25 - 2.3.0 - feat(installer/cli)
Add OS detection and git auto-installation support to install.sh and improve service setup prompt in CLI
- Implemented helper functions in install.sh to detect OS type and automatically install git if missing Add OS detection and git auto-installation support to install.sh and improve service setup prompt in
CLI
- Implemented helper functions in install.sh to detect OS type and automatically install git if
missing
- Prompt user for git installation if not present before cloning the repository - Prompt user for git installation if not present before cloning the repository
- Enhanced CLI service setup flow to offer starting the NUPST service immediately after installation - Enhanced CLI service setup flow to offer starting the NUPST service immediately after installation
## 2025-03-25 - 2.2.0 - feat(cli) ## 2025-03-25 - 2.2.0 - feat(cli)
Add 'config' command to display current configuration and update CLI help Add 'config' command to display current configuration and update CLI help
- Introduce new 'config' command to show SNMP settings, thresholds, and configuration file location - Introduce new 'config' command to show SNMP settings, thresholds, and configuration file location
- Update help text to include details for 'nupst config' command - Update help text to include details for 'nupst config' command
## 2025-03-25 - 2.1.0 - feat(cli) ## 2025-03-25 - 2.1.0 - feat(cli)
Add uninstall command to CLI and update shutdown delay for graceful VM shutdown Add uninstall command to CLI and update shutdown delay for graceful VM shutdown
- Implement uninstall command in ts/cli.ts that locates and executes uninstall.sh with user prompts - Implement uninstall command in ts/cli.ts that locates and executes uninstall.sh with user prompts
- Update uninstall.sh to support environment variables for configuration and repository removal - Update uninstall.sh to support environment variables for configuration and repository removal
- Increase shutdown delay in ts/snmp/manager.ts from 1 minute to 5 minutes to allow VMs more time to shut down - Increase shutdown delay in ts/snmp/manager.ts from 1 minute to 5 minutes to allow VMs more time to
shut down
## 2025-03-25 - 2.0.1 - fix(cli/systemd) ## 2025-03-25 - 2.0.1 - fix(cli/systemd)
Fix status command to pass debug flag and improve systemd status logging output Fix status command to pass debug flag and improve systemd status logging output
- ts/cli.ts: Now extracts debug options from process arguments and passes debug mode to getStatus. - ts/cli.ts: Now extracts debug options from process arguments and passes debug mode to getStatus.
- ts/systemd.ts: Updated getStatus to accept a debugMode parameter, enabling detailed SNMP debug logging, explicitly reloading configuration, and printing connection details. - ts/systemd.ts: Updated getStatus to accept a debugMode parameter, enabling detailed SNMP debug
logging, explicitly reloading configuration, and printing connection details.
## 2025-03-25 - 2.0.0 - BREAKING CHANGE(snmp) ## 2025-03-25 - 2.0.0 - BREAKING CHANGE(snmp)
refactor: update SNMP type definitions and interface names for consistency refactor: update SNMP type definitions and interface names for consistency
- Renamed SnmpConfig to ISnmpConfig, OIDSet to IOidSet, UpsStatus to IUpsStatus, and UpsModel to TUpsModel in ts/snmp/types.ts. - Renamed SnmpConfig to ISnmpConfig, OIDSet to IOidSet, UpsStatus to IUpsStatus, and UpsModel to
- Updated internal references in ts/daemon.ts, ts/snmp/index.ts, ts/snmp/manager.ts, ts/snmp/oid-sets.ts, ts/snmp/packet-creator.ts, and ts/snmp/packet-parser.ts to use the new interface names. TUpsModel in ts/snmp/types.ts.
- Updated internal references in ts/daemon.ts, ts/snmp/index.ts, ts/snmp/manager.ts,
ts/snmp/oid-sets.ts, ts/snmp/packet-creator.ts, and ts/snmp/packet-parser.ts to use the new
interface names.
## 2025-03-25 - 1.10.1 - fix(systemd/readme) ## 2025-03-25 - 1.10.1 - fix(systemd/readme)
Improve README documentation and fix UPS status retrieval in systemd service Improve README documentation and fix UPS status retrieval in systemd service
- Updated README features and installation instructions to clarify SNMP version support, UPS models, and configuration - Updated README features and installation instructions to clarify SNMP version support, UPS models,
- Modified default SNMP host to '192.168.1.100' and added 'upsModel' property in configuration examples and configuration
- Modified default SNMP host to '192.168.1.100' and added 'upsModel' property in configuration
examples
- Enhanced instructions for real-time log viewing and update process in README - Enhanced instructions for real-time log viewing and update process in README
- Fixed systemd.ts to use a test configuration with an appropriate timeout when fetching UPS status - Fixed systemd.ts to use a test configuration with an appropriate timeout when fetching UPS status
## 2025-03-25 - 1.10.0 - feat(core) ## 2025-03-25 - 1.10.0 - feat(core)
Add update checking and version logging across startup components Add update checking and version logging across startup components
- In daemon.ts, log version info on startup and check for updates in the background using npm registry response - In daemon.ts, log version info on startup and check for updates in the background using npm
- In nupst.ts, implement getVersion, checkForUpdates, getUpdateStatus, and compareVersions functions with update notifications registry response
- In nupst.ts, implement getVersion, checkForUpdates, getUpdateStatus, and compareVersions functions
with update notifications
- Establish bidirectional reference between Nupst and NupstSnmp to support version logging - Establish bidirectional reference between Nupst and NupstSnmp to support version logging
- Update systemd service status output to include version information - Update systemd service status output to include version information
## 2025-03-25 - 1.9.0 - feat(cli) ## 2025-03-25 - 1.9.0 - feat(cli)
Add update command to CLI to update NUPST from repository and refresh the systemd service Add update command to CLI to update NUPST from repository and refresh the systemd service
- Integrate 'update' subcommand in CLI command parser - Integrate 'update' subcommand in CLI command parser
- Update documentation and help output to include new command - Update documentation and help output to include new command
- Implement update process that fetches changes from git, runs install.sh/setup.sh, and refreshes systemd service if installed - Implement update process that fetches changes from git, runs install.sh/setup.sh, and refreshes
systemd service if installed
## 2025-03-25 - 1.8.2 - fix(cli) ## 2025-03-25 - 1.8.2 - fix(cli)
Refactor logs command to use child_process spawn for real-time log tailing Refactor logs command to use child_process spawn for real-time log tailing
- Replaced execSync call with spawn to properly follow logs - Replaced execSync call with spawn to properly follow logs
@@ -444,12 +546,15 @@ Refactor logs command to use child_process spawn for real-time log tailing
- Await the child process exit to ensure clean shutdown of the CLI log command - Await the child process exit to ensure clean shutdown of the CLI log command
## 2025-03-25 - 1.8.1 - fix(systemd) ## 2025-03-25 - 1.8.1 - fix(systemd)
Update ExecStart in systemd service template to use /opt/nupst/bin/nupst for daemon startup Update ExecStart in systemd service template to use /opt/nupst/bin/nupst for daemon startup
- Changed ExecStart from '/usr/bin/nupst daemon-start' to '/opt/nupst/bin/nupst daemon-start' in the systemd service file - Changed ExecStart from '/usr/bin/nupst daemon-start' to '/opt/nupst/bin/nupst daemon-start' in the
systemd service file
- Ensures the service uses the correct binary installation path - Ensures the service uses the correct binary installation path
## 2025-03-25 - 1.8.0 - feat(core) ## 2025-03-25 - 1.8.0 - feat(core)
Enhance SNMP module and interactive CLI setup for UPS shutdown Enhance SNMP module and interactive CLI setup for UPS shutdown
- Refactored SNMP packet parsing and encoding utilities for clearer error handling and debugging - Refactored SNMP packet parsing and encoding utilities for clearer error handling and debugging
@@ -458,22 +563,28 @@ Enhance SNMP module and interactive CLI setup for UPS shutdown
- Expanded test coverage with simulated SNMP responses for various response types - Expanded test coverage with simulated SNMP responses for various response types
## 2025-03-25 - 1.7.6 - fix(core) ## 2025-03-25 - 1.7.6 - fix(core)
Refactor SNMP, systemd, and CLI modules to improve error handling, logging, and code clarity Refactor SNMP, systemd, and CLI modules to improve error handling, logging, and code clarity
- Removed unused dependency 'net-snmp' from package.json - Removed unused dependency 'net-snmp' from package.json
- Extracted helper functions for SNMP packet creation and parsing (using SnmpEncoder, SnmpPacketCreator and SnmpPacketParser) - Extracted helper functions for SNMP packet creation and parsing (using SnmpEncoder,
- Improved debug logging and added detailed documentation comments across SNMP, systemd, CLI and daemon modules SnmpPacketCreator and SnmpPacketParser)
- Improved debug logging and added detailed documentation comments across SNMP, systemd, CLI and
daemon modules
- Refactored systemd service management to extract status display and service disabling logic - Refactored systemd service management to extract status display and service disabling logic
- Updated test suite to use proper modular methods from the new SNMP utilities - Updated test suite to use proper modular methods from the new SNMP utilities
## 2025-03-25 - 1.7.5 - fix(cli) ## 2025-03-25 - 1.7.5 - fix(cli)
Enable SNMP debug mode in CLI commands and update debug flag handling in daemon-start and test; bump version to 1.7.4
Enable SNMP debug mode in CLI commands and update debug flag handling in daemon-start and test; bump
version to 1.7.4
- Call enableDebug() on SNMP client earlier in command parsing - Call enableDebug() on SNMP client earlier in command parsing
- Pass debug flag to 'daemon-start' and 'test' commands for consistent debug output - Pass debug flag to 'daemon-start' and 'test' commands for consistent debug output
- Update package version from 1.7.3 to 1.7.4 - Update package version from 1.7.3 to 1.7.4
## 2025-03-25 - 1.7.3 - fix(SNMP) ## 2025-03-25 - 1.7.3 - fix(SNMP)
Refine SNMP packet creation and response parsing for more reliable UPS status monitoring Refine SNMP packet creation and response parsing for more reliable UPS status monitoring
- Improve error handling and fallback logic when parsing SNMP responses - Improve error handling and fallback logic when parsing SNMP responses
@@ -481,13 +592,16 @@ Refine SNMP packet creation and response parsing for more reliable UPS status mo
- Enhance test coverage for various UPS scenarios - Enhance test coverage for various UPS scenarios
## 2025-03-25 - 1.7.2 - fix(core) ## 2025-03-25 - 1.7.2 - fix(core)
Refactor internal SNMP response parsing and enhance daemon logging for improved error reporting and clarity.
Refactor internal SNMP response parsing and enhance daemon logging for improved error reporting and
clarity.
- Improved fallback and error handling in SNMP response parsing - Improved fallback and error handling in SNMP response parsing
- Enhanced logging messages in daemon and systemd service management - Enhanced logging messages in daemon and systemd service management
- Minor refactoring for better maintainability without functional changes - Minor refactoring for better maintainability without functional changes
## 2025-03-25 - 1.7.1 - fix(snmp-cli) ## 2025-03-25 - 1.7.1 - fix(snmp-cli)
Improve SNMP response parsing and CLI UPS connection timeout handling Improve SNMP response parsing and CLI UPS connection timeout handling
- Expand parsing loop in SNMP responses to capture Gauge32 and Timeticks values - Expand parsing loop in SNMP responses to capture Gauge32 and Timeticks values
@@ -495,14 +609,17 @@ Improve SNMP response parsing and CLI UPS connection timeout handling
- Configure CLI test commands to use a shortened timeout for UPS connection tests - Configure CLI test commands to use a shortened timeout for UPS connection tests
## 2025-03-25 - 1.7.0 - feat(SNMP/UPS) ## 2025-03-25 - 1.7.0 - feat(SNMP/UPS)
Add UPS model selection and custom OIDs support to handle different UPS brands Add UPS model selection and custom OIDs support to handle different UPS brands
- Introduce distinct OID sets for CyberPower, APC, Eaton, TrippLite, Liebert, and a custom option - Introduce distinct OID sets for CyberPower, APC, Eaton, TrippLite, Liebert, and a custom option
- Update interactive setup to prompt for UPS model selection and custom OID entry when needed - Update interactive setup to prompt for UPS model selection and custom OID entry when needed
- Refactor SNMP status retrieval to dynamically select the appropriate OIDs based on the configured UPS model - Refactor SNMP status retrieval to dynamically select the appropriate OIDs based on the configured
UPS model
- Extend default configuration with an upsModel property for consistent behavior - Extend default configuration with an upsModel property for consistent behavior
## 2025-03-25 - 1.6.0 - feat(cli,snmp) ## 2025-03-25 - 1.6.0 - feat(cli,snmp)
Enhance debug logging and add debug mode support in CLI and SNMP modules Enhance debug logging and add debug mode support in CLI and SNMP modules
- Enable debug flags (--debug, -d) in CLI to trigger detailed SNMP logging - Enable debug flags (--debug, -d) in CLI to trigger detailed SNMP logging
@@ -511,6 +628,7 @@ Enhance debug logging and add debug mode support in CLI and SNMP modules
- Improve timeout and discovery logging details for streamlined troubleshooting - Improve timeout and discovery logging details for streamlined troubleshooting
## 2025-03-25 - 1.5.0 - feat(cli) ## 2025-03-25 - 1.5.0 - feat(cli)
Enhance CLI output: display SNMPv3 auth/priv details and support timeout customization during setup Enhance CLI output: display SNMPv3 auth/priv details and support timeout customization during setup
- Display authentication and privacy protocol details when SNMP version is 3 - Display authentication and privacy protocol details when SNMP version is 3
@@ -519,10 +637,11 @@ Enhance CLI output: display SNMPv3 auth/priv details and support timeout customi
- Allow users to customize SNMP timeout during interactive setup - Allow users to customize SNMP timeout during interactive setup
## 2025-03-25 - 1.4.1 - fix(version) ## 2025-03-25 - 1.4.1 - fix(version)
Bump patch version for consistency with commit info Bump patch version for consistency with commit info
## 2025-03-25 - 1.4.0 - feat(snmp) ## 2025-03-25 - 1.4.0 - feat(snmp)
Implement native SNMPv3 support with simulated encryption and enhanced authentication handling. Implement native SNMPv3 support with simulated encryption and enhanced authentication handling.
- Add fully native SNMPv3 GET request implementation replacing the snmpwalk fallback - Add fully native SNMPv3 GET request implementation replacing the snmpwalk fallback
@@ -531,12 +650,14 @@ Implement native SNMPv3 support with simulated encryption and enhanced authentic
- Introduce detailed security parameter management for SNMPv3 - Introduce detailed security parameter management for SNMPv3
## 2025-03-25 - 1.3.1 - fix(cli) ## 2025-03-25 - 1.3.1 - fix(cli)
Remove redundant SNMP tools checks in CLI and Systemd modules Remove redundant SNMP tools checks in CLI and Systemd modules
- Eliminate unnecessary snmpwalk dependency checks in the test command and interactive setup flow. - Eliminate unnecessary snmpwalk dependency checks in the test command and interactive setup flow.
- Adjust systemd configuration file check to avoid external dependency verification. - Adjust systemd configuration file check to avoid external dependency verification.
## 2025-03-25 - 1.3.0 - feat(cli) ## 2025-03-25 - 1.3.0 - feat(cli)
add test command to verify UPS SNMP configuration and connectivity add test command to verify UPS SNMP configuration and connectivity
- Introduce a new 'test' command in the CLI to check the SNMP configuration and UPS connection. - Introduce a new 'test' command in the CLI to check the SNMP configuration and UPS connection.
@@ -544,6 +665,7 @@ add test command to verify UPS SNMP configuration and connectivity
- Output UPS status details and compare against defined shutdown thresholds. - Output UPS status details and compare against defined shutdown thresholds.
## 2025-03-25 - 1.2.6 - fix(cli) ## 2025-03-25 - 1.2.6 - fix(cli)
Refactor interactive setup to use dynamic import for readline and ensure proper cleanup Refactor interactive setup to use dynamic import for readline and ensure proper cleanup
- Replaced synchronous require() with async import for ESM compatibility - Replaced synchronous require() with async import for ESM compatibility
@@ -551,13 +673,16 @@ Refactor interactive setup to use dynamic import for readline and ensure proper
- Enhanced error logging by outputting error.message - Enhanced error logging by outputting error.message
## 2025-03-25 - 1.2.5 - fix(error-handling) ## 2025-03-25 - 1.2.5 - fix(error-handling)
Improve error handling in CLI, daemon, and systemd lifecycle management with enhanced logging for configuration issues
Improve error handling in CLI, daemon, and systemd lifecycle management with enhanced logging for
configuration issues
- Wrap daemon and service start commands in try-catch blocks to properly handle and log errors - Wrap daemon and service start commands in try-catch blocks to properly handle and log errors
- Throw explicit errors when configuration file is missing instead of silently defaulting - Throw explicit errors when configuration file is missing instead of silently defaulting
- Enhance log messages for service installation, startup, and status retrieval for clearer debugging - Enhance log messages for service installation, startup, and status retrieval for clearer debugging
## 2025-03-25 - 1.2.4 - fix(cli/daemon) ## 2025-03-25 - 1.2.4 - fix(cli/daemon)
Improve logging and user feedback in interactive setup and UPS monitoring Improve logging and user feedback in interactive setup and UPS monitoring
- Refactor configuration summary output in the interactive setup for clearer display - Refactor configuration summary output in the interactive setup for clearer display
@@ -565,17 +690,20 @@ Improve logging and user feedback in interactive setup and UPS monitoring
- Improve error messages and user guidance during configuration and monitoring - Improve error messages and user guidance during configuration and monitoring
## 2025-03-24 - 1.2.3 - fix(nupst) ## 2025-03-24 - 1.2.3 - fix(nupst)
No changes No changes
## 2025-03-24 - 1.2.2 - fix(bin/nupst) ## 2025-03-24 - 1.2.2 - fix(bin/nupst)
Improve symlink resolution in launcher script to correctly determine project root based on execution path.
Improve symlink resolution in launcher script to correctly determine project root based on execution
path.
- Replace directory determination with readlink for accurate symlink resolution - Replace directory determination with readlink for accurate symlink resolution
- Set project root to '/opt/nupst' when script is run via symlink from /usr/local/bin - Set project root to '/opt/nupst' when script is run via symlink from /usr/local/bin
- Add debugging comments to assist with path resolution - Add debugging comments to assist with path resolution
## 2025-03-24 - 1.2.1 - fix(bin) ## 2025-03-24 - 1.2.1 - fix(bin)
Simplify Node.js binary detection in installation script Simplify Node.js binary detection in installation script
- Directly set Node binary path to vendor/node-linux-x64/bin/node - Directly set Node binary path to vendor/node-linux-x64/bin/node
@@ -583,59 +711,78 @@ Simplify Node.js binary detection in installation script
- Fallback to system Node if vendor binary is not found - Fallback to system Node if vendor binary is not found
## 2025-03-24 - 1.2.0 - feat(installer) ## 2025-03-24 - 1.2.0 - feat(installer)
Improve Node.js binary detection and dynamic LTS version retrieval in setup scripts Improve Node.js binary detection and dynamic LTS version retrieval in setup scripts
- Enhanced bin/nupst to search multiple possible locations for the Node.js binary and fallback to system node if necessary - Enhanced bin/nupst to search multiple possible locations for the Node.js binary and fallback to
- Updated setup.sh to fetch the latest LTS Node.js version from nodejs.org and use a fallback version when the request fails system node if necessary
- Updated setup.sh to fetch the latest LTS Node.js version from nodejs.org and use a fallback
version when the request fails
## 2025-03-24 - 1.1.2 - fix(setup.sh) ## 2025-03-24 - 1.1.2 - fix(setup.sh)
Improve error handling in setup.sh: exit immediately when the downloaded npm package lacks the dist_ts directory, removing the fallback build-from-source mechanism.
Improve error handling in setup.sh: exit immediately when the downloaded npm package lacks the
dist_ts directory, removing the fallback build-from-source mechanism.
- Removed BUILD_FROM_SOURCE logic that attempted to build from source on missing dist_ts directory - Removed BUILD_FROM_SOURCE logic that attempted to build from source on missing dist_ts directory
- Updated error messages to clearly indicate failure in downloading a valid package - Updated error messages to clearly indicate failure in downloading a valid package
- Ensured installation halts if essential files are missing - Ensured installation halts if essential files are missing
## 2025-03-24 - 1.1.1 - fix(package.json) ## 2025-03-24 - 1.1.1 - fix(package.json)
Remove unused prepublishOnly script and update files field in package.json Remove unused prepublishOnly script and update files field in package.json
- Removed prepublishOnly build trigger - Removed prepublishOnly build trigger
- Updated files list to accurately include intended directories and files - Updated files list to accurately include intended directories and files
## 2025-03-24 - 1.1.0 - feat(installer-setup) ## 2025-03-24 - 1.1.0 - feat(installer-setup)
Enhance installer and setup scripts for improved global installation and artifact management Enhance installer and setup scripts for improved global installation and artifact management
- Detect piped installation in install.sh, clone repository automatically, and clean up previous installations - Detect piped installation in install.sh, clone repository automatically, and clean up previous
installations
- Update readme.md with correct repository URL and clearer installation instructions - Update readme.md with correct repository URL and clearer installation instructions
- Improve setup.sh to remove existing dist_ts, download build artifacts from the npm registry, and simplify dependency installation - Improve setup.sh to remove existing dist_ts, download build artifacts from the npm registry, and
simplify dependency installation
## 2025-03-24 - 1.0.1 - fix(version) ## 2025-03-24 - 1.0.1 - fix(version)
Bump version to 1.0.1 Bump version to 1.0.1
- Updated commitinfo data to reflect the new patch version. - Updated commitinfo data to reflect the new patch version.
- Synchronized version information between commitinfo file and package metadata. - Synchronized version information between commitinfo file and package metadata.
## 2025-03-24 - 1.0.1 - fix(build) ## 2025-03-24 - 1.0.1 - fix(build)
Update build script to use 'tsbuild tsfolders --allowimplicitany' and adjust distribution paths in .gitignore
Update build script to use 'tsbuild tsfolders --allowimplicitany' and adjust distribution paths in
.gitignore
- Replaced 'tsc' with 'tsbuild tsfolders --allowimplicitany' in package.json - Replaced 'tsc' with 'tsbuild tsfolders --allowimplicitany' in package.json
- Updated .gitignore to reflect new compiled distribution folder pattern - Updated .gitignore to reflect new compiled distribution folder pattern
- Updated changelog to document build improvements and regenerated type definitions - Updated changelog to document build improvements and regenerated type definitions
## 2025-03-24 - 1.0.1 - fix(build) ## 2025-03-24 - 1.0.1 - fix(build)
Update build script to use 'tsbuild tsfolders --allowimplicitany' and regenerate distribution type definitions for CLI, daemon, index, nupst, snmp, and systemd modules
Update build script to use 'tsbuild tsfolders --allowimplicitany' and regenerate distribution type
definitions for CLI, daemon, index, nupst, snmp, and systemd modules
- Replaced 'tsc' command with tsbuild in package.json - Replaced 'tsc' command with tsbuild in package.json
- Updated .gitignore to reflect new compiled distribution folder pattern - Updated .gitignore to reflect new compiled distribution folder pattern
- Added new dist_ts files including .d.ts type definitions and compiled JavaScript for multiple modules - Added new dist_ts files including .d.ts type definitions and compiled JavaScript for multiple
modules
## 2025-03-24 - 1.0.1 - fix(build) ## 2025-03-24 - 1.0.1 - fix(build)
Update build script to use 'tsbuild tsfolders --allowimplicitany' and regenerate distribution type definitions for CLI, daemon, nupst, snmp, and systemd modules.
Update build script to use 'tsbuild tsfolders --allowimplicitany' and regenerate distribution type
definitions for CLI, daemon, nupst, snmp, and systemd modules.
- Replaced the 'tsc' command with 'tsbuild tsfolders --allowimplicitany' in package.json. - Replaced the 'tsc' command with 'tsbuild tsfolders --allowimplicitany' in package.json.
- Added new dist_ts files including type definitions (d.ts) and compiled JavaScript for CLI, daemon, index, nupst, snmp, and systemd. - Added new dist_ts files including type definitions (d.ts) and compiled JavaScript for CLI, daemon,
index, nupst, snmp, and systemd.
- Improved the generated CLI declarations and overall distribution build. - Improved the generated CLI declarations and overall distribution build.
## 2025-03-23 - 1.0.0 - initial setup ## 2025-03-23 - 1.0.0 - initial setup
This range covers the early commits that mainly established the repository structure. This range covers the early commits that mainly established the repository structure.
- Initial repository commit with basic project initialization. - Initial repository commit with basic project initialization.

View File

@@ -21,7 +21,8 @@
"useTabs": false, "useTabs": false,
"lineWidth": 100, "lineWidth": 100,
"indentWidth": 2, "indentWidth": 2,
"semiColons": true "semiColons": true,
"singleQuote": true
}, },
"compilerOptions": { "compilerOptions": {
"lib": ["deno.window"], "lib": ["deno.window"],

108
readme.md
View File

@@ -1,8 +1,10 @@
# NUPST - Network UPS Shutdown Tool # NUPST - Network UPS Shutdown Tool
NUPST is a lightweight, self-contained command-line tool that monitors SNMP-enabled UPS devices and initiates system shutdown when power outages are detected and battery levels are low. NUPST is a lightweight, self-contained command-line tool that monitors SNMP-enabled UPS devices and
initiates system shutdown when power outages are detected and battery levels are low.
**Version 4.0+** is powered by Deno and distributed as pre-compiled binaries requiring zero dependencies. **Version 4.0+** is powered by Deno and distributed as pre-compiled binaries requiring zero
dependencies.
## Features ## Features
@@ -10,12 +12,15 @@ NUPST is a lightweight, self-contained command-line tool that monitors SNMP-enab
- **Group Management**: Organize UPS devices into groups with different operating modes - **Group Management**: Organize UPS devices into groups with different operating modes
- **Redundant Mode**: Only shutdown when ALL UPS devices in a group are in critical condition - **Redundant Mode**: Only shutdown when ALL UPS devices in a group are in critical condition
- **Non-Redundant Mode**: Shutdown when ANY UPS device in a group is in critical condition - **Non-Redundant Mode**: Shutdown when ANY UPS device in a group is in critical condition
- **SNMP Protocol Support**: Full support for SNMP v1, v2c, and v3 with authentication and encryption - **SNMP Protocol Support**: Full support for SNMP v1, v2c, and v3 with authentication and
- **Multiple UPS Brands**: Works with CyberPower, APC, Eaton, TrippLite, Liebert/Vertiv, and custom OID configurations encryption
- **Multiple UPS Brands**: Works with CyberPower, APC, Eaton, TrippLite, Liebert/Vertiv, and custom
OID configurations
- **Systemd Integration**: Simple service installation and management - **Systemd Integration**: Simple service installation and management
- **Real-time Monitoring**: Live status updates and log viewing - **Real-time Monitoring**: Live status updates and log viewing
- **Zero Dependencies**: Single self-contained binary with no runtime requirements - **Zero Dependencies**: Single self-contained binary with no runtime requirements
- **Cross-Platform**: Binaries available for Linux (x64, ARM64), macOS (Intel, Apple Silicon), and Windows - **Cross-Platform**: Binaries available for Linux (x64, ARM64), macOS (Intel, Apple Silicon), and
Windows
## Installation ## Installation
@@ -36,6 +41,7 @@ curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh |
``` ```
The installer will: The installer will:
1. Auto-detect your platform (OS and architecture) 1. Auto-detect your platform (OS and architecture)
2. Download the latest pre-compiled binary from releases 2. Download the latest pre-compiled binary from releases
3. Install to `/opt/nupst/nupst` 3. Install to `/opt/nupst/nupst`
@@ -43,7 +49,8 @@ The installer will:
### Manual Installation ### Manual Installation
Download the appropriate binary for your platform from the [releases page](https://code.foss.global/serve.zone/nupst/releases): Download the appropriate binary for your platform from the
[releases page](https://code.foss.global/serve.zone/nupst/releases):
- **Linux x64**: `nupst-linux-x64` - **Linux x64**: `nupst-linux-x64`
- **Linux ARM64**: `nupst-linux-arm64` - **Linux ARM64**: `nupst-linux-arm64`
@@ -91,16 +98,17 @@ When installed, NUPST makes the following changes to your system:
### File System Changes ### File System Changes
| Path | Description | | Path | Description |
|------|-------------| | ----------------------------------- | -------------------------------------- |
| `/opt/nupst/nupst` | Pre-compiled binary (default location) | | `/opt/nupst/nupst` | Pre-compiled binary (default location) |
| `/etc/nupst/config.json` | Configuration file | | `/etc/nupst/config.json` | Configuration file |
| `/usr/local/bin/nupst` | Symlink to the NUPST binary | | `/usr/local/bin/nupst` | Symlink to the NUPST binary |
| `/etc/systemd/system/nupst.service` | Systemd service file (when enabled) | | `/etc/systemd/system/nupst.service` | Systemd service file (when enabled) |
### Service Changes ### Service Changes
- Creates and enables a systemd service called `nupst.service` (when enabled with `nupst service enable`) - Creates and enables a systemd service called `nupst.service` (when enabled with
`nupst service enable`)
- The service runs with root permissions to allow system shutdown capabilities - The service runs with root permissions to allow system shutdown capabilities
### Network Access ### Network Access
@@ -204,7 +212,8 @@ Aliases (for backward compatibility):
## Configuration ## Configuration
NUPST supports monitoring multiple UPS devices organized into groups. The configuration file is located at `/etc/nupst/config.json`. NUPST supports monitoring multiple UPS devices organized into groups. The configuration file is
located at `/etc/nupst/config.json`.
### Interactive Configuration ### Interactive Configuration
@@ -288,6 +297,7 @@ Here's an example configuration with multiple UPS devices in a redundant group:
- `groups`: Array of group IDs this UPS belongs to - `groups`: Array of group IDs this UPS belongs to
**SNMP Configuration:** **SNMP Configuration:**
- `host`: IP address or hostname of your UPS - `host`: IP address or hostname of your UPS
- `port`: SNMP port (default: 161) - `port`: SNMP port (default: 161)
- `version`: SNMP version (1, 2, or 3) - `version`: SNMP version (1, 2, or 3)
@@ -295,9 +305,11 @@ Here's an example configuration with multiple UPS devices in a redundant group:
- `upsModel`: UPS brand ('cyberpower', 'apc', 'eaton', 'tripplite', 'liebert', or 'custom') - `upsModel`: UPS brand ('cyberpower', 'apc', 'eaton', 'tripplite', 'liebert', or 'custom')
**For SNMPv1/v2c:** **For SNMPv1/v2c:**
- `community`: SNMP community string (default: "public") - `community`: SNMP community string (default: "public")
**For SNMPv3:** **For SNMPv3:**
- `securityLevel`: 'noAuthNoPriv', 'authNoPriv', or 'authPriv' - `securityLevel`: 'noAuthNoPriv', 'authNoPriv', or 'authPriv'
- `username`: SNMPv3 username - `username`: SNMPv3 username
- `authProtocol`: 'MD5' or 'SHA' - `authProtocol`: 'MD5' or 'SHA'
@@ -306,12 +318,14 @@ Here's an example configuration with multiple UPS devices in a redundant group:
- `privKey`: Privacy/encryption password - `privKey`: Privacy/encryption password
**For Custom UPS Models:** **For Custom UPS Models:**
- `customOIDs`: Custom OID mappings - `customOIDs`: Custom OID mappings
- `POWER_STATUS`: OID for AC power status - `POWER_STATUS`: OID for AC power status
- `BATTERY_CAPACITY`: OID for battery percentage - `BATTERY_CAPACITY`: OID for battery percentage
- `BATTERY_RUNTIME`: OID for runtime remaining (minutes) - `BATTERY_RUNTIME`: OID for runtime remaining (minutes)
**Shutdown Thresholds:** **Shutdown Thresholds:**
- `battery`: Battery percentage threshold (default: 60%) - `battery`: Battery percentage threshold (default: 60%)
- `runtime`: Runtime minutes threshold (default: 20 minutes) - `runtime`: Runtime minutes threshold (default: 20 minutes)
@@ -324,9 +338,11 @@ Here's an example configuration with multiple UPS devices in a redundant group:
### Group Modes ### Group Modes
- **Redundant Mode**: System shuts down only when ALL UPS devices in the group are critical. Ideal for setups with backup UPS units where one can maintain power. - **Redundant Mode**: System shuts down only when ALL UPS devices in the group are critical. Ideal
for setups with backup UPS units where one can maintain power.
- **Non-Redundant Mode**: System shuts down when ANY UPS device in the group is critical. Used when all UPS devices must be operational for system stability. - **Non-Redundant Mode**: System shuts down when ANY UPS device in the group is critical. Used when
all UPS devices must be operational for system stability.
## Setup as a Service ## Setup as a Service
@@ -361,6 +377,7 @@ curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh |
``` ```
The installer will: The installer will:
1. Download the latest binary 1. Download the latest binary
2. Replace the existing installation 2. Replace the existing installation
3. Preserve your configuration at `/etc/nupst/config.json` 3. Preserve your configuration at `/etc/nupst/config.json`
@@ -435,7 +452,8 @@ sha256sum -c SHA256SUMS.txt --ignore-missing
## Migration from v3.x ## Migration from v3.x
If you're upgrading from NUPST v3.x (Node.js-based) to v4.0 (Deno-based), the migration is straightforward using the install.sh script. If you're upgrading from NUPST v3.x (Node.js-based) to v4.0 (Deno-based), the migration is
straightforward using the install.sh script.
### Quick Migration ### Quick Migration
@@ -450,6 +468,7 @@ nupst service status
``` ```
**That's it!** The installer automatically: **That's it!** The installer automatically:
- Detects your v3.x installation - Detects your v3.x installation
- Stops the running service - Stops the running service
- Replaces the binary with v4.0 - Replaces the binary with v4.0
@@ -471,26 +490,27 @@ nupst service status
v4.0 uses a new subcommand structure, but **old commands still work** with deprecation warnings: v4.0 uses a new subcommand structure, but **old commands still work** with deprecation warnings:
| v3.x Command | v4.0 Command | Notes | | v3.x Command | v4.0 Command | Notes |
|-------------|--------------|-------| | ------------------- | ----------------------- | ---------------------- |
| `nupst enable` | `nupst service enable` | Old works with warning | | `nupst enable` | `nupst service enable` | Old works with warning |
| `nupst disable` | `nupst service disable` | Old works with warning | | `nupst disable` | `nupst service disable` | Old works with warning |
| `nupst start` | `nupst service start` | Old works with warning | | `nupst start` | `nupst service start` | Old works with warning |
| `nupst stop` | `nupst service stop` | Old works with warning | | `nupst stop` | `nupst service stop` | Old works with warning |
| `nupst status` | `nupst service status` | Old works with warning | | `nupst status` | `nupst service status` | Old works with warning |
| `nupst logs` | `nupst service logs` | Old works with warning | | `nupst logs` | `nupst service logs` | Old works with warning |
| `nupst add` | `nupst ups add` | Old works with warning | | `nupst add` | `nupst ups add` | Old works with warning |
| `nupst edit [id]` | `nupst ups edit [id]` | Old works with warning | | `nupst edit [id]` | `nupst ups edit [id]` | Old works with warning |
| `nupst delete <id>` | `nupst ups remove <id>` | Old works with warning | | `nupst delete <id>` | `nupst ups remove <id>` | Old works with warning |
| `nupst list` | `nupst ups list` | Old works with warning | | `nupst list` | `nupst ups list` | Old works with warning |
| `nupst test` | `nupst ups test` | Old works with warning | | `nupst test` | `nupst ups test` | Old works with warning |
| `nupst config` | `nupst config show` | Old works with warning | | `nupst config` | `nupst config show` | Old works with warning |
**New aliases:** `nupst ls` (list UPS devices), `nupst rm <id>` (remove UPS device) **New aliases:** `nupst ls` (list UPS devices), `nupst rm <id>` (remove UPS device)
### Configuration Compatibility ### Configuration Compatibility
✅ **Fully Compatible:** ✅ **Fully Compatible:**
- Configuration file format: `/etc/nupst/config.json` - Configuration file format: `/etc/nupst/config.json`
- All SNMP settings (host, port, community, version, security) - All SNMP settings (host, port, community, version, security)
- UPS device configurations (IDs, names, thresholds, groups) - UPS device configurations (IDs, names, thresholds, groups)
@@ -500,6 +520,7 @@ v4.0 uses a new subcommand structure, but **old commands still work** with depre
### Troubleshooting Migration ### Troubleshooting Migration
**Service won't start after migration:** **Service won't start after migration:**
```bash ```bash
# Re-enable service to update systemd file # Re-enable service to update systemd file
sudo nupst service disable sudo nupst service disable
@@ -508,11 +529,13 @@ sudo nupst service start
``` ```
**Binary won't execute:** **Binary won't execute:**
```bash ```bash
sudo chmod +x /opt/nupst/nupst sudo chmod +x /opt/nupst/nupst
``` ```
**Command not found:** **Command not found:**
```bash ```bash
# Recreate symlink # Recreate symlink
sudo ln -sf /opt/nupst/nupst /usr/local/bin/nupst sudo ln -sf /opt/nupst/nupst /usr/local/bin/nupst
@@ -575,6 +598,7 @@ sudo nupst group add
### Building from Source ### Building from Source
Requirements: Requirements:
- [Deno](https://deno.land/) v1.x or later - [Deno](https://deno.land/) v1.x or later
```bash ```bash
@@ -601,6 +625,7 @@ deno test --allow-all tests/
### Contributing ### Contributing
Contributions are welcome! Please: Contributions are welcome! Please:
1. Fork the repository 1. Fork the repository
2. Create a feature branch 2. Create a feature branch
3. Make your changes 3. Make your changes
@@ -614,19 +639,28 @@ Contributions are welcome! Please:
## License and Legal Information ## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. This repository contains open-source code licensed under the MIT License. A copy of the MIT License
can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file. **Please note:** The MIT License does not grant permission to use the trade names, trademarks,
service marks, or product names of the project, except as required for reasonable and customary use
in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks ### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH. This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated
with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture
Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these
trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be
approved in writing by Task Venture Capital GmbH.
### Company Information ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH Registered at District court Bremen HRB 35230 HB, Germany
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc. For any legal inquiries or if you require further information, please contact us via email at
hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. By using this repository, you acknowledge that you have read this section, agree to comply with its
terms, and understand that the licensing of the code does not imply endorsement by Task Venture
Capital GmbH of any derivative works.

View File

@@ -1,8 +1,8 @@
# NUPST Migration Plan: Node.js → Deno v4.0.0 # NUPST Migration Plan: Node.js → Deno v4.0.0
**Migration Goal**: Convert NUPST from Node.js to Deno with single-executable distribution **Migration Goal**: Convert NUPST from Node.js to Deno with single-executable distribution
**Version**: 3.1.2 → 4.0.0 (breaking changes) **Version**: 3.1.2 → 4.0.0 (breaking changes) **Platforms**: Linux x64/ARM64, macOS x64/ARM64,
**Platforms**: Linux x64/ARM64, macOS x64/ARM64, Windows x64 Windows x64
--- ---
@@ -20,10 +20,12 @@
## Phase 1: Dependency Migration (4-6 hours) ## Phase 1: Dependency Migration (4-6 hours)
### 1.1 Analyze Current Dependencies ### 1.1 Analyze Current Dependencies
- [ ] List all production dependencies from `package.json` - [ ] List all production dependencies from `package.json`
- Current: `net-snmp@3.20.0` - Current: `net-snmp@3.20.0`
- [ ] List all dev dependencies to be removed - [ ] List all dev dependencies to be removed
- `@git.zone/tsbuild`, `@git.zone/tsrun`, `@git.zone/tstest`, `@push.rocks/qenv`, `@push.rocks/tapbundle`, `@types/node` - `@git.zone/tsbuild`, `@git.zone/tsrun`, `@git.zone/tstest`, `@push.rocks/qenv`,
`@push.rocks/tapbundle`, `@types/node`
- [ ] Identify Node.js built-in module usage - [ ] Identify Node.js built-in module usage
- `child_process` (execSync) - `child_process` (execSync)
- `https` (for version checking) - `https` (for version checking)
@@ -31,6 +33,7 @@
- `path` (join, dirname, resolve) - `path` (join, dirname, resolve)
### 1.2 Create Deno Configuration ### 1.2 Create Deno Configuration
- [ ] Create `deno.json` with project configuration - [ ] Create `deno.json` with project configuration
```json ```json
{ {
@@ -68,15 +71,20 @@
``` ```
### 1.3 Update Import Statements ### 1.3 Update Import Statements
- [ ] `ts/snmp/manager.ts`: Change `import * as snmp from 'net-snmp'` to `import * as snmp from "npm:net-snmp@3.20.0"`
- [ ] `ts/cli.ts`: Change `import { execSync } from 'child_process'` to `import { execSync } from "node:child_process"` - [ ] `ts/snmp/manager.ts`: Change `import * as snmp from 'net-snmp'` to
- [ ] `ts/nupst.ts`: Change `import * as https from 'https'` to `import * as https from "node:https"` `import * as snmp from "npm:net-snmp@3.20.0"`
- [ ] `ts/cli.ts`: Change `import { execSync } from 'child_process'` to
`import { execSync } from "node:child_process"`
- [ ] `ts/nupst.ts`: Change `import * as https from 'https'` to
`import * as https from "node:https"`
- [ ] Search for all `fs` imports and update to `node:fs` - [ ] Search for all `fs` imports and update to `node:fs`
- [ ] Search for all `path` imports and update to `node:path` - [ ] Search for all `path` imports and update to `node:path`
- [ ] Update all relative imports to use `.ts` extension instead of `.js` - [ ] Update all relative imports to use `.ts` extension instead of `.js`
- Example: `'./nupst.js'` → `'./nupst.ts'` - Example: `'./nupst.js'` → `'./nupst.ts'`
### 1.4 Test npm: Specifier Compatibility ### 1.4 Test npm: Specifier Compatibility
- [ ] Create test file: `tests/snmp_compatibility_test.ts` - [ ] Create test file: `tests/snmp_compatibility_test.ts`
- [ ] Test SNMP v1 connection with npm:net-snmp - [ ] Test SNMP v1 connection with npm:net-snmp
- [ ] Test SNMP v2c connection with npm:net-snmp - [ ] Test SNMP v2c connection with npm:net-snmp
@@ -88,6 +96,7 @@
## Phase 2: Code Structure Refactoring (3-4 hours) ## Phase 2: Code Structure Refactoring (3-4 hours)
### 2.1 Create Main Entry Point ### 2.1 Create Main Entry Point
- [ ] Create `mod.ts` as main Deno entry point: - [ ] Create `mod.ts` as main Deno entry point:
```typescript ```typescript
#!/usr/bin/env -S deno run --allow-all #!/usr/bin/env -S deno run --allow-all
@@ -111,7 +120,9 @@
``` ```
### 2.2 Update All Import Extensions ### 2.2 Update All Import Extensions
Files to update (change .js → .ts in imports): Files to update (change .js → .ts in imports):
- [ ] `ts/index.ts` - [ ] `ts/index.ts`
- [ ] `ts/cli.ts` (imports from ./nupst.js, ./logger.js) - [ ] `ts/cli.ts` (imports from ./nupst.js, ./logger.js)
- [ ] `ts/nupst.ts` (imports from ./snmp/manager.js, ./daemon.js, etc.) - [ ] `ts/nupst.ts` (imports from ./snmp/manager.js, ./daemon.js, etc.)
@@ -127,10 +138,13 @@ Files to update (change .js → .ts in imports):
- [ ] `ts/logger.ts` - [ ] `ts/logger.ts`
### 2.3 Update process.argv References ### 2.3 Update process.argv References
- [ ] `ts/cli.ts`: Replace `process.argv` with `Deno.args` (adjust indexing: process.argv[2] → Deno.args[0])
- [ ] `ts/cli.ts`: Replace `process.argv` with `Deno.args` (adjust indexing: process.argv[2] →
Deno.args[0])
- [ ] Update parseAndExecute method to work with Deno.args (0-indexed vs 2-indexed) - [ ] Update parseAndExecute method to work with Deno.args (0-indexed vs 2-indexed)
### 2.4 Update File System Operations ### 2.4 Update File System Operations
- [ ] Search for `fs.readFileSync()` → Consider using `Deno.readTextFile()` or keep node:fs - [ ] Search for `fs.readFileSync()` → Consider using `Deno.readTextFile()` or keep node:fs
- [ ] Search for `fs.writeFileSync()` → Consider using `Deno.writeTextFile()` or keep node:fs - [ ] Search for `fs.writeFileSync()` → Consider using `Deno.writeTextFile()` or keep node:fs
- [ ] Search for `fs.existsSync()` → Keep node:fs or use Deno.stat - [ ] Search for `fs.existsSync()` → Keep node:fs or use Deno.stat
@@ -138,10 +152,12 @@ Files to update (change .js → .ts in imports):
- [ ] Decision: Keep node:fs for consistency or migrate to Deno APIs? - [ ] Decision: Keep node:fs for consistency or migrate to Deno APIs?
### 2.5 Update Path Operations ### 2.5 Update Path Operations
- [ ] Verify all `path.join()`, `path.resolve()`, `path.dirname()` work with node:path - [ ] Verify all `path.join()`, `path.resolve()`, `path.dirname()` work with node:path
- [ ] Consider using `@std/path` from JSR for better Deno integration - [ ] Consider using `@std/path` from JSR for better Deno integration
### 2.6 Handle __dirname and __filename ### 2.6 Handle __dirname and __filename
- [ ] Find all `__dirname` usage - [ ] Find all `__dirname` usage
- [ ] Replace with `import.meta.dirname` (Deno) or `dirname(fromFileUrl(import.meta.url))` - [ ] Replace with `import.meta.dirname` (Deno) or `dirname(fromFileUrl(import.meta.url))`
- [ ] Find all `__filename` usage - [ ] Find all `__filename` usage
@@ -152,7 +168,9 @@ Files to update (change .js → .ts in imports):
## Phase 3: CLI Command Simplification (3-4 hours) ## Phase 3: CLI Command Simplification (3-4 hours)
### 3.1 Design New Command Structure ### 3.1 Design New Command Structure
Current → New mapping: Current → New mapping:
``` ```
OLD NEW OLD NEW
=== === === ===
@@ -184,6 +202,7 @@ nupst help → nupst help / nupst --help
``` ```
### 3.2 Update CLI Parser (ts/cli.ts) ### 3.2 Update CLI Parser (ts/cli.ts)
- [ ] Refactor `parseAndExecute()` to handle new command structure - [ ] Refactor `parseAndExecute()` to handle new command structure
- [ ] Add `service` subcommand handler - [ ] Add `service` subcommand handler
- [ ] Add `ups` subcommand handler - [ ] Add `ups` subcommand handler
@@ -195,11 +214,13 @@ nupst help → nupst help / nupst --help
- [ ] Add `--json` flag for machine-readable output (future enhancement) - [ ] Add `--json` flag for machine-readable output (future enhancement)
### 3.3 Update Command Handlers ### 3.3 Update Command Handlers
- [ ] `ts/cli/service-handler.ts`: Update method names if needed - [ ] `ts/cli/service-handler.ts`: Update method names if needed
- [ ] `ts/cli/ups-handler.ts`: Rename `delete()` → `remove()`, remove `setup` method - [ ] `ts/cli/ups-handler.ts`: Rename `delete()` → `remove()`, remove `setup` method
- [ ] `ts/cli/group-handler.ts`: Rename `delete()` → `remove()` - [ ] `ts/cli/group-handler.ts`: Rename `delete()` → `remove()`
### 3.4 Improve Help Messages ### 3.4 Improve Help Messages
- [ ] Update `showHelp()` in ts/cli.ts with new command structure - [ ] Update `showHelp()` in ts/cli.ts with new command structure
- [ ] Update `showGroupHelp()` in ts/cli.ts - [ ] Update `showGroupHelp()` in ts/cli.ts
- [ ] Add `showServiceHelp()` method - [ ] Add `showServiceHelp()` method
@@ -208,6 +229,7 @@ nupst help → nupst help / nupst --help
- [ ] Include usage examples in help text - [ ] Include usage examples in help text
### 3.5 Add Version Command ### 3.5 Add Version Command
- [ ] Read version from deno.json - [ ] Read version from deno.json
- [ ] Create `--version` handler in CLI - [ ] Create `--version` handler in CLI
- [ ] Display version with build info - [ ] Display version with build info
@@ -217,6 +239,7 @@ nupst help → nupst help / nupst --help
## Phase 4: Compilation & Distribution (2-3 hours) ## Phase 4: Compilation & Distribution (2-3 hours)
### 4.1 Create Compilation Script ### 4.1 Create Compilation Script
- [ ] Create directory: `scripts/` - [ ] Create directory: `scripts/`
- [ ] Create `scripts/compile-all.sh`: - [ ] Create `scripts/compile-all.sh`:
```bash ```bash
@@ -261,12 +284,14 @@ nupst help → nupst help / nupst --help
- [ ] Make script executable: `chmod +x scripts/compile-all.sh` - [ ] Make script executable: `chmod +x scripts/compile-all.sh`
### 4.2 Test Local Compilation ### 4.2 Test Local Compilation
- [ ] Run `deno task compile` to compile for all platforms - [ ] Run `deno task compile` to compile for all platforms
- [ ] Verify all 5 binaries are created - [ ] Verify all 5 binaries are created
- [ ] Check binary sizes (should be reasonable, < 100MB each) - [ ] Check binary sizes (should be reasonable, < 100MB each)
- [ ] Test local binary on current platform: `./dist/binaries/nupst-linux-x64 --version` - [ ] Test local binary on current platform: `./dist/binaries/nupst-linux-x64 --version`
### 4.3 Update Installation Scripts ### 4.3 Update Installation Scripts
- [ ] Update `install.sh`: - [ ] Update `install.sh`:
- Remove Node.js download logic (lines dealing with vendor/node-*) - Remove Node.js download logic (lines dealing with vendor/node-*)
- Add detection for binary download from GitHub releases - Add detection for binary download from GitHub releases
@@ -283,6 +308,7 @@ nupst help → nupst help / nupst --help
- Update paths to new binary location - Update paths to new binary location
### 4.4 Update Systemd Service ### 4.4 Update Systemd Service
- [ ] Update systemd service file path in `ts/systemd.ts` - [ ] Update systemd service file path in `ts/systemd.ts`
- [ ] Verify ExecStart points to correct binary location: `/opt/nupst/bin/nupst daemon-start` - [ ] Verify ExecStart points to correct binary location: `/opt/nupst/bin/nupst daemon-start`
- [ ] Remove Node.js environment variables if any - [ ] Remove Node.js environment variables if any
@@ -293,6 +319,7 @@ nupst help → nupst help / nupst --help
## Phase 5: Testing & Validation (4-6 hours) ## Phase 5: Testing & Validation (4-6 hours)
### 5.1 Create Deno Test Suite ### 5.1 Create Deno Test Suite
- [ ] Create `tests/` directory (or migrate from existing `test/`) - [ ] Create `tests/` directory (or migrate from existing `test/`)
- [ ] Create `tests/snmp_test.ts`: Test SNMP manager functionality - [ ] Create `tests/snmp_test.ts`: Test SNMP manager functionality
- [ ] Create `tests/config_test.ts`: Test configuration loading/saving - [ ] Create `tests/config_test.ts`: Test configuration loading/saving
@@ -302,6 +329,7 @@ nupst help → nupst help / nupst --help
- [ ] Use Deno's built-in test runner (`Deno.test()`) - [ ] Use Deno's built-in test runner (`Deno.test()`)
### 5.2 Unit Tests ### 5.2 Unit Tests
- [ ] Test SNMP connection with mock responses - [ ] Test SNMP connection with mock responses
- [ ] Test configuration validation - [ ] Test configuration validation
- [ ] Test UPS status parsing for different models - [ ] Test UPS status parsing for different models
@@ -310,6 +338,7 @@ nupst help → nupst help / nupst --help
- [ ] Test version comparison logic - [ ] Test version comparison logic
### 5.3 Integration Tests ### 5.3 Integration Tests
- [ ] Test CLI command parsing for all commands - [ ] Test CLI command parsing for all commands
- [ ] Test config file creation and updates - [ ] Test config file creation and updates
- [ ] Test UPS add/edit/remove operations - [ ] Test UPS add/edit/remove operations
@@ -317,6 +346,7 @@ nupst help → nupst help / nupst --help
- [ ] Mock systemd operations for testing - [ ] Mock systemd operations for testing
### 5.4 Binary Testing ### 5.4 Binary Testing
- [ ] Test compiled binary on Linux x64 - [ ] Test compiled binary on Linux x64
- [ ] Test compiled binary on Linux ARM64 (if available) - [ ] Test compiled binary on Linux ARM64 (if available)
- [ ] Test compiled binary on macOS x64 (if available) - [ ] Test compiled binary on macOS x64 (if available)
@@ -327,6 +357,7 @@ nupst help → nupst help / nupst --help
- [ ] Test systemd integration with compiled binary - [ ] Test systemd integration with compiled binary
### 5.5 Performance Testing ### 5.5 Performance Testing
- [ ] Measure binary size for each platform - [ ] Measure binary size for each platform
- [ ] Measure startup time: `time ./nupst-linux-x64 --version` - [ ] Measure startup time: `time ./nupst-linux-x64 --version`
- [ ] Measure memory footprint during daemon operation - [ ] Measure memory footprint during daemon operation
@@ -334,6 +365,7 @@ nupst help → nupst help / nupst --help
- [ ] Document performance metrics - [ ] Document performance metrics
### 5.6 Upgrade Path Testing ### 5.6 Upgrade Path Testing
- [ ] Create test with v3.x config - [ ] Create test with v3.x config
- [ ] Verify v4.x can read existing config - [ ] Verify v4.x can read existing config
- [ ] Test migration from old commands to new commands - [ ] Test migration from old commands to new commands
@@ -344,6 +376,7 @@ nupst help → nupst help / nupst --help
## Phase 6: Distribution Strategy (2-3 hours) ## Phase 6: Distribution Strategy (2-3 hours)
### 6.1 GitHub Actions Workflow ### 6.1 GitHub Actions Workflow
- [ ] Create `.github/workflows/release.yml`: - [ ] Create `.github/workflows/release.yml`:
```yaml ```yaml
name: Release name: Release
@@ -373,6 +406,7 @@ nupst help → nupst help / nupst --help
``` ```
### 6.2 Update package.json for npm ### 6.2 Update package.json for npm
- [ ] Update version to 4.0.0 - [ ] Update version to 4.0.0
- [ ] Update description to mention Deno - [ ] Update description to mention Deno
- [ ] Add postinstall script to symlink appropriate binary: - [ ] Add postinstall script to symlink appropriate binary:
@@ -398,6 +432,7 @@ nupst help → nupst help / nupst --help
- [ ] Create `bin/nupst-npm-wrapper.js` as entry point - [ ] Create `bin/nupst-npm-wrapper.js` as entry point
### 6.3 Verify Distribution Methods ### 6.3 Verify Distribution Methods
- [ ] Test GitHub release download and installation - [ ] Test GitHub release download and installation
- [ ] Test npm install from tarball - [ ] Test npm install from tarball
- [ ] Test direct install.sh script - [ ] Test direct install.sh script
@@ -408,6 +443,7 @@ nupst help → nupst help / nupst --help
## Phase 7: Documentation Updates (2-3 hours) ## Phase 7: Documentation Updates (2-3 hours)
### 7.1 Update README.md ### 7.1 Update README.md
- [ ] Remove Node.js requirements section - [ ] Remove Node.js requirements section
- [ ] Update features list (mention Deno, single executable) - [ ] Update features list (mention Deno, single executable)
- [ ] Update installation methods: - [ ] Update installation methods:
@@ -422,6 +458,7 @@ nupst help → nupst help / nupst --help
- [ ] Update uninstallation instructions - [ ] Update uninstallation instructions
### 7.2 Create MIGRATION.md ### 7.2 Create MIGRATION.md
- [ ] Create detailed migration guide from v3.x to v4.x - [ ] Create detailed migration guide from v3.x to v4.x
- [ ] List all breaking changes: - [ ] List all breaking changes:
1. CLI command structure reorganization 1. CLI command structure reorganization
@@ -434,6 +471,7 @@ nupst help → nupst help / nupst --help
- [ ] Add rollback instructions - [ ] Add rollback instructions
### 7.3 Update CHANGELOG.md ### 7.3 Update CHANGELOG.md
- [ ] Add v4.0.0 section with all breaking changes - [ ] Add v4.0.0 section with all breaking changes
- [ ] List new features (Deno, single executable) - [ ] List new features (Deno, single executable)
- [ ] List improvements (startup time, binary size) - [ ] List improvements (startup time, binary size)
@@ -441,6 +479,7 @@ nupst help → nupst help / nupst --help
- [ ] Migration guide reference - [ ] Migration guide reference
### 7.4 Update Help Text ### 7.4 Update Help Text
- [ ] Ensure all help commands show new structure - [ ] Ensure all help commands show new structure
- [ ] Add examples for common operations - [ ] Add examples for common operations
- [ ] Include migration notes in help output - [ ] Include migration notes in help output
@@ -450,6 +489,7 @@ nupst help → nupst help / nupst --help
## Phase 8: Cleanup & Finalization (1 hour) ## Phase 8: Cleanup & Finalization (1 hour)
### 8.1 Remove Obsolete Files ### 8.1 Remove Obsolete Files
- [ ] Delete `vendor/` directory (Node.js binaries) - [ ] Delete `vendor/` directory (Node.js binaries)
- [ ] Delete `dist/` directory (old compiled JS) - [ ] Delete `dist/` directory (old compiled JS)
- [ ] Delete `dist_ts/` directory (old compiled TS) - [ ] Delete `dist_ts/` directory (old compiled TS)
@@ -460,6 +500,7 @@ nupst help → nupst help / nupst --help
- [ ] Delete `pnpm-lock.yaml` - [ ] Delete `pnpm-lock.yaml`
### 8.2 Update Git Configuration ### 8.2 Update Git Configuration
- [ ] Update `.gitignore`: - [ ] Update `.gitignore`:
``` ```
# Deno # Deno
@@ -480,6 +521,7 @@ nupst help → nupst help / nupst --help
- [ ] Create `.denoignore` if needed - [ ] Create `.denoignore` if needed
### 8.3 Final Validation ### 8.3 Final Validation
- [ ] Run `deno check mod.ts` - verify no type errors - [ ] Run `deno check mod.ts` - verify no type errors
- [ ] Run `deno lint` - verify code quality - [ ] Run `deno lint` - verify code quality
- [ ] Run `deno fmt --check` - verify formatting - [ ] Run `deno fmt --check` - verify formatting
@@ -488,6 +530,7 @@ nupst help → nupst help / nupst --help
- [ ] Test each binary manually - [ ] Test each binary manually
### 8.4 Prepare for Release ### 8.4 Prepare for Release
- [ ] Create git tag: `v4.0.0` - [ ] Create git tag: `v4.0.0`
- [ ] Push to main branch - [ ] Push to main branch
- [ ] Push tags to trigger release workflow - [ ] Push tags to trigger release workflow
@@ -502,6 +545,7 @@ nupst help → nupst help / nupst --help
## Rollback Strategy ## Rollback Strategy
If critical issues are discovered: If critical issues are discovered:
- [ ] Keep `v3.1.2` tag available for rollback - [ ] Keep `v3.1.2` tag available for rollback
- [ ] Create `v3-stable` branch for continued v3 maintenance - [ ] Create `v3-stable` branch for continued v3 maintenance
- [ ] Update install.sh to offer v3/v4 choice - [ ] Update install.sh to offer v3/v4 choice
@@ -547,6 +591,7 @@ If critical issues are discovered:
## Notes & Decisions ## Notes & Decisions
### Key Decisions Made: ### Key Decisions Made:
1. ✅ Use npm:net-snmp (no pure Deno SNMP library available) 1. ✅ Use npm:net-snmp (no pure Deno SNMP library available)
2. ✅ Major version bump to 4.0.0 (breaking changes) 2. ✅ Major version bump to 4.0.0 (breaking changes)
3. ✅ CLI reorganization with subcommands 3. ✅ CLI reorganization with subcommands
@@ -554,12 +599,14 @@ If critical issues are discovered:
5. ✅ 5 platform targets (Windows ARM not supported by Deno yet) 5. ✅ 5 platform targets (Windows ARM not supported by Deno yet)
### Open Questions: ### Open Questions:
- [ ] Should we keep tsconfig.json for npm package compatibility? - [ ] Should we keep tsconfig.json for npm package compatibility?
- [ ] Should we fully migrate to Deno APIs (Deno.readFile) or keep node:fs? - [ ] Should we fully migrate to Deno APIs (Deno.readFile) or keep node:fs?
- [ ] Should we remove the `bin/nupst` wrapper or keep it? - [ ] Should we remove the `bin/nupst` wrapper or keep it?
- [ ] Should setup.sh be completely removed or kept for dependencies? - [ ] Should setup.sh be completely removed or kept for dependencies?
### Risk Areas: ### Risk Areas:
- ⚠️ SNMP native addon compatibility in compiled binaries (HIGH PRIORITY TO TEST) - ⚠️ SNMP native addon compatibility in compiled binaries (HIGH PRIORITY TO TEST)
- ⚠️ Systemd integration with new binary structure - ⚠️ Systemd integration with new binary structure
- ⚠️ Config migration from v3 to v4 - ⚠️ Config migration from v3 to v4

View File

@@ -1,4 +1,4 @@
import { assertEquals, assert } from "jsr:@std/assert@^1.0.0"; import { assert, assertEquals } from 'jsr:@std/assert@^1.0.0';
import { Logger } from '../ts/logger.ts'; import { Logger } from '../ts/logger.ts';
// Create a Logger instance for testing // Create a Logger instance for testing
@@ -71,7 +71,7 @@ Deno.test('should create a complete logbox in one call', () => {
logger.logBox('Complete Box', [ logger.logBox('Complete Box', [
'Line 1', 'Line 1',
'Line 2', 'Line 2',
'Line 3' 'Line 3',
], 40); ], 40);
// Just assert that the test runs without errors // Just assert that the test runs without errors
@@ -81,7 +81,7 @@ Deno.test('should create a complete logbox in one call', () => {
Deno.test('should handle content that exceeds box width', () => { Deno.test('should handle content that exceeds box width', () => {
// Just ensuring no errors occur when content is too long // Just ensuring no errors occur when content is too long
logger.logBox('Truncation Test', [ logger.logBox('Truncation Test', [
'This line is way too long and should be truncated because it exceeds the available space' 'This line is way too long and should be truncated because it exceeds the available space',
], 30); ], 30);
// Just assert that the test runs without errors // Just assert that the test runs without errors
@@ -126,13 +126,13 @@ Deno.test('Logger Demo', () => {
logger.logBox('UPS Status', [ logger.logBox('UPS Status', [
'Power Status: onBattery', 'Power Status: onBattery',
'Battery Capacity: 75%', 'Battery Capacity: 75%',
'Runtime Remaining: 30 minutes' 'Runtime Remaining: 30 minutes',
], 45); ], 45);
// Logbox with content that's too long for the width // Logbox with content that's too long for the width
logger.logBox('Truncation Example', [ logger.logBox('Truncation Example', [
'This line is short enough to fit within the box width', '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' 'This line is way too long and will be truncated because it exceeds the available space for content within the logbox',
], 40); ], 40);
// Demonstrating logbox width being remembered // Demonstrating logbox width being remembered

View File

@@ -1,4 +1,4 @@
import { assert, assertEquals, assertExists } from "jsr:@std/assert@^1.0.0"; import { assert, assertEquals, assertExists } from 'jsr:@std/assert@^1.0.0';
import { NupstSnmp } from '../ts/snmp/manager.ts'; import { NupstSnmp } from '../ts/snmp/manager.ts';
import type { ISnmpConfig } from '../ts/snmp/types.ts'; import type { ISnmpConfig } from '../ts/snmp/types.ts';
@@ -32,7 +32,7 @@ Deno.test('Real UPS test v1', async () => {
// Use a short timeout for testing // Use a short timeout for testing
const testSnmpConfig = { const testSnmpConfig = {
...snmpConfig, ...snmpConfig,
timeout: Math.min(snmpConfig.timeout, 10000) // Use at most 10 seconds for testing timeout: Math.min(snmpConfig.timeout, 10000), // Use at most 10 seconds for testing
}; };
// Try to get the UPS status // Try to get the UPS status
@@ -69,7 +69,7 @@ Deno.test('Real UPS test v3', async () => {
// Use a short timeout for testing // Use a short timeout for testing
const testSnmpConfig = { const testSnmpConfig = {
...snmpConfig, ...snmpConfig,
timeout: Math.min(snmpConfig.timeout, 10000) // Use at most 10 seconds for testing timeout: Math.min(snmpConfig.timeout, 10000), // Use at most 10 seconds for testing
}; };
// Try to get the UPS status // Try to get the UPS status

View File

@@ -4,5 +4,5 @@
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/nupst', name: '@serve.zone/nupst',
version: '3.1.2', version: '3.1.2',
description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices',
} };

View File

@@ -1,4 +1,4 @@
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process';
import { Nupst } from './nupst.ts'; import { Nupst } from './nupst.ts';
import { logger } from './logger.ts'; import { logger } from './logger.ts';
@@ -62,7 +62,11 @@ export class NupstCli {
* @param commandArgs Additional command arguments * @param commandArgs Additional command arguments
* @param debugMode Whether debug mode is enabled * @param debugMode Whether debug mode is enabled
*/ */
private async executeCommand(command: string, commandArgs: string[], debugMode: boolean): Promise<void> { private async executeCommand(
command: string,
commandArgs: string[],
debugMode: boolean,
): Promise<void> {
// Get access to the handlers // Get access to the handlers
const upsHandler = this.nupst.getUpsHandler(); const upsHandler = this.nupst.getUpsHandler();
const groupHandler = this.nupst.getGroupHandler(); const groupHandler = this.nupst.getGroupHandler();
@@ -87,7 +91,7 @@ export class NupstCli {
break; break;
case 'restart': case 'restart':
await serviceHandler.stop(); await serviceHandler.stop();
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2s
await serviceHandler.start(); await serviceHandler.start();
break; break;
case 'status': case 'status':
@@ -263,7 +267,9 @@ export class NupstCli {
await serviceHandler.logs(); await serviceHandler.logs();
break; break;
case 'daemon-start': case 'daemon-start':
logger.log("Note: 'nupst daemon-start' is deprecated. Use 'nupst service start-daemon' instead."); logger.log(
"Note: 'nupst daemon-start' is deprecated. Use 'nupst service start-daemon' instead.",
);
await serviceHandler.daemonStart(debugMode); await serviceHandler.daemonStart(debugMode);
break; break;
@@ -328,8 +334,12 @@ export class NupstCli {
logger.logBoxLine(`${ups.name} (${ups.id}):`); logger.logBoxLine(`${ups.name} (${ups.id}):`);
logger.logBoxLine(` Host: ${ups.snmp.host}:${ups.snmp.port}`); logger.logBoxLine(` Host: ${ups.snmp.host}:${ups.snmp.port}`);
logger.logBoxLine(` Model: ${ups.snmp.upsModel}`); logger.logBoxLine(` Model: ${ups.snmp.upsModel}`);
logger.logBoxLine(` Thresholds: ${ups.thresholds.battery}% battery, ${ups.thresholds.runtime} min runtime`); logger.logBoxLine(
logger.logBoxLine(` Groups: ${ups.groups.length > 0 ? ups.groups.join(', ') : 'None'}`); ` Thresholds: ${ups.thresholds.battery}% battery, ${ups.thresholds.runtime} min runtime`,
);
logger.logBoxLine(
` Groups: ${ups.groups.length > 0 ? ups.groups.join(', ') : 'None'}`,
);
logger.logBoxLine(''); logger.logBoxLine('');
} }
logger.logBoxEnd(); logger.logBoxEnd();
@@ -346,9 +356,14 @@ export class NupstCli {
} }
// List UPS devices in this group // List UPS devices in this group
const upsInGroup = config.upsDevices.filter(ups => ups.groups && ups.groups.includes(group.id)); const upsInGroup = config.upsDevices.filter((ups) =>
logger.logBoxLine(` UPS Devices: ${upsInGroup.length > 0 ? ups.groups && ups.groups.includes(group.id)
upsInGroup.map(ups => ups.name).join(', ') : 'None'}`); );
logger.logBoxLine(
` UPS Devices: ${
upsInGroup.length > 0 ? upsInGroup.map((ups) => ups.name).join(', ') : 'None'
}`,
);
logger.logBoxLine(''); logger.logBoxLine('');
} }
logger.logBoxEnd(); logger.logBoxEnd();
@@ -390,11 +405,15 @@ export class NupstCli {
// 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) {
logger.logBoxLine('Custom OIDs:'); logger.logBoxLine('Custom OIDs:');
logger.logBoxLine(` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`);
logger.logBoxLine( logger.logBoxLine(
` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}` ` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`,
);
logger.logBoxLine(
` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`,
);
logger.logBoxLine(
` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`,
); );
logger.logBoxLine(` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
} }
} }
@@ -435,7 +454,11 @@ export class NupstCli {
// Ignore errors checking service status // Ignore errors checking service status
} }
} catch (error) { } catch (error) {
logger.error(`Failed to display configuration: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Failed to display configuration: ${
error instanceof Error ? error.message : String(error)
}`,
);
} }
} }

View File

@@ -69,9 +69,9 @@ export class GroupHandler {
const mode = (group.mode || 'unknown').padEnd(12, ' ').substring(0, 12); const mode = (group.mode || 'unknown').padEnd(12, ' ').substring(0, 12);
// Count UPS devices in this group // Count UPS devices in this group
const upsInGroup = config.upsDevices.filter(ups => ups.groups.includes(group.id)); const upsInGroup = config.upsDevices.filter((ups) => ups.groups.includes(group.id));
const upsCount = upsInGroup.length; const upsCount = upsInGroup.length;
const upsNames = upsInGroup.map(ups => ups.name).join(', '); const upsNames = upsInGroup.map((ups) => ups.name).join(', ');
logger.logBoxLine(`${id} | ${name} | ${mode} | ${upsCount > 0 ? upsNames : 'None'}`); logger.logBoxLine(`${id} | ${name} | ${mode} | ${upsCount > 0 ? upsNames : 'None'}`);
} }
@@ -79,7 +79,9 @@ export class GroupHandler {
logger.logBoxEnd(); logger.logBoxEnd();
} catch (error) { } catch (error) {
logger.error(`Failed to list UPS groups: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Failed to list UPS groups: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
@@ -110,7 +112,9 @@ export class GroupHandler {
try { try {
await this.nupst.getDaemon().loadConfig(); await this.nupst.getDaemon().loadConfig();
} catch (error) { } catch (error) {
logger.error('No configuration found. Please run "nupst setup" first to create a configuration.'); logger.error(
'No configuration found. Please run "nupst setup" first to create a configuration.',
);
return; return;
} }
@@ -149,7 +153,7 @@ export class GroupHandler {
id: groupId, id: groupId,
name: name || `Group-${groupId}`, name: name || `Group-${groupId}`,
mode, mode,
description: description || undefined description: description || undefined,
}; };
// Add the group to the configuration // Add the group to the configuration
@@ -171,7 +175,9 @@ export class GroupHandler {
// Check if there are UPS devices to assign to this group // Check if there are UPS devices to assign to this group
if (config.upsDevices.length > 0) { if (config.upsDevices.length > 0) {
const assignUps = await prompt('Would you like to assign UPS devices to this group now? (y/N): '); const assignUps = await prompt(
'Would you like to assign UPS devices to this group now? (y/N): ',
);
if (assignUps.toLowerCase() === 'y') { if (assignUps.toLowerCase() === 'y') {
await this.assignUpsToGroup(newGroup.id, config, prompt); await this.assignUpsToGroup(newGroup.id, config, prompt);
@@ -220,7 +226,9 @@ export class GroupHandler {
try { try {
await this.nupst.getDaemon().loadConfig(); await this.nupst.getDaemon().loadConfig();
} catch (error) { } catch (error) {
logger.error('No configuration found. Please run "nupst setup" first to create a configuration.'); logger.error(
'No configuration found. Please run "nupst setup" first to create a configuration.',
);
return; return;
} }
@@ -229,12 +237,14 @@ export class GroupHandler {
// Check if groups are initialized // Check if groups are initialized
if (!config.groups || !Array.isArray(config.groups)) { if (!config.groups || !Array.isArray(config.groups)) {
logger.error('No groups configured. Please run "nupst group add" first to create a group.'); logger.error(
'No groups configured. Please run "nupst group add" first to create a group.',
);
return; return;
} }
// Find the group to edit // Find the group to edit
const groupIndex = config.groups.findIndex(group => group.id === groupId); const groupIndex = config.groups.findIndex((group) => group.id === groupId);
if (groupIndex === -1) { if (groupIndex === -1) {
logger.error(`Group with ID "${groupId}" not found.`); logger.error(`Group with ID "${groupId}" not found.`);
return; return;
@@ -283,7 +293,9 @@ export class GroupHandler {
logger.logBoxEnd(); logger.logBoxEnd();
// Edit UPS assignments if requested // Edit UPS assignments if requested
const editAssignments = await prompt('Would you like to edit UPS assignments for this group? (y/N): '); const editAssignments = await prompt(
'Would you like to edit UPS assignments for this group? (y/N): ',
);
if (editAssignments.toLowerCase() === 'y') { if (editAssignments.toLowerCase() === 'y') {
await this.assignUpsToGroup(group.id, config, prompt); await this.assignUpsToGroup(group.id, config, prompt);
@@ -313,7 +325,9 @@ export class GroupHandler {
try { try {
await this.nupst.getDaemon().loadConfig(); await this.nupst.getDaemon().loadConfig();
} catch (error) { } catch (error) {
logger.error('No configuration found. Please run "nupst setup" first to create a configuration.'); logger.error(
'No configuration found. Please run "nupst setup" first to create a configuration.',
);
return; return;
} }
@@ -327,7 +341,7 @@ export class GroupHandler {
} }
// Find the group to delete // Find the group to delete
const groupIndex = config.groups.findIndex(group => group.id === groupId); const groupIndex = config.groups.findIndex((group) => group.id === groupId);
if (groupIndex === -1) { if (groupIndex === -1) {
logger.error(`Group with ID "${groupId}" not found.`); logger.error(`Group with ID "${groupId}" not found.`);
return; return;
@@ -342,10 +356,13 @@ export class GroupHandler {
output: process.stdout, output: process.stdout,
}); });
const confirm = await new Promise<string>(resolve => { const confirm = await new Promise<string>((resolve) => {
rl.question(`Are you sure you want to delete group "${groupToDelete.name}" (${groupId})? [y/N]: `, answer => { rl.question(
resolve(answer.toLowerCase()); `Are you sure you want to delete group "${groupToDelete.name}" (${groupId})? [y/N]: `,
}); (answer) => {
resolve(answer.toLowerCase());
},
);
}); });
rl.close(); rl.close();
@@ -376,7 +393,9 @@ export class GroupHandler {
// Check if service is running and restart it if needed // Check if service is running and restart it if needed
this.nupst.getUpsHandler().restartServiceIfRunning(); this.nupst.getUpsHandler().restartServiceIfRunning();
} catch (error) { } catch (error) {
logger.error(`Failed to delete group: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Failed to delete group: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
@@ -389,7 +408,7 @@ export class GroupHandler {
public async assignUpsToGroups( public async assignUpsToGroups(
ups: any, ups: any,
groups: any[], groups: any[],
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
// Initialize groups array if it doesn't exist // Initialize groups array if it doesn't exist
if (!ups.groups) { if (!ups.groups) {
@@ -400,7 +419,7 @@ export class GroupHandler {
logger.log('\nCurrent Group Assignments:'); logger.log('\nCurrent Group Assignments:');
if (ups.groups && ups.groups.length > 0) { if (ups.groups && ups.groups.length > 0) {
for (const groupId of ups.groups) { for (const groupId of ups.groups) {
const group = groups.find(g => g.id === groupId); const group = groups.find((g) => g.id === groupId);
if (group) { if (group) {
logger.log(`- ${group.name} (${group.id})`); logger.log(`- ${group.name} (${group.id})`);
} else { } else {
@@ -421,11 +440,15 @@ export class GroupHandler {
for (let i = 0; i < groups.length; i++) { for (let i = 0; i < groups.length; i++) {
const group = groups[i]; const group = groups[i];
const assigned = ups.groups && ups.groups.includes(group.id); const assigned = ups.groups && ups.groups.includes(group.id);
logger.log(`${i + 1}) ${group.name} (${group.id}) [${assigned ? 'Assigned' : 'Not Assigned'}]`); logger.log(
`${i + 1}) ${group.name} (${group.id}) [${assigned ? 'Assigned' : 'Not Assigned'}]`,
);
} }
// Prompt for group selection // Prompt for group selection
const selection = await prompt('\nSelect groups to assign/unassign (comma-separated numbers, or "clear" to remove all): '); const selection = await prompt(
'\nSelect groups to assign/unassign (comma-separated numbers, or "clear" to remove all): ',
);
if (selection.toLowerCase() === 'clear') { if (selection.toLowerCase() === 'clear') {
// Clear all group assignments // Clear all group assignments
@@ -440,7 +463,7 @@ export class GroupHandler {
} }
// Process selections // Process selections
const selections = selection.split(',').map(s => s.trim()); const selections = selection.split(',').map((s) => s.trim());
for (const sel of selections) { for (const sel of selections) {
const index = parseInt(sel, 10) - 1; const index = parseInt(sel, 10) - 1;
@@ -479,7 +502,7 @@ export class GroupHandler {
public async assignUpsToGroup( public async assignUpsToGroup(
groupId: string, groupId: string,
config: any, config: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
if (!config.upsDevices || config.upsDevices.length === 0) { if (!config.upsDevices || config.upsDevices.length === 0) {
logger.log('No UPS devices available. Use "nupst add" to add UPS devices.'); logger.log('No UPS devices available. Use "nupst add" to add UPS devices.');
@@ -494,7 +517,9 @@ export class GroupHandler {
// Show current assignments // Show current assignments
logger.log(`\nUPS devices in group "${group.name}" (${group.id}):`); logger.log(`\nUPS devices in group "${group.name}" (${group.id}):`);
const upsInGroup = config.upsDevices.filter((ups: { groups?: string[] }) => ups.groups && ups.groups.includes(groupId)); const upsInGroup = config.upsDevices.filter((ups: { groups?: string[] }) =>
ups.groups && ups.groups.includes(groupId)
);
if (upsInGroup.length === 0) { if (upsInGroup.length === 0) {
logger.log('- None'); logger.log('- None');
} else { } else {
@@ -512,7 +537,9 @@ export class GroupHandler {
} }
// Prompt for UPS selection // Prompt for UPS selection
const selection = await prompt('\nSelect UPS devices to assign/unassign (comma-separated numbers, or "clear" to remove all): '); const selection = await prompt(
'\nSelect UPS devices to assign/unassign (comma-separated numbers, or "clear" to remove all): ',
);
if (selection.toLowerCase() === 'clear') { if (selection.toLowerCase() === 'clear') {
// Clear all UPS from this group // Clear all UPS from this group
@@ -534,7 +561,7 @@ export class GroupHandler {
} }
// Process selections // Process selections
const selections = selection.split(',').map(s => s.trim()); const selections = selection.split(',').map((s) => s.trim());
for (const sel of selections) { for (const sel of selections) {
const index = parseInt(sel, 10) - 1; const index = parseInt(sel, 10) - 1;

View File

@@ -1,5 +1,5 @@
import process from 'node:process'; import process from 'node:process';
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process';
import { Nupst } from '../nupst.ts'; import { Nupst } from '../nupst.ts';
import { logger } from '../logger.ts'; import { logger } from '../logger.ts';
@@ -129,7 +129,7 @@ export class ServiceHandler {
try { try {
// Check if running as root // Check if running as root
this.checkRootAccess( this.checkRootAccess(
'This command must be run as root to update NUPST and refresh the systemd service.' 'This command must be run as root to update NUPST and refresh the systemd service.',
); );
const boxWidth = 45; const boxWidth = 45;
@@ -243,7 +243,7 @@ export class ServiceHandler {
// Ask about removing configuration // Ask about removing configuration
const removeConfig = await prompt( const removeConfig = await prompt(
'Do you want to remove the NUPST configuration files? (y/N): ' 'Do you want to remove the NUPST configuration files? (y/N): ',
); );
// Find the uninstall.sh script location // Find the uninstall.sh script location

View File

@@ -1,5 +1,5 @@
import process from 'node:process'; import process from 'node:process';
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process';
import { Nupst } from '../nupst.ts'; import { Nupst } from '../nupst.ts';
import { logger } from '../logger.ts'; import { logger } from '../logger.ts';
import * as helpers from '../helpers/index.ts'; import * as helpers from '../helpers/index.ts';
@@ -78,9 +78,9 @@ export class UpsHandler {
name: 'Default UPS', name: 'Default UPS',
snmp: config.snmp, snmp: config.snmp,
thresholds: config.thresholds, thresholds: config.thresholds,
groups: [] groups: [],
}], }],
groups: [] groups: [],
}; };
logger.log('Converting existing configuration to multi-UPS format.'); logger.log('Converting existing configuration to multi-UPS format.');
} }
@@ -89,7 +89,7 @@ export class UpsHandler {
config = { config = {
checkInterval: 30000, // Default check interval checkInterval: 30000, // Default check interval
upsDevices: [], upsDevices: [],
groups: [] groups: [],
}; };
logger.log('No existing configuration found. Creating a new configuration.'); logger.log('No existing configuration found. Creating a new configuration.');
} }
@@ -108,13 +108,13 @@ export class UpsHandler {
community: 'public', community: 'public',
version: 1, version: 1,
timeout: 5000, timeout: 5000,
upsModel: 'cyberpower' as TUpsModel upsModel: 'cyberpower' as TUpsModel,
}, },
thresholds: { thresholds: {
battery: 60, battery: 60,
runtime: 20 runtime: 20,
}, },
groups: [] groups: [],
}; };
// Gather SNMP settings // Gather SNMP settings
@@ -189,7 +189,10 @@ export class UpsHandler {
* @param upsId ID of the UPS to edit (undefined for default UPS) * @param upsId ID of the UPS to edit (undefined for default UPS)
* @param prompt Function to prompt for user input * @param prompt Function to prompt for user input
*/ */
public async runEditProcess(upsId: string | undefined, prompt: (question: string) => Promise<string>): Promise<void> { public async runEditProcess(
upsId: string | undefined,
prompt: (question: string) => Promise<string>,
): Promise<void> {
logger.log('\nNUPST Edit UPS'); logger.log('\nNUPST Edit UPS');
logger.log('=============\n'); logger.log('=============\n');
@@ -224,7 +227,7 @@ export class UpsHandler {
name: 'Default UPS', name: 'Default UPS',
snmp: config.snmp, snmp: config.snmp,
thresholds: config.thresholds, thresholds: config.thresholds,
groups: [] groups: [],
}]; }];
config.groups = []; config.groups = [];
logger.log('Converting existing configuration to multi-UPS format.'); logger.log('Converting existing configuration to multi-UPS format.');
@@ -234,7 +237,7 @@ export class UpsHandler {
let upsToEdit; let upsToEdit;
if (upsId) { if (upsId) {
// Find specific UPS by ID // Find specific UPS by ID
upsToEdit = config.upsDevices.find(ups => ups.id === upsId); upsToEdit = config.upsDevices.find((ups) => ups.id === upsId);
if (!upsToEdit) { if (!upsToEdit) {
logger.error(`UPS with ID "${upsId}" not found.`); logger.error(`UPS with ID "${upsId}" not found.`);
return; return;
@@ -316,7 +319,7 @@ export class UpsHandler {
} }
// Find the UPS to delete // Find the UPS to delete
const upsIndex = config.upsDevices.findIndex(ups => ups.id === upsId); const upsIndex = config.upsDevices.findIndex((ups) => ups.id === upsId);
if (upsIndex === -1) { if (upsIndex === -1) {
logger.error(`UPS with ID "${upsId}" not found.`); logger.error(`UPS with ID "${upsId}" not found.`);
return; return;
@@ -331,10 +334,13 @@ export class UpsHandler {
output: process.stdout, output: process.stdout,
}); });
const confirm = await new Promise<string>(resolve => { const confirm = await new Promise<string>((resolve) => {
rl.question(`Are you sure you want to delete UPS "${upsToDelete.name}" (${upsId})? [y/N]: `, answer => { rl.question(
resolve(answer.toLowerCase()); `Are you sure you want to delete UPS "${upsToDelete.name}" (${upsId})? [y/N]: `,
}); (answer) => {
resolve(answer.toLowerCase());
},
);
}); });
rl.close(); rl.close();
@@ -355,7 +361,9 @@ export class UpsHandler {
// 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();
} catch (error) { } catch (error) {
logger.error(`Failed to delete UPS: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Failed to delete UPS: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
@@ -395,7 +403,9 @@ export class UpsHandler {
logger.logBoxLine('Default UPS:'); logger.logBoxLine('Default UPS:');
logger.logBoxLine(` Host: ${config.snmp.host}:${config.snmp.port}`); logger.logBoxLine(` Host: ${config.snmp.host}:${config.snmp.port}`);
logger.logBoxLine(` Model: ${config.snmp.upsModel || 'cyberpower'}`); logger.logBoxLine(` Model: ${config.snmp.upsModel || 'cyberpower'}`);
logger.logBoxLine(` Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`); logger.logBoxLine(
` Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`,
);
logger.logBoxLine(''); logger.logBoxLine('');
logger.logBoxLine('Use "nupst add" to add more UPS devices and migrate'); logger.logBoxLine('Use "nupst add" to add more UPS devices and migrate');
logger.logBoxLine('to the multi-UPS configuration format.'); logger.logBoxLine('to the multi-UPS configuration format.');
@@ -413,8 +423,12 @@ export class UpsHandler {
} else { } else {
logger.logBoxLine(`Found ${config.upsDevices.length} UPS device(s)`); logger.logBoxLine(`Found ${config.upsDevices.length} UPS device(s)`);
logger.logBoxLine(''); logger.logBoxLine('');
logger.logBoxLine('ID | Name | Host | Mode | Groups'); logger.logBoxLine(
logger.logBoxLine('-----------+----------------------+-----------------+--------------+----------------'); 'ID | Name | Host | Mode | Groups',
);
logger.logBoxLine(
'-----------+----------------------+-----------------+--------------+----------------',
);
for (const ups of config.upsDevices) { for (const ups of config.upsDevices) {
const id = ups.id.padEnd(10, ' ').substring(0, 10); const id = ups.id.padEnd(10, ' ').substring(0, 10);
@@ -429,7 +443,9 @@ export class UpsHandler {
logger.logBoxEnd(); logger.logBoxEnd();
} catch (error) { } catch (error) {
logger.error(`Failed to list UPS devices: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Failed to list UPS devices: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
@@ -529,7 +545,9 @@ export class UpsHandler {
if (snmpConfig.upsModel === 'custom' && snmpConfig.customOIDs) { if (snmpConfig.upsModel === 'custom' && snmpConfig.customOIDs) {
logger.logBoxLine('Custom OIDs:'); logger.logBoxLine('Custom OIDs:');
logger.logBoxLine(` Power Status: ${snmpConfig.customOIDs.POWER_STATUS || 'Not set'}`); logger.logBoxLine(` Power Status: ${snmpConfig.customOIDs.POWER_STATUS || 'Not set'}`);
logger.logBoxLine(` Battery Capacity: ${snmpConfig.customOIDs.BATTERY_CAPACITY || 'Not set'}`); logger.logBoxLine(
` Battery Capacity: ${snmpConfig.customOIDs.BATTERY_CAPACITY || 'Not set'}`,
);
logger.logBoxLine(` Battery Runtime: ${snmpConfig.customOIDs.BATTERY_RUNTIME || 'Not set'}`); logger.logBoxLine(` Battery Runtime: ${snmpConfig.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
} }
logger.logBoxLine('Thresholds:'); logger.logBoxLine('Thresholds:');
@@ -538,7 +556,9 @@ export class UpsHandler {
// Show group assignments if this is a UPS config // Show group assignments if this is a UPS config
if (config.groups && Array.isArray(config.groups)) { if (config.groups && Array.isArray(config.groups)) {
logger.logBoxLine(`Group Assignments: ${config.groups.length === 0 ? 'None' : config.groups.join(', ')}`); logger.logBoxLine(
`Group Assignments: ${config.groups.length === 0 ? 'None' : config.groups.join(', ')}`,
);
} }
logger.logBoxLine(`Check Interval: ${checkInterval / 1000} seconds`); logger.logBoxLine(`Check Interval: ${checkInterval / 1000} seconds`);
@@ -599,26 +619,26 @@ export class UpsHandler {
if (status.batteryCapacity < thresholds.battery) { if (status.batteryCapacity < thresholds.battery) {
logger.logBoxLine('⚠️ WARNING: Battery capacity below threshold'); logger.logBoxLine('⚠️ WARNING: Battery capacity below threshold');
logger.logBoxLine( logger.logBoxLine(
` Current: ${status.batteryCapacity}% | Threshold: ${thresholds.battery}%` ` Current: ${status.batteryCapacity}% | Threshold: ${thresholds.battery}%`,
); );
logger.logBoxLine(' System would initiate shutdown'); logger.logBoxLine(' System would initiate shutdown');
} else { } else {
logger.logBoxLine('✓ Battery capacity above threshold'); logger.logBoxLine('✓ Battery capacity above threshold');
logger.logBoxLine( logger.logBoxLine(
` Current: ${status.batteryCapacity}% | Threshold: ${thresholds.battery}%` ` Current: ${status.batteryCapacity}% | Threshold: ${thresholds.battery}%`,
); );
} }
if (status.batteryRuntime < thresholds.runtime) { if (status.batteryRuntime < thresholds.runtime) {
logger.logBoxLine('⚠️ WARNING: Runtime below threshold'); logger.logBoxLine('⚠️ WARNING: Runtime below threshold');
logger.logBoxLine( logger.logBoxLine(
` Current: ${status.batteryRuntime} min | Threshold: ${thresholds.runtime} min` ` Current: ${status.batteryRuntime} min | Threshold: ${thresholds.runtime} min`,
); );
logger.logBoxLine(' System would initiate shutdown'); logger.logBoxLine(' System would initiate shutdown');
} else { } else {
logger.logBoxLine('✓ Runtime above threshold'); logger.logBoxLine('✓ Runtime above threshold');
logger.logBoxLine( logger.logBoxLine(
` Current: ${status.batteryRuntime} min | Threshold: ${thresholds.runtime} min` ` Current: ${status.batteryRuntime} min | Threshold: ${thresholds.runtime} min`,
); );
} }
@@ -632,7 +652,7 @@ export class UpsHandler {
*/ */
private async gatherSnmpSettings( private async gatherSnmpSettings(
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
// SNMP IP Address // SNMP IP Address
const defaultHost = snmpConfig.host || '127.0.0.1'; const defaultHost = snmpConfig.host || '127.0.0.1';
@@ -653,10 +673,9 @@ export class UpsHandler {
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);
snmpConfig.version = snmpConfig.version = versionInput.trim() && (version === 1 || version === 2 || version === 3)
versionInput.trim() && (version === 1 || version === 2 || version === 3) ? version
? version : defaultVersion;
: defaultVersion;
if (snmpConfig.version === 1 || snmpConfig.version === 2) { if (snmpConfig.version === 1 || snmpConfig.version === 2) {
// SNMP Community String (for v1/v2c) // SNMP Community String (for v1/v2c)
@@ -676,7 +695,7 @@ export class UpsHandler {
*/ */
private async gatherSnmpV3Settings( private async gatherSnmpV3Settings(
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
console.log('\nSNMPv3 Security Settings:'); console.log('\nSNMPv3 Security Settings:');
@@ -734,7 +753,7 @@ export class UpsHandler {
// Allow customizing the timeout value // Allow customizing the timeout value
const defaultTimeout = snmpConfig.timeout / 1000; // Convert from ms to seconds for display const defaultTimeout = snmpConfig.timeout / 1000; // Convert from ms to seconds for display
console.log( console.log(
'\nSNMPv3 operations with authentication and privacy may require longer timeouts.' '\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);
@@ -751,7 +770,7 @@ export class UpsHandler {
*/ */
private async gatherAuthenticationSettings( private async gatherAuthenticationSettings(
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
// Authentication protocol // Authentication protocol
console.log('\nAuthentication Protocol:'); console.log('\nAuthentication Protocol:');
@@ -759,7 +778,7 @@ export class UpsHandler {
console.log(' 2) SHA'); console.log(' 2) SHA');
const defaultAuthProtocol = snmpConfig.authProtocol === 'SHA' ? 2 : 1; const defaultAuthProtocol = snmpConfig.authProtocol === 'SHA' ? 2 : 1;
const authProtocolInput = await prompt( const authProtocolInput = await prompt(
`Select Authentication Protocol [${defaultAuthProtocol}]: ` `Select Authentication Protocol [${defaultAuthProtocol}]: `,
); );
const authProtocol = parseInt(authProtocolInput, 10) || defaultAuthProtocol; const authProtocol = parseInt(authProtocolInput, 10) || defaultAuthProtocol;
snmpConfig.authProtocol = authProtocol === 2 ? 'SHA' : 'MD5'; snmpConfig.authProtocol = authProtocol === 2 ? 'SHA' : 'MD5';
@@ -777,7 +796,7 @@ export class UpsHandler {
*/ */
private async gatherPrivacySettings( private async gatherPrivacySettings(
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
// Privacy protocol // Privacy protocol
console.log('\nPrivacy Protocol:'); console.log('\nPrivacy Protocol:');
@@ -801,31 +820,29 @@ export class UpsHandler {
*/ */
private async gatherThresholdSettings( private async gatherThresholdSettings(
thresholds: any, thresholds: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
console.log('\nShutdown Thresholds:'); console.log('\nShutdown Thresholds:');
// Battery threshold // Battery threshold
const defaultBatteryThreshold = thresholds.battery || 60; const defaultBatteryThreshold = thresholds.battery || 60;
const batteryThresholdInput = await prompt( const batteryThresholdInput = await prompt(
`Battery percentage threshold [${defaultBatteryThreshold}%]: ` `Battery percentage threshold [${defaultBatteryThreshold}%]: `,
); );
const batteryThreshold = parseInt(batteryThresholdInput, 10); const batteryThreshold = parseInt(batteryThresholdInput, 10);
thresholds.battery = thresholds.battery = batteryThresholdInput.trim() && !isNaN(batteryThreshold)
batteryThresholdInput.trim() && !isNaN(batteryThreshold) ? batteryThreshold
? batteryThreshold : defaultBatteryThreshold;
: defaultBatteryThreshold;
// Runtime threshold // Runtime threshold
const defaultRuntimeThreshold = thresholds.runtime || 20; const defaultRuntimeThreshold = thresholds.runtime || 20;
const runtimeThresholdInput = await prompt( const runtimeThresholdInput = await prompt(
`Runtime minutes threshold [${defaultRuntimeThreshold} minutes]: ` `Runtime minutes threshold [${defaultRuntimeThreshold} minutes]: `,
); );
const runtimeThreshold = parseInt(runtimeThresholdInput, 10); const runtimeThreshold = parseInt(runtimeThresholdInput, 10);
thresholds.runtime = thresholds.runtime = runtimeThresholdInput.trim() && !isNaN(runtimeThreshold)
runtimeThresholdInput.trim() && !isNaN(runtimeThreshold) ? runtimeThreshold
? runtimeThreshold : defaultRuntimeThreshold;
: defaultRuntimeThreshold;
} }
/** /**
@@ -835,7 +852,7 @@ export class UpsHandler {
*/ */
private async gatherUpsModelSettings( private async gatherUpsModelSettings(
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
console.log('\nUPS Model Selection:'); console.log('\nUPS Model Selection:');
console.log(' 1) CyberPower'); console.log(' 1) CyberPower');
@@ -845,20 +862,19 @@ export class UpsHandler {
console.log(' 5) Liebert/Vertiv'); console.log(' 5) Liebert/Vertiv');
console.log(' 6) Custom (Advanced)'); console.log(' 6) Custom (Advanced)');
const defaultModelValue = const defaultModelValue = snmpConfig.upsModel === 'cyberpower'
snmpConfig.upsModel === 'cyberpower' ? 1
? 1 : snmpConfig.upsModel === 'apc'
: snmpConfig.upsModel === 'apc' ? 2
? 2 : snmpConfig.upsModel === 'eaton'
: snmpConfig.upsModel === 'eaton' ? 3
? 3 : snmpConfig.upsModel === 'tripplite'
: snmpConfig.upsModel === 'tripplite' ? 4
? 4 : snmpConfig.upsModel === 'liebert'
: snmpConfig.upsModel === 'liebert' ? 5
? 5 : snmpConfig.upsModel === 'custom'
: snmpConfig.upsModel === 'custom' ? 6
? 6 : 1;
: 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;
@@ -905,7 +921,7 @@ export class UpsHandler {
logger.logBoxLine(`SNMP Version: ${ups.snmp.version}`); logger.logBoxLine(`SNMP Version: ${ups.snmp.version}`);
logger.logBoxLine(`UPS Model: ${ups.snmp.upsModel}`); logger.logBoxLine(`UPS Model: ${ups.snmp.upsModel}`);
logger.logBoxLine( logger.logBoxLine(
`Thresholds: ${ups.thresholds.battery}% battery, ${ups.thresholds.runtime} min runtime` `Thresholds: ${ups.thresholds.battery}% battery, ${ups.thresholds.runtime} min runtime`,
); );
if (ups.groups && ups.groups.length > 0) { if (ups.groups && ups.groups.length > 0) {
logger.logBoxLine(`Groups: ${ups.groups.join(', ')}`); logger.logBoxLine(`Groups: ${ups.groups.join(', ')}`);
@@ -923,10 +939,10 @@ export class UpsHandler {
*/ */
private async optionallyTestConnection( private async optionallyTestConnection(
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string> prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
const testConnection = await prompt( const testConnection = await prompt(
'Would you like to test the connection to your UPS? (y/N): ' 'Would you like to test the connection to your UPS? (y/N): ',
); );
if (testConnection.toLowerCase() === 'y') { if (testConnection.toLowerCase() === 'y') {
logger.log('\nTesting connection to UPS...'); logger.log('\nTesting connection to UPS...');
@@ -985,7 +1001,9 @@ export class UpsHandler {
logger.logBoxLine(' sudo systemctl restart nupst.service'); logger.logBoxLine(' sudo systemctl restart nupst.service');
} }
} catch (error) { } catch (error) {
logger.logBoxLine(`Error restarting service: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxLine(
`Error restarting service: ${error instanceof Error ? error.message : String(error)}`,
);
logger.logBoxLine('You may need to restart the service manually:'); logger.logBoxLine('You may need to restart the service manually:');
logger.logBoxLine(' sudo systemctl restart nupst.service'); logger.logBoxLine(' sudo systemctl restart nupst.service');
} }

View File

@@ -1,8 +1,8 @@
import process from 'node:process'; import process from 'node:process';
import * as fs from "node:fs"; import * as fs from 'node:fs';
import * as path from "node:path"; import * as path from 'node:path';
import { exec, execFile } from "node:child_process"; import { exec, execFile } from 'node:child_process';
import { promisify } from "node:util"; import { promisify } from 'node:util';
import { NupstSnmp } from './snmp/manager.ts'; import { NupstSnmp } from './snmp/manager.ts';
import type { ISnmpConfig } from './snmp/types.ts'; import type { ISnmpConfig } from './snmp/types.ts';
import { logger } from './logger.ts'; import { logger } from './logger.ts';
@@ -109,14 +109,14 @@ export class NupstDaemon {
privProtocol: 'AES', privProtocol: 'AES',
privKey: '', privKey: '',
// UPS model for OID selection // UPS model for OID selection
upsModel: 'cyberpower' upsModel: 'cyberpower',
}, },
thresholds: { thresholds: {
battery: 60, // Shutdown when battery below 60% battery: 60, // Shutdown when battery below 60%
runtime: 20, // Shutdown when runtime below 20 minutes runtime: 20, // Shutdown when runtime below 20 minutes
}, },
groups: [] groups: [],
} },
], ],
groups: [], groups: [],
checkInterval: 30000, // Check every 30 seconds checkInterval: 30000, // Check every 30 seconds
@@ -163,11 +163,11 @@ export class NupstDaemon {
name: 'Default UPS', name: 'Default UPS',
snmp: parsedConfig.snmp, snmp: parsedConfig.snmp,
thresholds: parsedConfig.thresholds, thresholds: parsedConfig.thresholds,
groups: [] groups: [],
} },
], ],
groups: [], groups: [],
checkInterval: parsedConfig.checkInterval checkInterval: parsedConfig.checkInterval,
}; };
logger.log('Legacy configuration format detected. Converting to multi-UPS format.'); logger.log('Legacy configuration format detected. Converting to multi-UPS format.');
@@ -180,11 +180,15 @@ export class NupstDaemon {
return this.config; return this.config;
} catch (error) { } catch (error) {
if (error instanceof Error && error.message && error.message.includes('No configuration found')) { if (
error instanceof Error && error.message && error.message.includes('No configuration found')
) {
throw error; // Re-throw the no configuration error throw error; // Re-throw the no configuration error
} }
this.logConfigError(`Error loading configuration: ${error instanceof Error ? error.message : String(error)}`); this.logConfigError(
`Error loading configuration: ${error instanceof Error ? error.message : String(error)}`,
);
throw new Error('Failed to load configuration'); throw new Error('Failed to load configuration');
} }
} }
@@ -215,7 +219,7 @@ export class NupstDaemon {
private logConfigError(message: string): void { private logConfigError(message: string): void {
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('└───────────────────────────────────────────┘');
} }
@@ -273,7 +277,9 @@ export class NupstDaemon {
await this.monitor(); await this.monitor();
} catch (error) { } catch (error) {
this.isRunning = false; this.isRunning = false;
logger.error(`Daemon failed to start: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Daemon failed to start: ${error instanceof Error ? error.message : String(error)}`,
);
process.exit(1); // Exit with error process.exit(1); // Exit with error
} }
} }
@@ -293,7 +299,7 @@ export class NupstDaemon {
batteryCapacity: 100, batteryCapacity: 100,
batteryRuntime: 999, // High value as default batteryRuntime: 999, // High value as default
lastStatusChange: Date.now(), lastStatusChange: Date.now(),
lastCheckTime: 0 lastCheckTime: 0,
}); });
} }
@@ -374,7 +380,9 @@ export class NupstDaemon {
// Wait before next check // Wait before next check
await this.sleep(this.config.checkInterval); await this.sleep(this.config.checkInterval);
} catch (error) { } catch (error) {
logger.error(`Error during UPS monitoring: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Error during UPS monitoring: ${error instanceof Error ? error.message : String(error)}`,
);
await this.sleep(this.config.checkInterval); await this.sleep(this.config.checkInterval);
} }
} }
@@ -398,7 +406,7 @@ export class NupstDaemon {
batteryCapacity: 100, batteryCapacity: 100,
batteryRuntime: 999, batteryRuntime: 999,
lastStatusChange: Date.now(), lastStatusChange: Date.now(),
lastCheckTime: 0 lastCheckTime: 0,
}); });
} }
@@ -417,7 +425,7 @@ export class NupstDaemon {
batteryCapacity: status.batteryCapacity, batteryCapacity: status.batteryCapacity,
batteryRuntime: status.batteryRuntime, batteryRuntime: status.batteryRuntime,
lastCheckTime: currentTime, lastCheckTime: currentTime,
lastStatusChange: currentStatus?.lastStatusChange || currentTime lastStatusChange: currentStatus?.lastStatusChange || currentTime,
}; };
// Check if power status changed // Check if power status changed
@@ -432,7 +440,11 @@ export class NupstDaemon {
// Update the status in the map // Update the status in the map
this.upsStatus.set(ups.id, updatedStatus); this.upsStatus.set(ups.id, updatedStatus);
} catch (error) { } catch (error) {
logger.error(`Error checking UPS ${ups.name} (${ups.id}): ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Error checking UPS ${ups.name} (${ups.id}): ${
error instanceof Error ? error.message : String(error)
}`,
);
} }
} }
} }
@@ -450,7 +462,9 @@ export class NupstDaemon {
for (const [id, status] of this.upsStatus.entries()) { for (const [id, status] of this.upsStatus.entries()) {
logger.logBoxLine(`UPS: ${status.name} (${id})`); logger.logBoxLine(`UPS: ${status.name} (${id})`);
logger.logBoxLine(` Power Status: ${status.powerStatus}`); logger.logBoxLine(` Power Status: ${status.powerStatus}`);
logger.logBoxLine(` Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`); logger.logBoxLine(
` Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`,
);
logger.logBoxLine(''); logger.logBoxLine('');
} }
@@ -466,7 +480,7 @@ export class NupstDaemon {
for (const [id, status] of this.upsStatus.entries()) { for (const [id, status] of this.upsStatus.entries()) {
if (status.powerStatus === 'onBattery') { if (status.powerStatus === 'onBattery') {
// Find the UPS config // Find the UPS config
const ups = this.config.upsDevices.find(u => u.id === id); const ups = this.config.upsDevices.find((u) => u.id === id);
if (ups) { if (ups) {
await this.evaluateUpsShutdownCondition(ups, status); await this.evaluateUpsShutdownCondition(ups, status);
} }
@@ -478,7 +492,7 @@ export class NupstDaemon {
// Evaluate each group // Evaluate each group
for (const group of this.config.groups) { for (const group of this.config.groups) {
// Find all UPS devices in this group // Find all UPS devices in this group
const upsDevicesInGroup = this.config.upsDevices.filter(ups => const upsDevicesInGroup = this.config.upsDevices.filter((ups) =>
ups.groups && ups.groups.includes(group.id) ups.groups && ups.groups.includes(group.id)
); );
@@ -501,7 +515,10 @@ export class NupstDaemon {
* Evaluate a redundant group for shutdown conditions * Evaluate a redundant group for shutdown conditions
* In redundant mode, we only shut down if ALL UPS devices are in critical condition * In redundant mode, we only shut down if ALL UPS devices are in critical condition
*/ */
private async evaluateRedundantGroup(group: IGroupConfig, upsDevices: IUpsConfig[]): Promise<void> { private async evaluateRedundantGroup(
group: IGroupConfig,
upsDevices: IUpsConfig[],
): Promise<void> {
// Count UPS devices on battery and in critical condition // Count UPS devices on battery and in critical condition
let upsOnBattery = 0; let upsOnBattery = 0;
let upsInCriticalCondition = 0; let upsInCriticalCondition = 0;
@@ -514,8 +531,10 @@ export class NupstDaemon {
upsOnBattery++; upsOnBattery++;
// Check if this UPS is in critical condition // Check if this UPS is in critical condition
if (status.batteryCapacity < ups.thresholds.battery || if (
status.batteryRuntime < ups.thresholds.runtime) { status.batteryCapacity < ups.thresholds.battery ||
status.batteryRuntime < ups.thresholds.runtime
) {
upsInCriticalCondition++; upsInCriticalCondition++;
} }
} }
@@ -531,7 +550,9 @@ export class NupstDaemon {
logger.logBoxLine(`All ${allUpsCount} UPS devices in critical condition`); logger.logBoxLine(`All ${allUpsCount} UPS devices in critical condition`);
logger.logBoxEnd(); logger.logBoxEnd();
await this.initiateShutdown(`All UPS devices in redundant group "${group.name}" in critical condition`); await this.initiateShutdown(
`All UPS devices in redundant group "${group.name}" in critical condition`,
);
} }
} }
@@ -539,23 +560,34 @@ export class NupstDaemon {
* Evaluate a non-redundant group for shutdown conditions * Evaluate a non-redundant group for shutdown conditions
* In non-redundant mode, we shut down if ANY UPS device is in critical condition * In non-redundant mode, we shut down if ANY UPS device is in critical condition
*/ */
private async evaluateNonRedundantGroup(group: IGroupConfig, upsDevices: IUpsConfig[]): Promise<void> { private async evaluateNonRedundantGroup(
group: IGroupConfig,
upsDevices: IUpsConfig[],
): Promise<void> {
for (const ups of upsDevices) { for (const ups of upsDevices) {
const status = this.upsStatus.get(ups.id); const status = this.upsStatus.get(ups.id);
if (!status) continue; if (!status) continue;
if (status.powerStatus === 'onBattery') { if (status.powerStatus === 'onBattery') {
// Check if this UPS is in critical condition // Check if this UPS is in critical condition
if (status.batteryCapacity < ups.thresholds.battery || if (
status.batteryRuntime < ups.thresholds.runtime) { status.batteryCapacity < ups.thresholds.battery ||
status.batteryRuntime < ups.thresholds.runtime
) {
logger.logBoxTitle(`Group Shutdown Required: ${group.name}`, 50); logger.logBoxTitle(`Group Shutdown Required: ${group.name}`, 50);
logger.logBoxLine(`Mode: Non-Redundant`); logger.logBoxLine(`Mode: Non-Redundant`);
logger.logBoxLine(`UPS ${ups.name} in critical condition`); logger.logBoxLine(`UPS ${ups.name} in critical condition`);
logger.logBoxLine(`Battery: ${status.batteryCapacity}% (threshold: ${ups.thresholds.battery}%)`); logger.logBoxLine(
logger.logBoxLine(`Runtime: ${status.batteryRuntime} min (threshold: ${ups.thresholds.runtime} min)`); `Battery: ${status.batteryCapacity}% (threshold: ${ups.thresholds.battery}%)`,
);
logger.logBoxLine(
`Runtime: ${status.batteryRuntime} min (threshold: ${ups.thresholds.runtime} min)`,
);
logger.logBoxEnd(); logger.logBoxEnd();
await this.initiateShutdown(`UPS "${ups.name}" in non-redundant group "${group.name}" in critical condition`); await this.initiateShutdown(
`UPS "${ups.name}" in non-redundant group "${group.name}" in critical condition`,
);
return; // Exit after initiating shutdown return; // Exit after initiating shutdown
} }
} }
@@ -572,11 +604,17 @@ export class NupstDaemon {
} }
// Check threshold conditions // Check threshold conditions
if (status.batteryCapacity < ups.thresholds.battery || if (
status.batteryRuntime < ups.thresholds.runtime) { status.batteryCapacity < ups.thresholds.battery ||
status.batteryRuntime < ups.thresholds.runtime
) {
logger.logBoxTitle(`UPS Shutdown Required: ${ups.name}`, 50); logger.logBoxTitle(`UPS Shutdown Required: ${ups.name}`, 50);
logger.logBoxLine(`Battery: ${status.batteryCapacity}% (threshold: ${ups.thresholds.battery}%)`); logger.logBoxLine(
logger.logBoxLine(`Runtime: ${status.batteryRuntime} min (threshold: ${ups.thresholds.runtime} min)`); `Battery: ${status.batteryCapacity}% (threshold: ${ups.thresholds.battery}%)`,
);
logger.logBoxLine(
`Runtime: ${status.batteryRuntime} min (threshold: ${ups.thresholds.runtime} min)`,
);
logger.logBoxEnd(); logger.logBoxEnd();
await this.initiateShutdown(`UPS "${ups.name}" battery or runtime below threshold`); await this.initiateShutdown(`UPS "${ups.name}" battery or runtime below threshold`);
@@ -599,7 +637,7 @@ export class NupstDaemon {
'/sbin/shutdown', '/sbin/shutdown',
'/usr/sbin/shutdown', '/usr/sbin/shutdown',
'/bin/shutdown', '/bin/shutdown',
'/usr/bin/shutdown' '/usr/bin/shutdown',
]; ];
let shutdownCmd = ''; let shutdownCmd = '';
@@ -617,11 +655,13 @@ export class NupstDaemon {
if (shutdownCmd) { if (shutdownCmd) {
// Execute shutdown command with delay to allow for VM graceful shutdown // Execute shutdown command with delay to allow for VM graceful shutdown
logger.log(`Executing: ${shutdownCmd} -h +${shutdownDelayMinutes} "UPS battery critical..."`); logger.log(
`Executing: ${shutdownCmd} -h +${shutdownDelayMinutes} "UPS battery critical..."`,
);
const { stdout } = await execFileAsync(shutdownCmd, [ const { stdout } = await execFileAsync(shutdownCmd, [
'-h', '-h',
`+${shutdownDelayMinutes}`, `+${shutdownDelayMinutes}`,
`UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes` `UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes`,
]); ]);
logger.log(`Shutdown initiated: ${stdout}`); logger.log(`Shutdown initiated: ${stdout}`);
logger.log(`Allowing ${shutdownDelayMinutes} minutes for VMs to shut down safely`); logger.log(`Allowing ${shutdownDelayMinutes} minutes for VMs to shut down safely`);
@@ -629,12 +669,17 @@ export class NupstDaemon {
// Try using the PATH to find shutdown // Try using the PATH to find shutdown
try { try {
logger.log('Shutdown command not found in common paths, trying via PATH...'); 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"`, { const { stdout } = await execAsync(
env: process.env // Pass the current environment `shutdown -h +${shutdownDelayMinutes} "UPS battery critical, shutting down in ${shutdownDelayMinutes} minutes"`,
}); {
env: process.env, // Pass the current environment
},
);
logger.log(`Shutdown initiated: ${stdout}`); logger.log(`Shutdown initiated: ${stdout}`);
} catch (e) { } catch (e) {
throw new Error(`Shutdown command not found: ${e instanceof Error ? e.message : String(e)}`); throw new Error(
`Shutdown command not found: ${e instanceof Error ? e.message : String(e)}`,
);
} }
} }
@@ -649,7 +694,7 @@ export class NupstDaemon {
{ cmd: 'poweroff', args: ['--force'] }, { cmd: 'poweroff', args: ['--force'] },
{ cmd: 'halt', args: ['-p'] }, { cmd: 'halt', args: ['-p'] },
{ cmd: 'systemctl', args: ['poweroff'] }, { cmd: 'systemctl', args: ['poweroff'] },
{ cmd: 'reboot', args: ['-p'] } // Some systems allow reboot -p for power off { cmd: 'reboot', args: ['-p'] }, // Some systems allow reboot -p for power off
]; ];
for (const alt of alternatives) { for (const alt of alternatives) {
@@ -659,7 +704,7 @@ export class NupstDaemon {
`/sbin/${alt.cmd}`, `/sbin/${alt.cmd}`,
`/usr/sbin/${alt.cmd}`, `/usr/sbin/${alt.cmd}`,
`/bin/${alt.cmd}`, `/bin/${alt.cmd}`,
`/usr/bin/${alt.cmd}` `/usr/bin/${alt.cmd}`,
]; ];
let cmdPath = ''; let cmdPath = '';
@@ -678,7 +723,7 @@ export class NupstDaemon {
// Try using PATH environment // Try using PATH environment
logger.log(`Trying alternative via PATH: ${alt.cmd} ${alt.args.join(' ')}`); logger.log(`Trying alternative via PATH: ${alt.cmd} ${alt.args.join(' ')}`);
await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, { await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, {
env: process.env // Pass the current environment env: process.env, // Pass the current environment
}); });
return; // Exit if successful return; // Exit if successful
} }
@@ -702,7 +747,9 @@ export class NupstDaemon {
const MAX_MONITORING_TIME = 5 * 60 * 1000; // Max 5 minutes of monitoring const MAX_MONITORING_TIME = 5 * 60 * 1000; // Max 5 minutes of monitoring
const startTime = Date.now(); const startTime = Date.now();
logger.log(`Emergency shutdown threshold: ${EMERGENCY_RUNTIME_THRESHOLD} minutes remaining battery runtime`); logger.log(
`Emergency shutdown threshold: ${EMERGENCY_RUNTIME_THRESHOLD} minutes remaining battery runtime`,
);
// Continue monitoring until max monitoring time is reached // Continue monitoring until max monitoring time is reached
while (Date.now() - startTime < MAX_MONITORING_TIME) { while (Date.now() - startTime < MAX_MONITORING_TIME) {
@@ -714,12 +761,16 @@ export class NupstDaemon {
try { try {
const status = await this.snmp.getUpsStatus(ups.snmp); const status = await this.snmp.getUpsStatus(ups.snmp);
logger.log(`UPS ${ups.name}: Battery ${status.batteryCapacity}%, Runtime: ${status.batteryRuntime} minutes`); logger.log(
`UPS ${ups.name}: Battery ${status.batteryCapacity}%, Runtime: ${status.batteryRuntime} minutes`,
);
// If any UPS battery runtime gets critically low, force immediate shutdown // If any UPS battery runtime gets critically low, force immediate shutdown
if (status.batteryRuntime < EMERGENCY_RUNTIME_THRESHOLD) { if (status.batteryRuntime < EMERGENCY_RUNTIME_THRESHOLD) {
logger.logBoxTitle('EMERGENCY SHUTDOWN', 50); logger.logBoxTitle('EMERGENCY SHUTDOWN', 50);
logger.logBoxLine(`UPS ${ups.name} runtime critically low: ${status.batteryRuntime} minutes`); logger.logBoxLine(
`UPS ${ups.name} runtime critically low: ${status.batteryRuntime} minutes`,
);
logger.logBoxLine('Forcing immediate shutdown!'); logger.logBoxLine('Forcing immediate shutdown!');
logger.logBoxEnd(); logger.logBoxEnd();
@@ -728,14 +779,22 @@ export class NupstDaemon {
return; return;
} }
} catch (upsError) { } catch (upsError) {
logger.error(`Error checking UPS ${ups.name} during shutdown: ${upsError instanceof Error ? upsError.message : String(upsError)}`); logger.error(
`Error checking UPS ${ups.name} during shutdown: ${
upsError instanceof Error ? upsError.message : String(upsError)
}`,
);
} }
} }
// Wait before checking again // Wait before checking again
await this.sleep(CHECK_INTERVAL); await this.sleep(CHECK_INTERVAL);
} catch (error) { } catch (error) {
logger.error(`Error monitoring UPS during shutdown: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Error monitoring UPS during shutdown: ${
error instanceof Error ? error.message : String(error)
}`,
);
await this.sleep(CHECK_INTERVAL); await this.sleep(CHECK_INTERVAL);
} }
} }
@@ -753,7 +812,7 @@ export class NupstDaemon {
'/sbin/shutdown', '/sbin/shutdown',
'/usr/sbin/shutdown', '/usr/sbin/shutdown',
'/bin/shutdown', '/bin/shutdown',
'/usr/bin/shutdown' '/usr/bin/shutdown',
]; ];
let shutdownCmd = ''; let shutdownCmd = '';
@@ -767,13 +826,20 @@ export class NupstDaemon {
if (shutdownCmd) { if (shutdownCmd) {
logger.log(`Executing emergency shutdown: ${shutdownCmd} -h now`); logger.log(`Executing emergency shutdown: ${shutdownCmd} -h now`);
await execFileAsync(shutdownCmd, ['-h', 'now', 'EMERGENCY: UPS battery critically low, shutting down NOW']); await execFileAsync(shutdownCmd, [
'-h',
'now',
'EMERGENCY: UPS battery critically low, shutting down NOW',
]);
} else { } else {
// Try using the PATH to find shutdown // Try using the PATH to find shutdown
logger.log('Shutdown command not found in common paths, trying via PATH...'); logger.log('Shutdown command not found in common paths, trying via PATH...');
await execAsync('shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"', { await execAsync(
env: process.env // Pass the current environment 'shutdown -h now "EMERGENCY: UPS battery critically low, shutting down NOW"',
}); {
env: process.env, // Pass the current environment
},
);
} }
} catch (error) { } catch (error) {
logger.error('Emergency shutdown failed, trying alternative methods...'); logger.error('Emergency shutdown failed, trying alternative methods...');
@@ -782,7 +848,7 @@ export class NupstDaemon {
const alternatives = [ const alternatives = [
{ cmd: 'poweroff', args: ['--force'] }, { cmd: 'poweroff', args: ['--force'] },
{ cmd: 'halt', args: ['-p'] }, { cmd: 'halt', args: ['-p'] },
{ cmd: 'systemctl', args: ['poweroff'] } { cmd: 'systemctl', args: ['poweroff'] },
]; ];
for (const alt of alternatives) { for (const alt of alternatives) {
@@ -792,7 +858,7 @@ export class NupstDaemon {
`/sbin/${alt.cmd}`, `/sbin/${alt.cmd}`,
`/usr/sbin/${alt.cmd}`, `/usr/sbin/${alt.cmd}`,
`/bin/${alt.cmd}`, `/bin/${alt.cmd}`,
`/usr/bin/${alt.cmd}` `/usr/bin/${alt.cmd}`,
]; ];
let cmdPath = ''; let cmdPath = '';
@@ -811,7 +877,7 @@ export class NupstDaemon {
// Try using PATH // Try using PATH
logger.log(`Emergency: trying ${alt.cmd} via PATH`); logger.log(`Emergency: trying ${alt.cmd} via PATH`);
await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, { await execAsync(`${alt.cmd} ${alt.args.join(' ')}`, {
env: process.env env: process.env,
}); });
return; // Exit if successful return; // Exit if successful
} }
@@ -828,6 +894,6 @@ export class NupstDaemon {
* Sleep for the specified milliseconds * Sleep for the specified milliseconds
*/ */
private sleep(ms: number): Promise<void> { private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
} }

View File

@@ -2,6 +2,7 @@
import { NupstCli } from './cli.ts'; import { NupstCli } from './cli.ts';
import { logger } from './logger.ts'; import { logger } from './logger.ts';
import process from 'node:process';
/** /**
* Main entry point for NUPST * Main entry point for NUPST
@@ -13,7 +14,7 @@ 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) => {
logger.error(`Error: ${error}`); logger.error(`Error: ${error}`);
process.exit(1); process.exit(1);
}); });

View File

@@ -6,7 +6,7 @@ import { logger } from './logger.ts';
import { UpsHandler } from './cli/ups-handler.ts'; import { UpsHandler } from './cli/ups-handler.ts';
import { GroupHandler } from './cli/group-handler.ts'; import { GroupHandler } from './cli/group-handler.ts';
import { ServiceHandler } from './cli/service-handler.ts'; import { ServiceHandler } from './cli/service-handler.ts';
import * as https from "node:https"; import * as https from 'node:https';
/** /**
* Main Nupst class that coordinates all components * Main Nupst class that coordinates all components
@@ -103,7 +103,9 @@ export class Nupst {
return this.updateAvailable; return this.updateAvailable;
} catch (error) { } catch (error) {
logger.error(`Error checking for updates: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Error checking for updates: ${error instanceof Error ? error.message : String(error)}`,
);
return false; return false;
} }
} }
@@ -113,14 +115,14 @@ export class Nupst {
* @returns Object with update status information * @returns Object with update status information
*/ */
public getUpdateStatus(): { public getUpdateStatus(): {
currentVersion: string, currentVersion: string;
latestVersion: string, latestVersion: string;
updateAvailable: boolean updateAvailable: boolean;
} { } {
return { return {
currentVersion: this.getVersion(), currentVersion: this.getVersion(),
latestVersion: this.latestVersion || this.getVersion(), latestVersion: this.latestVersion || this.getVersion(),
updateAvailable: this.updateAvailable updateAvailable: this.updateAvailable,
}; };
} }
@@ -136,8 +138,8 @@ export class Nupst {
method: 'GET', method: 'GET',
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'User-Agent': `nupst/${this.getVersion()}` 'User-Agent': `nupst/${this.getVersion()}`,
} },
}; };
const req = https.request(options, (res) => { const req = https.request(options, (res) => {
@@ -176,8 +178,8 @@ export class Nupst {
* @returns -1 if versionA < versionB, 0 if equal, 1 if versionA > versionB * @returns -1 if versionA < versionB, 0 if equal, 1 if versionA > versionB
*/ */
private compareVersions(versionA: string, versionB: string): number { private compareVersions(versionA: string, versionB: string): number {
const partsA = versionA.split('.').map(part => parseInt(part, 10)); const partsA = versionA.split('.').map((part) => parseInt(part, 10));
const partsB = versionB.split('.').map(part => parseInt(part, 10)); const partsB = versionB.split('.').map((part) => parseInt(part, 10));
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const partA = i < partsA.length ? partsA[i] : 0; const partA = i < partsA.length ? partsA[i] : 0;
@@ -208,7 +210,7 @@ export class Nupst {
logger.logBoxLine('Checking for updates...'); logger.logBoxLine('Checking for updates...');
// We can't end the box yet since we're in an async operation // 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) {
logger.logBoxLine(`Update Available: ${this.latestVersion}`); logger.logBoxLine(`Update Available: ${this.latestVersion}`);
logger.logBoxLine('Run "sudo nupst update" to update'); logger.logBoxLine('Run "sudo nupst update" to update');

View File

@@ -4,7 +4,7 @@
*/ */
// Re-export all public types // Re-export all public types
export type { IUpsStatus, IOidSet, TUpsModel, ISnmpConfig } from './types.ts'; export type { IOidSet, ISnmpConfig, IUpsStatus, TUpsModel } from './types.ts';
// Re-export the SNMP manager class // Re-export the SNMP manager class
export { NupstSnmp } from './manager.ts'; export { NupstSnmp } from './manager.ts';

View File

@@ -1,6 +1,6 @@
import * as snmp from "npm:net-snmp@3.20.0"; import * as snmp from 'npm:net-snmp@3.20.0';
import { Buffer } from "node:buffer"; import { Buffer } from 'node:buffer';
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.ts'; import type { IOidSet, ISnmpConfig, IUpsStatus, TUpsModel } from './types.ts';
import { UpsOidSets } from './oid-sets.ts'; import { UpsOidSets } from './oid-sets.ts';
/** /**
@@ -91,11 +91,13 @@ export class NupstSnmp {
public async snmpGet( public async snmpGet(
oid: string, oid: string,
config = this.DEFAULT_CONFIG, config = this.DEFAULT_CONFIG,
retryCount = 0 retryCount = 0,
): Promise<any> { ): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.debug) { if (this.debug) {
console.log(`Sending SNMP v${config.version} GET request for OID ${oid} to ${config.host}:${config.port}`); console.log(
`Sending SNMP v${config.version} GET request for OID ${oid} to ${config.host}:${config.port}`,
);
console.log('Using community:', config.community); console.log('Using community:', config.community);
} }
@@ -106,7 +108,7 @@ export class NupstSnmp {
timeout: config.timeout, timeout: config.timeout,
transport: 'udp4', transport: 'udp4',
idBitsSize: 32, idBitsSize: 32,
context: config.context || '' context: config.context || '',
}; };
// Set version based on config // Set version based on config
@@ -128,7 +130,7 @@ export class NupstSnmp {
// Create the user object with required structure for net-snmp // Create the user object with required structure for net-snmp
const user: any = { const user: any = {
name: config.username || '' name: config.username || '',
}; };
// Set security level // Set security level
@@ -191,11 +193,13 @@ export class NupstSnmp {
if (this.debug) { if (this.debug) {
console.log('SNMPv3 user configuration:', { console.log('SNMPv3 user configuration:', {
name: user.name, name: user.name,
level: Object.keys(snmp.SecurityLevel).find(key => snmp.SecurityLevel[key] === user.level), level: Object.keys(snmp.SecurityLevel).find((key) =>
snmp.SecurityLevel[key] === user.level
),
authProtocol: user.authProtocol ? 'Set' : 'Not Set', authProtocol: user.authProtocol ? 'Set' : 'Not Set',
authKey: user.authKey ? 'Set' : 'Not Set', authKey: user.authKey ? 'Set' : 'Not Set',
privProtocol: user.privProtocol ? 'Set' : 'Not Set', privProtocol: user.privProtocol ? 'Set' : 'Not Set',
privKey: user.privKey ? 'Set' : 'Not Set' privKey: user.privKey ? 'Set' : 'Not Set',
}); });
} }
@@ -230,9 +234,11 @@ export class NupstSnmp {
} }
// Check for SNMP errors in the response // Check for SNMP errors in the response
if (varbinds[0].type === snmp.ObjectType.NoSuchObject || if (
varbinds[0].type === snmp.ObjectType.NoSuchInstance || varbinds[0].type === snmp.ObjectType.NoSuchObject ||
varbinds[0].type === snmp.ObjectType.EndOfMibView) { varbinds[0].type === snmp.ObjectType.NoSuchInstance ||
varbinds[0].type === snmp.ObjectType.EndOfMibView
) {
if (this.debug) { if (this.debug) {
console.error('SNMP error:', snmp.ObjectType[varbinds[0].type]); console.error('SNMP error:', snmp.ObjectType[varbinds[0].type]);
} }
@@ -259,7 +265,7 @@ export class NupstSnmp {
console.log('SNMP response:', { console.log('SNMP response:', {
oid: varbinds[0].oid, oid: varbinds[0].oid,
type: varbinds[0].type, type: varbinds[0].type,
value: value value: value,
}); });
} }
@@ -302,9 +308,21 @@ export class NupstSnmp {
} }
// Get all values with independent retry logic // Get all values with independent retry logic
const powerStatusValue = await this.getSNMPValueWithRetry(this.activeOIDs.POWER_STATUS, 'power status', config); const powerStatusValue = await this.getSNMPValueWithRetry(
const batteryCapacity = await this.getSNMPValueWithRetry(this.activeOIDs.BATTERY_CAPACITY, 'battery capacity', config) || 0; this.activeOIDs.POWER_STATUS,
const batteryRuntime = await this.getSNMPValueWithRetry(this.activeOIDs.BATTERY_RUNTIME, 'battery runtime', config) || 0; 'power status',
config,
);
const batteryCapacity = await this.getSNMPValueWithRetry(
this.activeOIDs.BATTERY_CAPACITY,
'battery capacity',
config,
) || 0;
const batteryRuntime = await this.getSNMPValueWithRetry(
this.activeOIDs.BATTERY_RUNTIME,
'battery runtime',
config,
) || 0;
// Determine power status - handle different values for different UPS models // Determine power status - handle different values for different UPS models
const powerStatus = this.determinePowerStatus(config.upsModel, powerStatusValue); const powerStatus = this.determinePowerStatus(config.upsModel, powerStatusValue);
@@ -336,10 +354,15 @@ export class NupstSnmp {
} catch (error) { } catch (error) {
if (this.debug) { if (this.debug) {
console.error('---------------------------------------'); console.error('---------------------------------------');
console.error('Error getting UPS status:', error instanceof Error ? error.message : String(error)); console.error(
'Error getting UPS status:',
error instanceof Error ? error.message : String(error),
);
console.error('---------------------------------------'); console.error('---------------------------------------');
} }
throw new Error(`Failed to get UPS status: ${error instanceof Error ? error.message : String(error)}`); throw new Error(
`Failed to get UPS status: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
@@ -353,7 +376,7 @@ export class NupstSnmp {
private async getSNMPValueWithRetry( private async getSNMPValueWithRetry(
oid: string, oid: string,
description: string, description: string,
config: ISnmpConfig config: ISnmpConfig,
): Promise<any> { ): Promise<any> {
if (oid === '') { if (oid === '') {
if (this.debug) { if (this.debug) {
@@ -374,7 +397,10 @@ export class NupstSnmp {
return value; return value;
} catch (error) { } catch (error) {
if (this.debug) { if (this.debug) {
console.error(`Error getting ${description}:`, error instanceof Error ? error.message : String(error)); console.error(
`Error getting ${description}:`,
error instanceof Error ? error.message : String(error),
);
} }
// If we're using SNMPv3, try with different security levels // If we're using SNMPv3, try with different security levels
@@ -405,7 +431,7 @@ export class NupstSnmp {
private async tryFallbackSecurityLevels( private async tryFallbackSecurityLevels(
oid: string, oid: string,
description: string, description: string,
config: ISnmpConfig config: ISnmpConfig,
): Promise<any> { ): Promise<any> {
if (this.debug) { if (this.debug) {
console.log(`Retrying ${description} with fallback security level...`); console.log(`Retrying ${description} with fallback security level...`);
@@ -425,7 +451,10 @@ export class NupstSnmp {
return value; return value;
} catch (retryError) { } catch (retryError) {
if (this.debug) { if (this.debug) {
console.error(`Retry failed for ${description}:`, retryError instanceof Error ? retryError.message : String(retryError)); console.error(
`Retry failed for ${description}:`,
retryError instanceof Error ? retryError.message : String(retryError),
);
} }
} }
} }
@@ -444,7 +473,10 @@ export class NupstSnmp {
return value; return value;
} catch (retryError) { } catch (retryError) {
if (this.debug) { if (this.debug) {
console.error(`Retry failed for ${description}:`, retryError instanceof Error ? retryError.message : String(retryError)); console.error(
`Retry failed for ${description}:`,
retryError instanceof Error ? retryError.message : String(retryError),
);
} }
} }
} }
@@ -462,14 +494,16 @@ export class NupstSnmp {
private async tryStandardOids( private async tryStandardOids(
oid: string, oid: string,
description: string, description: string,
config: ISnmpConfig config: ISnmpConfig,
): Promise<any> { ): Promise<any> {
try { try {
// Try RFC 1628 standard UPS MIB OIDs // Try RFC 1628 standard UPS MIB OIDs
const standardOIDs = UpsOidSets.getStandardOids(); const standardOIDs = UpsOidSets.getStandardOids();
if (this.debug) { if (this.debug) {
console.log(`Trying standard RFC 1628 OID for ${description}: ${standardOIDs[description]}`); console.log(
`Trying standard RFC 1628 OID for ${description}: ${standardOIDs[description]}`,
);
} }
const standardValue = await this.snmpGet(standardOIDs[description], config); const standardValue = await this.snmpGet(standardOIDs[description], config);
@@ -479,7 +513,10 @@ export class NupstSnmp {
return standardValue; return standardValue;
} catch (stdError) { } catch (stdError) {
if (this.debug) { if (this.debug) {
console.error(`Standard OID retry failed for ${description}:`, stdError instanceof Error ? stdError.message : String(stdError)); console.error(
`Standard OID retry failed for ${description}:`,
stdError instanceof Error ? stdError.message : String(stdError),
);
} }
} }
@@ -494,7 +531,7 @@ export class NupstSnmp {
*/ */
private determinePowerStatus( private determinePowerStatus(
upsModel: TUpsModel | undefined, upsModel: TUpsModel | undefined,
powerStatusValue: number powerStatusValue: number,
): 'online' | 'onBattery' | 'unknown' { ): 'online' | 'onBattery' | 'unknown' {
if (upsModel === 'cyberpower') { if (upsModel === 'cyberpower') {
// CyberPower RMCARD205: upsBaseOutputStatus values // CyberPower RMCARD205: upsBaseOutputStatus values
@@ -540,7 +577,7 @@ export class NupstSnmp {
*/ */
private processRuntimeValue( private processRuntimeValue(
upsModel: TUpsModel | undefined, upsModel: TUpsModel | undefined,
batteryRuntime: number batteryRuntime: number,
): number { ): number {
if (this.debug) { if (this.debug) {
console.log('Raw runtime value:', batteryRuntime); console.log('Raw runtime value:', batteryRuntime);
@@ -550,14 +587,18 @@ export class NupstSnmp {
// CyberPower: TimeTicks is in 1/100 seconds, convert to minutes // CyberPower: TimeTicks is in 1/100 seconds, convert to minutes
const minutes = Math.floor(batteryRuntime / 6000); // 6000 ticks = 1 minute const minutes = Math.floor(batteryRuntime / 6000); // 6000 ticks = 1 minute
if (this.debug) { if (this.debug) {
console.log(`Converting CyberPower runtime from ${batteryRuntime} ticks to ${minutes} minutes`); console.log(
`Converting CyberPower runtime from ${batteryRuntime} ticks to ${minutes} minutes`,
);
} }
return minutes; return minutes;
} else if (upsModel === 'eaton' && batteryRuntime > 0) { } else if (upsModel === 'eaton' && batteryRuntime > 0) {
// Eaton: Runtime is in seconds, convert to minutes // Eaton: Runtime is in seconds, convert to minutes
const minutes = Math.floor(batteryRuntime / 60); const minutes = Math.floor(batteryRuntime / 60);
if (this.debug) { if (this.debug) {
console.log(`Converting Eaton runtime from ${batteryRuntime} seconds to ${minutes} minutes`); console.log(
`Converting Eaton runtime from ${batteryRuntime} seconds to ${minutes} minutes`,
);
} }
return minutes; return minutes;
} else if (batteryRuntime > 10000) { } else if (batteryRuntime > 10000) {

View File

@@ -49,7 +49,7 @@ export class UpsOidSets {
POWER_STATUS: '', POWER_STATUS: '',
BATTERY_CAPACITY: '', BATTERY_CAPACITY: '',
BATTERY_RUNTIME: '', BATTERY_RUNTIME: '',
} },
}; };
/** /**
@@ -67,9 +67,9 @@ export class UpsOidSets {
*/ */
public static getStandardOids(): Record<string, string> { public static getStandardOids(): Record<string, string> {
return { return {
'power status': '1.3.6.1.2.1.33.1.4.1.0', // upsOutputSource 'power status': '1.3.6.1.2.1.33.1.4.1.0', // upsOutputSource
'battery capacity': '1.3.6.1.2.1.33.1.2.4.0', // upsEstimatedChargeRemaining 'battery capacity': '1.3.6.1.2.1.33.1.2.4.0', // upsEstimatedChargeRemaining
'battery runtime': '1.3.6.1.2.1.33.1.2.3.0' // upsEstimatedMinutesRemaining 'battery runtime': '1.3.6.1.2.1.33.1.2.3.0', // upsEstimatedMinutesRemaining
}; };
} }
} }

View File

@@ -2,7 +2,7 @@
* Type definitions for SNMP module * Type definitions for SNMP module
*/ */
import { Buffer } from "node:buffer"; import { Buffer } from 'node:buffer';
/** /**
* UPS status interface * UPS status interface

View File

@@ -1,6 +1,6 @@
import process from 'node:process'; import process from 'node:process';
import { promises as fs } from "node:fs"; import { promises as fs } from 'node:fs';
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process';
import { NupstDaemon } from './daemon.ts'; import { NupstDaemon } from './daemon.ts';
import { logger } from './logger.ts'; import { logger } from './logger.ts';
@@ -158,7 +158,9 @@ WantedBy=multi-user.target
await this.displayServiceStatus(); await this.displayServiceStatus();
await this.displayAllUpsStatus(); await this.displayAllUpsStatus();
} catch (error) { } catch (error) {
logger.error(`Failed to get status: ${error instanceof Error ? error.message : String(error)}`); logger.error(
`Failed to get status: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
@@ -172,7 +174,7 @@ WantedBy=multi-user.target
const boxWidth = 45; const boxWidth = 45;
logger.logBoxTitle('Service Status', boxWidth); logger.logBoxTitle('Service Status', boxWidth);
// Process each line of the status output // Process each line of the status output
serviceStatus.split('\n').forEach(line => { serviceStatus.split('\n').forEach((line) => {
logger.logBoxLine(line); logger.logBoxLine(line);
}); });
logger.logBoxEnd(); logger.logBoxEnd();
@@ -210,7 +212,7 @@ WantedBy=multi-user.target
name: 'Default UPS', name: 'Default UPS',
snmp: config.snmp, snmp: config.snmp,
thresholds: config.thresholds, thresholds: config.thresholds,
groups: [] groups: [],
}; };
await this.displaySingleUpsStatus(legacyUps, snmp); await this.displaySingleUpsStatus(legacyUps, snmp);
@@ -220,7 +222,9 @@ WantedBy=multi-user.target
} catch (error) { } catch (error) {
const boxWidth = 45; const boxWidth = 45;
logger.logBoxTitle('UPS Status', boxWidth); logger.logBoxTitle('UPS Status', boxWidth);
logger.logBoxLine(`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxLine(
`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`,
);
logger.logBoxEnd(); logger.logBoxEnd();
} }
} }
@@ -253,7 +257,7 @@ WantedBy=multi-user.target
// Create a test config with a short timeout // Create a test config with a short timeout
const testConfig = { const testConfig = {
...ups.snmp, ...ups.snmp,
timeout: Math.min(ups.snmp.timeout, 10000) // Use at most 10 seconds for status check timeout: Math.min(ups.snmp.timeout, 10000), // Use at most 10 seconds for status check
}; };
const status = await snmp.getUpsStatus(testConfig); const status = await snmp.getUpsStatus(testConfig);
@@ -266,17 +270,23 @@ WantedBy=multi-user.target
// Show threshold status // Show threshold status
logger.logBoxLine(''); logger.logBoxLine('');
logger.logBoxLine('Thresholds:'); logger.logBoxLine('Thresholds:');
logger.logBoxLine(` Battery: ${status.batteryCapacity}% / ${ups.thresholds.battery}% ${ logger.logBoxLine(
status.batteryCapacity < ups.thresholds.battery ? '⚠️' : '✓' ` Battery: ${status.batteryCapacity}% / ${ups.thresholds.battery}% ${
}`); status.batteryCapacity < ups.thresholds.battery ? '⚠️' : '✓'
logger.logBoxLine(` Runtime: ${status.batteryRuntime} min / ${ups.thresholds.runtime} min ${ }`,
status.batteryRuntime < ups.thresholds.runtime ? '⚠️' : '✓' );
}`); logger.logBoxLine(
` Runtime: ${status.batteryRuntime} min / ${ups.thresholds.runtime} min ${
status.batteryRuntime < ups.thresholds.runtime ? '⚠️' : '✓'
}`,
);
logger.logBoxEnd(); logger.logBoxEnd();
} catch (error) { } catch (error) {
logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth); logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth);
logger.logBoxLine(`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxLine(
`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`,
);
logger.logBoxEnd(); logger.logBoxEnd();
} }
} }