Skip to main content

Proxmox LXC Auto-Enrollment Guide

Proxmox LXC Auto-Enrollment Guide

Overview

PatchMon's Proxmox Auto-Enrollment feature enables you to automatically discover and enroll LXC containers from your Proxmox hosts into PatchMon for centralized patch management. This eliminates manual host registration and ensures comprehensive coverage of your Proxmox infrastructure.

What It Does

  • Automatically discovers running LXC containers on Proxmox hosts
  • Bulk enrolls containers into PatchMon without manual intervention
  • Installs agents inside each container automatically
  • Assigns to host groups based on token configuration
  • Tracks enrollment with full audit logging

Key Benefits

Zero-Touch Enrollment - Run once, enroll all containers
Secure by Design - Token-based authentication with hashed secrets
Rate Limited - Prevents abuse with per-day host limits
IP Restricted - Optional IP whitelisting for enhanced security
Fully Auditable - Tracks who enrolled what and when
Safe to Rerun - Already-enrolled containers are automatically skipped

Table of Contents

How It Works

Architecture Overview

┌─────────────────────┐
│   PatchMon Admin    │
│                     │
│  1. Creates Token   │
│  2. Gets Key/Secret │
└──────────┬──────────┘
           │
           ├─────────────────────────────────┐
           ▼                                 ▼
┌─────────────────────┐          ┌─────────────────────┐
│  Proxmox Host       │          │   PatchMon Server   │
│                     │          │                     │
│  3. Runs Script ────┼──────────▶  4. Validates Token │
│  4. Discovers LXCs  │          │  5. Creates Hosts   │
│  5. Gets Credentials│◀─────────┤  6. Returns Creds   │
│  6. Installs Agents │          │                     │
└──────────┬──────────┘          └─────────────────────┘
           │
           ▼
┌─────────────────────┐
│   LXC Containers    │
│                     │
│  • curl installed   │
│  • Agent installed  │
│  • Reporting to PM  │
└─────────────────────┘

Enrollment Process (Step by Step)

  1. Admin creates auto-enrollment token in PatchMon UI

    • Configures rate limits, IP restrictions, host group assignment
    • Receives token_key and token_secret (shown only once!)
  2. Admin runs enrollment script on Proxmox host

    • Script authenticated with auto-enrollment token
    • Discovers all running LXC containers using pct list
  3. For each container, the script:

    • Gathers hostname, IP address, OS information, machine ID
    • Calls PatchMon API to create host entry
    • Receives unique api_id and api_key for that container
    • Uses pct exec to enter the container
    • Installs curl if missing
    • Downloads and runs PatchMon agent installer
    • Agent authenticates with container-specific credentials
  4. Containers appear in PatchMon with full patch tracking enabled

Two-Tier Security Model

1. Auto-Enrollment Token (Script → PatchMon)

  • Purpose: Create new host entries
  • Scope: Limited to enrollment operations only
  • Storage: Secret is hashed in database
  • Lifespan: Reusable until revoked/expired
  • Security: Rate limits + IP restrictions

2. Host API Credentials (Agent → PatchMon)

  • Purpose: Report patches, send data, receive commands
  • Scope: Per-host unique credentials
  • Storage: Plain text in database (per-host scope limits risk)
  • Lifespan: Permanent for that host
  • Security: Host-specific, can be regenerated

Why This Matters:

  • Compromised enrollment token ≠ compromised hosts
  • Compromised host credential ≠ compromised enrollment
  • Revoked enrollment token = no new enrollments (existing hosts unaffected)
  • Lost credentials = create new token, don't affect existing infrastructure

Prerequisites

PatchMon Server Requirements

  • ✅ PatchMon version with auto-enrollment support
  • ✅ Admin user with "Manage Settings" permission
  • ✅ Network accessible from Proxmox hosts

Proxmox Host Requirements

  • ✅ Proxmox VE installed and running
  • ✅ One or more LXC containers (VMs not supported)
  • ✅ Root access to Proxmox host
  • ✅ Network connectivity to PatchMon server
  • ✅ Required commands: pct, curl, jq, bash

Container Requirements

  • ✅ Running state (stopped containers are skipped)
  • ✅ Debian-based or RPM-based Linux distribution
  • ✅ Network connectivity to PatchMon server
  • ✅ Package manager (apt/yum/dnf) functional

Network Requirements

Source Destination Port Protocol Purpose
Proxmox Host PatchMon Server 443 (HTTPS) TCP Enrollment API calls
LXC Containers PatchMon Server 443 (HTTPS) TCP Agent installation & reporting

Firewall Notes:

  • Outbound only connections (no inbound ports needed)
  • HTTPS recommended (HTTP supported for internal networks)
  • Self-signed certificates supported with -k flag

Quick Start

1. Create Token (In PatchMon UI)

  1. Go to Settings → Integrations → Proxmox LXC tab
  2. Click "New Token"
  3. Configure:
    • Name: "Production Proxmox"
    • Max Hosts/Day: 100
    • Host Group: Select target group
    • IP Restriction: Your Proxmox host IP
  4. Save credentials immediately (shown only once!)

2. One-Line Enrollment (On Proxmox Host)

curl -s "https://patchmon.example.com/api/v1/auto-enrollment/proxmox-lxc?token_key=YOUR_KEY&token_secret=YOUR_SECRET" | bash

That's it! All running LXC containers will be enrolled and the PatchMon agent installed.

3. Verify in PatchMon

  • Go to Hosts page
  • See your containers listed with "pending" status
  • Wait for first agent check-in (~60 seconds)
  • Status changes to "active" with package data

Step-by-Step Setup

Step 1: Create Auto-Enrollment Token

Via PatchMon Web UI

  1. Log in to PatchMon as an administrator

  2. Navigate to Settings

    Dashboard → Settings → Integrations → Proxmox LXC tab
    
  3. Click "New Token" button

  4. Fill in token details:

    Field Value Required Description
    Token Name Proxmox Production Yes Descriptive name for this token
    Max Hosts Per Day 100 Yes Rate limit (1-1000)
    Default Host Group Proxmox LXC No Auto-assign enrolled hosts
    Allowed IP Addresses 192.168.1.10 No Comma-separated IPs
    Expiration Date 2026-01-01 No Auto-disable after date
  5. Click "Create Token"

  6. ⚠️ CRITICAL: Save Credentials Now!

    You'll see a success modal with:

    Token Key:    patchmon_ae_a1b2c3d4e5f6...
    Token Secret: 8f7e6d5c4b3a2f1e0d9c8b7a...
    

    Copy both values immediately! They cannot be retrieved later.

    Pro Tip: Copy the one-line installation command shown in the modal - it has credentials pre-filled.

Step 2: Prepare Proxmox Host

Install Required Dependencies

# SSH to your Proxmox host
ssh root@proxmox-host

# Install jq (JSON processor)
apt-get update && apt-get install -y jq curl

# Verify installations
which pct jq curl
# Should show paths for all three commands

Download Enrollment Script

Method A: Direct Download from PatchMon (Recommended)

# Download with credentials embedded (copy from PatchMon UI)
curl -s "https://patchmon.example.com/api/v1/auto-enrollment/proxmox-lxc?token_key=YOUR_KEY&token_secret=YOUR_SECRET" \
    -o /root/proxmox_auto_enroll.sh

chmod +x /root/proxmox_auto_enroll.sh

Method B: Manual Configuration

# Download script template
cd /root
wget https://raw.githubusercontent.com/9technologygroup/patchmon.net/main/agents/proxmox_auto_enroll.sh
chmod +x proxmox_auto_enroll.sh

# Edit configuration
nano proxmox_auto_enroll.sh

# Update these lines:
PATCHMON_URL="https://patchmon.example.com"
AUTO_ENROLLMENT_KEY="patchmon_ae_your_key_here"
AUTO_ENROLLMENT_SECRET="your_secret_here"

Step 3: Test with Dry Run

Always test first!

# Dry run shows what would happen without making changes
DRY_RUN=true ./proxmox_auto_enroll.sh

Expected output:

[INFO] Found 5 LXC container(s)
[INFO] Processing LXC 100: webserver (status: running)
[INFO]   [DRY RUN] Would enroll: proxmox-webserver
[INFO] Processing LXC 101: database (status: running)
[INFO]   [DRY RUN] Would enroll: proxmox-database
...
[INFO] Successfully Enrolled:  5 (dry run)

Step 4: Run Actual Enrollment

# Enroll all containers
./proxmox_auto_enroll.sh

Monitor the output:

  • ✅ Green [SUCCESS] = Container enrolled and agent installed
  • ⊘ Yellow [WARN] = Container skipped (already enrolled or stopped)
  • ✗ Red [ERROR] = Failure (check troubleshooting section)

Step 5: Verify in PatchMon

  1. Go to Hosts page in PatchMon UI
  2. Look for newly enrolled containers (names prefixed with "proxmox-")
  3. Initial status is "pending" (normal!)
  4. Wait 60-120 seconds for first agent check-in
  5. Status changes to "active" with package data populated

Troubleshooting: If status stays "pending" >5 minutes, see Agent Not Reporting section.

Usage Examples

Basic Enrollment

# Enroll all running LXC containers
./proxmox_auto_enroll.sh

Dry Run Mode

# Preview what would be enrolled (no changes made)
DRY_RUN=true ./proxmox_auto_enroll.sh

Debug Mode

# Show detailed logging for troubleshooting
DEBUG=true ./proxmox_auto_enroll.sh

Custom Host Prefix

# Prefix container names (e.g., "prod-webserver" instead of "webserver")
HOST_PREFIX="prod-" ./proxmox_auto_enroll.sh

Include Stopped Containers

