diff --git a/.gitignore b/.gitignore index 31904a1..5517b1d 100755 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ config.ron /archive /build_send +/.memory-bank-pod3sd + diff --git a/docs/guides/pod3_sd_card/windows_patching_guide/patching_guide.md b/docs/guides/pod3_sd_card/windows_patching_guide/patching_guide.md new file mode 100644 index 0000000..ad95476 --- /dev/null +++ b/docs/guides/pod3_sd_card/windows_patching_guide/patching_guide.md @@ -0,0 +1,382 @@ +# Pod 3 SD Card Patching Guide - Windows + +This guide provides Windows-specific instructions for patching Pod 3 SD card images to enable SSH access and install OpenSleep. + +## ⚠️ CRITICAL WARNING ⚠️ + +**DO NOT FORMAT THE SD CARD WHEN WINDOWS PROMPTS YOU!** + +When you insert the SD card into your Windows computer, Windows may show a popup saying: +- "You need to format the disk in drive X: before you can use it" +- "The disk is not formatted" +- "Format disk now?" + +**CLICK "CANCEL" OR CLOSE THE DIALOG!** + +🛑 **DO NOT CLICK FORMAT!** 🛑 + +The SD card contains a Linux filesystem (ext4) that Windows cannot read. **The data IS there**, Windows just doesn't understand the Linux file system. If you format the disk, **all your data will be permanently lost** and you'll need to start over from scratch. + +--- + +## Overview + +At a high level, here is the process: +1. Create an image backup of the SD card +2. Copy the image to Ubuntu running in WSL +3. Run the patching script to modify the image +4. Write the modified image back to the SD card + + +## Prerequisites + +### Required Software + +- **Windows Subsystem for Linux (WSL2)** with Ubuntu distribution +- **PowerShell** - Built into Windows +- **Win32 Disk Imager** - [Download here](https://win32diskimager.org/) for reading/writing SD card images + +### Required Hardware + +- **Micro SD Card Reader** - The author used [TS-RDF5K model](https://www.amazon.com/dp/B009D79VH4) +- **Your Pod 3 SD card** + +### Optional Tools + +- **ImageUSB by PassMark** - [Download here](https://www.osforensics.com/tools/write-usb-images.html) - Can create backups but not recommended for patching + +### Installing WSL2 + +1. Open PowerShell as Administrator +2. Run the following command: + ```powershell + wsl --install + ``` +3. Restart your computer when prompted +4. Complete the Ubuntu setup when it launches +5. Create a username and password for your Ubuntu instance + +--- + +## Step-by-Step Instructions + +### Step 1: Create SD Card Image + +1. Insert your Pod 3 SD card into the card reader +2. Open **Win32 Disk Imager** +3. Select the SD card device (verify it's the correct drive!) +4. Choose a location and filename for the image (e.g., `pod3_original.img`) +5. Click **Read** to create the image +6. Wait for the process to complete + +**Note:** Win32 Disk Imager will read the entire SD card even if you select the second partition. + +### Step 2: Gather Required Files + +Before patching, collect these files: +- `pod3_original.img` - Image file from Step 1 +- Your SSH public key (e.g., `~/.ssh/id_rsa.pub` or `~/.ssh/id_ed25519.pub`) +- `opensleep` - The opensleep binary +- `opensleep.service` - The systemd service file +- `config.ron` - Your opensleep configuration file +- `full_patch_workflow.sh` - The patching script from `scripts/` + +### Step 3: Run the Patching Script + +1. Open WSL (search for "Ubuntu" in Windows Start menu) +2. Navigate to where your files are located (Windows drives are mounted at `/mnt/`, e.g., `/mnt/c/Users/YourName/`) +3. Make the script executable: + ```bash + chmod +x ./full_patch_workflow.sh + ``` +4. Run the patching script with your arguments: + +**Basic example:** +```bash +./full_patch_workflow.sh \ + -i pod3_original.img \ + -k ~/.ssh/id_rsa.pub \ + -s "YourWiFiSSID" \ + -p "YourWiFiPassword" \ + -b ./opensleep \ + -S ./opensleep.service \ + -c ./config.ron \ + -d +``` + +**Real-world example (with file paths):** +```bash +/mnt/f/workspaces/opensleep/scripts/full_patch_workflow.sh \ + -i "/mnt/f/drive images/pod3_original.img" \ + -k /mnt/c/Users/YourName/.ssh/id_ed25519.pub \ + -s "MyWiFiNetwork" \ + -p "MyWiFiPassword" \ + -b "/mnt/f/drive images/opensleep" \ + -S "/mnt/f/drive images/opensleep.service" \ + -c "/mnt/f/drive images/config.ron" \ + -d +``` + +**Script arguments:** +- `-i` - Input image file (required) +- `-k` - SSH public key file (required) +- `-s` - WiFi SSID (optional, but recommended) +- `-p` - WiFi password (optional, but recommended) +- `-m` - MAC address for wlan0 (optional, format: XX:XX:XX:XX:XX:XX) +- `-P` - Password for rewt user (optional, enables password authentication) +- `-b` - opensleep binary (optional) +- `-S` - opensleep.service file (optional) +- `-c` - config.ron file (optional) +- `-d` - Disable Eight Sleep services (optional, use with opensleep) + +**⚠️ Note about `-d` flag:** +Using `-d` disables Eight Sleep services and prevents normal Eight Sleep app pairing. To restore Eight Sleep functionality, you must reflash the original image. + +**📝 Note about `-P` flag:** +Using `-P` enables password authentication for the `rewt` user. The password will persist across factory resets. This is useful if you want both SSH key and password authentication available. + +**📝 Note about `-m` flag:** +Setting a MAC address helps with router/firewall MAC filtering. The MAC address will persist across factory resets, but **WILL CHANGE** after each factory reset if not set (due to the Broadcom WiFi driver generating a random MAC). + +The script will create a patched image named `pod3_original-patched.img` in the same directory as the input image. + +### Step 4: Write Patched Image to SD Card + +1. Insert your SD card into the card reader +2. Open **Win32 Disk Imager** +3. Select your patched image file (e.g., `pod3_original-patched.img`) +4. Select the SD card device +5. **⚠️ VERIFY IT'S THE CORRECT DRIVE!** + - Double-check by looking at "Safely Remove USB" in the taskbar + - It should show 3 partitions for your SD card +6. Click **Write** to write the image +7. Wait for the process to complete +8. Safely eject the SD card + +### Step 5: Factory Reset the Pod + +1. Insert the SD card back into your Pod 3 +2. Hold down the small button on the Pod Hub while powering it on +3. Keep holding until the factory reset process begins +4. The Pod will boot with your patched image + +--- + +## Verification + +After the Pod boots up: + +1. **SSH Access (with key):** + ```bash + ssh rewt@ -p 8822 + ``` + +2. **SSH Access (with password, if -P was used):** + ```bash + ssh rewt@ -p 8822 + # Enter password when prompted + ``` + +3. **Check opensleep status** (if installed): + ```bash + systemctl status opensleep + ``` + +4. **Check WiFi connection:** + ```bash + nmcli connection show + ``` + +5. **Check MAC address** (if -m was used): + ```bash + ip addr show wlan0 | grep ether + ``` + +--- + +## Tested Features + +The following features have been tested and confirmed working: + +✅ **SSH Key Authentication** - Tested and working +✅ **WiFi Auto-Connect** - Tested and working +✅ **MAC Address Persistence** - Tested and working (survives factory reset) +✅ **Password Authentication** - Tested and working (survives factory reset) +✅ **Eight Sleep Service Disabling** - Tested and working +✅ **opensleep Service** - Tested and working +✅ **Factory Reset Survival** - All configurations persist after factory reset + power cycle + +**Note:** Password authentication (`-P` flag) was tested on November 2, 2025 and confirmed to work correctly. The validation bug that was rejecting legitimate root-owned shadow files has been fixed. + +--- + +## Troubleshooting + +### SD card not detected +- Try a different USB port +- Ensure the card reader is compatible with your SD card size + +### Image too large +- The patched image should be approximately the same size as the original +- Ensure you have enough free space (requires ~16GB during patching) + +### SSH connection fails +- Verify the Pod is on your network: `ping ` +- Check SSH port: `8822` (not the standard `22`) +- Ensure your public key was added correctly + +### WiFi not connecting +- Verify SSID and password are correct +- Check router compatibility (2.4GHz network recommended) +- Look at Pod logs after SSH access: `journalctl -u ssh-early.service` + +--- + +## Lessons Learned: MAC Address Configuration + +### Background + +Pod 3 uses the `brcmfmac` WiFi driver, which generates a **random MAC address** every time the firmware is loaded. This causes several issues: +- MAC address changes on every boot +- MAC address changes after factory reset +- DHCP reservations don't work reliably +- Network monitoring becomes difficult + +### What Doesn't Work + +❌ **systemd .link files** - The standard approach for setting persistent MAC addresses +- The `brcmfmac` driver ignores `.link` file MAC settings +- The driver loads at firmware initialization, before systemd-networkd processes `.link` files +- MAC is already set by the time `.link` files are evaluated + +❌ **Running MAC service too early** - Before the driver is loaded +- Services in `sysinit.target` run before the WiFi driver loads +- Setting MAC before driver load results in "device not found" errors +- Service would need to run as ExecStart in opensleep-wifi, but that's messy + +❌ **Hardcoded user/group IDs** - Using `0:0` for all files +- Some files (like `/etc/shadow`) require specific ownership +- Wrong ownership can corrupt authentication and break SSH completely +- Must preserve original permissions and ownership from tar extraction + +### What Does Work + +✅ **systemd service running AFTER WiFi driver loads + NetworkManager configuration** + +The solution is a four-stage boot process: + +1. **opensleep-wifi.service** - Loads the brcmfmac driver + - Runs in `network.target` + - Bypasses EEPROM check (Pod 3 reports WiFi unavailable) + - Initializes WiFi hardware with random MAC + +2. **opensleep-mac.service** - Sets persistent MAC address + - Runs `After=opensleep-wifi.service` (waits for driver) + - Uses `/sbin/ip link set` commands (not `/usr/sbin/ip`) + - Brings interface down, sets MAC, brings it back up + - Runs `Before=wpa_supplicant@wlan0.service` + +3. **NetworkManager** - Network management daemon + - Configured to NOT randomize MAC addresses + - Config: `/etc/NetworkManager/conf.d/99-disable-wifi-mac-randomization.conf` + - Uses `wifi.cloned-mac-address=permanent` (respects our set MAC) + - This was the missing piece - NetworkManager was overriding our MAC! + +4. **wpa_supplicant@wlan0.service** - Connects to WiFi + - Runs after both opensleep-wifi AND opensleep-mac complete + - Override config: `After=opensleep-wifi.service opensleep-mac.service` + - Uses the configured persistent MAC address + +### Critical Implementation Details + +**Service Dependencies:** +```ini +[Unit] +After=opensleep-wifi.service sys-subsystem-net-devices-wlan0.device +Before=network-pre.target wpa_supplicant@wlan0.service +BindsTo=sys-subsystem-net-devices-wlan0.device +``` + +**Correct IP Command Path:** +- Use `/sbin/ip` (NOT `/usr/sbin/ip`) +- Embedded systems have different PATH structures +- Wrong path causes exit code 203/EXEC failure + +**Three-Step MAC Setting:** +```bash +ExecStart=/sbin/ip link set dev wlan0 down +ExecStart=/sbin/ip link set dev wlan0 address $MAC_ADDRESS +ExecStart=/sbin/ip link set dev wlan0 up +``` + +**NetworkManager Configuration (CRITICAL):** +- NetworkManager can override manually set MAC addresses +- Config file: `/etc/NetworkManager/conf.d/99-disable-wifi-mac-randomization.conf` +- Settings: + ```ini + [device-mac-randomization] + wifi.scan-rand-mac-address=no + + [connection-mac-randomization] + wifi.cloned-mac-address=permanent + ``` +- Without this, NetworkManager will randomize the MAC after opensleep-mac sets it! + +**wpa_supplicant Override:** +- Must wait for BOTH opensleep-wifi AND opensleep-mac +- Override file: `/etc/systemd/system/wpa_supplicant@wlan0.service.d/override.conf` +- Only add opensleep-mac dependency if MAC address was configured (conditional) + +**File Permission Preservation:** +- Always check original permissions: `stat -c "%a" file` +- Always check original ownership: `stat -c "%u:%g" file` +- Validate ownership before modification (error if `0:0` for shadow file) +- Restore exact permissions after modification +- Dual-write: staging directory for tar + mounted partition for immediate use + +### Debugging Tips + +**Check service status:** +```bash +systemctl status opensleep-wifi.service +systemctl status opensleep-mac.service +systemctl status wpa_supplicant@wlan0.service +``` + +**Verify MAC address:** +```bash +ip link show wlan0 +cat /sys/class/net/wlan0/address +``` + +**Check service timing:** +```bash +systemd-analyze critical-chain opensleep-mac.service +``` + +**View boot logs:** +```bash +journalctl -u opensleep-wifi.service +journalctl -u opensleep-mac.service +``` + +### Factory Reset Behavior + +The persistent MAC configuration survives factory reset because: +1. Service file is in `/etc/systemd/system/` (user space) +2. Factory reset extracts `rootfs.tar.gz` which includes our service +3. Tar duplicate prevention ensures latest version is used +4. Service runs on every boot, reapplying MAC + +**Important:** After factory reset on patched images, you must **power cycle** (not just reboot): +- Factory reset LED: Green (in progress) → Yellow (complete) +- Power off the device when yellow LED shows +- Power back on to boot with patched configuration + +--- + +## Additional Resources + +- [Main SETUP.md](../../../../SETUP.md) - General setup instructions +- [OpenSleep README](../../../../README.md) - Project overview +- See the `scripts/full_patch_workflow.sh` for detailed script options (`-h` flag) \ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..88cc556 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,47 @@ +# Troubleshooting + +This page contains common issues and their solutions when working with OpenSleep. + +## Device Not Responding + +**Issue:** Device not responding in `manager.rs` + +**Solution:** This can happen if the frank service wasn't disabled. Make sure to disable the frank service before attempting to communicate with the device. + +## SleepEight App Crashes After Disabling Services + +**Issue:** The SleepEight app crashes after disabling the services. + +**Solution:** Clearing cache and data doesn't work in this case. The following steps seem to resolve the app crashing: + +1. Fully uninstall the SleepEight app +2. Reinstall the app +3. Exit out of setup as soon as the pod is paired, leaving no pod on your account + +**Note:** To avoid having this happen again, remove the pod from account prior to factoring resetting it. + +## Public Key Errors + +**Issue:** Encountering public key errors when trying to SSH into the device. + +**Solution:** You have a short window to SSH in and disable the services before the update service updates the pod. Once updated, your SSH keys will be dropped. Act quickly after initial setup to disable the update service. + +**Note:** If your able to connect and then the ssh fingerprint changes, resulting in you being unable to connect, that is a sign of an update taken place, you'll have to start over. + +## Factory Reset vs Live Partition Update + +**Question:** Do I need to factory reset, or can I just update the live partition? + +**Answer:** (Speculation) It seems the pod's security does some checking where the live partition authorized keys won't work if they don't match a signature from the factory reset. This impacts going back to the base image. A factory reset is recommended to ensure proper key authentication. + +## Reverting to Base Image + +**Question:** How do I go back to the base image? + +**Answer:** Assuming you've written back the original SD card image or are using the original SD card: + +1. Place the old SD card back in the device +2. Perform a factory reset +3. **Important:** Using the card without a factory reset will run into security issues that prevent SSH from working + +The factory reset is required for the authorized keys to work properly with the restored base image. diff --git a/scripts/extract_pod3_logs.sh b/scripts/extract_pod3_logs.sh new file mode 100644 index 0000000..1e25680 --- /dev/null +++ b/scripts/extract_pod3_logs.sh @@ -0,0 +1,509 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "======================================" +echo "Pod 3 (SD Card) Log Extraction Script" +echo "======================================" +echo "" +echo "⚠️ For Pod 3 with SD card only!" +echo "" + +# Default values +IMG_FILE="" +OUTPUT_DIR="" + +# Parse command-line arguments +usage() { + cat <) + -h Show this help message + +Description: + This script extracts system logs from a Pod 3 SD card image to help debug + boot issues, WiFi connection problems, and service failures. + +Extracted logs include: + - journalctl logs (system, ssh-early, NetworkManager, opensleep) + - NetworkManager configuration and connection files + - systemd service status + - dmesg output + - Various system configuration files + +Example: + $0 -i pod3_patched.img + $0 -i pod3_patched.img -o ./debug_logs +EOF + exit 1 +} + +while getopts "i:o:h" opt; do + case $opt in + i) IMG_FILE="$OPTARG" ;; + o) OUTPUT_DIR="$OPTARG" ;; + h) usage ;; + *) usage ;; + esac +done + +# Validate required arguments +if [[ -z "$IMG_FILE" ]]; then + echo "Error: Missing required argument -i IMAGE_FILE" + echo "" + usage +fi + +# Validate image file exists +if [[ ! -f "$IMG_FILE" ]]; then + echo "Error: Image file not found: $IMG_FILE" + exit 1 +fi + +echo "[*] Image file: $IMG_FILE" + +# Set default output directory if not provided +if [[ -z "$OUTPUT_DIR" ]]; then + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + OUTPUT_DIR="./pod3_logs_${TIMESTAMP}" +fi + +# Create output directory +mkdir -p "$OUTPUT_DIR" +echo "[*] Output directory: $OUTPUT_DIR" +echo "" + +# Create temporary working directory +WORK_DIR=$(mktemp -d -t extract_logs_$(basename "$IMG_FILE").XXXX) +MOUNT_DIR="$WORK_DIR/mount" + +echo "[*] Working directory: $WORK_DIR" + +# --- Cleanup function --- +cleanup() { + local exit_code=$? + echo "" + echo "[*] Cleaning up..." + + # Unmount if mounted + if mountpoint -q "$MOUNT_DIR" 2>/dev/null; then + echo "[*] Unmounting $MOUNT_DIR..." + sudo umount "$MOUNT_DIR" 2>/dev/null || true + fi + + # Detach loop device if attached + if [[ ! -z "${LOOP:-}" ]] && losetup "$LOOP" 2>/dev/null; then + echo "[*] Detaching loop device $LOOP..." + sudo losetup -d "$LOOP" 2>/dev/null || true + fi + + # Remove working directory + if [[ -d "$WORK_DIR" ]]; then + echo "[*] Removing work directory..." + rm -rf "$WORK_DIR" 2>/dev/null || true + fi + + if [[ $exit_code -ne 0 ]]; then + echo "[-] Script failed with exit code $exit_code" + else + echo "[+] Cleanup complete" + fi + + exit $exit_code +} + +# Set trap to call cleanup on exit, interrupt, or termination +trap cleanup EXIT INT TERM + +# --- Setup loop device (read-only) --- +echo "[*] Setting up loop device (read-only)..." +LOOP=$(sudo losetup -Prf --show "$IMG_FILE") +echo "[*] Loop device: $LOOP" + +# --- Mount root partition --- +ROOT_PART="${LOOP}p1" +mkdir -p "$MOUNT_DIR" +sudo mount "$ROOT_PART" "$MOUNT_DIR" +echo "[*] Mounted root partition at $ROOT_PART" + +echo "" +echo "======================================" +echo "Extracting Logs and Configuration" +echo "======================================" +echo "" +echo "[*] Note: Extracting logs from the mounted root partition" +echo "[*] These are the actual runtime logs from the Pod" +echo "" + +# --- Extract journal logs --- +echo "[*] Extracting journal logs..." +JOURNAL_DIR="$MOUNT_DIR/var/log/journal" +if sudo test -d "$JOURNAL_DIR"; then + sudo cp -r "$JOURNAL_DIR" "$OUTPUT_DIR/journal" 2>/dev/null || true + echo " ✓ Journal logs extracted" + + # Attempt to decode journal logs with journalctl + echo "[*] Decoding journal logs..." + if command -v journalctl &> /dev/null; then + # Full boot log + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager > "$OUTPUT_DIR/journal/decoded_full.log" 2>/dev/null || true + + # Service-specific logs + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -u variscite-wifi.service > "$OUTPUT_DIR/journal/variscite-wifi.log" 2>/dev/null || true + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -u wpa_supplicant@wlan0.service > "$OUTPUT_DIR/journal/wpa_supplicant.log" 2>/dev/null || true + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -u ssh-early.service > "$OUTPUT_DIR/journal/ssh-early.log" 2>/dev/null || true + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -u systemd-networkd.service > "$OUTPUT_DIR/journal/systemd-networkd.log" 2>/dev/null || true + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -u opensleep.service > "$OUTPUT_DIR/journal/opensleep.log" 2>/dev/null || true + + # Boot messages + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -b > "$OUTPUT_DIR/journal/decoded_boot.log" 2>/dev/null || true + + # Kernel messages + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -k > "$OUTPUT_DIR/journal/decoded_kernel.log" 2>/dev/null || true + + # Priority: errors and warnings + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -p err > "$OUTPUT_DIR/journal/decoded_errors.log" 2>/dev/null || true + sudo journalctl --directory="$OUTPUT_DIR/journal" --no-pager -p warning > "$OUTPUT_DIR/journal/decoded_warnings.log" 2>/dev/null || true + + echo " ✓ Journal logs decoded" + else + echo " ⚠ journalctl not found - install systemd to decode journal logs" + fi +else + echo " ℹ No persistent journal logs found" +fi + +# --- Extract system logs --- +echo "[*] Extracting system logs..." +if sudo test -f "$MOUNT_DIR/var/log/syslog"; then + sudo cp "$MOUNT_DIR/var/log/syslog" "$OUTPUT_DIR/syslog" 2>/dev/null || true + echo " ✓ syslog extracted" +fi + +if sudo test -f "$MOUNT_DIR/var/log/messages"; then + sudo cp "$MOUNT_DIR/var/log/messages" "$OUTPUT_DIR/messages" 2>/dev/null || true + echo " ✓ messages extracted" +fi + +if sudo test -f "$MOUNT_DIR/var/log/kern.log"; then + sudo cp "$MOUNT_DIR/var/log/kern.log" "$OUTPUT_DIR/kern.log" 2>/dev/null || true + echo " ✓ kern.log extracted" +fi + +if sudo test -f "$MOUNT_DIR/var/log/daemon.log"; then + sudo cp "$MOUNT_DIR/var/log/daemon.log" "$OUTPUT_DIR/daemon.log" 2>/dev/null || true + echo " ✓ daemon.log extracted" +fi + +if sudo test -f "$MOUNT_DIR/var/log/boot.log"; then + sudo cp "$MOUNT_DIR/var/log/boot.log" "$OUTPUT_DIR/boot.log" 2>/dev/null || true + echo " ✓ boot.log extracted" +fi + +# --- Extract all log files --- +echo "[*] Extracting additional log files..." +if sudo test -d "$MOUNT_DIR/var/log"; then + mkdir -p "$OUTPUT_DIR/var_log" + sudo find "$MOUNT_DIR/var/log" -type f -name "*.log" -exec cp {} "$OUTPUT_DIR/var_log/" \; 2>/dev/null || true + LOG_COUNT=$(ls "$OUTPUT_DIR/var_log" 2>/dev/null | wc -l) + if [[ $LOG_COUNT -gt 0 ]]; then + echo " ✓ Extracted $LOG_COUNT additional log files" + fi +fi + +# --- Extract NetworkManager logs and config --- +echo "[*] Extracting NetworkManager configuration..." +NM_DIR="$MOUNT_DIR/etc/NetworkManager" +if sudo test -d "$NM_DIR"; then + mkdir -p "$OUTPUT_DIR/NetworkManager" + sudo cp -r "$NM_DIR/system-connections" "$OUTPUT_DIR/NetworkManager/" 2>/dev/null || true + sudo cp "$NM_DIR/NetworkManager.conf" "$OUTPUT_DIR/NetworkManager/" 2>/dev/null || true + + # Also get NetworkManager state file + if sudo test -f "$MOUNT_DIR/var/lib/NetworkManager/NetworkManager.state"; then + sudo cp "$MOUNT_DIR/var/lib/NetworkManager/NetworkManager.state" "$OUTPUT_DIR/NetworkManager/" 2>/dev/null || true + fi + + echo " ✓ NetworkManager config extracted" +else + echo " ℹ NetworkManager not found" +fi + +# --- Extract ConnMan configuration --- +echo "[*] Extracting ConnMan configuration..." +if sudo test -d "$MOUNT_DIR/var/lib/connman"; then + mkdir -p "$OUTPUT_DIR/connman" + sudo cp -r "$MOUNT_DIR/var/lib/connman" "$OUTPUT_DIR/connman/var_lib" 2>/dev/null || true + echo " ✓ ConnMan config extracted" +else + echo " ℹ ConnMan not found" +fi + +if sudo test -d "$MOUNT_DIR/etc/connman"; then + mkdir -p "$OUTPUT_DIR/connman" + sudo cp -r "$MOUNT_DIR/etc/connman" "$OUTPUT_DIR/connman/etc" 2>/dev/null || true +fi + +# --- Extract systemd-networkd configuration --- +echo "[*] Extracting systemd-networkd configuration..." +if sudo test -d "$MOUNT_DIR/etc/systemd/network"; then + mkdir -p "$OUTPUT_DIR/systemd-networkd" + sudo cp -r "$MOUNT_DIR/etc/systemd/network" "$OUTPUT_DIR/systemd-networkd/" 2>/dev/null || true + echo " ✓ systemd-networkd config extracted" +else + echo " ℹ systemd-networkd config not found" +fi + +# --- Extract systemd service files --- +echo "[*] Extracting systemd service files..." +mkdir -p "$OUTPUT_DIR/systemd/services" +mkdir -p "$OUTPUT_DIR/systemd/enabled" + +# Copy all custom services from /etc/systemd/system/ +if sudo test -d "$MOUNT_DIR/etc/systemd/system"; then + sudo find "$MOUNT_DIR/etc/systemd/system" -maxdepth 1 -name "*.service" -exec cp {} "$OUTPUT_DIR/systemd/services/" \; 2>/dev/null || true +fi + +# Copy all system services from /lib/systemd/system/ +if sudo test -d "$MOUNT_DIR/lib/systemd/system"; then + mkdir -p "$OUTPUT_DIR/systemd/system_services" + sudo find "$MOUNT_DIR/lib/systemd/system" -maxdepth 1 -name "*network*" -o -name "*wifi*" -o -name "*wpa*" -o -name "*connman*" 2>/dev/null | while read service; do + sudo cp "$service" "$OUTPUT_DIR/systemd/system_services/" 2>/dev/null || true + done +fi + +# List all enabled services +if sudo test -d "$MOUNT_DIR/etc/systemd/system/multi-user.target.wants"; then + sudo ls -la "$MOUNT_DIR/etc/systemd/system/multi-user.target.wants" > "$OUTPUT_DIR/systemd/enabled/multi-user.target.wants.txt" 2>/dev/null || true +fi + +if sudo test -d "$MOUNT_DIR/etc/systemd/system/network-online.target.wants"; then + sudo ls -la "$MOUNT_DIR/etc/systemd/system/network-online.target.wants" > "$OUTPUT_DIR/systemd/enabled/network-online.target.wants.txt" 2>/dev/null || true +fi + +# List all systemd services +sudo find "$MOUNT_DIR/etc/systemd/system" -type l -o -type f 2>/dev/null | sudo xargs ls -la 2>/dev/null > "$OUTPUT_DIR/systemd/all_systemd_links.txt" || true + +echo " ✓ Systemd services extracted" + +# --- Extract SSH configuration --- +echo "[*] Extracting SSH configuration..." +mkdir -p "$OUTPUT_DIR/ssh" +if sudo test -f "$MOUNT_DIR/etc/ssh/sshd_config"; then + sudo cp "$MOUNT_DIR/etc/ssh/sshd_config" "$OUTPUT_DIR/ssh/" 2>/dev/null || true +fi +if sudo test -f "$MOUNT_DIR/etc/ssh/authorized_keys"; then + sudo cp "$MOUNT_DIR/etc/ssh/authorized_keys" "$OUTPUT_DIR/ssh/" 2>/dev/null || true +fi +if sudo test -f "$MOUNT_DIR/home/rewt/.ssh/authorized_keys"; then + sudo cp "$MOUNT_DIR/home/rewt/.ssh/authorized_keys" "$OUTPUT_DIR/ssh/rewt_authorized_keys" 2>/dev/null || true +fi +echo " ✓ SSH config extracted" + +# --- Extract network configuration --- +echo "[*] Extracting network configuration..." +mkdir -p "$OUTPUT_DIR/network" + +# Basic network config +if sudo test -f "$MOUNT_DIR/etc/resolv.conf"; then + sudo cp "$MOUNT_DIR/etc/resolv.conf" "$OUTPUT_DIR/network/" 2>/dev/null || true +fi +if sudo test -f "$MOUNT_DIR/etc/hosts"; then + sudo cp "$MOUNT_DIR/etc/hosts" "$OUTPUT_DIR/network/" 2>/dev/null || true +fi +if sudo test -f "$MOUNT_DIR/etc/hostname"; then + sudo cp "$MOUNT_DIR/etc/hostname" "$OUTPUT_DIR/network/" 2>/dev/null || true +fi + +# Network interfaces +if sudo test -f "$MOUNT_DIR/etc/network/interfaces"; then + sudo cp "$MOUNT_DIR/etc/network/interfaces" "$OUTPUT_DIR/network/" 2>/dev/null || true +fi +if sudo test -d "$MOUNT_DIR/etc/network/interfaces.d"; then + sudo cp -r "$MOUNT_DIR/etc/network/interfaces.d" "$OUTPUT_DIR/network/" 2>/dev/null || true +fi + +# wpa_supplicant +if sudo test -d "$MOUNT_DIR/etc/wpa_supplicant"; then + sudo cp -r "$MOUNT_DIR/etc/wpa_supplicant" "$OUTPUT_DIR/network/" 2>/dev/null || true + # List all wpa_supplicant config files for debugging + sudo ls -la "$MOUNT_DIR/etc/wpa_supplicant/" > "$OUTPUT_DIR/network/wpa_supplicant_files.txt" 2>/dev/null || true +fi + +# iwd (alternative to wpa_supplicant) +if sudo test -d "$MOUNT_DIR/var/lib/iwd"; then + sudo cp -r "$MOUNT_DIR/var/lib/iwd" "$OUTPUT_DIR/network/" 2>/dev/null || true +fi + +# Check for active network interfaces +echo "Network Interface Information:" > "$OUTPUT_DIR/network/interface_info.txt" 2>/dev/null || true +if sudo test -f "$MOUNT_DIR/sys/class/net/wlan0/address"; then + echo "wlan0 MAC: $(sudo cat $MOUNT_DIR/sys/class/net/wlan0/address 2>/dev/null || echo 'N/A')" >> "$OUTPUT_DIR/network/interface_info.txt" +fi + +echo " ✓ Network config extracted" + +# --- Extract opensleep logs if present --- +echo "[*] Extracting opensleep logs and configuration..." +if sudo test -d "$MOUNT_DIR/opt/opensleep"; then + mkdir -p "$OUTPUT_DIR/opensleep" + sudo cp "$MOUNT_DIR/opt/opensleep/config.ron" "$OUTPUT_DIR/opensleep/" 2>/dev/null || true + # Look for opensleep log files + if sudo test -f "$MOUNT_DIR/var/log/opensleep.log"; then + sudo cp "$MOUNT_DIR/var/log/opensleep.log" "$OUTPUT_DIR/opensleep/" 2>/dev/null || true + fi + echo " ✓ opensleep config and logs extracted" +else + echo " ℹ opensleep not installed" +fi + +# --- Extract Eight Sleep services and scripts --- +echo "[*] Extracting Eight Sleep configuration..." +mkdir -p "$OUTPUT_DIR/eightsleep" + +# Look for Eight Sleep services +if sudo test -d "$MOUNT_DIR/lib/systemd/system"; then + sudo find "$MOUNT_DIR/lib/systemd/system" -name "*capybara*" -o -name "*eight*" -o -name "*variscite*" 2>/dev/null | while read service; do + sudo cp "$service" "$OUTPUT_DIR/eightsleep/" 2>/dev/null || true + done +fi + +# Look for Eight Sleep scripts and binaries +if sudo test -d "$MOUNT_DIR/opt"; then + sudo ls -la "$MOUNT_DIR/opt" > "$OUTPUT_DIR/eightsleep/opt_contents.txt" 2>/dev/null || true +fi + +if sudo test -d "$MOUNT_DIR/usr/local/bin"; then + sudo ls -la "$MOUNT_DIR/usr/local/bin" > "$OUTPUT_DIR/eightsleep/usr_local_bin_contents.txt" 2>/dev/null || true +fi + +# Look for any WiFi-related scripts +if sudo test -d "$MOUNT_DIR/etc/init.d"; then + sudo ls -la "$MOUNT_DIR/etc/init.d" > "$OUTPUT_DIR/eightsleep/init_d_contents.txt" 2>/dev/null || true + sudo find "$MOUNT_DIR/etc/init.d" -name "*network*" -o -name "*wifi*" 2>/dev/null | while read script; do + sudo cp "$script" "$OUTPUT_DIR/eightsleep/" 2>/dev/null || true + done +fi + +# Extract variscite-wifi script (important for WiFi setup) +if sudo test -d "$MOUNT_DIR/etc/wifi"; then + sudo ls -la "$MOUNT_DIR/etc/wifi" > "$OUTPUT_DIR/eightsleep/etc_wifi_contents.txt" 2>/dev/null || true + if sudo test -f "$MOUNT_DIR/etc/wifi/variscite-wifi"; then + sudo cp "$MOUNT_DIR/etc/wifi/variscite-wifi" "$OUTPUT_DIR/eightsleep/" 2>/dev/null || true + echo " ✓ variscite-wifi script extracted" + fi +fi + +echo " ✓ Eight Sleep config extracted" + +# --- Extract installed packages list --- +echo "[*] Extracting package information..." +mkdir -p "$OUTPUT_DIR/packages" + +# dpkg (Debian/Ubuntu) +if sudo test -f "$MOUNT_DIR/var/lib/dpkg/status"; then + sudo grep "^Package:\|^Status:" "$MOUNT_DIR/var/lib/dpkg/status" | grep -B1 "install ok installed" | grep "^Package:" | awk '{print $2}' | sort > "$OUTPUT_DIR/packages/dpkg_installed.txt" 2>/dev/null || true +fi + +# opkg (embedded systems) +if sudo test -f "$MOUNT_DIR/usr/lib/opkg/status"; then + sudo grep "^Package:" "$MOUNT_DIR/usr/lib/opkg/status" | awk '{print $2}' | sort > "$OUTPUT_DIR/packages/opkg_installed.txt" 2>/dev/null || true +fi + +# Check for specific network managers +echo "Checking for network management tools:" > "$OUTPUT_DIR/packages/network_tools.txt" +for tool in NetworkManager connman systemd-networkd wpa_supplicant iwd nmcli connmanctl; do + if sudo test -f "$MOUNT_DIR/usr/bin/$tool" || sudo test -f "$MOUNT_DIR/usr/sbin/$tool" || sudo test -f "$MOUNT_DIR/bin/$tool" || sudo test -f "$MOUNT_DIR/sbin/$tool"; then + echo " ✓ $tool found" >> "$OUTPUT_DIR/packages/network_tools.txt" + else + echo " ✗ $tool not found" >> "$OUTPUT_DIR/packages/network_tools.txt" + fi +done + +echo " ✓ Package info extracted" + +# --- Create system information file --- +echo "[*] Creating system information file..." +cat > "$OUTPUT_DIR/system_info.txt" </dev/null || echo "N/A") + +WiFi Configuration Files Found: +$(if sudo test -f "$MOUNT_DIR/etc/wpa_supplicant/wpa_supplicant-wlan0.conf"; then echo " ✓ wpa_supplicant-wlan0.conf"; fi) +$(if sudo test -f "$MOUNT_DIR/etc/systemd/network/25-wlan0.network"; then echo " ✓ 25-wlan0.network"; fi) +$(if sudo test -f "$MOUNT_DIR/etc/wifi/variscite-wifi"; then echo " ✓ variscite-wifi script"; fi) + +Custom Services Found: +$(if sudo test -f "$MOUNT_DIR/etc/systemd/system/ssh-early.service"; then echo " ✓ ssh-early.service"; fi) +$(if sudo test -f "$MOUNT_DIR/etc/systemd/system/disable-eightsleep-services.service"; then echo " ✓ disable-eightsleep-services.service"; fi) +$(if sudo test -L "$MOUNT_DIR/etc/systemd/system/multi-user.target.wants/wpa_supplicant@wlan0.service"; then echo " ✓ wpa_supplicant@wlan0.service (enabled)"; fi) + +EOF + +# --- List NetworkManager connections --- +echo "[*] Listing NetworkManager connections..." +if sudo test -d "$MOUNT_DIR/etc/NetworkManager/system-connections"; then + echo "" >> "$OUTPUT_DIR/system_info.txt" + echo "NetworkManager Connections:" >> "$OUTPUT_DIR/system_info.txt" + sudo ls -la "$MOUNT_DIR/etc/NetworkManager/system-connections" >> "$OUTPUT_DIR/system_info.txt" 2>/dev/null || true +fi + +# --- Fix permissions on extracted files --- +echo "[*] Fixing file permissions..." +sudo chown -R $(id -u):$(id -g) "$OUTPUT_DIR" +sudo chmod -R u+rw "$OUTPUT_DIR" + +echo "" +echo "======================================" +echo "Extraction Complete!" +echo "======================================" +echo "" +echo "[+] Logs extracted to: $OUTPUT_DIR" +echo "" +echo "To view the extracted data:" +echo " - System info: cat $OUTPUT_DIR/system_info.txt" +echo " - Network tools: cat $OUTPUT_DIR/packages/network_tools.txt" +echo " - Installed packages: cat $OUTPUT_DIR/packages/*_installed.txt" +echo " - Network config: ls -la $OUTPUT_DIR/network/" +echo " - Systemd services: cat $OUTPUT_DIR/systemd/services/*.service" +echo " - Enabled services: cat $OUTPUT_DIR/systemd/enabled/*.txt" +echo " - Eight Sleep config: ls -la $OUTPUT_DIR/eightsleep/" +echo " - SSH config: cat $OUTPUT_DIR/ssh/sshd_config" +echo "" +echo "For journal logs (if present):" +echo " journalctl --directory=$OUTPUT_DIR/journal --no-pager" +echo "" diff --git a/scripts/full_patch_workflow.md b/scripts/full_patch_workflow.md new file mode 100644 index 0000000..0bf65a1 --- /dev/null +++ b/scripts/full_patch_workflow.md @@ -0,0 +1,1496 @@ +# Pod 3 (SD Card) Image Patching Script - Documentation + +## ⚠️ Important: Pod 3 SD Card Version ONLY + +**This script is specifically designed for:** +- Eight Sleep Pod 3 with **removable SD card** + +**This script does NOT work with:** +- ❌ Pod 1 (no SD card, no SSH access) +- ❌ Pod 2 (no SD card, no SSH access) +- ❌ Pod 3 eMMC version (non-removable storage, requires different method) +- ❌ Pod 4 (no SD card, see [free-sleep tutorial](https://github.com/throwaway31265/free-sleep)) +- ❌ Pod 5 (no SD card, see [free-sleep tutorial](https://github.com/throwaway31265/free-sleep)) + +For Pod 3 without SD card or other Pod versions, see the [SETUP.md](../SETUP.md) file. + +--- + +## Overview + +The `full_patch_workflow.sh` script modifies Pod 3 SD card images to enable: +- WiFi auto-connect on boot +- SSH access via WiFi +- Optional OpenSleep installation (MQTT bridge) +- Optional disabling of Eight Sleep cloud services + +This document explains what the script does and how the patched Pod 3 boots. + +--- + +## Table of Contents + +1. [What the Script Does](#what-the-script-does) +2. [Boot Sequence - Unpatched Pod 3](#boot-sequence---unpatched-pod-3) +3. [Boot Sequence - Patched Pod 3](#boot-sequence---patched-pod-3) +4. [Service Dependency Chain](#service-dependency-chain) +5. [The WiFi Problem and Solution](#the-wifi-problem-and-solution) +6. [Configuration Options](#configuration-options) +7. [Files Created/Modified](#files-createdmodified) +8. [Factory Reset Behavior](#factory-reset-behavior) +9. [Post-Patch Validation](#post-patch-validation) +10. [Troubleshooting](#troubleshooting) + +--- + +## What the Script Does + +### High-Level Process + +1. **Mounts SD card image** - Uses loop device to access partitions +2. **Extracts rootfs.tar.gz** - Decompresses root filesystem archive +3. **Stages modifications** - Prepares all files to be added/modified +4. **Appends to tar archive** - Efficiently adds modifications using `tar -rf` +5. **Recompresses rootfs.tar.gz** - Creates patched archive +6. **Writes back to image** - Updates both archive and mounted partition +7. **Unmounts and cleans up** - Safely releases resources + +### Key Features + +**Efficient Tar Append Method:** +- Does NOT fully extract rootfs (would be ~2GB) +- Uses `tar -rf` to append new files +- Preserves file ownership with `--numeric-owner` +- Much faster than full extract/repack + +**Dual-Write Strategy:** +- Writes configs to `rootfs.tar.gz` (survives factory reset) +- ALSO writes to mounted partition (works without factory reset) +- Ensures WiFi works in both scenarios + +**Safety Features:** +- Validates disk space before starting +- Checks for required files (SSH keys, binaries) +- Warns if creating unreachable system (opensleep without WiFi) +- Confirmation prompts for destructive operations + +--- + +## Boot Sequence - Unpatched Pod 3 + +### Standard Eight Sleep Boot (BROKEN WiFi) + +``` +1. Kernel loads + └─> imx8mm-var-dart-eight (Variscite SOM) + +2. systemd starts + └─> sysinit.target + +3. variscite-wifi.service attempts to start + ├─> Runs /etc/wifi/variscite-wifi start + ├─> Checks EEPROM (i2cget -f -y 0x0 0x52 0x20) + ├─> EEPROM bit 0 = 0 (WiFi reported as "not available") + └─> ❌ EXITS EARLY - No WiFi initialization! + └─> wlan0 interface NEVER CREATED + +4. variscite-bt.service starts + ├─> Bluetooth hardware initialization + └─> ✅ Bluetooth comes up successfully + +5. network.target reached + └─> No network interfaces available (eth0 doesn't exist, wlan0 not created) + +6. Eight Sleep services start + ├─> capybara.service (main control service) + ├─> variscite-bt.service (Bluetooth - already running) + └─> Other Eight Sleep services + +7. Result: + ❌ No WiFi connectivity (wlan0 never created) + ✅ Bluetooth working (for phone app pairing) + ❌ No network access initially + ❌ Eight Sleep app cannot connect yet + ⏳ Waiting for user to configure WiFi via Bluetooth... +``` + +### Normal User Flow (Without Patching) + +**How Eight Sleep configures WiFi:** + +``` +1. User performs factory reset + └─> Holds button, system resets to defaults + +2. Pod 3 boots + ├─> Bluetooth comes up (variscite-bt.service works) + └─> WiFi hardware NOT initialized (variscite-wifi exits early) + +3. User opens Eight Sleep app on phone + └─> App searches for Pod via Bluetooth + +4. Phone connects to Pod via Bluetooth + ├─> Bluetooth pairing successful + └─> Communication channel established + +5. App prompts for WiFi credentials + └─> User enters SSID and password in app + +6. Phone sends WiFi config to Pod via Bluetooth + ├─> capybara.service or another Eight Sleep service receives config + └─> Stores WiFi credentials (not via wpa_supplicant initially) + +7. Eight Sleep service configures WiFi + ├─> Likely bypasses variscite-wifi completely + ├─> May manually initialize WiFi hardware (GPIO, MMC, modprobe) + ├─> OR uses a different WiFi management service + └─> WiFi connects to network + +8. Pod connects to Eight Sleep cloud services + ├─> Checks for firmware updates + └─> Registers device with cloud + +9. If update available: + ├─> Update downloaded from Eight Sleep servers + ├─> Update applied (firmware, rootfs, etc.) + └─> System reboots + +10. Result: + ✅ WiFi configured and connected + ✅ Pod registered with Eight Sleep cloud + ✅ App can control Pod + ✅ Cloud services active +``` + +**Why variscite-wifi fails but Eight Sleep WiFi works:** + +Eight Sleep's setup process **bypasses the variscite-wifi EEPROM check** by: +- Using Bluetooth for initial configuration (not relying on WiFi at first) +- Manually initializing WiFi hardware through capybara or custom service +- OR downloading updated variscite-wifi script during firmware update +- OR using entirely different WiFi management (ConnMan, custom daemon, etc.) + +The EEPROM check failure is **intentional** - it prevents WiFi from auto-starting, forcing users to configure through the app (which ensures cloud registration). + +**Why WiFi Fails:** +The Eight Sleep Pod 3 hardware HAS WiFi (Broadcom BCM4339/2 chip), but the EEPROM configuration reports it as unavailable. This appears to be intentional - Eight Sleep likely configures WiFi through a different mechanism during manufacturing or through their cloud services. + +This is **intentional by design**: +- Prevents Pod from connecting to random WiFi networks +- Forces user to set up through Eight Sleep app +- Ensures device registers with Eight Sleep cloud during setup +- Allows Eight Sleep to control WiFi configuration process +- May download updated WiFi scripts during initial firmware update + +--- + +## Boot Sequence - Patched Pod 3 + +### With WiFi Configuration Only (`-w -s`) + +``` +1. Kernel loads + └─> imx8mm-var-dart-eight + +2. systemd starts + └─> sysinit.target + +3. opensleep-wifi.service starts ✅ + ├─> Runs /etc/wifi/opensleep-wifi start + ├─> Bypasses EEPROM check (always returns "available") + ├─> Configures GPIO pins: + │ ├─> GPIO 39 (WIFI_3V3) - 3.3V power rail + │ ├─> GPIO 52 (WIFI_1V8) - 1.8V power rail + │ ├─> GPIO 42 (WIFI_EN) - WiFi chip enable + │ └─> GPIO 38 (BT_EN) - Bluetooth enable + ├─> Power sequencing: + │ ├─> Enable 3.3V → Wait 10ms + │ ├─> Enable 1.8V → Wait 10ms + │ ├─> Assert WIFI_EN + │ ├─> Toggle BT_BUF (timing) + │ └─> Deassert BT_EN + ├─> MMC controller operations: + │ ├─> Unbind WiFi from MMC: echo 30b40000.mmc > .../unbind + │ ├─> Power up WiFi chip + │ └─> Rebind to MMC: echo 30b40000.mmc > .../bind + ├─> Load driver: modprobe brcmfmac + ├─> Wait for wlan0 interface (up to 20 seconds, 3 retries) + └─> ✅ SUCCESS - wlan0 interface created! + +4. Kernel initializes WiFi driver + ├─> brcmfmac: Firmware BCM4339/2 version 6.37.39.114 loaded + ├─> cfg80211: Using regulatory domain US + └─> wlan0 interface appears in /sys/class/net/ + +5. network.target reached + └─> Network infrastructure ready + +6. wpa_supplicant@wlan0.service starts ✅ + ├─> Waits for opensleep-wifi.service (dependency) + ├─> Reads /etc/wpa_supplicant/wpa_supplicant-wlan0.conf + ├─> Scans for configured SSID + ├─> Authenticates with PSK (WPA2) + ├─> Association successful + └─> ✅ CTRL-EVENT-CONNECTED + +7. systemd-networkd handles network configuration + ├─> Reads /etc/systemd/network/25-wlan0.network + ├─> Sends DHCP request + ├─> Receives IP address from router + └─> ✅ Network connectivity established + +8. network-online.target reached + └─> Network fully operational + +9. ssh-early.service starts ✅ + ├─> Waits for opensleep-wifi.service (dependency) + ├─> Waits 5 seconds (network stabilization) + ├─> Starts sshd.socket + └─> ✅ SSH now accessible over WiFi + +10. Multi-user.target reached + └─> System fully booted + +11. Result: + ✅ WiFi connected to configured network + ✅ IP address obtained via DHCP + ✅ SSH accessible: ssh rewt@ + ✅ Eight Sleep services still running (app works) + ✅ Device accessible for development/debugging +``` + +### With WiFi + OpenSleep (`-w -s -b opensleep`) + +``` +1-8. [Same as WiFi-only boot above] + +9. opensleep.service starts ✅ + ├─> Waits for network-online.target + ├─> Runs /opt/opensleep/opensleep + ├─> Reads /opt/opensleep/config.ron + ├─> Connects to MQTT broker + └─> ✅ Begins publishing Pod 3 sensor data + +10. disable-eightsleep-services.service starts ✅ + ├─> Waits for opensleep.service to be active + ├─> Timeout: 60 seconds + ├─> Checks: systemctl is-active opensleep.service + ├─> Once opensleep active: + │ ├─> systemctl disable capybara.service + │ ├─> systemctl disable variscite-bt.service + │ ├─> systemctl disable eightsleep-*.service + │ ├─> systemctl stop capybara.service + │ └─> systemctl stop [other Eight Sleep services] + └─> ✅ Eight Sleep cloud services disabled + +11. ssh-early.service starts ✅ + └─> SSH accessible + +12. Multi-user.target reached + └─> System fully booted + +13. Result: + ✅ WiFi connected (via opensleep-wifi, NOT variscite-wifi) + ✅ opensleep running and publishing to MQTT + ✅ Eight Sleep cloud services disabled + ✅ Eight Sleep app CANNOT connect (local control only) + ✅ SSH accessible over WiFi + ✅ Pod 3 under full local control +``` + +**Critical Point:** WiFi continues to work after Eight Sleep services are disabled because: +- opensleep-wifi.service is independent of Eight Sleep +- variscite-wifi.service is masked (never runs) +- wpa_supplicant is a system service, not Eight Sleep specific +- Network infrastructure doesn't depend on Eight Sleep services + +--- + +## Service Dependency Chain + +### Visual Dependency Graph + +``` +Boot + │ + ├──> sysinit.target + │ │ + │ └──> opensleep-wifi.service + │ ├─ Description: Initialize WiFi hardware (GPIO, MMC, driver) + │ ├─ Type: oneshot + │ ├─ RemainAfterExit: yes (stays "active") + │ ├─ Before: network.target + │ └─ Creates: wlan0 interface + │ │ + ├──> network.target <─────┘ + │ │ + │ ├──> systemd-networkd.service + │ │ └─ Manages: Network interface configuration + │ │ + │ └──> wpa_supplicant@wlan0.service + │ ├─ After: opensleep-wifi.service + │ ├─ Requires: opensleep-wifi.service ← Hard dependency + │ └─ Connects to WiFi network + │ │ + ├──> network-online.target <─┘ + │ │ + │ └──> opensleep.service (if installed) + │ ├─ After: network-online.target + │ ├─ Runs: /opt/opensleep/opensleep + │ └─ Provides: MQTT bridge for local control + │ │ + │ └──> disable-eightsleep-services.service (if -b used) + │ ├─ After: opensleep.service + │ ├─ Waits: Until opensleep is active (60s timeout) + │ └─ Disables: Eight Sleep cloud services + │ + └──> multi-user.target + │ + ├──> ssh-early.service + │ ├─ After: opensleep-wifi.service, wpa_supplicant.service + │ ├─ Requires: opensleep-wifi.service + │ ├─ Waits: 5 seconds for network stabilization + │ └─ Starts: sshd.socket (SSH daemon) + │ + └──> [Other services...] +``` + +### Service Relationships Explained + +**opensleep-wifi.service:** +- **Purpose:** Initialize WiFi hardware (what variscite-wifi should do but doesn't) +- **Why RemainAfterExit=yes:** Service stays "active" so dependents know WiFi is ready +- **Why Before=network.target:** Must create wlan0 before network initialization +- **What it does:** GPIO setup, MMC binding, load brcmfmac driver, create wlan0 + +**wpa_supplicant@wlan0.service:** +- **Purpose:** Connect to WiFi network +- **Requires opensleep-wifi:** Won't start if WiFi hardware init fails +- **After opensleep-wifi:** Waits until wlan0 exists +- **Template service:** @wlan0 specifies which interface to use + +**ssh-early.service:** +- **Purpose:** Enable SSH access over WiFi +- **Requires opensleep-wifi:** Ensures WiFi hardware is initialized +- **5-second wait:** Allows network to stabilize before starting SSH +- **Why "early":** Starts before other services, enables debugging + +**opensleep.service:** +- **Purpose:** MQTT bridge for local control +- **After network-online:** Waits for full network connectivity +- **Optional:** Only created if `-b` flag used + +**disable-eightsleep-services.service:** +- **Purpose:** Disable Eight Sleep cloud connectivity +- **Conditional:** Only created if `-b` flag used or `-d` flag set +- **Waits for opensleep:** Ensures local control is working before disabling cloud +- **One-shot:** Runs once on first boot, then disables itself + +--- + +## The WiFi Problem and Solution + +### The Problem + +**Eight Sleep's variscite-wifi.service fails on Pod 3:** + +```bash +#!/bin/bash +# variscite-wifi script (simplified) + +wifi_is_available() { + # Read SOM options from EEPROM + opt=$(i2cget -f -y 0x0 0x52 0x20) + + # Check bit 0 (WiFi present flag) + if [ $((opt & 0x1)) -eq 1 ]; then + return 0 # WiFi available + else + return 1 # WiFi NOT available + fi +} + +wifi_start() { + # Exit early if EEPROM says no WiFi + if ! wifi_is_available; then + exit 0 # ← PROBLEM: Exits without initializing WiFi! + fi + + # This code never runs because EEPROM check fails: + # - GPIO setup + # - MMC binding + # - modprobe brcmfmac + # - Wait for wlan0 +} +``` + +**Why the EEPROM check fails:** +- Pod 3 hardware DOES have WiFi (Broadcom BCM4339/2 chip) +- EEPROM at I2C address 0x52, offset 0x20, bit 0 = 0 (WiFi not available) +- **Intentional by Eight Sleep** - not a hardware defect +- Forces WiFi setup through app (ensures cloud registration) +- Prevents Pod from connecting without going through Eight Sleep pairing flow +- variscite-wifi exits early, no WiFi initialization happens +- wlan0 interface never created +- wpa_supplicant can't start (no interface) +- No network connectivity until user configures via Bluetooth + app + +### Eight Sleep's WiFi Configuration Method + +**During normal setup (via Eight Sleep app):** + +1. User factory resets Pod 3 +2. Bluetooth comes up (variscite-bt.service works fine) +3. User opens Eight Sleep app on phone +4. Phone connects to Pod via Bluetooth +5. App sends WiFi credentials to Pod over Bluetooth +6. Eight Sleep service (likely capybara or custom daemon): + - Receives WiFi config via Bluetooth + - **Manually initializes WiFi hardware** (bypassing variscite-wifi) + - OR triggers a different WiFi management service + - OR downloads updated scripts during firmware update +7. Pod connects to Eight Sleep cloud +8. Firmware/config updates applied if available +9. WiFi now works for cloud communication + +**Why Eight Sleep does this:** +- ✅ Ensures device registration with cloud +- ✅ Controls first-time setup experience +- ✅ Prevents "rogue" pods from connecting without app +- ✅ Allows firmware updates during initial setup +- ✅ Can track device activation + +**Why variscite-wifi fails but Eight Sleep WiFi works:** +- Eight Sleep bypasses variscite-wifi completely during setup +- Uses Bluetooth as initial communication channel +- Has alternative WiFi initialization in capybara or other service +- May download working WiFi scripts during firmware update + +### The Solution + +**Create opensleep-wifi service that bypasses EEPROM check:** + +This replicates what Eight Sleep does internally (manual WiFi init) but makes it automatic on boot: + +```bash +#!/bin/bash +# opensleep-wifi script (modified) + +wifi_is_available() { + # OpenSleep: Bypass EEPROM check + # WiFi hardware IS physically present + # Eight Sleep EEPROM has bit unset to force app-based setup + # We bypass this to enable direct WiFi configuration + return 0 # Always return "available" +} + +wifi_start() { + # Now this code DOES run: + if ! wifi_is_available; then # Always passes now + exit 0 + fi + + # GPIO initialization (runs) + # MMC binding (runs) + # modprobe brcmfmac (runs) + # Wait for wlan0 (runs) + # ✅ WiFi works! +} +``` + +**What we're doing:** +- Replicating Eight Sleep's internal WiFi initialization +- Bypassing the app-based setup requirement +- Allowing direct WiFi configuration via wpa_supplicant +- Skipping Eight Sleep cloud registration + +**Trade-offs:** +- ✅ WiFi auto-connects on boot (no app needed) +- ✅ SSH accessible immediately +- ✅ Can use local control (opensleep/MQTT) +- ⚠️ Bypasses Eight Sleep's intended setup flow +- ⚠️ Doesn't register with Eight Sleep cloud (unless you want it to) +- ⚠️ Eight Sleep app won't work (if you disable Eight Sleep services) + +**How we implement this:** + +1. **Copy variscite-wifi script** to `/etc/wifi/opensleep-wifi` +2. **Modify with sed** to replace EEPROM check with `return 0` +3. **Create opensleep-wifi.service** to run the modified script +4. **Mask variscite-wifi.service** to prevent conflicts +5. **Update dependencies** so services wait for opensleep-wifi, not variscite-wifi + +**Result:** +- WiFi hardware gets initialized on boot +- wlan0 interface appears +- wpa_supplicant can connect +- SSH becomes accessible +- Network connectivity works + +--- + +## Configuration Options + +### Required Arguments + +```bash +-i IMAGE_FILE # Path to SD card image file +-k PUBKEY_FILE # SSH public key for 'rewt' user authentication +``` + +### Optional Arguments + +```bash +# WiFi Configuration +-w WIFI_SSID # WiFi network name +-s WIFI_PASSWORD # WiFi network password/passphrase + +# MAC Address Configuration +-m MAC_ADDRESS # Set persistent MAC address (format: AA:BB:CC:DD:EE:FF) + # Prevents MAC from changing on factory reset + +# SSH Configuration +-P ROOT_PASSWORD # Set password for 'rewt' user (enables password auth) + +# OpenSleep Installation +-b OPENSLEEP_BINARY # Path to opensleep binary to install +-S OPENSLEEP_SERVICE # Path to custom opensleep.service file +-c OPENSLEEP_CONFIG # Path to custom config.ron file + +# Validation Script +-v VALIDATION_SCRIPT # Path to validation script (default: auto-detect in same directory) + +# Eight Sleep Services +-d # Disable Eight Sleep services (even without -b) + +# Output Control +-o OUTPUT_FILE # Custom output path (default: -patched.img) +-w WORK_DIR # Custom working directory (needs 16GB free) + +# Help +-h # Show usage information +``` + +### Validation Script Option + +The script can automatically include a validation script in the patched image: + +**Auto-detection (default):** +- Looks for `full_patch_workflow_post_ssh_validation.sh` in the same directory +- If found, automatically includes it in the image +- Placed at `/home/rewt/validate_deployment.sh` on the device +- Welcome banner shows script location on first SSH login + +**Manual specification:** +```bash +-v /path/to/custom_validation.sh +``` + +**Skip validation script:** +- Simply don't have the validation script in the same directory +- Or use `-v ""` to explicitly skip + +**After patching with validation script:** +```bash +# SSH into Pod 3 +ssh rewt@ + +# You'll see a welcome banner: +========================================== + OpenSleep Pod 3 - Welcome! +========================================== + +A validation script is available to verify +your system configuration. + +Run: ~/validate_deployment.sh +Or: ./validate_deployment.sh + +To skip this message in future sessions: + export VALIDATION_SHOWN=1 + +# Run validation +./validate_deployment.sh +``` + +**Note:** This validation is specific to Pod 3 with SD card. Other Pod versions have different configurations. + +See [Post-Patch Validation](#post-patch-validation) for details on what the script checks. + +### Common Usage Patterns + +**WiFi + SSH only:** +```bash +./full_patch_workflow.sh \ + -i pod3.img \ + -k ~/.ssh/id_rsa.pub \ + -w "MyNetwork" \ + -s "MyPassword" +``` +→ WiFi auto-connects, SSH accessible, Eight Sleep services still work + +**WiFi + SSH + Persistent MAC:** +```bash +./full_patch_workflow.sh \ + -i pod3.img \ + -k ~/.ssh/id_rsa.pub \ + -w "MyNetwork" \ + -s "MyPassword" \ + -m "02:11:22:33:44:55" +``` +→ WiFi auto-connects, MAC address stays the same after factory resets + +**WiFi + SSH + OpenSleep + Validation:** +```bash +./full_patch_workflow.sh \ + -i pod3.img \ + -k ~/.ssh/id_rsa.pub \ + -w "MyNetwork" \ + -s "MyPassword" \ + -b ./opensleep +``` +→ Full local control, Eight Sleep services disabled, WiFi works, validation script included (auto-detected) + +**With custom validation script:** +```bash +./full_patch_workflow.sh \ + -i pod3.img \ + -k ~/.ssh/id_rsa.pub \ + -w "MyNetwork" \ + -s "MyPassword" \ + -v ./custom_validation.sh +``` +→ Uses custom validation script instead of default + +**OpenSleep without WiFi (NOT RECOMMENDED):** +```bash +./full_patch_workflow.sh \ + -i pod3.img \ + -k ~/.ssh/id_rsa.pub \ + -b ./opensleep +``` +→ ⚠️ WARNING: Device will be unreachable, you will need to configure wifi via the app (no Ethernet on Pod 3) + +--- + +## Files Created/Modified + +### SSH Configuration + +**Created:** +- `/etc/ssh/authorized_keys` - SSH public key for 'rewt' user +- `/etc/systemd/system/ssh-early.service` - Enable SSH early in boot + +**Modified:** +- `/etc/ssh/sshd_config` - Enable key authentication, optionally password auth + +### WiFi Configuration (if `-w -s` provided) + +**Created:** +- `/etc/wifi/opensleep-wifi` - Modified variscite-wifi script (EEPROM bypass) +- `/etc/systemd/system/opensleep-wifi.service` - WiFi hardware initialization +- `/etc/systemd/system/network.target.wants/opensleep-wifi.service` - Enable service +- `/etc/wpa_supplicant/wpa_supplicant-wlan0.conf` - WiFi credentials (SSID/PSK) +- `/etc/systemd/network/25-wlan0.network` - DHCP configuration for wlan0 +- `/etc/systemd/system/wpa_supplicant@wlan0.service.d/override.conf` - Service dependencies +- `/etc/systemd/system/multi-user.target.wants/wpa_supplicant@wlan0.service` - Enable service + +**Masked:** +- `/etc/systemd/system/variscite-wifi.service` → `/dev/null` - Prevent Eight Sleep WiFi from running + +### MAC Address Configuration (if `-m` provided) + +**Created:** +- `/etc/systemd/system/opensleep-mac.service` - Service to set persistent MAC address +- `/etc/systemd/system/network.target.wants/opensleep-mac.service` - Enable service + +**How it works:** +- brcmfmac WiFi driver generates random MAC at firmware load time +- systemd `.link` files don't work reliably with this driver +- `opensleep-wifi.service` loads the brcmfmac driver (modprobe brcmfmac) +- `opensleep-mac.service` runs **after** opensleep-wifi loads the driver +- Service sets MAC using `ip link set dev wlan0 address ` +- Runs **before** wpa_supplicant starts, so WiFi connects with correct MAC +- Persists across reboots, updates, and **factory resets** + +**Service timing:** +``` +opensleep-wifi.service + └─> modprobe brcmfmac (loads driver, random MAC generated) + └─> opensleep-mac.service + └─> ip link set wlan0 down + └─> ip link set wlan0 address + └─> ip link set wlan0 up + └─> wpa_supplicant@wlan0.service (connects to WiFi with correct MAC) +``` + +**Why this approach:** +- `.link` files are processed by udev, but brcmfmac sets MAC during firmware init +- By the time udev processes the .link file, MAC is already set and can't be changed +- opensleep-wifi reloads the driver (modprobe), which generates a new random MAC +- We must set MAC **after** driver load but **before** wpa_supplicant starts +- Using a service with proper ordering dependencies is the only reliable way + +**Why this matters:** +- By default, Pod 3 generates a random MAC on each factory reset +- This breaks MAC filtering/whitelisting on routers +- With `-m` option, MAC stays the same forever +- Useful for network security policies that require MAC whitelisting + +**Example use cases:** +- Corporate/enterprise WiFi with MAC filtering +- Home network with MAC whitelist for security +- DHCP reservations based on MAC address +- Network access control lists (ACLs) + +### OpenSleep Installation (if `-b` provided) + +**Created:** +- `/opt/opensleep/opensleep` - OpenSleep binary +- `/opt/opensleep/config.ron` - Configuration file (MQTT broker, topics, etc.) +- `/lib/systemd/system/opensleep.service` - systemd service definition +- `/etc/systemd/system/multi-user.target.wants/opensleep.service` - Enable service + +### Validation Script (if included) + +**Created:** +- `/home/rewt/validate_deployment.sh` - Post-deployment validation script +- `/home/rewt/.bashrc` - Welcome banner showing script location + +**Purpose:** +- Validates network connectivity (WiFi, IP, internet) +- Checks service status (opensleep-wifi, wpa_supplicant, ssh-early, opensleep) +- Verifies Eight Sleep services are disabled +- Confirms files are in correct locations +- Shows service logs if available + +**Auto-included when:** +- `full_patch_workflow_post_ssh_validation.sh` exists in same directory as patching script +- Or explicitly specified with `-v` option + +**User Experience:** +- Welcome banner on first SSH login shows script location +- Easy to run: `./validate_deployment.sh` or `~/validate_deployment.sh` +- Banner can be suppressed: `export VALIDATION_SHOWN=1` + +### Eight Sleep Service Disabling (if `-b` or `-d` provided) + +**Created:** +- `/etc/systemd/system/disable-eightsleep-services.service` - One-time service disabler +- `/etc/systemd/system/multi-user.target.wants/disable-eightsleep-services.service` - Enable + +**Services Disabled (on first boot):** +- `capybara.service` - Eight Sleep main control service +- `variscite-bt.service` - Bluetooth service +- Other Eight Sleep cloud communication services + +--- + +## Factory Reset Behavior + +### What is Factory Reset? + +**Physical Button:** +- Pod 3 has a reset button (power button) accessible on the device +- Connected to `bd718xx-pwrkey` GPIO (power management IC) +- Detected by kernel as `/dev/input/event1` + +**How Factory Reset is Triggered:** + +``` +1. User holds reset button during power-on + └─> Button state detected by bd718xx power management IC + +2. Kernel boots, detects button press + └─> GPIO state readable at /dev/input/event1 + +3. factory-reset.service starts (early in boot) + ├─> Runs /usr/bin/factory-reset.sh or similar + ├─> Checks button state (held = factory reset) + └─> If button held: + ├─> Output: "loading globals..." + ├─> Output: "blinking leds..." (visual feedback) + └─> Calls: install_yocto.sh -u + +4. install_yocto.sh -u runs: + ├─> "*** Variscite MX8 Yocto eMMC Recovery ***" + ├─> Detects board: imx8mm-var-dart-eight + ├─> Deletes current eMMC partitions + ├─> Creates new partitions (A/B + cage style) + ├─> Formats partitions + └─> Extracts rootfs.tar.gz over root filesystem: + └─> cd / && gunzip -c /dev/mmcblk2p1/rootfs.tar.gz | tar -xp + +5. System reboots with fresh filesystem +``` + +**What Gets Reset:** +- ✅ All system files (from rootfs.tar.gz) +- ✅ Configuration in /etc +- ✅ Installed services +- ❌ User data in /var (may be preserved) +- ❌ SD card partition table (already set up) + +**What Persists:** +- SD card remains formatted (partitions exist) +- rootfs.tar.gz remains on partition 1 +- Hardware configuration (EEPROM) + +Pod 3's "factory reset" button does: +```bash +#!/bin/bash +# Simplified factory reset process + +# Extract rootfs over root filesystem +cd / +gunzip -c /dev/mmcblk2p1/rootfs.tar.gz | tar -xp + +# Reboot +reboot +``` + +**Effect:** Restores system files from `rootfs.tar.gz`, but preserves: +- User data in `/var` +- Some config files +- Partition table + +### Why We Write to Both Locations + +The patching script uses a **dual-write strategy** because factory reset behavior depends on when it's triggered: + +**Location 1: rootfs.tar.gz (SD card partition 1)** +- Modified files appended to tar archive +- Gzipped and written back to partition +- **Survives factory reset** ✅ +- User does factory reset → rootfs.tar.gz extracted → our changes applied + +**Location 2: Mounted root partition** +- Files written directly to `/etc`, `/opt`, etc. +- **Works immediately without factory reset** ✅ +- User reimages SD card and boots → WiFi works right away +- **Gets overwritten if factory reset triggered** ⚠️ + +### How factory-reset.service Works + +**Service behavior:** +``` +[Unit] +Description=Factory Reset Service +# Runs early in boot, before most services + +[Service] +Type=oneshot +ExecStart=/usr/bin/factory-reset.sh +# Checks if reset button (bd718xx-pwrkey) is held +# If held: extract rootfs.tar.gz and reboot +# If not held: exit quietly +``` + +**Important: Factory reset ALWAYS works, even if Eight Sleep services disabled!** +- factory-reset.service is a **system service**, not an Eight Sleep service +- Runs very early in boot (before network, before opensleep) +- Our patching script does NOT disable or modify it +- Provides a "safety net" - you can always factory reset to recover + +**Why this is important:** +- If something goes wrong with opensleep or WiFi config +- If device becomes unreachable +- If you want to revert changes +- Factory reset button ALWAYS works → extracts original rootfs.tar.gz → system restored + +**However, after factory reset:** +- If you patched rootfs.tar.gz: Your changes survive (WiFi, SSH, opensleep) +- If you only wrote to mounted partition: Changes lost, back to original state +- This is why we use **dual-write strategy** + +**⚠️ Important: MAC Address Changes on Factory Reset** +- **Without `-m` option:** The wlan0 MAC address is randomly generated on each factory reset +- **With `-m` option:** MAC address stays the same (configured via systemd .link file) +- If you have MAC filtering on your router, either: + - Use `-m` option to set a persistent MAC, OR + - Update your router's whitelist after each factory reset +- The validation script displays the current MAC address +- Random MAC is normal Eight Sleep behavior (for privacy) + +**Button detection:** +- Reads GPIO state from `/dev/input/event1` (bd718xx-pwrkey) +- Button held = factory reset triggered +- Button not held = normal boot continues +- Visual feedback: LEDs blink during reset process + +**⚠️ IMPORTANT: Power Cycle Required After Factory Reset (Patched Images Only)** + +When using our **patched Pod 3 image**, after factory reset you MUST power cycle the device: + +1. **Hold reset button during power-on** (factory reset starts) +2. **Wait for LED sequence:** + - Green blinking → Factory reset in progress + - **Yellow blinking → Factory reset complete** +3. **⚠️ POWER OFF the device** (unplug power) +4. **Wait 5-10 seconds** +5. **Power ON the device** (plug power back in) +6. **Device will now boot normally** with patched configuration + +**Why is this required?** +- Factory reset extracts files but some services may be in inconsistent state +- Power cycle ensures clean boot with all services starting correctly +- This is specific to our patched image (opensleep-wifi, ssh-early, opensleep services) +- **Not required for factory Eight Sleep images** (their reset process handles this) + +**What if I don't power cycle?** +- Some services may not start correctly (WiFi, SSH, opensleep) +- Device may appear to hang or not connect to WiFi +- Simply power cycle to fix + +**Reset process (when button held):** +```bash +#!/bin/bash +# Simplified factory-reset.sh logic + +# Check if button held +if button_is_held; then + echo "Factory reset triggered..." + + # Blink LEDs for visual feedback (green) + blink_leds + + # Run Yocto eMMC recovery + install_yocto.sh -u + # This script: + # - Repartitions eMMC (if needed) + # - Formats partitions + # - Extracts rootfs.tar.gz over / + + # LED changes to yellow when complete + # ⚠️ USER MUST POWER CYCLE HERE (patched images only) + + # Reboot + reboot +else + echo "Normal boot, no factory reset" + exit 0 +fi +``` + +### Factory Reset Scenarios + +**Scenario A: User reimages SD card, boots directly (no factory reset)** +``` +1. SD card written with patched image +2. Pod 3 boots from SD card +3. Reads files from mounted partition +4. ✅ opensleep-wifi script present → WiFi works +5. ✅ SSH accessible immediately +``` + +**Scenario B: User reimages SD card, performs factory reset** +``` +1. SD card written with patched image +2. User holds reset button +3. Factory reset extracts rootfs.tar.gz over / +4. opensleep-wifi script extracted from tar ✅ +5. Pod 3 reboots +6. ✅ WiFi works after reset +7. ✅ SSH accessible +``` + +**Scenario C: User patches SD card in live Pod 3** +``` +1. User removes SD card from Pod 3 +2. Patches SD card on computer +3. Reinserts SD card +4. Factory reset (to load new rootfs.tar.gz) +5. ✅ All changes applied +``` + +--- + +## Troubleshooting + +### WiFi Not Connecting + +**Step 1: Check if opensleep-wifi was created** + +Extract logs from SD card: +```bash +./extract_pod3_logs.sh -i patched_pod3.img -o ./logs +grep "opensleep-wifi" ./logs/system_info.txt +``` + +Should show: +``` +Custom services found: + ✓ opensleep-wifi.service +``` + +**Step 2: Check if opensleep-wifi.service started** + +```bash +cat ./logs/journal/decoded_full.log | grep opensleep-wifi +``` + +Should show: +``` +systemd[1]: Starting OpenSleep WiFi Initialization Service... +systemd[1]: Finished OpenSleep WiFi Initialization Service. +``` + +**Step 3: Check if wlan0 was created** + +```bash +cat ./logs/journal/decoded_kernel.log | grep wlan0 +``` + +Should show interface creation messages. + +**Step 4: Check wpa_supplicant connection** + +```bash +cat ./logs/journal/decoded_full.log | grep CTRL-EVENT-CONNECTED +``` + +Should show successful WiFi connection. + +### Device Unreachable After Disabling Eight Sleep Services + +**Symptom:** Installed opensleep without WiFi credentials, device now unreachable. + +**Cause:** Pod 3 has no Ethernet port, only WiFi. Without WiFi config: +- variscite-wifi fails (EEPROM check) +- opensleep-wifi not created (no `-w -s` flags) +- No network connectivity +- No way to access device + +**Solutions:** + +**Option 1: Factory Reset (ALWAYS WORKS) ✅** +```bash +# Hold reset button during power-on +# Factory reset is a SYSTEM service, not Eight Sleep service +# It ALWAYS works, even if you disabled all Eight Sleep services +# +# After reset: +# - If you patched rootfs.tar.gz: Your changes survive! +# (WiFi, SSH, opensleep all come back) +# - Device will boot with your patched configuration +# - You can SSH in again to fix issues +``` + +**Why factory reset is your safety net:** +- factory-reset.service is NOT disabled by our patching script +- Runs before opensleep, before network, before everything +- Physical button → GPIO → systemd → extract rootfs.tar.gz +- Cannot be disabled by software changes +- Even if system is completely broken, factory reset works + +**Option 2: Serial Console (if available)** +```bash +# Connect to serial console +screen /dev/ttyUSB0 115200 + +# Re-enable Eight Sleep services +systemctl enable capybara.service +systemctl start capybara.service + +# Or configure WiFi manually +wpa_passphrase "SSID" "password" > /etc/wpa_supplicant/wpa_supplicant.conf +systemctl start wpa_supplicant@wlan0 +``` + +**Option 3: Reimage SD Card** +```bash +# Write original Eight Sleep image +# Or write patched image WITH WiFi credentials this time +./full_patch_workflow.sh -i original.img -k key.pub -w "WiFi" -s "pass" -b opensleep +``` + +**Recommendation: Always use Option 1 (Factory Reset)** +- Fastest and easiest +- No need to open Pod or connect cables +- Works even if software is completely broken +- If you patched rootfs.tar.gz (which you did), all your changes come back +- Just hold button → wait 5 minutes → device back online with your patches + +### SSH Connection Refused + +**Symptom:** WiFi connected but SSH refuses connection. + +**Check 1: Is sshd running?** +```bash +cat ./logs/journal/decoded_full.log | grep ssh-early +``` + +**Check 2: Is SSH key correct?** +```bash +cat ./logs/ssh/rewt_authorized_keys +# Compare with your public key +``` + +**Check 3: Try password authentication (if `-P` was used)** +```bash +ssh rewt@ +# Enter password set with -P flag +``` + +### OpenSleep Not Starting + +**Check 1: Is binary executable?** +```bash +# On Pod 3: +ls -la /opt/opensleep/opensleep +# Should show: -rwxr-xr-x +``` + +**Check 2: Check service status** +```bash +systemctl status opensleep.service +journalctl -u opensleep.service +``` + +**Check 3: Verify network connectivity** +```bash +ping 8.8.8.8 # Test internet +ping # Test MQTT broker +``` + +### Factory Reset Doesn't Apply Changes + +**Symptom:** Factory reset but changes don't appear. + +**Cause:** Changes only written to mounted partition, not rootfs.tar.gz. + +**Verification:** +```bash +# Check if changes are in rootfs.tar.gz +mkdir /tmp/test +cd /tmp/test +tar -tzf /dev/mmcblk2p1/rootfs.tar.gz | grep opensleep-wifi +# Should show: ./etc/wifi/opensleep-wifi +``` + +**Solution:** +- Ensure patching script completed successfully +- Check that tar append step didn't fail +- Re-patch image + +--- + +## Technical Details + +### Efficient Tar Append Method + +**Traditional approach (slow):** +```bash +# Extract entire rootfs (2+ GB) +gunzip -c rootfs.tar.gz | tar -x +# Modify files +# Repack entire rootfs +tar -czf rootfs.tar.gz . +``` + +**Our approach (fast):** +```bash +# Decompress once +gunzip -c rootfs.tar.gz > rootfs.tar + +# Stage modifications in separate directory +mkdir staging +# ... create files in staging/ ... + +# Append modifications only +tar --numeric-owner -rf rootfs.tar -C staging . + +# Recompress +gzip -c rootfs.tar > rootfs-patched.tar.gz +``` + +**Benefits:** +- 10x faster (only append, don't extract) +- Uses less disk space +- Preserves original tar structure +- `--numeric-owner` preserves UID/GID (important for rewt user) + +### User ID Management + +**rewt user:** +- UID: 1001 +- GID: 1001 +- Home: /home/rewt +- Shell: /bin/bash + +**Why important:** +Files must be owned by rewt (1001:1001) to work correctly: +```bash +# Wrong (breaks permissions): +chown root:root /home/rewt/.ssh/authorized_keys + +# Correct: +chown 1001:1001 /home/rewt/.ssh/authorized_keys +``` + +Script uses `--numeric-owner` in tar to preserve these IDs. + +### systemd Service Types + +**Type=oneshot:** +- Used for: opensleep-wifi.service, ssh-early.service +- Runs once, then exits +- Can have `RemainAfterExit=yes` to stay "active" + +**Type=simple:** +- Used for: opensleep.service +- Long-running daemon +- Process must stay in foreground + +**Type=oneshot + ConditionPathExists:** +- Used for: disable-eightsleep-services.service +- Runs once on first boot only +- Disables itself after running + +--- + +## Summary + +### What Gets Configured + +**With `-w -s` (WiFi):** +- ✅ opensleep-wifi.service initializes WiFi hardware +- ✅ wlan0 interface created on boot +- ✅ wpa_supplicant connects to configured network +- ✅ DHCP obtains IP address +- ✅ SSH accessible over WiFi + +**With `-b` (OpenSleep):** +- ✅ opensleep binary installed +- ✅ opensleep.service starts on boot +- ✅ Eight Sleep services disabled after opensleep starts +- ✅ Local MQTT control available + +**With `-w -s -b` (WiFi + OpenSleep):** +- ✅ Full local control +- ✅ WiFi works independently (survives Eight Sleep disable) +- ✅ SSH accessible +- ✅ Eight Sleep app disconnected +- ✅ Pod 3 under complete local control + +**With validation script (auto or `-v`):** +- ✅ Validation script included at `/home/rewt/validate_deployment.sh` +- ✅ Welcome banner on first SSH login shows script location +- ✅ Can verify deployment after SSH'ing in +- ✅ Checks network, services, files, and configuration +- ✅ Survives factory reset (included in rootfs.tar.gz) + +**With persistent MAC (`-m`):** +- ✅ MAC address configured via systemd .link file +- ✅ MAC stays the same across reboots and factory resets +- ✅ Useful for MAC filtering/whitelisting on routers +- ✅ Prevents network access issues after factory reset + +### Boot Time Expectations + +**First boot after patching:** +- WiFi initialization: ~5 seconds +- Network connection: ~10 seconds +- SSH available: ~20-30 seconds total + +**After factory reset:** +- rootfs extraction: ~2-3 minutes +- Then same as above +- Total: ~3-4 minutes + +### Next Steps After Patching + +1. **Write image to SD card** +2. **Insert into Pod 3** +3. **Wait 1-2 minutes for boot** +4. **Check router for Pod 3's IP address** +5. **SSH to device:** `ssh rewt@` +6. **Run validation script:** See [Post-Patch Validation](#post-patch-validation) +7. **Verify opensleep (if installed):** `systemctl status opensleep` + +--- + +## Post-Patch Validation + +After successfully patching and booting the Pod 3 (SD card version), you can run a comprehensive validation script to verify everything is working correctly. + +### Running the Validation Script + +**If validation script was included during patching:** +```bash +# SSH into Pod 3 +ssh rewt@ + +# Welcome banner appears automatically: +========================================== + OpenSleep Pod 3 - Welcome! +========================================== + +A validation script is available to verify +your system configuration. + +Run: ~/validate_deployment.sh +Or: ./validate_deployment.sh + +# Run validation (in home directory) +./validate_deployment.sh +``` + +**If you need to copy it manually:** +```bash +# Copy to Pod 3 +scp scripts/full_patch_workflow_post_ssh_validation.sh rewt@:/tmp/ + +# SSH in and run +ssh rewt@ +chmod +x /tmp/full_patch_workflow_post_ssh_validation.sh +/tmp/full_patch_workflow_post_ssh_validation.sh +``` + +**Note:** The patching script automatically includes the validation script if it's in the same directory. You can also specify a custom validation script with the `-v` option. + +**Suppressing the welcome banner:** +```bash +# Add to .bashrc permanently +echo "export VALIDATION_SHOWN=1" >> ~/.bashrc + +# Or set for current session only +export VALIDATION_SHOWN=1 +``` + +### What the Validation Script Checks + +**System Information:** +- ✅ Hostname and boot time +- ✅ **MAC address** (⚠️ changes on every factory reset!) + +**Network Connectivity:** +- ✅ WiFi interface state (up/down) +- ✅ IP address assigned to wlan0 +- ✅ Routing table configuration +- ✅ Internet connectivity (ping test to 8.8.8.8) + +**Services That SHOULD Be Running:** +- ✅ `opensleep-wifi` - Creates wlan0 interface by bypassing EEPROM WiFi check +- ✅ `wpa_supplicant@wlan0` - Manages WiFi authentication and connection +- ✅ `ssh-early` - Enables SSH access early in boot process +- ✅ `opensleep` - Main opensleep daemon (if installed with `-b`) + +**Services That Should NOT Be Running:** +- ✅ `variscite-wifi` - Should be masked (original Eight Sleep WiFi service) +- ✅ `capybara` - Should be disabled (Eight Sleep's main control daemon) + +**Eight Sleep Services Status:** +- Checks: `dac`, `frank`, `capybara`, `swupdate-progress`, `swupdate`, `defibrillator` +- All should be disabled/inactive when opensleep is installed + +**File Verification:** +- ✅ opensleep-wifi script exists at `/etc/wifi/opensleep-wifi` +- ✅ opensleep binary (if installed with `-b`) +- ✅ opensleep config.ron (if installed with `-b`) +- ✅ SSH keys (informational - password auth is also valid) + +**Service Dependencies:** +- ✅ Verifies correct boot order chain +- ✅ Confirms ssh-early depends on opensleep-wifi +- ✅ Checks variscite-wifi is properly masked to `/dev/null` + +### Expected Output + +**Successful validation will show:** +``` +======================================== +Validation Summary +======================================== +✓ wlan0 has an IP address +✓ opensleep-wifi service is active +✓ wpa_supplicant@wlan0 service is active +✓ ssh-early service is active +ℹ Using SSH password authentication +✓ variscite-wifi service is masked/inactive + +=== Eight Sleep Services Status === +✓ dac is disabled/inactive +✓ frank is disabled/inactive +✓ capybara is disabled/inactive +✓ swupdate-progress is disabled/inactive +✓ swupdate is disabled/inactive +✓ defibrillator is disabled/inactive + +✓✓✓ All critical checks passed! ✓✓✓ +Pod 3 patch deployment is successful. +======================================== +``` + +### Interpreting Results + +**Information Messages (ℹ):** +- Not errors, just informational +- Example: "Using SSH password authentication" - means you're using password auth instead of SSH keys (both are valid) + +**Success Messages (✓):** +- Green checkmarks indicate everything working correctly +- Critical services are running +- Eight Sleep services are properly disabled + +**Warning/Error Messages (⚠ or ✗):** +- Indicates something may need attention +- Review the specific service or file mentioned +- Check troubleshooting section below + +**MAC Address Warning:** +- The validation script shows a warning that MAC address changes on factory reset +- This is normal Eight Sleep behavior, not a bug +- Important if you use MAC filtering on your router/firewall +- You'll need to update your whitelist after each factory reset + +--- + +## Troubleshooting + +### Common Issues + +**Pod 3 not showing up on network after factory reset:** +1. Check if MAC address changed (run validation script) +2. Update MAC whitelist on router if you have MAC filtering enabled +3. Verify WiFi credentials are still correct +4. Check router logs for DHCP denials + +**Pod 3 not showing up on network:** +1. Verify WiFi credentials are correct +2. Check router/AP supports 2.4GHz (Pod 3 doesn't support 5GHz) +3. Run validation script after SSH'ing via serial console +4. Check `journalctl -u opensleep-wifi` for errors + +**SSH connection refused:** +1. Wait 30 seconds after boot completes +2. Verify you can ping the Pod 3's IP address +3. Check port 22 (default SSH port, not 8822) +4. Try serial console if available + +**Factory reset doesn't restore patches:** +1. Verify you pressed button during boot (not before power on) +2. Check LED flashes yellow during reset (indicates extraction) +3. Wait full 5 minutes for extraction to complete +4. If patches not restored, rootfs.tar.gz may not have been written correctly + +**opensleep service not running:** +1. Run validation script to check status +2. Check logs: `journalctl -u opensleep` +3. Verify binary exists: `ls -la /opt/opensleep/opensleep` +4. Check config exists: `ls -la /opt/opensleep/config.ron` + +**WiFi connects but no internet:** +1. Check routing table: `route -n` +2. Verify gateway is reachable: `ping ` +3. Test DNS: `ping 8.8.8.8` +4. Check wpa_supplicant: `systemctl status wpa_supplicant@wlan0` + +### Getting Help + +For issues or questions: +1. Run validation script and capture output +2. Extract logs: `./extract_pod3_logs.sh -i image.img` +3. Review `.memory-bank/` documentation +4. Check GitHub issues +5. Include validation script output when reporting issues diff --git a/scripts/full_patch_workflow.sh b/scripts/full_patch_workflow.sh new file mode 100644 index 0000000..7a2ee7a --- /dev/null +++ b/scripts/full_patch_workflow.sh @@ -0,0 +1,1070 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "======================================" +echo "Pod 3 (SD Card) Image Patching Script" +echo "======================================" +echo "" +echo "⚠️ IMPORTANT: This script is for Pod 3 with SD card only!" +echo " - Does NOT work with Pod 1, 2, 4, or 5" +echo " - Does NOT work with Pod 3 without SD card (eMMC version)" +echo "" +echo "Note: This script requires sudo privileges for:" +echo " - Mounting disk images (losetup, mount)" +echo " - Extracting and modifying system files" +echo " - Setting proper file ownership and permissions" +echo "" + +# Default values +IMG_FILE="" +PUBKEY_FILE="" +SSID="" +PSK="" +PASSWORD="" +DISABLE_SERVICES=false +REMOVE_EIGHTSLEEP_KEYS=false +OPENSLEEP_BINARY="" +OPENSLEEP_SERVICE="" +OPENSLEEP_CONFIG="" +OUTPUT_FILE="" +WORK_DIR_CUSTOM="" +VALIDATION_SCRIPT="" +MAC_ADDRESS="" + +# Parse command-line arguments +usage() { + cat <-patched.img) + -w WORK_DIR Custom working directory (must be empty, needs 16GB free space) + -s SSID WiFi network SSID + -p PSK WiFi network password/passphrase + -P PASSWORD Password for the rewt user + -m MAC_ADDRESS Set persistent MAC address for wlan0 (format: AA:BB:CC:DD:EE:FF) + Prevents MAC from changing on factory reset + -R [DISABLED] Remove Eight Sleep SSH keys (currently causes SSH connection failure) + This feature is being debugged and will be re-enabled in a future update. + -d Disable Eight Sleep services on first boot + For saftey, it waits for a wifi connection before disabling, this ensures the pairing and reset options are not interfered with. + WARNING: This prevents normal Eight Sleep app pairing. + To restore Eight Sleep functionality, you must reflash the original image. + -b BINARY Path to opensleep binary to install + -S SERVICE Path to opensleep.service file to install + -c CONFIG Path to config.ron file to install + -v VALIDATION Path to validation script to include in image (default: auto-detect in same directory) + -h Show this help message + +Example: + $0 -i sdcard.img -k ~/.ssh/id_rsa.pub -s "MyWiFi" -p "password123" -P "userpass" -d + $0 -i sdcard.img -o sdcard-patched.img -w /mnt/bigdrive/temp -k ~/.ssh/id_rsa.pub -s "MyWiFi" -p "pass" -b ./opensleep -S ./opensleep.service -c ./config.ron -d + $0 -i sdcard.img -k ~/.ssh/id_rsa.pub -s "MyWiFi" -p "pass" -v ./custom_validation.sh + $0 -i sdcard.img -k ~/.ssh/id_rsa.pub -s "MyWiFi" -p "pass" -m "02:11:22:33:44:55" + $0 -i sdcard.img -k ~/.ssh/id_rsa.pub -s "MyWiFi" -p "pass" -R -d +EOF + exit 1 +} + +while getopts "i:k:o:w:s:p:P:b:S:c:v:m:dh" opt; do + case $opt in + i) IMG_FILE="$OPTARG" ;; + k) PUBKEY_FILE="$OPTARG" ;; + o) OUTPUT_FILE="$OPTARG" ;; + w) WORK_DIR_CUSTOM="$OPTARG" ;; + s) SSID="$OPTARG" ;; + p) PSK="$OPTARG" ;; + P) PASSWORD="$OPTARG" ;; + b) OPENSLEEP_BINARY="$OPTARG" ;; + S) OPENSLEEP_SERVICE="$OPTARG" ;; + c) OPENSLEEP_CONFIG="$OPTARG" ;; + v) VALIDATION_SCRIPT="$OPTARG" ;; + m) MAC_ADDRESS="$OPTARG" ;; + R) echo "ERROR: -R flag is temporarily disabled due to SSH connection issues" + echo "This feature is being debugged and will be re-enabled in a future update" + exit 1 ;; + d) DISABLE_SERVICES=true ;; + h) usage ;; + *) usage ;; + esac +done + +# Validate required arguments +if [[ -z "$IMG_FILE" ]] || [[ -z "$PUBKEY_FILE" ]]; then + echo "" + echo "Error: Missing required arguments" + echo "" + usage +fi + +echo "[*] Validating input files..." + +# Validate files exist +if [[ ! -f "$IMG_FILE" ]]; then + echo "Error: Image file not found: $IMG_FILE" + exit 1 +fi +echo " ✓ Image file found: $IMG_FILE" + +if [[ ! -f "$PUBKEY_FILE" ]]; then + echo "Error: Public key file not found: $PUBKEY_FILE" + exit 1 +fi +echo " ✓ Public key file found: $PUBKEY_FILE" + +# Validate opensleep files if provided +if [[ ! -z "$OPENSLEEP_BINARY" ]] && [[ ! -f "$OPENSLEEP_BINARY" ]]; then + echo "Error: opensleep binary not found: $OPENSLEEP_BINARY" + exit 1 +fi + +if [[ ! -z "$OPENSLEEP_SERVICE" ]] && [[ ! -f "$OPENSLEEP_SERVICE" ]]; then + echo "Error: opensleep.service file not found: $OPENSLEEP_SERVICE" + exit 1 +fi + +if [[ ! -z "$OPENSLEEP_CONFIG" ]] && [[ ! -f "$OPENSLEEP_CONFIG" ]]; then + echo "Error: config.ron file not found: $OPENSLEEP_CONFIG" + exit 1 +fi + +if [[ ! -z "$OPENSLEEP_BINARY" ]]; then + echo " ✓ opensleep binary found: $OPENSLEEP_BINARY" + echo " ✓ opensleep.service found: $OPENSLEEP_SERVICE" + echo " ✓ config.ron found: $OPENSLEEP_CONFIG" +fi + +# Auto-detect validation script if not specified +if [[ -z "$VALIDATION_SCRIPT" ]]; then + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + AUTO_VALIDATION="$SCRIPT_DIR/full_patch_workflow_post_ssh_validation.sh" + if [[ -f "$AUTO_VALIDATION" ]]; then + VALIDATION_SCRIPT="$AUTO_VALIDATION" + echo " ✓ Auto-detected validation script: $VALIDATION_SCRIPT" + fi +elif [[ ! -f "$VALIDATION_SCRIPT" ]]; then + echo "Error: Validation script not found: $VALIDATION_SCRIPT" + exit 1 +else + echo " ✓ Validation script found: $VALIDATION_SCRIPT" +fi + +# Validate MAC address format if provided +if [[ ! -z "$MAC_ADDRESS" ]]; then + if [[ ! "$MAC_ADDRESS" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then + echo "Error: Invalid MAC address format: $MAC_ADDRESS" + echo "Expected format: AA:BB:CC:DD:EE:FF (e.g., 02:11:22:33:44:55)" + exit 1 + fi + echo " ✓ MAC address validated: $MAC_ADDRESS" +fi + +echo "" + +# Check if all three opensleep files are provided together +OPENSLEEP_COUNT=0 +[[ ! -z "$OPENSLEEP_BINARY" ]] && ((OPENSLEEP_COUNT++)) || true +[[ ! -z "$OPENSLEEP_SERVICE" ]] && ((OPENSLEEP_COUNT++)) || true +[[ ! -z "$OPENSLEEP_CONFIG" ]] && ((OPENSLEEP_COUNT++)) || true + +if [[ $OPENSLEEP_COUNT -gt 0 ]] && [[ $OPENSLEEP_COUNT -lt 3 ]]; then + echo "Error: All three opensleep files must be provided together (-b, -S, -c)" + exit 1 +fi + +echo "[*] Setting up output path..." + +# Set default output file if not provided +if [[ -z "$OUTPUT_FILE" ]]; then + # Get the absolute path of the input image + IMG_ABSOLUTE=$(realpath "$IMG_FILE") + IMG_DIR=$(dirname "$IMG_ABSOLUTE") + IMG_NAME=$(basename "$IMG_ABSOLUTE") + + # Remove extension and add -patched + IMG_BASE="${IMG_NAME%.*}" + IMG_EXT="${IMG_NAME##*.}" + + # Create output path in same directory as input + OUTPUT_FILE="${IMG_DIR}/${IMG_BASE}-patched.${IMG_EXT}" +fi + +echo " ✓ Output will be: $OUTPUT_FILE" + +# Check if output file already exists +if [[ -f "$OUTPUT_FILE" ]]; then + echo "Error: Output file already exists: $OUTPUT_FILE" + echo "Please remove it or specify a different output path with -o" + exit 1 +fi + +echo "" +echo "[*] Creating working directory..." + +# Handle custom or temporary working directory +if [[ ! -z "$WORK_DIR_CUSTOM" ]]; then + # Validate custom working directory + if [[ ! -d "$WORK_DIR_CUSTOM" ]]; then + echo "Error: Custom working directory does not exist: $WORK_DIR_CUSTOM" + exit 1 + fi + + # Check if directory is empty + if [[ -n "$(ls -A "$WORK_DIR_CUSTOM" 2>/dev/null)" ]]; then + echo "Error: Custom working directory is not empty: $WORK_DIR_CUSTOM" + echo "Please provide an empty directory" + exit 1 + fi + + WORK_DIR="$WORK_DIR_CUSTOM" + echo " ✓ Using custom working directory: $WORK_DIR" +else + WORK_DIR=$(mktemp -d -t work_$(basename "$IMG_FILE").XXXX) + echo " ✓ Created temporary working directory: $WORK_DIR" +fi + +# Check available space (need at least 16GB) +REQUIRED_SPACE_GB=16 +REQUIRED_SPACE_KB=$((REQUIRED_SPACE_GB * 1024 * 1024)) +AVAILABLE_SPACE_KB=$(df -k "$WORK_DIR" | tail -1 | awk '{print $4}') +AVAILABLE_SPACE_GB=$((AVAILABLE_SPACE_KB / 1024 / 1024)) + +echo "[*] Checking available disk space..." +echo " Available: ${AVAILABLE_SPACE_GB}GB" +echo " Required: ${REQUIRED_SPACE_GB}GB" + +if [[ $AVAILABLE_SPACE_KB -lt $REQUIRED_SPACE_KB ]]; then + echo "Error: Insufficient disk space in working directory" + echo " Location: $WORK_DIR" + echo " Available: ${AVAILABLE_SPACE_GB}GB" + echo " Required: ${REQUIRED_SPACE_GB}GB" + echo "Please free up space or use -w to specify a different working directory" + exit 1 +fi + +echo " ✓ Sufficient space available" + +WORKING_IMG="$WORK_DIR/working_image.img" +EXTRACT_DIR="$WORK_DIR/rootfs_extract" +MOUNT_DIR="$WORK_DIR/mount" + +echo "[*] Original image: $IMG_FILE" +echo "[*] Output will be saved to: $OUTPUT_FILE" + +# --- Copy the original image --- +echo "[*] Copying original image to working directory..." +cp "$IMG_FILE" "$WORKING_IMG" +echo "[+] Image copied successfully" + +# --- Cleanup function --- +cleanup() { + local exit_code=$? + echo "[*] Cleaning up..." + + # Unmount if mounted + if mountpoint -q "$MOUNT_DIR" 2>/dev/null; then + echo "[*] Unmounting $MOUNT_DIR..." + sudo umount "$MOUNT_DIR" 2>/dev/null || true + fi + + # Detach loop device if attached + if [[ ! -z "$LOOP" ]] && losetup "$LOOP" 2>/dev/null; then + echo "[*] Detaching loop device $LOOP..." + sudo losetup -d "$LOOP" 2>/dev/null || true + fi + + # Remove working directory + if [[ -d "$WORK_DIR" ]]; then + echo "[*] Removing work directory..." + rm -rf "$WORK_DIR" 2>/dev/null || true + fi + + if [[ $exit_code -ne 0 ]]; then + echo "[-] Script failed with exit code $exit_code" + fi + + exit $exit_code +} + +# Set trap to call cleanup on exit, interrupt, or termination +trap cleanup EXIT INT TERM + +# --- Check if extraction directory already exists with contents --- +if [[ -d "$EXTRACT_DIR" ]] && [[ -n "$(ls -A "$EXTRACT_DIR" 2>/dev/null)" ]]; then + echo "[-] Error: Extraction directory already exists with contents: $EXTRACT_DIR" + echo "[-] This may indicate a previous run was interrupted or there's a conflict." + echo "[-] Please remove the directory manually if you're sure it's safe to do so:" + echo "[-] rm -rf \"$EXTRACT_DIR\"" + exit 1 +fi + +# --- Setup loop device --- +LOOP=$(sudo losetup -Pf --show "$WORKING_IMG") +echo "[*] Loop device: $LOOP" + +# --- Mount root partition --- +ROOT_PART="${LOOP}p1" +mkdir -p "$MOUNT_DIR" +sudo mount "$ROOT_PART" "$MOUNT_DIR" +echo "[*] Mounted root partition at $ROOT_PART" + +# --- Extract rootfs.tar.gz --- +ROOTFS_TAR_GZ=$(sudo find "$MOUNT_DIR" -name "rootfs.tar.gz" 2>/dev/null | head -n1) +if [[ -z "$ROOTFS_TAR_GZ" ]]; then + echo "[-] rootfs.tar.gz not found!" + exit 1 +fi + +# Ungzip the rootfs.tar.gz +echo "[*] Extracting $ROOTFS_TAR_GZ..." +ROOTFS_TAR="$WORK_DIR/rootfs.tar" +sudo gunzip -c "$ROOTFS_TAR_GZ" > "$ROOTFS_TAR" + +# Extract only what we need to modify +mkdir -p "$EXTRACT_DIR" +sudo tar -xf "$ROOTFS_TAR" -C "$EXTRACT_DIR" ./etc/ssh/authorized_keys ./home/rewt ./etc/shadow 2>/dev/null || true + +# --- Detect rewt UID/GID --- +REWT_HOME="$EXTRACT_DIR/home/rewt" +if [[ -d "$REWT_HOME" ]]; then + REWT_UID=$(sudo stat -c "%u" "$REWT_HOME") + REWT_GID=$(sudo stat -c "%g" "$REWT_HOME") + echo "[+] rewt detected, UID=$REWT_UID GID=$REWT_GID" +else + # Default to common embedded Linux UID/GID + REWT_UID=1001 + REWT_GID=1001 + echo "[+] Using default UID=$REWT_UID GID=$REWT_GID" +fi + +# Create a staging directory for files to append +STAGING_DIR="$WORK_DIR/staging" +mkdir -p "$STAGING_DIR" + +# --- Append new public key --- +echo "[*] Preparing SSH authorized_keys..." +AK_FILE="$STAGING_DIR/etc/ssh/authorized_keys" +sudo mkdir -p "$(dirname "$AK_FILE")" + +# Copy existing keys if they exist, otherwise create new +if sudo tar -xf "$ROOTFS_TAR" -C "$STAGING_DIR" ./etc/ssh/authorized_keys 2>/dev/null; then + echo " ✓ Existing authorized_keys found" +else + sudo mkdir -p "$STAGING_DIR/etc/ssh" + sudo touch "$AK_FILE" +fi + +# Remove Eight Sleep keys if requested +if [[ "$REMOVE_EIGHTSLEEP_KEYS" == true ]]; then + echo "[*] Removing Eight Sleep SSH keys..." + echo "[!] WARNING: This will prevent Eight Sleep mobile app pairing!" + echo "[!] Eight Sleep remote access will be disabled" + + # Create new authorized_keys with only the user's key + # Copy the user's public key + sudo cp "$PUBKEY_FILE" "$AK_FILE" + + # Ensure file ends with newline (SSH keys should end with newline) + sudo bash -c "echo '' >> '$AK_FILE'" + + echo " ✓ Eight Sleep keys removed, only your key remains" +else + # Append new public key to existing keys + sudo bash -c "cat '$PUBKEY_FILE' >> '$AK_FILE'" + echo " ✓ Your key appended to existing keys" +fi + +sudo chmod 600 "$AK_FILE" +sudo chown "$REWT_UID:$REWT_GID" "$AK_FILE" + +# --- Set password if provided --- +if [[ ! -z "$PASSWORD" ]]; then + echo "[*] Setting password for rewt..." + echo "[!] WARNING: Password modification is experimental and may cause SSH issues" + echo "[!] If SSH stops working, re-patch without -P flag" + + # Extract shadow and sshd_config files + if ! sudo tar -xf "$ROOTFS_TAR" -C "$STAGING_DIR" ./etc/shadow ./etc/ssh/sshd_config 2>/dev/null; then + echo "[-] WARNING: Failed to extract /etc/shadow or /etc/ssh/sshd_config from tar" + echo "[-] Skipping password modification to avoid breaking SSH" + echo "[-] You can still use SSH key authentication" + else + SHADOW_FILE="$STAGING_DIR/etc/shadow" + SSHD_CONFIG="$STAGING_DIR/etc/ssh/sshd_config" + + PASSWORD_SET=false + + if [[ -f "$SHADOW_FILE" ]]; then + # Get original permissions and ownership before modification + SHADOW_PERMS=$(sudo stat -c "%a" "$SHADOW_FILE") + SHADOW_OWNER=$(sudo stat -c "%u:%g" "$SHADOW_FILE") + + echo " [*] Original shadow file: perms=$SHADOW_PERMS owner=$SHADOW_OWNER" + + # Validate file has content (check if rewt entry exists) + if ! sudo grep -q "^rewt:" "$SHADOW_FILE" 2>/dev/null; then + echo "[-] WARNING: /etc/shadow doesn't contain rewt entry, skipping password modification" + echo "[-] File may be empty or corrupted" + else + HASH=$(openssl passwd -6 "$PASSWORD") + sudo sed -i "s|^rewt:[^:]*:|rewt:$HASH:|" "$SHADOW_FILE" + + # Restore original permissions and ownership + sudo chmod "$SHADOW_PERMS" "$SHADOW_FILE" + sudo chown "$SHADOW_OWNER" "$SHADOW_FILE" + + # Also write to mounted partition for immediate use + sudo mkdir -p "$MOUNT_DIR/etc" + sudo cp "$SHADOW_FILE" "$MOUNT_DIR/etc/shadow" + sudo chmod "$SHADOW_PERMS" "$MOUNT_DIR/etc/shadow" + sudo chown "$SHADOW_OWNER" "$MOUNT_DIR/etc/shadow" + + PASSWORD_SET=true + echo " ✓ Password set, permissions preserved" + fi + else + echo "[-] WARNING: /etc/shadow not found, skipping password modification" + fi + + if [[ -f "$SSHD_CONFIG" ]] && [[ "$PASSWORD_SET" = true ]]; then + # Get original permissions and ownership before modification + SSHD_PERMS=$(sudo stat -c "%a" "$SSHD_CONFIG") + SSHD_OWNER=$(sudo stat -c "%u:%g" "$SSHD_CONFIG") + + echo " [*] Original sshd_config: perms=$SSHD_PERMS owner=$SSHD_OWNER" + + # Enable password authentication (handle both commented and uncommented lines) + sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' "$SSHD_CONFIG" + + # Restore original permissions and ownership + sudo chmod "$SSHD_PERMS" "$SSHD_CONFIG" + sudo chown "$SSHD_OWNER" "$SSHD_CONFIG" + + # Also write to mounted partition for immediate use + sudo mkdir -p "$MOUNT_DIR/etc/ssh" + sudo cp "$SSHD_CONFIG" "$MOUNT_DIR/etc/ssh/sshd_config" + sudo chmod "$SSHD_PERMS" "$MOUNT_DIR/etc/ssh/sshd_config" + sudo chown "$SSHD_OWNER" "$MOUNT_DIR/etc/ssh/sshd_config" + + echo " ✓ Password authentication enabled, permissions preserved" + elif [[ "$PASSWORD_SET" = false ]]; then + echo "[-] Skipping sshd_config modification (password not set)" + else + echo "[-] WARNING: /etc/ssh/sshd_config not found, skipping modification" + fi + + if [[ "$PASSWORD_SET" = false ]]; then + echo "" + echo "[!] Password was NOT set due to errors" + echo "[!] SSH key authentication will still work" + fi + fi +fi + +# --- Create wpa_supplicant WiFi configuration --- +if [[ ! -z "$SSID" && ! -z "$PSK" ]]; then + echo "[*] Creating wpa_supplicant WiFi configuration..." + + # Create wpa_supplicant configuration directory + WPA_DIR="$STAGING_DIR/etc/wpa_supplicant" + sudo mkdir -p "$WPA_DIR" + + # Create wpa_supplicant configuration for wlan0 + WPA_CONF="$WPA_DIR/wpa_supplicant-wlan0.conf" + sudo bash -c "cat > '$WPA_CONF'" < '$NETWORKD_CONF'" < '$NM_CONF'" < '$OPENSLEEP_WIFI_SERVICE'" <<'EOF' +[Unit] +Description=OpenSleep WiFi Initialization Service +Before=network.target +After=sysinit.target +ConditionPathExists=/etc/wifi/opensleep-wifi + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/etc/wifi/opensleep-wifi start +ExecStop=/etc/wifi/opensleep-wifi stop + +[Install] +WantedBy=network.target +EOF + sudo chmod 644 "$OPENSLEEP_WIFI_SERVICE" + + # Enable opensleep-wifi.service (create symlink in network.target.wants) + sudo mkdir -p "$STAGING_DIR/etc/systemd/system/network.target.wants" + sudo ln -sf /etc/systemd/system/opensleep-wifi.service "$STAGING_DIR/etc/systemd/system/network.target.wants/opensleep-wifi.service" + + # Mask variscite-wifi.service to prevent conflicts + sudo ln -sf /dev/null "$STAGING_DIR/etc/systemd/system/variscite-wifi.service" + + # Also write to mounted partition for immediate use + sudo mkdir -p "$MOUNT_DIR/etc/wifi" + sudo cp "$STAGING_DIR/etc/wifi/opensleep-wifi" "$MOUNT_DIR/etc/wifi/" + sudo cp "$OPENSLEEP_WIFI_SERVICE" "$MOUNT_DIR/etc/systemd/system/" + sudo mkdir -p "$MOUNT_DIR/etc/systemd/system/network.target.wants" + sudo ln -sf /etc/systemd/system/opensleep-wifi.service "$MOUNT_DIR/etc/systemd/system/network.target.wants/opensleep-wifi.service" + sudo ln -sf /dev/null "$MOUNT_DIR/etc/systemd/system/variscite-wifi.service" + + echo "[+] opensleep-wifi service created and enabled" + echo " ✓ WiFi hardware will be initialized on boot" + echo " ✓ variscite-wifi.service masked (EEPROM check bypassed)" + else + echo "[!] WARNING: /etc/wifi/variscite-wifi not found in image" + echo " WiFi may not work without manual hardware initialization" + fi + + # --- Configure persistent MAC address (requires opensleep-wifi service) --- + if [[ ! -z "$MAC_ADDRESS" ]]; then + echo "[*] Configuring persistent MAC address..." + + # brcmfmac WiFi driver generates random MAC at firmware load time + # .link files don't work reliably, so we use a service to set MAC after interface is up + + SYSTEMD_DIR="$STAGING_DIR/etc/systemd/system" + sudo mkdir -p "$SYSTEMD_DIR" + + # Create a service to set MAC address before networking starts + MAC_SERVICE="$SYSTEMD_DIR/opensleep-mac.service" + sudo bash -c "cat > '$MAC_SERVICE'" < '$SSH_EARLY_SERVICE'" < '$SSH_EARLY_SERVICE'" < '$WPA_OVERRIDE_DIR/override.conf'" <<'EOF' +[Unit] +After=opensleep-wifi.service opensleep-mac.service +Requires=opensleep-wifi.service +EOF + echo " ✓ wpa_supplicant will wait for opensleep-wifi and opensleep-mac" + else + sudo bash -c "cat > '$WPA_OVERRIDE_DIR/override.conf'" <<'EOF' +[Unit] +After=opensleep-wifi.service +Requires=opensleep-wifi.service +EOF + echo " ✓ wpa_supplicant will wait for opensleep-wifi" + fi + + sudo chmod 644 "$WPA_OVERRIDE_DIR/override.conf" + + # Enable wpa_supplicant@wlan0.service + sudo ln -sf /lib/systemd/system/wpa_supplicant@.service "$SYSTEMD_WANTS/wpa_supplicant@wlan0.service" + + # Also enable on mounted partition + sudo mkdir -p "$MOUNT_DIR/etc/systemd/system/wpa_supplicant@wlan0.service.d" + sudo cp "$WPA_OVERRIDE_DIR/override.conf" "$MOUNT_DIR/etc/systemd/system/wpa_supplicant@wlan0.service.d/" + sudo ln -sf /lib/systemd/system/wpa_supplicant@.service "$MOUNT_DIR/etc/systemd/system/multi-user.target.wants/wpa_supplicant@wlan0.service" + + echo "[+] wpa_supplicant@wlan0.service enabled with opensleep-wifi dependency" +fi + +# --- Create service to disable Eight Sleep services --- +# Auto-enable if opensleep is being installed +if [[ "$DISABLE_SERVICES" = true ]] || [[ ! -z "$OPENSLEEP_BINARY" ]]; then + echo "" + echo "⚠️ WARNING: Disabling Eight Sleep services ⚠️" + echo "This will prevent the Eight Sleep app from pairing with the Pod." + + # Warn if opensleep is being installed without WiFi configuration + if [[ -z "$SSID" ]]; then + echo "" + echo "⚠️⚠️⚠️ CRITICAL WARNING ⚠️⚠️⚠️" + echo "Installing opensleep without WiFi credentials!" + echo " - Pod 3 has no Ethernet port" + echo " - Without WiFi, the device will have NO network connectivity" + echo " - The device will be UNREACHABLE without serial console access" + echo "" + echo "Recommendation: Provide WiFi credentials with -s and -p flags" + echo "" + read -p "Continue anyway? (yes/no): " -r + if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then + echo "Aborting..." + cleanup + exit 1 + fi + fi + + echo "This will prevent the Eight Sleep app from pairing with the Pod." + echo "To restore normal Eight Sleep functionality, you must reflash the original image." + echo "" + + echo "[*] Installing disable-eightsleep-services.service..." + + DISABLE_SERVICE="$STAGING_DIR/etc/systemd/system/disable-eightsleep-services.service" + sudo mkdir -p "$(dirname "$DISABLE_SERVICE")" + + if [[ ! -z "$OPENSLEEP_BINARY" ]]; then + # If opensleep is installed, wait for it to be running before disabling Eight Sleep services + sudo bash -c "cat > '$DISABLE_SERVICE'" < '$DISABLE_SERVICE'" < /dev/null <<'BASHRC_EOF' +# OpenSleep Pod 3 - Patched System + +# Show validation prompt on first interactive login +if [ -f ~/validate_deployment.sh ] && [ -z "$VALIDATION_SHOWN" ]; then + export VALIDATION_SHOWN=1 + echo "" + echo "==========================================" + echo " OpenSleep Pod 3 - Welcome!" + echo "==========================================" + echo "" + echo "A validation script is available to verify" + echo "your system configuration." + echo "" + echo "Run: ~/validate_deployment.sh" + echo "Or: ./validate_deployment.sh" + echo "" + echo "To run opensleep in debug mode:" + echo " sudo systemctl stop opensleep && cd /opt/opensleep && sudo RUST_LOG=debug,rumqttc=info ./opensleep" + echo "" + echo "To skip this message in future sessions:" + echo " export VALIDATION_SHOWN=1" + echo "" +fi +BASHRC_EOF + + sudo chmod 644 "$STAGING_DIR/home/rewt/.bashrc" + sudo chown "$REWT_UID:$REWT_GID" "$STAGING_DIR/home/rewt/.bashrc" + + # Copy .bashrc to mounted partition + sudo cp "$STAGING_DIR/home/rewt/.bashrc" "$MOUNT_DIR/home/rewt/.bashrc" + + echo " ✓ Validation script added to /home/rewt/validate_deployment.sh" + echo " ✓ Welcome banner configured (shows script location on first login)" +fi + +# --- Append modified files to rootfs.tar --- +echo "[*] Appending modified files to rootfs.tar..." + +# List of files that might already exist in the tar and need to be replaced +# (tar --delete doesn't work on compressed archives, so we work with uncompressed tar) +FILES_TO_REPLACE=( + "./etc/ssh/authorized_keys" +) + +# Only delete shadow and sshd_config if we actually modified them (password was provided) +if [[ ! -z "$PASSWORD" ]]; then + FILES_TO_REPLACE+=("./etc/shadow") + FILES_TO_REPLACE+=("./etc/ssh/sshd_config") +fi + +# Delete old versions of files that we're replacing (if they exist) +for file in "${FILES_TO_REPLACE[@]}"; do + if sudo tar -tf "$ROOTFS_TAR" "$file" >/dev/null 2>&1; then + echo "[*] Removing old $file from tar before updating..." + sudo tar --delete -f "$ROOTFS_TAR" "$file" 2>/dev/null || true + fi +done + +# Use --numeric-owner to preserve UID/GID +sudo tar --numeric-owner -rf "$ROOTFS_TAR" -C "$STAGING_DIR" . + +# --- Recompress rootfs.tar.gz --- +echo "[*] Recompressing rootfs.tar.gz..." +ROOTFS_PATCHED="$WORK_DIR/rootfs-patched.tar.gz" +sudo gzip -c "$ROOTFS_TAR" > "$ROOTFS_PATCHED" + +# --- Update rootfs.tar.gz in mounted image --- +echo "[*] Updating rootfs.tar.gz in image..." +sudo cp "$ROOTFS_PATCHED" "$ROOTFS_TAR_GZ" + +# --- Verify files --- +echo "" +echo "[*] Verifying patched files..." +if sudo test -f "$STAGING_DIR/etc/ssh/authorized_keys"; then + echo " ✓ SSH authorized_keys file created" +else + echo " ✗ Warning: Could not verify authorized_keys" +fi + +if [[ ! -z "$PASSWORD" ]] && sudo test -f "$STAGING_DIR/etc/ssh/sshd_config"; then + echo " ✓ SSH configuration updated" +fi + +if [[ ! -z "$SSID" ]] && sudo test -f "$STAGING_DIR/etc/wpa_supplicant/wpa_supplicant-wlan0.conf"; then + echo " ✓ WiFi connection configured: $SSID" + if sudo test -f "$STAGING_DIR/etc/wifi/opensleep-wifi"; then + echo " ✓ opensleep-wifi service created (WiFi hardware initialization)" + fi +fi + +if [[ ! -z "$MAC_ADDRESS" ]]; then + if sudo test -f "$STAGING_DIR/etc/systemd/system/opensleep-mac.service"; then + echo " ✓ Persistent MAC address configured: $MAC_ADDRESS" + fi +fi + +if [[ ! -z "$OPENSLEEP_BINARY" ]]; then + if sudo test -f "$STAGING_DIR/opt/opensleep/opensleep"; then + echo " ✓ opensleep binary installed" + fi + if sudo test -f "$STAGING_DIR/opt/opensleep/config.ron"; then + echo " ✓ opensleep config.ron installed" + fi + if sudo test -f "$STAGING_DIR/lib/systemd/system/opensleep.service"; then + echo " ✓ opensleep.service installed" + fi +fi + +if [[ ! -z "$VALIDATION_SCRIPT" ]]; then + if sudo test -f "$STAGING_DIR/home/rewt/validate_deployment.sh"; then + echo " ✓ Validation script included at /home/rewt/validate_deployment.sh" + fi +fi + +ROOTFS_SIZE=$(sudo stat -c%s "$ROOTFS_TAR_GZ") +ROOTFS_SIZE_MB=$((ROOTFS_SIZE / 1024 / 1024)) +echo " ✓ Repacked rootfs.tar.gz: ${ROOTFS_SIZE_MB}MB" + +echo "" +# --- Move patched image to output location --- +echo "[*] Moving patched image to output location..." +sudo mv "$WORKING_IMG" "$OUTPUT_FILE" +sudo chown $(id -u):$(id -g) "$OUTPUT_FILE" +echo "[+] Patched image saved to: $OUTPUT_FILE" +echo "" +echo "======================================" +echo "[+] Patch workflow complete!" +echo "======================================" + +if [[ ! -z "$SSID" ]]; then + echo "" + echo "WiFi Configuration:" + echo " - Network: $SSID" + echo " - opensleep-wifi service will initialize WiFi hardware" + echo " - wpa_supplicant will connect automatically on boot" + echo " - variscite-wifi.service masked (EEPROM check bypassed)" +fi + +if [[ ! -z "$MAC_ADDRESS" ]]; then + echo "" + echo "MAC Address Configuration:" + echo " - Persistent MAC: $MAC_ADDRESS" + echo " - Configured via systemd .link file" + echo " - Will NOT change on factory reset" + echo " - Survives reboots and system updates" +fi + +if [[ ! -z "$OPENSLEEP_BINARY" ]]; then + echo "" + echo "OpenSleep Configuration:" + echo " - opensleep binary installed to /opt/opensleep/" + echo " - opensleep.service will start on boot" + echo " - Eight Sleep services will be disabled after opensleep starts" + if [[ ! -z "$SSID" ]]; then + echo " - Device will remain reachable via WiFi after Eight Sleep disabled" + fi +fi + +if [[ ! -z "$VALIDATION_SCRIPT" ]]; then + echo "" + echo "Validation Script:" + echo " - Script included at: /home/rewt/validate_deployment.sh" + echo " - Welcome banner will show script location on first SSH login" + echo " - Run with: ~/validate_deployment.sh or ./validate_deployment.sh" + echo " - Validates network, services, and configuration" +fi + +echo "" +echo "[*] Flash patched SD card image and perform factory reset to apply changes." +echo "[*] Original image preserved at: $IMG_FILE" diff --git a/scripts/full_patch_workflow_post_ssh_validation.sh b/scripts/full_patch_workflow_post_ssh_validation.sh new file mode 100644 index 0000000..c81edf5 --- /dev/null +++ b/scripts/full_patch_workflow_post_ssh_validation.sh @@ -0,0 +1,319 @@ +#!/bin/bash + +# Pod 3 (SD Card) Post-Patch Validation Script +# Run this on the Pod 3 after patching to validate the setup +# Usage: ./full_patch_workflow_post_ssh_validation.sh +# +# ⚠️ For Pod 3 with SD card only! + +set -e + +echo "========================================" +echo "Pod 3 (SD Card) Patch Validation" +echo "========================================" +echo "" + +# === System Information === +echo "=== System Information ===" +echo "Hostname: $(hostname 2>/dev/null || echo 'unknown')" +echo "Boot time: $(uptime -s 2>/dev/null || cat /proc/uptime | awk '{print int($1)}' | xargs -I {} echo '{} seconds ago')" +echo "" + +# === MAC Address (Important: Changes on factory reset!) === +echo "=== MAC Address ===" +WLAN_MAC=$(cat /sys/class/net/wlan0/address 2>/dev/null || echo "unknown") +echo "wlan0 MAC: $WLAN_MAC" +echo "" +echo "⚠️ IMPORTANT: MAC address changes after factory reset!" +echo " If you have MAC filtering on your router/firewall," +echo " you'll need to update the whitelist after each reset." +echo "" + +# === WiFi and Network Status === +echo "=== WiFi Status ===" +# Try multiple commands to get WiFi status +if command -v iwconfig >/dev/null 2>&1; then + iwconfig wlan0 2>/dev/null +elif command -v iw >/dev/null 2>&1; then + iw dev wlan0 info 2>/dev/null +else + echo "Interface state: $(cat /sys/class/net/wlan0/operstate 2>/dev/null || echo 'unknown')" +fi +echo "" + +echo "=== IP Address ===" +# Try ip command first, fall back to ifconfig or cat /proc/net +if command -v ip >/dev/null 2>&1; then + ip addr show wlan0 +elif command -v ifconfig >/dev/null 2>&1; then + ifconfig wlan0 +else + echo "Checking via /sys filesystem..." + if [ -d /sys/class/net/wlan0 ]; then + echo "wlan0 exists" + echo "MAC Address: $(cat /sys/class/net/wlan0/address 2>/dev/null)" + echo "State: $(cat /sys/class/net/wlan0/operstate 2>/dev/null)" + # Try to get IP from busybox or other tools + if command -v busybox >/dev/null 2>&1; then + busybox ifconfig wlan0 + fi + else + echo "wlan0 interface not found" + fi +fi +echo "" + +echo "=== Routing Table ===" +if command -v ip >/dev/null 2>&1; then + ip route +elif command -v route >/dev/null 2>&1; then + route -n +elif command -v netstat >/dev/null 2>&1; then + netstat -rn +else + echo "No routing tools available" +fi +echo "" + +echo "=== Internet Connectivity ===" +ping -c 3 8.8.8.8 2>/dev/null || echo "No internet connectivity or ping not available" +echo "" + +# === Service Status === +echo "==========================================" +echo "Services That SHOULD Be Running" +echo "==========================================" +echo "" + +echo "=== opensleep-wifi Service ===" +echo "Purpose: Creates wlan0 interface by bypassing EEPROM WiFi check" +echo "Status:" +systemctl status opensleep-wifi --no-pager || echo "opensleep-wifi service not found" +echo "" + +echo "=== wpa_supplicant Service ===" +echo "Purpose: Manages WiFi authentication and connection" +echo "Status:" +systemctl status wpa_supplicant@wlan0 --no-pager || echo "wpa_supplicant service not running" +echo "" + +echo "=== ssh-early Service ===" +echo "Purpose: Enables SSH access early in boot process" +echo "Status:" +systemctl status ssh-early --no-pager || echo "ssh-early service not found" +echo "" + +echo "=== opensleep Service ===" +echo "Purpose: Main opensleep daemon for Pod control and MQTT communication" +echo "Status:" +systemctl status opensleep --no-pager || echo "opensleep service not found" +echo "" + +echo "==========================================" +echo "Services That Should NOT Be Running" +echo "==========================================" +echo "" + +echo "=== variscite-wifi Service ===" +echo "Purpose: Original Eight Sleep WiFi service (replaced by opensleep-wifi)" +echo "Expected: masked (symlinked to /dev/null)" +echo "Status:" +systemctl status variscite-wifi --no-pager || echo "variscite-wifi masked or not found" +echo "" + +echo "=== capybara Service ===" +echo "Purpose: Eight Sleep's main control daemon (conflicts with opensleep)" +echo "Expected: disabled/inactive" +echo "Status:" +systemctl status capybara --no-pager || echo "capybara service status" +echo "" + +# === Verify Files === +echo "=== Check opensleep-wifi script exists ===" +if [ -f /etc/wifi/opensleep-wifi ]; then + ls -la /etc/wifi/opensleep-wifi + echo "✓ opensleep-wifi script found" +else + echo "✗ opensleep-wifi script NOT found" +fi +echo "" + +echo "=== Check SSH key ===" +CURRENT_USER=$(whoami) +echo "Current user: $CURRENT_USER" +SSH_KEY_FOUND=false +for key_path in /home/$CURRENT_USER/.ssh/authorized_keys /home/root/.ssh/authorized_keys /root/.ssh/authorized_keys ~/.ssh/authorized_keys; do + if [ -f "$key_path" ]; then + ls -la "$key_path" + echo "✓ SSH authorized_keys found at $key_path" + echo "Key fingerprint:" + ssh-keygen -lf "$key_path" 2>/dev/null || echo "Could not read key fingerprint" + SSH_KEY_FOUND=true + break + fi +done +if [ "$SSH_KEY_FOUND" = false ]; then + echo "ℹ SSH authorized_keys NOT found (using password authentication)" +fi +echo "" + +echo "=== Check opensleep binary ===" +# Check multiple possible locations +OPENSLEEP_BIN="" +for bin_path in /usr/local/bin/opensleep /opt/opensleep/opensleep /usr/bin/opensleep; do + if [ -f "$bin_path" ]; then + OPENSLEEP_BIN="$bin_path" + break + fi +done + +if [ -n "$OPENSLEEP_BIN" ]; then + ls -la "$OPENSLEEP_BIN" + echo "✓ opensleep binary found at $OPENSLEEP_BIN" + "$OPENSLEEP_BIN" --version 2>/dev/null || echo "(version info not available)" +else + echo "✗ opensleep binary NOT found (may not have been installed with -b option)" +fi +echo "" + +echo "=== Check opensleep config ===" +# Check multiple possible locations +OPENSLEEP_CONFIG="" +for config_path in /etc/opensleep/config.ron /opt/opensleep/config.ron; do + if [ -f "$config_path" ]; then + OPENSLEEP_CONFIG="$config_path" + break + fi +done + +if [ -n "$OPENSLEEP_CONFIG" ]; then + ls -la "$OPENSLEEP_CONFIG" + echo "✓ opensleep config found at $OPENSLEEP_CONFIG" +else + echo "✗ opensleep config NOT found" +fi +echo "" + +echo "=== opensleep service logs (last 30 lines) ===" +if systemctl is-active opensleep >/dev/null 2>&1; then + # Use journalctl if available, fall back to dmesg + if command -v journalctl >/dev/null 2>&1; then + # Suppress the ACL/permission warnings + journalctl -u opensleep --no-pager -n 30 2>&1 | grep -v "Warning: some journal files were not opened" | grep -v "Failed to search journal ACL" || echo "(no recent logs or insufficient permissions)" + else + echo "(journalctl not available, checking dmesg...)" + dmesg | grep -i opensleep | tail -30 || echo "(no opensleep entries in dmesg)" + fi +else + echo "opensleep service is not active, no logs to display" +fi +echo "" + +# === Boot Order Verification === +echo "=== Service Dependencies (opensleep-wifi) ===" +systemctl list-dependencies opensleep-wifi --no-pager | head -20 +echo "" + +echo "=== Service Dependencies (ssh-early) ===" +systemctl list-dependencies ssh-early --no-pager | head -20 +echo "" + +# === Masked Services === +echo "=== Verify variscite-wifi is masked ===" +systemctl is-enabled variscite-wifi 2>/dev/null || echo "variscite-wifi status check" +ls -la /etc/systemd/system/variscite-wifi.service 2>/dev/null || echo "variscite-wifi service file not found in /etc/systemd/system" +echo "" + +# === Summary === +echo "========================================" +echo "Validation Summary" +echo "========================================" + +# Check critical items +ERRORS=0 + +# Check for IP address using available tools +HAS_IP=false +if command -v ip >/dev/null 2>&1; then + if ip addr show wlan0 2>/dev/null | grep -q "inet "; then + HAS_IP=true + fi +elif command -v ifconfig >/dev/null 2>&1; then + if ifconfig wlan0 2>/dev/null | grep -q "inet "; then + HAS_IP=true + fi +fi + +if [ "$HAS_IP" = false ]; then + echo "✗ wlan0 does not have an IP address" + ((ERRORS++)) +else + echo "✓ wlan0 has an IP address" +fi + +if ! systemctl is-active opensleep-wifi >/dev/null 2>&1; then + echo "✗ opensleep-wifi service is not active" + ((ERRORS++)) +else + echo "✓ opensleep-wifi service is active" +fi + +if ! systemctl is-active wpa_supplicant@wlan0 >/dev/null 2>&1; then + echo "✗ wpa_supplicant@wlan0 service is not active" + ((ERRORS++)) +else + echo "✓ wpa_supplicant@wlan0 service is active" +fi + +if ! systemctl is-active ssh-early >/dev/null 2>&1; then + echo "✗ ssh-early service is not active" + ((ERRORS++)) +else + echo "✓ ssh-early service is active" +fi + +# SSH key check is informational only (password auth is valid too) +SSH_KEY_EXISTS=false +for key_path in /home/root/.ssh/authorized_keys /root/.ssh/authorized_keys ~/.ssh/authorized_keys; do + if [ -f "$key_path" ]; then + SSH_KEY_EXISTS=true + break + fi +done +if [ "$SSH_KEY_EXISTS" = true ]; then + echo "✓ SSH key-based authentication configured" +else + echo "ℹ Using SSH password authentication" +fi + +if systemctl is-active variscite-wifi >/dev/null 2>&1; then + echo "⚠ variscite-wifi service is still active (should be masked)" + ((ERRORS++)) +else + echo "✓ variscite-wifi service is masked/inactive" +fi + +echo "" +echo "=== Eight Sleep Services Status ===" +EIGHT_SLEEP_SERVICES="dac frank capybara swupdate-progress swupdate defibrillator" +for service in $EIGHT_SLEEP_SERVICES; do + if systemctl is-active "$service" >/dev/null 2>&1; then + echo "⚠ $service is still active (should be disabled)" + elif systemctl is-enabled "$service" >/dev/null 2>&1; then + echo "⚠ $service is enabled but not running" + else + echo "✓ $service is disabled/inactive" + fi +done + +echo "" +if [ $ERRORS -eq 0 ]; then + echo "✓✓✓ All critical checks passed! ✓✓✓" + echo "Pod 3 patch deployment is successful." +else + echo "✗✗✗ Found $ERRORS issue(s) ✗✗✗" + echo "Review the output above for details." +fi + +echo "" +echo "========================================"