Author: fjgraf

  • WDS

    WDSUnattended-WIN10-ENTERPRISE-EVAL.xml

    <?xml version="1.0" encoding="utf-8"?>
    <unattend xmlns="urn:schemas-microsoft-com:unattend">
        <settings pass="windowsPE">
            <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <SetupUILanguage>
                    <UILanguage>en-US</UILanguage>
                </SetupUILanguage>
                <InputLocale>0409:00000409</InputLocale>
                <SystemLocale>en-US</SystemLocale>
                <UILanguage>en-US</UILanguage>
                <UILanguageFallback>en-US</UILanguageFallback>
                <UserLocale>en-US</UserLocale>
            </component>
            <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <DiskConfiguration>
                    <WillShowUI>OnError</WillShowUI>
                    <Disk wcm:action="add">
                        <DiskID>0</DiskID>
                        <WillWipeDisk>true</WillWipeDisk>
                        <CreatePartitions>
                            <CreatePartition wcm:action="add">
                                <Order>1</Order>
                                <Size>450</Size>
                                <Type>Primary</Type>
                            </CreatePartition>
                            <CreatePartition wcm:action="add">
                                <Order>2</Order>
                                <Size>100</Size>
                                <Type>EFI</Type>
                            </CreatePartition>
                            <CreatePartition wcm:action="add">
                                <Order>3</Order>
                                <Size>16</Size>
                                <Type>MSR</Type>
                            </CreatePartition>
                            <CreatePartition wcm:action="add">
                                <Order>4</Order>
                                <Extend>true</Extend>
                                <Type>Primary</Type>
                            </CreatePartition>
                        </CreatePartitions>
                        <ModifyPartitions>
                            <ModifyPartition wcm:action="add">
                                <Format>NTFS</Format>
                                <Label>WinRE</Label>
                                <Order>1</Order>
                                <PartitionID>1</PartitionID>
                                <TypeID>DE94BBA4-06D1-4D40-A16A-BFD50179D6AC</TypeID>
                            </ModifyPartition>
                            <ModifyPartition wcm:action="add">
                                <Format>FAT32</Format>
                                <Label>System</Label>
                                <Order>2</Order>
                                <PartitionID>2</PartitionID>
                            </ModifyPartition>
                            <ModifyPartition wcm:action="add">
                                <Order>3</Order>
                                <PartitionID>3</PartitionID>
                            </ModifyPartition>
                            <ModifyPartition wcm:action="add">
                                <Format>NTFS</Format>
                                <Label>WINDOWS</Label>
                                <Letter>C</Letter>
                                <Order>4</Order>
                                <PartitionID>4</PartitionID>
                            </ModifyPartition>
                        </ModifyPartitions>
                    </Disk>
                </DiskConfiguration>
                <UserData>
                    <AcceptEula>true</AcceptEula>
                    <Organization>station07</Organization>
                </UserData>
                <WindowsDeploymentServices>
                    <Login>
                        <Credentials>
                            <Domain>station07.com</Domain>
                            <Password>Amf123456</Password>
                            <Username>Administrator</Username>
                        </Credentials>
                    </Login>
                    <ImageSelection>
                        <InstallImage>
                            <Filename>install.wim</Filename>
                            <ImageGroup>ImageGroup1</ImageGroup>
                            <ImageName>Windows 10 Enterprise Evaluation</ImageName>
                        </InstallImage>
                        <InstallTo>
                            <DiskID>0</DiskID>
                            <PartitionID>4</PartitionID>
                        </InstallTo>
                    </ImageSelection>
                </WindowsDeploymentServices>
            </component>
        </settings>
    </unattend>

    AutoUnattended-WIN10-ENTERPRISE-EVAL.xml

    <?xml version="1.0" encoding="utf-8"?>
    <unattend xmlns="urn:schemas-microsoft-com:unattend">
        <settings pass="specialize">
            <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <OEMInformation>
                    <Manufacturer>HP</Manufacturer>
                    <Model>Prodesk</Model>
                </OEMInformation>
                <ComputerName>WorkPC</ComputerName>
                <CopyProfile>true</CopyProfile>
                <RegisteredOrganization>station07</RegisteredOrganization>
                <RegisteredOwner>station07.com</RegisteredOwner>
                <TimeZone>Eastern Standard Time</TimeZone>
            </component>
            <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <Identification>
                    <Credentials>
                        <Domain>station07.com</Domain>
                        <Password>Amf123456</Password>
                        <Username>Administrator</Username>
                    </Credentials>
                    <JoinDomain>station07.com</JoinDomain>
                </Identification>
            </component>
        </settings>
        <settings pass="oobeSystem">
            <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <OOBE>
                    <HideEULAPage>true</HideEULAPage>
                    <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
                    <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
                    <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
                    <NetworkLocation>Work</NetworkLocation>
                    <ProtectYourPC>1</ProtectYourPC>
                </OOBE>
                <UserAccounts>
                    <LocalAccounts>
                        <LocalAccount wcm:action="add">
                            <Password>
                                <Value>UABhAHMAcwB3AG8AcgBkAA==</Value>
                                <PlainText>false</PlainText>
                            </Password>
                            <Description>The Local admin account</Description>
                            <DisplayName>admin</DisplayName>
                            <Group>Administrators</Group>
                            <Name>admin</Name>
                        </LocalAccount>
                    </LocalAccounts>
                </UserAccounts>
            </component>
            <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <InputLocale>0409:00000409</InputLocale>
                <SystemLocale>en-US</SystemLocale>
                <UILanguage>en-US</UILanguage>
                <UserLocale>en-US</UserLocale>
            </component>
        </settings>
    </unattend>

  • MOTD Messages after you log in (debian)

    A custom Message of the Day (MOTD) provides useful system metrics every time you log in via SSH.

    While static text files in /etc/motd or /etc/motd.d/ print raw text, scripts placed in /etc/update-motd.d/ execute dynamically, allowing for real-time resource tracking and colorized formatting.

    1. How MOTD Processing Works in Debian

    On Debian and Ubuntu, PAM (Pluggable Authentication Modules) constructs the login screen in sequential order:

    1. Static Base Notice: Read from /etc/motd (e.g., standard OS version and copyright information).
    2. Dynamic Scripts: Executed numerically from /etc/update-motd.d/.
    3. Drop-in Static Notices: Appended from /etc/motd.d/ (such as the Cockpit web console notice).

    Key Rule: Scripts in /etc/update-motd.d/ must be executable (chmod +x), whereas files in /etc/motd.d/ are treated strictly as plain text.

    2. Setting Up the Dynamic Stats Script

    Step 1: Create the Script

    Create a new script file in /etc/update-motd.d/. Using the prefix 50- ensures it runs in the middle—after system notices and before Cockpit or custom app notices:

    Bash

    sudo nano /etc/update-motd.d/50-admin-stats

    Step 2: Add the Script Content

    Paste the following complete script. It includes standard system metrics alongside optional advanced features like Wazuh Agent status check, updates pending check, and failed SSH login tracking.

    Basic Version

    #!/usr/bin/env bash
    
    # Color definitions
    GREEN='\033[0;32m'
    RED='\033[0;31m'
    BLUE='\033[0;34m'
    NC='\033[0m' # No Color
    
    # System Stats
    HOSTNAME=$(hostname -f)
    KERNEL=$(uname -r)
    UPTIME=$(uptime -p | sed 's/up //' 2>/dev/null || uptime)
    LOAD=$(cut -d' ' -f1-3 /proc/loadavg)
    MEM_USAGE=$(free -h | awk '/Mem:/ {print $3 "/" $2}')
    DISK_USAGE=$(df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}')
    
    echo -e "${BLUE}============================================================${NC}"
    echo -e " Welcome to ${GREEN}${HOSTNAME}${NC}"
    echo -e " Kernel:   ${KERNEL}"
    echo -e " Uptime:   ${UPTIME}"
    echo -e " Load:     ${LOAD}"
    echo -e " Memory:   ${MEM_USAGE}"
    echo -e " Disk /:   ${DISK_USAGE}"
    echo -e "${BLUE}============================================================${NC}"

    Advanced Version

    #!/usr/bin/env bash
    # ==============================================================================
    # Custom Dynamic MOTD Script for Debian
    # Place in: /etc/update-motd.d/50-admin-stats
    # Permissions: chmod +x /etc/update-motd.d/50-admin-stats
    # ==============================================================================
    
    # ANSI Color Codes
    BOLD='\033[1m'
    GREEN='\033[0;32m'
    RED='\033[0;31m'
    YELLOW='\033[0;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    NC='\033[0m' # No Color
    
    # ------------------------------------------------------------------------------
    # 1. CORE SYSTEM METRICS
    # ------------------------------------------------------------------------------
    HOSTNAME=$(hostname -f)
    KERNEL=$(uname -r)
    UPTIME=$(uptime -p | sed 's/up //' 2>/dev/null || uptime)
    LOAD=$(cut -d' ' -f1-3 /proc/loadavg)
    
    # Memory & Swap Usage
    MEM_USED=$(free -m | awk '/Mem:/ {print $3}')
    MEM_TOTAL=$(free -m | awk '/Mem:/ {print $2}')
    MEM_PCT=$(awk "BEGIN {printf \"%.1f\", ($MEM_USED/$MEM_TOTAL)*100}")
    
    # Disk Usage (Root Partition)
    DISK_USED=$(df -h / | awk 'NR==2 {print $3}')
    DISK_TOTAL=$(df -h / | awk 'NR==2 {print $2}')
    DISK_PCT=$(df -h / | awk 'NR==2 {print $5}')
    
    # IP Addresses
    IP_LOCAL=$(hostname -I | awk '{print $1}')
    
    # ------------------------------------------------------------------------------
    # 2. OPTIONAL ADVANCED CHECKS
    # ------------------------------------------------------------------------------
    
    # Feature A: Check Wazuh Agent Service Status
    if systemctl is-active --quiet wazuh-agent 2>/dev/null; then
        WAZUH_STATUS="${GREEN}Active (Running)${NC}"
    elif systemctl is-active --quiet wazuh-manager 2>/dev/null; then
        WAZUH_STATUS="${GREEN}Active (Manager - Agent 000)${NC}"
    else
        WAZUH_STATUS="${RED}Inactive / Not Installed${NC}"
    fi
    
    # Feature B: Failed SSH Login Attempts (Last 24 Hours)
    FAILED_LOGINS=$(journalctl _SYSTEMD_UNIT=ssh.service --since "24 hours ago" 2>/dev/null | grep -c "Failed password")
    if [ "$FAILED_LOGINS" -gt 50 ]; then
        FAILED_STR="${RED}${FAILED_LOGINS} attempts!${NC}"
    elif [ "$FAILED_LOGINS" -gt 0 ]; then
        FAILED_STR="${YELLOW}${FAILED_LOGINS} attempts${NC}"
    else
        FAILED_STR="${GREEN}0 attempts${NC}"
    fi
    
    # ------------------------------------------------------------------------------
    # 3. DISPLAY OUTPUT
    # ------------------------------------------------------------------------------
    echo -e "${BLUE}============================================================${NC}"
    echo -e " ${BOLD}System Status for:${NC} ${CYAN}${HOSTNAME}${NC} (${IP_LOCAL})"
    echo -e "${BLUE}------------------------------------------------------------${NC}"
    echo -e "  OS Kernel : ${KERNEL}"
    echo -e "  Uptime    : ${UPTIME}"
    echo -e "  Load Avg  : ${LOAD}"
    echo -e "  Memory    : ${MEM_USED}MB / ${MEM_TOTAL}MB (${MEM_PCT}%)"
    echo -e "  Disk (/)  : ${DISK_USED} / ${DISK_TOTAL} (${DISK_PCT})"
    echo -e "  Wazuh     : ${WAZUH_STATUS}"
    echo -e "  SSH Security (24h) : ${FAILED_STR} failed logins"
    echo -e " DO NOT FORGET TO DO MANUAL WORDPRESS UPDATES"
    echo -e "https://fjgraf.fbx.one/2026/01/02/wp-cli/"
    echo -e "${BLUE}============================================================${NC}"
    

    More Advanced versions checking a small WordPress security scan:

    #!/usr/bin/env bash
    # ==============================================================================
    # Custom Dynamic MOTD Script for Debian
    # Place in: /etc/update-motd.d/50-admin-stats
    # Permissions: chmod +x /etc/update-motd.d/50-admin-stats
    # ==============================================================================
    
    # ANSI Color Codes
    BOLD='\033[1m'
    GREEN='\033[0;32m'
    RED='\033[0;31m'
    YELLOW='\033[0;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    NC='\033[0m' # No Color
    
    BASE_DIR="/var/www/"
    
    # ------------------------------------------------------------------------------
    # 1. CORE SYSTEM METRICS
    # ------------------------------------------------------------------------------
    HOSTNAME=$(hostname -f)
    KERNEL=$(uname -r)
    UPTIME=$(uptime -p | sed 's/up //' 2>/dev/null || uptime)
    LOAD=$(cut -d' ' -f1-3 /proc/loadavg)
    
    # Memory & Swap Usage
    MEM_USED=$(free -m | awk '/Mem:/ {print $3}')
    MEM_TOTAL=$(free -m | awk '/Mem:/ {print $2}')
    MEM_PCT=$(awk "BEGIN {printf \"%.1f\", ($MEM_USED/$MEM_TOTAL)*100}")
    
    # Disk Usage (Root Partition)
    DISK_USED=$(df -h / | awk 'NR==2 {print $3}')
    DISK_TOTAL=$(df -h / | awk 'NR==2 {print $2}')
    DISK_PCT=$(df -h / | awk 'NR==2 {print $5}')
    
    # IP Addresses
    IP_LOCAL=$(hostname -I | awk '{print $1}')
    
    # ------------------------------------------------------------------------------
    # 2. OPTIONAL ADVANCED CHECKS
    # ------------------------------------------------------------------------------
    
    # Feature A: Check Wazuh Agent Service Status
    if systemctl is-active --quiet wazuh-agent 2>/dev/null; then
        WAZUH_STATUS="${GREEN}Active (Running)${NC}"
    elif systemctl is-active --quiet wazuh-manager 2>/dev/null; then
        WAZUH_STATUS="${GREEN}Active (Manager - Agent 000)${NC}"
    else
        WAZUH_STATUS="${RED}Inactive / Not Installed${NC}"
    fi
    
    # Feature B: Failed SSH Login Attempts (Last 24 Hours)
    FAILED_LOGINS=$(journalctl _SYSTEMD_UNIT=ssh.service --since "24 hours ago" 2>/dev/null | grep -c "Failed password")
    if [ "$FAILED_LOGINS" -gt 50 ]; then
        FAILED_STR="${RED}${FAILED_LOGINS} attempts!${NC}"
    elif [ "$FAILED_LOGINS" -gt 0 ]; then
        FAILED_STR="${YELLOW}${FAILED_LOGINS} attempts${NC}"
    else
        FAILED_STR="${GREEN}0 attempts${NC}"
    fi
    
    # ------------------------------------------------------------------------------
    # 3. DISPLAY OUTPUT
    # ------------------------------------------------------------------------------
    echo -e "${BLUE}============================================================${NC}"
    echo -e " ${BOLD}System Status for:${NC} ${CYAN}${HOSTNAME}${NC} (${IP_LOCAL})"
    echo -e "${BLUE}------------------------------------------------------------${NC}"
    echo -e "  OS Kernel : ${KERNEL}"
    echo -e "  Uptime    : ${UPTIME}"
    echo -e "  Load Avg  : ${LOAD}"
    echo -e "  Memory    : ${MEM_USED}MB / ${MEM_TOTAL}MB (${MEM_PCT}%)"
    echo -e "  Disk (/)  : ${DISK_USED} / ${DISK_TOTAL} (${DISK_PCT})"
    echo -e "${BLUE}------------------------------------------------------------${NC}"
    echo -e "  Wazuh     : ${WAZUH_STATUS}"
    echo -e "  SSH Security (24h) : ${FAILED_STR} failed logins"
    echo -e "  ${YELLOW}${BOLD}REMINDER:${NC}DO NOT FORGET TO DO MANUAL WORDPRESS UPDATES"
    echo -e "  https://fjgraf.fbx.one/2026/01/02/wp-cli/"
    echo -e "${BLUE}============================================================${NC}"
    echo -e " ${BOLD}WordPress Sites Scan (/var/www):${NC}"
    
    # Find all directory paths containing a wp-config.php file
    mapfile -t WP_SITES < <(find "$BASE_DIR" -maxdepth 3 -type f -name "wp-config.php" -exec dirname {} \;)
    
    if [ ${#WP_SITES[@]} -eq 0 ]; then
        echo -e "  ${YELLOW}No WordPress installations found under ${BASE_DIR}${NC}"
    else
        for SITE_PATH in "${WP_SITES[@]}"; do
            SITE_NAME=$(basename "$SITE_PATH")
            
            # 1. Search for hidden PHP backdoors (e.g., .m982abd89.php, .ico.php) in root and subfolders
            HIDDEN_SHELLS=$(find "$SITE_PATH" -maxdepth 4 -type f -name ".*.php" 2>/dev/null | wc -l)
            
            # 2. Search for illicit executable PHP files inside wp-content/uploads/
            UPLOADS_PHP=0
            if [ -d "$SITE_PATH/wp-content/uploads" ]; then
                UPLOADS_PHP=$(find "$SITE_PATH/wp-content/uploads" -type f -name "*.php" 2>/dev/null | wc -l)
            fi
    
            # Format output per site
            echo -e "  ${BOLD}Site:${NC} ${CYAN}${SITE_NAME}${NC}"
            
            # Hidden Shell Status
            if [ "$HIDDEN_SHELLS" -gt 0 ]; then
                echo -e "   ├─ Backdoors : ${RED}CRITICAL! ${HIDDEN_SHELLS} hidden .php file(s) found!${NC}"
            else
                echo -e "   ├─ Backdoors : ${GREEN}Clean (No hidden .php files)${NC}"
            fi
    
            # Uploads PHP Status
            if [ "$UPLOADS_PHP" -gt 0 ]; then
                echo -e "   └─ Uploads   : ${RED}ALERT! ${UPLOADS_PHP} executable .php file(s) in uploads!${NC}"
            else
                echo -e "   └─ Uploads   : ${GREEN}Clean (No .php in uploads/)${NC}"
            fi
        done
    fi
    
    echo -e "${BLUE}============================================================${NC}"

    Step 3: Make the Script Executable

    Grant execution rights so PAM can run the script during authentication:

    Bash

    sudo chmod +x /etc/update-motd.d/50-admin-stats

    3. Testing and Verification

    You can test the entire MOTD assembly directly from the command line without opening a new SSH session:

    Bash

    sudo run-parts /etc/update-motd.d/

    Expected Output Example:

    Plaintext

    Linux webserver 6.1.0-51-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.177-1 (2026-07-16) x86_64
    
    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.
    
    ============================================================
     System Status for: ap-webserver.local (192.168.1.100)
    ------------------------------------------------------------
      OS Kernel : 6.1.0-51-amd64
      Uptime    : 2 days, 4 hours
      Load Avg  : 0.08 0.03 0.01
      Memory    : 1240MB / 7912MB (15.7%)
      Disk (/)  : 14G / 50G (28%)
      Wazuh     : Active (Running)
      SSH Security (24h) : 0 attempts failed logins
    ============================================================
    
    Web console: https://ap-webserver:9090/ or https://192.168.1.100:9090/
    
    Last login: Fri Jul 31 02:05:11 2026 from 192.168.15.236

    4. SSH Configuration Check

    If the MOTD output does not display during login, verify that /etc/ssh/sshd_config contains the following active settings:

    Ini, TOML

    PrintMotd yes
    UsePAM yes

    If you make any changes to sshd_config, reload the SSH daemon:

    Bash

    sudo systemctl restart ssh
  • Unbound – OpenWRT

    LuCI Web Interface.

    PackageVersionPackageVersion
    unbound-anchor1.21.0-r1libunbound1.21.0-r1
    unbound-checkconf1.21.0-r1luci-app-unbound25.250.61039~923f8d9
    unbound-control1.21.0-r1asterisk-res-resolver-unbound20.8.1-r1
    unbound-control-setup1.21.0-r1freeradius3-mod-unbound3.2.5-r3
    unbound-daemon1.21.0-r1unbound-host1.21.0-r1

    Services -> Recursive DNS -> Basic

    Configure Unbound (Services → Recursive DNS)

    1. Navigate to Services → Recursive DNS (or Services → Unbound DNS).
    2. Click on the Basic Settings tab:
      • Enabled: Check [x]
      • Listen Port: Set to 5533
      • DHCP Link: Select dnsmasq
      • Local Domain Type: Static (local only)
      • LAN DNS: Hostname, Primary Address
      • WAN DNS: Use Upstream
      • Extra DNS: Host Records
    3. Click Save & Apply at the bottom right.

    Route Dnsmasq to Unbound (Network → DHCP and DNS)

    1. Navigate to Network → DHCP and DNS.
    2. General Settings tab:
      • DNS Forwardings: Add 127.0.0.1#5533 (click the + button to save it).
    3. Advanced Settings tab:
      • DNS server port: Ensure this is set to 53 (or left blank, which defaults to 53).
      • Ignore resolve file: Check [x] (This prevents Dnsmasq from using WAN/ISP DNS servers).
    4. Click Save & Apply.

    Prevent ISP DNS Leaks (Network → Interfaces)

    1. Navigate to Network → Interfaces.
    2. Click Edit next to your WAN interface.
    3. Select the Advanced Settings tab inside the interface editor.
    4. Use DNS servers advertised by peer: Uncheck [ ].
    5. Ensure the Custom DNS servers field is completely empty.
    6. Click Save, then click Save & Apply.
    7. (Repeat Step 3 for the WAN6 interface if enabled).

  • Hermes + llama.cpp

    BREW

    Prerequisites:
    sudo apt install gcc build-essential
    
    - Run these commands in your terminal to add Homebrew to your PATH:
        echo >> /home/aidmin/.bashrc
        echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv bash)"' >> /home/aidmin/.bashrc
        eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv bash)"
    - Install Homebrew's dependencies if you have sudo access:
       For more information, see:
        https://docs.brew.sh/Homebrew-on-Linux
    - Run brew help to get started
    - Further documentation:
        https://docs.brew.sh  
    Start the llama.cpp server
    
    brew install llama.cpp
    
    llama-server -hf unsloth/Qwen3.6-27B-MTP-GGUF:
    UD-Q4_K_XL
    Configure Hermes
    
    curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
    hermes setup
    
    hermes config set model.provider custom
    hermes config set model.base_url http://127.0.0.1:8080/v1
    hermes config set model.default unsloth/Qwen3.6-27B-MTP-GGUF:
    UD-Q4_K_XL
    Run Hermes

    Make a systemd service for llama-server

    sudo nano /etc/systemd/system/llama-server.service
    [Unit]
    Description=Llama.cpp High-Performance LLM Server
    After=network.target
    
    [Service]
    Type=simple
    User=root
    ExecStart=/usr/local/bin/llama-server -m /path/to/your/gemma-4-E4B.gguf -c 32768 --host 0.0.0.0 --port 8080
    # The Magic: Auto-restart on ANY crash/freeze
    Restart=always
    RestartSec=5s
    # Prevent systemd from giving up if it loops a couple of times
    StartLimitIntervalSec=60s
    StartLimitBurst=5
    
    [Install]
    WantedBy=multi-user.target

    Save, Register, and Start It

    # Reload the systemd manager to see the new service
    sudo systemctl daemon-reload
    
    # Enable it so it boots automatically if your LXC container or Proxmox host reboots
    sudo systemctl enable llama-server
    
    # Start it right now
    sudo systemctl start llama-server

    Change models script

    systemd-llama-server.sh

    #!/usr/bin/env bash
    set -e
    
    #---------- CONFIGURATION -----------------------------
    MODEL_DIR="/home/aidmin/models_symlinks"
    SERVICE_FILE="/etc/systemd/system/llama-server.service"
    #------------------------------------------------------
    
    # 1. Verify the GGUF folder exists
    if [ ! -d "$MODEL_DIR" ]; then
        echo "❌ Error: The directory $MODEL_DIR does not exist."
        exit 1
    fi
    
    # 2. Collect all .gguf files into a list
    cd "$MODEL_DIR"
    GGUF_FILES=( *.gguf )
    
    # Check if the directory is completely empty of models
    if [ "${GGUF_FILES[0]}" == "*.gguf" ]; then
        echo "❌ Error: No .gguf files found inside $MODEL_DIR"
        exit 1
    fi
    
    echo "===================================================="
    echo "    LLAMA-SERVER INTERACTIVE MODEL SWAPPER          "
    echo "===================================================="
    echo "Scanning: $MODEL_DIR"
    echo "Please select a model number from the options below:"
    echo "----------------------------------------------------"
    
    # 3. Present the interactive menu prompt
    PS3="Select a model (or enter 'q' to cancel): "
    
    select SELECTED_MODEL in "${GGUF_FILES[@]}"; do
        if [ "$REPLY" == "q" ] || [ "$REPLY" == "Q" ]; then
            echo "Exiting without making changes."
            exit 0
        elif [ -n "$SELECTED_MODEL" ]; then
            # Valid choice made, break out of menu loop
            break
        else
            echo "❌ Invalid number choice. Please try again."
        fi
    done
    
    # Create the full absolute path of the chosen model
    FULL_MODEL_PATH="$MODEL_DIR/$SELECTED_MODEL"
    
    echo "--------------------------------------------------"
    echo "🎯 Selected: $SELECTED_MODEL"
    echo "--------------------------------------------------"
    
    # 4. Stop the active service
    echo "🛑 Stopping llama-server.service to clear VRAM..."
    sudo systemctl stop llama-server
    
    # 5. Overwrite the ExecStart line inside the service file using sed
    echo " ^=^s^} Rewriting systemd service configuration file..."
    # This regex searches for any line starting with ExecStart= and swaps it out completely
    #sudo sed -i "s|^ExecStart=.*|ExecStart=/home/linuxbrew/.linuxbrew/bin/llama-server -m $FULL_MODEL_PATH --host 0.0.0.0 --port 8080|" "$SERVICE_FILE"
    
    Updated to improve performance.
    sudo sed -i "s|^ExecStart=.*|ExecStart=/home/linuxbrew/.linuxbrew/bin/llama-server -m $FULL_MODEL_PATH --host 0.0.0.0 --port 8080 -ngl 99 --poll 0|" "$SERVICE_FILE"
    # -ngl 99 (--n-gpu-layers 99): Offloads all model layers to your GPU, drastically improving inference speed compared to CPU-only execution.
    # --poll 0: Prevents the CPU from maxing out at 100% usage during idle states while polling for incoming requests.
    
    
    # 6. Tell systemd to register our file changes
    sudo systemctl daemon-reload
    
    # 7. Start the service back up
    echo "⚡ Starting llama-server with the new model..."
    sudo systemctl start llama-server
    
    # 8. Service Verification Passthrough
    echo "⏳ Waiting 3 seconds for VRAM allocation..."
    sleep 3
    
    echo "--------------------------------------------------"
    echo "🔎 VERIFYING SYSTEM SERVICE HEALTH..."
    echo "--------------------------------------------------"
    
    if systemctl -q is-active llama-server; then
        # Get the exact runtime and process information dynamically from systemd
        ACTIVE_STATUS=$(systemctl show llama-server --property=ActiveState | cut -d= -f2)
        SUB_STATUS=$(systemctl show llama-server --property=SubState | cut -d= -f2)
        PID_NUM=$(systemctl show llama-server --property=MainPID | cut -d= -f2)
        
        echo "🟢 STATUS: SUCCESS"
        echo "📌 Service State : $ACTIVE_STATUS ($SUB_STATUS)"
        echo "🆔 Main Process ID: $PID_NUM"
        echo "🌐 Network Bind  : http://127.0.0.1:8080/v1"
        echo "--------------------------------------------------"
        echo "✅ Done! Active model swapped and healthy."
    else
        echo "🔴 STATUS: FAILED"
        echo "⚠️ Warning: llama-server failed to start cleanly."
        echo "💡 Run 'journalctl -u llama-server -n 20' to check logs."
        echo "--------------------------------------------------"
        exit 1
    fi
    echo "=================================================="
    

    Ways to Update Packages Installed with Brew

    Homebrew handles both command-line tools (formulae) and graphical applications (casks) using a unified command set.

    • Check what needs an update:Before upgrading, you can see which of your installed packages (including Llama or anything else) are outdated:
    brew outdated
    • Upgrade everything (Recommended):To update Homebrew itself and upgrade all outdated packages at once, run:
    brew update && brew upgrade
    • Upgrade a single specific package:If you only want to update one tool (for instance, just Llama) without touching anything else, specify its name:
    brew upgrade llama

    Important remote access capability

    You can run the Hermes Web UI from a client by connecting via ssh. This is done by securely forward the port from the host (where Hermes is installed) to your local machine (client). First you need to start the Web UI service:

    hermes dashboard --> command
    
    Output:
    HERMES_DASHBOARD_READY port=9119
      Hermes Web UI → http://127.0.0.1:9119

    Afther running the command, the server should be online. To stop it just ctrl + c.

    Open another terminal and run:

    ssh -L 9119:127.0.0.1:9119 your-username@<host-machine-lan-ip>

    Open the browser on your client device and navigate to http://localhost:9119. It will securely tunnel straight to Hermes running on the host machine.

    Is necessary to keep this terminal open, otherwise the connection will be closed. Logging out will close the tunnel and terminating the Hermes Web UI.

    Hermes Available commands

    +-------------------------------------------------------+
    |               (^_^)? Available Commands               |
    +-------------------------------------------------------+
    
      ── Session ──
        /new            - Start a new session (fresh session ID + history) (usage: /new [name])
        /reset          - Start a new session (fresh session ID + history) (alias for /new)
        /clear          - Clear screen and start a new session
        /redraw         - Force a full UI repaint (recovers from terminal drift)
        /history        - Show conversation history
        /save           - Save the current conversation
        /retry          - Retry the last message (resend to agent)
        /undo           - Back up N user turns and re-prompt (default 1) (usage: /undo [N])
        /title          - Set a title for the current session (usage: /title [name])
        /handoff        - Hand off this session to a messaging platform (Telegram, Discord, etc.) (usage: /handoff <platform>)
        /branch         - Branch the current session (explore a different path) (usage: /branch [name])
        /fork           - Branch the current session (explore a different path) (alias for /branch)
        /compress       - Compress conversation context (add 'here [N]' to keep recent N turns) (usage: /compress [here [N] | focus topic])
        /rollback       - List or restore filesystem checkpoints (usage: /rollback [number])
        /snapshot       - Create or restore state snapshots of Hermes config/state (usage: /snapshot [create|restore <id>|prune])
        /snap           - Create or restore state snapshots of Hermes config/state (alias for /snapshot)
        /stop           - Kill all running background processes
        /background     - Run a prompt in the background (usage: /background <prompt>)
        /bg             - Run a prompt in the background (alias for /background)
        /btw            - Run a prompt in the background (alias for /background)
        /agents         - Show active agents and running tasks
        /tasks          - Show active agents and running tasks (alias for /agents)
        /queue          - Queue a prompt for the next turn (doesn't interrupt) (usage: /queue <prompt>)
        /q              - Queue a prompt for the next turn (doesn't interrupt) (alias for /queue)
        /steer          - Inject a message after the next tool call without interrupting (usage: /steer <prompt>)
        /goal           - Set a standing goal Hermes works on across turns until achieved (usage: /goal [text | pause | resume | clear | status])
        /subgoal        - Add or manage extra criteria on the active goal (usage: /subgoal [text | remove N | clear])
        /status         - Show session info
        /resume         - Resume a previously-named session (usage: /resume [name])
        /sessions       - Browse and resume previous sessions
    
      ── Info ──
        /whoami         - Show your slash command access (admin / user)
        /profile        - Show active profile name and home directory
        /gquota         - Show Google Gemini Code Assist quota usage
        /help           - Show available commands
        /usage          - Show token usage and rate limits for the current session
        /insights       - Show usage insights and analytics (usage: /insights [days])
        /platforms      - Show gateway/messaging platform status
        /gateway        - Show gateway/messaging platform status (alias for /platforms)
        /copy           - Copy the last assistant response to clipboard (usage: /copy [number])
        /paste          - Attach clipboard image from your clipboard
        /image          - Attach a local image file for your next prompt (usage: /image <path>)
        /update         - Update Hermes Agent to the latest version
        /debug          - Upload debug report (system info + logs) and get shareable links
    
      ── Configuration ──
        /config         - Show current configuration
        /model          - Switch model for this session (usage: /model [model] [--provider name] [--global] [--refresh])
        /codex-runtime  - Toggle codex app-server runtime for OpenAI/Codex models (usage: /codex-runtime [auto|codex_app_server])
        /codex_runtime  - Toggle codex app-server runtime for OpenAI/Codex models (alias for /codex-runtime)
        /personality    - Set a predefined personality (usage: /personality [name])
        /statusbar      - Toggle the context/model status bar
        /sb             - Toggle the context/model status bar (alias for /statusbar)
        /verbose        - Cycle tool progress display: off -> new -> all -> verbose
        /footer         - Toggle gateway runtime-metadata footer on final replies (usage: /footer [on|off|status])
        /yolo           - Toggle YOLO mode (skip all dangerous command approvals)
        /reasoning      - Manage reasoning effort and display (usage: /reasoning [level|show|hide])
        /skin           - Show or change the display skin/theme (usage: /skin [name])
        /indicator      - Pick the TUI busy-indicator style (usage: /indicator [kaomoji|emoji|unicode|ascii])
        /voice          - Toggle voice mode (usage: /voice [on|off|tts|status])
        /busy           - Control what Enter does while Hermes is working (usage: /busy [queue|steer|interrupt|status])
    
      ── Tools & Skills ──
        /tools          - Manage tools: /tools [list|disable|enable] [name...] (usage: /tools [list|disable|enable] [name...])
        /toolsets       - List available toolsets
        /skills         - Search, install, inspect, or manage skills
        /bundles        - List skill bundles (aliases /<name> for multiple skills)
        /cron           - Manage scheduled tasks (usage: /cron [subcommand])
        /curator        - Background skill maintenance (status, run, pin, archive, list-archived) (usage: /curator [subcommand])
        /kanban         - Multi-profile collaboration board (tasks, links, comments) (usage: /kanban [subcommand])
        /reload         - Reload .env variables into the running session
        /reload-mcp     - Reload MCP servers from config
        /reload_mcp     - Reload MCP servers from config (alias for /reload-mcp)
        /reload-skills  - Re-scan ~/.hermes/skills/ for newly installed or removed skills
        /reload_skills  - Re-scan ~/.hermes/skills/ for newly installed or removed skills (alias for /reload-skills)
        /browser        - Connect browser tools to your live Chromium-family browser via CDP (usage: /browser [connect|disconnect|status])
        /plugins        - List installed plugins and their status
    
      ── Exit ──
        /quit           - Exit the CLI (use --delete to also remove session history) (usage: /quit [--delete])
        /exit           - Exit the CLI (use --delete to also remove session history) (alias for /quit)
    
      ⚡ Skill Commands (70 installed):
        /airtable              - Airtable REST API via curl. Records CRUD, filters, upserts.
        /architecture-diagram  - Dark-themed SVG architecture/cloud/infra diagrams as HTML.
        /arxiv                 - Search arXiv papers by keyword, author, category, or ID.
        /ascii-art             - ASCII art: pyfiglet, cowsay, boxes, image-to-ascii.
        /ascii-video           - ASCII video: convert video/audio to colored ASCII MP4/GIF.
        /audiocraft-audio-generation - AudioCraft: MusicGen text-to-music, AudioGen text-to-sound.
        /baoyu-infographic     - Infographics: 21 layouts x 21 styles (信息图, 可视化).
        /blogwatcher           - Monitor blogs and RSS/Atom feeds via blogwatcher-cli tool.
        /claude-code           - Delegate coding to Claude Code CLI (features, PRs).
        /claude-design         - Design one-off HTML artifacts (landing, deck, prototype).
        /codebase-inspection   - Inspect codebases w/ pygount: LOC, languages, ratios.
        /codex                 - Delegate coding to OpenAI Codex CLI (features, PRs).
        /comfyui               - Generate images, video, and audio with ComfyUI — install, launch, manage nodes/models, run workflows with parameter injection. Uses the official comfy-cli for lifecycle and 
    direct REST/WebSocket API for execution.
        /design-md             - Author/validate/export Google's DESIGN.md token spec files.
        /dogfood               - Exploratory QA of web apps: find bugs, evidence, reports.
        /evaluating-llms-harness - lm-eval-harness: benchmark LLMs (MMLU, GSM8K, etc.).
        /excalidraw            - Hand-drawn Excalidraw JSON diagrams (arch, flow, seq).
        /gif-search            - Search/download GIFs from Tenor via curl + jq.
        /github-auth           - GitHub auth setup: HTTPS tokens, SSH keys, gh CLI login.
        /github-code-review    - Review PRs: diffs, inline comments via gh or REST.
        /github-issues         - Create, triage, label, assign GitHub issues via gh or REST.
        /github-pr-workflow    - GitHub PR lifecycle: branch, commit, open, CI, merge.
        /github-repo-management - Clone/create/fork repos; manage remotes, releases.
        /godmode               - Jailbreak LLMs: Parseltongue, GODMODE, ULTRAPLINIAN.
        /google-workspace      - Gmail, Calendar, Drive, Docs, Sheets via gws CLI or Python.
        /heartmula             - HeartMuLa: Suno-like song generation from lyrics + tags.
        /hermes-agent          - Configure, extend, or contribute to Hermes Agent.
        /hermes-agent-skill-authoring - Author in-repo SKILL.md: frontmatter, validator, structure.
        /himalaya              - Himalaya CLI: IMAP/SMTP email from terminal.
        /huggingface-hub       - HuggingFace hf CLI: search/download/upload models, datasets.
        /humanizer             - Humanize text: strip AI-isms and add real voice.
        /jupyter-live-kernel   - Iterative Python via live Jupyter kernel (hamelnb).
        /kanban-orchestrator   - Decomposition playbook + anti-temptation rules for an orchestrator profile routing work through Kanban. The "don't do the work yourself" rule and the basic lifecycle are 
    auto-injected into every kanban worker's system prompt; this skill is the deeper playbook when you're specifically playing the orchestrator role.
        /kanban-worker         - Pitfalls, examples, and edge cases for Hermes Kanban workers. The lifecycle itself is auto-injected into every worker's system prompt as KANBAN_GUIDANCE (from 
    agent/prompt_builder.py); this skill is what you load when you want deeper detail on specific scenarios.
        /llama-cpp             - llama.cpp local GGUF inference + HF Hub model discovery.
        /llm-wiki              - Karpathy's LLM Wiki: build/query interlinked markdown KB.
        /manim-video           - Manim CE animations: 3Blue1Brown math/algo videos.
        /maps                  - Geocode, POIs, routes, timezones via OpenStreetMap/OSRM.
        /nano-pdf              - Edit PDF text/typos/titles via nano-pdf CLI (NL prompts).
        /network-system-analysis - Performs a comprehensive network and system diagnostics check. This skill is used to audit active processes (via lsof), check listening ports (via ss), review configuration 
    (via config.yaml), and verify external connections (like GitHub/Tirith). It outputs a structured report summarizing findings, security posture, and recommended actions.
        /node-inspect-debugger - Debug Node.js via --inspect + Chrome DevTools Protocol CLI.
        /notion                - Notion API + ntn CLI: pages, databases, markdown, Workers.
        /obliteratus           - OBLITERATUS: abliterate LLM refusals (diff-in-means).
        /obsidian              - Read, search, create, and edit notes in the Obsidian vault.
        /ocr-and-documents     - Extract text from PDFs/scans (pymupdf, marker-pdf).
        /opencode              - Delegate coding to OpenCode CLI (features, PR review).
        /openhue               - Control Philips Hue lights, scenes, rooms via OpenHue CLI.
        /p5js                  - p5.js sketches: gen art, shaders, interactive, 3D.
        /plan                  - Plan mode: write an actionable markdown plan to .hermes/plans/, no execution. Bite-sized tasks, exact paths, complete code.
        /polymarket            - Query Polymarket: markets, prices, orderbooks, history.
        /popular-web-designs   - 54 real design systems (Stripe, Linear, Vercel) as HTML/CSS.
        /powerpoint            - Create, read, edit .pptx decks, slides, notes, templates.
        /pretext               - Use when building creative browser demos with @chenglou/pretext — DOM-free text layout for ASCII art, typographic flow around obstacles, text-as-geometry games, kinetic 
    typography, and text-powered generative art. Produces single-file HTML demos by default.
        /python-debugpy        - Debug Python: pdb REPL + debugpy remote (DAP).
        /requesting-code-review - Pre-commit review: security scan, quality gates, auto-fix.
        /research-paper-writing - Write ML papers for NeurIPS/ICML/ICLR: design→submit.
        /segment-anything-model - SAM: zero-shot image segmentation via points, boxes, masks.
        /serving-llms-vllm     - vLLM: high-throughput LLM serving, OpenAI API, quantization.
        /sketch                - Throwaway HTML mockups: 2-3 design variants to compare.
        /songsee               - Audio spectrograms/features (mel, chroma, MFCC) via CLI.
        /songwriting-and-ai-music - Songwriting craft and Suno AI music prompts.
        /spike                 - Throwaway experiments to validate an idea before build.
        /systematic-debugging  - 4-phase root cause debugging: understand bugs before fixing.
        /teams-meeting-pipeline - Operate the Teams meeting summary pipeline via Hermes CLI — summarize meetings, inspect pipeline status, replay jobs, manage Microsoft Graph subscriptions.
        /test-driven-development - TDD: enforce RED-GREEN-REFACTOR, tests before code.
        /touchdesigner-mcp     - Control a running TouchDesigner instance via twozero MCP — create operators, set parameters, wire connections, execute Python, build real-time visuals. 36 native tools.
        /weights-and-biases    - W&B: log ML experiments, sweeps, model registry, dashboards.
        /xurl                  - X/Twitter via xurl CLI: post, search, DM, media, v2 API.
        /youtube-content       - YouTube transcripts to summaries, threads, blogs.
        /yuanbao               - Yuanbao (元宝) groups: @mention users, query info/members.
    
  • Expand hard disk on Hyper-v ubuntu vm

    Once you have expanded the virtual hard disk (.vhdx) in Hyper-V, start up your Ubuntu virtual machine.

    Because Ubuntu installations typically use LVMs by default, the process requires expanding the partition, resizing the physical volume, extending the logical volume, and finally resizing the filesystem.

    Follow these steps directly inside your Ubuntu terminal:

    Step 1: Install Cloud-Guest-Utils (if not already installed)

    This package includes the growpart utility, which makes resizing partitions safe and easy.

    sudo apt update
    sudo apt install cloud-guest-utils

    Step 2: Identify Your Partition Layout

    Run the following command to check your disk structure:

    sudo lvmdiskscan

    Look for your main disk (usually /dev/sda) and find the partition number that acts as the LVM physical volume (typically /dev/sda3 or /dev/sda2).

    Step 3: Expand the Partition

    Use growpart to expand the partition into the newly available unallocated space. (Note the space between the disk name and the partition number).

    sudo growpart /dev/sda 3

    (Replace 3 with your actual partition number found in Step 2 if it differs).

    Step 4: Resize the LVM Physical Volume (PV)

    Tell LVM that the underlying partition has grown:

    sudo pvresize /dev/sda3

    (Again, replace /dev/sda3 with your specific partition path).

    Step 5: Extend the Logical Volume (LV)

    Next, expand your logical volume to consume 100% of the newly freed space. On standard Ubuntu installations, the root logical volume path is usually /dev/ubuntu-vg/ubuntu-lv.

    sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv

    Step 6: Resize the Filesystem

    Finally, expand the actual filesystem so Ubuntu recognizes the usable space:

    sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv

    Step 7: Verify the Expansion

    Check your updated storage capacity:

    df -h

    Your root file system (/) should now reflect the full expanded size you allocated in Hyper-V.

  • openfortivpn

    Fortinet VPN with Docker: Setup Guide

    Overview of Architecture

    • openfortivpn-webview (Host): Electron app that opens Microsoft SAML/SSO login, handles MFA, and captures the SVPNCOOKIE.
    • openfortivpn (Docker): Lightweight ubuntu container that creates the actual ppp0 VPN network tunnel using the captured cookie.

    Step 1: Directory Structure

    Set up your workspace with the following file structure:

    fortinet-docker-vpn/
    ├── Dockerfile.alpine
    ├── Dockerfile.ubuntu
    ├── config
    ├── openfortivpn-connect.sh
    └── openfortivpn-webview/   (Cloned automatically by script)
    

    Dockerfile.ubuntu

    FROM ubuntu:latest
    
    # Update repositories and install dependencies using apt
    RUN apt-get update && apt-get install -y \
        openfortivpn \
        ppp \
        ca-certificates \
        iproute2 \
        && rm -rf /var/lib/apt/lists/*
    
    WORKDIR /home/admin/docker/openfortivpn
    
    COPY config /home/admin/docker/openfortivpn/config
    
    ENTRYPOINT ["openfortivpn", "-c", "/home/admin/docker/openfortivpn/config"]

    Dockerfile.alpine

    FROM alpine:latest
    
    # Install openfortivpn and network dependencies
    RUN apk add --no-cache \
         --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing \
        --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
        --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main \
        openfortivpn ppp ca-certificates iproute2
    
    WORKDIR /home/admin/docker/openfortivpn
    # Copy config into container
    COPY config /home/admin/docker/openfortivpn/config
    
    # Launch openfortivpn using local config
    ENTRYPOINT ["openfortivpn", "-c", "/home/admin/docker/openfortivpn/config"]

    Step 2: Create Configuration Files

    File 1: config

    Create a file named config in your project folder:

    # Fortinet Gateway Address & Port
    host = vpn.dawsoncollege.qc.ca
    port = 10443
    username = username@dawaoncollege.qc.ca #email
    #Password = # Optional
    # (Optional) Insert trusted certificate hash if prompted by gateway:
    # trusted-cert = 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef

    Step 3: Create the Master Automation Script (openfortivpn-connect.sh

    #!/bin/bash
    set -e
    #set -x
    
    # Work strictly relative to script location
    SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    cd "$SCRIPT_DIR"
    
    VPN_HOST="vpn.dawsoncollege.qc.ca:10443"
    #IMAGE_NAME="fortinet-vpn"
    #WEBVIEW_DIR="/home/admin/docker/openfortivpn/openfortivpn-webview/openfortivpn-webview-electron"
    WEBVIEW_DIR="$SCRIPT_DIR/openfortivpn-webview/openfortivpn-webview-electron"
    
    # 0. Distro Selection (Interactive prompt if no argument provided)
    CHOICE="$1"
    
    if [ -z "$CHOICE" ]; then
      echo "=========================================="
      echo " Select Docker Distro Image"
      echo " REMEMBER TO CLOSE THE BROWSER WINDOW "
      echo " TO CONTINUE WITH THE SCRIPT PROCESS "
      echo "=========================================="
      PS3="Please enter your choice (1-2): "
      options=("alpine" "ubuntu")
      select opt in "${options[@]}"; do
        case $opt in
          "alpine"|"ubuntu")
            CHOICE="$opt"
            break
            ;;
          *)
            echo "Invalid option $REPLY. Please choose 1 or 2."
            ;;
        esac
      done
      echo ""
    fi
    
    case "$CHOICE" in
      alpine)
        DOCKERFILE="Dockerfile.alpine"
        IMAGE_NAME="fortinet-vpn:alpine"
        ;;
      ubuntu)
        DOCKERFILE="Dockerfile.ubuntu"
        IMAGE_NAME="fortinet-vpn:ubuntu"
        ;;
      *)
        echo "Error: Unknown target distro '$CHOICE'. Use 'alpine' or 'ubuntu'."
        exit 1
        ;;
    esac
    
    # 0. Distro Selection (Defaults to 'alpine', accepts 'ubuntu' as argument: ./openfortivpn-connect.sh ubuntu)
    #CHOICE="${1:-alpine}"
    
    #case "$CHOICE" in
    #  alpine)
    #    DOCKERFILE="Dockerfile.alpine"
    #    IMAGE_NAME="fortinet-vpn:alpine"
    #    ;;
    #  ubuntu)
    #    DOCKERFILE="Dockerfile.ubuntu"
    #    IMAGE_NAME="fortinet-vpn:ubuntu"
    #    ;;
    #  *)
    #    echo "Error: Unknown target distro '$CHOICE'. Use 'alpine' or 'ubuntu'."
    #    exit 1
    #    ;;
    #esac
    
    echo "=========================================="
    echo "    Fortinet SSO VPN Automated Launcher    "
    echo "=========================================="
    
    # 1. Verify Prerequisites
    echo "[1/4] Checking host dependencies..."
    for cmd in docker node npm git; do
      if ! command -v $cmd &> /dev/null; then
        echo "Error: '$cmd' is not installed. Please install it first."
        exit 1
      fi
    done
    
    # Check PPP device
    if [ ! -e /dev/ppp ]; then
      echo "Setting up /dev/ppp..."
      sudo mknod /dev/ppp c 108 0 2>/dev/null || true
      sudo chmod 666 /dev/ppp 2>/dev/null || true
    fi
    
    # 2. Check/Build Docker Image
    echo "[2/4] Checking Docker image '$IMAGE_NAME' (using $DOCKERFILE)..."
    if ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
      echo "Docker image not found. Building $IMAGE_NAME from $SCRIPT_DIR..."
      
      if [ ! -f "$SCRIPT_DIR/$DOCKERFILE" ] || [ ! -f "$SCRIPT_DIR/config" ]; then
        echo "Error: Missing '$DOCKERFILE' or 'config' in $SCRIPT_DIR."
        exit 1
      fi
    #/home/admin/docker/openfortivpn
      docker build -f "$SCRIPT_DIR/$DOCKERFILE" -t "$IMAGE_NAME" "$SCRIPT_DIR"
    else
      echo "Docker image '$IMAGE_NAME' is ready."
    fi
    
    # 3. Setup openfortivpn-webview
    echo "[3/4] Preparing SAML authenticator..."
    if [ ! -d "$WEBVIEW_DIR" ]; then
      echo "Cloning openfortivpn-webview repository..."
      git clone https://github.com/gm-vm/openfortivpn-webview.git "$WEBVIEW_DIR"
      cd "$WEBVIEW_DIR"
      npm install
      cd ..
    else
      # Ensure dependencies are installed
      if [ ! -d "$WEBVIEW_DIR/node_modules" ]; then
        echo "Installing webview dependencies..."
        cd "$WEBVIEW_DIR"
        npm install
        cd ..
      fi
    fi
    
    # 4. Authenticate & Extract Cookie
    echo "[4/4] Launching SSO Browser window. Please log in..."
    cd "$WEBVIEW_DIR"
    
    # Capture webview output to isolate the SVPNCOOKIE value
    RAW_COOKIE=$(npx electron . "$VPN_HOST" --keep-open 2>&1 | grep -oP 'SVPNCOOKIE=\K[^\s]+' | tail -n 1)
    
    cd ..
    
    if [ -z "$RAW_COOKIE" ]; then
      echo "Error: Failed to capture SVPNCOOKIE. Did authentication complete?"
      exit 1
    fi
    
    echo ""
    echo "=========================================="
    echo " Authentication successful!"
    echo " Starting VPN Tunnel..."
    echo " Press Ctrl+C to disconnect."
    echo "=========================================="
    echo ""
    
    # 5. Launch Docker VPN
    docker run --rm -it \
      --cap-add=NET_ADMIN \
      --device=/dev/ppp \
      --net=host \
      "$IMAGE_NAME" --cookie="$RAW_COOKIE"

    Make it executable once:

    chmod +x openproxyvpn-connect.sh

    Step 4: Daily Operational Commands

    Connect

    ./openfortivpn.sh

    Disconnect

    Press Ctrl + C in the terminal running the script.

    Reset Stale/Saved SSO Sessions (Force Fresh Login)

    If you get stuck on “Already Logged In” or need to switch accounts, clear Electron’s session cache:

    # On Linux:
    rm -rf ~/.config/openfortivpn-webview
    
    # On macOS:
    rm -rf ~/Library/Application\ Support/openfortivpn-webview
  • Speedtest – ookla

    Download the official Linux x86_64 tarball

    curl -O https://install.speedtest.net/app/cli/ookla-speedtest-1.2.0-linux-x86_64.tgz

    Extract the binary

    tar -xvf ookla-speedtest-1.2.0-linux-x86_64.tgz

    Run it directly without root privileges

    ./speedtest
  • Firejail Cheat Sheet

    ####################################################
    #                    COMPREHENSIVE FIREJAIL CLI CHEAT SHEET                   #
    ####################################################
    
    # 1. NETWORK CONTROL & BANDWIDTH SHAPING
    # -----------------------------------------------------------------------------
    firejail --net=none name-of-app                  # Run app with NO internet access
    firejail --net=lo name-of-app                    # Run app with local loopback interface only
    firejail --dns=1.1.1.1 name-of-app               # Force app to use a specific DNS server
    firejail --net=eth0 --bandwidth=eth0:down:1mbps  # Shape network; cap download speed at 1mbps
    
    # 2. FILE SYSTEM ISOLATION (EPHEMERAL VS. PERSISTENT)
    # -----------------------------------------------------------------------------
    firejail --private name-of-app                   # Ephemeral: Blank home directory (wiped on close)
    firejail --private=/home/user/isolated_folder/ \
             name-of-app                             # Persistent: Lock app data into a specific real folder
    firejail --read-only=~/Documents name-of-app     # Mount a target directory as read-only
    firejail --blacklist=~/.ssh name-of-app          # Hide a sensitive folder completely from the app
    firejail --whitelist=~/Downloads name-of-app     # Restrictive: ONLY allow access to Downloads, hide rest of home
    
    # 3. DISPLAY & HARDWARE GRAPHICS SECURITY
    # -----------------------------------------------------------------------------
    firejail --nodisplay name-of-app                 # Completely block access to X11/Wayland display servers
    firejail --x11=xpra name-of-app                  # Sandbox X11: Prevent app from keylogging other open windows
    firejail --nortcwd name-of-app                   # Mask the real current working directory from the app
    firejail --nou2f name-of-app                     # Disable access to U2F/Yubikey security keys
    
    # 4. RESOURCE LIMITS & PERFORMANCE (CGROUPS)
    # -----------------------------------------------------------------------------
    firejail --cpu=0,1 name-of-app                   # CPU Pinning: Force app to run ONLY on CPU cores 0 and 1
    firejail --rlimit-as=1g name-of-app              # RAM Cap: Kill the process if it uses more than 1GB of memory
    firejail --nice=10 name-of-app                   # Lower process priority so it doesn't slow down your PC
    
    # 5. ADVANCED SYSTEM LOCKDOWNS
    # -----------------------------------------------------------------------------
    firejail --noroot name-of-app                    # Strip app of any ability to gain root/sudo privileges
    firejail --seccomp name-of-app                   # Block dangerous, non-standard Linux system calls
    firejail --caps.drop=all name-of-app             # Drop all Linux kernel capabilities for the process
    firejail --net=none --private --noroot app-name  # Hardcore "prison" combo (No net, no home, no root)
    
    # 6. MONITORING & MANAGING ACTIVE SANDBOXES
    # -----------------------------------------------------------------------------
    firejail --list                                  # List all running sandboxes and their PIDs
    firejail --tree                                  # View a live process tree inside active sandboxes
    firejail --netstats                              # View real-time bandwidth/network stats
    firejail --shutdown=12345                        # Forcefully kill a sandbox by its PID
    
    # 7. INTEGRATION & PERMANENT CONFIGURATIONS
    # -----------------------------------------------------------------------------
    sudo firecfg                                     # Automatically route desktop shortcuts through firejail
    sudo firecfg --clean                             # Undo all permanent desktop firejail symlinks
  • App isolation with Namespace

    Isolation of firefox from the internet script to communicate only with openwebui or llama UI.

    #!/bin/bash
    
    # --- CONFIGURATION ---
    NS_NAME="restricted_net"
    VETH_HOST="172.200.1.1"
    VETH_NS="172.200.1.2"
    TARGET_IP="192.168.1.100"
    
    # 1. CLEANUP (Wipe the slate clean)
    echo "Cleaning up..."
    sudo ip netns delete $NS_NAME 2>/dev/null
    sudo ip link delete veth-host 2>/dev/null
    # Clean old NAT/Forward rules to avoid "Rule already exists"
    sudo iptables -t nat -D POSTROUTING -s $VETH_NS/32 -j MASQUERADE 2>/dev/null
    sudo iptables -D FORWARD -s $VETH_NS/32 -j ACCEPT 2>/dev/null
    sudo iptables -D FORWARD -d $VETH_NS/32 -j ACCEPT 2>/dev/null
    
    # 2. CREATE NAMESPACE & LINK
    sudo ip netns add $NS_NAME
    sudo ip link add veth-host type veth peer name veth-ns
    sudo ip link set veth-ns netns $NS_NAME
    
    # 3. ADDRESSING
    sudo ip addr add $VETH_HOST/24 dev veth-host
    sudo ip netns exec $NS_NAME ip addr add $VETH_NS/24 dev veth-ns
    
    # 4. BRING UP INTERFACES
    sudo ip link set veth-host up
    sudo ip netns exec $NS_NAME ip link set veth-ns up
    sudo ip netns exec $NS_NAME ip link set lo up
    
    # 5. ROUTING: The "Precision" Path
    # We tell the bubble: "To reach your specific target, go through the host."
    sudo ip netns exec $NS_NAME ip route add $TARGET_IP/32 via $VETH_HOST dev veth-ns
    
    # 6. SYSTEM-WIDE FORWARDING (Kernel Level)
    sudo sysctl -w net.ipv4.ip_forward=1 > /dev/null
    
    # 7. FIREWALL: Open the "Gates"
    # This tells the Debian firewall to allow the traffic to pass through the host
    sudo iptables -A FORWARD -s $VETH_NS/32 -j ACCEPT
    sudo iptables -A FORWARD -d $VETH_NS/32 -m state --state ESTABLISHED,RELATED -j ACCEPT
    
    # 8. NAT (MASQUERADE)
    # This makes the target see your host's IP instead of the 10.200.x.x IP
    sudo iptables -t nat -A POSTROUTING -s $VETH_NS/32 -j MASQUERADE
    
    # 9. PERMISSION TO DRAW WINDOWS
    xhost +local: > /dev/null
    
    echo "-------------------------------------------------------"
    echo "Targeting: $TARGET_IP"
    echo "Testing path with 2 pings..."
    sudo ip netns exec $NS_NAME ping -c 2 -W 2 $TARGET_IP
    echo "-------------------------------------------------------"
    
    # 10. LAUNCH FIREFOX
    # --no-remote is CRITICAL
    # sudo ip netns exec $NS_NAME sudo -u $USER firefox --no-remote         # No Auto Redirection
    sudo ip netns exec $NS_NAME sudo -u $USER firefox --no-remote "http://192.168.1.100:8080"
    

    Now lets configure a new shortcut so that when firefox executes, the script to run the namespace configuration is initialized every time it’s launched leaving firefox in its own bubble. Your sudo password will be prompted.

    Save the following config in:

    .local/share/applications/vault-firefox.desktop
      GNU nano 8.4            .local/share/applications/vault-firefox.desktop                     
    [Desktop Entry]
    Categories=Network;WebBrowser;
    Comment=Isolated Firefox for VM Access
    Exec=kdesu /home/bee/namespace/LAN-only-Firefox.sh
    Icon=firefox-esr-symbolic
    Name=Firefox Vault
    NoDisplay=false
    Path=
    PrefersNonDefaultGPU=false
    StartupNotify=true
    Terminal=false
    TerminalOptions=
    Type=Application
    X-KDE-SubstituteUID=false
    X-KDE-Username=
    
  • Force QUAD9 DNS

    Network Configuration Summary: Dual-Interface DNS

    1. The Strategy

    • Uniform DNS: Force Quad9 on both Wired and Wireless profiles to prevent DNS “Race Conditions.”
    • Predictable Routing: Use Route Metrics to ensure the Wired connection is always preferred over Wi-Fi when both are active.
    • Isolation: Ignore ISP-provided DNS settings entirely.

    Verify from where NetworkManager is getting its DNS. Usually Netowrk Manager will get DNS configuration from DHCP server. If your secondary network adapter (in this case a wifi adapter connected to the ISP router) is getting ISP DNSs, that might conflict with the default configuration of the wired adapter and force it to use the ISP DNS. We dont want this as we want to use QUAD9 DNS.

    sudo ls /var/lib/NetworkManager/

    Verify current DNS configuration:

    cat /etc/resolv.conf 

    2. Implementation Commands

    A. Wired Connection (Primary)

    Goal: Highest priority, direct Quad9 access.

    # Set DNS and Ignore DHCP DNS
    nmcli con mod "Wired connection 1" ipv4.dns "9.9.9.9, 149.112.112.112"
    nmcli con mod "Wired connection 1" ipv4.ignore-auto-dns yes
    
    # Set Metric to 100 (Lower = Higher Priority)
    nmcli con mod "Wired connection 1" ipv4.route-metric 100
    
    # Apply
    nmcli con up "Wired connection 1"
    

    B. Wireless Connection (Backup)

    Secondary priority, still forced to Quad9.

    # Set DNS and Ignore DHCP DNS
    nmcli con mod "ISP_WiFi_Name" ipv4.dns "9.9.9.9, 149.112.112.112"
    nmcli con mod "ISP_WiFi_Name" ipv4.ignore-auto-dns yes
    
    # Set Metric to 600 (Higher = Lower Priority)
    nmcli con mod "ISP_WiFi_Name" ipv4.route-metric 600
    
    # Apply
    nmcli con up "ISP_WiFi_Name"
    

    3. Operational Behavior

    ScenarioData PathDNS ProviderLatency
    Only Wired ActiveOpenWRTQuad9Instant
    Only Wireless ActiveISP RouterQuad9Instant
    Both ActiveWired (via Metric 100)Quad9Instant

    4. Troubleshooting & Verification

    • Check DNS Order: cat /etc/resolv.confIf both interfaces are up, you should see only Quad9 IPs. If you see the ISP router IP, ipv4.ignore-auto-dns was not set correctly.
    • Check Active Metrics: ip route show defaultLook for the metric value. The interface with the lowest number is your active internet path.
    • Force Refresh: If settings don’t seem to apply:Bashsudo systemctl restart NetworkManager

    5. Key Parameters for Records

    • Quad9 Primary: 9.9.9.9
    • Quad9 Secondary: 149.112.112.112
    • Preferred Metric: 100 (Wired)
    • Failover Metric: 600 (Wireless)

    Note: Always use the specific connection names found in nmcli con show when running these commands. If your SSID has spaces, wrap it in quotes: "My ISP WiFi".