Initial commit: homelab configuration and documentation

This commit is contained in:
2025-11-29 19:03:14 +00:00
commit 0769ca6888
72 changed files with 7806 additions and 0 deletions

53
scripts/backup_daily.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# backup_daily.sh - Daily backup script using restic to Backblaze B2
set -euo pipefail
# Configuration
export B2_ACCOUNT_ID="your_b2_account_id"
export B2_ACCOUNT_KEY="your_b2_account_key"
export RESTIC_REPOSITORY="b2:your-bucket-name:/backups"
export RESTIC_PASSWORD="your_restic_password"
# Backup targets
BACKUP_DIRS=(
"/var/lib/docker/volumes/homeassistant/_data"
"/var/lib/docker/volumes/portainer/_data"
"/var/lib/docker/volumes/nextcloud/_data"
"/mnt/nas/models"
)
# Logging
LOG_FILE="/var/log/restic_backup.log"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "=== Restic Backup Started at $(date) ==="
# Check if repository is initialized
if ! restic snapshots &>/dev/null; then
echo "Repository not initialized. Initializing..."
restic init
fi
# Perform backup
echo "Backing up directories: ${BACKUP_DIRS[*]}"
restic backup "${BACKUP_DIRS[@]}" \
--tag homelab \
--verbose
# Prune old backups (keep last 7 daily, 4 weekly, 12 monthly)
echo "Pruning old backups..."
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 12 \
--prune
# Check repository integrity (monthly)
DAY_OF_MONTH=$(date +%d)
if [ "$DAY_OF_MONTH" == "01" ]; then
echo "Running repository check..."
restic check
fi
echo "=== Restic Backup Completed at $(date) ==="

View File

@@ -0,0 +1,96 @@
#!/bin/bash
# create_docker_secrets.sh - Create all Docker secrets for swarm stacks
# Run this ONCE before deploying the fixed stack files
set -euo pipefail
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
echo -e "${YELLOW}Docker Secrets Creation Script${NC}"
echo "This will create all required secrets for your swarm stacks."
echo ""
# Check if running on swarm manager
if ! docker node ls &>/dev/null; then
echo -e "${RED}Error: This must be run on a Docker Swarm manager node${NC}"
exit 1
fi
# Function to create secret
create_secret() {
local SECRET_NAME=$1
local SECRET_DESCRIPTION=$2
local DEFAULT_VALUE=$3
if docker secret inspect "$SECRET_NAME" &>/dev/null; then
echo -e "${YELLOW}⚠ Secret '$SECRET_NAME' already exists, skipping${NC}"
return 0
fi
echo -e "\n${GREEN}Creating secret: $SECRET_NAME${NC}"
echo "$SECRET_DESCRIPTION"
if [[ -n "$DEFAULT_VALUE" ]]; then
read -p "Enter value (default: $DEFAULT_VALUE): " SECRET_VALUE
SECRET_VALUE=${SECRET_VALUE:-$DEFAULT_VALUE}
else
read -sp "Enter value (hidden): " SECRET_VALUE
echo
fi
if [[ -z "$SECRET_VALUE" ]]; then
echo -e "${RED}Error: Secret value cannot be empty${NC}"
return 1
fi
echo -n "$SECRET_VALUE" | docker secret create "$SECRET_NAME" -
echo -e "${GREEN}✓ Created secret: $SECRET_NAME${NC}"
}
echo "==================================="
echo "Paperless Secrets"
echo "==================================="
create_secret "paperless_db_password" \
"Database password for Paperless PostgreSQL" \
""
create_secret "paperless_secret_key" \
"Django secret key for Paperless (50+ random characters)" \
""
echo ""
echo "==================================="
echo "Grafana Secrets"
echo "==================================="
create_secret "grafana_admin_password" \
"Grafana admin password" \
""
echo ""
echo "==================================="
echo "DuckDNS Secret"
echo "==================================="
create_secret "duckdns_token" \
"DuckDNS API token (from duckdns.org account)" \
""
echo ""
echo -e "${GREEN}==================================="
echo "All secrets created successfully!"
echo "===================================${NC}"
echo ""
echo "Verify secrets:"
echo " docker secret ls"
echo ""
echo "To remove a secret (if needed):"
echo " docker secret rm <secret_name>"
echo ""
echo "IMPORTANT: Secret values cannot be retrieved after creation."
echo "Store them securely in a password manager!"

