Author: fjgraf

  • Manual ip addressing – nmcli

    Phase 1: Information Gathering

    Before changing anything, you need to know your current environment.

    GoalCommandWhat to look for
    Find Interface Namenmcli devLook for eth0, enp0s3, etc.
    Find Current Profilenmcli con showThe name in the NAME column.
    Get IP/Gateway/DNSnmcli dev show <interface>IP4.ADDRESS, IP4.GATEWAY, IP4.DNS

    Phase 2: The Implementation

    If you want to rename the connection and set the static IP in a clean, two-step process, use these commands (replace bracketed text with your actual info):

    1. Rename the connection (Optional):

    sudo nmcli connection modify "Old Name" connection.id "NewName"

    2. Set Static IP, Gateway, and DNS:

    Bash

    sudo nmcli con mod "NewName" \
    ipv4.addresses 192.168.1.100/24 \
    ipv4.gateway 192.168.1.1 \
    ipv4.dns "8.8.8.8 1.1.1.1" \
    ipv4.method manual

    3. Apply the changes:

    Bash

    sudo nmcli con up "NewName"

  • proxmox iommu

    1- Enable Virtualization in BIOS

    Enter BIOS and enable:

    • VT-x / SVM
    • VT-d / IOMMU
    • Above 4G decoding (important for GPUs)

    Save and reboot.


    2- Enable IOMMU in Proxmox

    Edit GRUB:

    nano /etc/default/grub

    For Intel:

    GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"

    For AMD:

    GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt"

    Update grub:

    update-grub

    Reboot.

  • cpupower

    [Unit]
    Description=Set CPU governor at boot
    After=network.target

    [Service]
    Type=oneshot
    ExecStart=/usr/bin/cpupower frequency-set -g ondemand

    [Install]
    WantedBy=multi-user.target

    auto-cpufreq setup

    To get a “flawless” setup on an Intel-based Debian laptop, you should avoid the Snap version and use the Git Installer. This ensures you get the power_helper.py script and proper system integration that Snaps often lack.

    1. Full Installation Instructions
      Open your terminal and run these in order:

    Bash

    1. Install dependencies

    sudo apt update
    sudo apt install git python3-dev python3-pip python3-venv python3-setuptools dmidecode libcairo2-dev libgtk-3-dev gcc python3-gi

    2. Clone the repository

    git clone https://github.com/AdnanHodzic/auto-cpufreq.git
    cd auto-cpufreq

    3. Run the installer

    sudo ./auto-cpufreq-installer
    During installation, choose i to install the daemon.

    1. The Critical “Debian/Intel” Cleanup
      As you saw, your governor was stuck. This is because Debian/GNOME services are fighting auto-cpufreq.

    Step A: Disable the Conflict

    Bash
    sudo systemctl stop power-profiles-daemon
    sudo systemctl mask power-profiles-daemon
    Step B: Configure for Cooling (Your 800MHz Heat Issue)
    Create the config file to force powersave and kill turbo (the main cause of idle heat).

    Bash
    sudo nano /etc/auto-cpufreq.conf
    Paste this exactly:

    Ini, TOML
    [charger]
    governor = powersave
    turbo = never

    [battery]
    governor = powersave
    turbo = never
    Save and exit (Ctrl+O, Enter, Ctrl+X).

    Step C: Restart the Daemon

    Bash
    sudo systemctl restart auto-cpufreq

    1. Setup Questions (To refine your “Flawless” config)
      To tailor this specifically to your Intel hardware, answer these:

    What is the output of cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver?

    If it is intel_pstate, we should check your HWP (Hardware P-State) settings.

    If it is acpi-cpufreq, your CPU is older and might need different governor names.

    Does your laptop have a dedicated GPU (Nvidia)?

    If yes, the 800MHz heat is likely the GPU “leaking” heat to the shared CPU heatsink.

    Are you using a GNOME desktop environment?

    GNOME has a “Power” slider in the top right menu. If you touch that slider, it can sometimes override these settings.

    What is the age/generation of the Intel CPU? (e.g., i5-8250U, i7-12700H)

    12th gen and newer (Hybrid architecture) require specific handling of E-cores vs. P-cores.

    Would you like me to show you how to check your C-States now that we’ve (hopefully) forced the powersave governor?

  • VLan openwrt config


    OpenWrt VLAN Configuration

    Option 1: The UCI Method (quickest)

    Best for reproducibility and remote configuration via SSH. for fast deployment but note that you have to edit the script accordingly.

    # 1. Define the VLAN on the Hardware Bridge
    # We use 'add' to create a new bridge-vlan section
    uci add network bridge-vlan
    uci set network.@bridge-vlan[-1].device='br-lan'
    uci set network.@bridge-vlan[-1].vlan='43'
    # IMPORTANT: 'lan1' is the port to Proxmox. ':t' means Tagged.
    # You MUST also tag the 'vlan43' (CPU/Local) port so the router can talk to it.
    uci add_list network.@bridge-vlan[-1].ports='lan1:t'
    uci add_list network.@bridge-vlan[-1].ports='vlan43:t'
    
    # 2. Create the Logic Interface (The Gateway)
    uci set network.matrix_vlan=interface
    uci set network.matrix_vlan.proto='static'
    uci set network.matrix_vlan.device='br-lan.43'
    uci set network.matrix_vlan.ipaddr='192.168.43.1'
    uci set network.matrix_vlan.netmask='255.255.255.0'
    
    # 3. Configure the DHCP Server
    uci set dhcp.matrix_vlan=dhcp
    uci set dhcp.matrix_vlan.interface='matrix_vlan'
    uci set dhcp.matrix_vlan.start='100'
    uci set dhcp.matrix_vlan.limit='50'
    uci set dhcp.matrix_vlan.leasetime='12h'
    
    # 4. Create the Firewall Zone and Rules
    uci add firewall zone
    uci set firewall.@zone[-1].name='vlan43'
    uci set firewall.@zone[-1].network='matrix_vlan'
    uci set firewall.@zone[-1].input='ACCEPT'
    uci set firewall.@zone[-1].output='ACCEPT'
    uci set firewall.@zone[-1].forward='REJECT'
    
    # Allow the VLAN to reach the Internet (WAN)
    uci add firewall forwarding
    uci set firewall.@forwarding[-1].src='vlan43'
    uci set firewall.@forwarding[-1].dest='wan'
    
    # 5. Apply changes
    uci commit
    /etc/init.d/network restart
    /etc/init.d/firewall restart

    Option 2: The LuCI (Web GUI) Method

    Best for visual verification of port status.

    Step 1: Bridge Configuration

    1. Navigate to Network -> Interfaces -> Devices.
    2. Click Configure next to br-lan.
    3. Go to the Bridge VLAN filtering tab.
    4. Ensure Enable VLAN filtering is checked.
    5. Click Add:
      • VLAN ID: 43
      • Local: Checked (Crucial: This lets the router’s CPU join the VLAN).
      • Ports: Find the port connected to Proxmox (e.g., LAN 1) and set it to tagged.
    6. Save (Do not click Save & Apply yet).

    Step 2: Interface Creation

    1. Navigate to Network -> Interfaces.
    2. Click Add new interface….
      • Name: MATRIX_VLAN
      • Protocol: Static address
      • Device: Select @br-lan.43 (This represents the tagged traffic from the bridge).
    3. Settings:
      • IPv4 address: 192.168.43.1
      • Netmask: 255.255.255.0
    4. Go to the DHCP Server tab -> General Setup and click Setup DHCP Server.

    Step 3: Firewall Zone

    1. While still in the MATRIX_VLAN interface settings, go to Firewall Settings.
    2. In Create / Assign firewall-zone, type vlan43 and press enter.
    3. Save & Apply.
    4. Navigate to Network -> Firewall.
    5. Find your new vlan43 zone and ensure the Forward is set to allow traffic to the WAN zone.

    The “Don’t Get Locked Out” Checklist

    1. Always make sure your computer’s current port is set to Untagged on VLAN 1 before clicking Apply.
    2. The “Local” Checkbox: In LuCI, if “Local” isn’t checked for a VLAN, the router’s software can’t “see” that traffic, and your DHCP server won’t work.
    3. Standardized Octets: Notice how we used VLAN 43 and 192.168.43.1. This is your new standard.

    To finish the loop, we need to make sure Proxmox knows how to handle that VLAN 43 tag. In Proxmox, the default networking is usually a “Linux Bridge” (vmbr0). By default, it acts like an unmanaged switch—it doesn’t care about tags unless you tell it to.

    Here is how to set up the Proxmox side to match your new OpenWrt configuration.


    1. Make the Proxmox Bridge “VLAN Aware”

    If your bridge isn’t VLAN-aware, it will drop any tagged packets coming from your LXC before they even reach the physical cable.

    1. Log into your Proxmox Web UI.
    2. Go to System -> Network.
    3. Select your main bridge (usually vmbr0).
    4. Click Edit.
    5. Check the box for VLAN Aware.
    6. Apply Configuration (This does not usually require a reboot, but it will momentarily restart the network stack).

    2. Configure the LXC Container

    Now you need to tell the specific Matrix LXC to “sit” on VLAN 43.

    1. Select your Matrix LXC from the left-hand sidebar.
    2. Go to the Network tab.
    3. Select the network interface (usually net0) and click Edit.
    4. In the VLAN Tag field, enter 43.
    5. Ensure IPv4 is set to DHCP.
    6. Click OK.

    3. The “Moment of Truth” Test

    Start your LXC and open the console. Run the following commands to verify the “Subnet-Nightmare” is actually working:

    • Check IP:ip addr show
      • Result: You should see an IP like 192.168.43.XXX.
    • Check Gateway:ip route
      • Result: The default via should be 192.168.43.1.
    • Check DNS/Internet:ping -c 3 google.com
      • Result: If this fails but the IP is correct, check your OpenWrt Firewall zone to ensure vlan43 is allowed to forward to wan.

    Troubleshooting “Subnet-Nightmare”

    If you aren’t getting an IP:

    1. OpenWrt Side: Ensure the physical port (e.g., lan1) in your bridge-vlan section has the :t (tagged) suffix.
    2. Proxmox Side: Ensure the physical network card (e.g., eno1 or eth0) is actually a member of vmbr0.
    3. The Cable: Ensure you aren’t using a “dumb” unmanaged switch between OpenWrt and Proxmox. Some cheap switches strip VLAN tags, which will kill the connection.

    Pro-Tip for Matrix

    Once the networking is solid, remember that Matrix Synapse defaults to port 8008. You will eventually need to create a Port Forward in OpenWrt (WAN port 443 -> LXC IP port 8008) so your students can connect from outside your home lab.

    Would you like the homeserver.yaml snippets to configure the listener and database connection for Synapse once you’re inside the LXC?

  • Simple contact form with html and php


    This code is a PHP-based contact form processor that sends emails using the msmtp command. It validates and sanitizes user input, composes an email with the user’s input, and sends it to a hardcoded recipient email address. The code prioritizes security and includes error handling and debugging mechanisms. To use this code, you’ll need to have your own email account that can work with an msmtp configuration, as it relies on this setup to send emails. Overall, the code provides a solid foundation for a contact form processor, and with some customizations, it can become even more robust and feature-rich. Expansion opportunities include adding support for multiple recipient email addresses, implementing CAPTCHA or anti-spam measures, integrating with popular email services, and allowing file attachments.

    HTML

    <style>
        /* Set a max width for the form */<br />
        form {<br />
            width: 100%;<br />
            max-width: 600px;  /* Adjust the max-width as needed */<br />
            margin: 0 auto;  /* Center the form */<br />
            padding: 20px;<br />
            background-color: #f9f9f9;<br />
            border-radius: 8px;<br />
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);<br />
        }</p>
    <p>    /* Style the labels */<br />
        label {<br />
            display: block;<br />
            margin-bottom: 8px;<br />
            font-weight: bold;<br />
        }</p>
    <p>    /* Style the input fields */<br />
        input[type="text"],<br />
        input[type="email"] {<br />
            width: 100%;  /* Make input fields full width */<br />
            padding: 10px;<br />
            margin-bottom: 15px;<br />
            border: 1px solid #ccc;<br />
            border-radius: 4px;<br />
            box-sizing: border-box;  /* Include padding in the width calculation */<br />
        }</p>
    <p>    /* Style the textarea */<br />
        textarea {<br />
            width: 100%;  /* Make the textarea full width */<br />
            height: 150px;  /* Increase the height of the message box */<br />
            padding: 10px;<br />
            margin-bottom: 15px;<br />
            border: 1px solid #ccc;<br />
            border-radius: 4px;<br />
            box-sizing: border-box;  /* Include padding in the width calculation */<br />
        }</p>
    <p>    /* Style the submit button */<br />
        button {<br />
            padding: 12px 20px;<br />
            background-color: #4CAF50;  /* Green background */<br />
            color: white;<br />
            border: none;<br />
            border-radius: 4px;<br />
            cursor: pointer;<br />
            font-size: 16px;<br />
        }</p>
    <p>    button:hover {<br />
            background-color: #45a049;  /* Slightly darker green on hover */<br />
        }</p>
    <p>    /* Add small text for the message character limit */<br />
        .char-limit {<br />
            font-size: 14px;<br />
            color: #888;<br />
            margin-bottom: 10px;<br />
        }<br />
    </style>
    <form action="/contact-process-form.php" method="POST">
        <input type="hidden" name="nonce" value="<?php echo $nonce; ?>"></p>
    <p>    <label for="name">Name:</label><br />
        <input type="text" id="name" name="name" required maxlength="100"></p>
    <p>    <label for="email">Email:</label><br />
        <input type="email" id="email" name="email" required maxlength="100"></p>
    <p>    <label for="subject">Subject:</label><br />
        <input type="text" id="subject" name="subject" required maxlength="100"></p>
    <p>    <label for="message">Message:</label><br />
        <textarea id="message" name="message" required maxlength="6000"></textarea></p>
    <p>    <!-- Add a character limit notice --><br />
        <small class="char-limit">Maximum characters allowed in message: 6000</small><br /> <!-- Added line break --></p>
    <p>    <button type="submit">Send</button><br />
    </form>

    php contact-process-form.php in root folder of website.

    <?php
    // Load WordPress functions (adjust path if necessary)
    require_once('/var/www/bashing.life/wp-load.php');
    
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    
    // Log errors to a secure file instead of displaying
    ini_set('log_errors', 1);
    ini_set('error_log', '/var/log/php_errors.log');
    
    // Define the path to msmtp (adjust according to where msmtp is installed)
    $msmtp_path = '/usr/bin/msmtp';  // Replace with your msmtp path if it's different
    
    // Define the path to the msmtp configuration file
    $msmtp_config_file = '/etc/msmtprc';  // Replace with the correct path to the msmtp configuration file
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and retrieve the form data
        $name = sanitize_text_field($_POST['name']);
        $email = sanitize_email($_POST['email']);
        $subject = sanitize_text_field($_POST['subject']);
        $message = wp_kses_post($_POST['message']);  // Sanitizing message further
    
        // Validate the email address
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            echo "Invalid email format.";
            exit;
        }
    
    // Escape each variable to prevent command injection
        $safe_name = escapeshellarg($name);
        $safe_email = escapeshellarg($email);
        $safe_subject = escapeshellarg($subject);
        $safe_message = escapeshellarg($message);
    
        $hardcoded_subject = "New Contact Form Submission";
    
        $email_content = "You have received a new contact form submission:\n\n";
        $email_content .= "Name: $safe_name\n";
        $email_content .= "Email: $safe_email\n";
        $email_content .= "Subject (from form): $safe_subject\n\n";
        $email_content .= "Message:\n$safe_message\n";
    
     // Escape the full email content (to ensure no issues with newlines, quotes, etc.)
        $escaped_email_content = escapeshellarg($email_content);
    
        // Send the email using msmtp
        $recipient = 'example@example.com';  // Your recipient email address
    
        // Properly escape the command to avoid injection
    
        $command = "echo \"Subject:$hardcoded_subject\nContent-Type: text/plain; charset=UTF-8\n\n$email_content\" | $msmtp_path --file=$msmtp_config_file -a default example@example.com";
    
    
        // Execute the command and capture the output
        $output = shell_exec($command . ' 2>&1');  // Capture both stdout and stderr
    
        // Debugging: Check if output is captured and display it
        if ($output === null) {
            echo "Failed to send the message. No output received from msmtp.";
        } else {
            //echo "Message sent successfully!<br>"; //for future debugging
            //echo "<strong>Debugging Output from msmtp:</strong><br>"; //for future debugging
            //echo "<pre>$output</pre>";  // Debugging output from msmtp
            // Redirect to the thank-you page after success
            header("Location: thank-you");
            exit;
        }
    
    } else {
        echo "Please fill in all fields.";
    }
    ?>
    
    

    The msmtprc system-wide configuration file must be in /etc

  • Secure access OpenWRT

    For openwrt devices exposed directly to the internet such as in a DMZ and need to have ssh access without compromising too much, we will have to follow these steps to achieve some basic security. Internet traffic is very much infested with bots trying to brute force into systems so this is a small but very useful step to harden your security and privacy. The main idea is to disable password authentication and use only a certificate with a custom ssh port.

    First is to create a public and private key pair in the client from which we usually perform administrative tasks then we follow by copying the public key to our openwrt device. We continue by changing the standard ssh port 22 for a non standard port preferably in the range of dynamic or Private Ports (49152-65535) and we finish by creating a config file in .shh to manage the settings to log in with the certificate generated at the beginning and disabling password authentication.

    In your local device (from where you intent to access your openwrt) generate the certificates. Go to your users .ssh directory (/home/[your_local_user]/.ssh) and type:

    ssh-keygen -t ed25519 -a 100 -f .ssh/key-openwrt

    This will generate key-openwrt (private) and key-openwrt.pub files. We will create a config file later to configure and use the connection for convenience.

    Now its time to copy your public key to openwrt:

    ssh-copy-id -i key-openwrt.pub root@ip_address

    As you are trying to copy the pub key as root, the key will be associated to the root user. You will be prompted to provide your root password once. If the copy is successful lets try to log in without the password. The private key in your local machine will get verified against the public key you’ve just copied over to openwrt and if validation is successful you will log in as your openwrt’s root user.

    ssh -i key-opewrt root@server_ip 

    If login was successful we can now create a config file (named config) inside the .ssh directory. This file will manage your key based authentications but before doing that, let’s change the default port to put the new info in your config file:

    Log in in the LUCI portal and click in Administration –> ssh-access:

    Choose a non standard port for ssh and disable password authentication. Save and exit. Now lets create the config file in /home/[you_local_user]/.ssh:

    nano config

    host   openwrt
           Hostname [ip_address]
           User root
           port [example:49595]
           PreferredAuthentications publickey
           IdentityFile /home/[your_local_user]/.ssh/key-openwrt

    Once the files has been saved and if everything went well you should be to log in without a password using your key pair. Try out the configuration:

    ssh openwrt

    Done!

  • Raspberry pi zero w config – Raspbian

    Steps:

    1. Create the wpa_supplicant.conf file in the bootfs partition:
      • Create a new text file named wpa_supplicant.conf (or similar) using a text editor on your computer.
      • Add the following configuration, replacing the placeholders with your actual network details: 

    Code

    ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
         update_config=1
         country=US
         network={
              ssid="YOUR_NETWORK_SSID"
              psk="YOUR_NETWORK_PASSWORD"
              key_mgmt=WPA-PSK
         }
    • ctrl_interface: Required for command-line utilities to interact with wpa_supplicant. 
    • update_config: Allows wpa_cli to update the configuration. 
    • country: Set your country code. 
    • ssid: Your Wi-Fi network’s SSID (name). 
    • psk: Your Wi-Fi network’s password. 
    • key_mgmt: Specifies the authentication method (WPA-PSK for personal/home networks). 
    1. 1. Enable SSH (optional, but recommended):
      • Create an empty file named ssh (no extension) in the /boot directory. 
      • This enables SSH access on first boot, allowing you to connect remotely without a monitor and keyboard. 
    2. 2. Place the files on the SD card:
      • Copy both wpa_supplicant.conf and ssh (if created) to the /boot partition of your Raspberry Pi’s SD card. 
    3. 3. Boot the Raspberry Pi:
      • Insert the SD card into your Raspberry Pi and power it on. 
      • The Pi will automatically move the wpa_supplicant.conf file to the correct location and attempt to connect to your Wi-Fi network. 
      • If you created the ssh file, you will be able to SSH into your Pi once it connects to the network. 
    4. 4. (Optional) Verify the connection:
      • If you have a monitor and keyboard connected, you can check the network connection using ifconfig or ip addr. 
      • You can also use wpa_cli to interact with wpa_supplicant and check connection status. 
  • Fixing Proxmox SSL Certificates Behind HAProxy: Step-by-Step Tutorial

    This tutorial chronicles my troubleshooting and resolution process for SSL certificate issues with Proxmox when running behind an OpenWRT router with HAProxy. It includes all intermediate steps, challenges, and reasoning, as well as interactions with my assistant, which helped guide the solution.


    1. Network Context and Initial Problem

    My setup was as follows:

    ISP Router -> OpenWRT (DMZ) with HAProxy -> Proxmox
    • Proxmox listens on its default HTTPS port 8006.
    • Other internal web servers are served through HAProxy, which handles TLS for domains like aurispetreus.net.
    • My goal was to have valid, trusted TLS certificates for Proxmox while maintaining other web services on the same HAProxy instance.

    Problem Observed

    When attempting to access Proxmox through its domain (for example, using the Proxmox Android app), I encountered SSL protocol errors. Initially, I wasn’t sure whether:

    1. Proxmox was failing to pick up the manually uploaded certificates.
    2. HAProxy configuration was interfering with TLS termination.
    3. File permissions or certificate formats were incorrect.

    2. Generating and Copying Certificates

    Proxmox could not directly request Let’s Encrypt certificates because it wasn’t publicly accessible from the internet. Therefore, I generated certificates on a separate Debian server (debian) that could reach Let’s Encrypt:

    certbot certonly -d da3.aurispetreus.net

    This created:

    /etc/letsencrypt/archive/da3.aurispetreus.net/fullchain1.pem
    /etc/letsencrypt/archive/da3.aurispetreus.net/privkey1.pem

    I then manually copied these files to Proxmox (proxmox A) and renamed them to match Proxmox’s expected filenames:

    /etc/pve/local/pve-ssl.pem       # fullchain
    /etc/pve/local/pve-ssl.key       # private key

    I also set proper permissions for the private key:

    chmod 600 /etc/pve/local/pve-ssl.key

    At this point, I suspected that the certificates might not be correctly paired, so I decided to verify them.


    3. Verifying Certificate and Key Match

    To ensure the certificate and private key matched, I used OpenSSL to check the modulus:

    openssl x509 -noout -modulus -in fullchain1.pem | openssl md5
    openssl rsa -noout -modulus -in privkey1.pem | openssl md5

    Initially, I received:

    Not an RSA key

    This indicated a misuse of commands or possibly a non-RSA key (like EC). After adjusting the method (taking into account the correct type of key generated by Certbot), I confirmed that the modulus of the certificate and the private key matched, verifying they belonged together.


    4. HAProxy Configuration for Proxmox

    Since Proxmox sits behind HAProxy, it was important that TLS traffic be forwarded correctly. I started with the following principles:

    1. Use TCP mode on HAProxy for port 443 to allow end-to-end TLS (Proxmox manages its own certificates).
    2. Route traffic using SNI to direct specific domains to their respective backend servers.
    3. Keep the standard HTTPS port (443) public, without binding Proxmox’s internal port 8006 on HAProxy.

    Here’s the relevant HAProxy frontend for HTTPS:

    frontend https_in
        bind *:443
        mode tcp
        option tcplog
    
        # Inspect TLS handshake for SNI
        tcp-request inspect-delay 5s
        tcp-request content accept if { req_ssl_hello_type 1 }
    
        # Use backend based on requested SNI
        use_backend %[req.ssl_sni,lower]_tls if { req.ssl_sni -m found }

    And the Proxmox backend:

    backend da3.aurispetreus.net_tls
        mode tcp
        option tcp-check
        server da3.aurispetreus.net 192.168.3.144:8006 check
    # Note: No need to bind HAProxy to port 8006 externally

    Important detail: The assistant reminded me explicitly:
    “No, you do not bind port 8006 on HAProxy, because HAProxy is meant to expose a public listener on the standard HTTPS port (443).”
    This avoids exposing internal Proxmox ports directly and keeps the network cleaner.


    5. Initial Testing

    I tested the TLS handshake locally:

    curl -vk https://192.168.3.144:8006/

    Output confirmed that TLSv1.3 was being used, and the correct certificate was presented.

    Externally, trying to access 188.21.68.172:8006 failed because the ISP router blocked the direct connection, which was expected.


    6. Debugging “Wrong Version Number” Error

    When testing public access via HAProxy:

    curl -vk https://da3.aurispetreus.net:443

    I got:

    error:0A00010B:SSL routines::wrong version number

    Analysis:

    • The HAProxy frontend for port 443 was in HTTP mode.
    • TLS traffic to Proxmox requires TCP passthrough, not HTTP mode.

    Solution: Change the frontend to TCP mode. After doing so, the handshake worked correctly, and the Proxmox Android app could connect.

  • Cockpit via Apache Reverse Proxy

    I know, this is very unsafe if your login credentials are stolen but it serves as a proof of concept!!!

    1. The Goal

    To provide secure, external access to the Cockpit Web Console via https://example.site/cockpit/ without opening extra ports (like 9090) on the router and without managing separate SSL certificates for the dashboard.

    2. Architecture Flow

    1. User requests https://example.site/cockpit/.
    2. OpenWrt (HAProxy) receives traffic on port 443 and passes it to the Debian LXC (192.168.1.16:443).
    3. Apache (inside LXC) terminates the SSL using Let’s Encrypt certificates.
    4. Apache proxies the request internally to 127.0.0.1:9090.
    5. Cockpit processes the request and responds through the tunnel.

    3. Key Configurations

    A. Apache VirtualHost (/etc/apache2/sites-available/...)

    We used mod_proxy and mod_rewrite to handle both standard web traffic and the persistent WebSocket connections required for the Cockpit terminal.

    # WebSocket Upgrade (Fixes "Login then Blank Page" issue)
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /cockpit/(.*) ws://127.0.0.1:9090/cockpit/$1 [P,L]
    
    # Subfolder Proxy
    <Location /cockpit/>
        ProxyPass http://127.0.0.1:9090/cockpit/
        ProxyPassReverse http://127.0.0.1:9090/cockpit/
    </Location>

    B. Cockpit Config (/etc/cockpit/cockpit.conf)

    Crucial for preventing CSRF (Cross-Site Request Forgery) security blocks and allowing the unencrypted internal “handshake.”

    [WebService]
    Origins = https://hissite.org
    ProtocolHeader = X-Forwarded-Proto
    AllowUnencrypted = true
    UrlRoot = /cockpit

    4. Troubleshoot Log & Fixes

    • Issue:sscg: command not found.
      • Fix: Installed sscg package to satisfy Cockpit dependencies.
    • Issue:gnutls_handshake failed: A TLS fatal alert.
      • Reason: Cockpit was expecting HTTPS but Apache was sending HTTP.
      • Fix: Added AllowUnencrypted = true and restarted cockpit.socket.
    • Issue: Blank page after login.
      • Reason: WebSockets (the “live” part of the site) were failing to tunnel through Apache.
      • Fix: Added specific RewriteRule for ws:// protocol.

    5. Security Hardening Applied

    • Consolidated SSL: All traffic uses the main site’s hardened TLS 1.2/1.3 settings.
    • Internal Communication: Apache talks to Cockpit over 127.0.0.1 (local loopback), meaning no unencrypted traffic ever touches your LAN or the WAN.

    6. Maintenance Commands

    If Cockpit ever stops responding, run these in order:

    1. sudo systemctl restart cockpit.socket (Restarts the listener)
    2. sudo systemctl restart apache2 (Restarts the proxy)
    3. sudo journalctl -u cockpit -f (To watch live logs if a crash occurs)
  • GnuDIP

    GnuDIP2 can be used to update your ip address. The following configuration is going to be tailored to manage the free ddns service provided in ddns.freedombox.org or compatible ddns services.

    Before starting you need to already have a gnudip account and install the curl package (sudo apt install curl).

    Download the latest version of GnuDIP from the official site:
    https://gnudip2.sourceforge.net/gnudip-www/latest/gnudip/html/clients.html

    Direct link:
    https://gnudip2.sourceforge.net/gnudip-www/latest/gnudip/html/client/UNIX/gnudip-latest-gdipc.tar.gz

    Change to your root account: This is so that the gdipc script (necessary to update the ip address of your service) can be run at a regular basis without the need to log in with your personal user account. Just let the root user handle it and forget it…

    Decompress and untar the file to /usr/local/

    Now you need to add a new directory path to your $PATH so you can run the script at the location where the gdipc folder was placed. Recomended location /usr/local/

    Edit the .bashrc file of your root account and add the following line at the end:

    #DDNS GnuDIP update service
    export PATH="$PATH:/usr/local/gdipc/bin"

    Source the file to update your $PATH with:

    source ~/.bashrc

    Running gdipc.pl script to add a new entry. This entry will contain the necessary information so that GnuDIP2 logs in to your account and verify if it needs to update your ip address.

    When you run gdipc.pl with the -c option, it allows you to configure the script through an interactive setup. This can help avoid manual editing of the script and ensure all necessary settings are entered correctly. Additionally, the information you enter will be saved in .GnuDIP2 in the same root home directory.

    1. Run the Script with -c Option:To begin the configuration process, run the script with the -c flag: gdipc.pl -c
    2. Follow the Prompts:The script will prompt you for the required configuration details. You will need to input the following:
      • Username: Your user account created in ddns.freedombox.org
      • Domain: Depending on the subdomain chosen at the time of registration like fbx.one or freedombox.rocks.
      • Connect by direct TCP (d) or web server (w) [d]:
      • GnuDIP Server – host[:port]: ddns.freedombox.org
      • Password: Your password provided at registration.
      • Cache File [/root/.GnuDIP2.cache..]: Location of the cache file.
      • Minimum Seconds Between Updates [0]: Leave as default
      • Maximum Seconds Between Updates [2073600]: Leave as default
    3. Complete the Configuration:After entering all the necessary information, the script will configure itself with the values you’ve provided.
    4. Verify the Configuration:Once you’ve configured the script, you can run it without the -c option to check if the DDNS update process works correctly:
      gdipc.pl The script should now update the DDNS service with your current IP address and domain.
    5. Automate the Process :To ensure your IP is updated regularly, you can automate the execution of gdipc.pl using a cron job.
      • Here’s an example cron job that runs the script every 10 minutes:
      • crontab -e Add the following line to run the script every 10 minutes:
        • */10 * * * * /usr/local/gdipc/bin/gdipc.pl -q "curl -s ifconfig.me" >> /home/root/.GnuDIP2.log 2>&1
        • Note that “curl -s ifconfig.me” will retrieve you public ip address.
    6. Executing and Logging: As you can see in the cron job configured, the script needs to be executed with the -q option. This is because if its run without it, it will use the local ip address of your network and not your public ip address to perform the update. Additionally, is configured standard and error output to a log file to keep track of the script work.
    • With -q Option: You can specify a command to retrieve the IP address, and the script will execute that command.
    • Without -q Option: The script tries to obtain the local machine’s IP address via getsockname() on a socket connection.
    • With -g Option: If you’re behind a gateway, the script registers the external IP address that the GnuDIP server sees.