Skip to content

Implement Intelligent Auto-Update with Version Check, Atomic Swap, and Rollback #97

@julio75012

Description

@julio75012

Current Behavior

The current install.sh performs a "blind reinstall" on every execution:

  1. Always downloads the latest release without checking if it's necessary
  2. Executes rm -rf "$APP_DIR" before verifying the new download integrity
  3. No backup mechanism — if extraction fails after removal, user is left with broken installation
  4. No version comparison feedback ("Already up to date" vs "Updated to X")

Proposed Implementation

Stage 1: Version Tracking

Add a .version file in $APP_DIR containing the installed version tag (e.g., v1.2.3).

Implementation:

  • During install, write echo "$VERSION" > "${APP_DIR}/.version"
  • On update, read current version: CURRENT_VERSION=$(cat "${APP_DIR}/.version" 2>/dev/null || echo "unknown")

Stage 2: Pre-flight Version Check

Before any destructive operation, compare remote vs local:

if [ "$CURRENT_VERSION" = "$VERSION" ]; then
    echo "✓ thin-wrap $VERSION is already installed and up to date."
    exit 0
fi

echo "Updating: $CURRENT_VERSION$VERSION"

Stage 3: Atomic Update with Backup

Replace the dangerous rm -rf + mv sequence with atomic operations:

# 1. Create backup
BACKUP_DIR="${APP_DIR}.backup.$(date +%s)"
cp -r "$APP_DIR" "$BACKUP_DIR"

# 2. Extract to temporary location first
TEMP_EXTRACT="${TMPDIR}/thin-wrap-new-$$"
mkdir -p "$TEMP_EXTRACT"
unzip -o "${ARCHIVE}" -d "$TEMP_EXTRACT"

# 3. Verify extraction success
if [ ! -f "$TEMP_EXTRACT/thin-wrap" ] && [ ! -f "$TEMP_EXTRACT/thin-wrap/thin-wrap" ]; then
    echo "ERROR: Extraction failed, keeping current version"
    rm -rf "$TEMP_EXTRACT"
    exit 1
fi

# 4. Atomic swap (remove old, move new)
rm -rf "$APP_DIR"
mv "$TEMP_EXTRACT" "$APP_DIR"

Stage 4: Integrity Verification

Add checksum verification (if available in GitHub releases):

# Download checksum file if available
if curl -fsL "${DOWNLOAD_URL}.sha256" -o "${ARCHIVE}.sha256" 2>/dev/null; then
    sha256sum -c "${ARCHIVE}.sha256" || {
        echo "ERROR: Checksum verification failed"
        exit 1
    }
fi

Stage 5: Cleanup and Rollback Strategy

  • On success: Remove backup after 24h (or keep last 3 versions)
  • On failure: Auto-restore from backup:
cleanup_failed_update() {
    echo "Update failed, restoring previous version..."
    rm -rf "$APP_DIR"
    mv "$BACKUP_DIR" "$APP_DIR"
    exit 1
}
trap cleanup_failed_update ERR

Detailed Acceptance Criteria

  • Idempotent updates: Running install script twice with same version exits cleanly with "already up to date" message
  • Version persistence: .version file exists in $APP_DIR after any install/update
  • Atomic operation: No window where $APP_DIR is empty or partially populated
  • Backup retention: Keep last 3 backups in ~/.local/lib/thin-wrap.backups/, auto-cleanup older ones
  • Integrity check: Verify downloaded archive size > 1MB and binary exists before replacing old version
  • Rollback: If new binary fails --version test (optional post-install check), auto-restore previous version
  • Non-interactive friendly: All operations must work via curl ... | sh without prompts

Optional Enhancements (Future PRs)

  1. Delta updates: Use zsync or rsume for partial downloads if release > 50MB
  2. Release notes: Display changelog between old and new version using GitHub API
  3. Self-update command: Add thin-wrap --update that spawns the installer (requires wrapper script modification)

Testing Checklist for PR

# Test 1: Fresh install
rm -rf ~/.local/lib/thin-wrap ~/.local/bin/thin-wrap
./install.sh
# Verify: .version file exists, config.json created

# Test 2: Re-run same version
./install.sh
# Verify: Should exit immediately with "already up to date"

# Test 3: Simulate failed download (corrupt zip)
# (requires manual network cut or invalid URL test)

# Test 4: Verify backup works
ls ~/.local/lib/thin-wrap.backup.*
./install.sh --force  # hypothetical flag to test reinstall
# Verify old version restorable

Priority: Medium
Effort: Small (1-2 hours for basic implementation, 4-6 hours with full rollback)
Breaking changes: None (backwards compatible)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions