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:
333
scripts/collect-truenas-config.sh
Executable file
333
scripts/collect-truenas-config.sh
Executable 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
|
||||
123
scripts/test_truenas_api_connectivity.sh
Executable file
123
scripts/test_truenas_api_connectivity.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# TrueNAS Scale API Connectivity Test
|
||||
# Purpose: Validate network connectivity and API accessibility for TrueNAS collection script
|
||||
# Target: 192.168.2.150
|
||||
# Date: 2025-12-14
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TRUENAS_IP="192.168.2.150"
|
||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
LOG_FILE="/tmp/truenas_api_test_$(date '+%Y%m%d_%H%M%S').log"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() {
|
||||
echo -e "${1}" | tee -a "${LOG_FILE}"
|
||||
}
|
||||
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
log "${BLUE}=========================================="
|
||||
log "TrueNAS Scale API Connectivity Test"
|
||||
log "=========================================="
|
||||
log "${NC}Target: ${TRUENAS_IP}"
|
||||
log "Date: ${TIMESTAMP}"
|
||||
log ""
|
||||
|
||||
# TEST 1: Network Connectivity
|
||||
log "${YELLOW}[TEST 1] Network Connectivity${NC}"
|
||||
if ping -c 3 -W 5 "${TRUENAS_IP}" >> "${LOG_FILE}" 2>&1; then
|
||||
log "${GREEN}✓ PASS: Host reachable${NC}"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
log "${RED}✗ FAIL: Host NOT reachable${NC}"
|
||||
((TESTS_FAILED++))
|
||||
exit 1
|
||||
fi
|
||||
log ""
|
||||
|
||||
# TEST 2: HTTP Port
|
||||
log "${YELLOW}[TEST 2] HTTP Port (80)${NC}"
|
||||
if timeout 5 bash -c "echo > /dev/tcp/${TRUENAS_IP}/80" 2>/dev/null; then
|
||||
log "${GREEN}✓ PASS: Port 80 OPEN${NC}"
|
||||
((TESTS_PASSED++))
|
||||
HTTP_AVAILABLE=true
|
||||
else
|
||||
log "${RED}✗ FAIL: Port 80 CLOSED${NC}"
|
||||
((TESTS_FAILED++))
|
||||
HTTP_AVAILABLE=false
|
||||
fi
|
||||
log ""
|
||||
|
||||
# TEST 3: HTTPS Port
|
||||
log "${YELLOW}[TEST 3] HTTPS Port (443)${NC}"
|
||||
if timeout 5 bash -c "echo > /dev/tcp/${TRUENAS_IP}/443" 2>/dev/null; then
|
||||
log "${GREEN}✓ PASS: Port 443 OPEN${NC}"
|
||||
((TESTS_PASSED++))
|
||||
HTTPS_AVAILABLE=true
|
||||
else
|
||||
log "${RED}✗ FAIL: Port 443 CLOSED${NC}"
|
||||
((TESTS_FAILED++))
|
||||
HTTPS_AVAILABLE=false
|
||||
fi
|
||||
log ""
|
||||
|
||||
# TEST 4: HTTP API
|
||||
if [ "$HTTP_AVAILABLE" = true ]; then
|
||||
log "${YELLOW}[TEST 4] HTTP API Endpoint${NC}"
|
||||
HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" -m 10 "http://${TRUENAS_IP}/api/v2.0/system/version" 2>&1 || echo "000")
|
||||
HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -n 1)
|
||||
|
||||
log "HTTP Status: ${HTTP_CODE}"
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
log "${GREEN}✓ PASS: HTTP API accessible${NC}"
|
||||
((TESTS_PASSED++))
|
||||
elif [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
|
||||
log "${YELLOW}⚠ INFO: Authentication required${NC}"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
log "${RED}✗ FAIL: Unexpected status ${HTTP_CODE}${NC}"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
fi
|
||||
log ""
|
||||
|
||||
# TEST 5: HTTPS API
|
||||
if [ "$HTTPS_AVAILABLE" = true ]; then
|
||||
log "${YELLOW}[TEST 5] HTTPS API Endpoint${NC}"
|
||||
HTTPS_RESPONSE=$(curl -s -k -w "\n%{http_code}" -m 10 "https://${TRUENAS_IP}/api/v2.0/system/version" 2>&1 || echo "000")
|
||||
HTTPS_CODE=$(echo "$HTTPS_RESPONSE" | tail -n 1)
|
||||
HTTPS_BODY=$(echo "$HTTPS_RESPONSE" | sed '$d')
|
||||
|
||||
log "HTTPS Status: ${HTTPS_CODE}"
|
||||
log "Response: ${HTTPS_BODY}"
|
||||
|
||||
if [ "$HTTPS_CODE" = "200" ]; then
|
||||
log "${GREEN}✓ PASS: HTTPS API accessible${NC}"
|
||||
((TESTS_PASSED++))
|
||||
elif [ "$HTTPS_CODE" = "401" ] || [ "$HTTPS_CODE" = "403" ]; then
|
||||
log "${YELLOW}⚠ INFO: Authentication required${NC}"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
log "${RED}✗ FAIL: Unexpected status ${HTTPS_CODE}${NC}"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
fi
|
||||
log ""
|
||||
|
||||
log "${BLUE}=========================================="
|
||||
log "SUMMARY"
|
||||
log "=========================================="
|
||||
log "${NC}Passed: ${GREEN}${TESTS_PASSED}${NC}"
|
||||
log "Failed: ${RED}${TESTS_FAILED}${NC}"
|
||||
log ""
|
||||
log "Log: ${LOG_FILE}"
|
||||
|
||||
exit $TESTS_FAILED
|
||||
Reference in New Issue
Block a user