Compare commits

...

10 Commits

Author SHA1 Message Date
Muhammad Ibrahim
f0b028cb77 alpine support on the agent installation script 2025-11-08 22:00:34 +00:00
Muhammad Ibrahim
8c2d4aa42b alpine support (apk) support agents 2025-11-08 21:15:08 +00:00
Muhammad Ibrahim
082ceed27c arm support 2025-11-08 12:23:50 +00:00
9 Technology Group LTD
5a3938d7fc Merge pull request #291 from PatchMon/1-3-3
fixed env flag issue when auto-enrolling into proxmox
2025-11-08 09:35:52 +00:00
Muhammad Ibrahim
eb433719dd fixed env flag issue when auto-enrolling into proxmox 2025-11-08 09:34:39 +00:00
9 Technology Group LTD
106ab6f5f8 Merge pull request #290 from PatchMon/1-3-3
new agent files for 1.3.3
2025-11-07 23:07:49 +00:00
Muhammad Ibrahim
148ff2e77f new agent files for 1.3.3 2025-11-07 23:02:53 +00:00
9 Technology Group LTD
a9e4349f5f Merge pull request #289 from PatchMon/1-3-3
1 3 3
2025-11-07 22:33:28 +00:00
Muhammad Ibrahim
a655a24f2f 1.3.3 agents 2025-11-07 22:32:11 +00:00
Muhammad Ibrahim
417f6deccf 1.3.3 version changes 2025-11-07 22:29:13 +00:00
11 changed files with 265 additions and 82 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,7 +1,32 @@
#!/bin/bash
#!/bin/sh
# PatchMon Agent Installation Script
# This script requires bash for full functionality
# Usage: curl -s {PATCHMON_URL}/api/v1/hosts/install -H "X-API-ID: {API_ID}" -H "X-API-KEY: {API_KEY}" | sh
# Check if bash is available, if not try to install it (for Alpine Linux)
if ! command -v bash >/dev/null 2>&1; then
if command -v apk >/dev/null 2>&1; then
echo "Installing bash for script compatibility..."
apk add --no-cache bash >/dev/null 2>&1 || true
fi
fi
# If bash is available and we're not already running in bash, switch to bash
# When piped, we can't re-execute easily, so we'll continue with sh
# but ensure bash is available for bash-specific features
if command -v bash >/dev/null 2>&1 && [ -z "${BASH_VERSION:-}" ]; then
# Check if we're being piped (stdin is not a terminal)
if [ -t 0 ]; then
# Direct execution, re-execute with bash
exec bash "$0" "$@"
exit $?
fi
# When piped, we continue with sh but bash is now available
# The script will use bash-specific features which should work if bash is installed
fi
# PatchMon Agent Installation Script
# Usage: curl -s {PATCHMON_URL}/api/v1/hosts/install -H "X-API-ID: {API_ID}" -H "X-API-KEY: {API_KEY}" | bash
# Usage: curl -s {PATCHMON_URL}/api/v1/hosts/install -H "X-API-ID: {API_ID}" -H "X-API-KEY: {API_KEY}" | sh
set -e
@@ -36,7 +61,7 @@ warning() {
}
# Check if running as root
if [[ $EUID -ne 0 ]]; then
if [ "$(id -u)" -ne 0 ]; then
error "This script must be run as root (use sudo)"
fi
@@ -45,8 +70,8 @@ verify_datetime() {
info "🕐 Verifying system datetime and timezone..."
# Get current system time
local system_time=$(date)
local timezone=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "Unknown")
system_time=$(date)
timezone=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "Unknown")
# Display current datetime info
echo ""
@@ -56,14 +81,17 @@ verify_datetime() {
echo ""
# Check if we can read from stdin (interactive terminal)
if [[ -t 0 ]]; then
if [ -t 0 ]; then
# Interactive terminal - ask user
read -p "Does this date/time look correct to you? (y/N): " -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
printf "Does this date/time look correct to you? (y/N): "
read -r response
case "$response" in
[Yy]*)
success "✅ Date/time verification passed"
echo ""
return 0
else
;;
*)
echo ""
echo -e "${RED}❌ Date/time verification failed${NC}"
echo ""
@@ -74,7 +102,8 @@ verify_datetime() {
echo ""
echo -e "${BLUE} After fixing the date/time, re-run this installation script.${NC}"
error "Installation cancelled - please fix date/time and re-run"
fi
;;
esac
else
# Non-interactive (piped from curl) - show warning and continue
echo -e "${YELLOW}⚠️ Non-interactive installation detected${NC}"
@@ -121,9 +150,9 @@ cleanup_old_files
# Generate or retrieve machine ID
get_machine_id() {
# Try multiple sources for machine ID
if [[ -f /etc/machine-id ]]; then
if [ -f /etc/machine-id ]; then
cat /etc/machine-id
elif [[ -f /var/lib/dbus/machine-id ]]; then
elif [ -f /var/lib/dbus/machine-id ]; then
cat /var/lib/dbus/machine-id
else
# Fallback: generate from hardware info (less ideal but works)
@@ -132,12 +161,12 @@ get_machine_id() {
}
# Parse arguments from environment (passed via HTTP headers)
if [[ -z "$PATCHMON_URL" ]] || [[ -z "$API_ID" ]] || [[ -z "$API_KEY" ]]; then
if [ -z "$PATCHMON_URL" ] || [ -z "$API_ID" ] || [ -z "$API_KEY" ]; then
error "Missing required parameters. This script should be called via the PatchMon web interface."
fi
# Auto-detect architecture if not explicitly set
if [[ -z "$ARCHITECTURE" ]]; then
if [ -z "$ARCHITECTURE" ]; then
arch_raw=$(uname -m 2>/dev/null || echo "unknown")
# Map architecture to supported values
@@ -162,13 +191,16 @@ if [[ -z "$ARCHITECTURE" ]]; then
fi
# Validate architecture
if [[ "$ARCHITECTURE" != "amd64" && "$ARCHITECTURE" != "386" && "$ARCHITECTURE" != "arm64" && "$ARCHITECTURE" != "arm" ]]; then
if [ "$ARCHITECTURE" != "amd64" ] && [ "$ARCHITECTURE" != "386" ] && [ "$ARCHITECTURE" != "arm64" ] && [ "$ARCHITECTURE" != "arm" ]; then
error "Invalid architecture '$ARCHITECTURE'. Must be one of: amd64, 386, arm64, arm"
fi
# Check if --force flag is set (for bypassing broken packages)
FORCE_INSTALL="${FORCE_INSTALL:-false}"
if [[ "$*" == *"--force"* ]] || [[ "$FORCE_INSTALL" == "true" ]]; then
case "$*" in
*"--force"*) FORCE_INSTALL="true" ;;
esac
if [ "$FORCE_INSTALL" = "true" ]; then
FORCE_INSTALL="true"
warning "⚠️ Force mode enabled - will bypass broken packages"
fi
@@ -224,7 +256,7 @@ install_apt_packages() {
# Build apt-get command based on force mode
local apt_cmd="apt-get install ${missing_packages[*]} -y"
if [[ "$FORCE_INSTALL" == "true" ]]; then
if [ "$FORCE_INSTALL" = "true" ]; then
info "Using force mode - bypassing broken packages..."
apt_cmd="$apt_cmd -o APT::Get::Fix-Broken=false -o DPkg::Options::=\"--force-confold\" -o DPkg::Options::=\"--force-confdef\""
fi
@@ -240,7 +272,7 @@ install_apt_packages() {
local all_ok=true
for pkg in "${packages[@]}"; do
if ! command_exists "$pkg"; then
if [[ "$FORCE_INSTALL" == "true" ]]; then
if [ "$FORCE_INSTALL" = "true" ]; then
error "Critical dependency '$pkg' is not available even with --force. Please install manually."
else
error "Critical dependency '$pkg' is not available. Try again with --force flag or install manually: apt-get install $pkg"
@@ -279,7 +311,7 @@ install_yum_dnf_packages() {
info "Need to install: ${missing_packages[*]}"
if [[ "$pkg_manager" == "yum" ]]; then
if [ "$pkg_manager" = "yum" ]; then
yum install -y "${missing_packages[@]}"
else
dnf install -y "${missing_packages[@]}"
@@ -346,7 +378,41 @@ install_apk_packages() {
fi
info "Need to install: ${missing_packages[*]}"
apk add --no-cache "${missing_packages[@]}"
# Update package index before installation
info "Updating package index..."
apk update -q || true
# Build apk command
local apk_cmd="apk add --no-cache ${missing_packages[*]}"
# Try to install packages
if eval "$apk_cmd" 2>&1 | tee /tmp/patchmon_apk_install.log; then
success "Packages installed successfully"
return 0
else
warning "Package installation encountered issues, checking if required tools are available..."
# Verify critical dependencies are actually available
local all_ok=true
for pkg in "${packages[@]}"; do
if ! command_exists "$pkg"; then
if [ "$FORCE_INSTALL" = "true" ]; then
error "Critical dependency '$pkg' is not available even with --force. Please install manually."
else
error "Critical dependency '$pkg' is not available. Try again with --force flag or install manually: apk add $pkg"
fi
all_ok=false
fi
done
if $all_ok; then
success "All required tools are available despite installation warnings"
return 0
else
return 1
fi
fi
}
# Detect package manager and install jq, curl, and bc
@@ -357,7 +423,7 @@ if command -v apt-get >/dev/null 2>&1; then
# Check for broken packages
if dpkg -l | grep -q "^iH\|^iF" 2>/dev/null; then
if [[ "$FORCE_INSTALL" == "true" ]]; then
if [ "$FORCE_INSTALL" = "true" ]; then
warning "Detected broken packages on system - force mode will work around them"
else
warning "⚠️ Broken packages detected on system"
@@ -412,7 +478,7 @@ echo ""
info "📁 Setting up configuration directory..."
# Check if configuration directory already exists
if [[ -d "/etc/patchmon" ]]; then
if [ -d "/etc/patchmon" ]; then
warning "⚠️ Configuration directory already exists at /etc/patchmon"
warning "⚠️ Preserving existing configuration files"
@@ -429,8 +495,8 @@ fi
# Check if agent is already configured and working (before we overwrite anything)
info "🔍 Checking if agent is already configured..."
if [[ -f /etc/patchmon/config.yml ]] && [[ -f /etc/patchmon/credentials.yml ]]; then
if [[ -f /usr/local/bin/patchmon-agent ]]; then
if [ -f /etc/patchmon/config.yml ] && [ -f /etc/patchmon/credentials.yml ]; then
if [ -f /usr/local/bin/patchmon-agent ]; then
info "📋 Found existing agent configuration"
info "🧪 Testing existing configuration with ping..."
@@ -461,7 +527,7 @@ fi
info "🔐 Creating configuration files..."
# Check if config file already exists
if [[ -f "/etc/patchmon/config.yml" ]]; then
if [ -f "/etc/patchmon/config.yml" ]; then
warning "⚠️ Config file already exists at /etc/patchmon/config.yml"
warning "⚠️ Moving existing file out of the way for fresh installation"
@@ -474,7 +540,7 @@ if [[ -f "/etc/patchmon/config.yml" ]]; then
fi
# Check if credentials file already exists
if [[ -f "/etc/patchmon/credentials.yml" ]]; then
if [ -f "/etc/patchmon/credentials.yml" ]; then
warning "⚠️ Credentials file already exists at /etc/patchmon/credentials.yml"
warning "⚠️ Moving existing file out of the way for fresh installation"
@@ -487,7 +553,7 @@ if [[ -f "/etc/patchmon/credentials.yml" ]]; then
fi
# Clean up old credentials file if it exists (from previous installations)
if [[ -f "/etc/patchmon/credentials" ]]; then
if [ -f "/etc/patchmon/credentials" ]; then
warning "⚠️ Found old credentials file, removing it..."
rm -f /etc/patchmon/credentials
info "📋 Removed old credentials file"
@@ -523,7 +589,7 @@ info "📥 Downloading PatchMon agent binary..."
BINARY_NAME="patchmon-agent-linux-${ARCHITECTURE}"
# Check if agent binary already exists
if [[ -f "/usr/local/bin/patchmon-agent" ]]; then
if [ -f "/usr/local/bin/patchmon-agent" ]; then
warning "⚠️ Agent binary already exists at /usr/local/bin/patchmon-agent"
warning "⚠️ Moving existing file out of the way for fresh installation"
@@ -536,7 +602,7 @@ if [[ -f "/usr/local/bin/patchmon-agent" ]]; then
fi
# Clean up old shell script if it exists (from previous installations)
if [[ -f "/usr/local/bin/patchmon-agent.sh" ]]; then
if [ -f "/usr/local/bin/patchmon-agent.sh" ]; then
warning "⚠️ Found old shell script agent, removing it..."
rm -f /usr/local/bin/patchmon-agent.sh
info "📋 Removed old shell script agent"
@@ -562,7 +628,7 @@ info "📁 Setting up log directory..."
mkdir -p /etc/patchmon/logs
# Handle existing log files
if [[ -f "/etc/patchmon/logs/patchmon-agent.log" ]]; then
if [ -f "/etc/patchmon/logs/patchmon-agent.log" ]; then
warning "⚠️ Existing log file found at /etc/patchmon/logs/patchmon-agent.log"
warning "⚠️ Rotating log file for fresh start"
@@ -579,23 +645,26 @@ else
error "❌ Failed to validate API credentials or reach server"
fi
# Step 5: Setup systemd service for WebSocket connection
# Step 5: Setup service for WebSocket connection
# Note: The service will automatically send an initial report on startup (see serve.go)
info "🔧 Setting up systemd service..."
# Detect init system and create appropriate service
if command -v systemctl >/dev/null 2>&1; then
# Systemd is available
info "🔧 Setting up systemd service..."
# Stop and disable existing service if it exists
if systemctl is-active --quiet patchmon-agent.service 2>/dev/null; then
# Stop and disable existing service if it exists
if systemctl is-active --quiet patchmon-agent.service 2>/dev/null; then
warning "⚠️ Stopping existing PatchMon agent service..."
systemctl stop patchmon-agent.service
fi
fi
if systemctl is-enabled --quiet patchmon-agent.service 2>/dev/null; then
if systemctl is-enabled --quiet patchmon-agent.service 2>/dev/null; then
warning "⚠️ Disabling existing PatchMon agent service..."
systemctl disable patchmon-agent.service
fi
fi
# Create systemd service file
cat > /etc/systemd/system/patchmon-agent.service << EOF
# Create systemd service file
cat > /etc/systemd/system/patchmon-agent.service << EOF
[Unit]
Description=PatchMon Agent Service
After=network.target
@@ -618,24 +687,104 @@ SyslogIdentifier=patchmon-agent
WantedBy=multi-user.target
EOF
# Clean up old crontab entries if they exist (from previous installations)
if crontab -l 2>/dev/null | grep -q "patchmon-agent"; then
# Clean up old crontab entries if they exist (from previous installations)
if crontab -l 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Found old crontab entries, removing them..."
crontab -l 2>/dev/null | grep -v "patchmon-agent" | crontab -
info "📋 Removed old crontab entries"
fi
fi
# Reload systemd and enable/start the service
systemctl daemon-reload
systemctl enable patchmon-agent.service
systemctl start patchmon-agent.service
# Reload systemd and enable/start the service
systemctl daemon-reload
systemctl enable patchmon-agent.service
systemctl start patchmon-agent.service
# Check if service started successfully
if systemctl is-active --quiet patchmon-agent.service; then
# Check if service started successfully
if systemctl is-active --quiet patchmon-agent.service; then
success "✅ PatchMon Agent service started successfully"
info "🔗 WebSocket connection established"
else
else
warning "⚠️ Service may have failed to start. Check status with: systemctl status patchmon-agent"
fi
SERVICE_TYPE="systemd"
elif [ -d /etc/init.d ] && command -v rc-service >/dev/null 2>&1; then
# OpenRC is available (Alpine Linux)
info "🔧 Setting up OpenRC service..."
# Stop and disable existing service if it exists
if rc-service patchmon-agent status >/dev/null 2>&1; then
warning "⚠️ Stopping existing PatchMon agent service..."
rc-service patchmon-agent stop
fi
if rc-update show default 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Disabling existing PatchMon agent service..."
rc-update del patchmon-agent default
fi
# Create OpenRC service file
cat > /etc/init.d/patchmon-agent << 'EOF'
#!/sbin/openrc-run
name="patchmon-agent"
description="PatchMon Agent Service"
command="/usr/local/bin/patchmon-agent"
command_args="serve"
command_user="root"
pidfile="/var/run/patchmon-agent.pid"
command_background="yes"
working_dir="/etc/patchmon"
depend() {
need net
after net
}
EOF
chmod +x /etc/init.d/patchmon-agent
# Clean up old crontab entries if they exist (from previous installations)
if crontab -l 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Found old crontab entries, removing them..."
crontab -l 2>/dev/null | grep -v "patchmon-agent" | crontab -
info "📋 Removed old crontab entries"
fi
# Enable and start the service
rc-update add patchmon-agent default
rc-service patchmon-agent start
# Check if service started successfully
if rc-service patchmon-agent status >/dev/null 2>&1; then
success "✅ PatchMon Agent service started successfully"
info "🔗 WebSocket connection established"
else
warning "⚠️ Service may have failed to start. Check status with: rc-service patchmon-agent status"
fi
SERVICE_TYPE="openrc"
else
# No init system detected, use crontab as fallback
warning "⚠️ No init system detected (systemd or OpenRC). Using crontab for service management."
# Clean up old crontab entries if they exist
if crontab -l 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Found old crontab entries, removing them..."
crontab -l 2>/dev/null | grep -v "patchmon-agent" | crontab -
info "📋 Removed old crontab entries"
fi
# Add crontab entry to run the agent
(crontab -l 2>/dev/null; echo "@reboot /usr/local/bin/patchmon-agent serve >/dev/null 2>&1") | crontab -
info "📋 Added crontab entry for PatchMon agent"
# Start the agent manually
/usr/local/bin/patchmon-agent serve >/dev/null 2>&1 &
success "✅ PatchMon Agent started in background"
info "🔗 WebSocket connection established"
SERVICE_TYPE="crontab"
fi
# Installation complete
@@ -646,14 +795,20 @@ echo " • Configuration directory: /etc/patchmon"
echo " • Agent binary installed: /usr/local/bin/patchmon-agent"
echo " • Architecture: $ARCHITECTURE"
echo " • Dependencies installed: jq, curl, bc"
echo " • Systemd service configured and running"
if [ "$SERVICE_TYPE" = "systemd" ]; then
echo " • Systemd service configured and running"
elif [ "$SERVICE_TYPE" = "openrc" ]; then
echo " • OpenRC service configured and running"
else
echo " • Service configured via crontab"
fi
echo " • API credentials configured and tested"
echo " • WebSocket connection established"
echo " • Logs directory: /etc/patchmon/logs"
# Check for moved files and show them
MOVED_FILES=$(ls /etc/patchmon/credentials.yml.backup.* /etc/patchmon/config.yml.backup.* /usr/local/bin/patchmon-agent.backup.* /etc/patchmon/logs/patchmon-agent.log.old.* /usr/local/bin/patchmon-agent.sh.backup.* /etc/patchmon/credentials.backup.* 2>/dev/null || true)
if [[ -n "$MOVED_FILES" ]]; then
if [ -n "$MOVED_FILES" ]; then
echo ""
echo -e "${YELLOW}📋 Files Moved for Fresh Installation:${NC}"
echo "$MOVED_FILES" | while read -r moved_file; do
@@ -668,8 +823,17 @@ echo -e "${BLUE}🔧 Management Commands:${NC}"
echo " • Test connection: /usr/local/bin/patchmon-agent ping"
echo " • Manual report: /usr/local/bin/patchmon-agent report"
echo " • Check status: /usr/local/bin/patchmon-agent diagnostics"
echo " • Service status: systemctl status patchmon-agent"
echo " • Service logs: journalctl -u patchmon-agent -f"
echo " • Restart service: systemctl restart patchmon-agent"
if [ "$SERVICE_TYPE" = "systemd" ]; then
echo " • Service status: systemctl status patchmon-agent"
echo " • Service logs: journalctl -u patchmon-agent -f"
echo " • Restart service: systemctl restart patchmon-agent"
elif [ "$SERVICE_TYPE" = "openrc" ]; then
echo " • Service status: rc-service patchmon-agent status"
echo " • Service logs: tail -f /etc/patchmon/logs/patchmon-agent.log"
echo " • Restart service: rc-service patchmon-agent restart"
else
echo " • Service logs: tail -f /etc/patchmon/logs/patchmon-agent.log"
echo " • Restart service: pkill -f 'patchmon-agent serve' && /usr/local/bin/patchmon-agent serve &"
fi
echo ""
success "✅ Your system is now being monitored by PatchMon!"

View File

@@ -318,7 +318,8 @@ while IFS= read -r line; do
# Download and execute in separate steps to avoid stdin issues with piping
# Pass CURL_FLAGS as environment variable to container
install_output=$(timeout 180 pct exec "$vmid" --env CURL_FLAGS="$CURL_FLAGS" -- bash -c "
install_output=$(timeout 180 pct exec "$vmid" -- bash -c "
export CURL_FLAGS='$CURL_FLAGS'
cd /tmp
curl \$CURL_FLAGS \
-H \"X-API-ID: $api_id\" \
@@ -458,7 +459,8 @@ if [[ ${#dpkg_error_containers[@]} -gt 0 ]]; then
install_exit_code=0
# Pass CURL_FLAGS as environment variable to container
install_output=$(timeout 180 pct exec "$vmid" --env CURL_FLAGS="$CURL_FLAGS" -- bash -c "
install_output=$(timeout 180 pct exec "$vmid" -- bash -c "
export CURL_FLAGS='$CURL_FLAGS'
cd /tmp
curl \$CURL_FLAGS \
-H \"X-API-ID: $api_id\" \

View File

@@ -1,6 +1,6 @@
{
"name": "patchmon-backend",
"version": "1.3.2",
"version": "1.3.3",
"description": "Backend API for Linux Patch Monitoring System",
"license": "AGPL-3.0",
"main": "src/server.js",

View File

@@ -1,6 +1,7 @@
const axios = require("axios");
const fs = require("node:fs").promises;
const path = require("node:path");
const os = require("node:os");
const { exec, spawn } = require("node:child_process");
const { promisify } = require("node:util");
const _execAsync = promisify(exec);
@@ -106,10 +107,26 @@ class AgentVersionService {
try {
console.log("🔍 Getting current agent version...");
// Try to find the agent binary in agents/ folder only (what gets distributed)
// Detect server architecture and map to Go architecture names
const serverArch = os.arch();
// Map Node.js architecture to Go architecture names
const archMap = {
x64: "amd64",
ia32: "386",
arm64: "arm64",
arm: "arm",
};
const serverGoArch = archMap[serverArch] || serverArch;
console.log(
`🔍 Detected server architecture: ${serverArch} -> ${serverGoArch}`,
);
// Try to find the agent binary in agents/ folder based on server architecture
const possiblePaths = [
path.join(this.agentsDir, "patchmon-agent-linux-amd64"),
path.join(this.agentsDir, "patchmon-agent"),
path.join(this.agentsDir, `patchmon-agent-linux-${serverGoArch}`),
path.join(this.agentsDir, "patchmon-agent-linux-amd64"), // Fallback
path.join(this.agentsDir, "patchmon-agent"), // Legacy fallback
];
let agentPath = null;
@@ -126,7 +143,7 @@ class AgentVersionService {
if (!agentPath) {
console.log(
"⚠️ No agent binary found in agents/ folder, current version will be unknown",
`⚠️ No agent binary found in agents/ folder for architecture ${serverGoArch}, current version will be unknown`,
);
console.log("💡 Use the Download Updates button to get agent binaries");
this.currentVersion = null;

View File

@@ -1,7 +1,7 @@
{
"name": "patchmon-frontend",
"private": true,
"version": "1.3.2",
"version": "1.3.3",
"license": "AGPL-3.0",
"type": "module",
"scripts": {

View File

@@ -1,6 +1,6 @@
{
"name": "patchmon",
"version": "1.3.2",
"version": "1.3.3",
"description": "Linux Patch Monitoring System",
"license": "AGPL-3.0",
"private": true,

View File

@@ -34,7 +34,7 @@ BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Global variables
SCRIPT_VERSION="self-hosting-install.sh v1.3.2-selfhost-2025-10-31-1"
SCRIPT_VERSION="self-hosting-install.sh v1.3.3-selfhost-2025-11-07"
DEFAULT_GITHUB_REPO="https://github.com/PatchMon/PatchMon.git"
FQDN=""
CUSTOM_FQDN=""