# Also process stopped containers (enrollment only, agent install fails)
SKIP_STOPPED=false ./proxmox_auto_enroll.sh

Force Install Mode (Broken Packages)

If containers have broken packages (CloudPanel, WHM, cPanel, etc.) that block apt-get:

# Bypass broken packages during agent installation
FORCE_INSTALL=true ./proxmox_auto_enroll.sh

Or use the force parameter when downloading:

curl -s "https://patchmon.example.com/api/v1/auto-enrollment/proxmox-lxc?token_key=KEY&token_secret=SECRET&force=true" | bash

What force mode does:

  • Skips apt-get update if broken packages detected
  • Only installs missing critical tools (jq, curl, bc)
  • Uses --fix-broken --yes flags safely
  • Validates installations before proceeding

Scheduled Enrollment (Cron)

Automatically enroll new containers:

# Edit crontab
crontab -e

# Run daily at 2 AM
0 2 * * * /root/proxmox_auto_enroll.sh >> /var/log/patchmon-enroll.log 2>&1

# Or hourly for dynamic environments
0 * * * * /root/proxmox_auto_enroll.sh >> /var/log/patchmon-enroll.log 2>&1

Safe to rerun: Already-enrolled containers are automatically skipped (no duplicates, no errors).

Multi-Environment Setup

# Production environment (uses prod token)
export PATCHMON_URL="https://patchmon.example.com"
export AUTO_ENROLLMENT_KEY="patchmon_ae_prod_..."
export AUTO_ENROLLMENT_SECRET="prod_secret..."
export HOST_PREFIX="prod-"
./proxmox_auto_enroll.sh

# Development environment (uses dev token with different host group)
export AUTO_ENROLLMENT_KEY="patchmon_ae_dev_..."
export AUTO_ENROLLMENT_SECRET="dev_secret..."
export HOST_PREFIX="dev-"
./proxmox_auto_enroll.sh

Configuration Options

Environment Variables

All configuration can be set via environment variables:

Variable Default Description Example
PATCHMON_URL Required PatchMon server URL https://patchmon.example.com
AUTO_ENROLLMENT_KEY Required Token key from PatchMon patchmon_ae_abc123...
AUTO_ENROLLMENT_SECRET Required Token secret from PatchMon def456ghi789...
CURL_FLAGS -s Curl options -sk (for self-signed SSL)
DRY_RUN false Preview mode (no changes) true/false
HOST_PREFIX "" Prefix for host names proxmox-, prod-, etc.
SKIP_STOPPED true Skip stopped containers true/false
FORCE_INSTALL false Bypass broken packages true/false
DEBUG false Enable debug logging true/false

Script Configuration Section

Or edit the script directly:

# ===== CONFIGURATION =====
PATCHMON_URL="${PATCHMON_URL:-https://patchmon.example.com}"
AUTO_ENROLLMENT_KEY="${AUTO_ENROLLMENT_KEY:-your_key_here}"
AUTO_ENROLLMENT_SECRET="${AUTO_ENROLLMENT_SECRET:-your_secret_here}"
CURL_FLAGS="${CURL_FLAGS:--s}"
DRY_RUN="${DRY_RUN:-false}"
HOST_PREFIX="${HOST_PREFIX:-}"
SKIP_STOPPED="${SKIP_STOPPED:-true}"
FORCE_INSTALL="${FORCE_INSTALL:-false}"

Token Configuration (PatchMon UI)

Configure tokens in Settings → Integrations → Proxmox LXC:

General Settings:

  • Token Name: Descriptive identifier
  • Active Status: Enable/disable without deleting
  • Expiration Date: Auto-disable after date

Security Settings:

  • Max Hosts Per Day: Rate limit (resets daily at midnight)
  • Allowed IP Addresses: Comma-separated IP whitelist
  • Default Host Group: Auto-assign enrolled hosts

Usage Statistics:

  • Hosts Created Today: Current daily count
  • Last Used: Timestamp of most recent enrollment
  • Created By: Admin user who created token
  • Created At: Token creation timestamp

Security Best Practices

Token Management

  1. Store Securely

    • Save credentials in password manager (1Password, LastPass, etc.)
    • Never commit to version control
    • Use environment variables or secure config management (Vault)
  2. Principle of Least Privilege

    • Create separate tokens for prod/dev/staging
    • Use different tokens for different Proxmox clusters
    • Set appropriate rate limits per environment
  3. Regular Rotation

    • Rotate tokens every 90 days
    • Disable unused tokens immediately
    • Monitor token usage for anomalies
  4. IP Restrictions

    • Always set allowed_ip_ranges in production
    • Update if Proxmox host IPs change
    • Use VPN/private network IPs when possible
  5. Expiration Dates

    • Set expiration for temporary/testing tokens
    • Review and extend before expiration
    • Delete expired tokens to reduce attack surface

Network Security

  1. Use HTTPS

    • Always use encrypted connections in production
    • Use valid SSL certificates (avoid -k flag)
    • Self-signed OK for internal/testing environments
  2. Firewall Configuration

    # Allow outbound HTTPS from Proxmox host
    iptables -A OUTPUT -p tcp --dport 443 -d <patchmon-ip> -j ACCEPT
    
    # Allow outbound HTTPS from LXC containers
    iptables -A FORWARD -p tcp --dport 443 -d <patchmon-ip> -j ACCEPT
    
  3. Network Segmentation

    • Run enrollment over private network if possible
    • Use VPN for remote Proxmox hosts
    • Restrict PatchMon server access to known IPs

Access Control

  1. Admin Permissions

    • Only admins with "Manage Settings" can create tokens
    • Regular users cannot see token secrets
    • Use role-based access control (RBAC)
  2. Audit Logging

    • Monitor token creation/deletion in PatchMon logs
    • Track enrollment activity per token
    • Review host notes for enrollment source
  3. Container Security

    • Ensure containers have minimal privileges
    • Don't run enrollment as unprivileged user
    • Use unprivileged containers where possible (enrollment still works)

Incident Response

If a token is compromised:

  1. Immediately disable the token in PatchMon UI

    • Settings → Integrations → Proxmox LXC → Toggle "Disable"
  2. Review recently enrolled hosts

    • Check host notes for token name and enrollment date
    • Verify all recent enrollments are legitimate
    • Delete any suspicious hosts
  3. Create new token

    • Generate new credentials
    • Update Proxmox script with new credentials
    • Test enrollment with dry run
  4. Investigate root cause

    • How were credentials exposed?
    • Update procedures to prevent recurrence
    • Consider additional security measures
  5. Delete old token

    • After verifying new token works
    • Document incident in change log

Troubleshooting

Common Errors and Solutions

Error: "pct command not found"

Symptom:

[ERROR] This script must run on a Proxmox host (pct command not found)

Cause: Script is running on a non-Proxmox machine

Solution:

# SSH to Proxmox host first
ssh root@proxmox-host
cd /root
./proxmox_auto_enroll.sh

Error: "Missing or invalid authorization header"

Symptom:

[ERROR] Failed to enroll hostname - HTTP 401
Response: {"error":"Missing or invalid authorization header"}

Cause: Token credentials are incorrect or missing

Solution:

  1. Verify token_key starts with patchmon_ae_
  2. Check for extra spaces/newlines in credentials
  3. Ensure no special characters were corrupted
  4. Regenerate token if credentials lost
# Test credentials manually
curl -X POST \
  -H "X-Auto-Enrollment-Key: YOUR_KEY" \
  -H "X-Auto-Enrollment-Secret: YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"friendly_name":"test","machine_id":"test"}' \
  https://patchmon.example.com/api/v1/auto-enrollment/enroll

Error: "Invalid or inactive token"

Symptom:

[ERROR] Failed to enroll hostname - HTTP 401
Response: {"error":"Invalid or inactive token"}

Cause: Token is disabled, expired, or deleted

Solution:

  1. Check token status in PatchMon UI (Settings → Integrations)
  2. Enable if disabled
  3. Extend expiration if expired
  4. Create new token if deleted

Error: "Rate limit exceeded"

Symptom:

[ERROR] Rate limit exceeded - maximum hosts per day reached

Cause: Token's max_hosts_per_day limit reached

Solution:

# Option 1: Wait until tomorrow (limit resets at midnight)
date
# Check current time, wait until 00:00

# Option 2: Increase limit in PatchMon UI
# Settings → Integrations → Edit Token → Max Hosts Per Day: 200

# Option 3: Create additional token for large enrollments

Error: "IP address not authorized"

Symptom:

[ERROR] Failed to enroll hostname - HTTP 403
Response: {"error":"IP address not authorized for this token"}

Cause: Proxmox host IP not in token's allowed_ip_ranges

Solution:

  1. Find your Proxmox host IP:

    ip addr show | grep 'inet ' | grep -v 127.0.0.1
    
  2. Update token in PatchMon UI:

    • Settings → Integrations → Edit Token
    • Allowed IP Addresses: Add your IP
  3. Or remove IP restriction entirely (not recommended for production)

Error: "jq: command not found"

Symptom:

[ERROR] Required command 'jq' not found. Please install it first.

Cause: Missing dependency

Solution:

# Debian/Ubuntu
apt-get update && apt-get install -y jq

# CentOS/RHEL
yum install -y jq

# Alpine
apk add --no-cache jq

Error: "Failed to install agent in container"

Symptom:

[WARN] ✗ Failed to install agent in container-name (exit: 1)
Install output: E: Unable to locate package curl

Cause: Agent installation failed inside LXC container

Solutions:

A. Network connectivity issue:

# Test from Proxmox host
pct exec 100 -- ping -c 3 patchmon.example.com

# Test from inside container
pct enter 100
curl -I https://patchmon.example.com
exit

B. Package manager issue:

# Enter container
pct enter 100

# Update package lists
apt-get update
# or
yum makecache

# Try manual agent install
curl https://patchmon.example.com/api/v1/hosts/install \
  -H "X-API-ID: patchmon_xxx" \
  -H "X-API-KEY: xxx" | bash

C. Unsupported OS:

  • Agent supports: Ubuntu, Debian, CentOS, RHEL, Rocky Linux, AlmaLinux, Alpine
  • Check /etc/os-release in container
  • Manually install on other distributions

D. Broken packages (use force mode):

FORCE_INSTALL=true ./proxmox_auto_enroll.sh

Error: SSL Certificate Problems

Symptom:

curl: (60) SSL certificate problem: self signed certificate

Cause: Self-signed certificate on PatchMon server

Solution:

# Use -k flag to skip certificate verification
export CURL_FLAGS="-sk"
./proxmox_auto_enroll.sh

Better solution: Install valid SSL certificate on PatchMon server using Let's Encrypt or corporate CA

Warning: Container Already Enrolled

Symptom:

[WARN] ⊘ Host container-name already enrolled - skipping

Cause: Container was previously enrolled (HTTP 409 response)

This is normal! The script safely skips already-enrolled hosts. No action needed.

If you need to re-enroll:

  1. Delete host from PatchMon UI (Hosts page)
  2. Rerun enrollment script

Agent Not Reporting

If containers show "pending" status >5 minutes after enrollment:

1. Check agent cron job exists:

pct enter 100
crontab -l | grep patchmon
# Should show: */5 * * * * /opt/patchmon/patchmon-agent.sh

2. Check agent files exist:

ls -la /opt/patchmon/
# Should show: patchmon-agent.sh and patchmon.conf

3. Check agent logs:

# Agent logs are typically in /var/log or stdout
cat /var/log/patchmon-agent.log
# Or check recent cron logs
grep patchmon /var/log/syslog | tail -n 50
# Or on CentOS/RHEL
grep patchmon /var/log/cron | tail -n 50

4. Test manual agent execution:

bash /opt/patchmon/patchmon-agent.sh
# This should execute and show output/errors

5. Verify credentials:

cat /opt/patchmon/patchmon.conf
# Should show api_id and api_key

6. Test connectivity:

curl -I https://patchmon.example.com/api/v1/hosts/report \
  -H "X-API-ID: $(grep api_id /opt/patchmon/patchmon.conf | cut -d= -f2)" \
  -H "X-API-KEY: $(grep api_key /opt/patchmon/patchmon.conf | cut -d= -f2)"

7. Check cron is running:

ps aux | grep cron
# Should show crond or cron process running

Debug Mode

Enable detailed logging:

DEBUG=true ./proxmox_auto_enroll.sh

Debug output includes:

  • API request/response bodies
  • Container command execution details
  • Detailed error messages
  • curl verbose output

Getting Help

If issues persist:

  1. Check PatchMon server logs:

    tail -f /path/to/patchmon/backend/logs/error.log
    
  2. Create GitHub issue with:

    • PatchMon version
    • Proxmox version
    • Script output (redact credentials!)
    • Debug mode output
    • Server logs (if accessible)
  3. Join Discord community for real-time support

Advanced Usage

Selective Enrollment

Enroll only specific containers:

# Only enroll containers 100-199
nano proxmox_auto_enroll.sh

# Add after line "while IFS= read -r line; do"
vmid=$(echo "$line" | awk '{print $1}')
if [[ $vmid -lt 100 ]] || [[ $vmid -gt 199 ]]; then
    continue
fi

Or use container name filtering:

# Only enroll containers with "prod" in name
if [[ ! "$name" =~ prod ]]; then
    continue
fi

Custom Host Naming

Advanced naming strategies:

# Include Proxmox node name
HOST_PREFIX="$(hostname)-"
# Result: proxmox01-webserver, proxmox02-database

# Include datacenter/location
HOST_PREFIX="dc1-"
# Result: dc1-webserver, dc1-database

# Include environment and node
HOST_PREFIX="prod-$(hostname | cut -d. -f1)-"
# Result: prod-px01-webserver

Multi-Node Proxmox Cluster

For Proxmox clusters with multiple nodes:

Option 1: Same token, different prefix per node

# On node 1
HOST_PREFIX="node1-" ./proxmox_auto_enroll.sh

# On node 2
HOST_PREFIX="node2-" ./proxmox_auto_enroll.sh

Option 2: Different tokens per node

  • Create token for each node with different default host groups
  • Node 1 → "Proxmox Node 1" group
  • Node 2 → "Proxmox Node 2" group

Option 3: Centralized automation

#!/bin/bash
# central_enroll.sh

NODES=(
  "root@proxmox01.example.com"
  "root@proxmox02.example.com"
  "root@proxmox03.example.com"
)

for node in "${NODES[@]}"; do
  echo "Enrolling containers from $node..."
  ssh "$node" "bash /root/proxmox_auto_enroll.sh"
done

Integration with Infrastructure as Code

Ansible Playbook:

---
- name: Enroll Proxmox LXC containers in PatchMon
  hosts: proxmox_hosts
  become: yes
  tasks:
    - name: Install dependencies
      apt:
        name:
          - curl
          - jq
        state: present

    - name: Download enrollment script
      get_url:
        url: "{{ patchmon_url }}/api/v1/auto-enrollment/proxmox-lxc?token_key={{ token_key }}&token_secret={{ token_secret }}"
        dest: /root/proxmox_auto_enroll.sh
        mode: '0700'

    - name: Run enrollment
      command: /root/proxmox_auto_enroll.sh
      register: enrollment_output

    - name: Show enrollment results
      debug:
        var: enrollment_output.stdout_lines

Terraform (with null_resource):

resource "null_resource" "patchmon_enrollment" {
  triggers = {
    cluster_instance_ids = join(",", proxmox_lxc.containers.*.vmid)
  }

  provisioner "remote-exec" {
    connection {
      host = var.proxmox_host
      user = "root"
      private_key = file(var.ssh_key_path)
    }

    inline = [
      "apt-get install -y jq",
      "curl -s '${var.patchmon_url}/api/v1/auto-enrollment/proxmox-lxc?token_key=${var.token_key}&token_secret=${var.token_secret}' | bash"
    ]
  }
}

Bulk API Enrollment

For very large deployments (100+ containers), use the bulk API endpoint directly:

#!/bin/bash
# bulk_enroll.sh

# Gather all container info
containers_json=$(pct list | tail -n +2 | while read -r line; do
  vmid=$(echo "$line" | awk '{print $1}')
  name=$(echo "$line" | awk '{print $3}')
  
  echo "{\"friendly_name\":\"$name\",\"machine_id\":\"proxmox-lxc-$vmid\"}"
done | jq -s '.')

# Send bulk enrollment request
curl -X POST \
  -H "X-Auto-Enrollment-Key: $AUTO_ENROLLMENT_KEY" \
  -H "X-Auto-Enrollment-Secret: $AUTO_ENROLLMENT_SECRET" \
  -H "Content-Type: application/json" \
  -d "{\"hosts\":$containers_json}" \
  "$PATCHMON_URL/api/v1/auto-enrollment/enroll/bulk"

Benefits:

  • Single API call for all containers
  • Faster for 50+ containers
  • Atomic operation (all or nothing)

Limitations:

  • Max 50 hosts per request
  • Does not install agents (must be done separately)
  • Less detailed error reporting per host

Webhook-Triggered Enrollment

Trigger enrollment from PatchMon webhook (requires custom setup):

#!/bin/bash
# webhook_listener.sh

# Simple webhook listener
while true; do
  # Listen for webhook on port 9000
  nc -l -p 9000 -c 'echo -e "HTTP/1.1 200 OK\n\n"; /root/proxmox_auto_enroll.sh'
done

Then configure PatchMon (or monitoring system) to call webhook when conditions are met.

API Reference

Admin Endpoints (Authentication Required)

All admin endpoints require JWT authentication:

Authorization: Bearer <jwt_token>

Create Token

Endpoint: POST /api/v1/auto-enrollment/tokens

Request:

{
  "token_name": "Proxmox Production",
  "max_hosts_per_day": 100,
  "default_host_group_id": "uuid",
  "allowed_ip_ranges": ["192.168.1.10", "10.0.0.5"],
  "expires_at": "2026-12-31T23:59:59Z",
  "metadata": {
    "integration_type": "proxmox-lxc",
    "environment": "production"
  }
}

Response: 201 Created

{
  "message": "Auto-enrollment token created successfully",
  "token": {
    "id": "uuid",
    "token_name": "Proxmox Production",
    "token_key": "patchmon_ae_abc123...",
    "token_secret": "def456...",  // Only shown here!
    "max_hosts_per_day": 100,
    "default_host_group": {
      "id": "uuid",
      "name": "Proxmox LXC",
      "color": "#3B82F6"
    },
    "created_by": {
      "id": "uuid",
      "username": "admin",
      "first_name": "John",
      "last_name": "Doe"
    },
    "expires_at": "2026-12-31T23:59:59Z"
  },
  "warning": "⚠️ Save the token_secret now - it cannot be retrieved later!"
}

List Tokens

Endpoint: GET /api/v1/auto-enrollment/tokens

Response: 200 OK

[
  {
    "id": "uuid",
    "token_name": "Proxmox Production",
    "token_key": "patchmon_ae_abc123...",
    "is_active": true,
    "allowed_ip_ranges": ["192.168.1.10"],
    "max_hosts_per_day": 100,
    "hosts_created_today": 15,
    "last_used_at": "2025-10-11T14:30:00Z",
    "expires_at": "2026-12-31T23:59:59Z",
    "created_at": "2025-10-01T10:00:00Z",
    "default_host_group_id": "uuid",
    "metadata": {"integration_type": "proxmox-lxc"},
    "host_groups": {
      "id": "uuid",
      "name": "Proxmox LXC",
      "color": "#3B82F6"
    },
    "users": {
      "id": "uuid",
      "username": "admin",
      "first_name": "John",
      "last_name": "Doe"
    }
  }
]