181
scripts/deploy_all.sh Executable file
View File

@@ -0,0 +1,181 @@
#!/bin/bash
# deploy_all.sh - Master deployment script for all homelab improvements
# This script orchestrates the deployment of all components in the correct order
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging
LOG_FILE="/var/log/homelab_deployment.log"
exec > >(tee -a "$LOG_FILE") 2>&1
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Home Lab Deployment Script${NC}"
echo -e "${GREEN}Started at $(date)${NC}"
echo -e "${GREEN}========================================${NC}\n"
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Deployment phases
PHASES=(
"network:Network Upgrade"
"storage:Storage Enhancements"
"services:Service Consolidation"
"security:Security Hardening"
"monitoring:Monitoring & Automation"
"backup:Backup Strategy"
)
deploy_network() {
echo -e "\n${YELLOW}[PHASE 1/6] Network Upgrade${NC}"
echo "This phase requires manual hardware installation."
echo "Please ensure the 2.5Gb switch is installed before proceeding."
read -p "Has the new switch been installed? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Skipping network upgrade. Please install switch first."
return 0
fi
echo "Configuring VLAN firewall rules..."
bash /workspace/homelab/scripts/vlan_firewall.sh
echo -e "${GREEN}✓ Network configuration complete${NC}"
}
deploy_storage() {
echo -e "\n${YELLOW}[PHASE 2/6] Storage Enhancements${NC}"
read -p "Create ZFS pool on Proxmox host? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Creating ZFS pool..."
bash /workspace/homelab/scripts/zfs_setup.sh
fi
echo -e "\n${YELLOW}Please mount NAS shares manually using:${NC}"
echo " Guide: /workspace/homelab/docs/guides/NAS_Mount_Guide.md"
read -p "Press enter when NAS is mounted..."
echo "Setting up AI model pruning cron job..."
(crontab -l 2>/dev/null; echo "0 3 * * * /workspace/homelab/scripts/prune_ai_models.sh") | crontab -
echo -e "${GREEN}✓ Storage configuration complete${NC}"
}
deploy_services() {
echo -e "\n${YELLOW}[PHASE 3/6] Service Consolidation${NC}"
read -p "Deploy Traefik Swarm service? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Deploying Traefik stack..."
docker stack deploy -c /workspace/homelab/services/swarm/traefik/stack.yml traefik
sleep 5
docker service ls | grep traefik
fi
read -p "Deploy Caddy fallback on Pi Zero? (requires SSH to .62) (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Please deploy Caddy manually on Pi Zero (.62)"
echo " cd /workspace/homelab/services/standalone/Caddy"
echo " docker-compose up -d"
fi
read -p "Deploy n8n stack? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Deploying n8n stack..."
docker stack deploy -c /workspace/homelab/services/swarm/stacks/n8n-stack.yml n8n
sleep 5
docker service ls | grep n8n
fi
echo -e "${GREEN}✓ Service consolidation complete${NC}"
}
deploy_security() {
echo -e "\n${YELLOW}[PHASE 4/6] Security Hardening${NC}"
read -p "Install fail2ban on manager VM? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Installing fail2ban..."
bash /workspace/homelab/scripts/install_fail2ban.sh
fi
echo -e "${GREEN}✓ Security hardening complete${NC}"
}
deploy_monitoring() {
echo -e "\n${YELLOW}[PHASE 5/6] Monitoring & Automation${NC}"
read -p "Deploy monitoring stack? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Setting up monitoring..."
bash /workspace/homelab/scripts/setup_monitoring.sh
fi
echo -e "${GREEN}✓ Monitoring setup complete${NC}"
}
deploy_backup() {
echo -e "\n${YELLOW}[PHASE 6/6] Backup Strategy${NC}"
echo -e "${YELLOW}Before proceeding, ensure you have:${NC}"
echo " 1. Backblaze B2 account created"
echo " 2. B2 bucket created"
echo " 3. Updated /workspace/homelab/scripts/backup_daily.sh with credentials"
read -p "Are credentials configured? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Skipping backup setup. Please configure credentials first."
return 0
fi
echo "Installing restic backup..."
bash /workspace/homelab/scripts/install_restic_backup.sh
echo -e "${GREEN}✓ Backup strategy complete${NC}"
}
# Main deployment flow
main() {
echo "This script will guide you through the deployment of all homelab improvements."
echo "You can skip any phase if needed."
echo ""
deploy_network
deploy_storage
deploy_services
deploy_security
deploy_monitoring
deploy_backup
echo -e "\n${GREEN}========================================${NC}"
echo -e "${GREEN}Deployment Complete!${NC}"
echo -e "${GREEN}Completed at $(date)${NC}"
echo -e "${GREEN}========================================${NC}\n"
echo "Post-deployment verification:"
echo " 1. Check Docker services: docker service ls"
echo " 2. Check container health: docker ps --filter health=healthy"
echo " 3. Check fail2ban: sudo fail2ban-client status"
echo " 4. Check monitoring: curl http://192.168.1.196:9100/metrics"
echo " 5. Check backups: sudo systemctl status restic-backup.timer"
echo ""
echo "Full verification guide: /workspace/homelab/docs/guides/DEPLOYMENT_GUIDE.md"
echo "Log file: $LOG_FILE"
}
main "$@"

