Sunday, September 27, 2026

How Google Antigravity Solved the Mysterious NVIDIA Sleep Reboot on My Dell Inspiron 7567 (Ubuntu Linux)

If you run modern Ubuntu or Linux on a Dell Inspiron 15 Gaming (7567) or a similar 7th-gen Intel laptop paired with an NVIDIA GeForce GTX 1050 / 1050 Ti (Pascal architecture), you may have encountered an infuriating problem: putting the laptop to sleep causes an abrupt reboot, either about 14 seconds into sleep or the moment you open the lid to wake it up.

For months, the standard advice across Linux forums has been to "switch to hibernate," "disable sleep entirely," or "turn off the NVIDIA card." None of those are real fixes. Instead of giving up on proper S3 sleep, I paired with Google Antigravity—an agentic AI assistant capable of running system diagnostics and kernel-level debugging—to investigate the root cause down to the ACPI firmware bytecode. Here is what was actually causing the failure, and the permanent 4-step fix that resolved it.


The Symptoms: Unclean Reboots During Sleep

Analyzing system logs across 24 boots on Ubuntu with kernel 7.0.0-generic and the NVIDIA proprietary driver (580 series) revealed 15 boots that ended in an unclean hard reset:

  • Pattern A (14-second watchdog reset): The laptop entered S3 deep sleep (PM: suspend entry (deep)). Exactly 14.3 seconds later, the hardware watchdog tripped an immediate reset. The laptop would reboot and sit unattended at the GDM login screen for hours.
  • Pattern B (Overnight wake reset): The laptop stayed asleep in S3 through the night, but the instant the lid was opened or power button pressed in the morning, the machine suffered a hard reset during S3 resume.

The Culprit Found by Antigravity: Dell BIOS ACPI AML Bug

Antigravity inspected the kernel journal, PCI subsystem states, and decompiled the laptop's ACPI tables (DSDT and SSDT3 / PegSsdt). It uncovered a critical firmware bug in Dell's CBX3 BIOS when Linux transitions the PCIe Root Port (0000:00:01.0 / \_SB.PCI0.PEG0) into D3cold:

  1. Entering D3cold: When putting the machine to sleep, Linux places the PCIe root port into D3cold, evaluating Dell's ACPI method PG00._OFF(), which cuts GPU rail GPIO power and marks the power resource as off (PG00._STA = 0).
  2. The Infinite Loop on Resume: Upon waking, Linux calls PG00._ON() during early device resume (dpm_resume_noirq) with interrupts disabled. In Dell's bytecode, PGON(0) checks if the power rail GPIO is high. Because firmware already re-powered the rail, it returns early without re-enabling or retraining the PCIe link!
  3. Immediately after, PG00._ON() executes:
    While ((VEID != 0x10DE))
    {
        Sleep (One)
    }
    Because the link was never retrained, reading VEID (the NVIDIA PCI Vendor ID) over MMIO returns 0xFFFF. The kernel hangs forever in this infinite loop until the Linux NMI watchdog resets the system ~14 seconds later!
Why doesn't this happen in Windows? Windows does not transition the parent PCIe root port into ACPI D3cold during S3 sleep; it leaves it in D3hot, keeping PG00 powered on and completely avoiding the buggy BIOS AML loop.

Additionally, Antigravity identified two secondary conflicts on modern Ubuntu:

  • systemd-sleep user session freezing: systemd-sleep freezes user.slice before suspend and keeps it frozen while NVIDIA's post-resume scripts try to switch virtual terminals (chvt 2), deadlocking against gnome-shell under Wayland.
  • Conflicting DRM framebuffers: The NVIDIA module defaulted to nvidia_drm.fbdev=1, creating a competing fb0: nvidia-drmdrmfb alongside Intel's i915drmfb during atomic KMS modesetting.

The 4-Step Solution

To eliminate these issues cleanly and permanently, Antigravity implemented four targeted adjustments:

1. Prevent D3cold via Persistent Udev Rule

By forcing d3cold_allowed=0 and power/control=on across the PCIe Root Port and the NVIDIA devices, Linux keeps them in D3hot during S3 suspend. ACPI power resource PG00 remains on, completely bypassing the buggy While (VEID != 0x10DE) code path.

Create /etc/udev/rules.d/80-nvidia-pm-fix.rules:

# Prevent D3cold (buggy ACPI PG00._OFF / PG00._ON infinite VEID loop in SSDT3)
# on Intel PCIe Root Port PEG0 (0000:00:01.0) and NVIDIA GTX 1050 Ti (0000:01:00.0 / 0000:01:00.1)
ACTION=="add|bind|change", SUBSYSTEM=="pci", KERNEL=="0000:00:01.0", ATTR{d3cold_allowed}="0", ATTR{power/control}="on", ATTR{power/wakeup}="disabled"
ACTION=="add|bind|change", SUBSYSTEM=="pci", KERNEL=="0000:01:00.0", ATTR{d3cold_allowed}="0", ATTR{power/control}="on"
ACTION=="add|bind|change", SUBSYSTEM=="pci", KERNEL=="0000:01:00.1", ATTR{d3cold_allowed}="0", ATTR{power/control}="on"

2. Disable Spurious PEG0 ACPI Wakeups via Systemd

Create a oneshot systemd service at /etc/systemd/system/disable-peg0-wakeup.service:

[Unit]
Description=Disable ACPI wakeup on PEG0 and keep NVIDIA power states consistent
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'if grep -q "^PEG0.*\*enabled" /proc/acpi/wakeup; then echo PEG0 > /proc/acpi/wakeup; fi; for dev in 0000:00:01.0 0000:01:00.0 0000:01:00.1; do [ -d "/sys/bus/pci/devices/$dev" ] && echo 0 > "/sys/bus/pci/devices/$dev/d3cold_allowed" 2>/dev/null && echo on > "/sys/bus/pci/devices/$dev/power/control" 2>/dev/null; done'
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable --now disable-peg0-wakeup.service

3. Prevent User Session Freezing Before NVIDIA Resume

Prevent systemd-sleep from deadlocking frozen user sessions during VT switches:

for svc in systemd-suspend.service systemd-hibernate.service systemd-suspend-then-hibernate.service; do
    sudo mkdir -p "/etc/systemd/system/${svc}.d"
    echo -e '[Service]
Environment="SYSTEMD_SLEEP_FREEZE_USER_SESSIONS=false"' | sudo tee "/etc/systemd/system/${svc}.d/override.conf"
done

4. Set nvidia_drm fbdev=0 and Update Initramfs

In /etc/modprobe.d/nvidia-graphics-drivers-kms.conf, set:

options nvidia_drm modeset=1 fbdev=0
options nvidia NVreg_PreserveVideoMemoryAllocations=1
options nvidia NVreg_TemporaryFilePath=/var

Then rebuild initramfs and reboot:

sudo update-initramfs -u -k all
sudo reboot

Verification: 11+ Hours of Clean Overnight Sleep

After applying the fix and rebooting into kernel 7.0.0-34-generic, we ran multiple sleep tests. The system journal confirmed:

  • Test 1 (Short sleep): Slept 35 seconds → resumed cleanly.
  • Test 2 (Medium sleep): Slept 1 hour 38 minutes → resumed cleanly.
  • Test 3 (Overnight test): Slept in S3 deep sleep for 11 hours and 20 minutes continuously → woke up instantly with zero crashes, zero resets, and both internal display and external HDMI functioning perfectly!

Final Thoughts on AI-Assisted Troubleshooting

Hardware power management issues on Linux often feel like impenetrable black boxes. Working with Google Antigravity showed the real power of pairing with an autonomous AI: rather than relying on generic recommendations, it analyzed the exact system journal timestamps, decompiled Dell's ACPI tables, identified the exact assembly instruction causing the hang, and generated a tailored, verified fix.

If you are struggling with unexplained sleep reboots on a Dell Inspiron 7567 or similar Pascal-era hybrid graphics laptop, give this 4-step configuration a try!

Tuesday, July 8, 2025

How to Stream RTSP IP Cameras and USB Webcams to the Browser with JSMpeg and WebSockets

Commercial NVRs and cloud-based streaming services often impose high latency, heavy resource overhead, or recurring subscriptions. If you have an IP camera (such as a Hikvision IPC-B120 with an RTSP stream) or a USB webcam (such as a Logitech BRIO), you can stream live video directly to any modern browser with low latency and zero plugins.

Architecture Overview

This lightweight pipeline converts video on-demand using three core components:

  • FFmpeg: Pulls the RTSP feed or V4L2 webcam stream, encodes it as an MPEG-TS container with MPEG-1 video (mpeg1video) and MP2 audio, and pipes stdout to websocat.
  • websocat: Listens as an on-demand WebSocket server. When a browser client connects, websocat launches FFmpeg and streams the video chunks over WebSocket. When the browser tab closes, websocat terminates FFmpeg automatically—consuming 0% CPU when idle!
  • JSMpeg: A tiny JavaScript decoder that renders MPEG-1 video frames directly onto an HTML5 <canvas> element in the browser.

1. RTSP Camera Service (/etc/systemd/system/rtsp-ws.service)

Create a systemd service for your Hikvision or ONVIF RTSP camera:

[Unit]
Description=On-demand WebSocket->FFmpeg bridge for JSMpeg RTSP
After=network.target

[Service]
User=pi
Group=pi
ExecStart=/usr/local/bin/websocat \
  --binary \
  --exit-on-eof \
  -s 127.0.0.1:10000 \
  sh-c:'/usr/bin/ffmpeg -hide_banner -loglevel error -rtsp_transport tcp -i "rtsp://user:pass@192.168.1.100:554/Streaming/Channels/101" -f mpegts -codec:v mpeg1video -bf 0 -r 25 pipe:1'
Restart=on-failure

[Install]
WantedBy=multi-user.target

2. USB Webcam Service (/etc/systemd/system/webcam-ws.service)

Create a systemd service for a local V4L2 USB camera (e.g. Logitech BRIO) with ALSA microphone audio:

[Unit]
Description=On-demand WebSocket->FFmpeg bridge for Logitech Brio
After=network.target

[Service]
User=pi
ExecStart=/usr/local/bin/websocat \
  --binary \
  --exit-on-eof \
  -s 127.0.0.1:10001 \
  sh-c:'WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 /usr/bin/ffmpeg -hide_banner -loglevel error \
    -f v4l2 -framerate 30 -video_size 1920x1080 -i /dev/video0 \
    -f alsa -ac 2 -ar 44100 -i default \
    -f mpegts -codec:v mpeg1video -bf 0 -r 30 -codec:a mp2 pipe:1'
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable and start the services:

sudo systemctl daemon-reload
sudo systemctl enable --now rtsp-ws webcam-ws

3. Frontend HTML5 Player (player.html)

Serve this minimalist HTML5 page with JSMpeg to view the stream from any desktop or mobile browser:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Live Camera Stream (JSMpeg)</title>
  <style>
    html, body {
      margin: 0; padding: 0;
      width: 100vw; height: 100vh;
      background: #000;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .player-container {
      width: 90vw;
      max-width: 1280px;
      max-height: 90vh;
    }
    .player-container canvas {
      width: 100%;
      height: auto;
      display: block;
    }
  </style>
</head>
<body>
  <div class="player-container">
    <canvas id="videoCanvas"></canvas>
  </div>

  <script src="/jsmpeg.min.js"></script>
  <script>
    const protocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
    const url = protocol + location.host + '/my-ws-prefix';

    new JSMpeg.Player(url, {
      canvas: document.getElementById('videoCanvas'),
      autoplay: true,
      audio: false
    });
  </script>
</body>
</html>

Sunday, May 25, 2025

How to Configure an Nginx Reverse Proxy with WebSockets for Kodi Chorus2

This tutorial details how to configure Nginx as a secure reverse proxy for Kodi's Chorus2 web interface, enabling full remote media playback control over HTTPS under a subpath (e.g., https://domain.com/kodi/).

Before configuring Nginx, enable the Kodi Web Server in Settings → Services → Control and set a secure username and password, as documented in the Kodi Web Interface Wiki.

The Nginx URI Normalization Challenge

Kodi Chorus2 requires two distinct routing configurations:

  1. WebSocket JSON-RPC (port 9090): Real-time playback status and control events route through /jsonrpc without path prefixing.
  2. Web Assets & Album Art (port 10080 / 8080): Chorus2 fetches album artwork and movie posters using dynamic queries. Under Nginx's proxy_pass specification, naive subpath proxying will corrupt the image query string unless the normalized request URI is explicitly matched and forwarded.

Nginx Configuration (/etc/nginx/sites-available/kodi.conf)

Add the following location blocks to your Nginx HTTPS server block:

## Kodi Reverse Proxy Configuration

# 1) WebSocket channel for JSON-RPC
location ^~ /jsonrpc {
    proxy_http_version 1.1;
    proxy_set_header   Upgrade    $http_upgrade;
    proxy_set_header   Connection "Upgrade";
    proxy_set_header   Host       $host;
    proxy_set_header   X-Real-IP  $remote_addr;
    proxy_read_timeout 360s;
    proxy_pass         http://10.10.1.5:9090;
}

# 2) Redirect bare /kodi to canonical /kodi/
location = /kodi {
    return 301 /kodi/;
}

# 3) Proxy Chorus2 web interface and strip /kodi prefix
location /kodi/ {
    if ($request_uri ~ "^/kodi(?/[^?]*)") {
        set $new_path $after;
    }

    proxy_cache        off;
    proxy_set_header   Host      $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_read_timeout 300s;

    proxy_pass         http://10.10.1.5:10080$new_path$is_args$args;
}

Test your configuration and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

In Chorus2 settings (in your browser), enable reverse proxy mode and set the port to 443. Album art, audio streaming, and WebSocket playback controls will now operate flawlessly.

Friday, April 18, 2025

How to Compile the Latest wf-recorder from Source on Raspberry Pi 5 (Debian 12 Wayland)

On Debian 12 Bookworm (Raspberry Pi OS), the desktop environment runs natively on Wayland. Consequently, traditional X11 screen capture utilities (like FFmpeg's -f x11grab) no longer work.

The standard tool for recording Wayland sessions is wf-recorder. However, the stock wf-recorder v0.3 package provided in Debian's default repository lacks several modern enhancements, most notably the --overwrite flag. Compiling the latest version from source gives you full feature support, hardware-accelerated encoding, and audio capture.

1. Install Build Dependencies

Install the required C++ compilers, Meson build system, PipeWire, and FFmpeg development headers:

sudo apt-get update
sudo apt-get install -y g++ meson ninja-build libavutil-dev libavcodec-dev     libavformat-dev libswscale-dev libpulse-dev wayland-protocols libpipewire-0.3-dev wget

2. Download, Configure, and Build wf-recorder

mkdir -p ~/src && cd ~/src
wget https://github.com/ammen99/wf-recorder/releases/download/v0.5.0/wf-recorder-0.5.0.tar.xz
tar -xf wf-recorder-0.5.0.tar.xz
cd wf-recorder-0.5.0

# Configure release build with Meson
meson setup build --prefix=/usr --buildtype=release --reconfigure

# Compile with Ninja
ninja -C build

# Install binary to /usr/local/bin
sudo cp build/wf-recorder /usr/local/bin/

3. Recording Desktop Video and Audio

To record your full Wayland desktop screen with system audio output:

wf-recorder --overwrite -a default -f capture.mp4
Flag Breakdown:
  • --overwrite: Automatically overwrites existing destination files without prompting.
  • -a default: Captures audio from the default PulseAudio / PipeWire monitor source.
  • -f capture.mp4: Output file path. Stop recording at any time by pressing Ctrl + C.

Friday, February 21, 2025

How to Set Up Kodi 21 with YouTube Add-on and HDMI-CEC on Raspberry Pi 5

This guide covers how to set up Kodi 21 (Omega) with the unofficial YouTube Add-on, Chorus2 web interface, and intelligent HDMI-CEC control on a Raspberry Pi 5 running Raspberry Pi OS (Debian 12/13).

1. Installing Kodi 21

Raspberry Pi repositories provide both kodi (v20) and kodi21 (v21 Omega). Install Kodi 21 along with the adaptive streaming library to prevent playback stuttering:

sudo apt-get update
sudo apt-get install kodi21 kodi21-inputstream-adaptive -y

2. Installing the Unofficial YouTube Plugin

cd ~/Downloads
wget https://ftp.fau.de/osmc/osmc/download/dev/anxdpanic/repositories/repository.yt.testing_unofficial-2.0.7.zip
  1. In Kodi: Settings → Add-ons → Install from zip file → Home folder → Downloads → repository.yt.testing_unofficial-2.0.7.zip.
  2. Under Add-ons, open the context menu / options at bottom left and select Check for updates.
  3. Select Install from repository → YouTube Test Repo (Unofficial) → Video add-ons → YouTube → Install.

3. YouTube Personal API v3 Keys

To avoid hitting shared API quota limits, generate personal Google Cloud credentials (Setup Guide):

  • Create a project in Google Cloud Console with YouTube Data API v3 enabled.
  • Create an OAuth 2.0 Client ID (Application type: TV and Limited Input devices) and an API Key. Ensure the Publishing Status in OAuth Consent Screen is set to Production.
  • In Kodi: YouTube → Settings → API and enter your API Key, Client ID, and Client Secret.

4. Installing the Chorus2 Web Interface

Install the latest Chorus2 web control interface:

tmp="$(mktemp -d)"
curl -L -o "$tmp/chorus2.zip" https://github.com/xbmc/chorus2/archive/refs/heads/master.zip
unzip -q "$tmp/chorus2.zip" -d "$tmp"

mkdir -p ~/.kodi/addons
rm -rf ~/.kodi/addons/webinterface.default.new
cp -a "$tmp"/chorus2-master/dist ~/.kodi/addons/webinterface.default.new

if [ -d ~/.kodi/addons/webinterface.default ]; then
  mv ~/.kodi/addons/webinterface.default ~/.kodi/addons/webinterface.default.bak.$(date +%Y%m%d-%H%M%S)
fi

mv ~/.kodi/addons/webinterface.default.new ~/.kodi/addons/webinterface.default
rm -rf "$tmp"

5. Fine-Tuning HDMI-CEC Power Behavior

To prevent your Raspberry Pi from turning on your TV every time it reboots, add this parameter to /boot/firmware/config.txt:

hdmi_ignore_cec_init=1

Configure automatic playback pausing in Kodi when you switch TV inputs or turn off the screen:

  • Navigate to: Kodi → Settings → System → Input → Peripherals → CEC Adapter
  • Set Action when switching to another source → Stop playback
  • Set When the TV is switched off → Stop playback

6. Wayland Autostart / Remote Launch Script (/scripts/youtube.py)

To launch Kodi remotely over SSH, DBus, or a Telegram bot under Wayland without display collisions:

#!/usr/bin/env python3
import os

os.chdir('/home/pi')
os.environ["DISPLAY"] = ":0"
os.environ["WAYLAND_DISPLAY"] = "wayland-0"
os.environ["XDG_RUNTIME_DIR"] = "/run/user/1000"
os.environ["DBUS_SESSION_BUS_ADDRESS"] = "unix:path=/run/user/1000/bus"

# Terminate competing browser windows before launching media center
os.system("pkill -9 -f 'chrome|chromium|google-chrome|kodi|kodi21.bin'")
os.system('/usr/bin/kodi &')

Tuesday, August 27, 2024

How to Export Apple Health and Google Fit Activities to TCX for Strava (Free Method)

When using fitness trackers like the Xiaomi Smart Band 7, the companion Mi Fitness app may intermittently fail to sync running and cycling activities directly to Strava. While Mi Fitness natively supports data syncing with Apple Health, Suunto, and Strava, third-party sync disruptions can leave workouts trapped on your phone.

Commercial iOS apps such as HealthFit or RunGap can export workout files, but require paid subscriptions. Here is a 100% free workflow to export workout tracks in TCX format and upload them manually to Strava.

Step-by-Step Free Export Workflow

  1. Sync Mi Fitness to Apple Health: In the Mi Fitness app, navigate to Profile → Connected apps → Apple Health and enable all workout sync categories.
  2. Bridge to Google Fit: Install the free Google Fit app on iOS. Grant Google Fit read access to Apple Health workout and location data. Verify that your running sessions appear in Google Fit.
  3. Export via Google Takeout: Go to Google Takeout in your web browser. Deselect all items except Google Fit, and request a download archive.
  4. Extract TCX Activity Files: Once the archive is ready, download and extract the ZIP file. Inside the extracted archive, navigate to the Takeout/Fit/Activities/ directory. Each workout session is saved as an individual .tcx XML file containing GPS trackpoints, heart rate data, and timestamps.
  5. Upload to Strava: Open Strava Manual Upload and upload your .tcx files directly. Strava will parse the GPS coordinates, splits, and heart rate telemetry seamlessly.

Friday, March 1, 2024

How to Disable Automatic Sleep and Suspend on Debian 12 Production Servers

When running Debian 12 (Bookworm) on home servers, lab nodes, or production workstations with desktop environments installed, power-saving defaults can cause the system to automatically suspend or sleep after periods of user inactivity (no keyboard or mouse movement). This disconnects SSH sessions and halts background services.

Method 1: Configure GDM3 Greeter Power Settings (Desktop / GDM3)

For systems running the GNOME Display Manager (GDM3), edit the greeter defaults configuration:

sudo nano /etc/gdm3/greeter.dconf-defaults

Uncomment or add the following directives under [org/gnome/settings-daemon/plugins/power] to set inactive timeout actions to blank instead of suspend:

[org/gnome/settings-daemon/plugins/power]
sleep-inactive-ac-type='blank'
sleep-inactive-battery-type='blank'

Method 2: Mask Systemd Sleep Targets (Recommended for Headless / Servers)

To completely prohibit the Linux kernel and systemd from entering sleep, suspend, or hibernation at any time, mask the respective systemd targets:

sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
Verification: Check system sleep state with systemctl status sleep.target. If masked, the target points to /dev/null, ensuring 24/7 server uptime without unexpected idle shutdowns.

Wednesday, February 21, 2024

How to Set Up a Tunneled Wi-Fi Hotspot on Raspberry Pi OS Bookworm with WireGuard and NetworkManager

This guide updates our earlier tutorial for Debian 12 Bookworm on Raspberry Pi. In this architecture, the Raspberry Pi connects to upstream internet via Ethernet (eth0), establishes an encrypted WireGuard VPN tunnel (wg0), and broadcasts a secure Wi-Fi access point on wlan0. All client traffic connected to the hotspot is policy-routed strictly through the WireGuard tunnel.

Modern Bookworm Architecture: NetworkManager replaces dhcpcd and leverages the built-in dnsmasq-base plugin for DHCP and DNS caching, eliminating the need for standalone services like isc-dhcp-server.

1. Create the Wi-Fi Hotspot Profile in NetworkManager

Create a hotspot connection in the Raspberry Pi desktop Network GUI or via nmcli. Set the hotspot subnet to 10.0.1.1/24, enable auto-connect, and set MTU to 1420.

To enforce robust WPA2-only (RSN/AES) authentication and disable legacy WPA1:

nmcli con modify "Wi-Fi Hot" 802-11-wireless-security.proto rsn

2. Configure Policy Routing Table

Create a dedicated routing table for hotspot clients (subnet 10.0.1.0/24):

echo "200 INET2" | sudo tee -a /etc/iproute2/rt_tables

3. WireGuard Configuration (/etc/wireguard/wg0.conf)

Configure WireGuard with policy rules and MSS clamping to ensure seamless MTU handling through the tunnel:

[Interface]
PrivateKey = YOUR_PRIVATE_KEY
Address = 10.10.0.6/24
PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE; ip rule add from 10.0.1.0/24 table INET2 priority 100; ip route add default dev wg0 table INET2; ip route add 8.8.8.8/32 dev wg0; ip route add 8.8.4.4/32 dev wg0; iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu; iptables -t mangle -A FORWARD -i wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu; ip route flush cache
PreDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE; ip rule del from 10.0.1.0/24 table INET2 priority 100; ip route del default dev wg0 table INET2; ip route del 8.8.8.8/32 dev wg0; ip route del 8.8.4.4/32 dev wg0; iptables -t mangle -D FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu; iptables -t mangle -D FORWARD -i wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu; ip route flush cache
Table = off
MTU = 1280

[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = YOUR_VPN_SERVER_IP:PORT
PersistentKeepalive = 25

4. Enable Kernel IPv4 Forwarding

Enable packet forwarding in /etc/sysctl.conf:

sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

5. DNS Configuration for DHCP Clients

Configure NetworkManager's shared dnsmasq instance to supply public DNS servers to hotspot clients:

sudo mkdir -p /etc/NetworkManager/dnsmasq-shared.d/
echo "dhcp-option=option:dns-server,8.8.8.8,8.8.4.4" | sudo tee /etc/NetworkManager/dnsmasq-shared.d/dns.conf

6. Monitoring and Verification

Check active DHCP leases granted to Wi-Fi clients:

cat /var/lib/NetworkManager/dnsmasq-wlan0.leases

Verify dnsmasq runtime process parameters:

ps aux | grep dnsmasq

How to Fix Repeating Keys and Keystroke Lag on WayVNC (Debian 12 Bookworm)

When connecting remotely to a WayVNC server on Debian 12 Bookworm (or Raspberry Pi OS) over high-latency or cross-border connections, you may encounter severe repeating keys (for instance, pressing a key once produces a long string like aaaaaaaaaa).

Root Cause

In remote desktop protocols, pressing and releasing a key sends distinct key-down and key-up network packets. If packet loss or network jitter causes the key-release packet to be delayed or dropped, the compositor's auto-repeat timer assumes the key is still physically held down and floods the active application with repeats.

Solution: Disable Compositor Key-Repeat

As documented in the Wayfire Core Options, disabling key repeat entirely on the host side prevents phantom keystrokes over latent links.

Edit the Wayfire configuration file (/etc/wayfire/defaults.ini or user-level ~/.config/wayfire.ini):

sudo nano /etc/wayfire/defaults.ini

Add or set kb_repeat_rate = 0 under the [input] section:

[input]
kb_repeat_rate = 0

Save the file and restart the WayVNC service or reboot the Raspberry Pi to apply the change:

sudo systemctl restart wayvnc
# or:
sudo reboot

Once rebooted, keystrokes will register accurately without runaway repetitions, even across lossy WAN connections.

Saturday, February 10, 2024

How to Capture Screenshots over SSH on Debian 12 Wayland Desktops

On legacy Debian 11 (Bullseye) and older X11 desktops, taking a screenshot of an active desktop session remotely over SSH was straightforward using scrot:

DISPLAY=:0.0 scrot -o screenshot.jpg

In Python:

import os
os.environ["DISPLAY"] = ":0.0"
os.system("scrot -o screenshot.jpg")

Wayland on Debian 12 (Bookworm)

On Debian 12 (and Raspberry Pi OS Bookworm with Wayfire or labwc), Wayland is the default compositor. Because Wayland enforces strict security isolation between graphical clients and display sockets, scrot will fail with the error:

failed to create display

To take screenshots under Wayland, use the native grim utility (pre-installed on Raspberry Pi OS). When calling grim over SSH from a non-interactive TTY, you must explicitly export both WAYLAND_DISPLAY and XDG_RUNTIME_DIR:

WAYLAND_DISPLAY=wayland-1 XDG_RUNTIME_DIR=/run/user/1000 grim screenshot.png

Python Automation Script

To capture screenshots programmatically in Python over SSH:

import os

pngfile = "/tmp/screenshot.png"
os.environ["WAYLAND_DISPLAY"] = "wayland-1"
os.environ["XDG_RUNTIME_DIR"] = "/run/user/1000"

os.system(f"grim {pngfile}")
Tip: If you are unsure of your socket names, run echo $WAYLAND_DISPLAY and echo $XDG_RUNTIME_DIR from a terminal inside the active desktop session, or check the socket files in /run/user/$(id -u)/.

Thursday, February 8, 2024

How to Set Default Route and Network Metrics on Debian 12 (Bookworm) Using NetworkManager

On Debian 12 (Bookworm) and Raspberry Pi OS Bookworm, dhcpcd has been deprecated and replaced by NetworkManager as the default network management stack. Consequently, /etc/dhcpcd.conf is no longer present.

When multiple network interfaces (such as Ethernet eth0 and Wi-Fi wlan0) are connected simultaneously, the Linux kernel determines the default gateway based on routing metrics. Lower metric values take precedence over higher values.

Rule: Lower route metric = higher routing priority. To prioritize an interface for default internet access, assign it a lower metric than competing interfaces.

1. Inspect Current Routing Metrics

Check the existing routing table and metric values:

ip route show
# or:
route -n

2. List NetworkManager Connection Profiles

Identify the exact connection name you wish to adjust:

nmcli connection show

3. Adjust the Route Metric

You can set the route metric directly using a single nmcli command:

# Set metric to 100 for higher priority (e.g. Ethernet)
nmcli connection modify "Wired connection 1" ipv4.route-metric 100

# Reactivate the connection to apply changes
nmcli connection up "Wired connection 1"

Alternatively, you can edit the connection interactively:

nmcli connection edit "Wired connection 1"
nmcli> set ipv4.route-metric 100
nmcli> save
nmcli> quit

Run ip route show again to verify that your preferred connection now holds the lowest metric on the default route.

Wednesday, July 5, 2023

How to Configure Eturnal TURN Server with TLS 1.3 and Static Credentials on Debian 12

Eturnal is a modern, lightweight STUN/TURN server written in Erlang that serves as an efficient alternative to Coturn with built-in TLS 1.3 support. While Eturnal natively uses time-limited ephemeral credentials (REST API authentication), many WebRTC clients require static usernames and passwords. Here is how to configure Eturnal on Debian 12 and generate static credentials using an HMAC-SHA1 Python helper.

Stack: Eturnal 1.12+ • Debian 12 Bookworm • TLS 1.3 • HMAC-SHA1 Password Generator

1. Installing Eturnal on Debian 12

# Add Eturnal package repository and install
curl -fsSL https://eturnal.net/gpg.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/eturnal.gpg
echo "deb https://eturnal.net/debian bookworm main" | sudo tee /etc/apt/sources.list.d/eturnal.list
sudo apt-get update && sudo apt-get install eturnal -y

2. Generating Static TURN Credentials via Python

To produce valid credentials matching your configured shared secret (secret: "YOUR_SHARED_SECRET" in /etc/eturnal.yml):

import time
import hmac
import hashlib
import base64

SHARED_SECRET = "YOUR_SHARED_SECRET"
# Set expiration timestamp (e.g. 1 year in the future)
expiry = int(time.time()) + (365 * 24 * 3600)
username = f"{expiry}:user1"

# Compute HMAC-SHA1 hash
key = SHARED_SECRET.encode('utf-8')
msg = username.encode('utf-8')
password = base64.b64encode(hmac.new(key, msg, hashlib.sha1).digest()).decode('utf-8')

print(f"TURN Username: {username}")
print(f"TURN Password: {password}")

Monday, July 3, 2023

How to Compile and Install Coturn 4.6.2 with TLS 1.3 Support on Debian 12 (Bookworm)

Coturn is the standard open-source STUN/TURN server enabling WebRTC audio/video connections across symmetric NATs and firewalls. Full support for modern TLS 1.3 encrypted TURN connections (TURNS) was added in Coturn 4.6.2. Because Debian 12 (Bookworm) originally packaged Coturn 4.6.1, compiling version 4.6.2 from source with OpenSSL 3.x is necessary for enterprise TLS 1.3 compliance.

Features: TURNS over TLS 1.3 • WebRTC relay performance • Debian 12 Bookworm

Compilation & Installation Commands

# 1. Install build tools and OpenSSL 3 development libraries
sudo apt-get update
sudo apt-get install -y pkg-config build-essential libssl-dev libevent-dev git

# 2. Download Coturn 4.6.2 release
cd /usr/src
git clone https://github.com/coturn/coturn.git
cd coturn
git checkout 4.6.2

# 3. Configure and compile with OpenSSL
./configure --prefix=/usr/local
make -j$(nproc)
sudo make install

# Verify binary and TLS support
turnserver -v

Sunday, April 23, 2023

How to Suppress OpenCV and GStreamer nvarguscamerasrc (GST_ARGUS) Debug Logs on NVIDIA Jetson

When capturing MIPI CSI camera video on NVIDIA Jetson Nano, Xavier, or Orin platforms using OpenCV with GStreamer's nvarguscamerasrc plugin, the driver constantly spams stdout and stderr with verbose hardcoded debug strings (such as GST_ARGUS: Creating output stream and CONSUMER: Waiting until producer is connected...).

Why Log Levels Don't Work: These prints are hardcoded inside NVIDIA's gstnvarguscamerasrc.cpp C++ source code via printf(), bypassing standard GStreamer debug level filters (GST_DEBUG=0).

1. Rebuilding nvarguscamerasrc Without Hardcoded Prints

Download your matching JetPack BSP public sources and remove the hardcoded print macros:

# Check active L4T release
cat /etc/nv_tegra_release

# Extract gst-nvarguscamera source
cd ~/src/Linux_for_Tegra/source/public
tar -jxvf gst-nvarguscamera_src.tbz2
cd gst-nvarguscamera/

# Edit gstnvarguscamerasrc.cpp and empty out the print macros:
sed -i 's/#define GST_ARGUS_PRINT(...) printf(__VA_ARGS__)/#define GST_ARGUS_PRINT(...)/g' gstnvarguscamerasrc.cpp
sed -i 's/#define CONSUMER_PRINT(...) printf(__VA_ARGS__)/#define CONSUMER_PRINT(...)/g' gstnvarguscamerasrc.cpp

# Compile and install plugin
make
sudo make install

2. Suppressing OpenCV Warnings in Python

In your Python application, silence OpenCV library logs and restart the camera daemon prior to acquisition:

import os
import warnings

# Suppress Python warnings & OpenCV internal logging
warnings.filterwarnings('ignore')
os.environ["OPENCV_LOG_LEVEL"] = "OFF"

# Restart camera daemon to ensure clean hardware state
os.system("sudo systemctl restart nvargus-daemon")

Your camera capture scripts will now run silently without terminal clutter.

Thursday, February 10, 2022

How to Install Asterisk 18 and FreePBX 16 on Raspberry Pi OS Bullseye (64-bit & 32-bit)

Deploying Asterisk 18 LTS alongside the modern FreePBX 16 administrative interface on Raspberry Pi OS (Debian 11 Bullseye) enables a carrier-grade VoIP telephony platform on energy-efficient ARM hardware. This guide provides the complete setup for both 64-bit (aarch64) and 32-bit (armhf) architectures.

OS: Raspberry Pi OS Bullseye • Components: Asterisk 18.x, FreePBX 16, Node.js 14, MariaDB 10.5, Apache 2.4

1. Installing System Packages & PHP 7.4

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y build-essential libssl-dev libncurses5-dev libnewt-dev   libxml2-dev libsqlite3-dev uuid-dev mariadb-server mariadb-client   libjansson-dev libedit-dev sox libsox-fmt-all git curl subversion

# Install PHP 7.4 modules required by FreePBX 16
sudo apt-get install -y apache2 php php-cli php-mysql php-pear php-gd php-mbstring   php-intl php-xml php-curl php-zip

2. Building Asterisk 18 LTS

cd /usr/src
sudo wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-18-current.tar.gz
sudo tar -zxf asterisk-18-current.tar.gz
cd asterisk-18.*

sudo contrib/scripts/get_mp3_source.sh
sudo contrib/scripts/install_prereq install

sudo ./configure --with-pjproject-bundled --with-jansson-bundled
sudo make menuselect.makeopts
sudo menuselect/menuselect --enable format_mp3 menuselect.makeopts
sudo make -j$(nproc)
sudo make install
sudo make config
sudo ldconfig

3. Installing FreePBX 16

cd /usr/src
sudo wget http://mirror.freepbx.org/modules/packages/freepbx/freepbx-16.0-latest.tgz
sudo tar -zxf freepbx-16.0-latest.tgz
cd freepbx
sudo ./install -n

Monday, February 7, 2022

How to Set Up a Samba Network Storage Drive on Raspberry Pi for Hikvision IP Cameras

Hikvision value IP cameras (such as the IPC-B120, IPC-B121H, and IPC-B120-D-W) feature onboard motion detection, infrared night vision, and H.265+ compression, but lack internal hard drives. Rather than purchasing an expensive commercial NVR, you can attach an external USB hard disk to a Raspberry Pi configured as an SMB/CIFS Network Attached Storage (NAS) target for continuous or motion-triggered video archiving.

Stack: Raspberry Pi 4 • External USB HDD (ext4) • Samba Server (SMBv2/v3) • Hikvision Storage Management

1. Installing and Configuring Samba on the Raspberry Pi

sudo apt-get update
sudo apt-get install samba samba-common-bin -y

# Create recording directory on mounted external drive
sudo mkdir -p /media/cctv_storage/hikvision
sudo chown -R nobody:nogroup /media/cctv_storage/hikvision
sudo chmod -R 0777 /media/cctv_storage/hikvision

2. Samba Share Definition (/etc/samba/smb.conf)

Hikvision firmware requires specific Samba dialect and locking settings to format network drives successfully:

[hikvision]
   comment = Hikvision CCTV Storage
   path = /media/cctv_storage/hikvision
   browseable = yes
   writeable = yes
   guest ok = yes
   read only = no
   create mask = 0777
   directory mask = 0777
   force user = nobody

Restart Samba:

sudo systemctl restart smbd

3. Mounting the Drive in Hikvision Web GUI

  1. Log into your camera's web interface → Configuration → Storage → Storage Management → Net HDD.
  2. Set Type to NAS / SMB/CIFS, enter the Raspberry Pi's IP address, set File Path to /hikvision, and save.
  3. Under HDD Management, select the newly added network disk and click Format / Init. Once status changes to Normal, motion recordings will save directly to your Pi.

Monday, June 7, 2021

TD-LTE Router In-Depth Comparison: AirMaster 3100V vs GreenPacket DT-350

Evaluating indoor TD-LTE desktop modems for Band 42 (3500 MHz) deployments requires comparing RF sensitivity, throughput stability under weak signal conditions, and hardware VoIP capabilities. Here is an empirical comparison between the AirMaster 3100V and the GreenPacket DT-350.

Tested Models: AirMaster 3100V (Firmware V2.0.0B31) • GreenPacket DT-350 (Unlocked Band 42 SIMs)

Key Findings and Benchmark Summary

Feature AirMaster 3100V GreenPacket DT-350
VoIP Hardware No FXS RJ-11 port Built-in SIP client & RJ-11 port
Wi-Fi Performance 802.11n 300Mbps 802.11n 300Mbps
Weak Signal Stability Superior antenna gain (RSRP stability) Moderate gain; benefits from external SMA antennas
Firmware Flexibility Clean web management Requires unlocking for full VoIP/APN menus

Recommendation: If telephone VoIP handset integration is required, the GreenPacket DT-350 is the clear choice. For raw RF reception in fringe coverage areas, the AirMaster 3100V delivers more consistent speeds.

Friday, June 4, 2021

How to Perform Path MTU Discovery on Linux and macOS (Ping and Tracepath)

Mismatched Maximum Transmission Unit (MTU) sizes across VPNs, tunnels (WireGuard, IPsec, PPPoE), and intermediate network links frequently cause silent TCP connection hangs and packet fragmentation issues. Testing your path's effective MTU using the "Don't Fragment" (DF) bit pinpoints the exact link threshold.

Formula: Total MTU = ICMP Payload Size + 28 bytes (20 bytes IP header + 8 bytes ICMP header).

1. Path MTU Discovery on Linux

Use ping with -M do (enforce Don't Fragment) or tracepath:

# Ping with 1472 bytes payload (1472 + 28 = 1500 MTU)
ping -M do -s 1472 8.8.8.8

# If it reports "Frag needed and DF set", step down until packets pass:
ping -M do -s 1392 8.8.8.8 # (1392 + 28 = 1420 MTU, standard for WireGuard)

# Automatic MTU trace across intermediate hops:
tracepath 8.8.8.8

2. Path MTU Discovery on macOS

# On macOS, use -D to set DF bit:
ping -D -s 1472 8.8.8.8

Tuesday, May 18, 2021

How to Proxy Secure WebSockets (WSS) in a Subfolder with Apache mod_proxy_wstunnel

Routing secure WebSocket connections (wss://) originating from a specific URL subfolder (such as /websocket1) to a local backend daemon (running on an internal TCP port like 2085) requires configuring Apache's mod_proxy_wstunnel.

Prerequisites: Enable Apache modules: proxy, proxy_http, proxy_wstunnel, and rewrite.

Apache VirtualHost Configuration

# Enable proxy tunneling for WebSockets
RewriteEngine On

# Detect WebSocket upgrade request headers for /websocket1
RewriteCond %{REQUEST_URI} ^/websocket1 [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/websocket1/(.*)$ ws://127.0.0.1:2085/$1 [P,L]

# Normal HTTP proxy fallback (if needed):
ProxyPass /websocket1 http://127.0.0.1:2085/
ProxyPassReverse /websocket1 http://127.0.0.1:2085/

Reload Apache to apply:

sudo systemctl reload apache2

Tuesday, May 11, 2021

Building a Dedicated Jitsi Meet Video Conferencing Station on Raspberry Pi 4

Using a Raspberry Pi 4 connected directly to an HDMI living room TV with an external USB webcam creates a dedicated, affordable family video-conferencing terminal. However, running WebRTC in standard Chromium on ARM without optimizations often causes CPU throttling, audio noise, and meeting lag.

Optimal Hardware Choices: Raspberry Pi 4 (4GB) • Logitech C920 / C922 / C270 USB Webcam • Micro-HDMI to HDMI audio

Critical Hardware & Audio Points

  • Microphone Input: The Raspberry Pi 4's 3.5mm onboard jack is output only (no microphone input pin). Connecting standard headset microphones requires either an external USB audio dongle or a webcam with integrated noise-cancelling microphones (e.g. Logitech C920).
  • Audio Routing: Configure ALSA/PulseAudio to output conference sound through HDMI to your TV speakers while capturing voice from the USB webcam microphone.

Chromium Performance Optimizations for WebRTC

Launch Chromium in kiosk mode with hardware GPU acceleration flags:

chromium-browser --kiosk --start-fullscreen   --enable-accelerated-video-decode   --use-gl=egl   --ignore-gpu-blocklist   --autoplay-policy=no-user-gesture-required   "https://meet.jit.si/YourPrivateRoom#config.startWithVideoMuted=false"

How Google Antigravity Solved the Mysterious NVIDIA Sleep Reboot on My Dell Inspiron 7567 (Ubuntu Linux)

If you run modern Ubuntu or Linux on a Dell Inspiron 15 Gaming (7567) or a similar 7th-gen Intel laptop paired with an NVIDIA GeForce GTX ...