Get Token Details

Endpoint: GET /api/v1/auto-enrollment/tokens/:tokenId

Response: 200 OK (same structure as single token in list)

Update Token

Endpoint: PATCH /api/v1/auto-enrollment/tokens/:tokenId

Request:

{
  "is_active": false,
  "max_hosts_per_day": 200,
  "allowed_ip_ranges": ["192.168.1.0/24"],
  "expires_at": "2027-01-01T00:00:00Z"
}

Response: 200 OK

{
  "message": "Token updated successfully",
  "token": { /* updated token object */ }
}

Delete Token

Endpoint: DELETE /api/v1/auto-enrollment/tokens/:tokenId

Response: 200 OK

{
  "message": "Auto-enrollment token deleted successfully",
  "deleted_token": {
    "id": "uuid",
    "token_name": "Proxmox Production"
  }
}

Enrollment Endpoints (Token Authentication)

Authentication via headers:

X-Auto-Enrollment-Key: patchmon_ae_abc123...
X-Auto-Enrollment-Secret: def456...

Download Enrollment Script

Endpoint: GET /api/v1/auto-enrollment/proxmox-lxc

Query Parameters:

  • token_key (required): Auto-enrollment token key
  • token_secret (required): Auto-enrollment token secret
  • force (optional): true to enable force install mode

Example:

curl "https://patchmon.example.com/api/v1/auto-enrollment/proxmox-lxc?token_key=KEY&token_secret=SECRET&force=true"

Response: 200 OK (bash script with credentials injected)

Enroll Single Host

Endpoint: POST /api/v1/auto-enrollment/enroll

Request:

{
  "friendly_name": "webserver",
  "machine_id": "proxmox-lxc-100-abc123",
  "metadata": {
    "vmid": "100",
    "proxmox_node": "proxmox01",
    "ip_address": "10.0.0.10",
    "os_info": "Ubuntu 22.04 LTS"
  }
}

Response: 201 Created

{
  "message": "Host enrolled successfully",
  "host": {
    "id": "uuid",
    "friendly_name": "webserver",
    "api_id": "patchmon_abc123",
    "api_key": "def456ghi789",
    "host_group": {
      "id": "uuid",
      "name": "Proxmox LXC",
      "color": "#3B82F6"
    },
    "status": "pending"
  }
}

Error Responses:

409 Conflict - Host already exists:

{
  "error": "Host already exists",
  "host_id": "uuid",
  "api_id": "patchmon_abc123",
  "machine_id": "proxmox-lxc-100-abc123",
  "friendly_name": "webserver",
  "message": "This machine is already enrolled in PatchMon (matched by machine ID)"
}

429 Too Many Requests - Rate limit exceeded:

{
  "error": "Rate limit exceeded",
  "message": "Maximum 100 hosts per day allowed for this token"
}

Bulk Enroll Hosts

Endpoint: POST /api/v1/auto-enrollment/enroll/bulk

Request:

{
  "hosts": [
    {
      "friendly_name": "webserver",
      "machine_id": "proxmox-lxc-100-abc123"
    },
    {
      "friendly_name": "database",
      "machine_id": "proxmox-lxc-101-def456"
    }
  ]
}

Limits:

  • Minimum: 1 host
  • Maximum: 50 hosts per request

Response: 201 Created

{
  "message": "Bulk enrollment completed: 2 succeeded, 0 failed, 0 skipped",
  "results": {
    "success": [
      {
        "id": "uuid",
        "friendly_name": "webserver",
        "api_id": "patchmon_abc123",
        "api_key": "def456"
      },
      {
        "id": "uuid",
        "friendly_name": "database",
        "api_id": "patchmon_ghi789",
        "api_key": "jkl012"
      }
    ],
    "failed": [],
    "skipped": []
  }
}

FAQ

General Questions

Q: Can I use the same token for multiple Proxmox hosts?
A: Yes, as long as the combined enrollment count stays within max_hosts_per_day limit. Rate limits are per-token, not per-host.

Q: What happens if I run the script multiple times?
A: Already-enrolled containers are automatically skipped (returns HTTP 409). Safe to rerun!

Q: Can I enroll stopped LXC containers?
A: No, containers must be running. The script needs to execute commands inside the container to install the agent. Start containers before enrolling.

Q: Does this work with Proxmox VMs (QEMU)?
A: No, this script is LXC-specific and uses pct exec to enter containers. VMs require manual enrollment or a different automation approach (SSH-based).

Q: How do I unenroll a host?
A: Go to PatchMon UI → Hosts → Select host → Delete. The agent will stop reporting and the host record is removed from the database.

Q: Can I change the host group after enrollment?
A: Yes! In PatchMon UI → Hosts → Select host → Edit → Change host group.

Q: Can I see which hosts were enrolled by which token?
A: Yes, check the host "Notes" field in PatchMon. It includes the token name and enrollment timestamp.

Q: What if my Proxmox host IP address changes?
A: Update the token's allowed_ip_ranges in PatchMon UI (Settings → Integrations → Edit Token).

Q: Can I have multiple tokens with different host groups?
A: Yes! Create separate tokens for prod/dev/staging with different default host groups. Great for environment segregation.

Q: Is there a way to trigger enrollment from PatchMon GUI?
A: Not currently (would require inbound network access). The script must run on the Proxmox host. Future versions may support webhooks or agent-initiated enrollment.

Security Questions

Q: Are token secrets stored securely?
A: Yes, token secrets are hashed using bcrypt before storage. Only the hash is stored in the database, never the plain text.

Q: What happens if someone steals my auto-enrollment token?
A: They can create new hosts up to the rate limit, but cannot control existing hosts or access host data. Immediately disable the token in PatchMon UI if compromised.

Q: Can I audit who created which tokens?
A: Yes, each token stores the created_by_user_id. View in PatchMon UI or query the database.

Q: How does IP whitelisting work?
A: PatchMon checks the client IP from the HTTP request. If allowed_ip_ranges is configured, the IP must match one of the allowed ranges (substring match). Use CIDR notation for ranges.

Q: Can I use the same credentials for enrollment and agent communication?
A: No, they're separate. Auto-enrollment credentials create hosts. Each host gets unique API credentials for agent communication. This separation limits the blast radius of credential compromise.

Technical Questions

Q: Why does the agent require curl inside the container?
A: The agent script uses curl to communicate with PatchMon. The enrollment script automatically installs curl if missing.

Q: What Linux distributions are supported in containers?
A: Ubuntu, Debian, CentOS, RHEL, Rocky Linux, AlmaLinux, Alpine Linux. Any distribution with apt/yum/dnf/apk package managers.

Q: How much bandwidth does enrollment use?
A: Minimal. The script download is ~15KB, agent installation is ~50-100KB per container. Total: ~1-2MB for 10 containers.

Q: Can I run enrollment in parallel for faster processing?
A: Not recommended. The script processes containers sequentially to avoid overwhelming the PatchMon server. For 100+ containers, consider the bulk API endpoint.

Q: Does enrollment restart containers?
A: No, containers remain running. The agent is installed without reboots or service disruptions.

Q: What if the container doesn't have a hostname?
A: The script uses the container name from Proxmox as a fallback.

Q: Can I customize the agent installation?
A: Yes, modify the install_url in the enrollment script or use the PatchMon agent installation API parameters.

Troubleshooting Questions

Q: Why does enrollment fail with "dpkg was interrupted"?
A: Your container has broken packages. Use FORCE_INSTALL=true to bypass, or manually fix dpkg:

pct enter 100
dpkg --configure -a
apt-get install -f

Q: Why does the agent show "pending" status forever?
A: Agent likely can't reach PatchMon server. Check:

  1. Container network connectivity: pct exec 100 -- ping patchmon.example.com
  2. Agent service running: pct exec 100 -- systemctl status patchmon-agent.timer
  3. Agent logs: pct exec 100 -- journalctl -u patchmon-agent.service

Q: Can I test enrollment without actually creating hosts?
A: Yes, use dry run mode: DRY_RUN=true ./proxmox_auto_enroll.sh

Q: How do I get more verbose output?
A: Use debug mode: DEBUG=true ./proxmox_auto_enroll.sh

Support and Resources

Documentation

  • PatchMon Documentation: https://docs.patchmon.net
  • API Reference: https://docs.patchmon.net/api
  • Agent Documentation: https://docs.patchmon.net/agent

Community

  • Discord: https://patchmon.net/discord
  • GitHub Issues: https://github.com/9technologygroup/patchmon.net/issues
  • GitHub Discussions: https://github.com/9technologygroup/patchmon.net/discussions

Professional Support

For enterprise support, training, or custom integrations:

  • Email: support@patchmon.net
  • Website: https://patchmon.net/support

Changelog

Version 2.0.0 (2025-10-11)

  • Added force install mode for containers with broken packages
  • Improved error handling and recovery
  • Enhanced dpkg error detection and automatic recovery
  • Better logging and debug output
  • Added dry run mode
  • Parallel installation support (experimental)

Version 1.0.0 (2025-09-15)

  • Initial release of Proxmox LXC Auto-Enrollment
  • Token-based authentication with hashed secrets
  • Rate limiting (hosts per day)
  • IP whitelisting support
  • Default host group assignment
  • Bulk enrollment API endpoint
  • Frontend token management UI

Made with ❤️ by the PatchMon Team