27
scripts/install_fail2ban.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
# install_fail2ban.sh - Install and configure fail2ban on manager VM
set -euo pipefail
echo "Installing fail2ban..."
sudo apt-get update
sudo apt-get install -y fail2ban
echo "Creating fail2ban directories..."
sudo mkdir -p /etc/fail2ban/filter.d
echo "Copying custom filters..."
sudo cp /workspace/homelab/security/fail2ban/filter.d/portainer.conf /etc/fail2ban/filter.d/
sudo cp /workspace/homelab/security/fail2ban/filter.d/traefik-auth.conf /etc/fail2ban/filter.d/
echo "Copying jail configuration..."
sudo cp /workspace/homelab/security/fail2ban/jail.local /etc/fail2ban/
echo "Restarting fail2ban service..."
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban
echo "Checking fail2ban status..."
sudo fail2ban-client status
echo "fail2ban installation complete."

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# install_restic_backup.sh - Install restic and configure systemd timer
set -euo pipefail
echo "Installing restic..."
sudo apt-get update
sudo apt-get install -y restic
echo "Making backup script executable..."
sudo chmod +x /workspace/homelab/scripts/backup_daily.sh
echo "Installing systemd service and timer..."
sudo cp /workspace/homelab/systemd/restic-backup.service /etc/systemd/system/
sudo cp /workspace/homelab/systemd/restic-backup.timer /etc/systemd/system/
echo "Reloading systemd daemon..."
sudo systemctl daemon-reload
echo "Enabling and starting timer..."
sudo systemctl enable restic-backup.timer
sudo systemctl start restic-backup.timer
echo "Checking timer status..."
sudo systemctl status restic-backup.timer
echo "Restic backup installation complete."
echo "Remember to update /workspace/homelab/scripts/backup_daily.sh with your B2 credentials."

View File

