feat(infrastructure): initialize TrueNAS Scale infrastructure collection system

Initial repository setup for TrueNAS Scale configuration management and
disaster recovery. This system provides automated collection, versioning,
and documentation of TrueNAS configuration state.

Key components:
- Configuration collection scripts with API integration
- Disaster recovery exports (configs, storage, system state)
- Comprehensive documentation and API reference
- Sub-agent architecture for specialized operations

Infrastructure protected:
- Storage pools and datasets configuration
- Network configuration and routing
- Sharing services (NFS, SMB, iSCSI)
- System tasks (snapshots, replication, cloud sync)
- User and group management

Security measures:
- API keys managed via environment variables
- Sensitive data excluded via .gitignore
- No credentials committed to repository

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-16 08:03:33 -07:00
commit 52e1822de8
37 changed files with 40881 additions and 0 deletions

333
scripts/collect-truenas-config.sh Executable file
View File

@@ -0,0 +1,333 @@
#!/usr/bin/env bash
#
# TrueNAS Scale Collection Script v1.1.0
# Collects TrueNAS Scale infrastructure configuration via API
#
set -euo pipefail
# Default Configuration
TRUENAS_HOST="${TRUENAS_HOST:-192.168.2.150}"
TRUENAS_API_KEY="${TRUENAS_API_KEY:-}"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
OUTPUT_DIR=""
COLLECTION_LEVEL="standard"
# Colors
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
# Counters
COLLECTED=0; SKIPPED=0; ERRORS=0
# Usage/Help function
usage() {
cat << 'EOF'
Usage: collect-truenas-config.sh [OPTIONS] [OUTPUT_DIR]
TrueNAS Scale configuration collection script.
OPTIONS:
-l, --level LEVEL Collection level: basic, standard, full, paranoid
(default: standard)
-o, --output DIR Output directory path
(default: ./truenas-export-YYYYMMDD-HHMMSS)
-h, --host HOST TrueNAS host IP or hostname
(default: $TRUENAS_HOST or 192.168.2.150)
--help Show this help message
ENVIRONMENT VARIABLES:
TRUENAS_HOST TrueNAS host (default: 192.168.2.150)
TRUENAS_API_KEY TrueNAS API key (required)
COLLECTION_LEVEL Default collection level (default: standard)
EXAMPLES:
# Standard collection with named arguments
collect-truenas-config.sh --level standard --output ./truenas-exports
# Full collection to disaster recovery directory
collect-truenas-config.sh --level full --output ../disaster-recovery/
# Backward compatible (positional argument for output directory)
collect-truenas-config.sh ./my-export-dir
# Custom host
collect-truenas-config.sh --host 192.168.2.151 --level basic --output ./exports
COLLECTION LEVELS:
basic - System info, storage, shares, network, services
standard - Basic + tasks and users (default)
full - Standard + SMART data and metrics
paranoid - Full + all available diagnostics
EOF
exit 0
}
# Argument Parsing
parse_arguments() {
# Handle empty arguments
if [[ $# -eq 0 ]]; then
OUTPUT_DIR="./truenas-export-${TIMESTAMP}"
return
fi
# Check for help first
for arg in "$@"; do
if [[ "$arg" == "--help" ]]; then
usage
fi
done
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-l|--level)
if [[ -z "${2:-}" ]]; then
echo "Error: --level requires an argument" >&2
usage
fi
COLLECTION_LEVEL="$2"
shift 2
;;
-o|--output)
if [[ -z "${2:-}" ]]; then
echo "Error: --output requires an argument" >&2
usage
fi
OUTPUT_DIR="$2"
shift 2
;;
-h|--host)
if [[ -z "${2:-}" ]]; then
echo "Error: --host requires an argument" >&2
usage
fi
TRUENAS_HOST="$2"
shift 2
;;
--help)
usage
;;
-*)
echo "Error: Unknown option: $1" >&2
usage
;;
*)
# Backward compatibility: positional argument for output directory
if [[ -z "$OUTPUT_DIR" ]]; then
OUTPUT_DIR="$1"
shift
else
echo "Error: Unexpected argument: $1" >&2
usage
fi
;;
esac
done
# Set defaults if not provided
if [[ -z "$OUTPUT_DIR" ]]; then
OUTPUT_DIR="./truenas-export-${TIMESTAMP}"
fi
# Validate collection level
case "$COLLECTION_LEVEL" in
basic|standard|full|paranoid)
# Valid level
;;
*)
echo "Error: Invalid collection level: $COLLECTION_LEVEL" >&2
echo "Valid levels: basic, standard, full, paranoid" >&2
exit 1
;;
esac
}
# Parse arguments before setting up API configuration
parse_arguments "$@"
# Now validate API key and set up API base
TRUENAS_API_KEY="${TRUENAS_API_KEY:?API key required. Set TRUENAS_API_KEY environment variable}"
TRUENAS_API_BASE="https://${TRUENAS_HOST}/api/v2.0"
# Logging
log() {
local level=$1; shift
case "$level" in
INFO) echo -e "${BLUE}[INFO]${NC} $*" ;;
OK) echo -e "${GREEN}[✓]${NC} $*"; ((COLLECTED++)) ;;
WARN) echo -e "${YELLOW}[WARN]${NC} $*"; ((SKIPPED++)) ;;
ERROR) echo -e "${RED}[ERROR]${NC} $*" >&2; ((ERRORS++)) ;;
esac
}
# API call wrapper with timeout protection
api_call() {
local endpoint=$1 output=$2 desc=$3
mkdir -p "$(dirname "$output")"
log INFO "Fetching: ${endpoint}"
local code
local start_time=$(date +%s)
# FIXED: Added connection timeout (10s) and max time (30s) to prevent hangs
code=$(timeout 30 curl -s -w "%{http_code}" -X GET \
--connect-timeout 10 \
--max-time 30 \
"${TRUENAS_API_BASE}${endpoint}" \
-H "Authorization: Bearer ${TRUENAS_API_KEY}" \
-H "Content-Type: application/json" \
--insecure -o "$output" 2>/dev/null || echo "000")
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [[ "$code" == "200" ]]; then
# FIXED: Validate JSON response (only if jq is available)
if command -v jq &>/dev/null; then
if jq empty "$output" 2>/dev/null; then
log OK "$desc (${duration}s)"
return 0
else
log ERROR "$desc - Invalid JSON response"
rm -f "$output"
return 1
fi
else
# jq not available, skip validation
log OK "$desc (${duration}s)"
return 0
fi
elif [[ "$code" == "000" ]]; then
log ERROR "$desc - Connection timeout or failure after ${duration}s (endpoint: ${endpoint})"
rm -f "$output"
return 1
else
log WARN "$desc (HTTP $code after ${duration}s, endpoint: ${endpoint})"
rm -f "$output"
return 1
fi
}
# Main collection
main() {
echo -e "${CYAN}======================================${NC}"
echo -e "${CYAN}TrueNAS Scale Collection v1.1.0${NC}"
echo -e "${CYAN}======================================${NC}"
echo
log INFO "Host: $TRUENAS_HOST"
log INFO "Level: $COLLECTION_LEVEL"
log INFO "Output: $OUTPUT_DIR"
echo
# Create directory structure
mkdir -p "$OUTPUT_DIR"/{configs/{system,storage,sharing,network,services,tasks,users},exports/{storage,system,logs},metrics}
# System Information
echo -e "${CYAN}=== System Information ===${NC}"
api_call "/system/version" "$OUTPUT_DIR/exports/system/version.json" "TrueNAS version" || true
api_call "/system/info" "$OUTPUT_DIR/exports/system/info.json" "System information" || true
api_call "/system/general" "$OUTPUT_DIR/configs/system/general.json" "General config" || true
api_call "/system/advanced" "$OUTPUT_DIR/configs/system/advanced.json" "Advanced config" || true
echo
# Storage
echo -e "${CYAN}=== Storage ===${NC}"
api_call "/pool" "$OUTPUT_DIR/exports/storage/pools.json" "Storage pools" || true
api_call "/pool/dataset" "$OUTPUT_DIR/exports/storage/datasets.json" "Datasets" || true
api_call "/disk" "$OUTPUT_DIR/exports/storage/disks.json" "Disk inventory" || true
api_call "/zfs/snapshot" "$OUTPUT_DIR/exports/storage/snapshots.json" "ZFS snapshots" || true
echo
# Shares
echo -e "${CYAN}=== Shares ===${NC}"
api_call "/sharing/nfs" "$OUTPUT_DIR/configs/sharing/nfs.json" "NFS shares" || true
api_call "/sharing/smb" "$OUTPUT_DIR/configs/sharing/smb.json" "SMB shares" || true
api_call "/iscsi/target" "$OUTPUT_DIR/configs/sharing/iscsi-targets.json" "iSCSI targets" || true
echo
# Network
echo -e "${CYAN}=== Network ===${NC}"
api_call "/interface" "$OUTPUT_DIR/configs/network/interfaces.json" "Network interfaces" || true
api_call "/network/configuration" "$OUTPUT_DIR/configs/network/config.json" "Network config" || true
api_call "/staticroute" "$OUTPUT_DIR/configs/network/routes.json" "Static routes" || true
echo
# Services
echo -e "${CYAN}=== Services ===${NC}"
api_call "/service" "$OUTPUT_DIR/configs/services/status.json" "Services status" || true
api_call "/ssh" "$OUTPUT_DIR/configs/services/ssh.json" "SSH config" || true
echo
# Tasks
if [[ "$COLLECTION_LEVEL" != "basic" ]]; then
echo -e "${CYAN}=== Tasks ===${NC}"
api_call "/cronjob" "$OUTPUT_DIR/configs/tasks/cron.json" "Cron jobs" || true
api_call "/pool/snapshottask" "$OUTPUT_DIR/configs/tasks/snapshots.json" "Snapshot tasks" || true
api_call "/replication" "$OUTPUT_DIR/configs/tasks/replication.json" "Replication" || true
api_call "/cloudsync" "$OUTPUT_DIR/configs/tasks/cloudsync.json" "Cloud sync" || true
echo
fi
# Users
if [[ "$COLLECTION_LEVEL" != "basic" ]]; then
echo -e "${CYAN}=== Users ===${NC}"
api_call "/user" "$OUTPUT_DIR/configs/users/users.json" "User accounts" || true
api_call "/group" "$OUTPUT_DIR/configs/users/groups.json" "Groups" || true
echo
fi
# SMART data (full/paranoid only)
if [[ "$COLLECTION_LEVEL" == "full" ]] || [[ "$COLLECTION_LEVEL" == "paranoid" ]]; then
echo -e "${CYAN}=== SMART Data ===${NC}"
api_call "/smart/test/results" "$OUTPUT_DIR/metrics/smart-results.json" "SMART test results" || true
echo
fi
# Generate summary
cat > "$OUTPUT_DIR/SUMMARY.md" << EOF
# TrueNAS Scale Export Summary
**Date**: $(date '+%Y-%m-%d %H:%M:%S')
**Host**: $TRUENAS_HOST
**Level**: $COLLECTION_LEVEL
## Statistics
- Collected: $COLLECTED items
- Skipped: $SKIPPED items
- Errors: $ERRORS items
## Files Created
$(find "$OUTPUT_DIR" -type f -name "*.json" | wc -l) JSON files collected
See individual files in:
- configs/ - Configuration files
- exports/ - System state exports
- metrics/ - Performance data
EOF
# Summary
echo
echo -e "${CYAN}======================================${NC}"
echo -e "${CYAN}Collection Complete${NC}"
echo -e "${CYAN}======================================${NC}"
echo -e "${GREEN}✓ Collected:${NC} $COLLECTED items"
echo -e "${YELLOW}⊘ Skipped:${NC} $SKIPPED items"
echo -e "${RED}✗ Errors:${NC} $ERRORS items"
echo
echo -e "${BLUE}Output:${NC} $OUTPUT_DIR"
echo -e "${BLUE}Summary:${NC} $OUTPUT_DIR/SUMMARY.md"
echo
# Compress
if command -v tar &>/dev/null; then
local archive="${OUTPUT_DIR}.tar.gz"
log INFO "Compressing to $archive..."
tar -czf "$archive" -C "$(dirname "$OUTPUT_DIR")" "$(basename "$OUTPUT_DIR")" 2>/dev/null
log INFO "Archive: $(du -h "$archive" | cut -f1)"
fi
}
main