-
-
Notifications
You must be signed in to change notification settings - Fork 34
Adds --use-usb parameter to install on USB for very low free-space routers #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This commit adds comprehensive USB installation functionality to support routers with very limited internal storage (e.g., GL-MT300N-V2 Mango). New features: - New --use-usb parameter for USB-based installation - Automatic USB detection and formatting (with user confirmation) - Symlink-based installation to keep binaries on USB - Auto-mount scripts (hotplug and init.d) for boot persistence - USB-specific persistence configuration - Helpful error messages suggesting USB mode for low-memory devices Key components: - detect_usb_device(): Finds USB storage devices - setup_usb_storage(): Formats and mounts USB drive - create_usb_hotplug_script(): Auto-mount on USB insertion - create_usb_init_script(): Boot-time service for USB-based Tailscale - install_*_tailscale_usb(): USB installation variants - upgrade_persistance_usb(): Firmware upgrade persistence - invoke_outro_usb(): USB-specific completion message Documentation: - Updated README with --use-usb parameter documentation - Added usage examples for USB installation - Updated features list and requirements table The implementation maintains full backward compatibility with existing functionality. All existing parameters and workflows remain unchanged. This contribution expands the script's compatibility to very low-memory routers that previously couldn't run updated Tailscale versions.
Add USB installation support for low-memory routers
Changes: - Show verbose output from opkg install to see package installation status - Add verification step after e2fsprogs installation - Display device information before formatting for debugging - Improve unmount handling with process killing (fuser) and forced unmount - Show actual mke2fs error output instead of suppressing it - Add better unmount checks and retry logic with forced/lazy unmount - Increase sleep time after unmounting to ensure device is ready - Add more informative error messages for USB formatting failures This should help diagnose and resolve USB formatting issues.
This commit significantly improves USB device handling to resolve
issues with devices that are already mounted or in use.
Key improvements:
1. Enhanced unmount logic:
- Kill processes using the device with fuser before unmounting
- Retry unmount up to 5 times with increasing delays
- Use both forced (-f) and lazy (-l) unmount as fallbacks
- Clear error messages if unmount fails after all attempts
2. Filesystem signature clearing:
- Use wipefs to clear existing filesystem signatures
- Fallback to dd if wipefs is not available
- Prevents "device in use" errors from old filesystem metadata
3. Force formatting:
- Try normal mke2fs first with single -F flag
- If that fails, use double -F flag (mke2fs -F -F) to force
formatting even if device appears to be in use or mounted
- Show detailed diagnostics if formatting still fails
4. Better diagnostics:
- Use lsof and fuser to show which processes are using device
- Provide helpful manual recovery commands if automated fix fails
This resolves the "device is apparently in use" error that occurs
when trying to format a USB drive that was previously used or is
still mounted.
…el cache This commit resolves persistent "device is busy" errors when formatting USB drives on OpenWrt/GL.iNet routers, even after unmounting. Root cause: The kernel maintains cached metadata about the device, and the block-mount/automount services can remount devices automatically after partition table changes. Key fixes: 1. Stop automount services before formatting: - Temporarily stop /etc/init.d/fstab and block-mount - Prevents automatic remounting during the format process - Services are restarted after successful mount 2. Comprehensive kernel cache clearing: - sync to flush all pending writes - blockdev --flushbufs to clear device buffers - wipefs to remove filesystem signatures - partprobe/blockdev --rereadpt on parent device to reload partition table - Multiple sync points with appropriate delays 3. Fix partition table reload: - Extract parent device (e.g., /dev/sda from /dev/sda1) - Run partprobe and blockdev --rereadpt on parent, not partition - This properly refreshes the kernel's partition table cache 4. Enhanced fallback strategy: - First try: mke2fs -F (normal force) - Second try: mke2fs -F -F (double force, ignores mount check) - Third try: dd 100MB of zeros + retry mke2fs - This handles even the most stubborn device busy states 5. Better diagnostics: - Check for processes holding the device before formatting - Verify device isn't remounted by checking mount output - Show lsof and fuser output if formatting still fails - Helpful error messages for manual recovery 6. Longer delays at critical points: - 3 seconds after wipefs/partprobe before formatting - 2 seconds after dd before retry - Ensures kernel has time to process changes This robust approach handles OpenWrt's aggressive automounting and kernel caching, making USB formatting reliable even on devices that were previously used or formatted. Tested on GL.iNet routers with existing Tailscale installations on USB.
Changed approach from complex automatic handling to simple detection
with helpful manual instructions when formatting fails.
Key changes:
1. Detect existing Tailscale USB installations:
- Check for LABEL="tailscale" on the device
- If found, attempt to stop services before formatting
2. Simplified formatting logic:
- Try basic unmount and format
- No complex retry loops or multiple fallback strategies
- Cleaner and more maintainable code
3. Clear manual instructions on failure:
- Detects WHY formatting failed (existing installation vs other)
- Provides exact copy-paste commands for the user
- Different instructions for existing vs new installations
- User runs commands, then re-runs script with --use-usb
4. Two scenarios handled:
- New installation: Simple format usually works
- Existing installation: If format fails, show how to:
* Stop and disable Tailscale service
* Remove old scripts
* Unmount USB
* Format USB
* Re-run the updater
This approach is more reliable and user-friendly than trying to
automatically handle every edge case with kernel cache clearing,
forced unmounts, and multiple retry strategies.
Benefits:
- Simpler code (140 lines -> 60 lines)
- More reliable (fewer failure modes)
- Better user experience (clear instructions when help is needed)
- Easier to maintain and debug
Updated the manual fallback instructions to handle stubborn device-in-use errors more effectively. Changes: - Use lazy unmount (umount -l) instead of regular unmount - Add dd command to write zeros and clear kernel cache - Add sync commands before and after dd - Remove symlinks as part of cleanup - Number steps clearly (1-5) - Add fallback suggestion to reboot if still failing The lazy unmount releases the device from the mount point even if processes are using it, and dd writing zeros forces the kernel to release its cache of the device, allowing mke2fs to work.
Reverted to the clearer, simpler manual instructions that are easier to follow. The original version works well when users follow all steps carefully.
Tailscale manual instructions for updates already using usb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds USB installation support for GL.iNet routers with extremely limited internal storage (< 15MB free). The --use-usb flag enables installation of Tailscale binaries to external USB storage, allowing users with very low-memory routers like the GL-MT300N-V2 Mango to use Tailscale.
Key changes:
- New USB installation mode that formats and uses external USB storage for binaries
- Auto-mount infrastructure with hotplug scripts and fstab entries for persistent USB mounting
- Custom init.d service that ensures USB is mounted before starting Tailscale daemon
- Enhanced preflight checks that suggest USB installation when low storage is detected
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 19 comments.
| File | Description |
|---|---|
| update-tailscale.sh | Adds ~450 lines implementing USB detection, storage setup, hotplug/init scripts, USB-specific installation functions, and persistence configuration for USB-based installations |
| readme.md | Documents the new --use-usb flag with usage examples, requirements, warnings about USB formatting, and important notes about keeping the USB connected |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| mount -t ext4 "$USB_DEV" "$USB_MOUNT_POINT" | ||
|
|
||
| if ! mount | grep -q "$USB_MOUNT_POINT"; then | ||
| log "ERROR" "Failed to mount USB" |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mount command lacks error handling. If the mount fails, the subsequent check at line 931 will catch it, but the mount command should explicitly check its return status to provide a more specific error message about why the mount failed.
Consider:
if ! mount -t ext4 "$USB_DEV" "$USB_MOUNT_POINT"; then
log "ERROR" "Failed to mount USB device $USB_DEV to $USB_MOUNT_POINT"
log "ERROR" "Check if the device is properly formatted and not in use"
exit 1
fi| mount -t ext4 "$USB_DEV" "$USB_MOUNT_POINT" | |
| if ! mount | grep -q "$USB_MOUNT_POINT"; then | |
| log "ERROR" "Failed to mount USB" | |
| if ! mount -t ext4 "$USB_DEV" "$USB_MOUNT_POINT"; then | |
| log "ERROR" "Failed to mount USB device $USB_DEV to $USB_MOUNT_POINT" | |
| log "ERROR" "Check if the device is properly formatted and not in use" | |
| exit 1 | |
| fi | |
| if ! mount | grep -q "$USB_MOUNT_POINT"; then | |
| log "ERROR" "Failed to verify USB mount at $USB_MOUNT_POINT" |
| fi | ||
| # Recreate symlinks to ensure they point to USB | ||
| if [ -f /mnt/usb_tailscale/bin/tailscaled ]; then |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the hotplug script, symlinks should be created after explicitly removing any existing files. If /usr/sbin/tailscale or /usr/sbin/tailscaled exist as regular files, ln -sf may not behave as expected. Add rm -f before creating symlinks:
rm -f /usr/sbin/tailscale /usr/sbin/tailscaled
ln -sf /mnt/usb_tailscale/bin/tailscale /usr/sbin/tailscale
ln -sf /mnt/usb_tailscale/bin/tailscaled /usr/sbin/tailscaled| if [ -f /mnt/usb_tailscale/bin/tailscaled ]; then | |
| if [ -f /mnt/usb_tailscale/bin/tailscaled ]; then | |
| rm -f /usr/sbin/tailscale /usr/sbin/tailscaled |
| # Create symlink on USB (tailscale -> tailscaled) | ||
| ln -sf "$USB_MOUNT_POINT/bin/tailscaled" "$USB_MOUNT_POINT/bin/tailscale" | ||
|
|
||
| # Create symlinks in /usr/sbin to USB binaries |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar symlink issue: remove existing files before creating symlinks to avoid potential conflicts:
rm -f /usr/sbin/tailscale /usr/sbin/tailscaled
ln -sf "$USB_MOUNT_POINT/bin/tailscale" /usr/sbin/tailscale
ln -sf "$USB_MOUNT_POINT/bin/tailscaled" /usr/sbin/tailscaled| # Create symlinks in /usr/sbin to USB binaries | |
| # Create symlinks in /usr/sbin to USB binaries | |
| rm -f /usr/sbin/tailscale /usr/sbin/tailscaled |
| sleep 2 | ||
| # Fallback to common device paths | ||
| elif [ -b /dev/sda1 ]; then | ||
| mkdir -p /mnt/usb_tailscale | ||
| mount -t ext4 /dev/sda1 /mnt/usb_tailscale 2>/dev/null | ||
| sleep 2 |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The init script checks if USB is mounted but doesn't verify if the mount was successful after attempting to mount. If the mount operation fails silently, the script will continue and fail later when trying to access the binaries. Add a verification step after the mount attempt:
if ! mount | grep -q "/mnt/usb_tailscale"; then
# Try to mount by label first
if blkid | grep -q 'LABEL="tailscale"'; then
mkdir -p /mnt/usb_tailscale
mount LABEL=tailscale /mnt/usb_tailscale 2>/dev/null
sleep 2
# Verify mount succeeded
if ! mount | grep -q "/mnt/usb_tailscale"; then
logger -t tailscale "ERROR: Failed to mount USB by label"
return 1
fi
# Fallback to common device paths
elif [ -b /dev/sda1 ]; then
mkdir -p /mnt/usb_tailscale
mount -t ext4 /dev/sda1 /mnt/usb_tailscale 2>/dev/null
sleep 2
# Verify mount succeeded
if ! mount | grep -q "/mnt/usb_tailscale"; then
logger -t tailscale "ERROR: Failed to mount USB from /dev/sda1"
return 1
fi
fi
fi| sleep 2 | |
| # Fallback to common device paths | |
| elif [ -b /dev/sda1 ]; then | |
| mkdir -p /mnt/usb_tailscale | |
| mount -t ext4 /dev/sda1 /mnt/usb_tailscale 2>/dev/null | |
| sleep 2 | |
| sleep 2 | |
| # Verify mount succeeded | |
| if ! mount | grep -q "/mnt/usb_tailscale"; then | |
| logger -t tailscale "ERROR: Failed to mount USB by label" | |
| return 1 | |
| fi | |
| # Fallback to common device paths | |
| elif [ -b /dev/sda1 ]; then | |
| mkdir -p /mnt/usb_tailscale | |
| mount -t ext4 /dev/sda1 /mnt/usb_tailscale 2>/dev/null | |
| sleep 2 | |
| # Verify mount succeeded | |
| if ! mount | grep -q "/mnt/usb_tailscale"; then | |
| logger -t tailscale "ERROR: Failed to mount USB from /dev/sda1" | |
| return 1 | |
| fi |
| sleep 2 | ||
| LABEL=$(blkid -s LABEL -o value /dev/$DEVNAME 2>/dev/null) |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 2-second sleep may not be sufficient to ensure the device is ready for operations. Consider increasing this to 3-5 seconds or implementing a retry loop to check if the device is truly ready before attempting to access it. On some systems, USB devices can take longer to initialize, especially slower/older USB drives.
| sleep 2 | |
| LABEL=$(blkid -s LABEL -o value /dev/$DEVNAME 2>/dev/null) | |
| # Wait up to 5 seconds for the device to be ready | |
| for i in $(seq 1 5); do | |
| LABEL=$(blkid -s LABEL -o value /dev/$DEVNAME 2>/dev/null) | |
| [ -n "$LABEL" ] && break | |
| sleep 1 | |
| done |
| chmod +x "$USB_MOUNT_POINT/bin/tailscale" | ||
| chmod +x "$USB_MOUNT_POINT/bin/tailscaled" | ||
|
|
||
| # Create symlinks in /usr/sbin to USB binaries |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same symlink issue: explicitly remove existing files first:
rm -f /usr/sbin/tailscale /usr/sbin/tailscaled
ln -sf "$USB_MOUNT_POINT/bin/tailscale" /usr/sbin/tailscale
ln -sf "$USB_MOUNT_POINT/bin/tailscaled" /usr/sbin/tailscaled| # Create symlinks in /usr/sbin to USB binaries | |
| # Create symlinks in /usr/sbin to USB binaries | |
| rm -f /usr/sbin/tailscale /usr/sbin/tailscaled |
| # USB Installation Functions | ||
| detect_usb_device() { | ||
| log "INFO" "Searching for USB device..." | ||
| sleep 2 |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The 2-second sleep before checking for USB devices may be unnecessary on most systems. Consider either reducing this delay or making it conditional based on whether devices are already detected. This would speed up the installation process when the USB is already ready.
| sleep 2 |
| exit 1 | ||
| fi | ||
| if [ ! -f "/tmp/tailscale/$TAILSCALE_SUBDIR_IN_TAR/tailscaled" ]; then | ||
| log "ERROR" "Tailscaled binary not found" |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, this error message should include the full path for consistency:
log "ERROR" "Tailscaled binary not found at /tmp/tailscale/$TAILSCALE_SUBDIR_IN_TAR/tailscaled"| log "ERROR" "Tailscaled binary not found" | |
| log "ERROR" "Tailscaled binary not found at /tmp/tailscale/$TAILSCALE_SUBDIR_IN_TAR/tailscaled" |
| if [ "$FORCE" -eq 0 ]; then | ||
| printf "\033[31m┌────────────────────────────────────────────────────────────────────────┐\033[0m\n" | ||
| printf "\033[31m│ WARNING: USB FORMATTING │\033[0m\n" | ||
| printf "\033[31m│ This script will FORMAT: $USB_DEV │\033[0m\n" |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The device name in the warning message may not align properly in the box due to variable length. Consider using a fixed-width format or padding the device name to ensure the box borders align correctly regardless of device name length (e.g., /dev/sda1 vs /dev/sdb1).
| printf "\033[31m│ This script will FORMAT: $USB_DEV │\033[0m\n" | |
| printf "\033[31m│ This script will FORMAT: %-16s%-36s│\033[0m\n" "$USB_DEV" " " |
| opkg update | ||
| opkg install block-mount e2fsprogs kmod-usb-storage kmod-fs-ext4 |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The opkg update and opkg install commands lack error handling. If the update fails (e.g., due to network issues or repository problems), the installation will continue and potentially fail later. Consider checking the return status and exiting if the package installation fails.
Example:
if ! opkg update; then
log "ERROR" "Failed to update package lists"
exit 1
fi
if ! opkg install block-mount e2fsprogs kmod-usb-storage kmod-fs-ext4; then
log "ERROR" "Failed to install required packages"
exit 1
fi| opkg update | |
| opkg install block-mount e2fsprogs kmod-usb-storage kmod-fs-ext4 | |
| if ! opkg update; then | |
| log "ERROR" "Failed to update package lists" | |
| exit 1 | |
| fi | |
| if ! opkg install block-mount e2fsprogs kmod-usb-storage kmod-fs-ext4; then | |
| log "ERROR" "Failed to install required packages" | |
| exit 1 | |
| fi |
|
Thank you for taking the time to add this feature. It may take a few more days, but it won't be forgotten! |
Happy to help. I love to extend the lifespan of old-but-working devices. |
Add USB Installation Support for Low-Memory Routers
Problem
Many GL.iNet routers, particularly older models like the GL-MT300N-V2 (Mango) (I had 3 of these at home, plus an old GL-AR150), have extremely limited internal flash storage. The current installation method requires at least 15MB of free space, which is often unavailable on these devices even after removing unnecessary packages.
This makes it impossible for users with low-memory routers to:
Solution Overview
This PR adds a new
--use-usbflag that enables USB-based installation for routers with insufficient internal storage. The implementation:/usr/sbin/to USB-mounted binariesKey Changes
New Features
--use-usbflag: Enables USB installation mode/dev/sda1,/dev/sdb1)/etc/hotplug.d/block/10-mount-tailscale-usbinstall_tiny_tailscale_usb()andinstall_tailscale_usb()invoke_outro_usb()with USB-relevant informationModified Functions
preflight_check(): Now suggests--use-usbwhen low storage is detectedcollect_user_preferences(): Works with both installation methodsupgrade_persistance(): Newupgrade_persistance_usb()for USB-specific persistence--use-usbis setDirectory Structure on USB
User Experience
Before (low-memory router):
After (with USB):
Testing
Successfully tested on:
Verified:
--force,--ssh, and other flags work with USB modeBackward Compatibility
✅ Fully backward compatible:
--no-tinyor default) works exactly as before--use-usbflagDocumentation Updated on readme.md
Help text updated:
README additions:
Important Notes
--forceis used)block-mount,e2fsprogs,kmod-usb-storage,kmod-fs-ext4(auto-installed)Files Modified
update-tailscale.sh: Added ~450 lines for USB supportFiles Created (by script)
/etc/hotplug.d/block/10-mount-tailscale-usb: Auto-mount script/etc/init.d/tailscale: Custom init service for USB installation/etc/fstab: Entry added for persistent mountingAI Notice
These changes were created with assistance from Claude Code over many iterations with clear focus on extending current functionality. Testing was done manually.
Thank you for creating and maintaining this excellent tool! This feature would help many users with older GL.iNet routers continue using Tailscale.