@@ -0,0 +1,80 @@
#!/bin/bash
# network_performance_test.sh - Test network performance between nodes
# This script uses iperf3 to measure bandwidth between homelab nodes
set -euo pipefail
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Node IPs
NODES=(
"192.168.1.81:Ryzen"
"192.168.1.57:Proxmox"
"192.168.1.196:Manager"
"192.168.1.245:Pi4"
"192.168.1.62:PiZero"
)
echo "========================================="
echo "Network Performance Testing"
echo "========================================="
# Check if iperf3 is installed
if ! command -v iperf3 >/dev/null 2>&1; then
echo "Installing iperf3..."
sudo apt-get update && sudo apt-get install -y iperf3
fi
# Get current node IP
CURRENT_IP=$(hostname -I | awk '{print $1}')
echo -e "\nTesting from: $CURRENT_IP\n"
test_node() {
local NODE_INFO=$1
local IP=$(echo $NODE_INFO | cut -d: -f1)
local NAME=$(echo $NODE_INFO | cut -d: -f2)
if [[ "$IP" == "$CURRENT_IP" ]]; then
return
fi
echo -e "${YELLOW}Testing to $NAME ($IP)...${NC}"
# Test if iperf3 server is running
if timeout 2 nc -z $IP 5201 2>/dev/null; then
# Run bandwidth test
RESULT=$(iperf3 -c $IP -t 5 -f M 2>/dev/null | grep "receiver" | awk '{print $7, $8}')
if [[ -n "$RESULT" ]]; then
echo -e "${GREEN} → Bandwidth: $RESULT${NC}"
else
echo " → Test failed (server may be busy)"
fi
else
echo " → iperf3 server not running on $NAME"
echo " → Run on $NAME: iperf3 -s -D"
fi
}
# Test all nodes
for NODE in "${NODES[@]}"; do
test_node "$NODE"
done
echo -e "\n========================================="
echo "Test complete"
echo "=========================================
"
# Recommendations
echo -e "\nRecommendations:"
echo "• Expected speeds:"
echo " - Ryzen/Proxmox: 2.5 Gb (2500 Mbits/sec)"
echo " - Pi 4: 1 Gb (1000 Mbits/sec)"
echo " - Pi Zero: 100 Mb (100 Mbits/sec)"
echo "• If speeds are lower, check:"
echo " - Switch port configuration"
echo " - Cable quality (Cat6 for 2.5Gb)"
echo " - Network interface settings"

18
scripts/prune_ai_models.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# prune_ai_models.sh - Remove AI model files older than 30 days to free space
# Adjust the MODEL_DIR path to where your AI models are stored (e.g., /mnt/nas/models)
set -euo pipefail
MODEL_DIR="/mnt/nas/models"
DAYS=30
if [[ ! -d "$MODEL_DIR" ]]; then
echo "Model directory $MODEL_DIR does not exist. Exiting."
exit 1
fi
echo "Pruning model files in $MODEL_DIR older than $DAYS days..."
find "$MODEL_DIR" -type f -mtime +$DAYS -print -delete
echo "Prune completed."

132
scripts/quick_status.sh Executable file
View File

