Implement comprehensive directory reorganization to improve discoverability,
logical grouping, and separation of concerns across documentation, scripts,
and infrastructure snapshots.
Major Changes:
1. Documentation Reorganization:
- Created start-here-docs/ for onboarding documentation
* Moved QUICK-START.md, START-HERE.md, GIT-SETUP-GUIDE.md
* Moved GIT-QUICK-REFERENCE.md, SCRIPT-USAGE.md, SETUP-COMPLETE.md
- Created troubleshooting/ directory
* Moved BUGFIX-SUMMARY.md for centralized issue resolution
- Created mcp/ directory for Model Context Protocol configurations
* Moved OBSIDIAN-MCP-SETUP.md to mcp/obsidian/
2. Scripts Reorganization:
- Created scripts/crawlers-exporters/ for infrastructure collection
* Moved collect*.sh scripts and collection documentation
* Consolidates Proxmox homelab export tooling
- Created scripts/fixers/ for operational repair scripts
* Moved fix_n8n_db_*.sh scripts
* Isolated scripts with embedded credentials (templates tracked)
- Created scripts/qol/ for quality-of-life utilities
* Moved git-aliases.sh and git-first-commit.sh
3. Infrastructure Snapshots:
- Created disaster-recovery/ for active infrastructure state
* Moved latest homelab-export-20251202-204939/ snapshot
* Contains current VM/CT configurations and system state
- Created archive-homelab/ for historical snapshots
* Moved homelab-export-*.tar.gz archives
* Preserves point-in-time backups for reference
4. Agent Definitions:
- Created sub-agents/ directory
* Added backend-builder.md (development agent)
* Added lab-operator.md (infrastructure operations agent)
* Added librarian.md (git/version control agent)
* Added scribe.md (documentation agent)
5. Updated INDEX.md:
- Reflects new directory structure throughout
- Updated all file path references
- Enhanced navigation with new sections
- Added agent roles documentation
- Updated quick reference commands
6. Security Improvements:
- Updated .gitignore to match reorganized file locations
- Corrected path for scripts/fixers/fix_n8n_db_c_locale.sh exclusion
- Maintained template-based credential management pattern
Infrastructure State Update:
- Latest snapshot: 2025-12-02 20:49:54
- Removed: VM 101 (gitlab), CT 112 (Anytype)
- Added: CT 113 (n8n)
- Total: 9 VMs, 3 Containers
Impact:
- Improved repository navigation and discoverability
- Logical separation of documentation, scripts, and snapshots
- Clearer onboarding path for new users
- Enhanced maintainability through organized structure
- Foundation for multi-agent workflow support
Files changed: 90 files (+935/-349)
- 3 modified, 14 new files, 73 renames/moves
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1024 lines
32 KiB
Bash
1024 lines
32 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
################################################################################
|
|
# Homelab Infrastructure Collection Script
|
|
# Version: 1.0.0
|
|
# Purpose: Collects Proxmox VE configurations, system information, and exports
|
|
# infrastructure state in an organized, documented format
|
|
#
|
|
# Usage: ./collect-homelab-config.sh [OPTIONS]
|
|
#
|
|
# This script performs READ-ONLY operations and makes no modifications to your
|
|
# Proxmox environment. It is designed to be run directly on the Proxmox host
|
|
# or remotely via SSH.
|
|
################################################################################
|
|
|
|
set -euo pipefail # Exit on error, undefined variables, and pipe failures
|
|
|
|
# Script metadata
|
|
SCRIPT_VERSION="1.0.0"
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
|
|
|
# Color codes for output (disabled if not a TTY)
|
|
if [[ -t 1 ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
MAGENTA='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
else
|
|
RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' BOLD='' NC=''
|
|
fi
|
|
|
|
################################################################################
|
|
# Configuration Variables
|
|
################################################################################
|
|
|
|
# Default collection level: basic, standard, full, paranoid
|
|
COLLECTION_LEVEL="${COLLECTION_LEVEL:-standard}"
|
|
|
|
# Sanitization options
|
|
SANITIZE_IPS="${SANITIZE_IPS:-false}"
|
|
SANITIZE_PASSWORDS="${SANITIZE_PASSWORDS:-true}"
|
|
SANITIZE_TOKENS="${SANITIZE_TOKENS:-true}"
|
|
|
|
# Output configuration
|
|
OUTPUT_BASE_DIR="${OUTPUT_DIR:-./homelab-export-${TIMESTAMP}}"
|
|
COMPRESS_OUTPUT="${COMPRESS_OUTPUT:-true}"
|
|
|
|
# Logging
|
|
LOG_FILE="${OUTPUT_BASE_DIR}/collection.log"
|
|
VERBOSE="${VERBOSE:-false}"
|
|
|
|
# Summary tracking
|
|
COLLECTED_ITEMS=()
|
|
SKIPPED_ITEMS=()
|
|
ERROR_ITEMS=()
|
|
|
|
################################################################################
|
|
# Utility Functions
|
|
################################################################################
|
|
|
|
log() {
|
|
local level="$1"
|
|
shift
|
|
local message="$*"
|
|
local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
echo "[${timestamp}] [${level}] ${message}" >> "${LOG_FILE}" 2>/dev/null || true
|
|
|
|
case "${level}" in
|
|
INFO)
|
|
echo -e "${BLUE}[INFO]${NC} ${message}"
|
|
;;
|
|
SUCCESS)
|
|
echo -e "${GREEN}[✓]${NC} ${message}"
|
|
;;
|
|
WARN)
|
|
echo -e "${YELLOW}[WARN]${NC} ${message}"
|
|
;;
|
|
ERROR)
|
|
echo -e "${RED}[ERROR]${NC} ${message}" >&2
|
|
;;
|
|
DEBUG)
|
|
if [[ "${VERBOSE}" == "true" ]]; then
|
|
echo -e "${MAGENTA}[DEBUG]${NC} ${message}"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
banner() {
|
|
local text="$1"
|
|
local width=80
|
|
echo ""
|
|
echo -e "${BOLD}${CYAN}$(printf '=%.0s' $(seq 1 ${width}))${NC}"
|
|
echo -e "${BOLD}${CYAN} ${text}${NC}"
|
|
echo -e "${BOLD}${CYAN}$(printf '=%.0s' $(seq 1 ${width}))${NC}"
|
|
echo ""
|
|
}
|
|
|
|
check_command() {
|
|
local cmd="$1"
|
|
if command -v "${cmd}" &> /dev/null; then
|
|
log DEBUG "Command '${cmd}' is available"
|
|
return 0
|
|
else
|
|
log DEBUG "Command '${cmd}' is NOT available"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
check_proxmox() {
|
|
if [[ ! -f /etc/pve/.version ]]; then
|
|
log ERROR "This does not appear to be a Proxmox VE host"
|
|
log ERROR "/etc/pve/.version not found"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
safe_copy() {
|
|
local source="$1"
|
|
local dest="$2"
|
|
local description="${3:-file}"
|
|
|
|
if [[ -f "${source}" ]]; then
|
|
mkdir -p "$(dirname "${dest}")"
|
|
cp "${source}" "${dest}" 2>/dev/null && {
|
|
log SUCCESS "Collected ${description}"
|
|
COLLECTED_ITEMS+=("${description}")
|
|
return 0
|
|
} || {
|
|
log WARN "Failed to copy ${description} from ${source}"
|
|
ERROR_ITEMS+=("${description}")
|
|
return 1
|
|
}
|
|
elif [[ -d "${source}" ]]; then
|
|
mkdir -p "${dest}"
|
|
cp -r "${source}"/* "${dest}/" 2>/dev/null && {
|
|
log SUCCESS "Collected ${description}"
|
|
COLLECTED_ITEMS+=("${description}")
|
|
return 0
|
|
} || {
|
|
log WARN "Failed to copy directory ${description} from ${source}"
|
|
ERROR_ITEMS+=("${description}")
|
|
return 1
|
|
}
|
|
else
|
|
log DEBUG "Source does not exist: ${source} (${description})"
|
|
SKIPPED_ITEMS+=("${description}")
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
safe_command() {
|
|
local output_file="$1"
|
|
local description="$2"
|
|
shift 2
|
|
local cmd=("$@")
|
|
|
|
mkdir -p "$(dirname "${output_file}")"
|
|
|
|
if "${cmd[@]}" > "${output_file}" 2>/dev/null; then
|
|
log SUCCESS "Collected ${description}"
|
|
COLLECTED_ITEMS+=("${description}")
|
|
return 0
|
|
else
|
|
log WARN "Failed to execute: ${cmd[*]} (${description})"
|
|
ERROR_ITEMS+=("${description}")
|
|
rm -f "${output_file}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
sanitize_file() {
|
|
local file="$1"
|
|
|
|
[[ ! -f "${file}" ]] && return 0
|
|
|
|
# Sanitize passwords
|
|
if [[ "${SANITIZE_PASSWORDS}" == "true" ]]; then
|
|
sed -i 's/password=.*/password=<REDACTED>/g' "${file}" 2>/dev/null || true
|
|
sed -i 's/passwd=.*/passwd=<REDACTED>/g' "${file}" 2>/dev/null || true
|
|
sed -i 's/"password"[[:space:]]*:[[:space:]]*"[^"]*"/"password": "<REDACTED>"/g' "${file}" 2>/dev/null || true
|
|
fi
|
|
|
|
# Sanitize tokens and keys
|
|
if [[ "${SANITIZE_TOKENS}" == "true" ]]; then
|
|
sed -i 's/token=.*/token=<REDACTED>/g' "${file}" 2>/dev/null || true
|
|
sed -i 's/api[_-]key=.*/api_key=<REDACTED>/g' "${file}" 2>/dev/null || true
|
|
sed -i 's/secret=.*/secret=<REDACTED>/g' "${file}" 2>/dev/null || true
|
|
fi
|
|
|
|
# Sanitize IP addresses (if requested)
|
|
if [[ "${SANITIZE_IPS}" == "true" ]]; then
|
|
# Replace IPv4 with 10.x.x.x equivalents
|
|
sed -i 's/\b\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}\b/10.X.X.X/g' "${file}" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
################################################################################
|
|
# Directory Structure Creation
|
|
################################################################################
|
|
|
|
create_directory_structure() {
|
|
banner "Creating Directory Structure"
|
|
|
|
local dirs=(
|
|
"${OUTPUT_BASE_DIR}"
|
|
"${OUTPUT_BASE_DIR}/docs"
|
|
"${OUTPUT_BASE_DIR}/configs/proxmox"
|
|
"${OUTPUT_BASE_DIR}/configs/vms"
|
|
"${OUTPUT_BASE_DIR}/configs/lxc"
|
|
"${OUTPUT_BASE_DIR}/configs/storage"
|
|
"${OUTPUT_BASE_DIR}/configs/network"
|
|
"${OUTPUT_BASE_DIR}/configs/backup"
|
|
"${OUTPUT_BASE_DIR}/exports/system"
|
|
"${OUTPUT_BASE_DIR}/exports/cluster"
|
|
"${OUTPUT_BASE_DIR}/exports/guests"
|
|
"${OUTPUT_BASE_DIR}/scripts"
|
|
"${OUTPUT_BASE_DIR}/diagrams"
|
|
)
|
|
|
|
for dir in "${dirs[@]}"; do
|
|
mkdir -p "${dir}"
|
|
log DEBUG "Created directory: ${dir}"
|
|
done
|
|
|
|
# Initialize log file
|
|
mkdir -p "$(dirname "${LOG_FILE}")"
|
|
touch "${LOG_FILE}"
|
|
|
|
log SUCCESS "Directory structure created at: ${OUTPUT_BASE_DIR}"
|
|
}
|
|
|
|
################################################################################
|
|
# Collection Functions
|
|
################################################################################
|
|
|
|
collect_system_information() {
|
|
banner "Collecting System Information"
|
|
|
|
local sys_dir="${OUTPUT_BASE_DIR}/exports/system"
|
|
|
|
# Proxmox version
|
|
safe_command "${sys_dir}/pve-version.txt" "Proxmox VE version" pveversion -v || true
|
|
|
|
# System information
|
|
safe_command "${sys_dir}/hostname.txt" "Hostname" hostname || true
|
|
safe_command "${sys_dir}/uname.txt" "Kernel information" uname -a || true
|
|
safe_command "${sys_dir}/uptime.txt" "System uptime" uptime || true
|
|
safe_command "${sys_dir}/date.txt" "System date/time" date || true
|
|
|
|
# CPU information
|
|
safe_command "${sys_dir}/cpuinfo.txt" "CPU information" lscpu || true
|
|
safe_copy "/proc/cpuinfo" "${sys_dir}/proc-cpuinfo.txt" "Detailed CPU info" || true
|
|
|
|
# Memory information
|
|
safe_command "${sys_dir}/meminfo.txt" "Memory information" free -h || true
|
|
safe_copy "/proc/meminfo" "${sys_dir}/proc-meminfo.txt" "Detailed memory info" || true
|
|
|
|
# Disk information
|
|
safe_command "${sys_dir}/df.txt" "Filesystem usage" df -h || true
|
|
safe_command "${sys_dir}/lsblk.txt" "Block devices" lsblk || true
|
|
|
|
if check_command "pvdisplay"; then
|
|
safe_command "${sys_dir}/pvdisplay.txt" "LVM physical volumes" pvdisplay || true
|
|
safe_command "${sys_dir}/vgdisplay.txt" "LVM volume groups" vgdisplay || true
|
|
safe_command "${sys_dir}/lvdisplay.txt" "LVM logical volumes" lvdisplay || true
|
|
fi
|
|
|
|
# Network information
|
|
safe_command "${sys_dir}/ip-addr.txt" "IP addresses" ip addr show || true
|
|
safe_command "${sys_dir}/ip-route.txt" "Routing table" ip route show || true
|
|
safe_command "${sys_dir}/ss-listening.txt" "Listening sockets" ss -tulpn || true
|
|
|
|
# Installed packages
|
|
if check_command "dpkg"; then
|
|
safe_command "${sys_dir}/dpkg-list.txt" "Installed packages" dpkg -l || true
|
|
fi
|
|
}
|
|
|
|
collect_proxmox_configs() {
|
|
banner "Collecting Proxmox Configurations"
|
|
|
|
local pve_dir="${OUTPUT_BASE_DIR}/configs/proxmox"
|
|
|
|
# Main Proxmox configuration files
|
|
safe_copy "/etc/pve/datacenter.cfg" "${pve_dir}/datacenter.cfg" "Datacenter config" || true
|
|
safe_copy "/etc/pve/storage.cfg" "${pve_dir}/storage.cfg" "Storage config" || true
|
|
safe_copy "/etc/pve/user.cfg" "${pve_dir}/user.cfg" "User config" || true
|
|
safe_copy "/etc/pve/domains.cfg" "${pve_dir}/domains.cfg" "Authentication domains" || true
|
|
safe_copy "/etc/pve/authkey.pub" "${pve_dir}/authkey.pub" "Auth public key" || true
|
|
|
|
# Firewall configurations
|
|
if [[ -f /etc/pve/firewall/cluster.fw ]]; then
|
|
safe_copy "/etc/pve/firewall/cluster.fw" "${pve_dir}/firewall-cluster.fw" "Cluster firewall rules" || true
|
|
fi
|
|
|
|
# Cluster configuration (if in a cluster)
|
|
if [[ -f /etc/pve/corosync.conf ]]; then
|
|
safe_copy "/etc/pve/corosync.conf" "${pve_dir}/corosync.conf" "Corosync config" || true
|
|
fi
|
|
|
|
# HA configuration
|
|
if [[ -d /etc/pve/ha ]]; then
|
|
safe_copy "/etc/pve/ha" "${pve_dir}/ha" "HA configuration" || true
|
|
fi
|
|
|
|
# Sanitize sensitive information
|
|
for file in "${pve_dir}"/*; do
|
|
[[ -f "${file}" ]] && sanitize_file "${file}" || true
|
|
done
|
|
}
|
|
|
|
collect_vm_configs() {
|
|
banner "Collecting VM Configurations"
|
|
|
|
local vm_dir="${OUTPUT_BASE_DIR}/configs/vms"
|
|
|
|
# Get list of VMs
|
|
if [[ -d /etc/pve/nodes ]]; then
|
|
for node_dir in /etc/pve/nodes/*; do
|
|
local node_name="$(basename "${node_dir}")"
|
|
|
|
if [[ -d "${node_dir}/qemu-server" ]]; then
|
|
for vm_config in "${node_dir}/qemu-server"/*.conf; do
|
|
[[ -f "${vm_config}" ]] || continue
|
|
|
|
local vmid="$(basename "${vm_config}" .conf)"
|
|
local vm_name="$(grep -E '^name:' "${vm_config}" 2>/dev/null | cut -d' ' -f2 || echo "unknown")"
|
|
|
|
safe_copy "${vm_config}" "${vm_dir}/${vmid}-${vm_name}.conf" "VM ${vmid} (${vm_name}) config" || true
|
|
|
|
# Firewall rules for this VM
|
|
if [[ -f "${node_dir}/qemu-server/${vmid}.fw" ]]; then
|
|
safe_copy "${node_dir}/qemu-server/${vmid}.fw" "${vm_dir}/${vmid}-${vm_name}.fw" "VM ${vmid} firewall rules" || true
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Sanitize VM configs
|
|
for file in "${vm_dir}"/*; do
|
|
[[ -f "${file}" ]] && sanitize_file "${file}" || true
|
|
done
|
|
}
|
|
|
|
collect_lxc_configs() {
|
|
banner "Collecting LXC Container Configurations"
|
|
|
|
local lxc_dir="${OUTPUT_BASE_DIR}/configs/lxc"
|
|
|
|
# Get list of containers
|
|
if [[ -d /etc/pve/nodes ]]; then
|
|
for node_dir in /etc/pve/nodes/*; do
|
|
local node_name="$(basename "${node_dir}")"
|
|
|
|
if [[ -d "${node_dir}/lxc" ]]; then
|
|
for lxc_config in "${node_dir}/lxc"/*.conf; do
|
|
[[ -f "${lxc_config}" ]] || continue
|
|
|
|
local ctid="$(basename "${lxc_config}" .conf)"
|
|
local ct_name="$(grep -E '^hostname:' "${lxc_config}" 2>/dev/null | cut -d' ' -f2 || echo "unknown")"
|
|
|
|
safe_copy "${lxc_config}" "${lxc_dir}/${ctid}-${ct_name}.conf" "Container ${ctid} (${ct_name}) config" || true
|
|
|
|
# Firewall rules for this container
|
|
if [[ -f "${node_dir}/lxc/${ctid}.fw" ]]; then
|
|
safe_copy "${node_dir}/lxc/${ctid}.fw" "${lxc_dir}/${ctid}-${ct_name}.fw" "Container ${ctid} firewall rules" || true
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Sanitize LXC configs
|
|
for file in "${lxc_dir}"/*; do
|
|
[[ -f "${file}" ]] && sanitize_file "${file}" || true
|
|
done
|
|
}
|
|
|
|
collect_network_configs() {
|
|
banner "Collecting Network Configurations"
|
|
|
|
local net_dir="${OUTPUT_BASE_DIR}/configs/network"
|
|
|
|
# Network interface configurations
|
|
safe_copy "/etc/network/interfaces" "${net_dir}/interfaces" "Network interfaces config" || true
|
|
|
|
if [[ -d /etc/network/interfaces.d ]]; then
|
|
safe_copy "/etc/network/interfaces.d" "${net_dir}/interfaces.d" "Additional interface configs" || true
|
|
fi
|
|
|
|
# SDN configuration (Software Defined Networking)
|
|
if [[ -d /etc/pve/sdn ]]; then
|
|
safe_copy "/etc/pve/sdn" "${net_dir}/sdn" "SDN configuration" || true
|
|
fi
|
|
|
|
# Hosts file
|
|
safe_copy "/etc/hosts" "${net_dir}/hosts" "Hosts file" || true
|
|
safe_copy "/etc/resolv.conf" "${net_dir}/resolv.conf" "DNS resolver config" || true
|
|
|
|
# Sanitize network configs
|
|
for file in "${net_dir}"/*; do
|
|
[[ -f "${file}" ]] && sanitize_file "${file}" || true
|
|
done
|
|
}
|
|
|
|
collect_storage_configs() {
|
|
banner "Collecting Storage Information"
|
|
|
|
local storage_dir="${OUTPUT_BASE_DIR}/configs/storage"
|
|
|
|
# Storage configuration is already in proxmox config, but let's get status
|
|
if check_command "pvesm"; then
|
|
safe_command "${storage_dir}/pvesm-status.txt" "Storage status" pvesm status || true
|
|
fi
|
|
|
|
# ZFS pools (if any)
|
|
if check_command "zpool"; then
|
|
safe_command "${storage_dir}/zpool-status.txt" "ZFS pool status" zpool status || true
|
|
safe_command "${storage_dir}/zpool-list.txt" "ZFS pool list" zpool list || true
|
|
fi
|
|
|
|
if check_command "zfs"; then
|
|
safe_command "${storage_dir}/zfs-list.txt" "ZFS datasets" zfs list || true
|
|
fi
|
|
|
|
# NFS exports
|
|
if [[ -f /etc/exports ]]; then
|
|
safe_copy "/etc/exports" "${storage_dir}/nfs-exports" "NFS exports" || true
|
|
fi
|
|
|
|
# Samba configuration
|
|
if [[ -f /etc/samba/smb.conf ]]; then
|
|
safe_copy "/etc/samba/smb.conf" "${storage_dir}/smb.conf" "Samba config" || true
|
|
fi
|
|
|
|
# iSCSI configuration
|
|
if [[ -f /etc/iscsi/iscsid.conf ]]; then
|
|
safe_copy "/etc/iscsi/iscsid.conf" "${storage_dir}/iscsid.conf" "iSCSI initiator config" || true
|
|
fi
|
|
}
|
|
|
|
collect_backup_configs() {
|
|
banner "Collecting Backup Configurations"
|
|
|
|
local backup_dir="${OUTPUT_BASE_DIR}/configs/backup"
|
|
|
|
# Vzdump configuration
|
|
if [[ -f /etc/vzdump.conf ]]; then
|
|
safe_copy "/etc/vzdump.conf" "${backup_dir}/vzdump.conf" "Vzdump config" || true
|
|
fi
|
|
|
|
# Backup jobs
|
|
if [[ -d /etc/pve/jobs ]]; then
|
|
safe_copy "/etc/pve/jobs" "${backup_dir}/jobs" "Scheduled backup jobs" || true
|
|
fi
|
|
|
|
# PBS configuration (if connected to Proxmox Backup Server)
|
|
if [[ -f /etc/pve/priv/storage.cfg ]]; then
|
|
grep -A5 "type: pbs" /etc/pve/priv/storage.cfg > "${backup_dir}/pbs-storage.txt" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
collect_cluster_information() {
|
|
banner "Collecting Cluster Information"
|
|
|
|
local cluster_dir="${OUTPUT_BASE_DIR}/exports/cluster"
|
|
|
|
# Cluster status
|
|
if check_command "pvecm"; then
|
|
safe_command "${cluster_dir}/cluster-status.txt" "Cluster status" pvecm status || true
|
|
safe_command "${cluster_dir}/cluster-nodes.txt" "Cluster nodes" pvecm nodes || true
|
|
fi
|
|
|
|
# Resource information
|
|
if check_command "pvesh"; then
|
|
safe_command "${cluster_dir}/cluster-resources.json" "Cluster resources" pvesh get /cluster/resources --output-format json
|
|
safe_command "${cluster_dir}/cluster-tasks.json" "Recent tasks" pvesh get /cluster/tasks --output-format json
|
|
fi
|
|
}
|
|
|
|
collect_guest_information() {
|
|
banner "Collecting Guest Information"
|
|
|
|
local guests_dir="${OUTPUT_BASE_DIR}/exports/guests"
|
|
|
|
# List all VMs
|
|
if check_command "qm"; then
|
|
safe_command "${guests_dir}/vm-list.txt" "VM list" qm list
|
|
fi
|
|
|
|
# List all containers
|
|
if check_command "pct"; then
|
|
safe_command "${guests_dir}/container-list.txt" "Container list" pct list
|
|
fi
|
|
|
|
# Detailed guest information in JSON format
|
|
if check_command "pvesh"; then
|
|
safe_command "${guests_dir}/all-guests.json" "All guests (JSON)" pvesh get /cluster/resources --type vm --output-format json
|
|
fi
|
|
}
|
|
|
|
collect_service_configs() {
|
|
banner "Collecting Service Configurations (Advanced)"
|
|
|
|
# Only collect if level is 'full' or 'paranoid'
|
|
if [[ "${COLLECTION_LEVEL}" != "full" ]] && [[ "${COLLECTION_LEVEL}" != "paranoid" ]]; then
|
|
log INFO "Skipping service configs (collection level: ${COLLECTION_LEVEL})"
|
|
return 0
|
|
fi
|
|
|
|
local services_dir="${OUTPUT_BASE_DIR}/configs/services"
|
|
mkdir -p "${services_dir}"
|
|
|
|
# Systemd service status
|
|
safe_command "${services_dir}/systemd-services.txt" "Systemd services" systemctl list-units --type=service --all
|
|
|
|
# Collect specific Proxmox service configs
|
|
local pve_services=(
|
|
"pve-cluster"
|
|
"pvedaemon"
|
|
"pveproxy"
|
|
"pvestatd"
|
|
"pve-firewall"
|
|
"pvescheduler"
|
|
)
|
|
|
|
for service in "${pve_services[@]}"; do
|
|
if systemctl list-unit-files | grep -q "^${service}"; then
|
|
safe_command "${services_dir}/${service}-status.txt" "${service} status" systemctl status "${service}" || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
################################################################################
|
|
# Documentation Generation
|
|
################################################################################
|
|
|
|
generate_readme() {
|
|
banner "Generating Documentation"
|
|
|
|
local readme="${OUTPUT_BASE_DIR}/README.md"
|
|
|
|
cat > "${readme}" <<'EOF'
|
|
# Homelab Infrastructure Export
|
|
|
|
This directory contains a complete snapshot of your Proxmox-based homelab infrastructure, collected automatically via the homelab collection script.
|
|
|
|
## Collection Information
|
|
|
|
- **Collection Date**: $(date '+%Y-%m-%d %H:%M:%S')
|
|
- **Proxmox Node**: $(hostname)
|
|
- **Collection Level**: ${COLLECTION_LEVEL}
|
|
- **Sanitization Applied**: IPs=${SANITIZE_IPS}, Passwords=${SANITIZE_PASSWORDS}, Tokens=${SANITIZE_TOKENS}
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
homelab-export-<timestamp>/
|
|
├── README.md # This file
|
|
├── SUMMARY.md # Collection summary report
|
|
├── collection.log # Detailed collection log
|
|
├── configs/ # Configuration files
|
|
│ ├── proxmox/ # Proxmox VE configurations
|
|
│ ├── vms/ # Virtual machine configs
|
|
│ ├── lxc/ # LXC container configs
|
|
│ ├── storage/ # Storage configurations
|
|
│ ├── network/ # Network configurations
|
|
│ ├── backup/ # Backup job configurations
|
|
│ └── services/ # System service configs (if collected)
|
|
├── exports/ # System state exports
|
|
│ ├── system/ # System information
|
|
│ ├── cluster/ # Cluster status and resources
|
|
│ └── guests/ # Guest VM/CT information
|
|
├── docs/ # Documentation (for manual additions)
|
|
├── scripts/ # Automation scripts (for manual additions)
|
|
└── diagrams/ # Network diagrams (for manual additions)
|
|
```
|
|
|
|
## Configuration Files
|
|
|
|
### Proxmox Core Configurations
|
|
- `datacenter.cfg` - Datacenter-wide settings
|
|
- `storage.cfg` - Storage pool definitions
|
|
- `user.cfg` - User and permission configurations
|
|
- `firewall-cluster.fw` - Cluster-level firewall rules
|
|
|
|
### Virtual Machines
|
|
Each VM configuration is named: `<VMID>-<name>.conf`
|
|
Firewall rules (if present): `<VMID>-<name>.fw`
|
|
|
|
### LXC Containers
|
|
Each container configuration is named: `<CTID>-<name>.conf`
|
|
Firewall rules (if present): `<CTID>-<name>.fw`
|
|
|
|
## System Exports
|
|
|
|
### System Information
|
|
- Proxmox version, hostname, kernel info
|
|
- CPU, memory, and disk information
|
|
- Network configuration and routing
|
|
- Installed packages
|
|
|
|
### Cluster Information
|
|
- Cluster status and membership
|
|
- Resource allocation
|
|
- Recent tasks
|
|
|
|
### Guest Information
|
|
- List of all VMs and containers
|
|
- Resource usage and status
|
|
- JSON exports for programmatic access
|
|
|
|
## Security Notes
|
|
|
|
This export may contain sensitive information depending on sanitization settings:
|
|
|
|
- **Passwords**: ${SANITIZE_PASSWORDS}
|
|
- **API Tokens**: ${SANITIZE_TOKENS}
|
|
- **IP Addresses**: ${SANITIZE_IPS}
|
|
|
|
**Recommendation**: Store this export securely. Do not commit to public repositories without careful review.
|
|
|
|
## Using This Export
|
|
|
|
### As Documentation
|
|
These files serve as a snapshot of your infrastructure at a point in time. Use them for:
|
|
- Documentation and disaster recovery
|
|
- Change tracking (diff with previous exports)
|
|
- Migration planning
|
|
|
|
### Infrastructure as Code
|
|
Use the collected configurations to:
|
|
- Create Terraform/OpenTofu templates
|
|
- Build Ansible playbooks
|
|
- Document network architecture
|
|
|
|
### Restoration Reference
|
|
In a disaster recovery scenario:
|
|
1. Reinstall Proxmox VE
|
|
2. Reference storage configuration from `configs/proxmox/storage.cfg`
|
|
3. Reference network setup from `configs/network/interfaces`
|
|
4. Recreate VMs/containers using configs in `configs/vms/` and `configs/lxc/`
|
|
5. Restore VM disk images from backups
|
|
|
|
## Next Steps
|
|
|
|
1. **Review the SUMMARY.md** for collection statistics
|
|
2. **Check collection.log** for any warnings or errors
|
|
3. **Manually add documentation** to the `docs/` folder
|
|
4. **Create network diagrams** and place in `diagrams/`
|
|
5. **Version control** this export in a private Git repository
|
|
6. **Set up regular collections** to track infrastructure changes
|
|
|
|
## Collection Script
|
|
|
|
This export was created by the Homelab Infrastructure Collection Script.
|
|
For questions or issues, consult the script documentation.
|
|
|
|
---
|
|
*Generated by homelab-export-script v${SCRIPT_VERSION}*
|
|
EOF
|
|
|
|
# Perform variable substitution
|
|
eval "cat > \"${readme}\" <<EOF
|
|
$(<"${readme}")
|
|
EOF"
|
|
|
|
log SUCCESS "Generated README.md"
|
|
}
|
|
|
|
generate_summary() {
|
|
banner "Generating Summary Report"
|
|
|
|
local summary="${OUTPUT_BASE_DIR}/SUMMARY.md"
|
|
|
|
cat > "${summary}" <<EOF
|
|
# Collection Summary Report
|
|
|
|
## Collection Metadata
|
|
|
|
- **Date/Time**: $(date '+%Y-%m-%d %H:%M:%S')
|
|
- **Hostname**: $(hostname)
|
|
- **Collection Level**: ${COLLECTION_LEVEL}
|
|
- **Script Version**: ${SCRIPT_VERSION}
|
|
|
|
## Sanitization Settings
|
|
|
|
- **IP Addresses**: ${SANITIZE_IPS}
|
|
- **Passwords**: ${SANITIZE_PASSWORDS}
|
|
- **Tokens/Keys**: ${SANITIZE_TOKENS}
|
|
|
|
## Collection Statistics
|
|
|
|
### Successfully Collected
|
|
Total items collected: ${#COLLECTED_ITEMS[@]}
|
|
|
|
EOF
|
|
|
|
for item in "${COLLECTED_ITEMS[@]}"; do
|
|
echo "- ${item}" >> "${summary}"
|
|
done
|
|
|
|
cat >> "${summary}" <<EOF
|
|
|
|
### Skipped Items
|
|
Total items skipped: ${#SKIPPED_ITEMS[@]}
|
|
|
|
EOF
|
|
|
|
if [[ ${#SKIPPED_ITEMS[@]} -gt 0 ]]; then
|
|
for item in "${SKIPPED_ITEMS[@]}"; do
|
|
echo "- ${item}" >> "${summary}"
|
|
done
|
|
else
|
|
echo "*None*" >> "${summary}"
|
|
fi
|
|
|
|
cat >> "${summary}" <<EOF
|
|
|
|
### Errors
|
|
Total errors: ${#ERROR_ITEMS[@]}
|
|
|
|
EOF
|
|
|
|
if [[ ${#ERROR_ITEMS[@]} -gt 0 ]]; then
|
|
for item in "${ERROR_ITEMS[@]}"; do
|
|
echo "- ${item}" >> "${summary}"
|
|
done
|
|
else
|
|
echo "*None*" >> "${summary}"
|
|
fi
|
|
|
|
cat >> "${summary}" <<EOF
|
|
|
|
## System Overview
|
|
|
|
### Proxmox Version
|
|
\`\`\`
|
|
$(pveversion -v 2>/dev/null || echo "Unable to retrieve version")
|
|
\`\`\`
|
|
|
|
### Virtual Machines
|
|
\`\`\`
|
|
$(qm list 2>/dev/null || echo "Unable to retrieve VM list")
|
|
\`\`\`
|
|
|
|
### Containers
|
|
\`\`\`
|
|
$(pct list 2>/dev/null || echo "Unable to retrieve container list")
|
|
\`\`\`
|
|
|
|
### Storage
|
|
\`\`\`
|
|
$(pvesm status 2>/dev/null || echo "Unable to retrieve storage status")
|
|
\`\`\`
|
|
|
|
### Disk Usage
|
|
\`\`\`
|
|
$(df -h 2>/dev/null || echo "Unable to retrieve disk usage")
|
|
\`\`\`
|
|
|
|
## Next Actions
|
|
|
|
1. Review any errors or skipped items above
|
|
2. Consult collection.log for detailed information
|
|
3. Manually verify sensitive information was sanitized
|
|
4. Add this export to your documentation repository
|
|
5. Create diagrams and additional documentation in respective folders
|
|
|
|
---
|
|
*Report generated $(date '+%Y-%m-%d %H:%M:%S')*
|
|
EOF
|
|
|
|
log SUCCESS "Generated SUMMARY.md"
|
|
}
|
|
|
|
################################################################################
|
|
# Main Collection Orchestration
|
|
################################################################################
|
|
|
|
run_collection() {
|
|
banner "Starting Homelab Infrastructure Collection"
|
|
|
|
log INFO "Collection Level: ${COLLECTION_LEVEL}"
|
|
log INFO "Output Directory: ${OUTPUT_BASE_DIR}"
|
|
log INFO "Sanitization: IPs=${SANITIZE_IPS} | Passwords=${SANITIZE_PASSWORDS} | Tokens=${SANITIZE_TOKENS}"
|
|
|
|
# Check if we're on a Proxmox host
|
|
if ! check_proxmox; then
|
|
log ERROR "This script must be run on a Proxmox VE host"
|
|
exit 1
|
|
fi
|
|
|
|
# Create directory structure
|
|
create_directory_structure
|
|
|
|
# System information (always collected)
|
|
collect_system_information
|
|
|
|
# Proxmox configurations (always collected)
|
|
collect_proxmox_configs
|
|
|
|
# VM and container configs (always collected)
|
|
collect_vm_configs
|
|
collect_lxc_configs
|
|
|
|
# Network configurations (always collected)
|
|
collect_network_configs
|
|
|
|
# Storage configurations (always collected)
|
|
collect_storage_configs
|
|
|
|
# Backup configurations (standard and above)
|
|
if [[ "${COLLECTION_LEVEL}" != "basic" ]]; then
|
|
collect_backup_configs
|
|
collect_cluster_information
|
|
collect_guest_information
|
|
fi
|
|
|
|
# Service configurations (full and paranoid)
|
|
collect_service_configs
|
|
|
|
# Generate documentation
|
|
generate_readme
|
|
generate_summary
|
|
|
|
banner "Collection Complete"
|
|
|
|
log SUCCESS "Total items collected: ${#COLLECTED_ITEMS[@]}"
|
|
log INFO "Total items skipped: ${#SKIPPED_ITEMS[@]}"
|
|
|
|
if [[ ${#ERROR_ITEMS[@]} -gt 0 ]]; then
|
|
log WARN "Total errors: ${#ERROR_ITEMS[@]}"
|
|
log WARN "Review ${LOG_FILE} for details"
|
|
fi
|
|
|
|
# Compress output if requested
|
|
if [[ "${COMPRESS_OUTPUT}" == "true" ]]; then
|
|
banner "Compressing Export"
|
|
local archive="${OUTPUT_BASE_DIR}.tar.gz"
|
|
tar -czf "${archive}" -C "$(dirname "${OUTPUT_BASE_DIR}")" "$(basename "${OUTPUT_BASE_DIR}")" 2>/dev/null && {
|
|
log SUCCESS "Created archive: ${archive}"
|
|
log INFO "Archive size: $(du -h "${archive}" | cut -f1)"
|
|
} || {
|
|
log WARN "Failed to create archive"
|
|
}
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BOLD}${GREEN}Export Location:${NC} ${OUTPUT_BASE_DIR}"
|
|
echo -e "${BOLD}${GREEN}Summary Report:${NC} ${OUTPUT_BASE_DIR}/SUMMARY.md"
|
|
echo -e "${BOLD}${GREEN}Collection Log:${NC} ${LOG_FILE}"
|
|
echo ""
|
|
}
|
|
|
|
################################################################################
|
|
# Script Usage and Help
|
|
################################################################################
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
${BOLD}Homelab Infrastructure Collection Script${NC}
|
|
Version: ${SCRIPT_VERSION}
|
|
|
|
${BOLD}USAGE:${NC}
|
|
${SCRIPT_NAME} [OPTIONS]
|
|
|
|
${BOLD}DESCRIPTION:${NC}
|
|
Collects Proxmox VE infrastructure configurations, system information,
|
|
and exports them in an organized directory structure. This script performs
|
|
READ-ONLY operations and makes no modifications to your system.
|
|
|
|
${BOLD}OPTIONS:${NC}
|
|
-l, --level LEVEL Collection level: basic, standard, full, paranoid
|
|
Default: standard
|
|
|
|
-o, --output DIR Output directory (default: ./homelab-export-<timestamp>)
|
|
|
|
-s, --sanitize WHAT Sanitize sensitive data. Options:
|
|
all - Sanitize everything (IPs, passwords, tokens)
|
|
ips - Sanitize IP addresses only
|
|
none - No sanitization
|
|
Default: passwords and tokens only
|
|
|
|
-c, --compress Compress output to .tar.gz (default: true)
|
|
--no-compress Skip compression
|
|
|
|
-v, --verbose Verbose output
|
|
-h, --help Show this help message
|
|
|
|
${BOLD}COLLECTION LEVELS:${NC}
|
|
basic - System info, Proxmox configs, VM/CT configs
|
|
standard - Basic + storage, network, backup configs, cluster info
|
|
full - Standard + service configs, detailed system state
|
|
paranoid - Full + everything possible (experimental)
|
|
|
|
${BOLD}EXAMPLES:${NC}
|
|
# Standard collection with default settings
|
|
${SCRIPT_NAME}
|
|
|
|
# Full collection with complete sanitization
|
|
${SCRIPT_NAME} --level full --sanitize all
|
|
|
|
# Basic collection without compression
|
|
${SCRIPT_NAME} --level basic --no-compress
|
|
|
|
# Custom output location with verbose logging
|
|
${SCRIPT_NAME} -o /backup/homelab-export -v
|
|
|
|
${BOLD}NOTES:${NC}
|
|
- Must be run on the Proxmox VE host (or via SSH)
|
|
- Requires root privileges for full access to configurations
|
|
- Output can be transferred to your documentation repository
|
|
- Review SUMMARY.md and collection.log after completion
|
|
|
|
${BOLD}SECURITY:${NC}
|
|
By default, passwords and tokens are sanitized. Use --sanitize all
|
|
to also redact IP addresses. Review exported files before committing
|
|
to version control or sharing.
|
|
|
|
For more information, consult the README.md generated with each export.
|
|
EOF
|
|
}
|
|
|
|
################################################################################
|
|
# Argument Parsing
|
|
################################################################################
|
|
|
|
parse_arguments() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-l|--level)
|
|
COLLECTION_LEVEL="$2"
|
|
shift 2
|
|
;;
|
|
-o|--output)
|
|
OUTPUT_BASE_DIR="$2"
|
|
LOG_FILE="${OUTPUT_BASE_DIR}/collection.log"
|
|
shift 2
|
|
;;
|
|
-s|--sanitize)
|
|
case "$2" in
|
|
all)
|
|
SANITIZE_IPS=true
|
|
SANITIZE_PASSWORDS=true
|
|
SANITIZE_TOKENS=true
|
|
;;
|
|
ips)
|
|
SANITIZE_IPS=true
|
|
SANITIZE_PASSWORDS=false
|
|
SANITIZE_TOKENS=false
|
|
;;
|
|
none)
|
|
SANITIZE_IPS=false
|
|
SANITIZE_PASSWORDS=false
|
|
SANITIZE_TOKENS=false
|
|
;;
|
|
*)
|
|
echo "Invalid sanitization option: $2"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift 2
|
|
;;
|
|
-c|--compress)
|
|
COMPRESS_OUTPUT=true
|
|
shift
|
|
;;
|
|
--no-compress)
|
|
COMPRESS_OUTPUT=false
|
|
shift
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate collection level
|
|
case "${COLLECTION_LEVEL}" in
|
|
basic|standard|full|paranoid)
|
|
;;
|
|
*)
|
|
echo "Invalid collection level: ${COLLECTION_LEVEL}"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
################################################################################
|
|
# Main Execution
|
|
################################################################################
|
|
|
|
main() {
|
|
parse_arguments "$@"
|
|
run_collection
|
|
}
|
|
|
|
# Check if script is being sourced or executed
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
main "$@"
|
|
fi
|