Skip to main content

Home Server Set-up


Home Server

Home Server Status Dashboard

HomeServer Status Dashboard
Home Server Status Dashboard

How to Turn an Old Laptop into a Power-Efficient Proxmox Home Server

Laptops make incredible home lab servers. They consume very little electricity, feature a built-in keyboard and monitor for troubleshooting, and contain an integrated battery backup (UPS) to handle sudden power cuts.

This guide walks you through preparing the hardware, installing Proxmox Virtual Environment (VE), and applying critical laptop-specific tweaks so your node runs reliably 24/7 with the lid closed.


Phase 1: BIOS and Hardware Preparation

Before running the installer, the laptop's motherboard firmware must be configured to support virtualization and prevent boot errors.

  1. Enter BIOS: Power on the laptop and repeatedly tap the appropriate manufacturer key (e.g., F10 on HP devices) to open the firmware configuration menu.
  2. Enable Virtualization: Navigate to the Advanced or System Configuration menu and enable Intel Virtualization Technology (VT-x) and VT-d (or AMD-V / AMD-Vi for AMD processors).
  3. Disable Secure Boot: Turn off Secure Boot to allow the custom Proxmox kernel to bootstrap successfully.
  4. Set Storage Mode: Ensure the internal storage controller is configured to AHCI mode rather than RAID or Intel RST.
  5. Connect a Wired Network: Plug a physical Ethernet cable into the laptop's network port or use a compatible USB-to-Ethernet adapter. Proxmox requires a stable, wired connection and does not natively support Wi-Fi configurations during initialization.

Phase 2: Create the Installation Media

  1. Download the Installer: Visit the official Proxmox website and download the latest Proxmox VE ISO installer image.
  2. Flash the Drive: Insert a USB flash drive (minimum 8GB) and use a tool like Rufus or BalenaEtcher to burn the downloaded ISO file onto the flash drive.

Phase 3: Run the Proxmox Installation Wizard

  1. Boot from USB: Insert the flashed USB drive into the laptop, power it on, and repeatedly tap the temporary boot options key (e.g., F9 on HP devices). Select your USB flash drive from the hardware menu.
  2. Launch Installer: Select Install Proxmox VE (Graphical) from the initial menu choice.
  3. Select Target Harddisk: Choose your primary internal SSD or HDD. For single-drive laptop builds, format the disk as ext4 or XFS. Avoid ZFS on basic setups to prevent high disk write wear on consumer flash layouts.
  4. Network Configuration:
    • Select your active wired Ethernet network interface card.
    • Assign a designated Static IP address outside of your home router's automatic DHCP lease pool.
    • Input your local gateway router IP and your preferred network DNS server.
  5. Complete and Reboot: Follow the remaining prompts to set your administrative root password and alert email. Click install, wait for completion, remove the USB drive when prompted, and let the system reboot.

Phase 4: Critical Post-Install Laptop Tweaks & Repository Setup

By default, standard Linux installations are programmed to put laptops to sleep when the lid is closed. Because Proxmox is a headless server, you must explicitly disable this power-saving feature so it operates continuously.