@@ -0,0 +1,132 @@
#!/bin/bash
# quick_status.sh - Quick health check of all homelab components
# Run this anytime to get a fast overview of system status
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
clear
echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Home Lab Quick Status Check ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════╝${NC}"
echo ""
# System Info
echo -e "${YELLOW}📊 System Information${NC}"
echo " Hostname: $(hostname)"
echo " Uptime: $(uptime -p)"
echo " Load: $(uptime | awk -F'load average:' '{print $2}')"
echo ""
# Docker Swarm
echo -e "${YELLOW}🐳 Docker Swarm${NC}"
if docker node ls &>/dev/null; then
TOTAL_NODES=$(docker node ls | grep -c Ready || echo "0")
echo -e " ${GREEN}${NC} Swarm active ($TOTAL_NODES nodes)"
docker service ls --format "table {{.Name}}\t{{.Replicas}}" | head -10
else
echo -e " ${RED}${NC} Not a swarm manager"
fi
echo ""
# Services Health
echo -e "${YELLOW}🏥 Container Health${NC}"
HEALTHY=$(docker ps --filter "health=healthy" --format "{{.Names}}" | wc -l 2>/dev/null || echo "0")
UNHEALTHY=$(docker ps --filter "health=unhealthy" --format "{{.Names}}" | wc -l 2>/dev/null || echo "0")
TOTAL=$(docker ps --format "{{.Names}}" | wc -l 2>/dev/null || echo "0")
echo -e " Healthy: ${GREEN}$HEALTHY${NC}"
echo -e " Unhealthy: ${RED}$UNHEALTHY${NC}"
echo -e " Total: $TOTAL"
if [[ $UNHEALTHY -gt 0 ]]; then
echo -e " ${RED}⚠ Unhealthy containers:${NC}"
docker ps --filter "health=unhealthy" --format " - {{.Names}}"
fi
echo ""
# Storage
echo -e "${YELLOW}💾 Storage${NC}"
df -h / /mnt/nas 2>/dev/null | tail -n +2 | awk '{printf " %-20s %5s used of %5s\n", $6, $3, $2}'
if command -v zpool &>/dev/null && zpool list tank &>/dev/null; then
HEALTH=$(zpool list -H -o health tank)
if [[ "$HEALTH" == "ONLINE" ]]; then
echo -e " ZFS tank: ${GREEN}$HEALTH${NC}"
else
echo -e " ZFS tank: ${RED}$HEALTH${NC}"
fi
fi
echo ""
# Network
echo -e "${YELLOW}🌐 Network${NC}"
IP=$(hostname -I | awk '{print $1}')
echo " IP: $IP"
if command -v ethtool &>/dev/null; then
SPEED=$(ethtool eth0 2>/dev/null | grep Speed | awk '{print $2}' || echo "Unknown")
echo " Speed: $SPEED"
fi
if ping -c 1 8.8.8.8 &>/dev/null; then
echo -e " Internet: ${GREEN}✓ Connected${NC}"
else
echo -e " Internet: ${RED}✗ Disconnected${NC}"
fi
echo ""
# Security
echo -e "${YELLOW}🔒 Security${NC}"
if systemctl is-active --quiet fail2ban 2>/dev/null; then
BANNED=$(sudo fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $4}' || echo "0")
echo -e " fail2ban: ${GREEN}✓ Active${NC} ($BANNED IPs banned)"
else
echo -e " fail2ban: ${YELLOW}⚠ Not running${NC}"
fi
echo ""
# Backups
echo -e "${YELLOW}💾 Backups${NC}"
if systemctl is-active --quiet restic-backup.timer 2>/dev/null; then
NEXT=$(systemctl list-timers | grep restic-backup | awk '{print $1, $2}')
echo -e " Restic timer: ${GREEN}✓ Active${NC}"
echo " Next backup: $NEXT"
else
echo -e " Restic timer: ${YELLOW}⚠ Not configured${NC}"
fi
echo ""
# Monitoring
echo -e "${YELLOW}📈 Monitoring${NC}"
if curl -s http://localhost:9100/metrics &>/dev/null; then
echo -e " node-exporter: ${GREEN}✓ Running${NC}"
else
echo -e " node-exporter: ${YELLOW}⚠ Not accessible${NC}"
fi
if curl -s http://192.168.1.196:3000 &>/dev/null; then
echo -e " Grafana: ${GREEN}✓ Accessible${NC}"
else
echo -e " Grafana: ${YELLOW}⚠ Not accessible${NC}"
fi
echo ""
# Quick recommendations
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
if [[ $UNHEALTHY -gt 0 ]]; then
echo -e "${YELLOW}⚠ Action needed: $UNHEALTHY unhealthy containers${NC}"
fi
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [[ $DISK_USAGE -gt 80 ]]; then
echo -e "${YELLOW}⚠ Warning: Disk usage at ${DISK_USAGE}%${NC}"
fi
echo ""
echo "For detailed validation: bash /workspace/homelab/scripts/validate_deployment.sh"
echo ""

