mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-10-22 23:32:03 +00:00
Added arm32 based agent
Added support for migrating from legacy bash script to new binary via intermediatry 1.2.9 script
This commit is contained in:
Binary file not shown.
Binary file not shown.
BIN
agents/patchmon-agent-linux-arm
Executable file
BIN
agents/patchmon-agent-linux-arm
Executable file
Binary file not shown.
Binary file not shown.
460
agents/patchmon-agent.sh
Executable file
460
agents/patchmon-agent.sh
Executable file
@@ -0,0 +1,460 @@
|
||||
#!/bin/bash
|
||||
|
||||
# PatchMon Agent Migration Script v1.2.9
|
||||
# This script migrates from legacy bash agent (v1.2.8) to Go agent (v1.3.0+)
|
||||
# It acts as an intermediary during the upgrade process
|
||||
|
||||
# Configuration
|
||||
PATCHMON_SERVER="${PATCHMON_SERVER:-http://localhost:3001}"
|
||||
API_VERSION="v1"
|
||||
AGENT_VERSION="1.2.9"
|
||||
CREDENTIALS_FILE="/etc/patchmon/credentials"
|
||||
LOG_FILE="/var/log/patchmon-agent.log"
|
||||
|
||||
# This placeholder will be dynamically replaced by the server when serving this
|
||||
# script based on the "ignore SSL self-signed" setting. If set to -k, curl will
|
||||
# ignore certificate validation. Otherwise, it will be empty for secure default.
|
||||
CURL_FLAGS=""
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging function
|
||||
log() {
|
||||
if [[ -w "$(dirname "$LOG_FILE")" ]] 2>/dev/null; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] MIGRATION: $1" >> "$LOG_FILE" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Error handling
|
||||
error() {
|
||||
echo -e "${RED}ERROR: $1${NC}" >&2
|
||||
log "ERROR: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Info logging
|
||||
info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}" >&2
|
||||
log "INFO: $1"
|
||||
}
|
||||
|
||||
# Success logging
|
||||
success() {
|
||||
echo -e "${GREEN}✅ $1${NC}" >&2
|
||||
log "SUCCESS: $1"
|
||||
}
|
||||
|
||||
# Warning logging
|
||||
warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}" >&2
|
||||
log "WARNING: $1"
|
||||
}
|
||||
|
||||
# Check if running as root
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error "This migration script must be run as root"
|
||||
fi
|
||||
}
|
||||
|
||||
# Load API credentials from legacy format
|
||||
load_legacy_credentials() {
|
||||
if [[ ! -f "$CREDENTIALS_FILE" ]]; then
|
||||
error "Legacy credentials file not found at $CREDENTIALS_FILE"
|
||||
fi
|
||||
|
||||
source "$CREDENTIALS_FILE"
|
||||
|
||||
if [[ -z "$API_ID" ]] || [[ -z "$API_KEY" ]]; then
|
||||
error "API_ID and API_KEY must be configured in $CREDENTIALS_FILE"
|
||||
fi
|
||||
|
||||
# Use PATCHMON_URL from credentials if available
|
||||
if [[ -n "$PATCHMON_URL" ]]; then
|
||||
PATCHMON_SERVER="$PATCHMON_URL"
|
||||
fi
|
||||
}
|
||||
|
||||
# Convert legacy credentials to YAML format
|
||||
convert_credentials_to_yaml() {
|
||||
local yaml_file="/etc/patchmon/credentials.yml"
|
||||
|
||||
info "Converting credentials to YAML format..."
|
||||
|
||||
cat > "$yaml_file" << EOF
|
||||
api_id: "$API_ID"
|
||||
api_key: "$API_KEY"
|
||||
EOF
|
||||
|
||||
chmod 600 "$yaml_file"
|
||||
success "Credentials converted to YAML format"
|
||||
}
|
||||
|
||||
# Create Go agent configuration
|
||||
create_go_agent_config() {
|
||||
local config_file="/etc/patchmon/config.yml"
|
||||
|
||||
info "Creating Go agent configuration..."
|
||||
|
||||
cat > "$config_file" << EOF
|
||||
patchmon_server: "$PATCHMON_SERVER"
|
||||
api_version: "$API_VERSION"
|
||||
credentials_file: "/etc/patchmon/credentials.yml"
|
||||
log_file: "/etc/patchmon/logs/patchmon-agent.log"
|
||||
log_level: "info"
|
||||
EOF
|
||||
|
||||
chmod 644 "$config_file"
|
||||
success "Go agent configuration created"
|
||||
}
|
||||
|
||||
# Download Go agent binary
|
||||
download_go_agent() {
|
||||
local arch=$(uname -m)
|
||||
local goos="linux"
|
||||
local goarch=""
|
||||
|
||||
# Map architecture
|
||||
case "$arch" in
|
||||
"x86_64")
|
||||
goarch="amd64"
|
||||
;;
|
||||
"i386"|"i686")
|
||||
goarch="386"
|
||||
;;
|
||||
"aarch64"|"arm64")
|
||||
goarch="arm64"
|
||||
;;
|
||||
"armv7l"|"armv6l"|"armv5l")
|
||||
goarch="arm"
|
||||
;;
|
||||
*)
|
||||
error "Unsupported architecture: $arch"
|
||||
;;
|
||||
esac
|
||||
|
||||
local download_url="$PATCHMON_SERVER/api/$API_VERSION/hosts/agent/go-binary?arch=$goarch"
|
||||
local temp_dir="/etc/patchmon/tmp"
|
||||
local temp_binary="$temp_dir/patchmon-agent-new"
|
||||
|
||||
# Create temp directory if it doesn't exist
|
||||
mkdir -p "$temp_dir"
|
||||
|
||||
info "Downloading Go agent binary for $goos-$goarch..."
|
||||
|
||||
# Download with API credentials
|
||||
if curl $CURL_FLAGS -H "X-API-ID: $API_ID" -H "X-API-KEY: $API_KEY" \
|
||||
-o "$temp_binary" "$download_url"; then
|
||||
|
||||
# Verify binary - check if it's a valid executable
|
||||
chmod +x "$temp_binary"
|
||||
|
||||
# Test binary by trying to run it
|
||||
if "$temp_binary" check-version >/dev/null 2>&1; then
|
||||
success "Go agent binary downloaded and verified"
|
||||
echo "$temp_binary"
|
||||
else
|
||||
# Try to get more info about the file
|
||||
local file_info=$(file "$temp_binary" 2>/dev/null || echo "unknown")
|
||||
rm -f "$temp_binary" # Clean up failed download
|
||||
error "Downloaded Go agent binary is not executable. File info: $file_info"
|
||||
fi
|
||||
else
|
||||
rm -f "$temp_binary" # Clean up failed download
|
||||
error "Failed to download Go agent binary"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install Go agent binary
|
||||
install_go_agent() {
|
||||
local temp_binary="$1"
|
||||
local install_path="/usr/local/bin/patchmon-agent"
|
||||
|
||||
info "Installing Go agent binary..."
|
||||
|
||||
# Create backup of current script if it exists
|
||||
if [[ -f "/usr/local/bin/patchmon-agent.sh" ]]; then
|
||||
local backup_path="/usr/local/bin/patchmon-agent.sh.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
cp "/usr/local/bin/patchmon-agent.sh" "$backup_path"
|
||||
info "Backed up legacy script to: $backup_path"
|
||||
fi
|
||||
|
||||
# Install new binary
|
||||
mv "$temp_binary" "$install_path"
|
||||
success "Go agent binary installed to: $install_path"
|
||||
|
||||
# Clean up the temporary file
|
||||
rm -f "$temp_binary"
|
||||
}
|
||||
|
||||
# Remove cron entries
|
||||
remove_cron_entries() {
|
||||
info "Removing legacy cron entries..."
|
||||
|
||||
# Get current crontab
|
||||
local current_crontab=$(crontab -l 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "$current_crontab" ]]; then
|
||||
# Remove any lines containing patchmon-agent
|
||||
local new_crontab=$(echo "$current_crontab" | grep -v "patchmon-agent" || true)
|
||||
|
||||
# Update crontab if it changed
|
||||
if [[ "$current_crontab" != "$new_crontab" ]]; then
|
||||
if [[ -n "$new_crontab" ]]; then
|
||||
echo "$new_crontab" | crontab -
|
||||
success "Legacy cron entries removed (kept other cron jobs)"
|
||||
else
|
||||
crontab -r 2>/dev/null || true
|
||||
success "All cron entries removed"
|
||||
fi
|
||||
else
|
||||
info "No patchmon cron entries found to remove"
|
||||
fi
|
||||
else
|
||||
info "No crontab found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure Go agent
|
||||
configure_go_agent() {
|
||||
info "Configuring Go agent..."
|
||||
|
||||
# Create necessary directories
|
||||
mkdir -p /etc/patchmon/logs
|
||||
|
||||
# Configure credentials
|
||||
if ! /usr/local/bin/patchmon-agent config set-credentials "$API_ID" "$API_KEY"; then
|
||||
warning "Failed to configure credentials via CLI, but files were created manually"
|
||||
fi
|
||||
|
||||
success "Go agent configured"
|
||||
}
|
||||
|
||||
# Test Go agent
|
||||
test_go_agent() {
|
||||
info "Testing Go agent..."
|
||||
|
||||
# Test configuration
|
||||
if /usr/local/bin/patchmon-agent config show >/dev/null 2>&1; then
|
||||
success "Go agent configuration test passed"
|
||||
else
|
||||
warning "Go agent configuration test failed, but continuing..."
|
||||
fi
|
||||
|
||||
# Test connectivity
|
||||
if /usr/local/bin/patchmon-agent ping >/dev/null 2>&1; then
|
||||
success "Go agent connectivity test passed"
|
||||
else
|
||||
warning "Go agent connectivity test failed, but continuing..."
|
||||
fi
|
||||
}
|
||||
|
||||
# Clean up temporary directory
|
||||
cleanup_temp_directory() {
|
||||
info "Cleaning up temporary files..."
|
||||
|
||||
local temp_dir="/etc/patchmon/tmp"
|
||||
if [[ -d "$temp_dir" ]]; then
|
||||
rm -rf "$temp_dir"
|
||||
success "Temporary directory cleaned up"
|
||||
fi
|
||||
}
|
||||
|
||||
# Clean up legacy files
|
||||
cleanup_legacy_files() {
|
||||
info "Cleaning up legacy files..."
|
||||
|
||||
# Remove legacy script
|
||||
if [[ -f "/usr/local/bin/patchmon-agent.sh" ]]; then
|
||||
rm -f "/usr/local/bin/patchmon-agent.sh"
|
||||
success "Removed legacy script"
|
||||
fi
|
||||
|
||||
# Remove legacy credentials file
|
||||
if [[ -f "$CREDENTIALS_FILE" ]]; then
|
||||
rm -f "$CREDENTIALS_FILE"
|
||||
success "Removed legacy credentials file"
|
||||
fi
|
||||
|
||||
# Remove legacy config file
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
rm -f "$CONFIG_FILE"
|
||||
success "Removed legacy config file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show migration summary
|
||||
show_migration_summary() {
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "PatchMon Agent Migration Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "✅ Successfully migrated from bash agent to Go agent"
|
||||
echo ""
|
||||
echo "What was done:"
|
||||
echo " • Converted credentials to YAML format"
|
||||
echo " • Created Go agent configuration"
|
||||
echo " • Downloaded and installed Go agent binary"
|
||||
echo " • Removed legacy cron entries"
|
||||
echo " • Cleaned up legacy files"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " • The Go agent runs as a service, no cron needed"
|
||||
echo " • Use: patchmon-agent serve (to run as service)"
|
||||
echo " • Use: patchmon-agent report (for one-time report)"
|
||||
echo " • Use: patchmon-agent --help (for all commands)"
|
||||
echo ""
|
||||
echo "Monitoring commands:"
|
||||
echo " • Check status: systemctl status patchmon-agent"
|
||||
echo " • View logs: tail -f /etc/patchmon/logs/patchmon-agent.log"
|
||||
echo " • Run diagnostics: patchmon-agent diagnostics"
|
||||
echo ""
|
||||
echo "Configuration files:"
|
||||
echo " • Config: /etc/patchmon/config.yml"
|
||||
echo " • Credentials: /etc/patchmon/credentials.yml"
|
||||
echo " • Logs: /etc/patchmon/logs/patchmon-agent.log"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Post-migration verification
|
||||
post_migration_check() {
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Post-Migration Verification"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check if patchmon-agent is running
|
||||
info "Checking if patchmon-agent is running..."
|
||||
if pgrep -f "patchmon-agent serve" >/dev/null 2>&1; then
|
||||
success "PatchMon agent is running"
|
||||
else
|
||||
warning "PatchMon agent is not running (this is normal if not started as service)"
|
||||
info "To start as service: patchmon-agent serve"
|
||||
fi
|
||||
|
||||
# Check WebSocket connection (if agent is running)
|
||||
if pgrep -f "patchmon-agent serve" >/dev/null 2>&1; then
|
||||
info "Checking WebSocket connection..."
|
||||
if /usr/local/bin/patchmon-agent ping >/dev/null 2>&1; then
|
||||
success "WebSocket connection is active"
|
||||
else
|
||||
warning "WebSocket connection test failed"
|
||||
fi
|
||||
else
|
||||
info "Skipping WebSocket check (agent not running)"
|
||||
fi
|
||||
|
||||
# Run diagnostics
|
||||
info "Running system diagnostics..."
|
||||
echo ""
|
||||
if /usr/local/bin/patchmon-agent diagnostics >/dev/null 2>&1; then
|
||||
success "Diagnostics completed successfully"
|
||||
echo ""
|
||||
echo "Full diagnostics output:"
|
||||
echo "----------------------------------------"
|
||||
/usr/local/bin/patchmon-agent diagnostics
|
||||
echo "----------------------------------------"
|
||||
else
|
||||
warning "Diagnostics failed to run"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Migration Verification Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
success "Thank you for using PatchMon Agent!"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main migration function
|
||||
perform_migration() {
|
||||
info "Starting PatchMon Agent migration from bash to Go..."
|
||||
echo ""
|
||||
|
||||
# Load legacy credentials
|
||||
load_legacy_credentials
|
||||
|
||||
# Convert credentials
|
||||
convert_credentials_to_yaml
|
||||
|
||||
# Create Go agent config
|
||||
create_go_agent_config
|
||||
|
||||
# Download Go agent
|
||||
local temp_binary=$(download_go_agent)
|
||||
|
||||
# Install Go agent
|
||||
install_go_agent "$temp_binary"
|
||||
|
||||
# Remove cron entries
|
||||
remove_cron_entries
|
||||
|
||||
# Configure Go agent
|
||||
configure_go_agent
|
||||
|
||||
# Test Go agent
|
||||
test_go_agent
|
||||
|
||||
# Clean up legacy files
|
||||
cleanup_legacy_files
|
||||
|
||||
# Clean up temporary directory
|
||||
cleanup_temp_directory
|
||||
|
||||
# Show summary
|
||||
show_migration_summary
|
||||
|
||||
# Run post-migration verification
|
||||
post_migration_check
|
||||
|
||||
success "Migration completed successfully!"
|
||||
|
||||
# Exit here to prevent the legacy script from continuing
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Handle command line arguments
|
||||
case "$1" in
|
||||
"migrate")
|
||||
check_root
|
||||
perform_migration
|
||||
;;
|
||||
"test")
|
||||
check_root
|
||||
load_legacy_credentials
|
||||
test_go_agent
|
||||
;;
|
||||
"update-agent")
|
||||
# This is called by legacy agents during update
|
||||
check_root
|
||||
perform_migration
|
||||
;;
|
||||
*)
|
||||
# If no arguments provided, check if we're being executed by a legacy agent
|
||||
# Legacy agents will call this script directly during update
|
||||
if [[ -f "$CREDENTIALS_FILE" ]]; then
|
||||
info "Detected legacy agent execution - starting migration..."
|
||||
check_root
|
||||
perform_migration
|
||||
else
|
||||
echo "PatchMon Agent Migration Script v$AGENT_VERSION"
|
||||
echo "Usage: $0 {migrate|test|update-agent}"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " migrate - Perform full migration from bash to Go agent"
|
||||
echo " test - Test Go agent after migration"
|
||||
echo " update-agent - Called by legacy agents during update"
|
||||
echo ""
|
||||
echo "This script should be executed by the legacy agent during update."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
@@ -14,7 +14,7 @@ const {
|
||||
const router = express.Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Secure endpoint to download the agent binary (requires API authentication)
|
||||
// Secure endpoint to download the agent script/binary (requires API authentication)
|
||||
router.get("/agent/download", async (req, res) => {
|
||||
try {
|
||||
// Verify API credentials
|
||||
@@ -34,21 +34,125 @@ router.get("/agent/download", async (req, res) => {
|
||||
return res.status(401).json({ error: "Invalid API credentials" });
|
||||
}
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
// Check if this is a legacy agent (bash script) requesting update
|
||||
// Legacy agents will have agent_version < 1.3.0
|
||||
const isLegacyAgent =
|
||||
host.agent_version &&
|
||||
(host.agent_version.startsWith("1.2.") ||
|
||||
host.agent_version.startsWith("1.1.") ||
|
||||
host.agent_version.startsWith("1.0.") ||
|
||||
!host.agent_version);
|
||||
|
||||
if (isLegacyAgent) {
|
||||
// Serve migration script for legacy agents
|
||||
const migrationScriptPath = path.join(
|
||||
__dirname,
|
||||
"../../../agents/patchmon-agent.sh",
|
||||
);
|
||||
|
||||
if (!fs.existsSync(migrationScriptPath)) {
|
||||
return res.status(404).json({ error: "Migration script not found" });
|
||||
}
|
||||
|
||||
// Set appropriate headers for script download
|
||||
res.setHeader("Content-Type", "text/plain");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="patchmon-agent.sh"',
|
||||
);
|
||||
|
||||
// Stream the migration script
|
||||
const fileStream = fs.createReadStream(migrationScriptPath);
|
||||
fileStream.pipe(res);
|
||||
|
||||
fileStream.on("error", (error) => {
|
||||
console.error("Migration script stream error:", error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: "Failed to stream migration script" });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Serve Go binary for new agents
|
||||
const architecture = req.query.arch || "amd64";
|
||||
|
||||
// Validate architecture
|
||||
const validArchitectures = ["amd64", "386", "arm64", "arm"];
|
||||
if (!validArchitectures.includes(architecture)) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid architecture. Must be one of: amd64, 386, arm64, arm",
|
||||
});
|
||||
}
|
||||
|
||||
const binaryName = `patchmon-agent-linux-${architecture}`;
|
||||
const binaryPath = path.join(__dirname, "../../../agents", binaryName);
|
||||
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
return res.status(404).json({
|
||||
error: `Agent binary not found for architecture: ${architecture}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Set appropriate headers for binary download
|
||||
res.setHeader("Content-Type", "application/octet-stream");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="${binaryName}"`,
|
||||
);
|
||||
|
||||
// Stream the binary file
|
||||
const fileStream = fs.createReadStream(binaryPath);
|
||||
fileStream.pipe(res);
|
||||
|
||||
fileStream.on("error", (error) => {
|
||||
console.error("Binary stream error:", error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: "Failed to stream agent binary" });
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Agent download error:", error);
|
||||
res.status(500).json({ error: "Failed to serve agent" });
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint to download Go agent binary (for migration script)
|
||||
router.get("/agent/go-binary", async (req, res) => {
|
||||
try {
|
||||
// Verify API credentials
|
||||
const apiId = req.headers["x-api-id"];
|
||||
const apiKey = req.headers["x-api-key"];
|
||||
|
||||
if (!apiId || !apiKey) {
|
||||
return res.status(401).json({ error: "API credentials required" });
|
||||
}
|
||||
|
||||
// Validate API credentials
|
||||
const host = await prisma.hosts.findUnique({
|
||||
where: { api_id: apiId },
|
||||
});
|
||||
|
||||
if (!host || host.api_key !== apiKey) {
|
||||
return res.status(401).json({ error: "Invalid API credentials" });
|
||||
}
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
// Get architecture parameter (default to amd64)
|
||||
const architecture = req.query.arch || "amd64";
|
||||
|
||||
// Validate architecture
|
||||
const validArchitectures = ["amd64", "386", "arm64"];
|
||||
const validArchitectures = ["amd64", "386", "arm64", "arm"];
|
||||
if (!validArchitectures.includes(architecture)) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid architecture. Must be one of: amd64, 386, arm64",
|
||||
error: "Invalid architecture. Must be one of: amd64, 386, arm64, arm",
|
||||
});
|
||||
}
|
||||
|
||||
// Serve agent binary directly from file system
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const binaryName = `patchmon-agent-linux-${architecture}`;
|
||||
const binaryPath = path.join(__dirname, "../../../agents", binaryName);
|
||||
|
||||
@@ -76,8 +180,8 @@ router.get("/agent/download", async (req, res) => {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Agent download error:", error);
|
||||
res.status(500).json({ error: "Failed to serve agent binary" });
|
||||
console.error("Go binary download error:", error);
|
||||
res.status(500).json({ error: "Failed to serve Go agent binary" });
|
||||
}
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user