Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ba50da73c | |||
| 684319983d | |||
| 18bd9f6cda | |||
| f03c683d02 | |||
| f750299780 | |||
| ca1039408d | |||
| df3e0b9424 | |||
| c8e5960abd | |||
| 7304a62357 | |||
| a5a88e53ba | |||
| 73bc271c59 | |||
| 1e98181e71 | |||
| eb5a8185ae | |||
| ef3d3f3fa3 | |||
| 34e6e850ad |
183
.github/workflows/npm-publish.yml
vendored
Normal file
183
.github/workflows/npm-publish.yml
vendored
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
name: Publish to npm
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Version to publish (e.g., 5.0.6)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Checkout the repository
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Setup Deno
|
||||||
|
- name: Setup Deno
|
||||||
|
uses: denoland/setup-deno@v1
|
||||||
|
with:
|
||||||
|
deno-version: v1.x
|
||||||
|
|
||||||
|
# Setup Node.js for npm publishing
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18.x'
|
||||||
|
registry-url: 'https://registry.npmjs.org/'
|
||||||
|
|
||||||
|
# Compile binaries for all platforms
|
||||||
|
- name: Compile binaries
|
||||||
|
run: |
|
||||||
|
echo "Compiling binaries for all platforms..."
|
||||||
|
deno task compile
|
||||||
|
echo ""
|
||||||
|
echo "Binary sizes:"
|
||||||
|
ls -lh dist/binaries/
|
||||||
|
|
||||||
|
# Update version in package.json if triggered manually
|
||||||
|
- name: Update version in package.json
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |
|
||||||
|
VERSION=${{ github.event.inputs.version }}
|
||||||
|
echo "Updating package.json to version ${VERSION}"
|
||||||
|
npm version ${VERSION} --no-git-tag-version
|
||||||
|
|
||||||
|
# Extract version from tag if triggered by tag push
|
||||||
|
- name: Extract version from tag
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
run: |
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
|
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||||
|
echo "Extracted version: ${VERSION}"
|
||||||
|
|
||||||
|
# Ensure versions are synchronized
|
||||||
|
- name: Sync versions
|
||||||
|
run: |
|
||||||
|
if [ -n "${VERSION}" ]; then
|
||||||
|
echo "Syncing version ${VERSION} across files..."
|
||||||
|
|
||||||
|
# Update deno.json
|
||||||
|
sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" deno.json
|
||||||
|
|
||||||
|
# Update package.json
|
||||||
|
npm version ${VERSION} --no-git-tag-version --allow-same-version
|
||||||
|
|
||||||
|
echo "Updated versions:"
|
||||||
|
echo "deno.json: $(grep '"version"' deno.json)"
|
||||||
|
echo "package.json: $(grep '"version"' package.json | head -1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate SHA256 checksums for binaries
|
||||||
|
- name: Generate checksums
|
||||||
|
run: |
|
||||||
|
cd dist/binaries
|
||||||
|
sha256sum * > SHA256SUMS
|
||||||
|
echo "Checksums generated:"
|
||||||
|
cat SHA256SUMS
|
||||||
|
cd ../..
|
||||||
|
|
||||||
|
# Create npm package
|
||||||
|
- name: Create npm package
|
||||||
|
run: |
|
||||||
|
echo "Creating npm package..."
|
||||||
|
npm pack
|
||||||
|
echo ""
|
||||||
|
echo "Package created:"
|
||||||
|
ls -lh *.tgz
|
||||||
|
|
||||||
|
# Test package installation locally
|
||||||
|
- name: Test local installation
|
||||||
|
run: |
|
||||||
|
echo "Testing local package installation..."
|
||||||
|
PACKAGE_FILE=$(ls *.tgz)
|
||||||
|
npm install -g ${PACKAGE_FILE}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Testing nupst command:"
|
||||||
|
nupst --version || echo "Note: Binary execution may fail in CI environment"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Checking installed files:"
|
||||||
|
npm ls -g @serve.zone/nupst
|
||||||
|
|
||||||
|
# Publish to npm (only on tag push or manual trigger)
|
||||||
|
- name: Publish to npm
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
run: |
|
||||||
|
echo "Publishing to npm registry..."
|
||||||
|
npm publish --access public
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Successfully published @serve.zone/nupst to npm!"
|
||||||
|
echo ""
|
||||||
|
echo "Package info:"
|
||||||
|
npm view @serve.zone/nupst
|
||||||
|
|
||||||
|
# Create GitHub Release (only on tag push)
|
||||||
|
- name: Create GitHub Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
dist/binaries/nupst-*
|
||||||
|
dist/binaries/SHA256SUMS
|
||||||
|
*.tgz
|
||||||
|
generate_release_notes: true
|
||||||
|
body: |
|
||||||
|
## NUPST ${{ env.VERSION }}
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
#### Via npm (recommended)
|
||||||
|
```bash
|
||||||
|
npm install -g @serve.zone/nupst
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Direct download
|
||||||
|
Download the appropriate binary for your platform from the assets below.
|
||||||
|
|
||||||
|
### Platform Support
|
||||||
|
- Linux x64 / ARM64
|
||||||
|
- macOS x64 / ARM64 (Apple Silicon)
|
||||||
|
- Windows x64
|
||||||
|
|
||||||
|
### Checksums
|
||||||
|
SHA256 checksums are available in `SHA256SUMS` file.
|
||||||
|
|
||||||
|
# Verify the published package
|
||||||
|
verify:
|
||||||
|
needs: build-and-publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18.x'
|
||||||
|
|
||||||
|
- name: Wait for npm propagation
|
||||||
|
run: sleep 30
|
||||||
|
|
||||||
|
- name: Verify npm package
|
||||||
|
run: |
|
||||||
|
echo "Verifying published package..."
|
||||||
|
npm view @serve.zone/nupst
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Testing installation from npm:"
|
||||||
|
npm install -g @serve.zone/nupst
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Package installed successfully!"
|
||||||
|
which nupst || echo "Binary location check skipped"
|
||||||
54
.npmignore
Normal file
54
.npmignore
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Source code (not needed for binary distribution)
|
||||||
|
/ts/
|
||||||
|
/test/
|
||||||
|
mod.ts
|
||||||
|
*.ts
|
||||||
|
|
||||||
|
# Development files
|
||||||
|
.git/
|
||||||
|
.gitea/
|
||||||
|
.claude/
|
||||||
|
.serena/
|
||||||
|
.nogit/
|
||||||
|
.github/
|
||||||
|
deno.json
|
||||||
|
deno.lock
|
||||||
|
tsconfig.json
|
||||||
|
|
||||||
|
# Scripts not needed for npm
|
||||||
|
/scripts/compile-all.sh
|
||||||
|
install.sh
|
||||||
|
uninstall.sh
|
||||||
|
example-action.sh
|
||||||
|
|
||||||
|
# Documentation files not needed for npm package
|
||||||
|
readme.plan.md
|
||||||
|
readme.hints.md
|
||||||
|
npm-publish-instructions.md
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Keep only the install-binary.js in scripts/
|
||||||
|
/scripts/*
|
||||||
|
!/scripts/install-binary.js
|
||||||
|
|
||||||
|
# Exclude all dist directory (binaries will be downloaded during install)
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# Logs and temporary files
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Other
|
||||||
|
node_modules/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
108
bin/nupst-wrapper.js
Normal file
108
bin/nupst-wrapper.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NUPST npm wrapper
|
||||||
|
* This script executes the appropriate pre-compiled binary based on the current platform
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { platform, arch } from 'os';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the binary name for the current platform
|
||||||
|
*/
|
||||||
|
function getBinaryName() {
|
||||||
|
const plat = platform();
|
||||||
|
const architecture = arch();
|
||||||
|
|
||||||
|
// Map Node's platform/arch to our binary naming
|
||||||
|
const platformMap = {
|
||||||
|
'darwin': 'macos',
|
||||||
|
'linux': 'linux',
|
||||||
|
'win32': 'windows'
|
||||||
|
};
|
||||||
|
|
||||||
|
const archMap = {
|
||||||
|
'x64': 'x64',
|
||||||
|
'arm64': 'arm64'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mappedPlatform = platformMap[plat];
|
||||||
|
const mappedArch = archMap[architecture];
|
||||||
|
|
||||||
|
if (!mappedPlatform || !mappedArch) {
|
||||||
|
console.error(`Error: Unsupported platform/architecture: ${plat}/${architecture}`);
|
||||||
|
console.error('Supported platforms: Linux, macOS, Windows');
|
||||||
|
console.error('Supported architectures: x64, arm64');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct binary name
|
||||||
|
let binaryName = `nupst-${mappedPlatform}-${mappedArch}`;
|
||||||
|
if (plat === 'win32') {
|
||||||
|
binaryName += '.exe';
|
||||||
|
}
|
||||||
|
|
||||||
|
return binaryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the binary
|
||||||
|
*/
|
||||||
|
function executeBinary() {
|
||||||
|
const binaryName = getBinaryName();
|
||||||
|
const binaryPath = join(__dirname, '..', 'dist', 'binaries', binaryName);
|
||||||
|
|
||||||
|
// Check if binary exists
|
||||||
|
if (!existsSync(binaryPath)) {
|
||||||
|
console.error(`Error: Binary not found at ${binaryPath}`);
|
||||||
|
console.error('This might happen if:');
|
||||||
|
console.error('1. The postinstall script failed to run');
|
||||||
|
console.error('2. The platform is not supported');
|
||||||
|
console.error('3. The package was not installed correctly');
|
||||||
|
console.error('');
|
||||||
|
console.error('Try reinstalling the package:');
|
||||||
|
console.error(' npm uninstall -g @serve.zone/nupst');
|
||||||
|
console.error(' npm install -g @serve.zone/nupst');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn the binary with all arguments passed through
|
||||||
|
const child = spawn(binaryPath, process.argv.slice(2), {
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle child process events
|
||||||
|
child.on('error', (err) => {
|
||||||
|
console.error(`Error executing nupst: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('exit', (code, signal) => {
|
||||||
|
if (signal) {
|
||||||
|
process.kill(process.pid, signal);
|
||||||
|
} else {
|
||||||
|
process.exit(code || 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Forward signals to child process
|
||||||
|
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
||||||
|
signals.forEach(signal => {
|
||||||
|
process.on(signal, () => {
|
||||||
|
if (!child.killed) {
|
||||||
|
child.kill(signal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
executeBinary();
|
||||||
11
changelog.md
11
changelog.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-10-22 - 5.1.0 - feat(packaging)
|
||||||
|
Add npm packaging and installer: wrapper, postinstall downloader, publish workflow, and packaging files
|
||||||
|
|
||||||
|
- Add package.json (v5.0.5) and npm packaging metadata to publish @serve.zone/nupst
|
||||||
|
- Include a small Node.js wrapper (bin/nupst-wrapper.js) to execute platform-specific precompiled binaries
|
||||||
|
- Add postinstall script (scripts/install-binary.js) that downloads the correct binary for the current platform and sets executable permissions
|
||||||
|
- Add GitHub Actions workflow (.github/workflows/npm-publish.yml) to build binaries, pack and publish to npm, and create releases
|
||||||
|
- Add .npmignore to keep source, tests and dev files out of npm package; keep only runtime installer and wrapper
|
||||||
|
- Move example action script into docs (docs/example-action.sh) and remove the top-level example-action.sh
|
||||||
|
- Include generated npm package artifact (serve.zone-nupst-5.0.5.tgz) and npmextra.json
|
||||||
|
|
||||||
## 2025-10-18 - 4.0.0 - BREAKING CHANGE(core): Complete migration to Deno runtime
|
## 2025-10-18 - 4.0.0 - BREAKING CHANGE(core): Complete migration to Deno runtime
|
||||||
|
|
||||||
**MAJOR RELEASE: NUPST v4.0 is a complete rewrite powered by Deno**
|
**MAJOR RELEASE: NUPST v4.0 is a complete rewrite powered by Deno**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/nupst",
|
"name": "@serve.zone/nupst",
|
||||||
"version": "4.3.2",
|
"version": "5.0.5",
|
||||||
"exports": "./mod.ts",
|
"exports": "./mod.ts",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run --allow-all mod.ts",
|
"dev": "deno run --allow-all mod.ts",
|
||||||
|
|||||||
94
install.sh
94
install.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# NUPST Installer Script (v4.0+)
|
# NUPST Installer Script (v5.0+)
|
||||||
# Downloads and installs pre-compiled NUPST binary from Gitea releases
|
# Downloads and installs pre-compiled NUPST binary from Gitea releases
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash
|
||||||
#
|
#
|
||||||
# With version specification:
|
# With version specification:
|
||||||
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0
|
# curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v5.0.0
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# -h, --help Show this help message
|
# -h, --help Show this help message
|
||||||
@@ -48,14 +48,14 @@ while [[ $# -gt 0 ]]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ $SHOW_HELP -eq 1 ]; then
|
if [ $SHOW_HELP -eq 1 ]; then
|
||||||
echo "NUPST Installer Script (v4.0+)"
|
echo "NUPST Installer Script (v5.0+)"
|
||||||
echo "Downloads and installs pre-compiled NUPST binary"
|
echo "Downloads and installs pre-compiled NUPST binary"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage: $0 [options]"
|
echo "Usage: $0 [options]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " -h, --help Show this help message"
|
echo " -h, --help Show this help message"
|
||||||
echo " --version VERSION Install specific version (e.g., v4.0.0)"
|
echo " --version VERSION Install specific version (e.g., v5.0.0)"
|
||||||
echo " --install-dir DIR Installation directory (default: /opt/nupst)"
|
echo " --install-dir DIR Installation directory (default: /opt/nupst)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
@@ -63,7 +63,7 @@ if [ $SHOW_HELP -eq 1 ]; then
|
|||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash"
|
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash"
|
||||||
echo ""
|
echo ""
|
||||||
echo " # Install specific version"
|
echo " # Install specific version"
|
||||||
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v4.0.0"
|
echo " curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash -s -- --version v5.0.0"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ get_latest_version() {
|
|||||||
|
|
||||||
# Main installation process
|
# Main installation process
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo " NUPST Installation Script (v4.0+)"
|
echo " NUPST Installation Script (v5.0+)"
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -169,51 +169,26 @@ DOWNLOAD_URL="${GITEA_BASE_URL}/${GITEA_REPO}/releases/download/${VERSION}/${BIN
|
|||||||
echo "Download URL: $DOWNLOAD_URL"
|
echo "Download URL: $DOWNLOAD_URL"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check if installation directory exists
|
# Check if service is running and stop it
|
||||||
SERVICE_WAS_RUNNING=0
|
SERVICE_WAS_RUNNING=0
|
||||||
OLD_NODE_INSTALL=0
|
if systemctl is-enabled --quiet nupst 2>/dev/null || systemctl is-active --quiet nupst 2>/dev/null; then
|
||||||
|
|
||||||
if [ -d "$INSTALL_DIR" ]; then
|
|
||||||
# Check if this is an old Node.js-based installation
|
|
||||||
if [ -f "$INSTALL_DIR/package.json" ] || [ -d "$INSTALL_DIR/node_modules" ]; then
|
|
||||||
OLD_NODE_INSTALL=1
|
|
||||||
echo "Detected old Node.js-based NUPST installation (v3.x or earlier)"
|
|
||||||
echo "This installer will migrate to the new Deno-based binary version (v4.0+)"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Updating existing installation at $INSTALL_DIR..."
|
|
||||||
|
|
||||||
# Check if service exists (enabled or running) and stop it if active
|
|
||||||
if systemctl is-enabled --quiet nupst 2>/dev/null || systemctl is-active --quiet nupst 2>/dev/null; then
|
|
||||||
SERVICE_WAS_RUNNING=1
|
SERVICE_WAS_RUNNING=1
|
||||||
if systemctl is-active --quiet nupst 2>/dev/null; then
|
if systemctl is-active --quiet nupst 2>/dev/null; then
|
||||||
echo "Stopping NUPST service..."
|
echo "Stopping NUPST service..."
|
||||||
systemctl stop nupst
|
systemctl stop nupst
|
||||||
else
|
|
||||||
echo "Service is installed but not currently running (will be updated)..."
|
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up old Node.js installation files
|
|
||||||
if [ $OLD_NODE_INSTALL -eq 1 ]; then
|
|
||||||
echo "Cleaning up old Node.js installation files..."
|
|
||||||
rm -rf "$INSTALL_DIR/node_modules" 2>/dev/null || true
|
|
||||||
rm -rf "$INSTALL_DIR/vendor" 2>/dev/null || true
|
|
||||||
rm -rf "$INSTALL_DIR/dist_ts" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/package.json" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/package-lock.json" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/pnpm-lock.yaml" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/tsconfig.json" 2>/dev/null || true
|
|
||||||
rm -f "$INSTALL_DIR/setup.sh" 2>/dev/null || true
|
|
||||||
rm -rf "$INSTALL_DIR/bin" 2>/dev/null || true
|
|
||||||
echo "Old installation files removed."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Creating installation directory: $INSTALL_DIR"
|
|
||||||
mkdir -p "$INSTALL_DIR"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Clean installation directory - ensure only binary exists
|
||||||
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
|
echo "Cleaning installation directory: $INSTALL_DIR"
|
||||||
|
rm -rf "$INSTALL_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create fresh installation directory
|
||||||
|
echo "Creating installation directory: $INSTALL_DIR"
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
# Download binary
|
# Download binary
|
||||||
echo "Downloading NUPST binary..."
|
echo "Downloading NUPST binary..."
|
||||||
TEMP_FILE="$INSTALL_DIR/nupst.download"
|
TEMP_FILE="$INSTALL_DIR/nupst.download"
|
||||||
@@ -241,9 +216,20 @@ fi
|
|||||||
BINARY_PATH="$INSTALL_DIR/nupst"
|
BINARY_PATH="$INSTALL_DIR/nupst"
|
||||||
mv "$TEMP_FILE" "$BINARY_PATH"
|
mv "$TEMP_FILE" "$BINARY_PATH"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ] || [ ! -f "$BINARY_PATH" ]; then
|
||||||
|
echo "Error: Failed to move binary to $BINARY_PATH"
|
||||||
|
rm -f "$TEMP_FILE" 2>/dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Make executable
|
# Make executable
|
||||||
chmod +x "$BINARY_PATH"
|
chmod +x "$BINARY_PATH"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Failed to make binary executable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Binary installed successfully to: $BINARY_PATH"
|
echo "Binary installed successfully to: $BINARY_PATH"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -260,14 +246,6 @@ echo "Symlink created: $BIN_DIR/nupst -> $BINARY_PATH"
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Update systemd service file if migrating from v3
|
|
||||||
if [ $SERVICE_WAS_RUNNING -eq 1 ] && [ $OLD_NODE_INSTALL -eq 1 ]; then
|
|
||||||
echo "Updating systemd service file for v4..."
|
|
||||||
$BINARY_PATH service enable > /dev/null 2>&1
|
|
||||||
echo "Service file updated."
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Restart service if it was running before update
|
# Restart service if it was running before update
|
||||||
if [ $SERVICE_WAS_RUNNING -eq 1 ]; then
|
if [ $SERVICE_WAS_RUNNING -eq 1 ]; then
|
||||||
echo "Restarting NUPST service..."
|
echo "Restarting NUPST service..."
|
||||||
@@ -280,20 +258,6 @@ echo "================================================"
|
|||||||
echo " NUPST Installation Complete!"
|
echo " NUPST Installation Complete!"
|
||||||
echo "================================================"
|
echo "================================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ $OLD_NODE_INSTALL -eq 1 ]; then
|
|
||||||
echo "Migration from v3.x to v4.0 successful!"
|
|
||||||
echo ""
|
|
||||||
echo "What changed:"
|
|
||||||
echo " • Node.js runtime removed (now a self-contained binary)"
|
|
||||||
echo " • Faster startup and lower memory usage"
|
|
||||||
echo " • CLI commands now use subcommand structure"
|
|
||||||
echo " (old commands still work with deprecation warnings)"
|
|
||||||
echo ""
|
|
||||||
echo "See readme for migration details: https://code.foss.global/serve.zone/nupst#migration-from-v3x"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Installation details:"
|
echo "Installation details:"
|
||||||
echo " Binary location: $BINARY_PATH"
|
echo " Binary location: $BINARY_PATH"
|
||||||
echo " Symlink location: $BIN_DIR/nupst"
|
echo " Symlink location: $BIN_DIR/nupst"
|
||||||
|
|||||||
1
npmextra.json
Normal file
1
npmextra.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
62
package.json
Normal file
62
package.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "@serve.zone/nupst",
|
||||||
|
"version": "5.1.0",
|
||||||
|
"description": "Network UPS Shutdown Tool - Monitor SNMP-enabled UPS devices and orchestrate graceful system shutdowns during power emergencies",
|
||||||
|
"keywords": [
|
||||||
|
"ups",
|
||||||
|
"snmp",
|
||||||
|
"power",
|
||||||
|
"shutdown",
|
||||||
|
"monitoring",
|
||||||
|
"cyberpower",
|
||||||
|
"apc",
|
||||||
|
"eaton",
|
||||||
|
"tripplite",
|
||||||
|
"liebert",
|
||||||
|
"vertiv",
|
||||||
|
"battery",
|
||||||
|
"backup"
|
||||||
|
],
|
||||||
|
"homepage": "https://code.foss.global/serve.zone/nupst",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://code.foss.global/serve.zone/nupst/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://code.foss.global/serve.zone/nupst.git"
|
||||||
|
},
|
||||||
|
"author": "Serve Zone",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"nupst": "./bin/nupst-wrapper.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "node scripts/install-binary.js",
|
||||||
|
"prepublishOnly": "echo 'Publishing NUPST binaries to npm...'",
|
||||||
|
"test": "echo 'Tests are run with Deno: deno task test'"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin/",
|
||||||
|
"scripts/install-binary.js",
|
||||||
|
"readme.md",
|
||||||
|
"license",
|
||||||
|
"changelog.md"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"darwin",
|
||||||
|
"linux",
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64",
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
}
|
||||||
|
}
|
||||||
231
scripts/install-binary.js
Normal file
231
scripts/install-binary.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NUPST npm postinstall script
|
||||||
|
* Downloads the appropriate binary for the current platform from GitHub releases
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { platform, arch } from 'os';
|
||||||
|
import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'fs';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import https from 'https';
|
||||||
|
import { pipeline } from 'stream';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import { createWriteStream } from 'fs';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
const streamPipeline = promisify(pipeline);
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const REPO_BASE = 'https://code.foss.global/serve.zone/nupst';
|
||||||
|
const VERSION = process.env.npm_package_version || '5.0.5';
|
||||||
|
|
||||||
|
function getBinaryInfo() {
|
||||||
|
const plat = platform();
|
||||||
|
const architecture = arch();
|
||||||
|
|
||||||
|
const platformMap = {
|
||||||
|
'darwin': 'macos',
|
||||||
|
'linux': 'linux',
|
||||||
|
'win32': 'windows'
|
||||||
|
};
|
||||||
|
|
||||||
|
const archMap = {
|
||||||
|
'x64': 'x64',
|
||||||
|
'arm64': 'arm64'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mappedPlatform = platformMap[plat];
|
||||||
|
const mappedArch = archMap[architecture];
|
||||||
|
|
||||||
|
if (!mappedPlatform || !mappedArch) {
|
||||||
|
return { supported: false, platform: plat, arch: architecture };
|
||||||
|
}
|
||||||
|
|
||||||
|
let binaryName = `nupst-${mappedPlatform}-${mappedArch}`;
|
||||||
|
if (plat === 'win32') {
|
||||||
|
binaryName += '.exe';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
supported: true,
|
||||||
|
platform: mappedPlatform,
|
||||||
|
arch: mappedArch,
|
||||||
|
binaryName,
|
||||||
|
originalPlatform: plat
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFile(url, destination) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(`Downloading from: ${url}`);
|
||||||
|
|
||||||
|
// Follow redirects
|
||||||
|
const download = (url, redirectCount = 0) => {
|
||||||
|
if (redirectCount > 5) {
|
||||||
|
reject(new Error('Too many redirects'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
https.get(url, (response) => {
|
||||||
|
if (response.statusCode === 301 || response.statusCode === 302) {
|
||||||
|
console.log(`Following redirect to: ${response.headers.location}`);
|
||||||
|
download(response.headers.location, redirectCount + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalSize = parseInt(response.headers['content-length'], 10);
|
||||||
|
let downloadedSize = 0;
|
||||||
|
let lastProgress = 0;
|
||||||
|
|
||||||
|
response.on('data', (chunk) => {
|
||||||
|
downloadedSize += chunk.length;
|
||||||
|
const progress = Math.round((downloadedSize / totalSize) * 100);
|
||||||
|
|
||||||
|
// Only log every 10% to reduce noise
|
||||||
|
if (progress >= lastProgress + 10) {
|
||||||
|
console.log(`Download progress: ${progress}%`);
|
||||||
|
lastProgress = progress;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const file = createWriteStream(destination);
|
||||||
|
|
||||||
|
pipeline(response, file, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
console.log('Download complete!');
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('error', reject);
|
||||||
|
};
|
||||||
|
|
||||||
|
download(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('===========================================');
|
||||||
|
console.log(' NUPST - Binary Installation');
|
||||||
|
console.log('===========================================');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
const binaryInfo = getBinaryInfo();
|
||||||
|
|
||||||
|
if (!binaryInfo.supported) {
|
||||||
|
console.error(`❌ Error: Unsupported platform/architecture: ${binaryInfo.platform}/${binaryInfo.arch}`);
|
||||||
|
console.error('');
|
||||||
|
console.error('Supported platforms:');
|
||||||
|
console.error(' • Linux (x64, arm64)');
|
||||||
|
console.error(' • macOS (x64, arm64)');
|
||||||
|
console.error(' • Windows (x64)');
|
||||||
|
console.error('');
|
||||||
|
console.error('If you believe your platform should be supported, please file an issue:');
|
||||||
|
console.error(' https://code.foss.global/serve.zone/nupst/issues');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Platform: ${binaryInfo.platform} (${binaryInfo.originalPlatform})`);
|
||||||
|
console.log(`Architecture: ${binaryInfo.arch}`);
|
||||||
|
console.log(`Binary: ${binaryInfo.binaryName}`);
|
||||||
|
console.log(`Version: ${VERSION}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Create dist/binaries directory if it doesn't exist
|
||||||
|
const binariesDir = join(__dirname, '..', 'dist', 'binaries');
|
||||||
|
if (!existsSync(binariesDir)) {
|
||||||
|
console.log('Creating binaries directory...');
|
||||||
|
mkdirSync(binariesDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryPath = join(binariesDir, binaryInfo.binaryName);
|
||||||
|
|
||||||
|
// Check if binary already exists and skip download
|
||||||
|
if (existsSync(binaryPath)) {
|
||||||
|
console.log('✓ Binary already exists, skipping download');
|
||||||
|
} else {
|
||||||
|
// Construct download URL
|
||||||
|
// Try release URL first, fall back to raw branch if needed
|
||||||
|
const releaseUrl = `${REPO_BASE}/releases/download/v${VERSION}/${binaryInfo.binaryName}`;
|
||||||
|
const fallbackUrl = `${REPO_BASE}/raw/branch/main/dist/binaries/${binaryInfo.binaryName}`;
|
||||||
|
|
||||||
|
console.log('Downloading platform-specific binary...');
|
||||||
|
console.log('This may take a moment depending on your connection speed.');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try downloading from release
|
||||||
|
await downloadFile(releaseUrl, binaryPath);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Release download failed: ${err.message}`);
|
||||||
|
console.log('Trying fallback URL...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try fallback URL
|
||||||
|
await downloadFile(fallbackUrl, binaryPath);
|
||||||
|
} catch (fallbackErr) {
|
||||||
|
console.error(`❌ Error: Failed to download binary`);
|
||||||
|
console.error(` Primary URL: ${releaseUrl}`);
|
||||||
|
console.error(` Fallback URL: ${fallbackUrl}`);
|
||||||
|
console.error('');
|
||||||
|
console.error('This might be because:');
|
||||||
|
console.error('1. The release has not been created yet');
|
||||||
|
console.error('2. Network connectivity issues');
|
||||||
|
console.error('3. The version specified does not exist');
|
||||||
|
console.error('');
|
||||||
|
console.error('You can try:');
|
||||||
|
console.error('1. Installing from source: https://code.foss.global/serve.zone/nupst');
|
||||||
|
console.error('2. Downloading the binary manually from the releases page');
|
||||||
|
console.error('3. Using the install script: curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash');
|
||||||
|
|
||||||
|
// Clean up partial download
|
||||||
|
if (existsSync(binaryPath)) {
|
||||||
|
unlinkSync(binaryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✓ Binary downloaded successfully`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Unix-like systems, ensure the binary is executable
|
||||||
|
if (binaryInfo.originalPlatform !== 'win32') {
|
||||||
|
try {
|
||||||
|
console.log('Setting executable permissions...');
|
||||||
|
chmodSync(binaryPath, 0o755);
|
||||||
|
console.log('✓ Binary permissions updated');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`⚠️ Warning: Could not set executable permissions: ${err.message}`);
|
||||||
|
console.error(' You may need to manually run:');
|
||||||
|
console.error(` chmod +x ${binaryPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('✅ NUPST installation completed successfully!');
|
||||||
|
console.log('');
|
||||||
|
console.log('You can now use NUPST by running:');
|
||||||
|
console.log(' nupst --help');
|
||||||
|
console.log('');
|
||||||
|
console.log('For initial setup, run:');
|
||||||
|
console.log(' sudo nupst ups add');
|
||||||
|
console.log('');
|
||||||
|
console.log('===========================================');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the installation
|
||||||
|
main().catch(err => {
|
||||||
|
console.error(`❌ Installation failed: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
BIN
serve.zone-nupst-5.0.5.tgz
Normal file
BIN
serve.zone-nupst-5.0.5.tgz
Normal file
Binary file not shown.
@@ -1,10 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* commitinfo - reads version from deno.json
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
*/
|
*/
|
||||||
import denoConfig from '../deno.json' with { type: 'json' };
|
|
||||||
|
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: denoConfig.name,
|
name: '@serve.zone/nupst',
|
||||||
version: denoConfig.version,
|
version: '5.1.0',
|
||||||
description: 'Network UPS Shutdown Tool (https://nupst.serve.zone)',
|
description: 'Network UPS Shutdown Tool - Monitor SNMP-enabled UPS devices and orchestrate graceful system shutdowns during power emergencies'
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
|
import process from 'node:process';
|
||||||
import { exec } from 'node:child_process';
|
import { exec } from 'node:child_process';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import { Action, type IActionConfig, type IActionContext } from './base-action.ts';
|
import { Action, type IActionConfig, type IActionContext } from './base-action.ts';
|
||||||
|
|||||||
82
ts/cli.ts
82
ts/cli.ts
@@ -127,8 +127,7 @@ export class NupstCli {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove':
|
case 'remove':
|
||||||
case 'rm': // Alias
|
case 'rm': {
|
||||||
case 'delete': { // Backward compatibility
|
|
||||||
const upsIdToRemove = subcommandArgs[0];
|
const upsIdToRemove = subcommandArgs[0];
|
||||||
if (!upsIdToRemove) {
|
if (!upsIdToRemove) {
|
||||||
logger.error('UPS ID is required for remove command');
|
logger.error('UPS ID is required for remove command');
|
||||||
@@ -172,8 +171,7 @@ export class NupstCli {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove':
|
case 'remove':
|
||||||
case 'rm': // Alias
|
case 'rm': {
|
||||||
case 'delete': { // Backward compatibility
|
|
||||||
const groupIdToRemove = subcommandArgs[0];
|
const groupIdToRemove = subcommandArgs[0];
|
||||||
if (!groupIdToRemove) {
|
if (!groupIdToRemove) {
|
||||||
logger.error('Group ID is required for remove command');
|
logger.error('Group ID is required for remove command');
|
||||||
@@ -206,8 +204,7 @@ export class NupstCli {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'remove':
|
case 'remove':
|
||||||
case 'rm': // Alias
|
case 'rm': {
|
||||||
case 'delete': { // Backward compatibility
|
|
||||||
const upsId = subcommandArgs[0];
|
const upsId = subcommandArgs[0];
|
||||||
const actionIndex = subcommandArgs[1];
|
const actionIndex = subcommandArgs[1];
|
||||||
await actionHandler.remove(upsId, actionIndex);
|
await actionHandler.remove(upsId, actionIndex);
|
||||||
@@ -242,72 +239,8 @@ export class NupstCli {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle top-level commands and backward compatibility
|
// Handle top-level commands
|
||||||
switch (command) {
|
switch (command) {
|
||||||
// Backward compatibility - old UPS commands
|
|
||||||
case 'add':
|
|
||||||
logger.log("Note: 'nupst add' is deprecated. Use 'nupst ups add' instead.");
|
|
||||||
await upsHandler.add();
|
|
||||||
break;
|
|
||||||
case 'edit':
|
|
||||||
logger.log("Note: 'nupst edit' is deprecated. Use 'nupst ups edit' instead.");
|
|
||||||
await upsHandler.edit(commandArgs[0]);
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
logger.log("Note: 'nupst delete' is deprecated. Use 'nupst ups remove' instead.");
|
|
||||||
if (!commandArgs[0]) {
|
|
||||||
logger.error('UPS ID is required for delete command');
|
|
||||||
this.showHelp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await upsHandler.remove(commandArgs[0]);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
logger.log("Note: 'nupst list' is deprecated. Use 'nupst ups list' instead.");
|
|
||||||
await upsHandler.list();
|
|
||||||
break;
|
|
||||||
case 'test':
|
|
||||||
logger.log("Note: 'nupst test' is deprecated. Use 'nupst ups test' instead.");
|
|
||||||
await upsHandler.test(debugMode);
|
|
||||||
break;
|
|
||||||
case 'setup':
|
|
||||||
logger.log("Note: 'nupst setup' is deprecated. Use 'nupst ups edit' instead.");
|
|
||||||
await upsHandler.edit(undefined);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Backward compatibility - old service commands
|
|
||||||
case 'enable':
|
|
||||||
logger.log("Note: 'nupst enable' is deprecated. Use 'nupst service enable' instead.");
|
|
||||||
await serviceHandler.enable();
|
|
||||||
break;
|
|
||||||
case 'disable':
|
|
||||||
logger.log("Note: 'nupst disable' is deprecated. Use 'nupst service disable' instead.");
|
|
||||||
await serviceHandler.disable();
|
|
||||||
break;
|
|
||||||
case 'start':
|
|
||||||
logger.log("Note: 'nupst start' is deprecated. Use 'nupst service start' instead.");
|
|
||||||
await serviceHandler.start();
|
|
||||||
break;
|
|
||||||
case 'stop':
|
|
||||||
logger.log("Note: 'nupst stop' is deprecated. Use 'nupst service stop' instead.");
|
|
||||||
await serviceHandler.stop();
|
|
||||||
break;
|
|
||||||
case 'status':
|
|
||||||
logger.log("Note: 'nupst status' is deprecated. Use 'nupst service status' instead.");
|
|
||||||
await serviceHandler.status();
|
|
||||||
break;
|
|
||||||
case 'logs':
|
|
||||||
logger.log("Note: 'nupst logs' is deprecated. Use 'nupst service logs' instead.");
|
|
||||||
await serviceHandler.logs();
|
|
||||||
break;
|
|
||||||
case 'daemon-start':
|
|
||||||
logger.log(
|
|
||||||
"Note: 'nupst daemon-start' is deprecated. Use 'nupst service start-daemon' instead.",
|
|
||||||
);
|
|
||||||
await serviceHandler.daemonStart(debugMode);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Top-level commands (no changes)
|
|
||||||
case 'update':
|
case 'update':
|
||||||
await serviceHandler.update();
|
await serviceHandler.update();
|
||||||
break;
|
break;
|
||||||
@@ -589,11 +522,6 @@ export class NupstCli {
|
|||||||
logger.dim(' nupst group list # Show all configured groups');
|
logger.dim(' nupst group list # Show all configured groups');
|
||||||
logger.dim(' nupst config # Display current configuration');
|
logger.dim(' nupst config # Display current configuration');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
// Note about deprecated commands
|
|
||||||
logger.warn('Note: Old command format (e.g., \'nupst add\') still works but is deprecated.');
|
|
||||||
logger.dim(' Use the new format (e.g., \'nupst ups add\') going forward.');
|
|
||||||
console.log('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -692,7 +620,7 @@ Usage:
|
|||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
add <ups-id|group-id> - Add a new action to a UPS or group interactively
|
add <ups-id|group-id> - Add a new action to a UPS or group interactively
|
||||||
remove <ups-id|group-id> <index> - Remove an action by index (alias: rm, delete)
|
remove <ups-id|group-id> <index> - Remove an action by index (alias: rm)
|
||||||
list [ups-id|group-id] - List all actions (optionally for specific target) (alias: ls)
|
list [ups-id|group-id] - List all actions (optionally for specific target) (alias: ls)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|||||||
@@ -277,6 +277,9 @@ WantedBy=multi-user.target
|
|||||||
for (const ups of config.upsDevices) {
|
for (const ups of config.upsDevices) {
|
||||||
await this.displaySingleUpsStatus(ups, snmp);
|
await this.displaySingleUpsStatus(ups, snmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display groups after UPS devices
|
||||||
|
this.displayGroupsStatus();
|
||||||
} else if (config.snmp) {
|
} else if (config.snmp) {
|
||||||
// Legacy single UPS configuration (v1/v2 format)
|
// Legacy single UPS configuration (v1/v2 format)
|
||||||
logger.info('UPS Devices (1):');
|
logger.info('UPS Devices (1):');
|
||||||
@@ -365,6 +368,27 @@ WantedBy=multi-user.target
|
|||||||
logger.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`);
|
logger.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display actions if any
|
||||||
|
if (ups.actions && ups.actions.length > 0) {
|
||||||
|
for (const action of ups.actions) {
|
||||||
|
let actionDesc = `${action.type}`;
|
||||||
|
if (action.thresholds) {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyThresholds'}: battery<${action.thresholds.battery}%, runtime<${action.thresholds.runtime}min`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
} else {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyPowerChanges'}`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
}
|
||||||
|
logger.log(` ${theme.dim('Action:')} ${theme.info(actionDesc)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.log('');
|
logger.log('');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -376,6 +400,69 @@ WantedBy=multi-user.target
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display status of all groups
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private displayGroupsStatus(): void {
|
||||||
|
const config = this.daemon.getConfig();
|
||||||
|
|
||||||
|
if (!config.groups || config.groups.length === 0) {
|
||||||
|
return; // No groups to display
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('');
|
||||||
|
logger.info(`Groups (${config.groups.length}):`);
|
||||||
|
|
||||||
|
for (const group of config.groups) {
|
||||||
|
// Display group name and mode
|
||||||
|
const modeColor = group.mode === 'redundant' ? theme.success : theme.warning;
|
||||||
|
logger.log(
|
||||||
|
` ${symbols.info} ${theme.highlight(group.name)} ${theme.dim(`(${modeColor(group.mode)})`)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Display description if present
|
||||||
|
if (group.description) {
|
||||||
|
logger.log(` ${theme.dim(group.description)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display UPS devices in this group
|
||||||
|
const upsInGroup = config.upsDevices.filter((ups) =>
|
||||||
|
ups.groups && ups.groups.includes(group.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (upsInGroup.length > 0) {
|
||||||
|
const upsNames = upsInGroup.map((ups) => ups.name).join(', ');
|
||||||
|
logger.log(` ${theme.dim(`UPS Devices (${upsInGroup.length}):`)} ${upsNames}`);
|
||||||
|
} else {
|
||||||
|
logger.log(` ${theme.dim('UPS Devices: None')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display actions if any
|
||||||
|
if (group.actions && group.actions.length > 0) {
|
||||||
|
for (const action of group.actions) {
|
||||||
|
let actionDesc = `${action.type}`;
|
||||||
|
if (action.thresholds) {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyThresholds'}: battery<${action.thresholds.battery}%, runtime<${action.thresholds.runtime}min`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
} else {
|
||||||
|
actionDesc += ` (${action.triggerMode || 'onlyPowerChanges'}`;
|
||||||
|
if (action.shutdownDelay) {
|
||||||
|
actionDesc += `, delay=${action.shutdownDelay}s`;
|
||||||
|
}
|
||||||
|
actionDesc += ')';
|
||||||
|
}
|
||||||
|
logger.log(` ${theme.dim('Action:')} ${theme.info(actionDesc)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable and uninstall the systemd service
|
* Disable and uninstall the systemd service
|
||||||
* @throws Error if disabling fails
|
* @throws Error if disabling fails
|
||||||
|
|||||||
Reference in New Issue
Block a user