87
scripts/setup_log_rotation.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
# setup_log_rotation.sh - Configure log rotation for homelab services
set -euo pipefail
echo "Configuring log rotation for homelab services..."
# Docker logs
cat > /etc/logrotate.d/docker-containers << 'EOF'
/var/lib/docker/containers/*/*.log {
rotate 7
daily
compress
size=10M
missingok
delaycompress
copytruncate
}
EOF
# Traefik logs
cat > /etc/logrotate.d/traefik << 'EOF'
/var/log/traefik/*.log {
rotate 14
daily
compress
missingok
delaycompress
postrotate
docker service update --force traefik_traefik > /dev/null 2>&1 || true
endscript
}
EOF
# fail2ban logs
cat > /etc/logrotate.d/fail2ban-custom << 'EOF'
/var/log/fail2ban.log {
rotate 30
daily
compress
missingok
notifempty
postrotate
systemctl reload fail2ban > /dev/null 2>&1 || true
endscript
}
EOF
# Restic backup logs
cat > /etc/logrotate.d/restic-backup << 'EOF'
/var/log/restic_backup.log {
rotate 30
daily
compress
missingok
notifempty
}
EOF
# Caddy logs
cat > /etc/logrotate.d/caddy << 'EOF'
/var/log/caddy/*.log {
rotate 7
daily
compress
missingok
delaycompress
}
EOF
# Home lab deployment logs
cat > /etc/logrotate.d/homelab << 'EOF'
/var/log/homelab_deployment.log {
rotate 90
daily
compress
missingok
notifempty
}
EOF
echo "Testing logrotate configuration..."
logrotate -d /etc/logrotate.d/docker-containers
echo "Log rotation configured successfully."
echo "Logs will be rotated daily and compressed."
echo "Configuration files created in /etc/logrotate.d/"

22
scripts/setup_monitoring.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# setup_monitoring.sh - Deploy node-exporter and configure Grafana alerts
set -euo pipefail
echo "Deploying node-exporter stack..."
docker stack deploy -c /workspace/homelab/services/swarm/stacks/node-exporter-stack.yml monitoring
echo "Waiting for node-exporter to start..."
sleep 10
echo "Copying alert rules to Grafana provisioning directory..."
# Adjust this path to match your Grafana data directory
GRAFANA_PROVISIONING="/var/lib/docker/volumes/grafana-provisioning/_data/alerting"
sudo mkdir -p "$GRAFANA_PROVISIONING"
sudo cp /workspace/homelab/monitoring/grafana/alert_rules.yml "$GRAFANA_PROVISIONING/"
echo "Restarting Grafana to load new alert rules..."
docker service update --force grafana_grafana
echo "Monitoring setup complete."
echo "Check Grafana UI to verify alerts are loaded."

195
scripts/validate_deployment.sh Executable file
View File

@@ -0,0 +1,195 @@
#!/bin/bash
# validate_deployment.sh - Validation script to verify all homelab components
# Run this after deployment to ensure everything is working correctly
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
PASSED=0
FAILED=0
WARNINGS=0
check_pass() {
echo -e "${GREEN}$1${NC}"
((PASSED++))
}
check_fail() {
echo -e "${RED}$1${NC}"
((FAILED++))
}
check_warn() {
echo -e "${YELLOW}$1${NC}"
((WARNINGS++))
}
echo "========================================="
echo "Home Lab Deployment Validation"
echo "Started at $(date)"
echo "========================================="
# Network Validation
echo -e "\n${YELLOW}[1/6] Network Configuration${NC}"
if ip -d link show | grep -q "vlan"; then
check_pass "VLANs configured"
else
check_warn "VLANs not detected (may not be configured yet)"
fi
if command -v ethtool >/dev/null 2>&1; then
SPEED=$(ethtool eth0 2>/dev/null | grep Speed | awk '{print $2}')
if [[ "$SPEED" == *"2500"* ]] || [[ "$SPEED" == *"5000"* ]]; then
check_pass "High-speed network detected: $SPEED"
else
check_warn "Network speed: $SPEED (expected 2.5Gb or higher)"
fi
else
check_warn "ethtool not installed, cannot verify network speed"
fi
# Storage Validation
echo -e "\n${YELLOW}[2/6] Storage Configuration${NC}"
if command -v zpool >/dev/null 2>&1; then
if zpool list tank >/dev/null 2>&1; then
HEALTH=$(zpool list -H -o health tank)
if [[ "$HEALTH" == "ONLINE" ]]; then
check_pass "ZFS pool 'tank' is ONLINE"
else
check_fail "ZFS pool 'tank' health: $HEALTH"
fi
else
check_warn "ZFS pool 'tank' not found (may not be on this node)"
fi
else
check_warn "ZFS not installed on this node"
fi
if mount | grep -q "/mnt/nas"; then
check_pass "NAS is mounted"
else
check_warn "NAS not mounted at /mnt/nas"
fi
if crontab -l 2>/dev/null | grep -q "prune_ai_models.sh"; then
check_pass "AI model pruning cron job configured"
else
check_warn "AI model pruning cron job not found"
fi
# Service Validation
echo -e "\n${YELLOW}[3/6] Docker Services${NC}"
if command -v docker >/dev/null 2>&1; then
if docker service ls >/dev/null 2>&1; then
TRAEFIK_COUNT=$(docker service ls | grep -c traefik || true)
if [[ $TRAEFIK_COUNT -ge 1 ]]; then
REPLICAS=$(docker service ls | grep traefik | awk '{print $4}')
check_pass "Traefik service running ($REPLICAS)"
else
check_warn "Traefik service not found in Swarm"
fi
if docker service ls | grep -q node-exporter; then
check_pass "node-exporter service running"
else
check_warn "node-exporter service not found"
fi
else
check_warn "Not a Swarm manager node"
fi
UNHEALTHY=$(docker ps --filter "health=unhealthy" --format "{{.Names}}" | wc -l)
if [[ $UNHEALTHY -eq 0 ]]; then
check_pass "No unhealthy containers"
else
check_fail "$UNHEALTHY unhealthy containers detected"
docker ps --filter "health=unhealthy" --format " - {{.Names}}"
fi
else
check_fail "Docker not installed"
fi
# Security Validation
echo -e "\n${YELLOW}[4/6] Security Configuration${NC}"
if systemctl is-active --quiet fail2ban 2>/dev/null; then
check_pass "fail2ban service is active"
BANNED=$(sudo fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $4}')
if [[ -n "$BANNED" ]]; then
check_pass "fail2ban protecting SSH ($BANNED IPs banned)"
fi
else
check_warn "fail2ban not installed or not running"
fi
if sudo iptables -L >/dev/null 2>&1; then
RULES=$(sudo iptables -L | grep -c "ACCEPT\|DROP" || true)
if [[ $RULES -gt 0 ]]; then
check_pass "Firewall rules configured ($RULES rules)"
else
check_warn "No firewall rules detected"
fi
else
check_warn "Cannot check iptables (permission denied)"
fi
# Monitoring Validation
echo -e "\n${YELLOW}[5/6] Monitoring & Metrics${NC}"
if curl -s http://localhost:9100/metrics >/dev/null 2>&1; then
check_pass "node-exporter metrics accessible"
else
check_warn "node-exporter not accessible on this node"
fi
if curl -s http://192.168.1.196:3000 >/dev/null 2>&1; then
check_pass "Grafana UI accessible"
else
check_warn "Grafana not accessible (may not be on this node)"
fi
# Backup Validation
echo -e "\n${YELLOW}[6/6] Backup Configuration${NC}"
if systemctl list-timers --all | grep -q restic-backup.timer; then
if systemctl is-active --quiet restic-backup.timer; then
check_pass "Restic backup timer is active"
NEXT_RUN=$(systemctl list-timers | grep restic-backup | awk '{print $1, $2}')
echo " Next backup: $NEXT_RUN"
else
check_fail "Restic backup timer is not active"
fi
else
check_warn "Restic backup timer not found"
fi
if command -v restic >/dev/null 2>&1; then
check_pass "Restic is installed"
else
check_warn "Restic not installed"
fi
# Summary
echo -e "\n========================================="
echo "Validation Summary"
echo "========================================="
echo -e "${GREEN}Passed: $PASSED${NC}"
echo -e "${YELLOW}Warnings: $WARNINGS${NC}"
echo -e "${RED}Failed: $FAILED${NC}"
if [[ $FAILED -eq 0 ]]; then
echo -e "\n${GREEN}✓ Deployment validation successful!${NC}"
exit 0
else
echo -e "\n${RED}✗ Some checks failed. Review above for details.${NC}"
exit 1
fi

34
scripts/vlan_firewall.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
# vlan_firewall.sh - Configure firewall rules for VLAN isolation
# This script sets up basic firewall rules for TP-Link router or iptables-based systems
set -euo pipefail
echo "Configuring VLAN firewall rules..."
# VLAN 10: Management (192.168.10.0/24)
# VLAN 20: Services (192.168.20.0/24)
# VLAN 1: Default LAN (192.168.1.0/24)
# Allow management VLAN to access all networks
sudo iptables -A FORWARD -s 192.168.10.0/24 -j ACCEPT
# Allow services VLAN to access default LAN on specific ports only
# Port 53 (DNS), 80 (HTTP), 443 (HTTPS), 9000 (Portainer), 8080 (Traefik)
sudo iptables -A FORWARD -s 192.168.20.0/24 -d 192.168.1.0/24 -p tcp -m multiport --dports 53,80,443,9000,8080 -j ACCEPT
sudo iptables -A FORWARD -s 192.168.20.0/24 -d 192.168.1.0/24 -p udp --dport 53 -j ACCEPT
# Block all other traffic from services VLAN to default LAN
sudo iptables -A FORWARD -s 192.168.20.0/24 -d 192.168.1.0/24 -j DROP
# Allow default LAN to access services VLAN
sudo iptables -A FORWARD -s 192.168.1.0/24 -d 192.168.20.0/24 -j ACCEPT
# Allow established connections
sudo iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
echo "Saving iptables rules..."
sudo iptables-save | sudo tee /etc/iptables/rules.v4
echo "VLAN firewall rules configured."
echo "Note: For TP-Link router, configure ACLs via web UI using similar logic."

28
scripts/zfs_setup.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
# zfs_setup.sh - Create ZFS pool 'tank' on Proxmox host SSDs
# Adjust device names (/dev/sda /dev/sdb) as appropriate for your hardware.
set -euo pipefail
POOL_NAME="tank"
DEVICES=(/dev/sda /dev/sdb)
# Check if pool already exists
if zpool list "$POOL_NAME" >/dev/null 2>&1; then
echo "ZFS pool '$POOL_NAME' already exists. Exiting."
exit 0
fi
# Create the pool with RAID-Z (single parity) for redundancy
zpool create "$POOL_NAME" raidz "${DEVICES[0]}" "${DEVICES[1]}"
# Enable compression for better space efficiency
zfs set compression=on "$POOL_NAME"
# Create a dataset for Docker volumes
zfs create "$POOL_NAME/docker"
# Set appropriate permissions for Docker to use the dataset
chmod 777 "/$POOL_NAME/docker"
echo "ZFS pool '$POOL_NAME' created and configured."