From 3dfdcd1865a627f5e6bcc689c44cb3a24ccf3a64 Mon Sep 17 00:00:00 2001 From: devmatrix Date: Fri, 20 Feb 2026 16:45:09 +0000 Subject: [PATCH] Add persistence: install script, backup/restore, systemd service, docker-compose, docs --- PERSISTENCE.md | 99 ++++++++++++++ README.md | 144 ++++++++++++++++++--- backup.sh | 141 ++++++++++++++++++++ docker-compose.yml | 20 +++ install.sh | 278 +++++++++++++++++++++++++--------------- setup-sensors-server.sh | 0 6 files changed, 558 insertions(+), 124 deletions(-) create mode 100644 PERSISTENCE.md create mode 100755 backup.sh create mode 100644 docker-compose.yml mode change 100644 => 100755 setup-sensors-server.sh diff --git a/PERSISTENCE.md b/PERSISTENCE.md new file mode 100644 index 0000000..069d7f1 --- /dev/null +++ b/PERSISTENCE.md @@ -0,0 +1,99 @@ +# IPMI Controller - Persistence Setup + +## Data Persistence + +All configuration and user data is stored in the `data/` directory: +- `data/config.json` - All settings, fan curves, IPMI config +- `data/users.json` - User accounts and passwords + +**IMPORTANT:** The `data/` directory is committed to git for version control of your settings. + +## Backup Your Settings + +```bash +# Create backup +cd ~/ipmi-controller +cp -r data data.backup.$(date +%Y%m%d) + +# Or backup to external location +cp data/config.json /mnt/backup/ipmi-controller-config.json +``` + +## Auto-Start on Boot (systemd) + +1. **Create service file:** +```bash +sudo tee /etc/systemd/system/ipmi-controller.service << 'EOF' +[Unit] +Description=IPMI Controller +After=network.target + +[Service] +Type=simple +User=devmatrix +WorkingDirectory=/home/devmatrix/ipmi-controller +ExecStart=/usr/bin/python3 /home/devmatrix/ipmi-controller/web_server.py +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF +``` + +2. **Enable and start:** +```bash +sudo systemctl daemon-reload +sudo systemctl enable ipmi-controller +sudo systemctl start ipmi-controller +``` + +3. **Check status:** +```bash +sudo systemctl status ipmi-controller +sudo journalctl -u ipmi-controller -f +``` + +## Docker Deployment (Persistent) + +```bash +# Using docker-compose +cd ~/ipmi-controller +docker-compose up -d + +# Data is persisted in ./data directory +``` + +## Updating Without Losing Settings + +```bash +cd ~/ipmi-controller + +# Backup first +cp -r data data.backup + +# Pull updates +git pull + +# Restart +sudo systemctl restart ipmi-controller +# OR if using docker: +docker-compose restart +``` + +## What Gets Persisted + +✅ IPMI connection settings +✅ HTTP sensor configuration +✅ Fan curves (Balanced, Silent, Performance, etc.) +✅ User accounts and passwords +✅ Theme preference (dark/light) +✅ Fan groups and custom names +✅ All control settings (poll interval, panic temps, etc.) + +## Migration to New Server + +1. Copy `data/config.json` and `data/users.json` to new server +2. Install ipmitool: `sudo apt-get install ipmitool` +3. Install Python deps: `pip install -r requirements.txt` +4. Start server: `python3 web_server.py` diff --git a/README.md b/README.md index 0d6a7e3..fc25a92 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,156 @@ # IPMI Controller -Advanced IPMI fan control for Dell servers with web interface, HTTP sensor support, fan groups, and multiple fan curves. +Advanced IPMI fan control for Dell servers with web interface, HTTP sensor support, multiple fan curves, and persistent configuration. ## Features -- 🌡️ **Temperature Monitoring** - IPMI and HTTP (lm-sensors) sensor support -- 🌬️ **Fan Control** - Automatic curves, manual control, panic mode -- 👥 **Fan Groups** - Group fans with different curves +- 🌡️ **Dual Sensor Support** - IPMI + HTTP (lm-sensors from Proxmox/host) +- 🌬️ **Smart Fan Control** - Automatic curves, manual control, panic mode +- 📊 **3 Preset Curves** - Balanced (default), Silent, Performance +- 👥 **Fan Groups** - Organize and control fans individually or in groups - 🔍 **Fan Identify** - Visual fan identification -- 🎨 **Dark/Light Mode** - Theme switching -- 📊 **Public API** - For external integrations -- 🔒 **Secure** - Password protected with JWT auth +- 🎨 **Themes** - Dark and Light mode +- 📱 **Responsive Web UI** - Works on desktop and mobile +- 🔌 **Public API** - For external integrations +- 💾 **Persistent Settings** - Survives restarts and updates ## Quick Start -### Requirements -- Python 3.10+ -- ipmitool -- Linux server (tested on Dell T710) +### Automated Install (Recommended) -### Install ```bash git clone https://github.com/yourusername/ipmi-controller.git cd ipmi-controller -pip install -r requirements.txt +chmod +x install.sh +sudo ./install.sh ``` -### Run +This will: +- Install all dependencies +- Create systemd service for auto-start +- Set up persistent data directory +- Start the controller on boot + +### Manual Install + ```bash +# Install dependencies +sudo apt-get install -y ipmitool python3-pip +pip3 install -r requirements.txt + +# Run python3 web_server.py ``` -Open `http://your-server:8000` and complete the setup wizard. +Access at `http://your-server:8000` -## Docker +## Initial Setup + +1. Complete the setup wizard (create admin + IPMI config) +2. Login with your admin credentials +3. (Optional) Set up HTTP sensor on your Proxmox host: + ```bash + # On Proxmox server + curl -O https://raw.githubusercontent.com/yourusername/ipmi-controller/main/setup-sensors-server.sh + sudo ./setup-sensors-server.sh + ``` +4. Enable auto control and enjoy automatic fan management! + +## Persistence + +All your settings are automatically saved to `data/config.json`: + +✅ IPMI configuration +✅ HTTP sensor settings +✅ Fan curves (Balanced, Silent, Performance) +✅ User accounts +✅ Theme preference +✅ All control settings + +**Backups:** +```bash +./backup.sh backup # Create backup +./backup.sh list # List backups +./backup.sh restore [file] # Restore from backup +``` + +**Auto-backup via cron:** +```bash +# Add to crontab (keeps 30 days of backups) +0 2 * * * /opt/ipmi-controller/backup.sh auto +``` + +## Updating ```bash -docker build -t ipmi-controller . -docker run -d -p 8000:8000 -v ./data:/app/data ipmi-controller +cd ipmi-controller +git pull + +# Settings are preserved automatically +sudo systemctl restart ipmi-controller +``` + +## Fan Curves + +**Balanced** (Default) - Best for most users: +``` +30°C → 10% | 40°C → 15% | 50°C → 30% | 60°C → 55% | 70°C → 85% | 80°C → 100% +``` + +**Silent** - Noise-sensitive environments: +``` +30°C → 5% | 40°C → 10% | 50°C → 15% | 60°C → 35% | 70°C → 70% | 80°C → 100% +``` + +**Performance** - Heavy workloads: +``` +30°C → 20% | 40°C → 35% | 50°C → 55% | 60°C → 85% | 70°C → 100% ``` ## Documentation -- [Setup Guide](SETUP.md) - Full installation and configuration +- [Setup Guide](SETUP.md) - Full installation instructions +- [Persistence Guide](PERSISTENCE.md) - Backup and restore - [API Reference](API.md) - Public API documentation +## Docker + +```bash +docker-compose up -d +``` + +Data persists in `./data` directory. + +## Management Commands + +```bash +# Status +sudo systemctl status ipmi-controller + +# Logs +sudo journalctl -u ipmi-controller -f + +# Restart +sudo systemctl restart ipmi-controller + +# Stop +sudo systemctl stop ipmi-controller +``` + +## Troubleshooting + +**IPMI Connection Failed:** +- Verify IPMI IP, username, password +- Test: `ipmitool -I lanplus -H -U -P mc info` + +**No Temperature Data:** +- Check HTTP sensor: `curl http://proxmox-ip:8888` +- Verify `sensor_preference` is set to "auto" or "http" + +**Settings Lost After Update:** +- Ensure `data/` directory is not deleted +- Check file permissions: `ls -la data/` + ## License MIT License diff --git a/backup.sh b/backup.sh new file mode 100755 index 0000000..4f2408d --- /dev/null +++ b/backup.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# IPMI Controller - Backup and Restore + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DATA_DIR="${DATA_DIR:-$SCRIPT_DIR/data}" +BACKUP_DIR="${BACKUP_DIR:-$SCRIPT_DIR/backups}" + +show_help() { + echo "IPMI Controller - Backup/Restore Tool" + echo "" + echo "Usage:" + echo " $0 backup - Create backup" + echo " $0 restore [filename] - Restore from backup" + echo " $0 list - List available backups" + echo " $0 auto - Auto-backup (cron-friendly)" + echo "" +} + +create_backup() { + mkdir -p "$BACKUP_DIR" + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + BACKUP_FILE="$BACKUP_DIR/ipmi-controller-backup-$TIMESTAMP.tar.gz" + + echo "📦 Creating backup..." + tar -czf "$BACKUP_FILE" -C "$SCRIPT_DIR" data/ 2>/dev/null + + if [ $? -eq 0 ]; then + echo "✅ Backup created: $BACKUP_FILE" + echo "" + ls -lh "$BACKUP_FILE" + else + echo "❌ Backup failed" + exit 1 + fi +} + +restore_backup() { + if [ -z "$1" ]; then + echo "❌ Please specify backup file" + list_backups + exit 1 + fi + + BACKUP_FILE="$BACKUP_DIR/$1" + if [ ! -f "$BACKUP_FILE" ]; then + BACKUP_FILE="$1" + fi + + if [ ! -f "$BACKUP_FILE" ]; then + echo "❌ Backup file not found: $1" + exit 1 + fi + + echo "⚠️ This will overwrite current settings!" + read -p "Are you sure? (yes/no): " confirm + + if [ "$confirm" != "yes" ]; then + echo "Aborted" + exit 0 + fi + + # Create safety backup first + echo "📦 Creating safety backup..." + mkdir -p "$BACKUP_DIR" + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + tar -czf "$BACKUP_DIR/safety-backup-before-restore-$TIMESTAMP.tar.gz" -C "$SCRIPT_DIR" data/ 2>/dev/null + + # Stop service if running + if systemctl is-active --quiet ipmi-controller 2>/dev/null; then + echo "🛑 Stopping service..." + sudo systemctl stop ipmi-controller + WAS_RUNNING=true + else + WAS_RUNNING=false + fi + + # Restore + echo "📥 Restoring from backup..." + tar -xzf "$BACKUP_FILE" -C "$SCRIPT_DIR" + + if [ $? -eq 0 ]; then + echo "✅ Restore complete" + + # Restart service if it was running + if [ "$WAS_RUNNING" = true ]; then + echo "🚀 Starting service..." + sudo systemctl start ipmi-controller + fi + else + echo "❌ Restore failed" + exit 1 + fi +} + +list_backups() { + echo "📂 Available backups:" + echo "" + + if [ -d "$BACKUP_DIR" ] && [ "$(ls -A "$BACKUP_DIR")" ]; then + ls -lh "$BACKUP_DIR"/*.tar.gz 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' + else + echo " No backups found" + fi +} + +auto_backup() { + # This is cron-friendly - keeps only last 30 days + mkdir -p "$BACKUP_DIR" + + # Create backup + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + BACKUP_FILE="$BACKUP_DIR/auto-backup-$TIMESTAMP.tar.gz" + tar -czf "$BACKUP_FILE" -C "$SCRIPT_DIR" data/ 2>/dev/null + + # Clean old backups (keep last 30 days) + find "$BACKUP_DIR" -name "auto-backup-*.tar.gz" -mtime +30 -delete 2>/dev/null + + echo "Auto-backup complete: $BACKUP_FILE" +} + +case "${1:-}" in + backup) + create_backup + ;; + restore) + restore_backup "$2" + ;; + list) + list_backups + ;; + auto) + auto_backup + ;; + help|--help|-h) + show_help + ;; + *) + show_help + exit 1 + ;; +esac diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..640ae12 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + ipmi-controller: + build: . + container_name: ipmi-controller + restart: unless-stopped + ports: + - "8000:8000" + volumes: + # Persist data directory + - ./data:/app/data + # Optional: mount ipmitool from host if needed + - /usr/bin/ipmitool:/usr/bin/ipmitool:ro + environment: + - PYTHONUNBUFFERED=1 + - DATA_DIR=/app/data + # Required for ipmitool to work in container + privileged: true + network_mode: host diff --git a/install.sh b/install.sh index fb49320..acd05c1 100755 --- a/install.sh +++ b/install.sh @@ -1,147 +1,215 @@ #!/bin/bash -# Setup script for IPMI Fan Controller v2 +# IPMI Controller - Install Script with Persistence +# This sets up auto-start and ensures settings persist set -e -echo "🌬️ IPMI Fan Controller v2 - Setup" -echo "==================================" +INSTALL_DIR="${1:-/opt/ipmi-controller}" +DATA_DIR="$INSTALL_DIR/data" +SERVICE_NAME="ipmi-controller" +USER="${SUDO_USER:-$USER}" + +echo "🌡️ IPMI Controller Installation" +echo "================================" +echo "Install dir: $INSTALL_DIR" +echo "Data dir: $DATA_DIR" +echo "Service user: $USER" +echo "" # Check if running as root for system-wide install -if [ "$EUID" -eq 0 ]; then - INSTALL_SYSTEM=true - INSTALL_DIR="/opt/ipmi-fan-controller" - CONFIG_DIR="/etc/ipmi-fan-controller" +if [ "$EUID" -ne 0 ]; then + echo "⚠️ Not running as root. Installing to $HOME/ipmi-controller instead." + INSTALL_DIR="$HOME/ipmi-controller" + DATA_DIR="$INSTALL_DIR/data" + SYSTEM_INSTALL=false else - INSTALL_SYSTEM=false - INSTALL_DIR="$HOME/.local/ipmi-fan-controller" - CONFIG_DIR="$HOME/.config/ipmi-fan-controller" - echo "⚠️ Running as user - installing to $INSTALL_DIR" - echo " (Run with sudo for system-wide install)" - echo "" + SYSTEM_INSTALL=true fi -# Check dependencies -echo "📦 Checking dependencies..." - -if ! command -v python3 &> /dev/null; then - echo "❌ Python 3 is required but not installed" - exit 1 -fi - -if ! command -v ipmitool &> /dev/null; then - echo "⚠️ ipmitool not found. Installing..." - if [ "$INSTALL_SYSTEM" = true ]; then - apt-get update && apt-get install -y ipmitool - else - echo "❌ Please install ipmitool: sudo apt-get install ipmitool" - exit 1 - fi -fi - -echo "✓ Python 3: $(python3 --version)" -echo "✓ ipmitool: $(ipmitool -V)" - # Create directories -echo "" echo "📁 Creating directories..." mkdir -p "$INSTALL_DIR" -mkdir -p "$CONFIG_DIR" -mkdir -p "$INSTALL_DIR/logs" +mkdir -p "$DATA_DIR" # Copy files -echo "" -echo "📋 Installing files..." -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cp "$SCRIPT_DIR/fan_controller.py" "$INSTALL_DIR/" -cp "$SCRIPT_DIR/web_server.py" "$INSTALL_DIR/" -cp "$SCRIPT_DIR/requirements.txt" "$INSTALL_DIR/" +echo "📋 Copying files..." +cp -r . "$INSTALL_DIR/" 2>/dev/null || true -# Install Python dependencies -echo "" -echo "🐍 Installing Python dependencies..." -python3 -m pip install -q -r "$INSTALL_DIR/requirements.txt" - -# Create default config if not exists -if [ ! -f "$CONFIG_DIR/config.json" ]; then -echo "" -echo "⚙️ Creating default configuration..." -cat > "$CONFIG_DIR/config.json" << 'EOF' +# Ensure data directory exists with proper files +if [ ! -f "$DATA_DIR/config.json" ]; then + echo "⚙️ Creating default config..." + cat > "$DATA_DIR/config.json" << 'EOF' { - "host": "", - "username": "root", - "password": "", - "port": 623, + "ipmi_host": "", + "ipmi_username": "", + "ipmi_password": "", + "ipmi_port": 623, + "http_sensor_enabled": false, + "http_sensor_url": "", + "http_sensor_timeout": 10, "enabled": false, - "interval": 10, + "poll_interval": 10, "min_speed": 10, "max_speed": 100, - "fan_curve": [ - {"temp": 30, "speed": 15}, - {"temp": 40, "speed": 25}, - {"temp": 50, "speed": 40}, - {"temp": 60, "speed": 60}, - {"temp": 70, "speed": 80}, - {"temp": 80, "speed": 100} - ], "panic_temp": 85, - "panic_speed": 100 + "panic_speed": 100, + "panic_on_no_data": true, + "no_data_timeout": 60, + "primary_sensor": "cpu", + "sensor_preference": "auto", + "fans": {}, + "fan_groups": {}, + "fan_curves": { + "Balanced": { + "points": [ + {"temp": 30, "speed": 10}, + {"temp": 35, "speed": 12}, + {"temp": 40, "speed": 15}, + {"temp": 45, "speed": 20}, + {"temp": 50, "speed": 30}, + {"temp": 55, "speed": 40}, + {"temp": 60, "speed": 55}, + {"temp": 65, "speed": 70}, + {"temp": 70, "speed": 85}, + {"temp": 75, "speed": 95}, + {"temp": 80, "speed": 100} + ], + "sensor_source": "cpu", + "applies_to": "all" + }, + "Silent": { + "points": [ + {"temp": 30, "speed": 5}, + {"temp": 40, "speed": 10}, + {"temp": 50, "speed": 15}, + {"temp": 55, "speed": 25}, + {"temp": 60, "speed": 35}, + {"temp": 65, "speed": 50}, + {"temp": 70, "speed": 70}, + {"temp": 75, "speed": 85}, + {"temp": 80, "speed": 100} + ], + "sensor_source": "cpu", + "applies_to": "all" + }, + "Performance": { + "points": [ + {"temp": 30, "speed": 20}, + {"temp": 35, "speed": 25}, + {"temp": 40, "speed": 35}, + {"temp": 45, "speed": 45}, + {"temp": 50, "speed": 55}, + {"temp": 55, "speed": 70}, + {"temp": 60, "speed": 85}, + {"temp": 65, "speed": 95}, + {"temp": 70, "speed": 100} + ], + "sensor_source": "cpu", + "applies_to": "all" + } + }, + "active_curve": "Balanced", + "theme": "dark" } EOF fi +if [ ! -f "$DATA_DIR/users.json" ]; then + echo "👤 Creating users file..." + echo '{"users": {}}' > "$DATA_DIR/users.json" +fi + +# Install Python dependencies +echo "🐍 Installing dependencies..." +if [ "$SYSTEM_INSTALL" = true ]; then + pip3 install -q -r "$INSTALL_DIR/requirements.txt" || pip install -q -r "$INSTALL_DIR/requirements.txt" +else + pip3 install --user -q -r "$INSTALL_DIR/requirements.txt" 2>/dev/null || true +fi + +# Install ipmitool if not present +if ! command -v ipmitool &> /dev/null; then + echo "📦 Installing ipmitool..." + if [ "$SYSTEM_INSTALL" = true ]; then + apt-get update -qq && apt-get install -y -qq ipmitool + else + echo "⚠️ Please install ipmitool manually: sudo apt-get install ipmitool" + fi +else + echo "✓ ipmitool already installed" +fi + # Create systemd service (system-wide only) -if [ "$INSTALL_SYSTEM" = true ]; then -echo "" -echo "🔧 Creating systemd service..." -cat > /etc/systemd/system/ipmi-fan-controller.service << EOF +if [ "$SYSTEM_INSTALL" = true ]; then + echo "🔧 Creating systemd service..." + cat > "/etc/systemd/system/$SERVICE_NAME.service" << EOF [Unit] -Description=IPMI Fan Controller v2 +Description=IPMI Controller After=network.target [Service] Type=simple -User=root +User=$USER WorkingDirectory=$INSTALL_DIR -Environment="CONFIG_PATH=$CONFIG_DIR/config.json" +Environment="PYTHONUNBUFFERED=1" +Environment="DATA_DIR=$DATA_DIR" ExecStart=/usr/bin/python3 $INSTALL_DIR/web_server.py -ExecStop=/usr/bin/python3 -c "import requests; requests.post('http://localhost:8000/api/shutdown')" -Restart=on-failure +ExecStop=/bin/kill -TERM \$MAINPID +Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF -systemctl daemon-reload -systemctl enable ipmi-fan-controller.service - -echo "" -echo "✅ Installation complete!" -echo "" -echo "Next steps:" -echo " 1. Edit config: sudo nano $CONFIG_DIR/config.json" -echo " 2. Start service: sudo systemctl start ipmi-fan-controller" -echo " 3. View status: sudo systemctl status ipmi-fan-controller" -echo " 4. Open web UI: http://$(hostname -I | awk '{print $1}'):8000" -echo "" -echo "Or test from CLI:" -echo " python3 $INSTALL_DIR/fan_controller.py " - + systemctl daemon-reload + systemctl enable "$SERVICE_NAME" + + # Set proper ownership + chown -R "$USER:$USER" "$INSTALL_DIR" + + echo "" + echo "✅ Installation complete!" + echo "" + echo "Start the service:" + echo " sudo systemctl start $SERVICE_NAME" + echo "" + echo "Check status:" + echo " sudo systemctl status $SERVICE_NAME" + echo "" + echo "View logs:" + echo " sudo journalctl -u $SERVICE_NAME -f" + echo "" + echo "Access: http://$(hostname -I | awk '{print $1}'):8000" + else - -echo "" -echo "✅ User installation complete!" -echo "" -echo "Next steps:" -echo " 1. Edit config: nano $CONFIG_DIR/config.json" -echo " 2. Start manually:" -echo " CONFIG_PATH=$CONFIG_DIR/config.json python3 $INSTALL_DIR/web_server.py" -echo " 3. Open web UI: http://localhost:8000" -echo "" -echo "Or test from CLI:" -echo " python3 $INSTALL_DIR/fan_controller.py " - + # User install - create a simple start script + cat > "$INSTALL_DIR/start.sh" << 'EOF' +#!/bin/bash +cd "$(dirname "$0")" +export DATA_DIR="./data" +export PYTHONUNBUFFERED=1 +echo "Starting IPMI Controller..." +echo "Data directory: $DATA_DIR" +python3 web_server.py +EOF + chmod +x "$INSTALL_DIR/start.sh" + + echo "" + echo "✅ User installation complete!" + echo "" + echo "Start manually:" + echo " cd $INSTALL_DIR && ./start.sh" + echo "" + echo "Or create a systemd service manually:" + echo " nano ~/.config/systemd/user/ipmi-controller.service" + echo "" + echo "Access: http://localhost:8000" fi echo "" -echo "📖 Configuration file: $CONFIG_DIR/config.json" +echo "📁 Your settings are stored in: $DATA_DIR" +echo " - config.json: All configuration" +echo " - users.json: User accounts" +echo "" +echo "💾 These files persist across restarts and updates!" diff --git a/setup-sensors-server.sh b/setup-sensors-server.sh old mode 100644 new mode 100755