Log into the Proxmox command line (via the physical laptop screen interface or through the Web UI terminal session located at https://<YOUR_ASSIGNED_STATIC_IP>:8006).

Step 1: Disable Laptop Lid Sleep

Open the system configuration file responsible for managing local login behavior and power button triggers:

nano /etc/systemd/logind.conf

Scroll through the file, locate the following lines, and uncomment them by removing the leading hash symbol (#). If they do not exist, append them cleanly to the bottom of the text block:

HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore

Step 2: Understand Sleep Blockers (Optional)

Within the same configuration file, you may notice a system entry named LidSwitchIgnoreInhibited.

  • Setting this to =yes (the system default) instructs the laptop to ignore any software process attempting to block power saving, forcing the laptop to sleep anyway on lid close.
  • Setting this to =no forces the operating system to respect software processes—such as active virtual machine backups or ongoing disk writes—preventing sleep until those operations complete.

*Note: Because we configured HandleLidSwitch=ignore in Step 1, the operating system is instructed to never enter sleep mode when the physical lid is shut, rendering the secondary inhibitor toggle redundant for this deployment.*

Step 3: Apply and Save Changes

Save the text configuration file and exit the terminal editor (Ctrl + O, tap Enter, then press Ctrl + X). Restart the login management service daemon to instantly initialize your parameters:

systemctl restart systemd-logind

Step 4: Configure the Free No-Subscription Repository

Proxmox comes pre-configured to track paid enterprise update streams. To switch your package index targets to the free community repository so you can safely run updates without authentication errors, run these commands in sequence:

  1. Disable the Default Enterprise List:
    sed -i 's/^deb/#deb/' /etc/apt/sources.list.d/pve-enterprise.list
  2. Add the Clean No-Subscription Mirror Path:
    echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" > /etc/apt/sources.list.d/pve-no-subscription.list
  3. Refresh System Indexes & Apply Security Patches:
    apt update && apt dist-upgrade -y

Step 5: Prevent Screen Burn-In

To turn off the physical, built-in laptop display panel automatically after 1 minute of absolute command-line interface inactivity, inject the console-blanking escape sequence straight into the pre-login shell wrapper:

setterm -blank 1 >> /etc/issue

Your power-efficient laptop server is now optimized, secured against lid-close sleep errors, and completely ready to deploy your virtual machines and container stacks!

*************************************************************************************

TigerVNC Server Setup: Auto-Restart on Login

Traditional VNC platforms (like TightVNC) often fail to process modern graphical rendering extensions (like RENDER), causing users to get stuck on an unrendered grey screen. TigerVNC acts as a modern, stable drop-in replacement.


1. Provision the Software Stack

Run the following command to install the standalone server package along with the necessary D-Bus utilities:

sudo apt update
sudo apt install xfce4 xfce4-goodies tigervnc-standalone-server dbus-x11 -y

2. Configure the VNC Environment Initialization Script

Create your custom session startup profile directories and open the deployment file:

mkdir -p ~/.config/tigervnc
nano ~/.config/tigervnc/xstartup

Paste this configuration layout to clean up host environment pollution, add local sandboxing permissions to avoid snap confinement bugs, and start XFCE within a dedicated D-Bus wrapper:

#!/bin/sh
# 1. Clear out host environment pollution leaks
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS

# 2. Initialize X resources and structural parameters
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &

# 3. Grant local sandbox permissions (Fixes Snap containment/CCTV viewer bugs)
xhost +local:

# 4. Handle specific execution loops inside a dedicated DBus architecture wrapper
exec dbus-launch --exit-with-session startxfce4

Ensure universal execution permissions are applied to the newly created configuration script:

chmod +x ~/.config/tigervnc/xstartup

3. Initialize Password Authentication Database

Create your system security authentication layer manually:

vncpasswd

Follow the terminal prompts to assign and verify your remote graphical session security password. These authorization keys will be saved directly into your secure directory profile.

4. Construct the Systemd Daemon Layout

To automate performance handling and ensure standard startup execution, map out a system unit config file:

sudo nano /etc/systemd/system/vncserver@.service

Paste the configuration below, substituting server1454 with your active Linux system username. Note the critical incorporation of the -localhost no parameter flag, which forces TigerVNC to bind beyond the loopback domain onto external endpoints like Tailscale network links:

[Unit]
Description=Start TigerVNC server at startup
After=syslog.target network.target

[Service]
Type=forking
User=server1454
WorkingDirectory=/home/server1454
Environment=HOME=/home/server1454

ExecStartPre=-/usr/bin/vncserver -kill :%i > /dev/null 2>&1
ExecStart=/usr/bin/vncserver -depth 16 -geometry 1920x1080 -localhost no :%i
ExecStop=/usr/bin/vncserver -kill :%i

[Install]
WantedBy=multi-user.target

5. Launch and Validate Daemon Services

Register the unit updates, link the profile to runtime boot executions, and initialize the graphical display array:

# Refresh system daemon maps
sudo systemctl daemon-reload

# Bind instance 1 to the autostart framework
sudo systemctl enable vncserver@1.service
sudo systemctl start vncserver@1.service

# Verify networking states across the engine topology
sudo netstat -ptnl | grep vnc

Your system verification matrix must report target network alignment across all active network interfaces, listening globally:

tcp        0      0 0.0.0.0:5901            0.0.0.0:* LISTEN      17714/Xtigervnc

Production Verification & Remote Connection

Launch your preferred client-side visual application toolkit (e.g., Remmina, RealVNC, TigerVNC Viewer) and specify your node's explicit private mapping destination:

[YOUR_SERVER_TAILSCALE_IP]:5901

Once you authenticate with your VNC security password, you will be securely routed through your private network interface and dropped straight into an optimized, lightning-fast XFCE4 desktop session!

*******************************************************************

The Ultimate Guide to Installing, Automating, and Ricing an XFCE Desktop over TigerVNC

XFCE is an incredibly lightweight remote desktop environment, making it the perfect choice for a headless server running a VNC loop. However, the stock configuration can look dated out of the box, and default rendering profiles can cause latency over remote connections.

This guide provides a comprehensive blueprint to deploy a secure, ultra-fast, and modern XFCE desktop environment via TigerVNC on Ubuntu 26.04 LTS, optimized for maximum performance.


Phase 1: Installation & Directory Architecture

First, update your package repository lists and pull down the core XFCE desktop, TigerVNC, and the D-Bus framework wrapper needed for graphical applications to communicate over virtual frames.

sudo apt update
sudo apt install xfce4 xfce4-goodies tigervnc-standalone-server dbus-x11 -y

Next, establish your user-level configuration path mapping:

mkdir -p ~/.config/tigervnc
nano ~/.config/tigervnc/xstartup

Paste this clean initialization sequence. This script flushes stale environmental leaks and uses xhost +local: to prevent Snap application containment blocks:

#!/bin/sh
# 1. Clear out host environment session leaks
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS

# 2. Load standard desktop resources
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &

# 3. Grant local sandbox permissions (Fixes Snap/CCTV viewer bugs)
xhost +local:

# 4. Launch XFCE inside a private D-Bus container loop
exec dbus-launch --exit-with-session startxfce4

Save and exit (Ctrl + O, Enter, Ctrl + X). Set correct file system ownership permissions and mark the initialization script as executable:

sudo chown -R server1454:server1454 /home/server1454/.config
chmod +x ~/.config/tigervnc/xstartup

Phase 2: Building the Non-Hanging Systemd Service

To ensure the desktop environment boots automatically on system startup and recovers from power cuts, wire it into a background service.

⚠️ The Pitfall Avoided: We completely removed the standard PIDFile directive from this configuration. The TigerVNC script wrapper often causes systemd to hang indefinitely while waiting for a process ID file that has moved. Removing it forces systemd to track the daemon natively through internal cgroups.

Create the global service configuration file:

sudo nano /etc/systemd/system/vncserver@.service

Paste this optimized configuration block. Note that we have explicitly set the color depth parameter to -depth 16 to reduce data overhead by roughly 33%, and included -localhost no to allow connections across your local network:

[Unit]
Description=Start VNC server at startup
After=syslog.target network.target

[Service]
Type=forking
User=server1454
WorkingDirectory=/home/server1454
Environment=HOME=/home/server1454

ExecStartPre=-/usr/bin/vncserver -kill :%i > /dev/null 2>&1
ExecStart=/usr/bin/vncserver -depth 16 -geometry 1920x1080 -localhost no :%i
ExecStop=/usr/bin/vncserver -kill :%i

[Install]
WantedBy=multi-user.target

Save and close the file. Reload the system management daemon maps, enable the automation layer to run on boot, and start display instance :1:

sudo systemctl daemon-reload
sudo systemctl enable vncserver@1.service
sudo systemctl start vncserver@1.service

Verify that the service has launched successfully and is listening cleanly on port 5901:

sudo systemctl status vncserver@1.service --no-pager
sudo netstat -ptnl | grep vnc

Phase 3: Performance Optimization (The Speed Layer)

VNC transmits updates by packaging modified pixel blocks. To make the interface highly responsive over remote connections, minimize the layout computations on the host server.

  • Kill the Window Compositor:
    Log into your desktop using a VNC Viewer. Open the main application menu and navigate to Settings → Window Manager Tweaks. Head to the Compositor tab and uncheck Enable display compositing. This cuts out drop shadows and window fade animations, eliminating hundreds of unnecessary network screen redraws.
  • Switch to a Solid Background:
    High-contrast wallpapers require heavy compression cycles over remote desktop protocols. Right-click your desktop wallpaper area, choose Desktop Settings, and set the Style dropdown selection to None. Choose a clean, dark solid color to minimize background transmission overhead.

Phase 4: Modern Visual Ricing (The Aesthetics Layer)

Transform XFCE's default retro interface into a flat, modern workspace by installing the Arc theme suite, Papirus flat icons, and an advanced application search dashboard.

sudo apt update
sudo apt install arc-theme papirus-icon-theme xfce4-whiskermenu-plugin -y

Open your VNC Viewer application and map the newly installed interface assets through your system configuration modules:

Visual Component Menu Navigation Path Configuration to Choose
Main Window Theme Settings → Appearance → Style Arc-Dark
System Icon Set Settings → Appearance → Icons Papirus-Dark
Window Frame Borders Settings → Window Manager → Style Arc-Dark
Font Smoothing Settings → Appearance → Fonts Enable Anti-Aliasing (Hinting: Slight, Sub-pixel: RGB)

🛠️ Upgrading the Panel Layout

  1. Deploy the Whisker Launcher: Right-click the stock application launcher menu button on your panel bar and click Remove. Next, right-click any empty spot on the taskbar panel, select Panel → Add New Items, select Whisker Menu, and click Add. Right-click the new button, select Move, and slide it to the far-left corner of the panel frame.
  2. Reposition the Taskbar: Right-click your panel layout track and open Panel Preferences. Uncheck Lock panel, grab the small dotted selection handles at the edge of the bar, and drag the entire assembly down to the bottom edge of the screen layout. Re-check Lock panel to secure it.
  3. Optimize Taskbar Scaling: While still inside the panel preferences layout, increase the Row Size (pixels) option to 38. This expands your application shortcuts and status indicators, providing a clean, modern user experience.

How to Enable and Configure SSH on Kali Linux

Secure Shell (SSH) is a critical protocol for administrators and security practitioners, enabling encrypted remote command-line access to a server or workstation. While Kali Linux comes with the OpenSSH server pre-installed in many distribution flavors, the service is disabled by default for security purposes to minimize the host's attack surface.

This guide walks you through installing the underlying software hooks, configuring the background system engine to initialize automatically during boot, and verifying remote cryptographic links safely.


Step 1: Provision the OpenSSH Server

Log into your local Kali Linux instance terminal. Before pulling down packages, it is recommended to update your repository definitions to guarantee compatibility with your rolling-release lifecycle index:

# Refresh package mirrors and install the server binaries
sudo apt update
sudo apt install openssh-server -y

Step 2: Bootstrap and Automate the Service Daemon

Once the package installation wraps up, initialize the background execution loop using systemd. Using the enable directive ensures that the SSH server recovers automatically whenever the system restarts or recovers from power losses:

# Start the runtime service engine instantly
sudo systemctl start ssh

# Bind the process to the system autostart framework
sudo systemctl enable ssh

Verify Operational Daemon States

Confirm that the secure shell listener is active, running, and successfully binding to system network sockets without errors:

sudo systemctl status ssh --no-pager

Look for a solid green active (running) tracking status in your console output block.


Step 3: Establish a Remote Connection

To access your Kali workspace from a separate computer over your network, track down your private local IP address mapping via the terminal:

ip addr show

Once you identify your local network interface address (e.g., 192.168.1.10), launch an SSH terminal or client utility from your remote client machine and target your destination route:

ssh kali@192.168.1.10
🔒 Security Warning: If you are establishing an SSH network link for the first time on a fresh setup, it is highly recommended to change the default user account security password by executing the passwd command inside the terminal window to lock out unauthorized network probes.

Accept the unique ECDSA/RSA cryptographic host key fingerprint verification prompt when prompted, enter your secure user account password credentials, and you will be securely dropped straight into an active remote Kali Linux terminal session!

**************************************************************

Enabling VNC Server at Boot and Fixing Common Desktop Errors

Setting up an XFCE or GNOME desktop to launch automatically over a virtual framebuffer at boot is an efficient way to manage a headless server. However, automated systemd startup links can sometimes run into environment locks, audio device blocks, or sandbox containment drops that prevent modern browsers (like Brave or Firefox) from opening inside a VNC session.

This guide walks you through building an optimal automated systemd deployment framework for your VNC server, correcting critical service architecture patterns, and mending common network runtime conflicts.


Step 1: Optimize the VNC Environment Script

First, access your system environment initialization script. This profile configures your virtual display constraints and handles internal message brokers before launching the window manager.

nano ~/.vnc/xstartup

To avoid sandbox access errors (such as the not a snap cgroup warning), your initialization script must explicitly grant local sandbox permissions using xhost and bind to the correct D-Bus system bus path. Update your configuration file to match this structure:

#!/bin/sh
# 1. Clear out host environment session leaks
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS

# 2. Load system defaults and resource parameters
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &

# 3. Grant local sandbox permissions (Fixes Snap browser isolation crashes)
xhost +local:

# 4. Map active runtime data tracks and export D-Bus environment vectors
export XKL_XMODMAP_DISABLE=1
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"

# 5. Launch the desktop environment wrapper cleanly
exec dbus-launch --exit-with-session startxfce4

Save and close out the window, then make sure the initialization script has full executable privileges:

chmod +x ~/.vnc/xstartup

Step 2: Create a Reliable Systemd Daemon Service

To ensure your remote environment starts automatically when the system boots up, manage it through a system service daemon config file:

sudo nano /etc/systemd/system/vncserver@.service
⚠️ The "Hanging Service" Pitfall Addressed: Standard VNC systemd service templates frequently include a PIDFile= directive. This often causes systemd to hang indefinitely or fail on boot because the VNC server script wrapper shifts its process ID file location during initialization. Completely removing the PID tracking property forces systemd to track the core daemon parameters natively via cgroups.

Paste this clean configuration template, replacing ser_g1454 with your active system username:

[Unit]
Description=Start VNC server at startup
After=syslog.target network.target

[Service]
Type=forking
User=ser_g1454
WorkingDirectory=/home/ser_g1454
Environment=HOME=/home/ser_g1454

ExecStartPre=-/usr/bin/vncserver -kill :%i > /dev/null 2>&1
ExecStart=/usr/bin/vncserver -depth 16 -geometry 1920x1080 -localhost no :%i
ExecStop=/usr/bin/vncserver -kill :%i

[Install]
WantedBy=multi-user.target

Step 3: Launch and Enable the Service

Register your new daemon configuration file with the system, enable it to run at boot, and initialize your display socket loop target (:1):

# Force systemd to map the new unit configuration
sudo systemctl daemon-reload

# Enable instance 1 to load during system boot sequences
sudo systemctl enable vncserver@1.service

# Clean up any stale sessions and spin up the service daemon
vncserver -kill :1 > /dev/null 2>&1
sudo systemctl start vncserver@1.service

Verify that your setup is working correctly by checking the system service status logs:

sudo systemctl status vncserver@1.service --no-pager

A successful configuration output will show a clean, green initialization signature:

● vncserver@1.service - Start VNC server at startup Loaded: loaded (/etc/systemd/system/vncserver@.service; enabled; preset: enabled) Active: active (running) since Thu 2026-07-09; 10s ago

To confirm that your server engine is actively listening across network interfaces on the correct ports, run a quick network socket query:

sudo netstat -ptnl | grep vnc

Your network verification metrics should show active listener ports matching this profile:

tcp 0 0 0.0.0.0:5901 0.0.0.0:* LISTEN 1669/Xtigervnc


Advanced Lab Troubleshooting Matrix

Error Fix: systemd-networkd-wait-online Timing Out at Boot

If your server is configured to boot completely headless, the startup sequence might hang for up to two minutes while waiting for unlinked interfaces (such as an empty secondary Ethernet port or an unconfigured Wi-Fi card) to establish a connection. You can fix this by restricting the wait script to your primary active interface:

sudo systemctl edit systemd-networkd-wait-online.service

An interactive system override configuration file will open. Insert the tracking constraint line directly between the designated block sections:

[Service]
ExecStart=
ExecStart=/usr/lib/systemd/systemd-networkd-wait-online -i <your_active_interface>

*(Be sure to replace <your_active_interface> with your server's literal network hardware device, such as eno1 or wlp1s0 discovered using the ip a command).*

Error Fix: Audio Core Engines Not Responding inside VNC

If your audio control configurations or software bridges become desynchronized or drop sound channels across virtual connections, execute a clean flush and reinstallation of the sound server packages:

# Purge the core audio engine configurations from your system
sudo apt-get purge alsa-base pulseaudio pavucontrol -y

# Refresh package indices and reinstall core audio packages
sudo apt update
sudo apt install alsa-base pulseaudio pavucontrol -y

# Reboot your server to initialize clean audio channels
sudo reboot

Once your server boots back up, your startup scripts will map correctly, application isolation paths will clear out automatically, and your browser suites will launch cleanly inside your optimized remote desktop session!

**************************************************************

Secure, Self-Hosted Remote Desktop: Deploying RustDesk over Tailscale with Docker

Self-hosting your remote desktop infrastructure is a fantastic way to maintain complete control over your data and privacy. RustDesk is a highly capable, open-source alternative to commercial remote desktop software. By running it inside a Docker container and routing it exclusively through a Tailscale VPN mesh, we can create a completely private, encrypted remote access solution.

In this guide, we will deploy the RustDesk ID Server (hbbs) and Relay Server (hbbr), enforce strict encryption keys, and securely retrieve those keys from a distroless container environment.


Step 1: The Docker Compose Configuration

To successfully route RustDesk over Tailscale, your ID server needs to know where to direct clients. We achieve this using the -r flag. Furthermore, to prevent unauthorized connections even within your VPN, we enforce encryption using the -k _ flag on both containers.

Create a new directory for your server, navigate into it, and create your docker-compose.yml file:

services:
  hbbs:
    container_name: hbbs
    image: rustdesk/rustdesk-server:latest
    environment:
      - ALWAYS_USE_RELAY=Y
    # -r points clients to the relay over your VPN. -k _ enforces encryption.
    command: hbbs -r <YOUR_VPN_IP>:21117 -k _
    volumes:
      - ./data:/root
    network_mode: "host"
    depends_on:
      - hbbr
    restart: unless-stopped

  hbbr:
    container_name: hbbr
    image: rustdesk/rustdesk-server:latest
    # -k _ ensures the relay also enforces encryption using the shared volume key.
    command: hbbr -k _
    volumes:
      - ./data:/root
    network_mode: "host"
    restart: unless-stopped

Important: Replace <YOUR_VPN_IP> with your server's actual Tailscale/VPN IP address.


Step 2: Deploying the Server

With your compose file configured, spin up the server in the background. Because we are using network_mode: "host", Docker will bind the necessary ports directly to your host network.

docker compose up -d

Step 3: Extracting the Encryption Key

Because we used the -k _ flag, the server automatically generated a highly secure ED25519 key pair on its first boot. You need the public key to configure your client applications.

The Distroless Catch: The official RustDesk Docker image is built on a "distroless" environment. This means standard Linux utilities like cat, ls, and bash are completely removed from the image for security reasons. You cannot simply execute a command inside the container to read the key.

Instead, we use the docker cp trick to extract the file to the host machine's temporary folder, and then read it from there:

  1. Copy the key out of the container:
    sudo docker cp hbbs:/root/id_ed25519.pub /tmp/id_ed25519.pub
  2. Read the copied key:
    cat /tmp/id_ed25519.pub

Copy the output text to your clipboard. Treat your keys carefully, but remember that this is the public key (.pub), which is safe to distribute to your personal client devices.


Step 4: Configuring the RustDesk Client

Your server is running and you have your encryption key. Now, it's time to connect a device.

  1. Open the RustDesk app on your computer or phone.
  2. Navigate to Settings > Network.
  3. In the ID Server field, enter your server's Tailscale IP address (e.g., 100.x.x.x).
  4. In the Relay Server field, enter that exact same IP address again.
  5. In the Key field, paste the public key string you extracted in Step 3.
  6. Click Apply.

Return to the main RustDesk dashboard. At the bottom of the screen, you should now see a green "Ready" status. You have successfully deployed a secure, end-to-end encrypted remote administration tool wrapped safely inside your personal VPN mesh!

*******************************************************************

Installing Glances Service and Configuring Hardware Sensors (lm-sensors)

Glances is an advanced, cross-platform system monitoring tool that allows you to track resource utilization via a CLI interface or a built-in web server. By combining Glances with lm-sensors, you can monitor real-time hardware telemetry, including CPU core temperatures, fan speeds, and voltage lines, all from a single centralized interface.


Step 1: Install System Dependencies

Log into your Linux server and update your local package lists, then install the core packages for Glances and the hardware abstractions framework:

sudo apt update
sudo apt install lm-sensors glances -y

Step 2: Detect and Map Hardware Sensors

Before running the software service, initialize your hardware monitoring interface modules. The automated tracking engine will scan your system's motherboard for onboard ISA, PCI, and embedded SMBus monitoring chips:

# Run automated sensor detection probe and bypass interactive prompts
sudo sensors-detect --auto

# Output live hardware metrics to verify functional telemetry
sensors

Step 3: Construct the Glances Systemd Service

To run Glances continually in the background as an automated web server, configure a system service structure:

sudo nano /etc/systemd/system/glances.service

Paste the following service layout template inside the file configuration. This block sets up your resource daemon to listen across all network endpoints (0.0.0.0) on port 61208:

[Unit]
Description=Glances Web Server Daemon
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/glances -w -B 0.0.0.0 -p 61208
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Save and close out the file changes (Ctrl + O, then Enter, followed by Ctrl + X).

Step 4: Initialize and Validate the Service

Force systemd to refresh its internal registry maps, enable the background service to execute at boot, and trigger its runtime instantly using the --now flag:

sudo systemctl daemon-reload
sudo systemctl enable --now glances.service
sudo systemctl status glances.service

💡 Web UI Asset Optimization (Optional Bugfix)

On certain older Debian-derived packages or older Ubuntu iterations, loading the browser connection endpoint can occasionally show an empty container frame. This occurs if the system package structure skips compiling local user assets. To ensure the Web UI components serialize smoothly without access drops, manually confirm the directory mapping path layout exists by running:

sudo mkdir -p /usr/lib/python3/dist-packages/glances/outputs/static/public

Open your browser and navigate directly to your server IP on port 61208 to view your dashboard:

http://<YOUR_SERVER_IP>:61208

Your hardware parameters, RAM configurations, containers, and core thermal limits are now being tracked dynamically on a clean, light page structure!

**********************************************************************

Home Server Setup Dashboard

Dashboard 01
Dashboard 01
Dashboard 02
Dashboard 02

How to Install Docker on Ubuntu 26.04 LTS (Resolute) — A Step-by-Step Guide

When installing Docker on a fresh modern Linux distribution like Ubuntu 26.04 LTS (resolute), standard copy-paste installation scripts often break. The two most common pitfalls are accidentally pointing the package manager to a Debian repository instead of Ubuntu, or failing to refresh the local package index after updating the sources files.

Here is the complete, error-free walkthrough compiled from our troubleshooting logs to help you successfully deploy Docker on your server from scratch.


Step 1: Prepare System Folders & Download GPG Security Key

Ubuntu requires a signed cryptographic key from Docker to securely verify that the software packages you download haven't been tampered with.

Run these commands to create the secure directory and download the official Ubuntu Docker key:

# Create the secure keyring directory if it doesn't exist
sudo install -m 0755 -d /etc/apt/keyrings

# Download Docker's official GPG encryption key
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

# Grant universal read permissions to the key file
sudo chmod a+r /etc/apt/keyrings/docker.asc

Step 2: Add the Official Ubuntu Docker Repository

Next, tell Ubuntu's package manager (apt) exactly where Docker's official download servers are located. We will write this out using a modern .sources file.

⚠️ The Pitfall to Avoid: Ensure the URIs line points explicitly to /linux/ubuntu and not to /linux/debian.

Execute this block to automatically detect your system architecture and inject the correct codename (resolute):

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

Step 3: Refresh the Local Package Index

Do not skip this step! Your system will not look for the new files until you force an update. Refresh your package definitions map so Ubuntu can see the newly added repository strings:

sudo apt update

While this command runs, watch your terminal logs. You should see a line confirming a successful connection to Docker's servers:

Hit/Get: https://download.docker.com/linux/ubuntu resolute InRelease

Step 4: Install the Complete Docker Suite

With the repository maps correctly linked, you can now install the entire Docker virtualization engine along with its essential plugins:

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

Step 5: Verify the Deployment

Once the background installation and extraction loops wrap up, verify that everything is running perfectly.

1. Check the Background Engine Status

sudo systemctl status docker --no-pager

Look for a solid green active (running) status indicator in the output.

2. Execute a Live Test Container

Force Docker to download and run its standard diagnostic container image:

sudo docker run hello-world

If you see a greeting that reads "Hello from Docker! This message shows that your installation appears to be working correctly," your engine is fully functional.


🚀 Pro-Tip: Run Docker Without Using sudo

By default, running any docker command requires system root privileges. To avoid typing sudo before every single command, add your normal system user to the newly created docker group:

# Add your user to the docker security cgroup
sudo usermod -aG docker $USER

# Apply the group updates to your current terminal session immediately
newgrp docker

You can now run commands like docker ps or docker run directly out of your active terminal window or VNC session!

*******************************************************************

How to Set Up Plex in Docker: From Network Share to Local USB Storage

Setting up Plex Media Server using Docker Compose is one of the most efficient ways to manage your home media. This guide covers how to deploy Plex using either a Network Attached Storage (NAS) share or a locally attached USB drive.


Method 1: Deploying Plex with Network Storage (SMB/CIFS)

If your media library lives on a separate NAS or network share, you can mount it directly inside your Docker container using the local CIFS volume driver loop.

Step 1: Install Host Prerequisites

For Docker to successfully parse network file mounts, the host system must be provisioned with the appropriate filesystem mount helpers. Run the following command on your server terminal:

sudo apt update && sudo apt install cifs-utils -y

Step 2: Create a Secure Environment File

To avoid exposing sensitive login credentials in your main infrastructure files, create a hidden configuration file named .env in your active project directory:

# .env
NAS_USER=your_nas_username
NAS_PASS=your_nas_password

Step 3: Configure Docker Compose

Create a compose.yml file. This setup links Plex directly to the host's network interfaces and structures an isolated network volume map that logs in securely using your environmental parameters:

services:
  plex:
    image: lscr.io/linuxserver/plex:latest
    container_name: plex
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
      - VERSION=docker
    volumes:
      - ./config:/config
      - nas_media:/data/nas_shared
    restart: unless-stopped

volumes:
  nas_media:
    driver: local
    driver_opts:
      type: cifs
      o: "username=${NAS_USER},password=${NAS_PASS},iocharset=utf8,vers=3.0"
      device: "//YOUR_NAS_IP_ADDRESS/your_share_folder"

Method 2: Deploying Plex with Local USB Storage

If you prefer to attach a physical USB hard drive directly to your server box, the orchestration layout is much simpler. It utilizes a local bind path map, cutting out the need for global volume driver options entirely.

Step 1: Locate the USB Mount Point

Connect your storage device directly to the server framework and identify its active partition assignment structure:

lsblk

Trace your drive through the partition layout tree and note its literal address path under the target mount column (e.g., /mnt/USB/Media).

Step 2: Configure File Security Permissions

Linux environments often restrict mount access profiles by default. Ensure your container engine holds full execution properties across your media library folders:

sudo chmod -R 755 /path/to/your/usb

Step 3: Configure Docker Compose

Populate your configuration deployment file. Notice that the media target maps straight from your raw mount point path down into the internal directory system:

services:
  plex:
    image: lscr.io/linuxserver/plex:latest
    container_name: plex
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
      - VERSION=docker
    volumes:
      - ./config:/config
      # Map the local host path directly into the internal container path
      - /path/to/your/usb:/data/usb_media
    restart: unless-stopped

Final Step: Launch and Add Libraries

  1. Initialize the background system engine loop:
    docker compose up -d
  2. Launch a web browser and navigate directly to the Plex graphical dashboard console:

    http://<YOUR_SERVER_IP>:32400/web

  3. Navigate to Add Library, pick your media specification layout (Movies, TV Shows), and click Browse for Media Folder.
  4. Map the path to your internal directory target:
    • Select /data/nas_shared if processing network volumes via Method 1.
    • Select /data/usb_media if running local media drives via Method 2.
  5. Save the parameters, and allow Plex to generate metadata files!

Troubleshooting Common Errors

1. Error: "Permission Denied" inside Plex Dashboard

If you connect your library folder profiles inside Plex but directories appear completely blank or display file-locking boundaries, your user definitions are mismatched.

The Fix: Confirm your user variables map directly to your system account ID by executing id in the host shell environment. Then, push comprehensive permissions across your assets recursively:

sudo chmod -R 755 /path/to/your/media

2. Error: Docker fails to mount the NAS volume layout

If the compose architecture throws failure strings when setting up network block loops, the kernel cannot communicate or authenticate against the remote asset target pool.

The Fix: Ensure cifs-utils is correctly installed. Double-check your secure credentials mapping variables. If your storage target uses an older protocol boundary, change your engine version string inside the YAML file from vers=3.0 to vers=2.0.

3. Error: USB drive path mutations after system restarts

If the hardware reboots and Plex completely drops its reference paths to your physical media libraries, your host OS may have auto-mounted the partition block structure to a different folder path naming array.

The Fix: Bind the physical device layout parameters permanently using your host's global file systems table (/etc/fstab) using the device's un-changing unique hardware signature identifier rather than a variable path string. Run the query script to track down your signature tag:

blkid

*************************************************************************************

How to Deploy Pi-hole in Docker: A Step-by-Step Troubleshooting Guide

Setting up a network-wide ad blocker like Pi-hole using Docker is a great weekend project. However, configuration syntax errors and host port conflicts can occasionally pop up during installation.

This guide outlines the exact, step-by-step process to deploy your own Pi-hole instance from scratch while avoiding common pitfalls.


Step 1: Create the Docker Compose Configuration

To get started, you need an orchestration configuration template. Create a directory for your service, then create a file named docker-compose.yml and paste the optimized template below.

This layout enforces correct indentation spacing blocks, ensures your local blocklists survive container restarts, and maps out a explicit password flag variable for your administrative dashboard.

# More info at https://github.com and https://pi-hole.net
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
      - "8443:443/tcp" # Changed host port mapping to avoid SSL web conflicts
    environment:
      TZ: 'Asia/Kolkata'
      WEBPASSWORD: 'your_secure_password_here'
      FTLCONF_dns_listeningMode: 'ALL'
    volumes:
      - './etc-pihole:/etc/pihole'
      - './etc-dnsmasq.d:/etc/dnsmasq.d'
    cap_add:
      - SYS_NICE
    restart: unless-stopped

Step 2: Handle Port Conflicts (Troubleshooting)

When running docker compose up -d using standard templates, you might encounter a common initialization error stating:

failed to bind host port 0.0.0.0:443/tcp: address already in use

This happens because another web server (like Nginx, Apache, or a reverse proxy) is already utilizing port 443 on your server's primary network interface. You have three ways to bypass this block:

Method A: Change the Host Port (Recommended)

As implemented in our YAML block above, alter the port assignment string on the left side of the colon from "443:443/tcp" to "8443:443/tcp". This transparently maps the container's HTTPS endpoint over an available alternative channel without clashing with host services.

Method B: Stop the Conflicting Service

If your design layout dictates that Pi-hole must hold native port 443, track down the process PID holding that specific socket map by executing:

sudo lsof -i :443

If an unneeded development service (like a placeholder Nginx setup task) is capturing the connection, stop it permanently via systemd:

sudo systemctl stop nginx

Method C: Remove Port 443 Completely

Pi-hole's web dashboard operates natively over HTTP on port 80. If you do not plan on configuring advanced local SSL certificates or specialized HTTPS blockpages for your infrastructure, you can cleanly delete the line - "443:443/tcp" from your docker configuration completely without impact.


Step 3: Launch the Container

Once your network ports are validated and your configuration file is saved, assemble and run your background virtualization stack:

docker compose up -d

The container engine will download the runtime dependencies and initialize the core engine framework. Once initialized, launch a web browser and access your unified dashboard using your custom network mapping destination:

http://<YOUR_SERVER_IP>/admin

Authenticate with the master secret token string you configured on the WEBPASSWORD environment line in Step 1, and you're ready to start pointing your router's DNS settings at your new ad blocker!

*******************************************************************

Self-Hosting Immich: Complete Installation, SMB External Library & Hardware Acceleration Guide

This guide walks through deploying Immich using Docker Compose or Portainer, attaching an external SMB/CIFS Network Attached Storage (NAS) library, troubleshooting database extensions, and configuring AMD/Intel iGPU hardware acceleration for video transcoding and Machine Learning.

Prerequisites: A Linux server running Docker and Docker Compose, a user with sudo privileges, and an SMB network share containing your photo/video backups.

1. Directory Structure & Configuration

Create a dedicated deployment directory on your host server:

mkdir -p ~/immich/upload ~/immich/postgres
cd ~/immich

Create an environment file (.env) inside ~/immich/.env:

# File: ~/immich/.env
UPLOAD_LOCATION=/home/your-user/immich/upload
DB_DATA_LOCATION=/home/your-user/immich/postgres
TZ=Asia/Kolkata
IMMICH_VERSION=release

# Database Settings (Alphanumeric characters only, no hyphens or special characters)
DB_PASSWORD=YourStrongPasswordHere123
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
Important: Ensure DB_PASSWORD contains only alphanumeric characters (A-Za-z0-9). Special characters or hyphens can cause initialization failures with internal PostgreSQL connection URIs.

2. Docker Compose File

Create the docker-compose.yml file. This configuration uses official vector-supported PostgreSQL images and includes device mappings for hardware-accelerated video transcoding and ML inference.

name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    devices:
      - /dev/dri:/dev/dri # Pass iGPU for VA-API video transcoding
    volumes:
      - ${UPLOAD_LOCATION}:/data
      - /mnt/nas_photos:/usr/src/app/external/nas_photos:ro # SMB Mount
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - '2283:2283'
    depends_on:
      - redis
      - database
    restart: always
    healthcheck:
      disable: false

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-openvino
    devices:
      - /dev/dri:/dev/dri # Pass iGPU for OpenVINO acceleration
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false

  redis:
    container_name: immich_redis
    image: valkey/valkey:8-alpine
    healthcheck:
      test: valkey-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    shm_size: 128mb
    restart: always
    healthcheck:
      disable: false

volumes:
  model-cache:

3. Troubleshooting Database Initialization Errors

Common Error: Error: No vector extension found. Available extensions: vchord, vector

If you encounter vector extension errors, it means the database was initialized with an incompatible PostgreSQL image. Resolve this with a clean database volume reset:

# 1. Stop all containers
docker compose down

# 2. Clear corrupted/older database files
sudo rm -rf ~/immich/postgres

# 3. Ensure correct directory permissions
mkdir -p ~/immich/postgres
sudo chown -R $USER:$USER ~/immich

# 4. Relaunch the stack
docker compose up -d

4. Mounting an SMB/CIFS External Library

To connect an external NAS share (e.g., Samba/Windows Share) as a read-only Immich library:

Step 4.1: Install CIFS Utilities & Create Mount Point

sudo apt update && sudo apt install cifs-utils -y
sudo mkdir -p /mnt/nas_photos

Step 4.2: Create SMB Credentials File

sudo nano /etc/smbcredentials_nas

Add your Samba user credentials:

username=YOUR_SAMBA_USERNAME
password=YOUR_SAMBA_PASSWORD
sudo chmod 600 /etc/smbcredentials_nas

Step 4.3: Configure Automount in /etc/fstab

sudo nano /etc/fstab

Append the following line at the bottom (replace 192.168.x.x with your NAS IP address):

//192.168.x.x/SharedPhotos /mnt/nas_photos cifs credentials=/etc/smbcredentials_nas,uid=1000,gid=1000,dir_mode=0755,file_mode=0644,iocharset=utf8,x-systemd.automount,_netdev 0 0

Mount the share and verify contents:

sudo mount -a
ls -la /mnt/nas_photos

Step 4.4: Verify Read Permissions inside Container

docker exec -it immich_server ls -la /usr/src/app/external/nas_photos

Step 4.5: Add Path in Immich UI

  1. Log into Immich Web UI at http://YOUR_SERVER_IP:2283.
  2. Navigate to Administration > External Libraries.
  3. Click Create Library, select the owner, and save.
  4. Click the three vertical dots (⋮) on the new library row and select Edit Import Paths.
  5. Add the in-container path: /usr/src/app/external/nas_photos
  6. Click ⋮ again and choose Scan All Library Files.

5. Enabling Hardware Acceleration

Video Transcoding (VA-API / QuickSync)

  1. Verify direct rendering nodes exist on your server: ls -la /dev/dri
  2. Ensure devices: - /dev/dri:/dev/dri is present under immich-server in docker-compose.yml.
  3. In Immich Web UI, go to Administration > Settings > Video Transcoding Settings.
  4. Set Hardware Acceleration API to VAAPI (for Intel / AMD iGPUs) and save.

Machine Learning (OpenVINO)

Using the -openvino tag on immich-machine-learning utilizes CPU vector instructions (AVX2/AVX-512) and integrated GPUs to accelerate facial recognition and smart search indexing.

docker logs -f immich_machine_learning

6. Dashboard Integration (Homepage)

To display Immich metrics on your Homepage dashboard, generate an API key in Immich (Account Settings > API Keys) and update services.yaml:

- Media & Photos:
    - Immich:
        icon: immich.svg
        href: http://YOUR_SERVER_IP:2283
        description: Self-Hosted Photo Management
        widget:
          type: immich
          url: http://YOUR_SERVER_IP:2283
          key: YOUR_IMMICH_API_KEY_HERE
          fields: ["photos", "videos", "usage"]
Deployment Complete! Your Immich instance is now fully set up with persistent storage, NAS integration, hardware acceleration, and dashboard integration.

*******************************************************************

How to Install Wazuh Security Platform on a Linux Server (Clean Installation)

Wazuh is an enterprise-ready, open-source security monitoring platform that offers unified XDR and SIEM protection. Deploying it on a clean Linux server allows you to aggregate logs, analyze endpoint telemetry, and identify security anomalies seamlessly.

This guide outlines the single-node / all-in-one setup process using the official Wazuh installation assistant. It installs the Wazuh Indexer, Wazuh Server, and Wazuh Dashboard on a single machine.

Minimum Hardware Requirements

Before running the script, ensure your system fulfills the baseline hardware requirements for a single-node setup supporting up to 25 agents:

  • CPU: 4 vCPUs
  • RAM: 8 GB RAM minimum
  • Storage: 50 GB free disk space
  • OS: Ubuntu (e.g., 22.04 LTS / 24.04 LTS) or Debian (11 / 12)

Step-by-Step Installation

Step 1: Update the System Dependencies

Log into your target Linux instance and ensure your local package tree and baseline cryptographic tools are up to date.

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y curl apt-transport-https unzip wget libcap2-bin software-properties-common lsb-release gnupg2

Step 2: Download the Official Installation Assistant

Pull down the official Wazuh installation automated script directly from the secure package repository.

curl -sO https://packages.wazuh.com/4.x/wazuh-install.sh

(Note: If you need a specific version boundary lock, replace 4.x with the target release version string, or run it as shown to grab the current standard lifecycle build).

Step 3: Run the All-In-One Automated Installer

Execute the helper script with the automated single-node payload flag (-a or --all-in-one). This process will build dependencies, compile cryptographic tokens, configure configuration templates, and hook up the background units.

sudo bash wazuh-install.sh -a
Note: The process can take between 5 to 15 minutes depending on your network download speeds and backend processing power.

Post-Installation & Accessing the Dashboard

Once the installation workflow achieves a successful exit status, the terminal output will print a confirmation block detailing your local node routes:

Wazuh indexer installation finished successfully.
Wazuh server installation finished successfully.
Wazuh dashboard installation finished successfully.
 
Installation finished.
You can access the web interface at https://<YOUR_SERVER_IP>
   User: admin
   Password: <A_RANDOMLY_GENERATED_SECURE_PASSWORD>

Step 4: Secure Your Generated Credentials

  1. Copy the randomly generated password immediately from the terminal window.
  2. Save this token inside an encrypted credential store or password manager.
  3. Do not clear the terminal session until this token is logged safely.

Step 5: Log in to the Web Interface

  1. Launch a browser window and navigate to your server's endpoint:

    https://<YOUR_SERVER_IP>

  2. Because the automated script provisions secure, self-signed local TLS certificates, your browser will display a warning stating that the connection is not private.
  3. Click on Advanced and choose Proceed or Accept the Risk.
  4. Enter the default master user profile name (admin) along with the custom generated password sequence you captured in Step 4.

Verifying Server Services Status

If you ever need to manually track or confirm the core operational health states of the system daemons via CLI, run these diagnostic status strings directly over SSH:

  • Verify Indexer Backend Status:
    sudo systemctl status wazuh-indexer
  • Verify Core Management Engine Status:
    sudo systemctl status wazuh-manager
  • Verify Web Console UI Engine Status:
    sudo systemctl status wazuh-dashboard

Your clean Wazuh environment is now fully deployed and ready to ingest telemetry agents!

*******************************************************************

How to Configure the Grafana OpenSearch Data Source for Wazuh Alerts

Connecting Grafana to your Wazuh Indexer allows you to build rich, custom security dashboards using your security telemetry. Because the Wazuh Indexer is built on OpenSearch architecture, using the official Grafana OpenSearch plugin provides the most reliable connection and field mapping.

Follow this complete step-by-step blueprint to install the plugin, bypass common UI validation errors, and properly map your security log fields without exposing sensitive credentials.


Step 1: Check and Install the OpenSearch Plugin

Before configuring the database connection, ensure Grafana has the translation layer required to speak to an OpenSearch backend.

  1. Check your Grafana UI under Connections > Data sources. Search for OpenSearch.
  2. If it is not listed, log into your Grafana server's terminal and deploy it securely via the Grafana CLI framework:
    grafana-cli plugins install grafana-opensearch-datasource
  3. Restart your local systems daemon to apply the plugin update immediately:
    sudo systemctl restart grafana-server

Step 2: Configure the Primary Connection Settings

Navigate back to your Grafana web interface under Connections > Data sources > Add new data source, select OpenSearch, and apply these specific core settings:

  • Name: wazuh-opensearch-core (or any descriptive cluster identifier).
  • URL: Enter your indexer network endpoint (e.g., https://<YOUR_INDEXER_IP>:9200).
    *(Note: If Grafana runs inside a Docker container on the same host machine, use https://host.docker.internal:9200 to resolve the network link loopback).*
  • Basic Auth: Toggle this ON. Under Basic Auth Details, enter your secure cluster service account credentials.
  • Skip TLS Verify: Toggle this ON. This parameter is mandatory if your indexer targets utilize default self-signed SSL certificates, which Grafana will otherwise reject.

Step 3: Input Index Mappings & Bypass the UI Validation Bug

A known UI validation bug in the plugin frequently throws a "Data source not found" or "404" error if you attempt to fetch the server version before a primary database record exists. Use this exact workflow to bypass it:

  1. Scroll down to the OpenSearch details block form.
  2. Index name: Enter wazuh-alerts-4.x-* (this targets the default daily security logs index pattern).
  3. Time field name: Type @timestamp.
  4. Version (The Bypass): Do not click "Get Version and Save". Instead, manually click inside the empty version text block and explicitly hardcode a placeholder version value like 2.13.0.
  5. Clear Unsaved States: Ensure the Logs metadata boxes (Message field name and Level field name) and the Data Links arrays are completely empty for this initial setup write. If a placeholder container block has initialized, use the trash can icon to clear it.
  6. Save the Shell: Scroll to the absolute bottom of the configuration console page and click the green Save & test button.
  7. Refresh: As soon as Grafana attempts the initial write loop, manually reload your browser window (F5) to force the client interface to parse the newly registered data source UID correctly.

Step 4: Finalize Log Definitions & Verify

Now that the structural data source profile is registered inside Grafana's core database architecture, you can safely map your database elements without triggering web dashboard validation crashes.

  1. Re-open your newly saved OpenSearch data source connection profile.
  2. Scroll down directly to the Logs sub-section form.
  3. Message field name: Enter rule.description. This mapping directive tells Grafana which field string contains the human-readable event alert notification text.
  4. Level field name: Enter rule.level.
💡 Pro-Tip: Avoid utilizing generic server configurations like syslog_level. Wazuh passes alert severities as clean numerical arrays ranking from 0 to 15 inside the rule.level key, which Grafana's Explore UI parses cleanly for automated dashboard color-coding.

Scroll down to the footer link and click Save & test one final time. You will receive a green confirmation success badge reading:

Index OK. Time field name OK.

Your Grafana analytics pipeline is now completely deployed, structured correctly, and ready for you to build rich security visualizations!

*************************************************************************************

How to Monitor Docker Containers with Wazuh

A step-by-step guide to configuring the native Wazuh Docker Listener module for real-time container tracking and event logging.

Overview: Wazuh uses a built-in docker-listener wodle to monitor container lifecycle events (start, stop, pause, pull). This configuration works for both a standalone Wazuh Agent and a local Wazuh Manager host listening to /var/run/docker.sock.

Step 1: Install Python Docker SDK

The Docker listener module requires the Python Docker library to interact directly with the local Docker API socket.

# Ubuntu / Debian
sudo apt update && sudo apt install python3-pip python3-docker -y

# RHEL / AlmaLinux / CentOS
sudo yum install python3-pip -y
sudo pip3 install docker

Step 2: Grant Socket Permissions

Ensure the Wazuh service account holds sufficient read and write permissions to communicate across the Docker Unix socket.

# Add the wazuh system user to the docker group
sudo usermod -aG docker wazuh

Step 3: Enable the Docker Listener Module

Open the main configuration file on your target host:

sudo nano /var/ossec/etc/ossec.conf

Add the <wodle name="docker-listener"> block inside the main <ossec_config> tag:

<wodle name="docker-listener">
  <disabled>no</disabled>
  <interval>10m</interval>
  <attempts>5</attempts>
  <run_on_start>yes</run_on_start>
</wodle>

Step 4: Restart Service & Verify Logs

Restart the active daemon (Manager or Agent) to initialize the module:

# For Wazuh Manager setup
sudo systemctl restart wazuh-manager

# For Wazuh Agent setup
sudo systemctl restart wazuh-agent

Stream the log file to confirm the background process initialized successfully:

sudo tail -n 30 /var/ossec/logs/ossec.log | grep -i docker

Step 5: Testing & Dashboard Verification

  1. Run a test container on the host machine to generate an event:
    docker run --rm hello-world
  2. Open your Wazuh Dashboard web interface.
  3. Navigate to Modules > Docker or query Explore > Discover using the rule filter:
    rule.groups: docker

*******************************************************************

Automated Debian & Kali Linux Daily Diagnostics Script

Running manual health audits on system components, software repositories, virtualization runtimes, and local network configurations can become tedious. By consolidating everyday tracking tools into a unified maintenance shell utility, you can audit your system parameters, verify server socket mappings, check disk health, and clean up unneeded dependencies in a single run.

This guide presents a comprehensive, production-ready system maintenance script designed for server configurations and Kali Linux environments, followed by an operational breakdown of each metric captured.


The Complete Maintenance Script (dailyscri.sh)

Save the following script framework to your local machine. It runs core package management streams, probes active network sockets, queries storage allocations, and manages hardware states automatically.

#!/bin/bash
# ==============================================================================
# DEBIAN / KALI LINUX DAILY MAINTENANCE & DIAGNOSTIC UTILITY
# ==============================================================================
# Usage: sudo ./dailyscri.sh
# Ensure runtime execution permissions are granted: chmod +x dailyscri.sh

# Ensure script executes with root authorization
if [ "$EUID" -ne 0 ]; then
 echo "[-] Error: Please run this diagnostic script with sudo privileges."
 exit 1
fi

echo "======================================================================"
echo " 🔄 PHASE 1: PACKAGE MANAGER RESYNCHRONIZATION & REPAIR"
echo "======================================================================"
echo "[+] Initializing local package index refresh..."
sudo apt update

echo "----------------------------------------------------------------------"
echo "[+] Executing upstream distribution upgrade patches..."
sudo apt upgrade -y

echo "----------------------------------------------------------------------"
echo "[+] Validating software tree integrity & fixing broken states..."
sudo apt --fix-broken install -y

echo "----------------------------------------------------------------------"
echo "[+] Purging residual isolated dependencies and software locks..."
sudo apt autoremove -y
echo ""

echo "======================================================================"
echo " 📡 PHASE 2: NETWORK INTERFACES & SECURE TOPOLOGY"
echo "======================================================================"
echo "[+] Fetching active NordVPN Meshnet peer distributions..."
if command -v nordvpn &> /dev/null; then
 sudo nordvpn meshnet peer list
else
 echo "[-] NordVPN binary not active on this node framework."
fi

echo "----------------------------------------------------------------------"
echo "[+] Auditing firewall profile configurations..."
# sudo ufw verbose

echo "----------------------------------------------------------------------"
echo "[+] Interrogating graphical network VNC socket bindings..."
sudo netstat -ptnl | grep vnc

echo "----------------------------------------------------------------------"
echo "[+] Listing local network socket listener interfaces..."
# sudo netstat -tulpn

echo "----------------------------------------------------------------------"
echo "[+] Querying structural link layer IP routing tables..."
ip addr
echo ""

echo "======================================================================"
echo " ⚙️ PHASE 3: OS ARCHITECTURE & ENVIRONMENT IDENTIFICATION"
echo "======================================================================"
echo "[+] Querying platform hostname mapping specifications..."
sudo hostnamectl

echo "----------------------------------------------------------------------"
echo "[+] Extracting LSB distribution release parameters..."
sudo lsb_release -a

echo "----------------------------------------------------------------------"
echo "[+] Reading base kernel release parameters..."
cat /etc/os-release
echo ""

echo "======================================================================"
echo " 📊 PHASE 4: DISK MANAGEMENT & CONTAINER METRICS"
echo "======================================================================"
echo "[+] Querying Samba network storage connection states..."
if command -v smbstatus &> /dev/null; then
 sudo smbstatus --shares | column -t
else
 echo "[-] Samba tracking utility not accessible."
fi

echo "----------------------------------------------------------------------"
echo "[+] Isolating active container virtualization footprints..."
# sudo docker ps

echo "----------------------------------------------------------------------"
echo "[+] Mapping block storage partitions & USB mount tracks..."
lsblk
echo ""

echo "======================================================================"
echo " 🛠️ PHASE 5: HARDWARE FOOTPRINT & PERFORMANCE METRICS"
echo "======================================================================"
echo "[+] Analyzing CPU core instruction topologies..."
lscpu

echo "----------------------------------------------------------------------"
echo "[+] Reading system kernel compilation parameters..."
uname -a

echo "----------------------------------------------------------------------"
echo "[+] Generating short-form hardware component configurations..."
# hwinfo --short | column -t

echo "----------------------------------------------------------------------"
echo "[+] Sampling kernel virtual memory and scheduling operations..."
vmstat

echo "----------------------------------------------------------------------"
echo "[+] Tracking active open file descriptors and port leaks..."
# sudo lsof -i -P -n
echo ""

echo "======================================================================"
echo " 🔌 PHASE 6: SERVICE MANAGEMENT & PERIPHERAL AUTOMATION"
echo "======================================================================"
echo "[+] Checking vulnerability scanner engine daemon states (GVM)..."
# sudo gvm-start

echo "----------------------------------------------------------------------"
echo "[+] Resetting Bluetooth subsystem communication states..."
sudo systemctl restart bluetooth.service
sudo systemctl daemon-reload

echo "----------------------------------------------------------------------"
echo "[+] Cycling network radio interfaces to force fresh lease hooks..."
sudo nmcli radio wifi off
sudo nmcli radio wifi on

echo "======================================================================"
echo " ✅ SYSTEM DIAGNOSTICS LOG LOOP COMPLETE!"
echo "======================================================================"

Script Operational Breakdown

This automated maintenance tool groups standalone management commands into specialized diagnostic categories:

Command String Functional Tracking Target
nordvpn meshnet peer list Lists remote machine pathways, routing variables, and unique public keys associated with your private encrypted mesh network topology.
netstat -ptnl | grep vnc Audits the active network stack to confirm your graphical TigerVNC/TightVNC daemon engines are listening securely on port 5901.
smbstatus --shares Monitors active cross-platform Samba network file shares, tracking client mounts and active locking mechanics.
vmstat Outputs low-overhead performance statistics, covering process queues, memory paging activity, disk block operations, and CPU cycles.
apt --fix-broken install Unlocks the apt package manager if incomplete extractions or broken maintainer dependencies occur during updates.
nmcli radio wifi off / on Cycles the local wireless radio frequencies via NetworkManager to flush stale connection flags and force a fresh DHCP IP address lease request.

How to Deploy and Schedule the Utility

To implement this script, paste the block code into a local text file, make it executable, and run it with administrative privileges:

# Create the script file
nano dailyscri.sh

# Apply executable permissions
chmod +x dailyscri.sh

# Execute the diagnostic framework
sudo ./dailyscri.sh

To completely automate your system maintenance, you can schedule this script to run as a root crontab task. Open the system scheduler by running sudo crontab -e and append a cron expression—such as 0 2 * * * /home/yourusername/dailyscri.sh—to run the entire audit process automatically every night at 2:00 AM completely hands-free.

**************************************************************

How to Build an Automated Daily Server Diagnostic Dashboard

Managing a Linux server requires constant vigilance. Between tracking package updates, monitoring disk health, auditing network ports, and reviewing SSH access logs, system administration can quickly become overwhelming.

In this guide, we will build a completely automated, zero-cost observability stack. We will create a core diagnostic script to gather system health metrics, wrap it in a secondary engine to highlight daily changes, and serve it to a sleek web dashboard using a lightweight Python server.


Step 1: The Core Diagnostic Script

First, we need a script that gathers all the raw data. This script uses enterprise-grade diagnostics to check your packages, network maps, Docker containers, and security logs.

  1. Create the script file in your home directory:
    nano ~/dailyscri.sh
  2. Paste the following optimized code block. (Note: We use apt-get with the -qq flag to prevent CLI interface warnings during automated runs).
#!/bin/bash
# ==============================================================================
# SYSTEM MAINTENANCE, MONITORING & DIAGNOSTIC AUTOMATION SCRIPT
# ==============================================================================

if [ "$EUID" -ne 0 ]; then
 echo "[-] Error: This script must be run with sudo."
 exit 1
fi

echo "======================================================================"
echo "🚀 STARTING COMPREHENSIVE SYSTEM AUDIT PACK"
echo "======================================================================"
echo "Timestamp: $(date)"
echo ""

# --- PHASE 1: PACKAGE MANAGEMENT & SOFTWARE UPDATES ---
echo "======================================================================"
echo "🔄 PHASE 1: PACKAGE MANAGEMENT & SOFTWARE UPDATES"
echo "======================================================================"
apt-get update -qq
apt-get full-upgrade -y -qq
apt-get --fix-broken install -y -qq
apt-get autoremove -y -qq
if command -v snap &> /dev/null; then
 snap refresh &> /dev/null
fi
echo "[+] Package updates and cleanup complete."
echo ""

# --- PHASE 2: OS ENVIRONMENT & HOST IDENTIFICATION ---
echo "======================================================================"
echo "📋 PHASE 2: OS ENVIRONMENT & HOST IDENTIFICATION"
echo "======================================================================"
hostnamectl | grep -E "Static hostname|Operating System|Kernel"
echo ""

# --- PHASE 3: HARDWARE & STORAGE TOPOLOGIES ---
echo "======================================================================"
echo "💻 PHASE 3: HARDWARE & STORAGE TOPOLOGIES"
echo "======================================================================"
echo "[+] Disk Capacity (df -h):"
df -h | grep -E "^/dev/"
echo ""

echo "[+] Physical Disk S.M.A.R.T. Status:"
if command -v smartctl &> /dev/null; then
  smartctl -H /dev/sda | grep "test result" || echo "[-] Cannot read /dev/sda SMART status."
else
  echo "[-] smartmontools not installed."
fi
echo ""

# --- PHASE 4: NETWORK ARCHITECTURE ---
echo "======================================================================"
echo "📡 PHASE 4: NETWORK ARCHITECTURE"
echo "======================================================================"
echo "[+] Local IP Addresses:"
ip -br addr
echo ""

echo "[+] Active Listening Ports (TCP/UDP):"
netstat -tulpn | grep LISTEN
echo ""

if command -v tailscale &> /dev/null; then
 echo "[+] Tailscale Status:"
 tailscale status
 echo ""
fi

# --- PHASE 5: CONTAINER & DAEMON HEALTH ---
echo "======================================================================"
echo "📊 PHASE 5: CONTAINER & DAEMON HEALTH"
echo "======================================================================"
echo "[+] Failed Systemd Services:"
systemctl --failed
echo ""

if command -v docker &> /dev/null; then
 echo "[+] Docker Container Resource Usage:"
 docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
 echo ""
fi

# --- PHASE 6: SECURITY & ACCESS AUDITING ---
echo "======================================================================"
echo "🛡️ PHASE 6: SECURITY & ACCESS AUDITING"
echo "======================================================================"
echo "[+] Last 5 Successful SSH Logins:"
journalctl -u ssh.service --no-pager | grep "Accepted" | tail -n 5 || echo "[-] No recent remote logins found."
echo ""

echo "[+] Critical System Errors Since Last Boot:"
journalctl -p 3 -xb --no-pager | tail -n 10
echo ""

if command -v fail2ban-client &> /dev/null; then
 echo "[+] Fail2Ban SSH Blocks:"
 fail2ban-client status sshd
 echo ""
fi

echo "======================================================================"
echo "✅ SYSTEM AUDIT COMPLETE"
echo "======================================================================"

Save the file and grant it execution permissions:

chmod +x ~/dailyscri.sh

Step 2: The Diffing & Web Output Wrapper

Next, we create a "wrapper" script. This script executes the diagnostic tool, compares today's data against yesterday's run (to highlight new ports or changes), and packages it all into an HTML file.

nano ~/run_daily_report.sh

Paste the following logic, ensuring you replace YOUR_USERNAME with your actual Linux user directory name:

#!/bin/bash
# ==============================================================================
# REPORT GENERATOR & DIFFING ENGINE
# ==============================================================================

# Define Paths
REPORTS_DIR="/home/YOUR_USERNAME/reports"
WEB_DIR="/home/YOUR_USERNAME/web_dashboard"
DIAG_SCRIPT="/home/YOUR_USERNAME/dailyscri.sh"

# 1. Ensure directories exist
mkdir -p "$REPORTS_DIR"
mkdir -p "$WEB_DIR"

# Ensure a dummy "yesterday.txt" exists for the very first run
if [ ! -f "$REPORTS_DIR/yesterday.txt" ]; then
    touch "$REPORTS_DIR/yesterday.txt"
fi

# 2. Run the diagnostic script and save today's output
sudo "$DIAG_SCRIPT" > "$REPORTS_DIR/today.txt"

# 3. Compare Today vs Yesterday (Ignoring dynamic timestamps)
diff -u "$REPORTS_DIR/yesterday.txt" "$REPORTS_DIR/today.txt" | grep -v "Timestamp:" > "$REPORTS_DIR/changes.txt"

# 4. Generate the HTML Report
HTML_FILE="$WEB_DIR/index.html"

cat << 'EOF' > "$HTML_FILE"
<!DOCTYPE html>
<html>
<head>
    <title>Daily Server Report</title>
    <style>
        body { font-family: monospace; background-color: #1e1e1e; color: #d4d4d4; padding: 20px; }
        h1 { color: #569cd6; border-bottom: 1px solid #333; padding-bottom: 10px; }
        h2 { color: #4ec9b0; margin-top: 30px; }
        pre { background-color: #252526; padding: 15px; border-radius: 5px; overflow-x: auto; }
        .added { color: #608b4e; }   /* Green for new lines */
        .removed { color: #d16969; } /* Red for removed lines */
    </style>
</head>
<body>
    <h1>Daily Server Diagnostics</h1>
    <p>Report generated on: $(date)</p>

    <h2>What Changed Since Yesterday? (The Diff)</h2>
    <pre>
EOF

# Read the diff file and add basic syntax highlighting
while IFS= read -r line; do
    if [[ $line == +* ]] && [[ $line != +++* ]]; then
        echo "<span class=\"added\">$line</span>" >> "$HTML_FILE"
    elif [[ $line == -* ]] && [[ $line != ---* ]]; then
        echo "<span class=\"removed\">$line</span>" >> "$HTML_FILE"
    else
        echo "$line" >> "$HTML_FILE"
    fi
done < "$REPORTS_DIR/changes.txt"

cat << 'EOF' >> "$HTML_FILE"
    </pre>

    <h2>Full Report (Today)</h2>
    <pre>
EOF

cat "$REPORTS_DIR/today.txt" >> "$HTML_FILE"

cat << 'EOF' >> "$HTML_FILE"
    </pre>
</body>
</html>
EOF

# 5. Rotate the files for tomorrow
mv "$REPORTS_DIR/today.txt" "$REPORTS_DIR/yesterday.txt"

echo "Report generated successfully at $HTML_FILE"

Save the file and grant it execution permissions:

chmod +x ~/run_daily_report.sh

Step 3: Serve the Dashboard & Automate

We will use Python's built-in HTTP module to serve this directory securely over your local network without needing a heavy Nginx or Apache setup.

  1. Generate the first report: Run the wrapper script manually to create the directories and initial HTML file.
    sudo ~/run_daily_report.sh
  2. Start the Web Server: Navigate to the web folder and launch the Python server in the background using nohup.
    cd ~/web_dashboard
    nohup python3 -m http.server 8080 &

    (You will see a message like [1] 2959278 nohup: ignoring input.... Press Enter to return to your prompt. The server is now running permanently in the background).

  3. Automate with Cron: Tell your system to run the report every morning at 2:00 AM.
    sudo crontab -e

    Paste this line at the absolute bottom of the file (adjusting your username):

    0 2 * * * /home/YOUR_USERNAME/run_daily_report.sh
🎉 Mission Complete! You can now open a browser on your local network and navigate to http://<YOUR_SERVER_IP>:8080. Every morning, you will wake up to a fresh dashboard detailing exactly what changed on your server overnight, complete with green and red highlights for added or removed system states!

*******************************************************************

Automating a Global Homelab: Intelligent Updates, Telegram Alerts, and Conditional Reboots

Managing a distributed fleet of servers—like remote virtual machines, local Raspberry Pis, and core Proxmox hypervisors—presents a unique automation challenge. You want all nodes to update securely and reliably, but you can't treat them all the same. For example, standard nodes should reboot after an update, but your core hypervisor must stay awake to protect running VMs. Furthermore, some nodes might have custom web dashboards that need triggering, while others just need standard package updates.

In this guide, we will build a "Master Orchestrator" bash script. This single, universal script dynamically adapts to the machine it is running on, pushes success/failure alerts directly to your phone via Telegram, and safely manages the reboot sequence.


Step 1: Set Up Telegram Alerts

Before writing the script, we need a way for the servers to securely message you. Telegram's API is incredibly fast, reliable, and free.

  1. Open Telegram and search for @BotFather (look for the verified checkmark).
  2. Send the message /newbot, give it a name, and choose a unique username ending in "bot".
  3. BotFather will provide an HTTP API Token. Save this securely.
  4. Start a chat with your new bot and send it a quick "hello".
  5. In your computer's web browser, go to: https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates
  6. Look for the section that says "chat":{"id":123456789... and copy that number. This is your personal Chat ID.

Step 2: The Master Orchestrator Script

Connect to your server via SSH and create the automation script in the root directory:

sudo nano /root/auto_update.sh

Paste the following script. Be sure to replace the placeholder Telegram credentials at the top, and modify the "pve" hostname to match whatever you named your Proxmox server.

#!/bin/bash
# ==============================================================================
# MASTER AUTOMATION: REPORTING, ALERTS & REBOOTS (TELEGRAM EDITION)
# ==============================================================================

# Telegram Credentials
TELEGRAM_TOKEN="YOUR_BOT_TOKEN_HERE"
TELEGRAM_CHAT_ID="YOUR_CHAT_ID_HERE"

MACHINE_NAME=$(hostname)

# Function to send the Telegram message silently
send_telegram() {
    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
        -d "chat_id=${TELEGRAM_CHAT_ID}" \
        -d "text=$1" \
        -d "parse_mode=Markdown" > /dev/null
}

# ------------------------------------------------------------------------------
# 1. INTELLIGENT UPDATE & DIAGNOSTIC EXECUTION
# ------------------------------------------------------------------------------
# Dynamically search for a custom web dashboard script (if you have one)
REPORT_SCRIPT=$(find /home -maxdepth 2 -name "run_daily_report.sh" 2>/dev/null | head -n 1)

if [ -n "$REPORT_SCRIPT" ] && [ -f "$REPORT_SCRIPT" ]; then
    # If a custom dashboard script exists on this specific server, execute it
    "$REPORT_SCRIPT"
    if [ $? -eq 0 ]; then
        UPDATE_STATUS="✅ Web Dashboard, Diagnostics & Updates successful"
    else
        UPDATE_STATUS="❌ Web Dashboard Execution FAILED"
    fi
else
    # Fallback for standard servers without custom dashboards
    apt-get update -qq
    apt-get full-upgrade -y -qq
    apt-get autoremove -y -qq
    if [ $? -eq 0 ]; then
        UPDATE_STATUS="✅ Standard Updates successful"
    else
        UPDATE_STATUS="❌ Standard Updates FAILED"
    fi
fi

# ------------------------------------------------------------------------------
# 2. ALERTS & REBOOT HANDLING
# ------------------------------------------------------------------------------
if [ "$MACHINE_NAME" == "pve" ]; then
    # PROXMOX EXCEPTION: Send alert, but DO NOT reboot
    MESSAGE="🖥️ *${MACHINE_NAME}* ($UPDATE_STATUS). Skipping reboot to protect running VMs."
    send_telegram "$MESSAGE"
    exit 0
else
    # ALL OTHER SERVERS: Send alert and gracefully reboot
    MESSAGE="🔄 *${MACHINE_NAME}* ($UPDATE_STATUS). Server is now rebooting."
    send_telegram "$MESSAGE"
    
    # Wait 5 seconds to ensure the webhook sends, then reboot
    sleep 5
    /sbin/reboot
fi

Save the file and grant it execution permissions:

sudo chmod +x /root/auto_update.sh

Step 3: Staggered Cron Scheduling

If you tell all your servers to update at the exact same minute, they will compete for network bandwidth. Worse, a VM might try to update at the same moment its host hypervisor is updating. Staggering is key.

Open the root crontab on each server:

sudo crontab -e

Important: If you previously had independent update or dashboard scripts scheduled in cron, delete them now. This Master Script handles everything!

Assign a unique time to each node in your fleet. For example:

  • VM 1: 0 3 * * * /root/auto_update.sh (3:00 AM)
  • Raspberry Pi: 30 3 * * * /root/auto_update.sh (3:30 AM)
  • Remote Bare Metal: 0 4 * * * /root/auto_update.sh (4:00 AM)
  • Proxmox Host: 30 4 * * * /root/auto_update.sh (4:30 AM)

Step 4: Manual Testing & Verification

To verify the setup without waiting for 3:00 AM, run the script manually as root:

sudo /root/auto_update.sh

Depending on the machine, you will experience the script's built-in intelligence. Standard nodes will text you and gracefully drop your SSH connection as they reboot. However, when you test this on your Proxmox server, your phone will buzz with the message: "Skipping reboot to protect running VMs," and your terminal session will remain completely intact.

You now have a fully automated, zero-touch infrastructure. Enjoy waking up to a fully patched server fleet and a neat row of green checkmarks on your phone every morning!

*******************************************************************

Centralized Telegram Alerts for Homelab Cronjobs

Homelab Automation Bash Monitoring

Centralized Telegram Alerts for Homelab Cronjobs

When your homelab scales up to include multiple VMs, Raspberry Pis, and bare-metal nodes, keeping track of automated background tasks becomes a nightmare. Are your auto-update scripts actually running? Did the daily backup fail silently? Did a node actually reboot when it was supposed to?

Instead of setting up a heavy, centralized monitoring server to SSH into all these machines, you can build a lightweight Bash Wrapper Agent. By placing a single, tiny script on each node and wrapping your existing `crontab` jobs in it, you can instantly push formatted execution metrics, exit codes, and error logs directly to your phone via Telegram.

Here is how to deploy a zero-cost, cross-platform cronjob alerting system across your entire homelab fleet.

Step 1: Create the Telegram Agent Script

You will need to create this script on every node in your cluster. First, get a Bot Token from BotFather on Telegram and note down your Chat ID.

Create the wrapper script in your local binaries folder:

sudo nano /usr/local/bin/cron_agent.sh

Paste in the following code. This script takes the hostname and job name as arguments, runs your target script, measures the execution time, captures the output, and pushes a perfectly formatted HTTP POST request to the Telegram API.

#!/bin/bash
# ==========================================
# Telegram Cronjob Monitoring Agent
# ==========================================

# Replace these with your Telegram Bot Token and Chat ID
BOT_TOKEN="YOUR_TELEGRAM_BOT_TOKEN"
CHAT_ID="YOUR_TELEGRAM_CHAT_ID"

# Parse arguments
ALERT_SUCCESS=true
if [ "$1" == "--fail-only" ]; then
    ALERT_SUCCESS=false
    shift
fi

SERVER_NAME="$1"
JOB_NAME="$2"
shift 2
COMMAND="$@"

# Start execution and timer
START_TIME=$(date +%s)
OUTPUT=$($COMMAND 2>&1)
EXIT_CODE=$?
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))

# Determine Status
if [ $EXIT_CODE -eq 0 ]; then
    # If success and fail-only is true, exit silently
    if [ "$ALERT_SUCCESS" = false ]; then
        exit 0
    fi
    STATUS="✅ SUCCESS"
else
    STATUS="❌ FAILED (Code: $EXIT_CODE)"
fi

# Format the Telegram Message
MESSAGE=$(cat <<EOF$STATUS
🖥 Host: $SERVER_NAME
🛠 Job: $JOB_NAME
⏱ Duration: ${DURATION}s

📄 Output:
$(echo "$OUTPUT" | tail -n 15)
EOF
)

# Send to Telegram
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
    -F chat_id="${CHAT_ID}" \
    -F text="$MESSAGE" > /dev/null
Pro-Tip: Notice the --fail-only flag logic. This allows us to silence successful runs for highly frequent jobs (like minute-by-minute watchdogs), ensuring your phone only buzzes when something actually crashes!

Step 2: Secure and Make Executable

Save the file, then make sure it has execution permissions so the cron daemon can trigger it:

sudo chmod +x /usr/local/bin/cron_agent.sh

Step 3: Wrap Your Crontab Jobs

Now, open the crontab on your server (sudo crontab -e) and modify your existing jobs to pass through the wrapper agent.

The syntax for the wrapper is simple:
/usr/local/bin/cron_agent.sh [OPTIONS] "Host Name" "Job Name" /path/to/your/script.sh

Example A: Standard Daily Maintenance Tasks

For standard daily backups or update scripts where you want a confirmation message every time they run:

# m h  dom mon dow   command
0 3 * * * /usr/local/bin/cron_agent.sh "Node-01" "Auto Update" /root/auto_update.sh
0 4 * * * /usr/local/bin/cron_agent.sh "Node-01" "Daily Report" /home/user/run_daily_report.sh

Example B: Handling Reboots

If you have an @reboot job that needs to wait for the network to come online before running (using a sleep command), you must place the sleep before calling the agent. Otherwise, the agent will include the 5-minute sleep duration in its execution timer!

# Shutdown at 8:00 AM, send alert when the command triggers
0 8 * * * /usr/local/bin/cron_agent.sh "Node-02" "Daily Reboot" /sbin/shutdown -r now

# Wait 5 minutes after boot, then run the update and send an alert
@reboot sleep 300 && /usr/local/bin/cron_agent.sh "Node-02" "Boot Update" /root/auto_update.sh

Example C: High-Frequency Watchdogs

If you have a hypervisor script or watchdog that runs every single minute, you absolutely do not want 1,440 success messages a day. Simply add the --fail-only flag to keep it completely silent unless it crashes (returns a non-zero exit code).

* * * * * /usr/local/bin/cron_agent.sh --fail-only "Hypervisor" "Watchdog" /root/watchdog.sh

Conclusion

With this architecture, every time `cron` triggers across your homelab, it passes the Hostname, the Job Name, and the actual script command directly into the wrapper. The agent runs the script seamlessly in the background, captures the last 15 lines of output, measures the duration, and fires off a clean HTTP POST request to Telegram.

No extra containers to maintain. No agent installation bloat. Just a single bash script turning your entire fleet into a smart, self-reporting ecosystem.

*******************************************************************

Benchmarking and Diagnosing Linux DNS Performance

Here is your comprehensive blog guide mapping out the DNS diagnostics workflow, followed by a production-ready, single-file Bash script to automate the entire assessment.

When web applications face intermittent lag or loading stalls, network administrators frequently ignore the most common failure point: the Domain Name System (DNS) resolver layer. Slow DNS lookup speeds introduce silent latencies that mirror hard network drops. This guide outlines a structured approach to inspecting, testing, and optimizing your Linux server's DNS layout.


Step 1: Map the Active Upstream Topology

Before modifying system files, you must isolate exactly where your system directs its outbound routing requests. Modern Linux distros delegate this execution loop to the local systemd-resolved interface layer.

resolvectl status | grep "DNS Servers" -A 2
🔍 Evaluating Results: This exposes your direct link layer targets. If you see a private local gateway (like 192.168.1.1), your lookups are bottlenecked by consumer-grade router hardware before hitting the public internet.

Step 2: Measure Baseline Lookup Latency

Using dig (Domain Information Groper), you can pinpoint the turnaround time of a live transaction down to a single millisecond.

dig ubuntu.com

Scan the resulting text matrix for the explicit Query time signature block:

  • 0 ms – 4 ms: Reading directly out of local system cache memory.
  • 5 ms – 30 ms: Ideal performance. Connected to a highly responsive, physically localized tier-1 resolver.
  • 31 ms – 80 ms: Standard performance metrics for traditional ISP or localized cloud nodes.
  • > 120 ms: Poor performance. Your current upstream server is saturated or suffering from high geographical path latency.

Step 3: Test Real-Time Stability and Variance

A single lightning-fast resolution does not guarantee stability. Network configurations must remain resilient under repetitive querying loops without dropping traffic packets or experiencing timing spikes.

while true; do dig google.com | grep "Query time"; sleep 1; done
📊 Jitter Analysis: Watch for large millisecond swings. If your latency jumps from 15ms up to 250ms under sequential lookups, your upstream provider is throttling requests or suffering from packet corruption. Press Ctrl + C to terminate the loop.

Step 4: Benchmark and Identify Fast Alternatives

If baseline checks show high latencies, bypass your system’s defaults entirely to query optimized public anycast infrastructure directly. This tells you if your performance issues are local or provider-based.

dig google.com @1.1.1.1 | grep "Query time"

By forcing dig to use Cloudflare’s @1.1.1.1 endpoint, you establish an optimized baseline. If this public lookup outpaces your local system defaults, your server's primary network configuration files should be updated to use public providers permanently.


Automated DNS Diagnostic Script

Save the following code block as a single executable script file (e.g., dns_audit.sh) on your server. It completely automates the steps detailed above, checking dependencies, calculating local performance, and running live external benchmarks.

#!/bin/bash
# ==============================================================================
# Linux DNS Performance & Diagnostic Benchmarking Tool
# ==============================================================================

# Ensure environment has diagnostic utilities present
if ! command -v dig &> /dev/null; then
 echo "⚙️ Missing dependency 'dnsutils' detected. Attempting automated install..."
 sudo apt update -qq && sudo apt install dnsutils -y -qq
fi

clear
echo "======================================================================"
echo " 🔍 PHASE 1: ACTIVE SYSTEM RESOLVER TOPOLOGY"
echo "======================================================================"
if command -v resolvectl &> /dev/null; then
 ACTIVE_DNS=$(resolvectl status | grep "DNS Servers" -A 2)
 if [ -n "$ACTIVE_DNS" ]; then
 echo "$ACTIVE_DNS"
 else
 echo "⚠️ No upstream records found in resolvectl status."
 fi
else
 echo "Fallback Mode (/etc/resolv.conf):"
 grep nameserver /etc/resolv.conf
fi
echo ""

echo "======================================================================"
echo " 📊 PHASE 2: BASELINE RESPONSE MATRIX"
echo "======================================================================"
echo "Executing cold query against target node (ubuntu.com)..."
dig ubuntu.com | grep -E "Query time|SERVER"
echo ""

echo "======================================================================"
echo " 📈 PHASE 3: STABILITY & JITTER ASSESSMENT (5-CYCLE STRESS TEST)"
echo "======================================================================"
for i in {1..5}; do
 TIME_MS=$(dig google.com | grep "Query time" | awk '{print $4}')
 echo " -> Query Iteration $i: \${TIME_MS} ms"
 sleep 0.5
done
echo ""

echo "======================================================================"
echo " ⚡ PHASE 4: ANYCAST PUBLIC RESOLVER BENCHMARK"
echo "======================================================================"
echo "System Default Link: \$(dig google.com | grep "Query time" | awk '{print \$4, \$5}')"
echo "Cloudflare Platform: \$(dig google.com @1.1.1.1 | grep "Query time" | awk '{print \$4, \$5}')"
echo "Google Engine Domain: \$(dig google.com @8.8.8.8 | grep "Query time" | awk '{print \$4, \$5}')"
echo "Quad9 Secure Node:   \$(dig google.com @9.9.9.9 | grep "Query time" | awk '{print \$4, \$5}')"
echo "======================================================================"
echo "💡 Optimization Rule: If public platforms outpace your System Link"
echo " by > 20ms, update your system interface to reference them permanently."

How to use the script:

  1. Copy the code block above into a new file on your server: nano dns_audit.sh
  2. Apply executable privileges to the shell file: chmod +x dns_audit.sh
  3. Execute the automated auditing matrix: ./dns_audit.sh

*******************************************************************

Automating Homelab Uptime: 60-Second Watchdog Alerts & Dashboards

When running a distributed fleet of servers, you need to know the moment a node drops offline. However, there is a fundamental logical problem: a server cannot send an alert if it is dead or disconnected.

To solve this, we can designate one highly available machine (like a core hypervisor that rarely reboots) to act as a "Watchdog." In this guide, we will set up a lightweight bash script to actively ping your network every 60 seconds and push Telegram alerts on state changes. Then, we will visualize this telemetry data in Home Assistant for a beautiful, real-time command center.





Part 1: The Watchdog Script (Telegram Alerts)

Log into your most stable "always-on" server as root and create the Watchdog script. This script remembers the previous state of your servers, ensuring you only receive alerts when a server drops offline or comes back online (preventing notification spam).

sudo nano /root/watchdog.sh

Paste the following code, replacing the placeholder Telegram credentials and IP addresses:

#!/bin/bash
# ==============================================================================
# SERVER FLEET WATCHDOG (UPTIME MONITOR)
# ==============================================================================

TELEGRAM_TOKEN="YOUR_BOT_TOKEN_HERE"
TELEGRAM_CHAT_ID="YOUR_CHAT_ID_HERE"

# Define your servers in a format: "Name|IP_Address"
# (Exclude the server running this script)
SERVERS=(
    "Remote-Server|100.x.x.x"
    "Local-VM|100.x.x.x"
    "Raspberry-Pi|100.x.x.x"
)

# Directory to store the UP/DOWN state of each server
STATE_DIR="/tmp/watchdog_states"
mkdir -p "$STATE_DIR"

# Function to send Telegram alerts silently
send_telegram() {
    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
        -d "chat_id=${TELEGRAM_CHAT_ID}" \
        -d "text=$1" \
        -d "parse_mode=Markdown" > /dev/null
}

# Loop through each server and check its status
for SERVER in "${SERVERS[@]}"; do
    NAME="${SERVER%%|*}"
    IP="${SERVER##*|}"
    STATE_FILE="$STATE_DIR/${NAME}.state"

    # If this is the first time running, assume the server is UP
    if [ ! -f "$STATE_FILE" ]; then
        echo "UP" > "$STATE_FILE"
    fi

    LAST_STATE=$(cat "$STATE_FILE")

    # Ping the server (send 3 packets, timeout after 3 seconds)
    if ping -c 3 -W 3 "$IP" > /dev/null 2>&1; then
        CURRENT_STATE="UP"
    else
        CURRENT_STATE="DOWN"
    fi

    # Compare current state to last known state. If it changed, send an alert!
    if [ "$CURRENT_STATE" != "$LAST_STATE" ]; then
        if [ "$CURRENT_STATE" == "DOWN" ]; then
            send_telegram "🚨 *OFFLINE ALERT:* Server **${NAME}** is unreachable!"
        else
            send_telegram "✅ *RECOVERY:* Server **${NAME}** is back ONLINE!"
        fi
        
        # Save the new state
        echo "$CURRENT_STATE" > "$STATE_FILE"
    fi
done

Save the file and grant it execution permissions:

sudo chmod +x /root/watchdog.sh

Next, automate the script using Cron so it checks your fleet every 60 seconds. Open your crontab (sudo crontab -e) and add this line to the bottom:

* * * * * /root/watchdog.sh

Part 2: Visualizing Health in Home Assistant

While the bash script handles push notifications, we want a visual dashboard. Home Assistant has a native, code-free integration specifically for this.

  1. In Home Assistant, navigate to Settings > Devices & Services.
  2. Click Add Integration and search for Ping (ICMP).
  3. Enter the IP addresses of your servers. Home Assistant will automatically generate a binary sensor (UP/DOWN) and a numeric sensor (Latency/Ping in ms) for each node.

Building the Dashboard Cards

Edit your Home Assistant dashboard and add the following cards to create a comprehensive Network Operations Center (NOC) view:

  • The Uptime List (Entities Card): Add an Entities card and select the binary_sensor for each machine. This gives you a clean list showing "Connected" or "Disconnected" at a glance.
  • The Outage Timeline (History Graph Card): Add a History Graph card using the same binary_sensor entities. Set the "Hours to show" to 24. This generates a color-coded bar chart where green means UP, making it easy to spot overnight network drops.
  • Real-time Network Speed (Gauge Card): Add a Gauge card using the sensor.xxx_round_trip_time_average entity. Enable severity colors (e.g., Green for 0ms, Yellow for 150ms, Red for 300ms) to monitor network lag and congestion in real-time.

With these two systems running in tandem, you achieve the best of both worlds: silent telemetry logging with visual dashboards when you want to look, and aggressive Telegram push notifications when things actually break.



*******************************************************************

Official Ubuntu Repository URLs for sources.list and DEB822 Formats

The package management system in Ubuntu (apt) relies on a central configuration mapping file to track down and pull down binary software updates. If your configuration files become corrupted during an upgrade or an aggressive repository mirror test, you can completely restore your tracking definitions by matching your specific system lifecycle profile below.

⚠️ Architecture Notice: Starting with Ubuntu 24.04 LTS, the operating system has officially transitioned away from the traditional, single-line sources.list file configuration format into a structured, multi-line container framework known as the DEB822 standard format located inside /etc/apt/sources.list.d/ubuntu.sources.

Ubuntu 24.04 LTS "Noble Numbat" (Modern DEB822 Format)

For modern environments running Noble Numbat, populate your configuration paths inside the modern file layer layout at /etc/apt/sources.list.d/ubuntu.sources:

# Core Applications, Updates, Backports, and Proposed Pools
Types: deb deb-src
URIs: http://us.archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports noble-proposed
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

# Mainstream Security Baseline Patches
Types: deb deb-src
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Ubuntu 22.04 LTS "Jammy Jellyfish" (Traditional One-Line Format)

For long-term hardware systems using Jammy Jellyfish, parameters remain mapped via the traditional single-string array layout profile. Append or replace this layout schema within the core file container located at /etc/apt/sources.list:

deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse

deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe multiverse

deb http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse

deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse

deb http://archive.canonical.com/ubuntu/ jammy partner
# deb-src http://archive.canonical.com/ubuntu/ jammy partner

*(Note: The hash symbols # on the deb-src parameters safely comment out source-code repository targets, cutting out redundant download cycles if your server environment only tracks standard pre-compiled binaries).*


Understanding Ubuntu Repository Components

Both layout configuration types share the same standard repository component classifications. These classifications split package groups based on licensing rules and support lifecycles:

Component Naming Functional Parameters & Software Scopes
main Officially supported, completely free, open-source software maintained directly by the core Canonical engineering teams.
restricted Proprietary driver modules, microcode binaries, and critical infrastructure dependencies required for hardware functionality that are not fully open-source.
universe A massive ecosystem of community-maintained free and open-source packages, managed entirely by volunteer community developers.
multiverse Software packages restricted by patents, copyright protections, or legal constraints (e.g., specific media codecs), requiring manual user compliance checks.

Step 3: Flush and Reinitialize Cache Pools

Once you have restored your destination maps to their appropriate structure, execute a full metadata cache purge via your terminal to drop corrupted indexes and pull down a clean mirror list:

# Purge active database repository paths
sudo rm -rf /var/lib/apt/lists/*

# Run verification indexes alignment update
sudo apt update

Your package manager is now cleanly aligned with official repository nodes and ready to resume normal software operations!

**************************************************************

How to Manage and Fix the Ubuntu Display Manager (GDM)

The display manager is the graphical interface responsible for user authentication and launching your Ubuntu desktop environment (the login screen). If it breaks or goes inactive, your system will fail to load the desktop, often leaving you stuck on a black screen or a blinking cursor.

This step-by-step guide covers how to check your display manager, switch between alternatives, and fix the system if the gdm service fails to start.


Part 1: Managing Your Display Manager

Step 1: Check Which Display Manager is Running

To see what display manager your system is currently utilizing, open your terminal and run:

systemctl status display-manager

Common active options include: gdm3 (Default Ubuntu GNOME), lightdm (Lightweight alternative), or sddm (KDE Plasma).

Step 2: Switch to a Different Display Manager

If you have multiple display managers installed and want to swap between them, reconfigure the active package allocation array:

sudo dpkg-reconfigure gdm3

*(Note: Running this reconfiguration command on whichever manager is active will open up the exact same selection engine list).*

  1. A interactive configuration screen will open in your terminal. Use your arrow keys to select your preferred default manager and press Enter.
  2. Restart your computer to apply the visual changes.

Part 2: Troubleshooting an Inactive GDM Service

If your desktop won't load because GDM is inactive, immediately press Ctrl + Alt + F2 (or F3) on your keyboard to drop into a text-only TTY virtual terminal. Log in with your standard username and password, then execute the following fixes in order:

Fix 1: Force Enable and Start the Service

Sometimes GDM is simply turned off or disabled after a desktop environment modification. You can force it to run and ensure it persists across system reboots:

sudo systemctl enable gdm3 --now

Fix 2: Verify the Boot Target is Graphical

If your operating system configurations accidentally got modified to boot only into a text-only CLI mode, tell systemd to load the graphical desktop interface framework by default:

sudo systemctl set-default graphical.target

Fix 3: Clear Out Full Disk Space

GDM will silently crash and refuse to start if your storage drive has exhausted its capacity to write lockfiles or session metrics. Check your disk space status:

df -h

If the root file system partition (/) is at 100% capacity, free up space instantly by clearing cached system installation archives:

sudo apt clean
sudo apt autoremove

Fix 4: Purge and Reinstall GDM

If binary files or environment definitions became corrupted during a partial system upgrade, perform a clean reinstallation of the display components:

sudo apt update
sudo apt purge gdm3 -y
sudo apt install gdm3 ubuntu-desktop -y

Select gdm3 on the terminal interactive pop-up menu if prompted during this compilation phase.

Fix 5: Turn Off Wayland (For NVIDIA Users)

Proprietary NVIDIA graphics drivers frequently conflict with modern Wayland display protocols, crashing GDM before it can spin up. Revert to the older, stable Xorg standard instead:

  1. Open the core configuration display file:
    sudo nano /etc/gdm3/custom.conf
  2. Locate the commented line: #WaylandEnable=false
  3. Delete the leading hash symbol (#) to uncomment the string parameter.
  4. Save the changes (Ctrl + O, then Enter) and exit (Ctrl + X).

Final Step: Reboot

Once you have applied these standard fixes, restart your server platform to load your clean graphical login desktop environment:

sudo reboot

*************************************************************************************

HP All-In-One Ubuntu Server Display & Power Automation Guide

Running Linux on an All-In-One hardware setup or standalone server frame often presents a unique challenge: the integrated LCD monitor remains permanently illuminated even when operating completely headless or through an SSH session. This guide provides an automated shell utility to detect your exact kernel backlight hardware channels, map persistent control shortcuts, and deploy system cron schedules to manage your screen power states seamlessly.


Step 1: The Automated Power Configuration Script

This single-file shell utility automatically discovers your GPU interface panel directory under the sysfs framework, calculates the absolute panel limits, maps shell commands for your environment, and configures an energy automation lifecycle.

#!/bin/bash
# ==============================================================================
# HP ALL-IN-ONE UBUNTU SERVER DISPLAY CONFIGURATOR
# ==============================================================================

# Ensure script runs as root
if [ "$EUID" -ne 0 ]; then
 echo "[-] Please run this script with sudo: sudo ./setup_screen.sh"
 exit 1
fi

echo "[+] Starting automated display configuration..."

# 1. Detect Backlight Interface
BACKLIGHT_DIR="/sys/class/backlight"
INTERFACE=$(ls "$BACKLIGHT_DIR" | head -n 1)

if [ -z "$INTERFACE" ]; then
 echo "[-] Error: No backlight controller found in $BACKLIGHT_DIR"
 exit 1
fi

BACKLIGHT_PATH="$BACKLIGHT_DIR/$INTERFACE"
echo "[+] Detected backlight interface: $INTERFACE"

# 2. Get Max Brightness
MAX_BRIGHTNESS=$(cat "$BACKLIGHT_PATH/max_brightness")
echo "[+] Detected maximum panel brightness value: $MAX_BRIGHTNESS"

# 3. Configure Time Zone (Defaulting to Asia/Kolkata)
TARGET_TZ="Asia/Kolkata"
echo "[+] Setting system time zone to $TARGET_TZ..."
timedatectl set-timezone "$TARGET_TZ"
echo "[+] Time zone successfully updated to: $(timedatectl | grep 'Time zone')"

# 4. Inject Bash Aliases for the Calling User
REAL_USER=${SUDO_USER:-$USER}
REAL_USER_HOME=$(eval echo ~$REAL_USER)
BASHRC_PATH="$REAL_USER_HOME/.bashrc"

if [ -f "$BASHRC_PATH" ]; then
 echo "[+] Injecting shortcuts into $BASHRC_PATH..."

 # Remove existing matching aliases to prevent duplicate entries
 sed -i '/alias screenoff=/d' "$BASHRC_PATH"
 sed -i '/alias screenon=/d' "$BASHRC_PATH"

 # Append new aliases
 echo "alias screenoff='echo 0 | sudo tee $BACKLIGHT_PATH/brightness'" >> "$BASHRC_PATH"
 echo "alias screenon='echo \$(cat $BACKLIGHT_PATH/max_brightness) | sudo tee $BACKLIGHT_PATH/brightness'" >> "$BASHRC_PATH"

 # Fix ownership of the modified .bashrc file
 chown "$REAL_USER":"$REAL_USER" "$BASHRC_PATH"
 echo "[+] Shortcuts 'screenoff' and 'screenon' configured successfully."
else
 echo "[-] Warning: Could not locate .bashrc for user $REAL_USER. Skipping aliases."
fi

# 5. Configure Console Idle Blanking (1 minute)
echo "[+] Setting local console timeout to 1 minute..."
setterm --blank 1 --term linux < /dev/tty1 2>/dev/null || true

# 6. Install Root Cron Jobs for Automation
echo "[+] Scheduling daily power automation (Off at 22:00, On at 07:00)..."

# Build temporary crontab file
TMP_CRON=$(mktemp)

# Export existing cron jobs to avoid losing them
crontab -l > "$TMP_CRON" 2>/dev/null || true

# Strip out older brightness cron jobs to avoid duplicate entries
sed -i '\#/sys/class/backlight/#d' "$TMP_CRON"

# Append the new time rules using variables detected in step 1 & 2
echo "0 22 * * * echo 0 > $BACKLIGHT_PATH/brightness" >> "$TMP_CRON"
echo "0 7 * * * cat $BACKLIGHT_PATH/max_brightness > $BACKLIGHT_PATH/brightness" >> "$TMP_CRON"

# Apply the new crontab rules
crontab "$TMP_CRON"
rm "$TMP_CRON"

echo "[+] Automated cron jobs deployed successfully."
echo "=============================================================================="
echo "[+] SETUP COMPLETE!"
echo "[*] To activate your new manual commands immediately, run: source ~/.bashrc"
echo "[*] You can now use 'screenoff' and 'screenon' freely!"
echo "=============================================================================="

Step 2: Deployment & Initializing Commands

To execute this orchestration setup on your host framework, write the script text out to a local file, grant execution privileges, and run the configuration engine wrapper using administrative access:

# Create the script file framework
nano setup_screen.sh

# Apply binary runtime permissions
chmod +x setup_screen.sh

# Fire the automation process loop
sudo ./setup_screen.sh

# Source your profile to dynamically unlock your manual shortcuts
source ~/.bashrc

Step 3: Execution Logs & Verification Matrix

Upon clean execution, the setup module will track system state parameters and output detailed system validation logs matching this tracking profile:

[+] Starting automated display configuration...
[+] Detected backlight interface: intel_backlight
[+] Detected maximum panel brightness value: 937
[+] Setting system time zone to Asia/Kolkata...
[+] Time zone successfully updated to: Time zone: Asia/Kolkata (IST, +0530)
[+] Injecting shortcuts into /home/server1454/.bashrc...
[+] Shortcuts 'screenoff' and 'screenon' configured successfully.
[+] Setting local console timeout to 1 minute...
[+] Scheduling daily power automation (Off at 22:00, On at 07:00)...
[+] Automated cron jobs deployed successfully.
==============================================================================
[+] SETUP COMPLETE!
[*] To activate your new manual commands immediately, run: source ~/.bashrc
[*] You can now use 'screenoff' and 'screenon' freely!
==============================================================================

Step 4: Configure Automatic Hardware Idle Timeout (Optional)

To drop hardware energy expenditure automatically when you step away from the physical workspace, tie your hardware cutoffs straight to the native Linux terminal utility setterm. This forces the server frame to toggle an standard screen blanking configuration if there is 1 minute of complete local keyboard inactivity on the direct tty channel:

sudo bash -c 'setterm --blank 1 --term linux < /dev/tty1'

Once active, your shortcuts handle direct runtime checks, and your background engine manages overnight maintenance tasks automatically, saving power and extending the hardware life of your panel environment completely hands-free.

*******************************************************************

Securing Docker Python Apps Behind an Authenticated Nginx Reverse Proxy

When running custom web applications or dashboards inside Docker containers on a home server, they often bind directly to all network interfaces on unencrypted ports. This guide walks through locking down those services by restricting Docker ports to localhost and putting them behind an Nginx reverse proxy secured with HTTP Basic Authentication.

Step 1: Restrict Docker Port Bindings

By default, Docker maps ports to all interfaces (0.0.0.0). To prevent direct local network access, edit your container configuration to bind strictly to localhost (127.0.0.1).

# In your docker-compose.yml file:
ports:
  - "127.0.0.1:5000:5000"

Restart your container using Docker Compose:

docker compose down && docker compose up -d --build

Step 2: Install Nginx and Utilities

Log into your server and install Nginx along with apache2-utils, which provides the tool required to generate password hashes.

sudo apt update && sudo apt install nginx apache2-utils -y

Step 3: Create Authentication Credentials

Generate a secure password file and create an administrative user for accessing your proxy-protected applications.

sudo htpasswd -c /etc/nginx/.htpasswd admin

Step 4: Configure the Nginx Reverse Proxy

Create a new site configuration file inside Nginx to route external proxy ports to your internal localhost application ports while enforcing basic authentication.

sudo nano /etc/nginx/sites-available/python_proxy

Paste the following configuration blocks:

# Proxy for Dashboard Application
server {
    listen 9000;
    server_name _;

    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Proxy for Secondary Service / Report App
server {
    listen 9001;
    server_name _;

    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Step 5: Open Firewall Ports and Enable Config

Allow traffic through your new proxy ports on your system firewall, enable the site configuration, test the syntax, and restart Nginx.

sudo ufw allow 9000/tcp
sudo ufw allow 9001/tcp
sudo ln -s /etc/nginx/sites-available/python_proxy /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

Verification: Attempting to access your app directly on its old port should now result in a connection refused error. Accessing the new proxy ports (e.g., port 9000) will prompt you for your login credentials before securely passing traffic to your backend application.

*******************************************************************

Deploying a Docker Bookmark-Doc-app with an Authenticated Nginx Reverse Proxy

When running containerized web apps on a home server, services often expose themselves directly on local network ports without encryption. This guide outlines how to automate the deployment of a Dockerized application while locking down access using an Nginx reverse proxy protected by HTTP Basic Authentication.

Step 1: Automated Deployment Script

Instead of executing manual commands every time, you can use a deployment script that checks for system dependencies, sets up password protection, clones the repository, and boots the container architecture.

#!/bin/bash

# 1. Check and install Nginx & Apache utilities if not already present
if ! command -v nginx &> /dev/null; then
    echo "Nginx not found. Installing Nginx and apache2-utils..."
    sudo apt update
    sudo apt install nginx apache2-utils -y
else
    echo "Nginx is already installed. Skipping installation."
fi

# 2. Setup Nginx Password File if it doesn't exist
if [ ! -f /etc/nginx/.htpasswd ]; then
    echo "Creating Nginx basic auth password file..."
    sudo htpasswd -c /etc/nginx/.htpasswd admin
else
    echo "Nginx password file already exists. Skipping creation."
fi

# 3. Pull/Update the Application repository
sudo rm -rf Bookmark-Doc-app/
sudo git clone https://github.com/NASdonald5/Bookmark-Doc-app.git
cd Bookmark-Doc-app/

# 4. Spin up the Docker container
sudo docker compose up --build -d
cd ..

# 5. Setup Nginx Proxy Configuration if not already linked
if [ ! -f /etc/nginx/sites-available/python_proxy ]; then
    echo "Creating Nginx proxy configuration..."
    sudo bash -c 'cat > /etc/nginx/sites-available/python_proxy << "EOF"
# Proxy Configuration
server {
    listen 9000;
    server_name _;

    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
EOF'
fi

# 6. Enable site, open firewall ports, and restart Nginx
if [ ! -L /etc/nginx/sites-enabled/python_proxy ]; then
    sudo ln -s /etc/nginx/sites-available/python_proxy /etc/nginx/sites-enabled/
fi

sudo ufw allow 9000/tcp
sudo nginx -t
sudo systemctl restart nginx

echo "Deployment and Nginx reverse proxy configuration complete!"

Step 2: Securing Docker Port Bindings

To prevent raw network access to your internal container application, ensure your docker-compose.yml file binds explicitly to localhost rather than exposing itself globally:

ports:
  - "127.0.0.1:5000:5000"

Security Note: By routing external requests through Nginx ports (like port 9000) with Basic Authentication enabled, users must successfully provide credentials before reaching the local container environment.

***##******##******##******##******##***

Self-Hosting Navidrome: Streaming from an External NAS

If you have a massive library of local MP3s or FLAC files, Navidrome is arguably the best self-hosted music server available. It is incredibly lightweight, lightning-fast, and compatible with almost all Subsonic clients.

While you can store your music locally, most homelabbers keep their massive media libraries on a Network Attached Storage (NAS) device. In this guide, we will deploy Navidrome using Docker Compose, mount an external NAS folder for our music, and safely store our database in our home directory.


Step 1: Prepare Your Directories

Navidrome needs two main folders to work properly: one to store its internal database (which should be on fast, local storage), and one where your actual music files live (which will be our NAS).

Open your host terminal and create the local data folder in your home directory:

# Create the Navidrome data folder
mkdir -p /home/<YOUR_USERNAME>/navidrome_data

# Ensure proper permissions (Navidrome runs as user 1000 by default)
sudo chown -R 1000:1000 /home/<YOUR_USERNAME>/navidrome_data

Note: This guide assumes you have already mounted your external NAS to your Linux machine at a path like /mnt/external/NAS/Music.

Step 2: Create the Docker Compose File

Navigate to your preferred Docker directory and create a docker-compose.yml file. If you are using Portainer, you can simply paste this directly into a new Stack.

services:
  navidrome:
    image: deluan/navidrome:latest
    user: 1000:1000 # Matches the local permissions
    ports:
      - "4533:4533"
    restart: always
    environment:
      ND_LOGLEVEL: info 
      ND_SCANSCHEDULE: "@every 1h"
      ND_ENABLEDOWNLOADS: "true" # Allows downloading files from the web UI
    volumes:
      # Local fast storage for the database
      - "/home/<YOUR_USERNAME>/navidrome_data:/data"
      # External NAS mount for the actual music
      - "/mnt/external/NAS/SharedMusic:/music:ro"
Security Tip: Notice the :ro at the end of the music volume mount. This mounts your NAS music folder as "Read-Only." This ensures that even if you accidentally delete a song in the Navidrome app, your raw audio files on the NAS are completely safe!

Step 3: Deploy the Container

With your Compose file ready, spin up the server by running:

sudo docker compose up -d

Docker will pull the latest Navidrome image and start the container in the background.

Step 4: Initial Setup & Admin Account

Navidrome is now running locally on port 4533, and it will immediately begin scanning your NAS library based on your ND_SCANSCHEDULE!

  1. Open a web browser and navigate to http://<YOUR_SERVER_IP>:4533
  2. You will be greeted by the initial setup screen. Create a strong admin username and password.

Crucial Warning: Do not use "admin" or "root" as your username. If you plan to expose this to the internet later via a reverse proxy or Tailscale Funnel, automated bots will guess those usernames immediately.

Your personal Spotify alternative is now fully operational and securely reading from your NAS! In upcoming guides, we will cover how to safely expose this to the public internet using a Tailscale Funnel and a Home Assistant kill switch.

Securely Expose Navidrome with a Home Assistant Kill Switch via Tailscale Funnel

Self-hosting Navidrome is fantastic until you want to listen to your music on a strict work computer where you can't install Tailscale. The solution? Tailscale Funnel. It allows you to expose a specific local port to the public internet securely, complete with an automatic SSL certificate.

But leaving a public tunnel open permanently isn't ideal. In this guide, we will build a physical "Kill Switch" dashboard button in Home Assistant that toggles the public gateway on and off instantly. Let's bridge the gap between your Dockerized Home Assistant and your host machine.


Step 1: Allow "Silent Sudo" on the Host

Home Assistant will need to execute Tailscale commands on the host machine without being prompted for a password.

  1. SSH into your Host Machine.
  2. Open the sudoers file securely:
sudo visudo

Add this line to the very bottom (replace <YOUR_USERNAME> with your actual Linux user):

<YOUR_USERNAME> ALL=(ALL) NOPASSWD: /usr/bin/tailscale

Step 2: Find Your True Docker Config Path

Because Home Assistant runs in Docker, it cannot access the host's root file system. We must store our SSH keys directly inside Home Assistant's mapped volume. First, find out exactly where that is on your host:

sudo docker inspect -f '{{ range .Mounts }}{{ .Source }} -> {{ .Destination }}{{ "\n" }}{{ end }}' homeassistant

Look for the path pointing to -> /config. For this guide, we will assume it is /path/to/hass/config.

Step 3: Generate and Authorize SSH Keys

Run these commands on your host machine to generate keys directly into the Docker mapped folder, allowing the container to SSH into the host.

# 1. Create the .ssh folder in the mapped directory
sudo mkdir -p /path/to/hass/config/.ssh

# 2. Generate a passwordless SSH key
sudo ssh-keygen -t rsa -f /path/to/hass/config/.ssh/id_rsa -q -N ""

# 3. Copy the key to the host's authorized keys (requires your password once)
sudo ssh-copy-id -i /path/to/hass/config/.ssh/id_rsa <YOUR_USERNAME>@<YOUR_HOST_IP>

# 4. Lock down permissions so Docker can use them
sudo chmod 700 /path/to/hass/config/.ssh
sudo chmod 600 /path/to/hass/config/.ssh/id_rsa
Docker Context Tip: Even though the keys are at /path/to/hass/config/.ssh/id_rsa on the host, inside the configuration.yaml file, Home Assistant will simply see them at /config/.ssh/id_rsa.

Step 4: The Home Assistant YAML Configuration

Open your Home Assistant configuration.yaml file. Add the following command_line block.

Note: The -q (quiet) flag and UserKnownHostsFile=/dev/null are crucial. Without them, Home Assistant will misinterpret SSH warnings as failed commands and the switch will not function.

command_line:
  - switch:
      name: "Navidrome Public Tunnel"
      unique_id: navidrome_public_tunnel
      # The command to turn it ON in the background
      command_on: "ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /config/.ssh/id_rsa <YOUR_USERNAME>@<YOUR_HOST_IP> 'sudo tailscale funnel --bg 4533'"
      
      # The command to turn it OFF and wipe the config
      command_off: "ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /config/.ssh/id_rsa <YOUR_USERNAME>@<YOUR_HOST_IP> 'sudo tailscale serve reset'"
      
      # The command to check if it is running (updates UI button state)
      command_state: "ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /config/.ssh/id_rsa <YOUR_USERNAME>@<YOUR_HOST_IP> 'sudo tailscale funnel status | grep 4533'"
      
      icon: mdi:music-network

Step 5: Restart and Add to Dashboard

  1. Since we added a new command_line integration, force reboot your Home Assistant container to read the new file:
    sudo docker restart homeassistant
  2. Wait 60 seconds, then navigate to your Home Assistant dashboard.
  3. Edit the dashboard, add a Button Card, and assign it to switch.navidrome_public_tunnel.

Step 6: First-Run Authorization

Before the switch will work perfectly from your dashboard, Tailscale requires explicit permission to open a funnel on your node for the very first time. You can trigger this authorization link by manually running the SSH command from your host terminal:

user@host:~$ sudo docker exec -it homeassistant ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /config/.ssh/id_rsa <YOUR_USERNAME>@<YOUR_HOST_IP> "sudo tailscale funnel --bg 4533"

Warning: Permanently added '<YOUR_HOST_IP>' (ED25519) to the list of known hosts.

Funnel is not enabled on your tailnet.
To enable, visit:

         https://login.tailscale.com/f/funnel?node=[YOUR_NODE_ID]

Success.
Another client is changing the serve config; please try again.
sending serve config: Preconditions failed: etag mismatch

Copy the authorization link provided in your terminal output and paste it into your browser to approve the funnel. Once approved, you can verify the tunnel is active by checking the status:

user@host:~$ sudo tailscale funnel status

# Funnel on:
#     - https://your-machine.tailnet-name.ts.net

https://your-machine.tailnet-name.ts.net (Funnel on)
|-- / proxy http://127.0.0.1:4533

The Final Result

When you tap the button on your Home Assistant dashboard, it will silently SSH into your host and open the Funnel. The button will glow, and your Navidrome instance will be securely accessible to the world at your Tailnet URL.

Done listening? Tap the button again to instantly slam the firewall door shut. Ultimate homelab convenience meets security!

***##******##******##******##******##***

Automating the Reverse Proxy: Telegram Controls & Safety Timers

Once you have a working Home Assistant kill switch for your Tailscale Funnel, the next logical step is remote management. What if you expose your music server to the web, but forget to close the tunnel? What if you want to shut it down from your phone without opening the Home Assistant app?

In this guide, we will build a two-way Telegram alert system with an interactive inline button, backed by a 2-hour "dead man's switch" to guarantee your homelab stays secure.


Step 1: The Sender Automation (Telegram Alert + Button)

This automation watches the tunnel switch. When the tunnel opens, it sends a Telegram alert containing an interactive button. When the tunnel closes, it sends a standard confirmation message.

In Home Assistant, create a new automation, edit it in YAML, and paste the following:

alias: "Security: Navidrome Tunnel Status Alert"
description: "Sends a Telegram message with an interactive close button."
mode: single
trigger:
  - platform: state
    entity_id: switch.navidrome_public_tunnel
    not_to:
      - "unknown"
      - "unavailable"
    not_from:
      - "unknown"
      - "unavailable"
action:
  - choose:
      - conditions:
          - condition: state
            entity_id: switch.navidrome_public_tunnel
            state: "on"
        sequence:
          - action: telegram_bot.send_message
            data:
              message: |
                🟢 Navidrome Tunnel: OPEN
                Your music server is now exposed to the public web.
              parse_mode: html
              inline_keyboard:
                - "🔒 Close Tunnel Now:/close_tunnel"
    default:
      - action: telegram_bot.send_message
        data:
          message: |
            🔴 Navidrome Tunnel: CLOSED
            The Tailscale Funnel has been shut down. Public access is fully blocked.
          parse_mode: html
YAML Formatting Note: Notice the inline_keyboard syntax formatting: "Button Text:/callback_command". This shorthand is specifically required by Home Assistant to generate a clickable button rather than plain text.

Step 2: The Listener Automation (Catching the Button Press)

When you tap the button in Telegram, it sends a hidden /close_tunnel event back to Home Assistant. This second automation acts as a listener: it catches that specific event, turns off the tunnel, and rewrites the original chat message to confirm the action.

alias: "Security: Telegram Close Tunnel Callback"
description: "Listens for the inline button press from Telegram."
mode: single
trigger:
  - platform: event
    event_type: telegram_callback
    event_data:
      data: "/close_tunnel"
action:
  # 1. Stops the loading spinner on the Telegram button
  - action: telegram_bot.answer_callback_query
    data:
      callback_query_id: "{{ trigger.event.data.id }}"
      message: "Closing tunnel..."
      
  # 2. Flips the physical switch to OFF
  - action: switch.turn_off
    target:
      entity_id: switch.navidrome_public_tunnel
      
  # 3. Edits the original Telegram message to remove the button
  - action: telegram_bot.edit_message
    data:
      message_id: "{{ trigger.event.data.message.message_id }}"
      chat_id: "{{ trigger.event.data.user_id }}"
      message: |
        🔴 Navidrome Tunnel: CLOSED via Telegram
        The Tailscale Funnel has been shut down remotely.
      parse_mode: html
      inline_keyboard: []

Step 3: The 2-Hour Auto-Shutoff Timer

Never rely on a simple delay action for security timers; if Home Assistant restarts, the delay is wiped from memory. Instead, use a State Duration Trigger. This constantly checks the clock and ensures the tunnel closes exactly 2 hours after it opens, even if your server reboots in the meantime.

alias: "Security: Auto-Close Navidrome Tunnel (2 Hours)"
description: "Automatically turns off the Tailscale funnel if left open for 2 hours."
mode: single
trigger:
  - platform: state
    entity_id: switch.navidrome_public_tunnel
    to: "on"
    for:
      hours: 2
      minutes: 0
      seconds: 0
action:
  - action: switch.turn_off
    target:
      entity_id: switch.navidrome_public_tunnel

How It All Comes Together

You now have a fully self-healing security perimeter. If you expose your server and forget about it, the 2-hour timer will automatically turn the switch off. The moment that switch flips, the Sender automation instantly detects the state change and messages you a 🔴 "CLOSED" confirmation on Telegram. Complete peace of mind!

***##******##******##******##******##***

Automating SSH Logins and Building a Windows Terminal Homelab Dashboard

powershell4plane

Managing multiple Linux servers, Proxmox nodes, or Raspberry Pis usually involves typing endless IP addresses and passwords. If you want to streamline your homelab management, you can completely automate your remote access from a Windows PC using cryptographic SSH keys, custom aliases, and advanced Windows Terminal configurations.

In this guide, we will eliminate passwords entirely and build a unified, 1-click split-screen dashboard to monitor your entire homelab at once.


Step 1: Generate a Secure SSH Key Pair

First, we need to replace password authentication with a cryptographic key. Open Windows PowerShell on your local machine and run:

When you type the command, you are simply telling your computer how to build the lock and key:

ssh-keygen: The program that creates the key pair.

-t: Stands for "type" (as in, what type of key).

ed25519: The specific mathematical formula to use (an Elliptic Curve algorithm).

ssh-keygen -t ed25519

Press Enter to accept the default file location, and press Enter twice to leave the passphrase empty. (Leaving it empty is required to enable fully automated, 1-click background logins).

Step 2: Copy the Public Key to Your Servers

Windows does not have the native ssh-copy-id command found on Linux. Instead, you can use PowerShell to pipe your newly generated public key directly into your remote server's authorized keys file. Run this for each server you want to manage:

Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | ssh <YOUR_USERNAME>@<YOUR_SERVER_IP> "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

You will be prompted to enter your server's password one final time. Once accepted, the key is securely lodged on the server.

Step 3: Create the Windows SSH Config File

Instead of typing ssh user@192.168.x.x every time, we can create custom, memorable aliases (like ssh server01).

We will use PowerShell to create the configuration file properly, bypassing Notepad to ensure Windows doesn't accidentally append a hidden .txt extension that breaks the SSH client.

@"
Host server01
    HostName <SERVER_01_IP>
    User <USERNAME_01>
    IdentityFile ~/.ssh/id_ed25519

Host server02
    HostName <SERVER_02_IP>
    User <USERNAME_02>
    IdentityFile ~/.ssh/id_ed25519

Host server03
    HostName <SERVER_03_IP>
    User <USERNAME_03>
    IdentityFile ~/.ssh/id_ed25519
"@ | Out-File -FilePath $env:USERPROFILE\.ssh\config -Encoding ascii -NoNewline
Troubleshooting Note: If you ever try to edit this file in Notepad and the aliases suddenly stop working, check if Notepad saved it as config.txt. You can fix this by running Rename-Item $env:USERPROFILE\.ssh\config.txt config in PowerShell.

Step 4: Add Dropdown Profiles to Windows Terminal

Now that the automated aliases work, let's embed them into the modern Windows Terminal.

  1. Open Windows Terminal.
  2. Click the downward arrow (˅) in the title bar and select Settings.
  3. Click the gear icon (Open JSON file) in the bottom-left corner.

Find the "profiles": { "list": [ section and paste your server blocks right after the opening bracket:

        {
            "name": "Primary Node",
            "commandline": "ssh server01",
            "icon": "💻",
            "hidden": false
        },
        {
            "name": "Storage NAS",
            "commandline": "ssh server02",
            "icon": "🖧",
            "hidden": false
        },

Save the file. Your servers will instantly appear in the Terminal's dropdown menu with custom icons, ready to launch with a single click.

Step 5: Build a Split-Screen Homelab Dashboard

For the ultimate command center, we can tell Windows Terminal to launch a grid of all our servers simultaneously. In the same JSON settings file, scroll down to the "actions": [ section and paste this block:

        {
            "name": "Launch Homelab Dashboard",
            "command": {
                "action": "multipleActions",
                "actions": [
                    { "action": "newTab", "commandline": "ssh server01" },
                    { "action": "splitPane", "split": "right", "commandline": "ssh server02" },
                    { "action": "splitPane", "split": "down", "commandline": "ssh server03" },
                    { "action": "moveFocus", "direction": "left" },
                    { "action": "splitPane", "split": "down", "commandline": "ssh server04" }
                ]
            }
        },

To use this, open Windows Terminal, press Ctrl + Shift + P to open the Command Palette, and type Launch Homelab Dashboard. The terminal will instantly snap into a 2x2 grid, firing off four passwordless SSH sessions simultaneously, logging you into your entire environment in less than a second.

***##******##******##******##******##***

⚡ The Mini PC Home Lab Power Optimization Guide: How to Get True VM & Host Telemetry on Proxmox VE

Mini PC Home Lab Power Optimization

If you are running a Proxmox VE home lab on a modern Mini PC (like an Intel N100 or AMD Ryzen box), saving power is a top priority. However, out-of-the-box settings often hide hardware telemetry, and virtual machines are usually blind to how many Watts they actually use.

This guide outlines the exact, step-by-step process used to force low-power sleep states, safely read raw CPU package power, and use virtual machine profiling to track per-VM power usage down to the milliwatt.


🛠️ Step 1: Force PCIe Deeper Sleep via AutoASPM

Linux kernels frequently disable Active State Power Management (ASPM) if a motherboard's BIOS/UEFI firmware fails to report it correctly. This causes your hardware links (like NVMe SSDs and Network cards) to sit at 100% full throttle even at idle.

Run this directly on the physical Proxmox Host (root@pve):

  1. Clone the open-source AutoASPM utility repository:
    git clone https://github.com/K0rnh0li0/autoaspm.git
  2. Navigate into the package directory:
    cd autoaspm/pkgs/
  3. Run the script as root to force all supported hardware channels into deep power-saving states (L0s and L1):
    python3 autoaspm.py

🤖 Step 2: Make AutoASPM Persistent on Boot

Because hardware link states reset whenever the computer restarts, you must build a system background service to apply the power savings every single time the host boots up.

  1. Create a clean systemd service configuration file:
    nano /etc/systemd/system/autoaspm.service
  2. Paste the following configuration script:
    [Unit]
    Description=AutoASPM PCIe Power Saving Script
    After=network.target
    
    [Service]
    Type=oneshot
    ExecStart=/usr/bin/python3 /root/autoaspm/pkgs/autoaspm.py
    RemainAfterExit=yes
    
    [Install]
    WantedBy=multi-user.target
  3. Save the file (Ctrl + O, Enter), exit nano (Ctrl + X), and activate the service:
    systemctl daemon-reload
    systemctl enable autoaspm.service
    systemctl start autoaspm.service

🖥️ Step 3: Expose Physical CPU Sensors to the VM

By default, virtualization engines use generic emulated CPUs (like the 1996 Standard-PC i440FX). Because the physical microarchitecture is hidden, hardware diagnostic utilities like turbostat will crash inside the VM with an Unsupported platform detected or msr offset read failed error.

To bypass this isolation, you must swap your VM configuration to CPU Passthrough Mode.

  1. Identify your exact 3-digit VM ID number using the Proxmox list control command:
    qm list
  2. Set the processor model to mimic the real physical host chip (replace 101 with your VM ID):
    qm set 101 --cpu host
  3. Boot back into your virtual machine (reboot) to allow it to recognize the native CPU instructions.

📊 Step 4: Measure Total Host Power via turbostat

With the boundaries cleared, you can read the precise live CPU energy consumption registers directly from the bare-metal hardware.

  1. Since Proxmox uses a custom Debian-derived kernel track, install the specific kernel-tied utilities:
    apt update && apt install linux-cpupower
  2. Launch the real-time reporting loop on the Proxmox Host:
    turbostat --Summary --quiet --interval 2 --hide IPC

What to check: Look at the far right of the layout grid. The PkgWatt column shows the complete CPU package power, CorWatt displays compute core active use, and GFXWatt shows integrated graphics.

Note: Thanks to Step 1, a healthy idle system will reveal the CPU package cleanly floating around a low 4W – 6W scale while spending over 50% of its runtime resting in deep package C7 sleep states.

🔍 Step 5: Profile Individual VM Wattage via Scaphandre

Even with CPU passthrough active, hypervisors block guest virtual machines from directly touching physical power limit registers for stability and safety reasons. To figure out what a specific VM is costing you, you must calculate it from the host using Scaphandre.

  1. Download the latest release from the Scaphandre GitHub (check their repo for the most current version tag if needed):
    wget https://github.com/hubblo-org/scaphandre/releases/download/v1.0.0/scaphandre_1.0.0-1_amd64.deb
  2. Install the package manually:
    dpkg -i scaphandre_1.0.0-1_amd64.deb
  3. Launch the live terminal profiling tool:
    scaphandre stdout -t 5
  4. Open a second terminal layout window to map the generic system process numbers back to human-readable Proxmox virtual machine tags:
    ps aux | grep qemu | grep -E "id 101|MintLinx"

The Payoff: Locate the matching PID number (Process ID) inside the live scaphandre list view. You will now see the exact mathematical slice of power that single VM is responsible for drawing from the chip (e.g., an idle VM pulling a tiny 0.032 Watts).


If you want to continue optimizing this setup, let me know if you would like to:

  • Configure powertop --auto-tune to push the Mini PC's power brick efficiency even further.
  • Export these live metrics into an InfluxDB or Grafana dashboard to visualize your home lab's electrical footprint over time.

***##******##******##******##******##***

⚡ Complete Home Lab Power & Telemetry Master Guide: Proxmox, Scaphandre, Prometheus, & Grafana

Home Lab Power & Telemetry 

A complete, consolidated master guide incorporating all installation steps, hardware optimizations, network isolation bridging, multi-node configuration for x86 and Raspberry Pi, and essential troubleshooting steps for building a comprehensive power and telemetry monitoring stack.

1. Host Power Optimization & CPU Passthrough

Before configuring telemetry, ensure your physical host hardware states are optimized and guest virtual machines are capable of exposing hardware performance counters.

  • Enable CPU Passthrough: Ensure your VMs mimic the physical host processor instructions rather than standard emulated hardware:
    qm set <VM_ID> --cpu host
  • Set Energy-Efficient CPU Governors: Apply the powersave frequency scaling governor across all host cores:
    echo "powersave" | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

2. Installing Scaphandre on Proxmox Host

Install Scaphandre on the bare-metal hypervisor to capture hardware package power consumption registers:

wget https://github.com/hubblo-org/scaphandre/releases/download/v1.0.2/scaphandre_v1.0.2-deb12_amd64.deb
sudo dpkg -i scaphandre_v1.0.2-deb12_amd64.deb

Create a persistent systemd service file to keep the exporter running continuously on port 8081:

sudo nano /etc/systemd/system/scaphandre.service

Paste the standard systemd configuration:

[Unit]
Description=Scaphandre Prometheus Exporter
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/scaphandre prometheus -p 8081
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Reload systemd and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now scaphandre.service

3. Troubleshooting Network Isolation via SSH Tunnel

The Issue: If your Prometheus monitoring instance runs inside a guest VM while Scaphandre executes on the physical Proxmox host, direct external binding requests can fail with connection refused or routing errors.

The Solution: Bridge the isolated subnet by configuring an automated local port-forwarding SSH tunnel managed by systemd.

  1. Generate and copy SSH keys from the monitoring VM to the physical Proxmox host to allow passwordless execution:
    ssh-keygen -t rsa -b 4096
    ssh-copy-id root@<PROXMOX_HOST_IP>
  2. Create the tunnel service configuration file on your monitoring node:
    sudo nano /etc/systemd/system/scaphandre-tunnel.service
  3. Ensure your service definition contains no stray syntax errors on line 1 and maps local port 8081 securely:
    [Unit]
    Description=SSH Tunnel to Proxmox Scaphandre
    After=network.target ssh.service
    
    [Service]
    User=vm-user
    ExecStart=/usr/bin/ssh -N -T -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -L 8081:localhost:8081 root@<PROXMOX_HOST_IP>
    Restart=always
    RestartSec=10
    
    [Install]
    WantedBy=multi-user.target
  4. Enable and start the tunnel:
    sudo systemctl daemon-reload
    sudo systemctl enable --now scaphandre-tunnel.service
  5. Verify local metrics response:
    curl http://localhost:8081/metrics

4. Expanding Telemetry to Remote x86 Servers & Raspberry Pi

  • Remote x86 Servers (Tailscale): Install Scaphandre using the standard Debian package, start the service on port 8081, and verify reachability across your secure mesh network IP using netcat or curl.
  • Raspberry Pi (ARM Architecture): Because ARM CPUs lack x86 RAPL power registers, Scaphandre is incompatible. Instead, install standard system metrics tracking via Node Exporter:
    sudo apt update && sudo apt install prometheus-node-exporter -y

5. Configuring Prometheus Scrape Targets

Open your central Prometheus configuration file:

sudo nano /etc/prometheus/prometheus.yml

Consolidate your multi-node scrape targets:

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'scaphandre_proxmox'
    scrape_interval: 15s
    static_configs:
      - targets: ['localhost:8081']

  - job_name: 'scaphandre_server1454'
    scrape_interval: 15s
    static_configs:
      - targets: ['<TAILSCALE_IP>:8081']

  - job_name: 'node_pi'
    scrape_interval: 15s
    static_configs:
      - targets: ['<RASPI_IP>:9100']

Validate your YAML file syntax using promtool before restarting the monitoring engine:

promtool check config /etc/prometheus/prometheus.yml
sudo systemctl restart prometheus

6. Unified Grafana Dashboard Configuration

Import the production-ready dashboard specification JSON into Grafana to render real-time time-series streams for host package power and granular virtual machine metrics:

{
  "annotations": { "list": [] },
  "editable": true,
  "panels": [
    {
      "id": 1,
      "title": "Total Physical Host Power",
      "type": "timeseries",
      "targets": [
        {
          "datasource": { "type": "prometheus", "uid": "YOUR_DS_UID" },
          "expr": "scaph_host_power_microwatts / 1000000",
          "legendFormat": "Host Package Power"
        }
      ],
      "fieldConfig": { "defaults": { "unit": "watt" } }
    },
    {
      "id": 2,
      "title": "Top 5 Most Power-Hungry VMs",
      "type": "timeseries",
      "targets": [
        {
          "datasource": { "type": "prometheus", "uid": "YOUR_DS_UID" },
          "expr": "topk(5, scaph_process_power_consumption_microwatts / 1000000)",
          "legendFormat": "PID: {{pid}}"
        }
      ],
      "fieldConfig": { "defaults": { "unit": "watt" } }
    }
  ],
  "time": { "from": "now-15m", "to": "now" },
  "title": "Multi-Server Power Monitoring"
}

7. Taming VM Insomnia: 4 Ways to Slash Idle Power Draw

Virtual machines often suffer from background polling and virtual hardware interrupts that force the physical host CPU to wake up thousands of times per second.

  1. Strip Away Unnecessary Virtual Hardware: Remove unused audio controllers, USB hubs, and SPICE display enhancements in the Proxmox GUI, switching the display to Serial terminal 0.
  2. Tame Docker Container Polling: Increase scan intervals in heavy apps like NetAlertX and adjust file integrity monitoring frequencies in Wazuh agents to prevent constant CPU wakeups.
  3. Run PowerTOP Auto-Tune Inside the VM: Leverage host CPU passthrough by installing PowerTOP and applying autosuspend tunables:
    sudo apt update && sudo apt install powertop -y
    sudo powertop --auto-tune
  4. Enable the QEMU Guest Agent: Ensure clean power state communication between the host and guest OS:
    sudo apt install qemu-guest-agent -y
    sudo systemctl enable --now qemu-guest-agent

The Verification Step: Once you have applied these configuration steps and tuning parameters, check your Prometheus targets page to ensure all nodes are green, and verify your Grafana dashboard to watch your host and VM power footprints settle into deep energy-efficient states!

***##******##******##******##******##***

⚡ The Prometheus & Alertmanager Telegram Integration Guide: Automated Home Lab Alerts & Recovery Notifications

Monitoring system health manually is inefficient. By leveraging Prometheus rule evaluations and Alertmanager webhooks, you can configure your home lab nodes to automatically detect resource bottlenecks, hardware issues, or offline services and push real-time HTML alerts directly to your Telegram mobile app.

This comprehensive guide details the complete end-to-end setup: writing custom Prometheus alert rules, installing and configuring Alertmanager, connecting a Telegram bot via API, and enabling automated "Resolved" recovery states.


🛠️ Step 1: Create Prometheus Alert Rules

Prometheus handles the detection of system anomalies by evaluating background PromQL expressions. We will set up a dedicated rules file to watch for offline nodes, high CPU load, critical memory exhaustion, low storage space, and high power usage[cite: 3].

  1. Create a new alert rules file on your monitoring node[cite: 3]:
    sudo nano /etc/prometheus/alert.rules.yml
  2. Paste the essential lab alert definitions into the file[cite: 3]:
    groups:
      - name: homelab_alerts
        rules:
          - alert: InstanceDown
            expr: up == 0
            for: 3m
            labels:
              severity: critical
            annotations:
              summary: "Host {{ $labels.instance }} is down"
              description: "Prometheus could not scrape {{ $labels.job }} for more than 3 minutes."
    
          - alert: HighCpuLoad
            expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
            for: 5m
            labels:
              severity: warning
            annotations:
              summary: "High CPU usage on {{ $labels.instance }}"
              description: "CPU load is > 90% for 5 minutes."
    
          - alert: HighMemoryUsage
            expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 90
            for: 5m
            labels:
              severity: warning
            annotations:
              summary: "High memory usage on {{ $labels.instance }}"
              description: "Memory usage is above 90%."
    
          - alert: DiskSpaceRunningOut
            expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|squashfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|squashfs"}) * 100 < 10
            for: 5m
            labels:
              severity: critical
            annotations:
              summary: "Low disk space on {{ $labels.instance }}"
              description: "Less than 10% disk space left on a partition."
    
          - alert: HighPowerDraw
            expr: (scaph_host_power_microwatts / 1000000) > 40
            for: 10m
            labels:
              severity: warning
            annotations:
              summary: "High power consumption on {{ $labels.instance }}"
              description: "Host is consistently drawing over 40 Watts."
  3. Register the rules file inside your main Prometheus configuration file (/etc/prometheus/prometheus.yml) under the rule_files: block[cite: 3]:
    rule_files:
      - "/etc/prometheus/alert.rules.yml"
  4. Validate and restart Prometheus to load the new rules[cite: 3]:
    promtool check config /etc/prometheus/prometheus.yml
    sudo systemctl restart prometheus

🤖 Step 2: Set Up Your Telegram Bot

To deliver automated notification messages to your phone, you must create a dedicated Telegram bot and capture its access keys.

  1. Open Telegram and search for the official @BotFather account[cite: 3].
  2. Send the command /newbot and complete the prompts to pick a display name[cite: 3].
  3. Copy the provided HTTP API Token (which includes your secure colon-separated token string)[cite: 3].
  4. Open a direct chat window with your new bot and send any initial test message (e.g., "hello") to activate the channel[cite: 3].
  5. Find your unique user chat ID by messaging the @userinfobot account on Telegram[cite: 3].

📦 Step 3: Install and Configure Alertmanager

While Prometheus detects when a threshold is breached, the companion service Alertmanager handles routing, formatting, and dispatching those notifications externally[cite: 3].

  1. Download and extract the Alertmanager binary package on your monitoring node[cite: 3]:
    wget https://github.com/prometheus/alertmanager/releases/download/v0.27.0/alertmanager-0.27.0.linux-amd64.tar.gz
    tar xvfz alertmanager-0.27.0.linux-amd64.tar.gz
  2. Move the binaries to system paths and set up a non-login background user[cite: 3]:
    sudo cp alertmanager-0.27.0.linux-amd64/alertmanager /usr/local/bin/
    sudo cp alertmanager-0.27.0.linux-amd64/amtool /usr/local/bin/
    sudo useradd -M -r -s /bin/false alertmanager
    sudo mkdir -p /etc/alertmanager /var/lib/alertmanager
    sudo chown -R alertmanager:alertmanager /etc/alertmanager /var/lib/alertmanager
  3. Create the Alertmanager routing configuration file[cite: 3]:
    sudo nano /etc/alertmanager/alertmanager.yml
  4. Paste the configuration block below, making sure to replace placeholder credentials with your actual bot_token and numeric chat_id, while enabling send_resolved: true for recovery hooks[cite: 3]:
    route:
      group_by: ['alertname', 'instance']
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 3h
      receiver: 'telegram-bot'
    
    receivers:
      - name: 'telegram-bot'
        telegram_configs:
          - send_resolved: true
            bot_token: 'YOUR_FULL_SECRET_BOT_TOKEN_HERE'
            chat_id: YOUR_CHAT_ID_INTEGER_HERE
            parse_mode: 'HTML'
            message: |-
              {{ if eq .Status "firing" }}🔴 FIRING{{ else }}🟢 RESOLVED{{ end }}: {{ .CommonLabels.alertname }}
              Host: {{ .CommonLabels.instance }}
              Details: {{ .CommonAnnotations.description }}
  5. Secure file ownership and set up the systemd background daemon service (/etc/systemd/system/alertmanager.service)[cite: 3]:
    sudo chown alertmanager:alertmanager /etc/alertmanager/alertmanager.yml
    sudo systemctl daemon-reload
    sudo systemctl enable --now alertmanager.service
  6. Link Prometheus to Alertmanager by adding the routing target block inside /etc/prometheus/prometheus.yml[cite: 3]:
    alerting:
      alertmanagers:
        - static_configs:
            - targets: ['localhost:9093']
  7. Restart Prometheus to complete the chain[cite: 3]:
    sudo systemctl restart prometheus

🧪 Step 4: Test Your Alert Pipeline

You can verify that your end-to-end alert routing is completely functional using quick simulated outages.

  1. Direct API Test: Push an instant payload using curl to verify Telegram bot syntax without waiting for real-world thresholds[cite: 3]:
    curl -H "Content-Type: application/json" -d '[
      {
        "labels": {
          "alertname": "TestTelegramConnection",
          "instance": "Homelab-Dashboard",
          "severity": "info"
        },
        "annotations": {
          "description": "If you are reading this in Telegram, Alertmanager routing is working perfectly!"
        }
      }
    ]' http://localhost:9093/api/v1/alerts
  2. Real-World Simulation: Stop a background target service on one of your nodes (e.g., node exporter) to see the live 🔴 FIRING alert trigger on your phone after the 3-minute evaluation delay[cite: 3]:
    sudo systemctl stop prometheus-node-exporter
  3. Start the service back up to ensure a clean green recovery notification is received[cite: 3]:
    sudo systemctl start prometheus-node-exporter

Success: Your architecture is now fully operational! Prometheus monitors your infrastructure states in the background, Alertmanager processes the triggered states, and custom HTML push alerts keep you instantly informed of both system faults and recoveries on your mobile device[cite: 3].

***##******##******##******##******##***

🐳 Docker Container Metrics & Monitoring Guide: Setting Up cAdvisor and Prometheus

Setting Up cAdvisor and Prometheus

Monitoring containerized environments effectively requires collecting granular performance telemetry like CPU, memory, and disk usage per individual service. This guide outlines the steps required to deploy cAdvisor, resolve common port binding and volume permission conflicts, configure Prometheus scraping targets, and query container names in Grafana.


🛠️ Step 1: Deploying cAdvisor & Resolving Port Conflicts

If port 8080 is already occupied by background services on your host machine, you can map cAdvisor to an alternative open port, such as 8082. Proper volume mounts and read-write socket access ensure that cAdvisor successfully communicates with the Docker daemon to capture container metadata labels.

Deploy cAdvisor using a Docker Compose configuration:

version: '3.2'
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - 8082:8080
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    depends_on:
      - redis
  redis:
    image: redis:latest
    container_name: redis
    ports:
      - 6379:6379

📊 Step 2: Configuring Prometheus Scrape Targets

To pull performance metrics from cAdvisor into your monitoring database, you need to add it as a static scrape target in your Prometheus configuration file (/etc/prometheus/prometheus.yml).

  1. Open the configuration file:
    sudo nano /etc/prometheus/prometheus.yml
  2. Add the cAdvisor job definition pointing to your host IP and port 8082:
    scrape_configs:
      - job_name: 'docker_cadvisor'
        scrape_interval: 15s
        static_configs:
          - targets: ['YOUR_HOST_IP:8082']
  3. Restart Prometheus to apply the updated target:
    sudo systemctl restart prometheus

📈 Step 3: Querying Container Metrics in Grafana

Once cAdvisor establishes an API handshake with Docker using read-write socket permissions, rich metadata labels (like name="plex") will be embedded into your metrics. You can utilize these labels inside your Grafana dashboard panels:

  1. For CPU usage tracking across individual containers, use the PromQL expression:
    sum(rate(container_cpu_usage_seconds_total{instance=~"$host", name=~"$container"}[5m])) by (name) * 100
  2. For memory tracking, configure panels using raw metrics and map panel legends explicitly to {{name}}.

Tip: If you are looking for pre-built templates to visualize your metrics, popular Grafana Dashboard community IDs like 14282 and 193 offer robust templates for cAdvisor metrics.

*******************************************************************

Repurposing a Laptop for Proxmox: Automating Screen Backlight Control

Repurposing an old laptop (like an HP Pavilion) into a Proxmox virtualization node is a fantastic homelab project. It comes with a built-in battery backup (UPS) and integrated keyboard/monitor for local troubleshooting.

However, running a server 24/7 with the laptop screen permanently powered on wastes electricity and creates unwanted light pollution in your room. Because Proxmox is a headless server operating system, there is no graphical desktop environment to manage "screen timeout" settings.

In this guide, we will use the Linux terminal to directly interact with the hardware's ACPI controller, shut down the backlight, and build custom terminal commands to control it.


Step 1: Identify Your Backlight Controller

First, we need to find out how the Linux kernel is labeling your specific laptop screen. Log into your Proxmox server as root and list the contents of the backlight directory:

ls /sys/class/backlight/

You will typically see a folder named either acpi_video0 or intel_backlight. Note this folder name down, as we will use it for the rest of the guide. (For this tutorial, we will assume it is acpi_video0).

Step 2: Test the Hardware Override

Before automating the process, let's verify that we can manually cut power to the backlight by sending a "0" value to the hardware controller.

echo 0 > /sys/class/backlight/acpi_video0/brightness

Your laptop screen should instantly go completely dark. Don't worry—the server is still running perfectly, and you haven't lost your SSH connection!

Step 3: Automate Screen Blanking on Boot

If you restart the server, the BIOS will turn the screen back on. Let's create a background task that automatically shuts the screen off exactly 1 hour (3600 seconds) after the server boots up.

  1. Open your cron table editor:
    crontab -e
  2. Scroll to the very bottom of the file and paste this exact line:
    @reboot sleep 3600 && echo 0 > /sys/class/backlight/acpi_video0/brightness
  3. Save and exit.
Why a 1-hour delay? If you ever need to reboot the server for maintenance, you will likely want the screen on while you monitor the startup process and check your containers. A 3600-second sleep timer ensures the screen stays on while you work, and turns itself off automatically once you walk away.

Step 4: Create Custom Aliases for Manual Control

Typing out those long system paths every time you want to toggle the screen is tedious. We can create simple, custom shortcut commands like screenon and screenoff by adding aliases to our Bash profile.

Open your root shell configuration file:

nano ~/.bashrc

Scroll to the very bottom and paste the following block of code. (Notice that the screenon command is dynamically designed to read your specific hardware's maximum brightness limit, ensuring it scales correctly regardless of laptop brand):

# Laptop Screen Backlight Controls
alias screenoff='echo 0 > /sys/class/backlight/acpi_video0/brightness'

alias screenon='cat /sys/class/backlight/acpi_video0/max_brightness > /sys/class/backlight/acpi_video0/brightness'

alias screenstatus='echo "$(( 100 * $(cat /sys/class/backlight/acpi_video0/brightness) / $(cat /sys/class/backlight/acpi_video0/max_brightness) ))%"'

Save and exit, then reload your profile to apply the changes immediately:

source ~/.bashrc

The Final Result

Your old laptop is now acting like a true enterprise headless server! You can log in via SSH at any time and simply type:

  • screenoff: Instantly kills the backlight.
  • screenon: Restores the screen to 100% maximum brightness.
  • screenstatus: Calculates the live raw hardware data and outputs a clean percentage (e.g., 0% or 100%).

*******************************************************************

Comments

Popular Posts

Chennai :MTC complaint cell Customer Care No.:+91-9445030516 /Toll Free : 18005991500

eNPS : OTP Authentication/eSign/Print