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:
Muhammad Ibrahim
2025-10-18 17:28:46 +01:00
parent fba6d0ede5
commit 3175ed79a5
6 changed files with 573 additions and 9 deletions

Binary file not shown.

Binary file not shown.

BIN
agents/patchmon-agent-linux-arm Executable file

Binary file not shown.

Binary file not shown.

460
agents/patchmon-agent.sh Executable file
View 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

View File

@@ -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" });
}
});