Initial commit: homelab configuration and documentation
This commit is contained in:
53
scripts/backup_daily.sh
Executable file
53
scripts/backup_daily.sh
Executable 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) ==="
|
||||
96
scripts/create_docker_secrets.sh
Executable file
96
scripts/create_docker_secrets.sh
Executable 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
181
scripts/deploy_all.sh
Executable 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
27
scripts/install_fail2ban.sh
Executable 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."
|
||||
28
scripts/install_restic_backup.sh
Executable file
28
scripts/install_restic_backup.sh
Executable 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."
|
||||
80
scripts/network_performance_test.sh
Executable file
80
scripts/network_performance_test.sh
Executable 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
18
scripts/prune_ai_models.sh
Executable 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
132
scripts/quick_status.sh
Executable 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
87
scripts/setup_log_rotation.sh
Executable 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
22
scripts/setup_monitoring.sh
Executable 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
195
scripts/validate_deployment.sh
Executable 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
34
scripts/vlan_firewall.sh
Executable 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
28
scripts/zfs_setup.sh
Executable 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."
|
||||
Reference in New Issue
Block a user