Skip to content

Conversation

@X9X0
Copy link
Owner

@X9X0 X9X0 commented Nov 17, 2025

Summary

Comprehensive fixes to the Raspberry Pi Image Builder and SD Card Writer workflow, resolving critical WiFi auto-connect issues, configuration persistence bugs, and device handling reliability problems.

Critical Issues Fixed

🔧 WiFi Auto-Connect (Primary Fix)

Problem: WiFi credentials written to image but Pi didn't auto-connect on first boot
Root Cause: Modern Raspberry Pi OS Bookworm (2024+) uses NetworkManager by default, which doesn't read traditional /etc/wpa_supplicant/wpa_supplicant.conf files
Solution:

  • Added NetworkManager connection file (.nmconnection) creation
  • Writes WiFi config to 4 locations for maximum compatibility:
    • /boot/wpa_supplicant.conf (legacy boot partition)
    • /boot/firmware/wpa_supplicant.conf (newer boot location)
    • /etc/wpa_supplicant/wpa_supplicant.conf (traditional wpa_supplicant)
    • /etc/NetworkManager/system-connections/*.nmconnection (NetworkManager - this was missing!)
  • Uses encrypted PSK via wpa_passphrase for security
  • Sets autoconnect=true and autoconnect-priority=1

🏷️ Environment Variable Bugs

Problem: Hostname always defaulted to "lablink", WiFi credentials ignored, admin password not applied
Root Causes:

  1. Variable name mismatch: GUI used PI_HOSTNAME/ADMIN_PASSWORD, but script expected LABLINK_HOSTNAME/LABLINK_ADMIN_PASSWORD
  2. Build script entered interactive mode when run via pkexec, overwriting environment variables with defaults

Solutions:

  • Corrected variable names in pi_image_builder.py to match script expectations
  • Added check to skip interactive prompts when LABLINK_HOSTNAME is already set

🎛️ First-Boot Configuration

Problem: Pi booted to setup wizard asking for keyboard layout, username, etc.
Solution:

  • Disabled piwiz using 5 complementary methods
  • Pre-configured locale (en_US.UTF-8), keyboard (US), timezone (America/New_York)
  • Configured auto-login for admin user via lightdm and console
  • Created admin user during image build to prevent username prompt
  • Made first-boot LabLink installation script more robust with network retry logic

💾 SD Card Writer Reliability

Problem: Intermittent "No medium found" errors when writing to SD card
Solution:

  • Added device readiness check with 10 retry attempts using blockdev --getsize64
  • Verifies medium is actually present before attempting write
  • Fixed verification to run with elevated privileges (pkexec/sudo)
  • Fixed eject to use elevated privileges, preventing permission errors
  • Added clear troubleshooting messages when device not ready

Files Changed

  • build-pi-image.sh - WiFi NetworkManager config, environment variables, interactive mode skip, first-boot setup
  • client/ui/pi_image_builder.py - Environment variable names corrected (LABLINK_HOSTNAME, LABLINK_ADMIN_PASSWORD)
  • client/ui/sd_card_writer.py - Device readiness checking, verification, eject with proper privileges

Testing Performed

✅ Built image with WiFi credentials and custom hostname "lablink-pi"
✅ WiFi auto-connects on first boot without manual intervention
✅ Hostname correctly set to user-specified value
✅ Admin user auto-logs in with no wizard prompts
✅ SD card writer handles intermittent device issues gracefully
✅ Verification and eject work without permission errors

Impact

Before: Users had to manually configure WiFi via WPS/GUI on first boot, wizard prompts blocked setup
After: Complete end-to-end automation - Build → Write → Boot → Auto-connect → LabLink ready

Users can now create fully pre-configured Raspberry Pi images that boot directly to a working LabLink installation with network connectivity.

- Changed title bar background from #2c3e50 to #1a252f for better contrast
- Added auto_fix_if_needed() method to automatically prompt for fixes
- Auto-fix dialog now appears when fixable errors are detected after system checks
- Improves user experience by proactively offering to fix dependency issues
UI Improvements:
- Added light gray background (#ecf0f1) to main window for better contrast
- Enhanced GroupBox elements with white backgrounds and defined borders
- Added proper margins (15px) and spacing (15px) to main layout
- Improved title bar with darker border for better definition

Button Enhancements:
- All buttons now have 2px borders with hover/press states
- Refresh button: Light gray with subtle borders
- Fix button: Orange theme (#f39c12) with darker borders
- Server button: Green (#27ae60) with defined borders and disabled state
- Client button: Blue (#3498db) with defined borders and disabled state
- Added border-radius to all buttons for consistency

LED Indicator Improvements:
- Added light background (#f8f9fa) with border to each indicator
- LED circles now have dark borders for better definition
- Improved glow effect with adjusted alpha (80)
- Better visual separation between indicators

Progress Elements:
- Progress label now has white background with border
- Progress bar styled with borders and custom chunk color
- Better visual integration with overall theme

Result: Much clearer visual hierarchy and better-defined UI elements
- Added 3px solid border (#34495e) to QMainWindow
- Creates clear visual definition for the outermost window boundary
- Border color complements the existing dark theme elements
- Improves overall window presence and visual hierarchy
- Increased border from 3px to 5px for stronger definition
- Darkened border color from #34495e to #2c3e50
- Creates more prominent window boundary
- Improves visual presence and professional appearance
- Increased border from 5px to 6px for stronger presence
- Darkened border color from #2c3e50 to #1a252f
- Border now matches header color for cohesive design
- Creates very strong, professional window definition
- Changed border color from #1a252f to #0d1419
- Border now darker than header background (#1a252f)
- Matches header border color for cohesive design
- Creates stronger visual hierarchy
Applied the same visual styling from the launcher to the client application:

Main Window (main_window.py):
- Added 6px solid border (#0d1419) to main window
- Set background color to #ecf0f1
- Added 15px margins and 10px spacing to central widget layout
- Consistent visual appearance with launcher

Connection Dialog (connection_dialog.py):
- Added light gray background (#ecf0f1)
- Styled GroupBox elements with white backgrounds and 2px borders
- Enhanced buttons with blue theme, borders, and hover/press states
- Added 15px margins and 10px spacing to layout

Login Dialog (login_dialog.py):
- Applied same styling as connection dialog
- Consistent GroupBox and button styling
- Proper margins (15px) and spacing (10px)

Result: Cohesive, professional appearance across all client UI components
Pi Image Builder Fixes:
- Fixed non-functional Next button by providing default output path
  - Output path field now defaults to ~/lablink-pi.img
  - Required field validation now satisfied on wizard open
- Applied consistent visual styling to wizard
  - Light gray background (#ecf0f1)
  - Styled GroupBox elements with white backgrounds and borders
  - Enhanced buttons with hover/press states

SD Card Writer Improvements:
- Applied consistent visual styling matching other dialogs
- Added proper margins (15px) and spacing (10px) to layout
- Styled GroupBox and button elements for cohesive appearance

Result: Pi Image Builder wizard now functional with Next button enabled
The issue was that the default output path was being set BEFORE the field
was registered with the wizard. Qt's wizard validation system checks field
completeness at registration time.

Fix:
- Moved setText() call to AFTER registerField()
- Added completeChanged.emit() to notify wizard of state change
- This ensures Qt recognizes the field as complete and enables Next button

The wizard now properly enables the Next button on startup with the default
path ~/lablink-pi.img pre-filled.
The issue was that setting the default value in __init__ happens before
the wizard fully initializes its validation system.

Solution:
- Moved default path setting to initializePage() method
- This is called by Qt when the page is displayed
- Qt's validation system is fully set up at this point
- The Next button will properly enable when the page loads

initializePage() is the correct Qt method for setting initial values
in wizard pages as it's called after the page is added to the wizard
and the validation system is ready.
The correct way to set default values for wizard fields is to pass them
to the widget constructor BEFORE registering the field, exactly like the
hostname field does.

Before (not working):
- Set value in initializePage() after registration

After (working):
- QLineEdit(default_path) - passes default to constructor
- Same pattern as hostname: QLineEdit("lablink-pi")
- Value exists when registerField() is called
- Qt wizard validation recognizes field as complete

This matches Qt's expected pattern for wizard field initialization.
Fixed two issues with Pi Image Builder wizard:

1. Next button validation:
   - Added explicit isComplete() method that checks required fields
   - Connected textChanged signals to completeChanged for real-time validation
   - Wizard now properly enables Next when both hostname and output_path have values

2. Button styling:
   - Added QPushButton:disabled state styling
   - Buttons now show proper hover effects (blue -> darker blue)
   - Disabled state shows gray appearance
   - Visual feedback now works correctly for all button states

The wizard buttons are now properly styled and the Next button enables
when the page loads with default values (hostname: "lablink-pi",
output_path: "~/lablink-pi.img").
Added extensive logging to diagnose build process issues:

- Log script path and existence check
- Log process PID when started
- Log every line of build output with line numbers
- Log process exit code
- Log all errors with full context
- Show script path and PID in GUI output window

This will help identify why the build gets stuck at 5% by showing:
1. Whether the script is found
2. Whether the process actually starts
3. What output (if any) the script produces
4. Any errors that occur

Logs go to both logger (console/file) and GUI output window.
The build script requires sudo to:
- Mount loop devices
- Use kpartx for partition mapping
- Resize filesystems
- Access /dev devices

Changes:
- Check if pkexec is available before running
- Run script with pkexec for graphical sudo prompt
- Warn users they'll need to enter their password
- Log whether running with elevated privileges or not
- Show clear error if pkexec not available

This fixes the issue where the wizard would hang at 5% because
the script was exiting immediately when it detected it wasn't
running as root (check_root() function exits with code 1).

The user will now see a password prompt dialog and the build
will proceed with proper permissions.
The build script output was being buffered when run through pkexec,
causing the GUI to show no progress even though the script was running.

Solution:
- Use stdbuf -oL -eL to force line-buffered output
- -oL: Line buffer stdout
- -eL: Line buffer stderr
- This makes output appear in real-time in the GUI

Now users will see the build progress as it happens instead of
waiting for the entire buffer to flush.
Changed from stdbuf to a different approach:
- Set bufsize=0 for unbuffered I/O in Popen
- Pass environment variables via bash -c wrapper instead of env parameter
- This ensures env vars are properly exported before script runs
- Removed stdbuf in favor of direct unbuffered subprocess

The previous approach used env parameter with Popen, but when wrapping
the command through pkexec, the env variables weren't being passed through.
Now we export them in a bash wrapper that pkexec executes.
…ffering

The real issue with buffering is that Python's text mode readline()
internally buffers even with bufsize=0.

Solution:
- Read in binary mode with bufsize=0 (truly unbuffered)
- Read one byte at a time
- Accumulate bytes until we hit newline (\n)
- Decode complete lines as UTF-8 and emit to GUI
- Parse progress from decoded lines

This gives us character-by-character unbuffered output from the
build script, so the GUI shows progress in real-time as the script
writes it, not when buffers flush.
…output

The previous approach was overriding the script command setup. This fix:

1. Creates a temporary shell script wrapper that exports all env variables
2. Uses 'unbuffer' command (from expect package) for truly unbuffered I/O
   - unbuffer forces programs to flush output immediately
   - Works by tricking programs into thinking they're connected to a TTY
3. Falls back to plain bash if unbuffer isn't installed
4. Cleans up temp wrapper file after execution

The unbuffer command is the gold standard for forcing unbuffered output
from shell scripts. If user doesn't have it, we suggest installing the
'expect' package which provides it.

Install with: sudo apt install expect
The fundamental issue is that bash buffers stdout when it detects
it's not connected to a terminal. All previous attempts (stdbuf,
unbuffer, binary mode) failed because the buffering happens inside
bash before Python ever sees the output.

Solution: Use Python's pty module
- pty.openpty() creates a pseudo-terminal pair (master/slave)
- Subprocess runs with stdout/stderr/stdin connected to slave fd
- Bash thinks it's connected to a terminal -> uses line-buffered I/O
- Parent process reads from master fd with select() for non-blocking I/O
- Process complete lines as they arrive in real-time

This is the definitive solution for getting unbuffered output from
shell scripts - it's what tools like 'script' and 'unbuffer' use
internally.

The GUI will now show build progress in real-time as the script
executes each step.
The build script checks if stdin is a TTY and prompts for configuration:
- Output image name
- Hostname
- Enable SSH
- Wi-Fi SSID/password
- Admin password

Since we're using a pty, the script thinks it's interactive and waits
for user input with read -p commands. This caused the hang after
"Configuration Options:" message.

Solution:
- Immediately after process starts, write 6 newlines to master_fd
- This accepts all the default values for the prompts
- The script uses our environment variables as the defaults
- Build proceeds non-interactively

Now the script will continue past the prompts and start the actual
build process.
Add a new LED indicator to track client utilities (deployment and
discovery tools) separately from client dependencies. This allows users
to see the status of optional utilities and install them automatically.

Changes:
- Add utilities LED indicator to launcher UI showing deployment and
  discovery tools status
- Implement check_utils_dependencies() to verify paramiko, scp, scapy,
  and zeroconf installation
- Add auto-fix support for installing missing utilities
- Integrate utilities check into the check sequence after client deps
- Add show_utils_details() for detailed utilities information dialog
- Update FixWorker to handle pip_install:client_utils fix command

The utilities are categorized as:
- Deployment: paramiko (SSH), scp (secure copy)
- Discovery: scapy (network packets), zeroconf (mDNS/Bonjour)

These utilities are already listed in client/requirements.txt but are
now separately tracked for better visibility and targeted installation.
Add build tools required for the Raspberry Pi image builder to the
launcher's system dependency checks, allowing them to be automatically
detected and installed.

Build tools added:
- kpartx: Partition mapping for disk images
- qemu-user-static: ARM emulation for chroot operations
- parted: Partition manipulation
- wget: Download utilities
- xz-utils: Compression utilities

These tools are now categorized separately as "Build tools (for Pi
image builder)" in the system packages check. When missing, they can
be auto-installed with the "Fix Issues" button.

This ensures users have all necessary dependencies before attempting to
build Raspberry Pi images through the client's image builder tool.
Fix segmentation fault crash when clicking "Fix Issues" button with
missing system packages. The crash was caused by trying to show
QMessageBox dialogs from a background worker thread, which Qt doesn't
allow.

Changes:
- Remove GUI dialog calls from FixWorker thread
- Run pkexec apt commands directly from worker thread
- pkexec handles its own GUI password prompt safely
- Add proper error handling for cancelled operations
- Check for pkexec availability before attempting install

The fix resolves the QPainter threading errors:
  "QPainter::begin: A paint device can only be painted by one painter"
  "Segmentation fault (core dumped)"

Now the launcher can automatically install missing build tools (kpartx,
qemu-user-static, parted) without crashing.
Fix bug where the script tried to move the extracted image to itself,
causing the error:
  "mv: '/tmp/lablink-pi-build/raspios.img' and
   '/tmp/lablink-pi-build/raspios.img' are the same file"

After xz extraction, the .img file is already in the correct location,
so the mv command was unnecessary and caused the build to fail.

Changes:
- Remove the mv command that tried to move file to itself
- Simplify extracted_img to point directly to the extracted file
- This fixes the "No such file or directory" error in expand_image()

The Pi image builder should now complete successfully.
Add comprehensive error checking to the Pi image extraction process
to diagnose why the extracted .img file is not being created.

Changes:
- Check xz command exit code and fail fast if extraction fails
- Verify extracted image file exists before proceeding
- List working directory contents if extraction verification fails
- Add success message when extraction completes

This will help identify whether the issue is:
- xz command failing
- Disk space/permissions issues
- File not being created despite successful xz return code

The detailed error output will make debugging much easier.
Add file existence check and ls output right before the dd command
to diagnose why the image file appears to exist after extraction but
is not found when expand_image tries to use it.

This will show:
- Whether the file still exists when expand_image is called
- File permissions and ownership
- Directory contents if file is missing
- Actual dd error output (2>&1)

This should help identify if the issue is:
- File being deleted between functions
- Permission/ownership problems
- Path/quoting issues
- dd-specific access problems
Fix critical bug where print_step/print_error/print_warning output was
contaminating function return values, causing expand_image to receive a
path containing ANSI color codes and multiple log lines instead of just
the image file path.

Changes:
- Redirect all print_step() output to stderr (>&2)
- Redirect all print_error() output to stderr (>&2)
- Redirect all print_warning() output to stderr (>&2)

This ensures that when functions like download_pi_os() use echo to
return values, only the actual return value appears on stdout, not
the status messages.

Before: $img_file contained colored log messages + path
After: $img_file contains only the clean path

This fixes the "No such file or directory" error in expand_image.
Add comprehensive improvements to the Raspberry Pi Image Builder:

**Pi Model Selection:**
- Add dropdown to select between Raspberry Pi 3, 4, and 5
- Build script automatically selects correct OS image based on model:
  * Pi 3: 32-bit armhf Raspberry Pi OS Lite
  * Pi 4: 64-bit arm64 Raspberry Pi OS Lite
  * Pi 5: 64-bit arm64 Raspberry Pi OS Lite (default)
- Pass PI_MODEL environment variable to build script

**Improved Output Formatting:**
- Terminal-style dark theme (black background, light text)
- Left-justified text with no wrapping for cleaner display
- Monospace font (Courier New/Consolas) for better readability
- Wider display area with horizontal scrolling
- More professional appearance matching terminal output

**Real-time Progress Updates:**
- Parse wget download progress (0-100%) and map to overall progress
- Show extraction progress with detailed steps
- Track dd (image expansion) progress
- Monitor compression progress
- More granular progress reporting (5%, 16%, 20%, 22%, 25%, etc.)
- Clear progress messages for each build phase

**User Experience:**
- Cleaner, more professional build output display
- Better visibility into what's happening during build
- Easier to spot errors with improved formatting
- Model selection makes it clear which Pi is being targeted

All improvements maintain backward compatibility with existing functionality.
- Added verbose (-v) flag to xz extraction for real-time progress output
- Enhanced progress parser to detect xz extraction percentage updates
- Fixed compression logic to only compress when output filename ends with .xz
- Remove existing compressed file before compression to prevent "File exists" error
- Improved progress tracking for extraction phase (16-20% of total progress)
claude and others added 26 commits November 16, 2025 18:40
Pi Image Builder improvements:
- Fix real-time progress parsing bug (referenced non-existent output_text)
- Add support for carriage return progress updates (xz in-place updates)
- Maintain recent output buffer in thread for context checking
- Extract progress parsing logic to reusable helper method
- Progress now updates in real-time during download/extraction/compression

SD Card Writer improvements:
- Add "Recent Images" button to quickly select recently created images
- Search common locations: home, Downloads, /tmp/lablink-pi-build, Desktop
- Show images sorted by modification time with size and date info
- Auto-detect images from Pi Image Builder output

Fixes issue where progress would show all at once after completion
instead of updating in real-time during operations.
The previous implementation was consuming lines with \r (carriage returns)
and only parsing progress without emitting the output to the user. This
caused important build output to be hidden, making the logs incomplete
and jumbled.

Reverted to simple newline-based processing. Progress updates will still
work through the xz verbose output, they just may not update AS frequently
(which is acceptable for readable output).

The "expect" package is unrelated - this was a bug in the CR handling logic.
Major improvements to the build output readability:

1. Strip ANSI escape codes - Remove color codes like [0m, [0;32m from display
2. Smart CR/LF handling - Properly handle both \r and \n line terminators
   - \r (carriage return) for progress updates - parse but don't spam output
   - \n (newline) for complete lines - display normally
3. Filter noise - Skip empty lines and resize2fs debug output
4. Better progress parsing - Track CR lines for progress without cluttering output

This fixes the issues where:
- ANSI color codes appeared as literal text
- Blank lines from \r handling
- Progress lines appeared fragmented
- Missing download/extraction/compression steps in output
- Output not representative of current workflow status

Now the output will be clean, readable, and show actual build progress.
The previous implementation was filtering out too much content:
- Lines were being stripped too early, causing content loss
- Empty line check was catching meaningful whitespace-only formatting
- All \r (carriage return) lines were hidden completely

Changes:
- Strip for checking but emit original line with formatting preserved
- Only filter resize2fs noise and truly empty stripped lines
- CR lines still parsed for progress but not displayed to avoid spam
- All newline-terminated lines now shown (the actual build steps)

This should show all meaningful build output including the build script's
formatted headers, step messages, and completion status.
The previous complex \r vs \n handling was hiding too much output.
Major simplification:

- Treat both \r and \n as line terminators (with CRLF support)
- Show ALL non-empty lines (except resize2fs debug noise)
- Remove is_final logic that was hiding CR-terminated lines
- Strip ANSI codes from all output
- Parse progress from all lines

This means:
- wget download lines with \r will be visible
- xz extraction lines with \r will be visible
- All build script step messages will be visible
- Progress bar still updates from parsed percentages

The output may have more lines now (including download/extraction
progress updates), but at least nothing will be hidden and the user
can see what's actually happening during the build.
Pi Image Builder:
- Add text-align: left to build output QTextEdit styling
- Ensures text is properly left-justified in the window

SD Card Writer image search:
- Add ~/LabLink/ directory to search paths
- Search for ~/LabLink-* directories (case-insensitive)
- Automatically finds LabLink, LabLink-test, LabLink-testing, etc.
- Update "No Images Found" dialog to show new search locations

The image finder will now search:
- Home directory
- ~/LabLink/
- ~/LabLink-* (any case variation)
- Downloads
- /tmp/lablink-pi-build
- Desktop
Major fix for image visibility issue:

Problem: Images were created with root ownership (via pkexec) making
them invisible/inaccessible to the normal user in file browsers

Solution:
1. Python launcher now passes current user info to build script:
   - SUDO_USER: username
   - ORIGINAL_UID: user ID
   - ORIGINAL_GID: group ID

2. Build script now fixes ownership after image creation:
   - Detects if running as root
   - Uses ORIGINAL_UID:ORIGINAL_GID to chown image files
   - Falls back to SUDO_USER if needed
   - Applies to both .img and .sha256 files

Now the built images will be:
- Visible in file browsers
- Accessible to the user who built them
- Selectable via "Recent Images" in SD Card Writer
The image was silently failing to be created due to missing error
handling in the finalize_image() function.

Changes:
1. Create output directory if it doesn't exist
2. Add error checking on cp command with detailed error messages
3. Fix ownership of output directory (not just the file)
4. Verify file is readable after creation
5. Better error messages to diagnose copy failures

Now if the image copy fails, we'll see exactly what went wrong instead
of silently succeeding with no output file.

This should help debug why images aren't appearing in /home/x9x0/
Problem: The build script was ignoring the OUTPUT_IMAGE path specified
by the GUI launcher and using a default filename with date instead.

Root cause: Script was using command-line argument instead of checking
the OUTPUT_IMAGE environment variable set by the Python launcher.

Now checks OUTPUT_IMAGE env var first, then command-line arg, then default.

This ensures images are created at the user-specified path
(e.g., /home/x9x0/lablink-pi.img) instead of in an unknown location.
Problem: SD Card Writer was checking if the entire app was running
as root and refusing to work if not, telling users to run with sudo.

Solution:
1. Remove the root privilege check from _write_image()
2. Update _write_unix() to use pkexec instead of sudo
3. Auto-detect pkexec availability, fall back to sudo if needed
4. pkexec provides graphical password prompt (no terminal needed)

Now users can:
- Run LabLink normally (not as root)
- Write SD cards via the GUI
- Get a graphical password prompt when needed
- Works the same way as the Pi Image Builder

This matches the UX of the image builder which already uses pkexec.
…erly

Problem: SD card writes were failing silently with 'dd failed:' but no
error message. The issue was that stderr from dd wasn't being captured
when using pkexec.

Root cause: When running 'pkexec dd ...', the stderr stream is consumed
by pkexec's authentication dialog and not available to the Python process.

Solution:
1. Create a temporary bash wrapper script (like image builder does)
2. Redirect stderr to stdout in the script (exec 2>&1)
3. Run 'pkexec bash /tmp/script.sh' instead of direct dd command
4. Capture all output (stdout + stderr merged)
5. Parse progress from combined output
6. Show last 10 lines of output on error for debugging

Now error messages will be visible and dd progress should work correctly.
Fixed SyntaxWarning about invalid escape sequence '\P' in the
ImageWriterThread.__init__ docstring.

Changed the docstring to a raw string (r""") to properly handle
the Windows path example: \\.\PhysicalDrive1

The backslashes in Windows device paths need to be in a raw string
or properly escaped to avoid Python interpreting them as escape sequences.
…iting

Problem: DD was failing with 'No medium found' even though the SD card
device existed and was visible in lsblk. This appeared to be a race
condition after unmounting.

Root cause: After unmounting partitions, the kernel needs time to
re-read the partition table and settle before the device is fully
accessible for raw writes.

Solution:
1. Add 1 second delay after unmount in Python code
2. Add sync and blockdev --rereadpt in wrapper script
3. Add 2 second delay after blockdev before dd
4. Verify device is a block device before attempting write
5. Add final sync after dd to ensure all writes are flushed

This gives the kernel time to:
- Release the mounted filesystem
- Re-read the partition table
- Make the device fully accessible for raw block writes

Should fix 'No medium found' errors when writing to SD cards.
The SD card write works perfectly but verification fails with
'Permission denied' because it runs in the Python thread without
elevated privileges.

Changes:
- Disable verification checkbox by default (was checked)
- Update label to indicate it requires root access
- Add tooltip explaining why it might fail
- Keep option available for users who run app with sudo

The write operation completes successfully - verification is just
a safety check and not critical. Users can enable it if needed,
but it will fail unless the entire app runs with sudo.

Verified working: Image successfully written to SD card with
real-time progress updates (4.58 GB in ~70 seconds).
…on boot

Problem: Raspberry Pi 4 image booted to blue screen asking for username
instead of using pre-configured 'admin' user.

Root cause: Modern Raspberry Pi OS (2022+) doesn't create a default user
for security reasons. The build script wasn't creating the admin user,
and the first boot script referenced non-existent 'pi' user.

Changes:
1. configure_first_boot() now creates admin user in chroot:
   - Uses useradd with all necessary groups (sudo, gpio, i2c, spi, etc.)
   - Sets password from LABLINK_ADMIN_PASSWORD or default 'lablink'
   - Adds sudoers entry for passwordless sudo
   - Copies qemu for ARM emulation

2. Fixed first boot script to use 'admin' instead of 'pi':
   - Changed 'usermod -aG docker pi' to 'usermod -aG docker admin'

Now the Pi will boot directly to login prompt with:
- Username: admin
- Password: lablink (or custom password from builder)

No more blue screen asking for username configuration!
The SD card writer was experiencing intermittent "No medium found" errors
even when the device was detected. This adds comprehensive device readiness
checking with:

- Device readiness check function with 10 retry attempts
- Uses blockdev --getsize64 to verify medium is actually present
- Retry logic with 1-second delays between attempts
- Clear error messages suggesting SD card reader troubleshooting
- Final verification right before write operation
- Updated progress messages to show readiness check phase

This should resolve intermittent write failures caused by the device not
being fully ready when dd attempts to open it.
The verification and eject operations were failing with "Permission denied"
because they tried to access the device without root privileges. This commit
fixes both issues:

**Verification fixes:**
- Rewrote _verify_write() to use pkexec/sudo like the write operation
- Uses 'cmp' command to compare first 100MB of image with device
- Runs in a temporary bash script with elevated privileges
- Updated checkbox label to indicate it compares first 100MB
- Updated tooltip to explain password requirement

**Eject fixes:**
- Updated _safely_eject() to use pkexec/sudo for the eject command
- Captures output to suppress permission denied warnings
- Changed final message to "SD card ready to remove"

Both operations now work correctly without permission errors.
The Raspberry Pi was booting to the first-boot wizard (piwiz) asking for
keyboard layout and other settings. This adds pre-configuration to skip
the wizard entirely:

- Remove piwiz from autostart (/etc/xdg/autostart/piwiz.desktop)
- Create .piwiz_done sentinel file in admin home directory
- Pre-configure locale to en_US.UTF-8
- Pre-configure keyboard layout to US (pc105)
- Pre-configure timezone to America/New_York
- Set WiFi country to US in wpa_supplicant.conf

The Pi should now boot directly to the desktop with admin user logged in,
no configuration wizard.
The wizard was still appearing despite previous fixes. This adds multiple
complementary methods to ensure the wizard is completely disabled:

**Additional wizard disabling methods:**
1. Create piwiz.desktop with Hidden=true as override
2. Disable userconfig.service systemd unit
3. Create userconf.txt/userconf in boot partition with encrypted password
4. Add .piwiz_done sentinel file in admin home directory

**Auto-login configuration:**
- Configure lightdm to auto-login admin user with no delay
- Configure getty@tty1 for console auto-login as fallback
- This ensures admin is logged in immediately on boot

The combination of disabling all wizard services and enabling auto-login
should completely bypass the first-boot keyboard/locale selection wizard.
The first-boot service was failing and blocking boot due to network timing
issues. This makes it much more robust:

**Script improvements:**
- Removed `set -e` to prevent failing on first error
- Added wait_for_network() function with 30 retry attempts
- Added proper error handling for each step (apt, docker, download)
- Logs to /var/log/lablink-first-boot.log for debugging
- Gracefully exits if network unavailable with helpful message
- Only fails if critical steps (Docker, LabLink download) fail

**Service improvements:**
- Added `SuccessExitStatus=0 1` to not fail boot on errors
- Added `RemainAfterExit=no` for proper cleanup
- Waits for multi-user.target to ensure system is ready

The Pi will now boot successfully even if LabLink setup fails, and users
can check /var/log/lablink-first-boot.log to see what happened.
The WiFi was not connecting on first boot. This fixes the configuration by:

**WiFi configuration improvements:**
- Use wpa_passphrase to generate encrypted PSK (more reliable than plaintext)
- Write WiFi config to BOTH /boot/wpa_supplicant.conf AND /etc/wpa_supplicant/wpa_supplicant.conf
- Remove duplicate WiFi country configuration that was being overwritten
- Fallback to plaintext password if wpa_passphrase is not available
- Create basic wpa_supplicant.conf even when no WiFi credentials provided

**Why this works:**
- Boot partition config is used by first-boot scripts
- Rootfs config is used directly if boot partition copy fails
- Encrypted PSK is more compatible with modern wpa_supplicant
- Dual placement ensures WiFi works regardless of boot mechanism

This should fix the network connectivity issues preventing the LabLink
first-boot setup from running.
The WiFi configuration wasn't working because:
1. Modern Pi OS may use /boot/firmware/ subdirectory
2. No debug output showing where files were written
3. Only wrote to one boot location

**Improvements:**
- Write wpa_supplicant.conf to boot partition root
- Also copy to /boot/firmware/ subdirectory if it exists
- Write to rootfs /etc/wpa_supplicant/wpa_supplicant.conf
- Add detailed logging showing exact paths where files are written
- Log the SSID being configured for verification
- Same triple-write approach for both encrypted and plaintext configs

This ensures WiFi config works on all Raspberry Pi OS versions (older and
newer boot partition layouts) and makes debugging easier with detailed logs.
Found critical bug where GUI was using wrong environment variable names:
- GUI was setting PI_HOSTNAME but build script expects LABLINK_HOSTNAME
- GUI was setting ADMIN_PASSWORD but build script expects LABLINK_ADMIN_PASSWORD

This caused:
1. Hostname to always default to "lablink" instead of user-specified value
2. Admin password to default to "lablink" instead of user-specified value

**Fixed in both places:**
- Direct environment variable passing (line 121, 129)
- pkexec wrapper script generation (line 159, 172)

Now matches build script expectations:
- LABLINK_HOSTNAME (used in build-pi-image.sh:45, 394, 397)
- LABLINK_ADMIN_PASSWORD (used in build-pi-image.sh:49, 222, 495)
The build script was entering interactive mode even when running via pkexec
with environment variables already set. This caused it to:
1. Prompt for input (which fails silently via pkexec)
2. Use empty input, which defaulted all values
3. Overwrite the environment variables we carefully set in the GUI

Root cause: Line 737 checked `if [ -t 0 ] && [ $# -eq 0 ]`
- When running via pkexec, stdin IS a terminal (the auth dialog)
- So it entered interactive mode
- And overwrote LABLINK_HOSTNAME, WIFI_SSID, etc. with defaults

**Fix:** Added check for LABLINK_HOSTNAME environment variable:
```bash
if [ -t 0 ] && [ $# -eq 0 ] && [ -z "$LABLINK_HOSTNAME" ]; then
```

Now it only prompts if LABLINK_HOSTNAME is NOT already set via environment.
This preserves the values set by the GUI wrapper script.

This fixes:
- Hostname always being "lablink" instead of user-specified value
- WiFi credentials never being applied
- Admin password always being default "lablink"
Modern Raspberry Pi OS (Bookworm 2024+) uses NetworkManager by default,
which doesn't automatically read /etc/wpa_supplicant/wpa_supplicant.conf.

The WiFi config was being written correctly, but NetworkManager wasn't
picking it up, requiring manual WPS connection on first boot.

**Changes:**
- Added Method 4: Create NetworkManager connection file (.nmconnection)
- Extracts PSK hash from wpa_passphrase output for encrypted config
- Places file in /etc/NetworkManager/system-connections/
- Sets strict 600 permissions (required by NetworkManager)
- Configures autoconnect=true and priority=1 for automatic connection
- Generates UUID for connection uniqueness
- Applies to both encrypted PSK and plaintext fallback paths

**Now creates 4 WiFi configs for maximum compatibility:**
1. /boot/wpa_supplicant.conf (boot partition)
2. /boot/firmware/wpa_supplicant.conf (newer boot location)
3. /etc/wpa_supplicant/wpa_supplicant.conf (traditional config)
4. /etc/NetworkManager/system-connections/*.nmconnection (modern NetworkManager)

This ensures WiFi auto-connects on first boot on all Raspberry Pi OS versions.
@X9X0 X9X0 merged commit c14b2f3 into main Nov 17, 2025
25 checks passed
@X9X0 X9X0 deleted the claude/fix-launcher-titlebar-repair-0187nhMwEvHWrbWYLWmwCBWB branch November 18, 2025 03:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants