Files
driftwood/APPIMAGE-MANAGER-DESIGN.md

111 KiB
Raw Blame History

Driftwood - Modern AppImage Manager

A GTK4/libadwaita application for managing, integrating, updating, and auditing AppImages on modern Linux desktops.

Last updated: February 26, 2026


Table of Contents

  1. Problem Statement
  2. Landscape Analysis
  3. Project Vision & Goals
  4. Target Users
  5. Architecture Overview
  6. Core Features - Detailed Design
  7. UI/UX Design
  8. Technical Stack & Dependencies
  9. AppImage Internals & Integration Points
  10. FUSE Compatibility Layer
  11. Wayland Awareness Engine
  12. Update System
  13. Sandboxing & Permissions
  14. Bundled Library Security Scanner
  15. Usage Tracking & Disk Space Reclamation
  16. Orphaned Config & Data Detection
  17. Duplicate & Multi-Version Detection
  18. Desktop Integration Manager
  19. Background Daemon
  20. CLI Interface
  21. Distribution & Self-Updating
  22. KDE/Plasma Support Strategy
  23. Accessibility
  24. Internationalization
  25. Testing Strategy
  26. Development Phases & Roadmap
  27. Project Naming & Branding
  28. Risk Analysis
  29. Community & Contribution Model
  30. Appendix: Technical Reference

1. Problem Statement

AppImages are the only Linux packaging format that delivers true single-file, distro-agnostic, no-root-required application distribution. In theory, you download one file, make it executable, and run it. In practice, the experience falls apart:

  • No central management UI. Users accumulate AppImages in ~/Downloads with no way to see what they have, what version it is, or whether an update exists. Gear Lever and the newer AppManager address parts of this but remain limited in scope.
  • Desktop integration is manual or fragmented. Users must either manually create .desktop files and extract icons, or rely on appimaged (a daemon with no UI), AppImageLauncher (v3.0.0 now supports the new static runtime but still uses Qt5 and has a dated UX), Gear Lever (GNOME-only, Flatpak-distributed), or AppManager (newer, Vala/libadwaita, macOS-style - but still missing key features).
  • The Wayland-only era is here. GNOME 50 (March 2026) completely removes X11 backend code from Mutter and GNOME Shell. Ubuntu 26.04 LTS ships Wayland-only. KDE Plasma 6.8 (October 2026) drops its X11 session. Many AppImages still bundle older toolkits that fall back to XWayland - causing blurry HiDPI rendering, broken clipboard, missing gestures - and users have no idea why.
  • FUSE2 deprecation broke everything. Ubuntu 22.04+ removed libfuse2 from default installs (renamed to libfuse2t64 on Ubuntu 24.04+). Every AppImage using FUSE mounting simply fails to launch with a cryptic error. The new type2-runtime attempts to support both fuse2 and fuse3 but has compatibility issues with stable AppImageLauncher installations. The workaround (--appimage-extract-and-run) is obscure and per-invocation.
  • Updates are invisible. AppImageUpdate (latest release October 2025) and zsync2 (v2.0.0-alpha, September 2025) exist but have no user-facing GUI integrated into a management workflow. Gear Lever v4.4.6 and AppManager v3.0.0 now offer some update support, but neither provides transparent delta updates or system-wide update orchestration.
  • Orphaned desktop entries accumulate. Delete an AppImage file and the .desktop entry and icon remain forever, cluttering menus with broken launchers.

This is the gap Pedro Innecco identified in his "Linux Deserves Better: The Future of AppImage Integration" blog post (September 2025): AppImage has all the right technical primitives but no cohesive user experience layer. Innecco argues that AppImage deserves to be elevated to a standard, freedesktop-level service - shipped by default, with a clear convention that every user can rely on - rather than remaining an optional add-on that leaves the format as a second-class citizen compared to Flatpak or Snap.

Driftwood is that missing layer.


2. Landscape Analysis

2.1 AppImageLauncher

Aspect Status
Repository github.com/TheAssassin/AppImageLauncher
Toolkit Qt5 (with libappindicator for tray)
Latest release v3.0.0 (released 2025, after long beta cycle); v2.2.0 remains widely deployed
Maintenance Active again after a long stall; v3.0.0 was a major effort to modernize
Wayland Tray icon relies on StatusNotifierItem - works on KDE, still problematic on GNOME Wayland without extensions; integration dialogs functional but not native-feeling
FUSE v3.0.0 adds support for the new static runtime (which can use fuse2 or fuse3); older v2.2.0 requires libfuse2 only
Distribution Ubuntu PPAs are deprecated; GitHub releases only now

What it does well: Intercepts AppImage launches via binfmt_misc and offers "integrate or run once" dialogs. Moves AppImages to ~/Applications. Creates .desktop files and extracts icons automatically. v3.0.0 now handles the new static runtime, ARM64 support, and fixes many daemon issues.

What's broken/missing: No update management. No version tracking. No Wayland awareness. The binfmt_misc hook is fragile (breaks on kernel updates, doesn't work in containers, conflicts with other handlers). The new type2-runtime has known compatibility issues with stable AppImageLauncher versions. UI looks dated (Qt5, not following any modern HIG). No sandbox integration. i386 support dropped in v3.0.0.

2.2 Gear Lever

Aspect Status
Repository github.com/mijorus/gearlever
Toolkit GTK4 / libadwaita (Python)
Latest version v4.4.6 (February 2026)
Distribution Flatpak via Flathub (primary)
Maintenance Actively maintained, single maintainer

What it does well: Modern GNOME-native UI. Drag-and-drop AppImage integration. Clean UX for the "open this AppImage and add it to my menu" workflow. As of v4.4.6: supports configuring update URLs per AppImage, adding environment variables and CLI arguments, offline caching of the AppImage index, Arabic/RTL localization. v4.3.0 added safety checks to block updates while an app is running.

What's missing: No automatic discovery/scanning of existing AppImages across directories. No delta updates (zsync). No Wayland audit or status reporting. No FUSE compatibility management or detection. No background daemon for watching directories. No bulk operations. No orphan desktop entry cleanup. No sandboxing. Limited to GNOME - no KDE adaptations. Being a Flatpak creates a philosophical tension (Flatpak managing AppImages) and portal permission issues for filesystem access.

2.3 AppManager

Aspect Status
Repository github.com/kem-a/AppManager
Toolkit GTK4 / libadwaita (Vala)
Latest version v3.0.0 (February 2026)
Distribution AppImage, Flatpak
Maintenance Active

What it does well: macOS-style drag-and-drop installer UX. Maintains an install registry. Supports both SquashFS and DwarFS AppImage formats. Has zsync delta update support and background auto-updates. Per-app configuration (CLI args, env vars, update servers). Can extract AppImages for faster launches. Multi-language support (15+ languages).

What's missing: No directory scanning/auto-discovery. No Wayland audit. No FUSE compatibility detection or management. No background daemon for file watching. No orphan cleanup. No sandboxing or permission auditing. No bulk operations across all managed AppImages. No CLI interface for scripting.

Why this matters for Driftwood: AppManager is the closest existing tool to what Driftwood aims to be. It validates the market but leaves significant gaps - particularly Wayland awareness, FUSE management, background daemon functionality, and the "system health" perspective that Driftwood provides.

2.4 appimaged

The official appimaged daemon from the AppImage project. Watches directories for AppImages and auto-creates desktop entries. No GUI. No update support. No FUSE handling. Useful as a reference implementation for the inotify-based file watching pattern but not a user-facing solution.

2.5 AM / AppMan

Community-driven CLI tool (github.com/ivan-hc/AM) that manages AppImage installation and updates via shell scripts. Since v5 (December 2023), AM and AppMan merged into a single script with two behaviors (system-wide vs local install). Features an extensible database of 2000+ portable apps, AppImage sandboxing, drag-and-drop integration, conversion of old AppImage types, and bulk update-all functionality. Comprehensive but entirely CLI-based - no GUI.

2.6 bauh

Multi-format package manager GUI (AppImage, Flatpak, Snap, native). Jack-of-all-trades, master of none. AppImage support is surface-level - wraps basic install/remove. Qt5-based, aging UI.

2.7 Gap Summary

Feature AppImageLauncher Gear Lever AppManager appimaged AM Driftwood
Modern toolkit (GTK4/Adw) No (Qt5) Yes Yes (Vala) N/A N/A Yes (Rust)
Auto-discovery Yes No No Yes No Yes
Desktop integration Yes Yes Yes Yes Partial Yes
Delta updates (zsync) No No Yes No No Yes
Version tracking No Partial Yes No Partial Yes
Wayland audit No No No No No Yes
FUSE compat mgmt Partial (v3) No No No No Yes
Orphan cleanup No No No No No Yes
Sandboxing No No No No Yes Yes
Background daemon Partial No No Yes No Yes
KDE support Partial No No Yes N/A Yes
Self-updating AppImage No N/A Yes N/A N/A Yes
DwarFS support No No Yes No No Planned
Bundled library CVE scan No No No No No Yes
Usage tracking / disk reclaim No No No No No Yes
Orphaned config detection No No No No No Yes
Duplicate/version detection No No No No No Yes

3. Project Vision & Goals

Vision

Make AppImage a first-class citizen on modern Linux desktops - as easy to manage as Flatpak, as lightweight as the format promises, and as well-integrated as native packages. Built for the Wayland-only era that GNOME 50 and KDE Plasma 6.8 are bringing in 2026.

Goals

  1. Zero-friction onboarding. Launch Driftwood, it finds all your AppImages, shows their status, and offers to integrate them - in one click.
  2. Wayland-native, Wayland-aware. Driftwood itself runs natively on Wayland, and it tells you which of your AppImages don't. With GNOME 50 (March 2026) and KDE Plasma 6.8 (October 2026) both going Wayland-only, this information is no longer optional - it's essential.
  3. Update without re-downloading. Leverage zsync2/AppImageUpdate for delta updates where available; fall back to full re-download via GitHub Releases API or OCS where needed.
  4. Clean system, always. No orphaned .desktop files, no ghost icons, no stale entries. Driftwood owns the lifecycle.
  5. FUSE just works. Detect the FUSE situation, offer to install libfuse2, or transparently use --appimage-extract-and-run as needed.
  6. Security-conscious. Optional sandboxing via bubblewrap/firejail, permission auditing, hash verification.
  7. Itself an AppImage. Dogfooding. Driftwood is distributed as an AppImage that can update itself.

Non-Goals

  • Not a package manager. Driftwood doesn't download AppImages from a catalog/store (that's a separate concern - AM/AppMan covers it well). It manages what's already on disk.
  • Not a Flatpak/Snap replacement. No opinion on other formats. Coexists peacefully.
  • Not a sandboxing framework. Integrates with existing tools, doesn't reinvent containment.

4. Target Users

Primary

  • Intermediate Linux users who download AppImages from project websites and want them properly integrated without manual .desktop file editing.
  • Users migrating from Windows/macOS who expect a "programs list" experience.
  • Power users managing 10-50+ AppImages who need bulk operations, version tracking, and update management.

Secondary

  • System administrators deploying AppImages in managed environments (multi-user, kiosk).
  • AppImage developers testing their own packages' integration, Wayland behavior, and update metadata.

5. Architecture Overview

                    ┌──────────────────────────────────────────┐
                    │              Driftwood GUI                 │
                    │         (GTK4 / libadwaita)               │
                    │                                          │
                    │  ┌─────────┐ ┌──────────┐ ┌───────────┐ │
                    │  │ Library │ │ Details  │ │ Settings  │ │
                    │  │  View   │ │  View    │ │   View    │ │
                    │  └────┬────┘ └────┬─────┘ └─────┬─────┘ │
                    │       │           │             │        │
                    │  ┌────┴───────────┴─────────────┴─────┐  │
                    │  │         Application Controller      │  │
                    │  └────────────────┬────────────────────┘  │
                    └───────────────────┼───────────────────────┘
                                        │
                    ┌───────────────────┼───────────────────────┐
                    │          Core Services Layer              │
                    │                                           │
                    │  ┌──────────┐ ┌──────────┐ ┌───────────┐ │
                    │  │Discovery │ │ Desktop  │ │  Update   │ │
                    │  │ Engine   │ │Integrator│ │  Manager  │ │
                    │  └──────────┘ └──────────┘ └───────────┘ │
                    │  ┌──────────┐ ┌──────────┐ ┌───────────┐ │
                    │  │  FUSE    │ │ Wayland  │ │ Sandbox   │ │
                    │  │ Manager  │ │ Auditor  │ │ Manager   │ │
                    │  └──────────┘ └──────────┘ └───────────┘ │
                    │  ┌──────────┐ ┌──────────┐ ┌───────────┐ │
                    │  │ Security │ │  Usage   │ │ Duplicate │ │
                    │  │ Scanner  │ │ Tracker  │ │ Detector  │ │
                    │  └──────────┘ └──────────┘ └───────────┘ │
                    │  ┌──────────────────┐ ┌─────────────────┐ │
                    │  │ AppImage         │ │ Config/Data     │ │
                    │  │ Inspector        │ │ Tracker         │ │
                    │  │ (ELF, squashfs)  │ │ (fanotify/proc) │ │
                    │  └──────────────────┘ └─────────────────┘ │
                    └──────────────────┬────────────────────────┘
                                       │
                    ┌──────────────────┼────────────────────────┐
                    │          Data Layer                       │
                    │  ┌──────────┐ ┌──────────────────────┐   │
                    │  │ SQLite   │ │ XDG Config/Data dirs │   │
                    │  │ Database │ │ (~/.local/share/      │   │
                    │  │          │ │  driftwood/)           │   │
                    │  └──────────┘ └──────────────────────┘   │
                    └──────────────────┬────────────────────────┘
                                       │
                    ┌──────────────────┼────────────────────────┐
                    │    Background Daemon (driftwoodd)          │
                    │    - inotify file watcher                 │
                    │    - periodic update checker              │
                    │    - D-Bus interface                      │
                    │    - desktop notification sender          │
                    └───────────────────────────────────────────┘

Language Choice: Python vs C vs Rust vs Vala

Language Pros Cons Verdict
Python + PyGObject Fast dev, Gear Lever proves GTK4/Adw works, large contributor pool Slower for ELF parsing, packaging complexity Good for MVP
Vala First-class GObject/GTK support, compiles to C, GNOME ecosystem native Smaller community, fewer contributors Good for GNOME-native
Rust + gtk4-rs Memory safety, performance, growing GNOME adoption (Loupe, Glycin) Steeper contributor barrier, longer compile times Best long-term
C + GTK4 Maximum performance, traditional GNOME Memory safety burden, slow development Not recommended

Recommendation: Rust with gtk4-rs and libadwaita-rs.

Rationale:

  • GNOME is increasingly adopting Rust (Loupe image viewer, Glycin, numerous new apps). Ubuntu 26.04 LTS itself is introducing Rust-based core utilities.
  • Memory safety matters for a tool that parses untrusted ELF binaries and squashfs images
  • gtk4-rs and libadwaita-rs are mature and actively maintained (last updated February 2026)
  • Performance for file scanning, ELF parsing, and squashfs inspection is important
  • Attracts contributors from the growing Rust-for-Linux-desktop community
  • Compiles to a single binary - perfect for AppImage distribution

Note on AppManager's approach: AppManager (the closest competitor) chose Vala, which validates the "compile to native code" instinct but Rust offers stronger safety guarantees for the ELF/squashfs parsing that Driftwood does heavily.

Fallback recommendation: Python + PyGObject for a faster MVP if contributor velocity is prioritized over performance. Gear Lever proves this stack works well for GTK4/Adw apps.


6. Core Features - Detailed Design

6.1 AppImage Discovery Engine

Purpose: Find all AppImages on the system, regardless of where the user dropped them.

Default scan locations (configurable):

~/Applications/          # XDG convention for user-installed apps
~/Downloads/             # Where browsers drop files
~/.local/bin/            # Common PATH location
~/Desktop/               # Some users put apps here
/opt/appimages/          # System-wide convention (requires root)

Detection methods (layered, in order):

  1. Magic bytes check. AppImages (Type 2) contain the magic bytes AI\x02 at ELF offset. Type 1 uses ISO 9660 magic. Check the ELF header for the AppImage magic at the section header offset field.

  2. File extension. .AppImage, .appimage, .app - but don't rely on this alone; many AppImages have no extension.

  3. ELF + squashfs heuristic. If magic bytes are absent, check if the file is an ELF binary with an embedded squashfs filesystem (read the ELF section headers, look for the squashfs magic hsqs at the offset indicated by the AppImage runtime).

  4. Executable bit check. Non-executable AppImages are flagged as "discovered but not runnable" - offer to chmod +x.

Performance considerations:

  • Initial scan: parallel directory walk using tokio or rayon (Rust) / concurrent.futures (Python)
  • Subsequent updates: inotify watches on configured directories
  • Cache results in SQLite; rescan only on file change events
  • Skip known non-AppImage files based on extension blacklist (.txt, .pdf, etc.)
  • Set reasonable file size minimum (AppImages are typically > 1MB)

Discovery output (per AppImage):

struct DiscoveredAppImage {
    path: PathBuf,
    filename: String,
    appimage_type: AppImageType,      // Type1 | Type2 | Unknown
    size_bytes: u64,
    modified_time: SystemTime,
    is_executable: bool,
    sha256: Option<String>,           // computed on demand
    // Extracted from embedded .desktop / AppStream:
    app_name: Option<String>,
    app_version: Option<String>,
    app_icon: Option<Icon>,
    categories: Vec<String>,
    desktop_entry: Option<DesktopEntry>,
    update_info: Option<UpdateInfo>,
    // Runtime analysis:
    fuse_requirement: FuseRequirement, // Fuse2 | Fuse3 | ExtractAndRun
    wayland_status: Option<WaylandStatus>,
    integrated: bool,                  // has matching .desktop file
}

6.2 AppImage Inspector

Purpose: Extract all available metadata from an AppImage without running it.

Extraction methods:

For Type 2 AppImages (vast majority, squashfs-based):

  1. Read the ELF header to find the AppImage runtime offset
  2. Read the .appimage_update_info section from the ELF (update URL)
  3. Mount (via FUSE) or extract (via unsquashfs / squashfuse) the embedded filesystem
  4. Parse .desktop file from the squashfs root
  5. Extract icon (.DirIcon, or icon referenced in .desktop)
  6. Parse AppStream metadata XML if present (usr/share/metainfo/*.appdata.xml)
  7. Check for AppRun script to determine runtime behavior
  8. Detect runtime type: legacy fuse2-only runtime vs new static runtime (fuse2/fuse3 capable)

For Type 1 AppImages (legacy, ISO 9660-based):

  1. Mount as ISO or extract with bsdtar
  2. Same metadata extraction from the contained filesystem

For DwarFS-based AppImages (emerging format, used by some projects):

  1. Detect DwarFS magic bytes instead of squashfs
  2. Use dwarfsextract or dwarfs FUSE mount for inspection
  3. Same metadata extraction from the contained filesystem
  4. Note: AppManager v3.0.0 already supports DwarFS; Driftwood should match this

Inspection without mounting (preferred path for FUSE2-less systems):

  • Use --appimage-extract --appimage-offset to find the squashfs offset
  • Use the squashfs-tools library or unsquashfs to list/extract specific files without full mount
  • Extract only: .desktop, icon, *.appdata.xml, AppRun - no need to extract the full payload

6.3 Library View (Main Window)

The primary UI. Shows all managed AppImages in a grid or list.

Per-AppImage card/row displays:

  • App icon (extracted from the AppImage)
  • App name (from .desktop Name= field)
  • Version (from AppStream, .desktop, or filename heuristic)
  • File size
  • Location on disk
  • Integration status: integrated / not integrated / orphaned entry
  • Update status: up to date / update available / unknown / checking
  • Wayland status badge: Native Wayland / XWayland / Unknown
  • FUSE status badge: OK / needs libfuse2 / using extract-and-run

Sorting/filtering:

  • By name, size, date added, date modified, update status
  • Filter by: integrated/not, has updates, Wayland status, category

Bulk operations:

  • Select multiple → integrate all / remove all / update all
  • "Integrate all discovered" one-click action

6.4 Detail View

Opened by clicking an AppImage in the library. Full information panel:

Sections:

  1. Identity

    • Name, version, description (from AppStream)
    • Icon (large)
    • Categories, keywords
    • Developer/maintainer info (from AppStream)
  2. File Info

    • Full path
    • File size
    • SHA256 hash (computed, with copy button)
    • AppImage type (1 or 2)
    • Created/modified timestamps
    • Permissions (executable bit)
  3. Desktop Integration

    • Status: integrated / not integrated
    • .desktop file path (if integrated)
    • Icon installation path
    • "Integrate" / "Remove integration" button
    • Preview of the generated .desktop file
  4. Runtime Compatibility

    • FUSE status with explanation and fix button
    • Wayland status with detailed explanation
    • Libraries bundled vs system (if detectable)
    • Architecture (x86_64, aarch64)
  5. Updates

    • Current version
    • Update information type (zsync, GitHub releases, OCS, none)
    • Update URL
    • Last checked timestamp
    • "Check now" / "Update" buttons
    • Update history
  6. Security & Library Audit

    • Signature status (if AppImage is signed)
    • GPG key info
    • Sandbox status: not sandboxed / firejail / bubblewrap
    • Permissions audit (what the AppImage can access)
    • "Run sandboxed" toggle
    • Bundled library CVE summary (critical/high/medium counts)
    • Expandable list of flagged libraries with CVE details
    • "Scan now" button to refresh CVE check
    • Comparison: bundled library version vs system version
  7. Usage & Disk Footprint

    • Last launched date/time
    • Launch count (total, last 30 days)
    • AppImage file size
    • Config data size (in ~/.config/)
    • Application data size (in ~/.local/share/)
    • Cache size (in ~/.cache/)
    • Total footprint (file + config + data + cache)
    • "This AppImage hasn't been used in N months" warning if stale
  8. Duplicates

    • "2 other versions of this app found" warning if applicable
    • Links to the other copies
    • "Keep this, remove others" quick action

6.5 Settings View

General:

  • Scan directories (add/remove paths)
  • Default AppImage storage location (default: ~/Applications)
  • Move AppImages to storage location on integration (toggle)
  • Language
  • Theme (follow system / light / dark)

Integration:

  • Auto-integrate newly discovered AppImages (toggle, default: off - ask first)
  • Desktop entry template customization
  • Custom icon theme integration
  • Auto-cleanup orphaned entries (toggle, default: on)

Updates:

  • Check frequency (never / daily / weekly / on launch)
  • Auto-update (toggle, default: off - notify only)
  • Keep old versions on update (toggle)
  • Number of old versions to keep

FUSE:

  • Preferred FUSE strategy: native mount / extract-and-run / auto-detect
  • Show "install libfuse2" prompts (toggle)

Sandboxing:

  • Default sandbox backend: none / firejail / bubblewrap
  • Default permission profile: permissive / standard / strict
  • Per-app override capability

Security Scanner:

  • CVE database update frequency (weekly / daily / manual)
  • Notification threshold (critical only / high+ / medium+ / all)
  • Show system vs bundled version comparisons (toggle)

Usage & Cleanup:

  • Enable launch tracking (toggle, default: on)
  • Stale threshold (1 month / 3 months / 6 months / 1 year)
  • Show disk usage in library view (toggle)
  • Track config/data paths via fanotify (toggle, default: on)
  • Backup config before cleanup (toggle, default: on)

Duplicates:

  • Auto-detect duplicates on scan (toggle, default: on)
  • Auto-clean old versions on update (toggle, default: off - prompt instead)
  • Number of old versions to keep when auto-cleaning

Daemon:

  • Enable background daemon (toggle)
  • Start on login (toggle)
  • Notification preferences

7. UI/UX Design

7.1 Design Language

Follow GNOME Human Interface Guidelines (HIG) strictly. Target libadwaita 1.7+ (GNOME 48) with an eye toward 1.8 (GNOME 50):

  • Use AdwApplicationWindow with AdwHeaderBar
  • Use AdwNavigationView for main → detail navigation
  • Use AdwPreferencesWindow for settings
  • Use AdwStatusPage for empty states
  • Use AdwToastOverlay for transient notifications
  • Use AdwBanner for persistent warnings (e.g., "FUSE2 not installed")
  • Use AdwDialog for modal interactions (libadwaita 1.5+)
  • Respect the new Adwaita Sans / Adwaita Mono default fonts (GNOME 48+, replacing Cantarell / Source Code Pro)

7.2 Key Screens

Empty State (First Launch)

┌─────────────────────────────────────────┐
│ ◀  Driftwood                    ☰  ⚙    │
├─────────────────────────────────────────┤
│                                         │
│          [Driftwood icon]                │
│                                         │
│      No AppImages Found                 │
│                                         │
│  Driftwood will look for AppImages in    │
│  ~/Applications and ~/Downloads.        │
│                                         │
│  Drop an AppImage here or configure     │
│  scan locations in Settings.            │
│                                         │
│        [ Scan Now ]  [ Settings ]       │
│                                         │
└─────────────────────────────────────────┘

Library View (Populated)

┌─────────────────────────────────────────┐
│ ◀  Driftwood (12 apps)       🔍  ☰  ⚙  │
├─────────────────────────────────────────┤
│ ⚠ 3 updates available        [Update All│
├─────────────────────────────────────────┤
│                                         │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐   │
│ │  [icon]  │ │  [icon]  │ │  [icon]  │   │
│ │ Firefox  │ │ Kdenlive │ │ Inkscape │   │
│ │ v124.0   │ │ v24.02   │ │ v1.3.2   │   │
│ │ 🟢 Wayl. │ │ 🟡 XWay. │ │ 🟢 Wayl. │   │
│ │ ⬆ Update │ │ ✓ Latest │ │ ⬆ Update │   │
│ └─────────┘ └─────────┘ └─────────┘   │
│                                         │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐   │
│ │  [icon]  │ │  [icon]  │ │  [icon]  │   │
│ │ Krita    │ │ Blender  │ │ MuseScore│   │
│ │ v5.2.2   │ │ v4.1     │ │ v4.2     │   │
│ │ 🟡 XWay. │ │ 🟢 Wayl. │ │ 🔴 X11   │   │
│ │ ✓ Latest │ │ ⬆ Update │ │ ✓ Latest │   │
│ └─────────┘ └─────────┘ └─────────┘   │
│                                         │
└─────────────────────────────────────────┘

Detail View

┌─────────────────────────────────────────┐
│ ◁ Back     Kdenlive              ⋮      │
├─────────────────────────────────────────┤
│                                         │
│   [large icon]  Kdenlive                │
│                 Video Editor             │
│                 Version 24.02.1          │
│                 KDE Community            │
│                                         │
│   [ Launch ]  [ Launch Sandboxed ]      │
│                                         │
├─────────────────────────────────────────┤
│ Desktop Integration              [  ✓ ] │
│   ~/.local/share/.../kdenlive.desktop   │
│                                         │
│ Wayland Status                          │
│   🟡 Running via XWayland               │
│   Bundled Qt 5.15 - no native Wayland   │
│   protocol support detected.            │
│                                         │
│ FUSE Status                             │
│   🟢 Using system libfuse2              │
│                                         │
│ Updates                                 │
│    zsync update info embedded          │
│   Last checked: 2 hours ago             │
│   [ Check for Updates ]                 │
│                                         │
│ File Details                            │
│   Path: ~/Applications/Kdenlive.App...  │
│   Size: 287 MB                          │
│   SHA256: a3f2e1...  [Copy]             │
│   Type: AppImage Type 2 (squashfs)      │
│                                         │
└─────────────────────────────────────────┘

7.3 Notification & Warning Design

AdwBanner (persistent, top of library):

  • "libfuse2 is not installed. Some AppImages may not launch. [Install] [Use extract-and-run]"
  • "3 orphaned desktop entries found. [Clean up]"
  • "Background daemon is not running. [Enable]"

AdwToast (transient, bottom):

  • "Firefox updated to v125.0"
  • "Desktop entry created for Inkscape"
  • "Removed 3 orphaned entries"

System notifications (via daemon):

  • "2 AppImage updates available" (clicking opens Driftwood)
  • "New AppImage discovered in ~/Downloads"

7.4 Drag and Drop

The main window accepts AppImage files via drag and drop:

  1. User drags .AppImage file onto window
  2. Drop zone highlights
  3. On drop: inspect → show detail view → offer integration
  4. Optionally move to ~/Applications on integration

8. Technical Stack & Dependencies

Build System

meson.build             # Primary build system
├── src/
│   ├── meson.build
│   ├── main.rs
│   ├── application.rs
│   ├── window.rs
│   ├── core/
│   │   ├── discovery.rs
│   │   ├── inspector.rs
│   │   ├── integrator.rs
│   │   ├── updater.rs
│   │   ├── fuse.rs
│   │   ├── wayland.rs
│   │   ├── sandbox.rs
│   │   ├── security_scanner.rs
│   │   ├── usage_tracker.rs
│   │   ├── config_tracker.rs
│   │   ├── duplicate_detector.rs
│   │   ├── cve_database.rs
│   │   └── database.rs
│   ├── ui/
│   │   ├── library_view.rs
│   │   ├── detail_view.rs
│   │   ├── settings_view.rs
│   │   ├── app_card.rs
│   │   └── widgets/
│   ├── daemon/
│   │   ├── main.rs
│   │   ├── watcher.rs
│   │   ├── update_checker.rs
│   │   └── dbus_interface.rs
│   └── cli/
│       └── main.rs
├── data/
│   ├── icons/
│   ├── driftwood.desktop
│   ├── driftwood.metainfo.xml
│   ├── driftwood.gschema.xml
│   └── resources.gresource.xml
├── po/                 # Translations
└── tests/

Dependencies

Build-time:

  • Rust toolchain (stable, latest)
  • Meson >= 0.64
  • gtk4 >= 4.16 (GNOME 48+)
  • libadwaita >= 1.7 (GNOME 48+; target 1.8 for GNOME 50 features)
  • SQLite3
  • gettext (i18n)

Runtime (required):

  • GTK4, libadwaita (linked)
  • SQLite3
  • unsquashfs or squashfs-tools (for AppImage inspection without FUSE)
  • D-Bus (for daemon communication)

Runtime (optional, for full functionality):

  • libfuse2 or libfuse3 (for AppImage mounting)
  • zsync2 or libzsync (for delta updates)
  • firejail or bubblewrap (for sandboxing)
  • libnotify / notification daemon (for system notifications)
  • gpg / gpgme (for signature verification)

GSettings Schema

<schemalist>
  <schema id="io.github.driftwood" path="/io/github/driftwood/">
    <key name="scan-directories" type="as">
      <default>['~/Applications', '~/Downloads']</default>
    </key>
    <key name="storage-directory" type="s">
      <default>'~/Applications'</default>
    </key>
    <key name="move-on-integrate" type="b">
      <default>true</default>
    </key>
    <key name="auto-integrate" type="b">
      <default>false</default>
    </key>
    <key name="update-check-interval" type="s">
      <default>'weekly'</default>
    </key>
    <key name="auto-update" type="b">
      <default>false</default>
    </key>
    <key name="keep-old-versions" type="b">
      <default>false</default>
    </key>
    <key name="fuse-strategy" type="s">
      <default>'auto'</default>
    </key>
    <key name="sandbox-backend" type="s">
      <default>'none'</default>
    </key>
    <key name="daemon-enabled" type="b">
      <default>true</default>
    </key>
    <key name="daemon-autostart" type="b">
      <default>true</default>
    </key>
    <key name="cve-scan-interval" type="s">
      <default>'weekly'</default>
    </key>
    <key name="cve-notify-threshold" type="s">
      <default>'high'</default>
    </key>
    <key name="launch-tracking-enabled" type="b">
      <default>true</default>
    </key>
    <key name="stale-threshold-days" type="i">
      <default>90</default>
    </key>
    <key name="config-tracking-enabled" type="b">
      <default>true</default>
    </key>
    <key name="backup-config-on-cleanup" type="b">
      <default>true</default>
    </key>
    <key name="auto-detect-duplicates" type="b">
      <default>true</default>
    </key>
    <key name="auto-clean-old-versions" type="b">
      <default>false</default>
    </key>
    <key name="old-versions-to-keep" type="i">
      <default>1</default>
    </key>
  </schema>
</schemalist>

9. AppImage Internals & Integration Points

9.1 AppImage Type 2 Structure (Current Standard)

┌─────────────────────────────────────┐
│ ELF Runtime Binary                  │  ← Executable header
│   - Contains AppImage magic AI\x02  │
│   - Contains update info in ELF     │
│     section .upd_info               │
│   - Handles FUSE mounting           │
│   - Offset to squashfs payload      │
├─────────────────────────────────────┤
│ SquashFS Filesystem                 │  ← Compressed payload
│   ├── AppRun                        │  ← Entry point script/binary
│   ├── *.desktop                     │  ← Desktop entry (exactly one)
│   ├── *.png / *.svg                 │  ← Application icon
│   ├── usr/                          │  ← App files
│   │   ├── bin/
│   │   ├── lib/
│   │   └── share/
│   │       ├── applications/
│   │       ├── icons/
│   │       └── metainfo/             │  ← AppStream metadata
│   │           └── *.appdata.xml
│   └── .DirIcon                      │  ← Fallback icon
└─────────────────────────────────────┘

9.2 Desktop Entry Generation

When integrating an AppImage, Driftwood generates a .desktop file:

Location: ~/.local/share/applications/driftwood-{app_id}.desktop

Template:

[Desktop Entry]
Type=Application
Name={app_name}
Exec={appimage_path} %U
Icon={icon_path}
Categories={categories}
Comment={comment}
Terminal=false
X-AppImage-Path={appimage_path}
X-AppImage-Version={version}
X-AppImage-Managed-By=Driftwood
X-AppImage-Integrated-Date={iso_date}

The X-AppImage-* fields are crucial - they let Driftwood:

  • Identify entries it created vs user-created ones
  • Detect orphans when the AppImage file is deleted
  • Track version for update comparison

Icon installation:

  • Extract icon from AppImage
  • Install to ~/.local/share/icons/hicolor/{size}x{size}/apps/driftwood-{app_id}.png
  • Support SVG icons in scalable/apps/ when available
  • Run gtk-update-icon-cache or xdg-icon-resource as needed

9.3 Orphan Detection

An orphaned desktop entry is one where:

  1. The .desktop file contains X-AppImage-Managed-By=Driftwood
  2. The X-AppImage-Path points to a file that no longer exists

Cleanup actions:

  • Remove the .desktop file
  • Remove the installed icon files
  • Run update-desktop-database ~/.local/share/applications/
  • Log the cleanup action

Scan trigger:

  • On every Driftwood launch
  • Periodically via daemon
  • On user request

10. FUSE Compatibility Layer

10.1 The Problem

AppImage Type 2 uses FUSE to mount the embedded squashfs at runtime. This requires:

  • libfuse2 on the system (for most existing AppImages compiled against fuse2)
  • The fusermount binary
  • The /dev/fuse device node (usually present)

Ubuntu 22.04+ removed libfuse2 from the default install. Ubuntu 24.04 renamed the package to libfuse2t64 (64-bit time_t transition). libfuse3 is available but is not ABI-compatible - AppImages compiled against fuse2 cannot use fuse3. The two can coexist, but users don't know this.

The new type2-runtime (updated January 2026) attempts to support both fuse2 and fuse3, preferring fuse3 when available. However, it has known compatibility issues with stable versions of AppImageLauncher - creating a fragmented situation where upgrading one tool can break another.

10.2 Detection Logic

function detect_fuse_status():
    has_fuse2 = check_library("libfuse.so.2")
    has_fuse3 = check_library("libfuse3.so.3")
    has_fusermount = which("fusermount") or which("fusermount3")
    has_dev_fuse = exists("/dev/fuse")

    if has_fuse2 and has_fusermount and has_dev_fuse:
        return FuseStatus::FullyFunctional
    elif has_fuse3 and not has_fuse2:
        return FuseStatus::Fuse3Only  // Most AppImages won't work
    elif not has_fusermount:
        return FuseStatus::NoFusermount
    elif not has_dev_fuse:
        return FuseStatus::NoDevFuse  // Container/WSL issue
    else:
        return FuseStatus::MissingLibfuse2

10.3 Resolution Strategies

Strategy 1: Install libfuse2 (preferred)

  • Detect distro via /etc/os-release
  • Show the correct install command:
    • Ubuntu 24.04+/Debian testing+: sudo apt install libfuse2t64 (renamed package)
    • Ubuntu 22.04/Debian 12: sudo apt install libfuse2
    • Fedora: sudo dnf install fuse-libs (usually present)
    • Arch: sudo pacman -S fuse2
    • openSUSE: sudo zypper install libfuse2
  • Offer to run the command via pkexec (polkit elevation)

Strategy 2: Extract-and-run (transparent fallback)

  • When FUSE is unavailable, launch AppImages with --appimage-extract-and-run
  • This extracts to a temp directory and runs from there
  • Slower startup, uses more temp disk space
  • Driftwood handles this transparently - user sees "Running in compatibility mode"

Strategy 3: New static runtime (already shipping)

  • The new type2-runtime (updated January 2026) statically links libfuse and can use either fuse2 or fuse3, preferring fuse3 when available
  • AppImages built with recent appimagetool use this runtime by default
  • Driftwood should detect which runtime an AppImage uses and report it
  • Driftwood could offer to re-pack older AppImages with the new runtime (advanced feature, Phase 3+)
  • Caveat: The new runtime has known compatibility issues with AppImageLauncher stable (v2.2.0) - Driftwood should warn about this if AppImageLauncher is detected on the system

10.4 Per-AppImage FUSE Status

Each AppImage in the library shows one of:

  • 🟢 Native FUSE mount - libfuse2/fuse3 available, working normally
  • 🟢 Static runtime - uses new type2-runtime with embedded FUSE, works with fuse2 or fuse3
  • 🟡 Extract-and-run mode - working but with slower startup
  • 🔴 Cannot launch - FUSE unavailable and extract-and-run failed
  • ⚠️ AppImageLauncher conflict - new static runtime detected but AppImageLauncher v2.2.0 is installed (known compatibility issue)

11. Wayland Awareness Engine

11.1 Why This Matters - The Wayland-Only Era (2026)

The Linux desktop has crossed the Wayland tipping point. As of early 2026:

  • GNOME 49 (September 2025): X11 session disabled by default
  • GNOME 50 (March 18, 2026): X11 backend code completely removed from Mutter and GNOME Shell. Wayland-only.
  • Ubuntu 25.10: Dropped GNOME-on-Xorg entirely
  • Ubuntu 26.04 LTS (April 2026): Will ship GNOME 50 - Wayland-only, with XWayland for legacy apps
  • KDE Plasma 6.8 (October 2026): Dropping the X11 session, going Wayland-only. X11 session supported until early 2027 via Plasma 6.7.
  • 79% of KDE Plasma 6 users are already on Wayland
  • Over 60% of all Linux desktops use Wayland by default

This means XWayland is no longer a "fallback" - it's the only way X11-toolkit apps can run on mainstream desktops. Applications running under XWayland experience:

  • Blurry rendering on HiDPI/fractional scaling (GNOME 50 includes initial stable fractional scaling and VRR support, making the gap more visible)
  • Broken or inconsistent clipboard behavior
  • No smooth touchpad gestures
  • Potential security issues (X11 allows keylogging between XWayland apps)
  • Higher memory/CPU usage
  • Missing Wayland features (per-monitor scaling, direct scanout, HDR)

Users currently have no way to know which of their AppImages run native Wayland vs XWayland without manually checking. With X11 sessions disappearing entirely, this is no longer a nice-to-have - it's critical information. Driftwood provides it.

11.2 Detection Methods

Method 1: Static analysis (pre-launch, preferred)

Inspect the bundled libraries inside the AppImage:

Libraries that indicate Wayland support:
  libwayland-client.so.*     → Direct Wayland protocol
  libGdk-4.0.so.*            → GTK4 (Wayland-native by default)
  libgtk-4.so.*              → GTK4
  libQt6WaylandClient.so.*   → Qt6 Wayland platform plugin
  libQt5WaylandClient.so.*   → Qt5 with Wayland plugin

Libraries that indicate X11-only:
  libGdk-3.0.so.* WITHOUT libwayland  → GTK3 without Wayland
  libgtk-x11-*.so.*         → GTK2 (X11 only)
  libQt5X11Extras.so.*      → Qt5 X11-specific

Presence of Wayland platform plugins:
  usr/lib/*/qt5/plugins/platforms/libqwayland*.so
  usr/lib/*/qt6/plugins/platforms/libqwayland*.so

Electron-based apps (version matters significantly):
  Electron >= 38 (Sept 2025+): --ozone-platform=auto is DEFAULT, native Wayland
  Electron 28-37: Look for --ozone-platform-hint=auto in wrapper scripts
  Electron < 28: X11 only or buggy Wayland

Method 2: Runtime detection (post-launch)

After launching an AppImage, check its Wayland status:

# Check the process's environment
cat /proc/{pid}/environ | tr '\0' '\n' | grep -E '^(WAYLAND_DISPLAY|GDK_BACKEND|QT_QPA_PLATFORM|XDG_SESSION_TYPE)'

# Check if the process has an X11 connection
ls -la /proc/{pid}/fd/ | grep -c '.X11-unix'

# Use xdotool/xprop to check if window appears in X11
xdotool search --pid {pid}  # If found, it's using X11/XWayland

# Check via compositor protocol
# GNOME: org.gnome.Shell.Introspect D-Bus interface
# KDE: org.kde.KWin.Windows D-Bus interface

Method 3: Toolkit version heuristic

Toolkit Wayland Status
GTK4 Native Wayland (default)
GTK3 >= 3.22 Native Wayland (if GDK_BACKEND not forced to x11)
GTK2 X11 only (forever)
Qt6 + QtWayland Native Wayland
Qt5 + QtWayland Native Wayland (if platform plugin present)
Qt5 without QtWayland XWayland fallback
Electron >= 38 Native Wayland by default (--ozone-platform=auto is now the default as of Electron 38.0, September 2025)
Electron 28-37 Native Wayland with --ozone-platform-hint=auto flag
Electron 12-27 Wayland possible with flags but buggy
Electron < 12 X11 only
Java/Swing/JavaFX X11 only (as of early 2026 - Wakefield project for Wayland support is still experimental)
wxWidgets (GTK3) Depends on GTK3 backend
Flutter (Linux) Native Wayland (GTK backend)

11.3 Wayland Status Display

Each AppImage gets a Wayland badge:

  • 🟢 Native Wayland - detected Wayland-capable toolkit and platform plugins
  • 🟡 XWayland - will run but via X11 compatibility layer
  • 🟠 Wayland possible - toolkit supports it but plugins may be missing or env vars needed
  • 🔴 X11 only - toolkit has no Wayland path
  • Unknown - couldn't determine (uncommon toolkit, static binary)

For 🟡 and 🟠 apps, Driftwood can suggest or apply fixes:

  • Set GDK_BACKEND=wayland for GTK3 apps
  • Set QT_QPA_PLATFORM=wayland for Qt5 apps with QtWayland present
  • Add --ozone-platform-hint=auto for Electron apps
  • Show a warning that these are per-app overrides and may cause issues

11.4 Session Detection

Driftwood itself needs to know the session type:

fn detect_session_type() -> SessionType {
    if env::var("WAYLAND_DISPLAY").is_ok() {
        if env::var("DISPLAY").is_ok() {
            SessionType::WaylandWithXWayland
        } else {
            SessionType::PureWayland
        }
    } else if env::var("DISPLAY").is_ok() {
        SessionType::X11
    } else {
        SessionType::Unknown
    }
}

On the few remaining X11 sessions (Linux Mint Cinnamon, some legacy setups), the Wayland audit is still useful - with GNOME 50 and KDE Plasma 6.8 going Wayland-only in 2026, users will be forced to switch soon. Badges should note "You're currently on X11 - Wayland status will matter when your desktop upgrades to Wayland-only."


12. Update System

12.1 Update Information Spec

AppImages can embed update information in the ELF binary's .upd_info section. Driftwood reads this to determine how to check for and download updates.

Supported update info formats:

Type Format Example
zsync zsync|{url} zsync|https://example.com/app-latest-x86_64.AppImage.zsync
GitHub Releases gh-releases-zsync|{owner}|{repo}|{release_tag}|{filename_pattern} gh-releases-zsync|user|repo|latest|*x86_64.AppImage.zsync
GitLab Releases gl-releases-zsync|{host}|{owner}|{repo}|{release_tag}|{filename_pattern} Similar to GitHub
OCS ocs-v1-appimagehub-direct|{url} For AppImageHub-listed apps
bintray bintray-zsync|... Deprecated (Bintray shut down)

12.2 Update Check Flow

1. Read update info from ELF section
2. Based on type:
   a. zsync: HEAD request to zsync URL, compare SHA headers
   b. GitHub: Query GitHub Releases API for latest release
      - GET https://api.github.com/repos/{owner}/{repo}/releases/latest
      - Compare tag/version against current
   c. GitLab: Similar via GitLab API
   d. OCS: Query OCS endpoint
3. If update available:
   a. Record new version, download URL, file size
   b. Show notification / badge in library
   c. Compute delta size if zsync available
4. Store result in database with timestamp

12.3 Delta Update (zsync2)

The zsync protocol enables downloading only the changed blocks of an AppImage:

  1. Download the .zsync control file (small, contains block checksums)
  2. Compare checksums against the local AppImage's blocks
  3. Download only the changed blocks
  4. Reconstruct the new AppImage from local blocks + downloaded deltas
  5. Verify full-file checksum
  6. Replace old AppImage with new one

Typical savings: 60-90% bandwidth reduction for minor version updates.

Implementation options:

  • Shell out to AppImageUpdate binary (simplest, if installed)
  • Shell out to zsync2 binary
  • Use libappimageupdate as a library (C API)
  • Implement zsync protocol in Rust (most work, most control)

Recommended: Shell out to AppImageUpdate (latest release October 2025) with fallback to full download. zsync2 v2.0.0-alpha (September 2025) is stabilizing but still alpha quality - the API is usable but debugging is ongoing. Shelling out provides isolation from upstream instability.

12.4 Full Download Fallback

When zsync is not available or the AppImage has no update info:

  1. For GitHub-hosted AppImages: use GitHub Releases API to find the latest .AppImage asset
  2. Download to a temporary location
  3. Verify (checksum if available, at minimum check it's a valid AppImage)
  4. Replace old file (atomic rename)
  5. Re-extract desktop entry and icon (version may have changed)
  6. Update database

12.5 Update UI

In library view:

  • Badge on app cards: "⬆ Update available"
  • Banner: "N updates available [Update All]"

Update dialog:

┌────────────────────────────────────────┐
│ Update Available                       │
├────────────────────────────────────────┤
│                                        │
│ Firefox 124.0 → 125.0                  │
│                                        │
│ Update size: ~45 MB (delta)            │
│ Full size: 287 MB                      │
│ Method: zsync (GitHub Releases)        │
│                                        │
│ ☐ Keep old version                     │
│                                        │
│      [ Cancel ]  [ Update ]            │
│                                        │
└────────────────────────────────────────┘

During update:

  • Progress bar with speed and ETA
  • Cancel button
  • "Updated successfully" toast on completion

13. Sandboxing & Permissions

13.1 Rationale

AppImages are unsigned, unreviewed binaries from the internet. Unlike Flatpak (which has a permissions model) or Snap (which has AppArmor confinement), AppImages run with the user's full permissions by default. Driftwood offers optional sandboxing to mitigate this.

13.2 Backend Options

Firejail:

  • Mature, widely packaged (v0.9.78 released January 2026, with emergency fix for GTK library compatibility changes)
  • Pre-built profiles for many applications
  • Simple wrapper: firejail --appimage /path/to/app.AppImage
  • The --appimage flag handles the FUSE mount correctly inside the sandbox
  • Good default choice

Bubblewrap (bwrap):

  • Lower-level, more flexible
  • Used internally by Flatpak
  • Requires more configuration per app
  • Better for custom permission profiles

Driftwood approach:

  1. Default: no sandbox (matches current AppImage behavior)
  2. One-click "Run sandboxed" using firejail (if installed) with default profile
  3. Custom profiles per app (advanced, stored in ~/.config/driftwood/sandbox/)
  4. Show what a sandbox would restrict (permission audit view)

13.3 Permission Audit

Even without sandboxing, Driftwood can show what an AppImage could access:

Potential Access:
  ✓ Full home directory read/write
  ✓ Network access (unrestricted)
  ✓ X11 display (if running via XWayland - enables keylogging)
  ✓ D-Bus session bus
  ✓ All mounted filesystems
  ✓ GPU acceleration
  ⚠ No Wayland isolation (X11 clients can see each other)

This is informational - educating users about the security model they're operating under.

13.4 Firejail Profile Generation

For apps without a pre-existing firejail profile, Driftwood can generate a basic one:

# Auto-generated by Driftwood for {app_name}
include disable-common.inc
include disable-devel.inc
include disable-exec.inc
include disable-interpreters.inc
include disable-programs.inc

whitelist ${HOME}/Documents
whitelist ${HOME}/Downloads
whitelist ${HOME}/Pictures

caps.drop all
netfilter
no3d
nodvd
nogroups
nonewprivs
noroot
nosound
notv
nou2f
novideo
protocol unix,inet,inet6
seccomp

Users can then customize from this starting point.


14. Bundled Library Security Scanner

14.1 The Problem

AppImages are self-contained - they bundle their own copies of libraries like OpenSSL, libcurl, zlib, libwebp, GnuTLS, SQLite, libpng, libjpeg, and dozens more. When a CVE is discovered in one of these libraries, your system package manager patches the system copy within hours or days. But the copy inside the AppImage never gets patched unless the AppImage developer rebuilds and re-releases.

This means users can be running applications with known-vulnerable OpenSSL, known-exploitable libwebp (remember CVE-2023-4863, the zero-click WebP exploit that hit Chrome/Firefox/Signal?), or ancient zlib with buffer overflows - and have absolutely no visibility into it.

No existing AppImage tool inspects bundled libraries for known vulnerabilities. Driftwood does.

14.2 How It Works

Phase 1: Library inventory extraction

When Driftwood inspects an AppImage (during discovery or on demand), it inventories the bundled shared libraries:

1. Extract or mount the AppImage's squashfs/dwarfs payload
2. Walk the filesystem looking for:
   - usr/lib/**/*.so*
   - usr/lib64/**/*.so*
   - lib/**/*.so*
   - Any .so files in the top-level directory
3. For each shared library found:
   a. Read the ELF SONAME from the dynamic section
   b. Extract version strings using heuristics:
      - SONAME version suffix (libssl.so.1.1 → OpenSSL 1.1.x)
      - .rodata section string scanning for version patterns
        (e.g., "OpenSSL 1.1.1k  25 Mar 2021")
      - Symbol version tables (OPENSSL_1_1_1 version tag)
      - pkg-config .pc files if bundled (usr/lib/pkgconfig/)
   c. Record: library name, SONAME, detected version, file path, file size

Output per AppImage:

struct BundledLibrary {
    soname: String,          // e.g., "libssl.so.1.1"
    detected_name: String,   // e.g., "OpenSSL"
    detected_version: String,// e.g., "1.1.1k"
    file_path: String,       // e.g., "usr/lib/x86_64-linux-gnu/libssl.so.1.1"
    file_size: u64,
    cve_matches: Vec<CveMatch>,
}

Phase 2: CVE matching

Driftwood maintains a local vulnerability database, updated periodically:

1. Data sources (fetched by daemon or on-demand):
   a. NVD (National Vulnerability Database) JSON feeds
      - https://nvd.nist.gov/feeds/json/cve/1.1/
      - Filtered to Linux-relevant libraries only
   b. OSV (Open Source Vulnerabilities) database
      - https://osv.dev/ - structured, easy to query
      - Has a simple REST API and downloadable dumps
   c. Debian Security Tracker data
      - Maps CVEs to specific library versions
      - Already has the "which versions are affected" mappings done

2. Local database structure:
   - Table of known libraries (name, CPE identifier, version ranges)
   - Table of CVEs with affected version ranges and severity (CVSS)
   - Updated weekly (or daily if user opts in)
   - Stored in SQLite alongside the main Driftwood database
   - Total size estimate: 5-15 MB compressed

3. Matching algorithm:
   For each bundled library:
   a. Normalize the library name to a CPE product name
      (libssl.so → openssl, libcurl.so → curl, libz.so → zlib)
   b. Look up all CVEs affecting that product
   c. Check if the detected version falls within the affected range
   d. Record matching CVEs with severity and description

Phase 3: Reporting

Per-AppImage security report in the detail view:

Security Audit - Kdenlive 24.02.1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 2 Critical  🟠 5 High  🟡 3 Medium

Libraries scanned: 47
Libraries with known CVEs: 4

  🔴 OpenSSL 1.1.1k (bundled)
     System has: 3.0.13
     CVE-2024-0727 (Critical, CVSS 9.8) - PKCS12 decoding crash
     CVE-2023-5678 (High, CVSS 7.5) - Excessive time in DH check
     + 3 more

  🟠 libcurl 7.81.0 (bundled)
     System has: 8.5.0
     CVE-2023-38545 (High, CVSS 7.5) - SOCKS5 heap overflow
     + 2 more

  🟢 zlib 1.2.13 (bundled) - No known CVEs
  🟢 libpng 1.6.39 (bundled) - No known CVEs

Recommendation: Update this AppImage to the latest version.
The developer may have rebuilt with patched libraries.

14.3 Key Libraries to Track

These are the highest-value targets for CVE scanning - libraries that are frequently bundled in AppImages and have regular security-critical updates:

Library Why it matters
OpenSSL / LibreSSL / GnuTLS TLS/crypto - CVEs can be catastrophic
libcurl HTTP client - SOCKS5, redirect, and auth vulns
zlib / zlib-ng Compression - buffer overflows
libwebp Image decoding - CVE-2023-4863 was a zero-click RCE
libpng / libjpeg-turbo Image codecs - parsing vulnerabilities
SQLite Database - query injection, overflow
libxml2 / libxslt XML parsing - XXE, buffer overflows
FFmpeg / libav* Media codecs - extremely frequent CVEs
GLib / GIO GNOME foundation - occasional vulns
Qt5 / Qt6 core libs Widget toolkit - rendering, input handling vulns
Electron / Chromium (libcef) If bundled: inherits all Chromium CVEs
Python / Node.js If bundled as runtime: interpreter-level CVEs

14.4 Comparison with System Libraries

A particularly useful feature: show the user what version their system has vs what the AppImage bundles:

Library       AppImage    System      Status
OpenSSL       1.1.1k      3.0.13      ⚠ AppImage is 3 major versions behind
libcurl       7.81.0      8.5.0       ⚠ AppImage is behind
zlib          1.2.13      1.3.1        Minor version difference
libpng        1.6.39      1.6.43       Patch version difference

This immediately tells the user: "your system is patched, but the AppImage is running its own old, unpatched copy."

14.5 Severity Thresholds and Notifications

  • Critical (CVSS >= 9.0): Red badge in library view. Desktop notification via daemon. Prominent banner in detail view.
  • High (CVSS >= 7.0): Orange badge. Included in periodic notifications.
  • Medium (CVSS >= 4.0): Yellow indicator in detail view only.
  • Low (CVSS < 4.0): Listed in full report, no badges.

Users can configure notification thresholds in settings.

14.6 Limitations and Honest Framing

This feature must be honest about what it can and cannot do:

  • It cannot guarantee completeness. Version detection from binary inspection is heuristic. Some libraries don't embed version strings.
  • It cannot tell you if a CVE is actually exploitable in the context of how the AppImage uses the library. A CVE in OpenSSL's PKCS12 parsing doesn't matter if the app never touches PKCS12.
  • It's informational, not blocking. Driftwood never prevents you from launching an AppImage. It informs you of the risk.
  • The fix is upstream. Driftwood can't patch the libraries inside an AppImage. It can only tell you to update the AppImage (which may or may not include patched libraries) or contact the developer.

This is framed as: "Here's what you should know about what's inside this binary you downloaded from the internet."


15. Usage Tracking & Disk Space Reclamation

15.1 The Problem

AppImages are big. A typical Electron app is 150-300 MB. Media apps like Kdenlive or Blender can be 300-500 MB. Users accumulate these over time, forget about them, and end up with gigabytes of AppImages they never launch sitting in ~/Applications or ~/Downloads.

Unlike Flatpak (which has flatpak uninstall --unused) or system packages (which have apt autoremove), there's no tooling to identify stale, unused AppImages.

15.2 How It Works

Launch tracking:

Driftwood wraps AppImage launches (when launched via its desktop entries or directly through Driftwood) and records usage:

1. When Driftwood creates a .desktop entry, the Exec= line goes through Driftwood:
     Exec=driftwood launch /path/to/App.AppImage %U

   The "driftwood launch" subcommand:
   a. Records a launch event (timestamp, AppImage path) in the database
   b. Immediately exec()s the actual AppImage (zero overhead after the DB write)
   c. The AppImage runs as a direct child - no wrapper process stays resident

2. For AppImages launched outside Driftwood (e.g., from terminal):
   - The background daemon can optionally monitor /proc for running AppImage
     processes and record "seen running" events
   - This is lightweight: poll /proc every 60 seconds, check for processes
     whose exe symlink points to a known AppImage path
   - Less precise than direct tracking but catches manual launches

3. Launch events are stored in SQLite:
   CREATE TABLE launch_events (
       id          INTEGER PRIMARY KEY AUTOINCREMENT,
       appimage_id INTEGER REFERENCES appimages(id),
       launched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
       source      TEXT  -- 'desktop_entry', 'cli', 'detected'
   );

Disk space analysis:

$ driftwood usage

AppImage Disk Usage Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━
Total: 14 AppImages using 4.7 GB

By last used:
  Not launched in 6+ months (1.8 GB):
    ⚠ Audacity 3.4.2        287 MB   Last used: 8 months ago
    ⚠ Godot 4.2              398 MB   Last used: 11 months ago
    ⚠ LibreOffice 7.6       1.1 GB   Last used: 7 months ago

  Not launched in 1-6 months (920 MB):
     Blender 4.1            412 MB   Last used: 3 months ago
     Krita 5.2.2            508 MB   Last used: 2 months ago

  Active - used in last 30 days (2.0 GB):
    ✓ Firefox 125.0          287 MB   Last used: today
    ✓ Kdenlive 24.02.1       312 MB   Last used: 2 days ago
    ✓ Inkscape 1.3.2         198 MB   Last used: 5 days ago
    ... 6 more

  Never launched (450 MB):
    ❓ SomeApp-x86_64        150 MB   Discovered: 4 months ago
    ❓ AnotherTool            300 MB   Discovered: 2 weeks ago

Potential savings: 2.25 GB from unused AppImages

15.3 UI Integration

Library view enhancements:

  • Optional "Last used" column in list view
  • Sort by "last used" to surface stale AppImages
  • Filter: "Not used in 3+ months" / "Never launched"
  • Color coding: recently used (normal) → stale (dimmed) → never used (dimmed + indicator)

Dashboard / summary widget:

  • Disk usage pie chart or bar showing space by usage category
  • "You could reclaim X GB" call-to-action
  • "Clean up" button that shows a checklist of stale AppImages to remove

Cleanup flow:

┌─────────────────────────────────────────┐
│ Reclaim Disk Space                      │
├─────────────────────────────────────────┤
│                                         │
│ These AppImages haven't been used       │
│ recently. Remove them to free space.    │
│                                         │
│ ☑ Audacity 3.4.2        287 MB         │
│   Last used 8 months ago                │
│                                         │
│ ☑ Godot 4.2              398 MB         │
│   Last used 11 months ago               │
│                                         │
│ ☐ LibreOffice 7.6       1.1 GB         │
│   Last used 7 months ago                │
│                                         │
│ Selected: 685 MB to reclaim             │
│                                         │
│  [ Cancel ]  [ Remove Selected ]        │
│                                         │
└─────────────────────────────────────────┘

15.4 Privacy Considerations

  • All usage data is local-only (SQLite database, never transmitted)
  • Users can disable launch tracking entirely in settings
  • "Clear usage history" button in settings
  • Usage data is deleted when an AppImage is removed from Driftwood
  • No tracking of what the user does inside the AppImage - only launch/exit events

16. Orphaned Config & Data Detection

16.1 The Problem

When you delete an AppImage, the application's configuration and data files remain scattered across your home directory. Unlike Flatpak (which confines app data to ~/.var/app/{app-id}/) or Snap (which uses ~/snap/{app}/), AppImages write to the same locations as natively-installed apps - and those locations are unpredictable:

  • ~/.config/{app-name}/ - settings
  • ~/.local/share/{app-name}/ - application data
  • ~/.cache/{app-name}/ - cached data (can be huge for media apps)
  • ~/.{app-name} or ~/.{app-name}rc - legacy dotfiles
  • ~/Documents/{app-name}/ - user-created content (should NOT auto-delete)
  • Various other XDG and non-XDG paths

After removing an AppImage, users have no idea what got left behind or how to clean it up. Over time, this accumulates into gigabytes of orphaned config, cache, and data scattered across hundreds of hidden directories.

16.2 How It Works

Phase 1: Data path discovery (learning phase)

Driftwood learns where each AppImage stores its data by monitoring filesystem activity during launches:

Method A: Filesystem monitoring via fanotify (preferred, Linux 5.1+)

1. Before launching an AppImage, set up fanotify watches on key directories:
   - ~/.config/
   - ~/.local/share/
   - ~/.local/state/
   - ~/.cache/
   - ~/

2. Launch the AppImage

3. Monitor fanotify events for FAN_CREATE, FAN_MODIFY, FAN_OPEN_PERM
   filtered to the AppImage's PID and child PIDs

4. Record all paths written to or created by the AppImage process tree

5. After the AppImage exits, store the discovered paths in the database

6. Over multiple launches, build a comprehensive map of where this
   AppImage reads and writes
Method B: /proc scanning fallback (less precise, no special permissions)

1. While an AppImage is running, periodically (every 30s) scan:
   - /proc/{pid}/fd/ - open file descriptors
   - /proc/{pid}/maps - memory-mapped files
   - /proc/{pid}/io - I/O stats (to detect active writing)

2. Record paths that are open for writing within the home directory

3. Less comprehensive than fanotify (misses files that were
   opened and closed between scans) but requires no special
   permissions
Method C: Heuristic matching (no runtime monitoring needed)

1. Extract the app name from the AppImage metadata
   (e.g., "kdenlive" from the .desktop file)

2. Search for matching directories in standard XDG locations:
   ~/.config/kdenlive/
   ~/.local/share/kdenlive/
   ~/.cache/kdenlive/
   ~/.config/kdenliverc  (KDE-style config)

3. Also check common variations:
   - Case variations (Kdenlive, KDENLIVE, kdenlive)
   - With org prefix (org.kde.kdenlive)
   - Flatpak-style reverse DNS (org.kde.kdenlive)
   - Electron-style (~/.config/{app-name}/ containing
     Preferences, Local Storage, etc.)

4. Score matches by confidence:
   - Exact name match in standard XDG dir: high confidence
   - Case-insensitive match: medium confidence
   - Partial name match: low confidence (flag for user review)

Phase 2: Data path storage

struct AppDataPath {
    appimage_id: i64,
    path: PathBuf,
    path_type: DataPathType,    // Config, Data, Cache, State, Other
    discovery_method: Method,   // Fanotify, ProcScan, Heuristic
    confidence: Confidence,     // High, Medium, Low
    first_seen: DateTime,
    last_accessed: DateTime,
    size_bytes: u64,
    contains_user_content: bool, // True for ~/Documents paths - don't auto-delete
}

enum DataPathType {
    Config,   // ~/.config/{app}/
    Data,     // ~/.local/share/{app}/
    Cache,    // ~/.cache/{app}/
    State,    // ~/.local/state/{app}/
    DotFile,  // ~/.{app}rc, ~/.{app}/
    UserData, // ~/Documents/{app}/ - flagged, never auto-deleted
    Other,
}

Phase 3: Orphan detection and cleanup

When an AppImage is removed (either deleted from disk or removed via Driftwood):

1. Look up all known data paths for this AppImage
2. Check which paths still exist on disk
3. Calculate total size of orphaned data
4. Present to user:

  Kdenlive was removed. The following data remains:

  Config:
    ~/.config/kdenliverc                    4 KB
    ~/.config/kdenlive/                    12 KB

  Cache (safe to delete):
    ~/.cache/kdenlive/                    340 MB

  Application data:
    ~/.local/share/kdenlive/               28 MB

  ⚠ User content (review before deleting):
    ~/Videos/Kdenlive Projects/           1.2 GB

  Total reclaimable: 380 MB (+ 1.2 GB user content)

  [ Keep All ]  [ Delete Cache Only ]  [ Delete All Config/Cache ]

16.3 Safety Rules

This feature handles user data, so safety is paramount:

  1. Never auto-delete anything. Always present a list and let the user choose.
  2. Separate cache from config from user content. Cache is always safe to delete. Config is usually safe. User-created content (documents, projects, saves) should be prominently warned about.
  3. Low-confidence matches require explicit confirmation. If a path was found heuristically with low confidence, show it separately: "We think this might belong to {app} but aren't sure."
  4. Dry-run by default. The cleanup dialog shows what would be deleted. The user clicks confirm.
  5. Backup option. Offer to tar the config before deleting: "Save a backup of Kdenlive's config to ~/driftwood-backups/ before removing?"
  6. Never touch paths outside $HOME without explicit user action and polkit elevation.

16.4 Proactive Data Mapping

Even before an AppImage is removed, Driftwood can show the data footprint in the detail view:

Data Footprint - Kdenlive 24.02.1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AppImage file:           312 MB
Configuration:            16 KB  (~/.config/kdenlive/)
Application data:         28 MB  (~/.local/share/kdenlive/)
Cache:                   340 MB  (~/.cache/kdenlive/)
Total footprint:         680 MB

Detection method: fanotify (high confidence)
Last scanned: 2 hours ago

This gives users visibility into the true cost of each AppImage - not just the file itself but everything it sprawls across the filesystem.


17. Duplicate & Multi-Version Detection

17.1 The Problem

Users commonly end up with duplicate or multiple versions of the same AppImage without realizing it:

  • Downloaded Firefox in January → ~/Downloads/Firefox-124.0-x86_64.AppImage
  • Downloaded Firefox update in March → ~/Downloads/Firefox-125.0-x86_64.AppImage
  • Moved an older copy to Applications → ~/Applications/Firefox.AppImage
  • Now there are 3 Firefox AppImages consuming 860 MB total

This happens because:

  • Browsers save new downloads without overwriting old ones
  • AppImage filenames often include version numbers, so each version is a separate file
  • No tool correlates "these are all the same application at different versions"
  • Users forget what they've already downloaded

17.2 How It Works

Detection algorithm:

1. Group AppImages by identity (not filename):

   For each discovered AppImage, extract identity fingerprints:
   a. Desktop entry Name= field (strongest signal)
      "Name=Firefox Web Browser" → identity: "firefox"
   b. Desktop entry WM_CLASS / StartupWMClass
   c. AppStream <id> element (e.g., "org.mozilla.firefox")
   d. Executable name from AppRun or Exec= line
   e. Filename heuristic: strip version numbers, architecture,
      and extension from filename
      "Firefox-125.0-x86_64.AppImage" → "firefox"
      "Krita-5.2.2-x86_64.appimage" → "krita"

2. Normalize and cluster:
   a. Lowercase all identity strings
   b. Strip common suffixes (-bin, -latest, -stable, -nightly)
   c. Group AppImages sharing any identity fingerprint
   d. Within each group, sort by version (extracted from metadata)

3. Flag groups with more than one member:
   - If versions differ: "Multiple versions detected"
   - If versions are identical: "Exact duplicate detected"
   - If one is in ~/Downloads and another in ~/Applications:
     "Likely an unmoved copy"

Version comparison:

struct DuplicateGroup {
    canonical_name: String,          // e.g., "Firefox"
    appimages: Vec<DuplicateEntry>,  // sorted by version, newest first
    total_size: u64,
    potential_savings: u64,          // size of all but the newest
    duplicate_type: DuplicateType,
}

enum DuplicateType {
    ExactDuplicate,    // Same app, same version, different locations
    MultipleVersions,  // Same app, different versions
    NightlyAndStable,  // Same app, different release channels
}

struct DuplicateEntry {
    appimage_id: i64,
    path: PathBuf,
    version: Option<SemanticVersion>,
    size: u64,
    last_modified: DateTime,
    last_launched: Option<DateTime>,  // from usage tracking
    is_integrated: bool,              // has a .desktop entry
}

17.3 UI Integration

Library view:

  • Duplicate groups get a subtle badge: "2 versions" or "duplicate"
  • Optional filter: "Show duplicates only"

Duplicate resolution dialog:

┌──────────────────────────────────────────────┐
│ Duplicate Detected: Firefox                   │
├──────────────────────────────────────────────┤
│                                               │
│ 3 copies of Firefox found (860 MB total):     │
│                                               │
│ ★ Firefox 125.0          287 MB               │
│   ~/Applications/Firefox-125.0.AppImage       │
│   Last used: today                            │
│   Integrated: ✓                               │
│                                               │
│   Firefox 124.0          287 MB               │
│   ~/Downloads/Firefox-124.0.AppImage          │
│   Last used: 2 months ago                     │
│   Integrated: ✗                               │
│                                               │
│   Firefox 124.0          287 MB               │
│   ~/Applications/Firefox.AppImage             │
│   Last used: 3 months ago                     │
│   Integrated: ✗ (orphaned .desktop exists)    │
│                                               │
│ ★ = Recommended to keep (newest, integrated)  │
│                                               │
│ Removing the other 2 would save 574 MB        │
│                                               │
│ [ Keep All ] [ Remove Old Versions ]          │
│              [ Remove Duplicates ]             │
│                                               │
└──────────────────────────────────────────────┘

17.4 Smart Recommendations

When duplicates are detected, Driftwood recommends which to keep based on:

  1. Version: Newest version preferred (unless user explicitly kept an older one)
  2. Integration status: The one with an active .desktop entry preferred
  3. Location: ~/Applications preferred over ~/Downloads
  4. Usage: Most recently launched preferred
  5. Nightly vs stable: If both channels are present, assume intentional - don't recommend removal, just inform

17.5 Automatic Cleanup on Update

When Driftwood updates an AppImage (via zsync or full download):

1. New version downloaded to temp location
2. Verify new AppImage is valid
3. Replace old AppImage (atomic rename)
4. Old version optionally kept:
   a. If "keep old versions" is enabled:
      Move to ~/Applications/.driftwood-old/Firefox-124.0.AppImage
      Maintain a configurable limit (default: 1 old version)
      Auto-prune beyond the limit
   b. If disabled: old version is deleted after successful replacement
5. Update .desktop entry with new version info
6. If any OTHER copies of this AppImage exist in other directories,
   notify the user: "You also have an older Firefox in ~/Downloads.
   Remove it?"

17.6 CLI Integration

$ driftwood duplicates

Duplicate AppImages Found
━━━━━━━━━━━━━━━━━━━━━━━━
Firefox (3 copies, 860 MB):
  ★ v125.0  ~/Applications/Firefox-125.0.AppImage       287 MB
    v124.0  ~/Downloads/Firefox-124.0.AppImage          287 MB
    v124.0  ~/Applications/Firefox.AppImage             287 MB

Krita (2 copies, 1016 MB):
  ★ v5.2.2  ~/Applications/Krita-5.2.2.AppImage        508 MB
    v5.1.5  ~/Downloads/Krita-5.1.5.AppImage           508 MB

Total potential savings: 1.4 GB

$ driftwood duplicates --clean
Remove 3 old/duplicate AppImages to save 1.4 GB? [y/N]

18. Desktop Integration Manager

18.1 Integration Flow

User clicks "Integrate" on an AppImage
        │
        ▼
┌─ Move AppImage to ~/Applications? ─┐
│  (if not already there and          │
│   move-on-integrate is enabled)     │
└──────────┬──────────────────────────┘
           ▼
┌─ Extract metadata ─┐
│  - .desktop file    │
│  - icon(s)          │
│  - AppStream XML    │
└──────────┬──────────┘
           ▼
┌─ Generate .desktop file ─┐
│  - Use extracted Name,    │
│    Icon, Categories       │
│  - Set Exec= to AppImage │
│    path (with FUSE        │
│    wrapper if needed)     │
│  - Add X-AppImage-* keys  │
└──────────┬───────────────┘
           ▼
┌─ Install icon ─┐
│  - Copy to     │
│    hicolor      │
│    theme dirs   │
└──────────┬─────┘
           ▼
┌─ Update databases ─┐
│  - update-desktop-  │
│    database          │
│  - gtk-update-icon-  │
│    cache             │
└──────────┬──────────┘
           ▼
┌─ Update Driftwood DB ─┐
│  - Mark as integrated │
│  - Store paths        │
│  - Record timestamp   │
└───────────────────────┘

18.2 Exec Line Generation

The Exec= line needs to handle several scenarios:

Normal (FUSE works):

Exec=/home/user/Applications/Firefox.AppImage %U

FUSE fallback (extract-and-run):

Exec=env APPIMAGE_EXTRACT_AND_RUN=1 /home/user/Applications/Firefox.AppImage %U

With Wayland hints:

Exec=env GDK_BACKEND=wayland /home/user/Applications/SomeGtk3App.AppImage %U

With sandboxing:

Exec=firejail --appimage /home/user/Applications/UntrustedApp.AppImage %U

Combined:

Exec=env APPIMAGE_EXTRACT_AND_RUN=1 GDK_BACKEND=wayland firejail --appimage /home/user/Applications/App.AppImage %U

18.3 Multi-Desktop Support

GNOME: Primary target. .desktop files in ~/.local/share/applications/ with standard freedesktop.org format. GNOME Shell picks them up automatically after update-desktop-database.

KDE Plasma: Same .desktop files work. KDE uses kbuildsycoca6 to rebuild its cache. Driftwood runs this when detected on KDE. KDE-specific X-KDE-* keys can be added.

Other (XFCE, Cinnamon, MATE, etc.): All follow freedesktop.org standards. Should work with the GNOME path. May need xfce4-panel --restart type commands for menu refresh.


19. Background Daemon

19.1 Purpose

driftwoodd runs in the background to:

  1. Watch configured directories for new/removed AppImages (via inotify)
  2. Periodically check for updates
  3. Detect and flag orphaned desktop entries
  4. Send desktop notifications
  5. Provide a D-Bus API for the GUI and CLI

19.2 D-Bus Interface

<node>
  <interface name="io.github.Driftwood.Manager">
    <!-- Methods -->
    <method name="Scan">
      <annotation name="org.freedesktop.DBus.Description"
        value="Trigger a full rescan of all configured directories"/>
    </method>
    <method name="GetAppImages">
      <arg direction="out" type="a(ssssbbb)" name="appimages"/>
      <!-- Returns array of (path, name, version, status, integrated, has_update, wayland_native) -->
    </method>
    <method name="Integrate">
      <arg direction="in" type="s" name="path"/>
      <arg direction="out" type="b" name="success"/>
    </method>
    <method name="RemoveIntegration">
      <arg direction="in" type="s" name="path"/>
      <arg direction="out" type="b" name="success"/>
    </method>
    <method name="CheckUpdates">
      <annotation name="org.freedesktop.DBus.Description"
        value="Check all AppImages for available updates"/>
    </method>
    <method name="UpdateAppImage">
      <arg direction="in" type="s" name="path"/>
      <arg direction="out" type="b" name="success"/>
    </method>
    <method name="CleanOrphans">
      <arg direction="out" type="u" name="removed_count"/>
    </method>

    <!-- Signals -->
    <signal name="AppImageDiscovered">
      <arg type="s" name="path"/>
      <arg type="s" name="name"/>
    </signal>
    <signal name="AppImageRemoved">
      <arg type="s" name="path"/>
    </signal>
    <signal name="UpdateAvailable">
      <arg type="s" name="path"/>
      <arg type="s" name="current_version"/>
      <arg type="s" name="new_version"/>
    </signal>
    <signal name="UpdateProgress">
      <arg type="s" name="path"/>
      <arg type="d" name="progress"/>  <!-- 0.0 to 1.0 -->
    </signal>
  </interface>
</node>

19.3 Autostart

When enabled, Driftwood installs an autostart entry:

# ~/.config/autostart/driftwoodd.desktop
[Desktop Entry]
Type=Application
Name=Driftwood Daemon
Exec=driftwood --daemon
NoDisplay=true
X-GNOME-Autostart-enabled=true
X-KDE-autostart-phase=1

19.4 Resource Usage

The daemon must be extremely lightweight:

  • No GUI, no GTK dependency when running as daemon
  • Idle memory target: < 15 MB RSS
  • inotify watches: one per configured directory (not recursive - avoids exhausting inotify limits)
  • Update checks: batched, rate-limited, with exponential backoff on failures
  • Wakes up only on events (inotify) or timer (update check interval)

20. CLI Interface

driftwood                          # Launch GUI
driftwood --daemon                 # Start background daemon
driftwood list                     # List all known AppImages
driftwood list --format=json       # Machine-readable output
driftwood scan                     # Trigger rescan
driftwood integrate <path>         # Integrate an AppImage
driftwood integrate --all          # Integrate all discovered
driftwood remove <path>            # Remove integration
driftwood update <path>            # Update specific AppImage
driftwood update --all             # Update all with available updates
driftwood check                    # Check for updates
driftwood inspect <path>           # Show metadata for an AppImage
driftwood audit                    # Show Wayland + FUSE status for all
driftwood clean                    # Remove orphaned desktop entries
driftwood security [path]          # CVE scan for one or all AppImages
driftwood usage                    # Show disk usage and last-used report
driftwood duplicates               # Show duplicate/multi-version AppImages
driftwood duplicates --clean       # Interactive removal of old duplicates
driftwood footprint <path>         # Show config/data/cache footprint of an AppImage
driftwood launch <path> [args]     # Launch an AppImage with usage tracking
driftwood version                  # Show Driftwood version

Example output:

$ driftwood list
 Name          Version   Wayland   FUSE      Updates   Integrated
 Firefox       124.0     🟢 Native 🟢 OK     ⬆ 125.0  ✓
 Kdenlive      24.02.1   🟡 XWayl  🟢 OK     ✓ Latest ✓
 Inkscape      1.3.2     🟢 Native 🟡 E&R    ⬆ 1.4.0  ✓
 MuseScore     4.2.0     🔴 X11    🟢 OK     ✓ Latest ✗
 MyApp         unknown   ⚪ N/A     🟢 OK     - None   ✗

 5 AppImages found, 3 integrated, 2 updates available
$ driftwood audit
FUSE Status:
  libfuse2: NOT INSTALLED
  libfuse3: installed (3.14.0)
  fusermount: /usr/bin/fusermount3
  /dev/fuse: present
  ⚠ Most AppImages require libfuse2. Install with: sudo apt install libfuse2
  Driftwood is using extract-and-run mode as fallback.

Wayland Status:
  Session: Wayland (GNOME 46 on Mutter)
  XWayland: available

  Per-app Wayland audit:
  🟢 Firefox 124.0      - GTK3 with Wayland backend
  🟡 Kdenlive 24.02.1   - Qt5 without QtWayland plugin
  🟢 Inkscape 1.3.2     - GTK4 (native Wayland)
  🔴 MuseScore 4.2.0    - Qt5 without Wayland, X11 only

21. Distribution & Self-Updating

21.1 AppImage Packaging

Driftwood itself is distributed as an AppImage. This is intentional dogfooding - it proves the format works and gives users a zero-dependency installation experience.

Build process:

  1. Compile the Rust binary with static linking where possible
  2. Bundle GTK4, libadwaita, and all Rust dependencies
  3. Use linuxdeploy with the gtk plugin to create the AppImage
  4. Embed update information pointing to GitHub Releases
  5. Sign the AppImage with GPG

AppImage contents:

Driftwood.AppImage
├── AppRun
├── driftwood.desktop
├── driftwood.svg
├── usr/
│   ├── bin/
│   │   └── driftwood              # Main binary (GUI + daemon + CLI)
│   ├── lib/
│   │   ├── libgtk-4.so.*
│   │   ├── libadwaita-1.so.*
│   │   ├── libsqlite3.so.*
│   │   └── ... (other deps)
│   └── share/
│       ├── applications/
│       │   └── driftwood.desktop
│       ├── icons/
│       ├── glib-2.0/schemas/
│       │   └── io.github.driftwood.gschema.xml
│       └── metainfo/
│           └── driftwood.metainfo.xml
└── .upd_info                     # zsync update info → GitHub Releases

21.2 Self-Updating

Driftwood uses its own update system on itself:

  1. On launch, check for updates (respecting the user's update check interval)
  2. If update available, show banner: "A new version of Driftwood is available [Update]"
  3. Download the new AppImage to a temp location
  4. Verify checksum / signature
  5. Replace the current AppImage file (atomic rename)
  6. Prompt user to restart: "Driftwood has been updated. Restart to use the new version."

21.3 Alternative Distribution

For users who prefer it:

  • Flatpak (Flathub) - for those who want sandboxed Driftwood (like Gear Lever's approach)
  • AUR (Arch) - driftwood-bin (AppImage) and driftwood-git (build from source)
  • Copr (Fedora) - RPM packaging
  • PPA (Ubuntu) - Debian packaging, targeting Ubuntu 24.04 LTS and 26.04 LTS
  • Nix - nixpkg for NixOS users

The AppImage is the primary distribution channel. Others are community-maintained. Ubuntu 26.04 LTS (April 2026) with its Wayland-only GNOME 50 and unified App Center is the ideal first target for mainstream adoption.


22. KDE/Plasma Support Strategy

While Driftwood targets GTK4/libadwaita primarily, KDE Plasma is a first-class desktop that deserves good support - especially as KDE Plasma 6.8 (October 2026) goes Wayland-only, meaning KDE users will face the same XWayland visibility issues that Driftwood addresses.

22.1 Approach: Single GTK4 App with KDE Adaptations

Rather than maintaining two toolkit versions, Driftwood ships as a GTK4 app but adapts to KDE:

Visual integration:

  • GTK4 apps on KDE Plasma 6 look good when xdg-desktop-portal-kde and kde-gtk-config are installed
  • Colors and fonts follow KDE's Breeze theme via the portal
  • libadwaita's adaptive colors work reasonably well with Breeze

Functional adaptations:

  • Detect $XDG_CURRENT_DESKTOP=KDE and adjust:
    • Use kbuildsycoca6 instead of / in addition to update-desktop-database
    • Support KDE's service menus if needed
    • Use KDE notification service (org.freedesktop.Notifications - same D-Bus interface)
    • File dialogs: use xdg-desktop-portal-kde for native KDE file picker
    • Wayland audit: use org.kde.KWin D-Bus interface for window inspection instead of GNOME's introspection API

22.2 Future: Qt6 Version

If demand warrants it, a Qt6/KDE Frameworks version (driftwood-qt) could be developed sharing the same core library (Rust). The architecture separates core logic from UI for this reason. Given that 79% of Plasma 6 users are already on Wayland, demand for Driftwood's Wayland audit on KDE may drive this.


23. Accessibility

Following GNOME accessibility standards:

  • All interactive elements have accessible names and descriptions
  • Keyboard navigation: full Tab/Shift+Tab cycling, Enter to activate, Escape to go back
  • Screen reader support: ATK via GTK4's built-in accessibility
  • High contrast mode: works via GTK4/libadwaita theme support
  • Reduced motion: respect prefers-reduced-motion (no gratuitous animations)
  • Status badges use text labels, not just color (color blind safe)
    • "🟢 Native Wayland" not just a green dot
    • All status indicators have tooltip text and aria labels

24. Internationalization

  • Use gettext for all user-facing strings
  • Ship with English (default)
  • Set up Weblate or similar for community translations
  • All date/time formatting via locale-aware formatters
  • File sizes in locale-appropriate units
  • RTL layout support via GTK4's built-in bidi handling

25. Testing Strategy

Unit Tests

  • Discovery engine: mock filesystem with various AppImage types
  • Inspector: test against sample AppImage binaries (Type 1, Type 2, broken, unsigned)
  • Desktop entry generation: verify output format
  • FUSE detection: mock /proc, library checks
  • Wayland detection: mock process inspection
  • Update info parsing: all supported formats

Integration Tests

  • Full flow: discover → inspect → integrate → verify desktop entry → remove
  • Update flow: embed test zsync info, verify delta download
  • Orphan detection: create entry, delete AppImage, verify cleanup
  • FUSE fallback: test with and without libfuse2

UI Tests

  • Screenshot tests against reference images (GTK4 broadway backend)
  • Accessibility audit with accerciser

Manual Testing Matrix

Distro Desktop FUSE Session Priority
Ubuntu 26.04 LTS GNOME 50 fuse3 (libfuse2t64 in universe) Wayland-only P0
Ubuntu 24.04 LTS GNOME 46 fuse3 (libfuse2t64 in universe) Wayland default, X11 available P0
Fedora 42+ GNOME 48/49 fuse2 + fuse3 Wayland default P0
Arch GNOME 50 / latest fuse2 + fuse3 Wayland default P0
Kubuntu 26.04 KDE Plasma 6.7+ fuse3 (libfuse2t64 in universe) Wayland default P1
Fedora KDE KDE Plasma 6.7+ fuse2 + fuse3 Wayland default P1
openSUSE TW GNOME or KDE fuse2 + fuse3 Wayland default P2
Debian 13 "Trixie" GNOME 48 fuse2 + fuse3 Wayland default P2
Linux Mint 22/23 Cinnamon fuse3 (libfuse2t64 avail) X11 default (Cinnamon Wayland in progress) P2

26. Development Phases & Roadmap

Phase 1 - Foundation (Months 1-3)

Goal: A working app that discovers, inspects, and integrates AppImages.

  • Project scaffolding (Meson + Rust + gtk4-rs)
  • AppImage discovery engine (file scanning, magic byte detection)
  • AppImage inspector (metadata extraction via unsquashfs)
  • SQLite database for tracking state
  • Library view (grid/list of discovered AppImages)
  • Detail view (metadata display)
  • Desktop integration (generate .desktop, extract icons)
  • Orphan detection and cleanup
  • Basic settings view
  • CLI: list, integrate, remove, scan, clean
  • AppImage packaging of Driftwood itself

Milestone: v0.1.0 - "It works. You can find, view, and integrate your AppImages."

Phase 2 - Intelligence (Months 4-6)

Goal: FUSE management, Wayland audit, update system, and usage tracking.

  • FUSE detection and compatibility management
  • Extract-and-run transparent fallback
  • Wayland awareness engine (static analysis)
  • Wayland status badges in library and detail views
  • Update info parsing (zsync, GitHub, GitLab, OCS)
  • Update checking (periodic + manual)
  • Delta updates via AppImageUpdate/zsync2
  • Full download fallback updates
  • Update UI (progress, notifications)
  • Driftwood self-update mechanism
  • Background daemon (file watching + update checking)
  • D-Bus interface
  • Desktop notifications
  • Usage tracking via driftwood launch wrapper
  • Duplicate and multi-version detection engine
  • Duplicate resolution UI
  • CLI: update, check, audit, usage, duplicates, launch

Milestone: v0.5.0 - "It's smart. It knows your FUSE status, Wayland status, available updates, and which AppImages you actually use."

Phase 3 - Security & Deep Analysis (Months 7-9)

Goal: Security scanning, data tracking, sandboxing, and polish.

  • Bundled library inventory extraction (ELF SONAME, version string scanning)
  • CVE database integration (NVD/OSV/Debian Security Tracker)
  • CVE matching engine and per-AppImage security reports
  • System vs bundled library version comparison
  • Security badges and notifications for critical CVEs
  • Config/data path discovery via fanotify (with proc-scan fallback)
  • Heuristic config/data path matching
  • Orphaned config detection on AppImage removal
  • Config cleanup UI with safety categories (cache vs config vs user content)
  • Firejail integration (one-click sandboxed launch)
  • Bubblewrap integration (alternative backend)
  • Permission audit view
  • Sandbox profile generation
  • GPG signature verification for AppImages
  • SHA256 verification for updates
  • Drag-and-drop support
  • CLI: security, footprint

Milestone: v0.8.0 - "It's secure. It knows what's inside your AppImages, where they store data, and can sandbox them."

Phase 4 - Polish & Release (Months 10-12)

Goal: Production-quality release.

  • Keyboard navigation audit
  • Screen reader testing
  • i18n infrastructure + initial translations
  • Comprehensive test suite
  • Performance optimization (startup time, scan speed)
  • Disk space reclamation UI (usage dashboard, cleanup wizard)
  • Auto-cleanup of old duplicate versions on update
  • AppStream metainfo for Driftwood itself
  • Flathub submission
  • AUR package
  • Documentation (user guide, contributor guide)

Milestone: v1.0.0 - "It's ready. A complete, polished AppImage management experience."

Phase 5 - Ecosystem (Months 13+)

Goal: Become the standard AppImage management tool.

  • AppImage catalog integration (optional download from curated sources)
  • Wayland runtime detection (post-launch analysis)
  • Batch re-packaging (FUSE runtime updates)
  • Multi-user / system-wide mode
  • GNOME Software / KDE Discover plugin
  • AppImage developer tools (validate your AppImage)
  • Qt6 frontend (for KDE purists)
  • ARM64/aarch64 support
  • Community sandbox profile sharing
  • CVE database push notifications (critical CVE in widely-bundled library)
  • Exportable security reports (for organizations managing AppImage deployments)
  • Config backup/restore across AppImage versions

27. Project Naming & Branding

Name: Driftwood

Driftwood conveys:

  • Something gathered and collected (like AppImages scattered across your filesystem)
  • Natural, organic - not corporate or clinical
  • Resilience - driftwood survives journeys, like AppImages surviving across distros
  • A warm, approachable tool, not an intimidating "manager"

Icon concept:

  • A stylized piece of driftwood with app-like elements (windows, gears) emerging from it
  • Or: a flowing, organic shape containing a grid pattern - nature meets organization
  • Colors: follow GNOME's app icon style (bold, flat, distinctive) - warm wood tones with accent color

App ID: io.github.driftwood (assuming GitHub hosting)


28. Risk Analysis

Risk Likelihood Impact Mitigation
AppImage format itself loses traction Low High Driftwood is useful as long as any AppImages exist. The format is deeply embedded in many projects' release workflows. The new type2-runtime (updated Jan 2026) shows continued investment.
libfuse2 completely removed from all distros Medium Medium Extract-and-run fallback exists. New type2-runtime supports fuse3. Ubuntu keeps libfuse2t64 in universe repos. Static runtime eliminates dependency entirely for new AppImages.
GTK4/libadwaita breaks backward compat Low Medium Pin to stable versions. Target libadwaita 1.7+ (GNOME 48). GNOME has strong ABI stability commitments.
unsquashfs not available on some systems Low Low Bundle it in the AppImage. It's a small static binary.
GitHub rate limits on update checks Medium Low Cache aggressively. Use conditional requests (ETags). Batch checks. Respect rate limit headers.
Upstream AppImageUpdate library changes Low Medium Shell out to the binary for isolation. zsync2 v2.0.0-alpha (Sept 2025) is stabilizing but still alpha. Maintain a thin interface layer.
Single maintainer burnout High High Design for contributor-friendliness. Rust attracts contributors. Good documentation. Modular architecture allows focused PRs.
AppManager catches up first Medium Medium AppManager (Vala/GTK4) v3.0.0 is the closest competitor. Driftwood differentiates with Wayland audit, FUSE management, background daemon, orphan cleanup, and system-health perspective. Collaborate where possible (shared AppImage inspection libraries).
GNOME 50 Wayland-only transition breaks assumptions Low Low Actually benefits Driftwood - makes Wayland audit MORE valuable since there's no X11 session to fall back to.
type2-runtime compatibility fragmentation Medium Medium Driftwood should handle both old (fuse2-only) and new (fuse2/fuse3 static) runtimes gracefully. Test against both AppImageLauncher v2.2 and v3.0 interactions.

29. Community & Contribution Model

Governance

  • Open source (GPL-3.0 or LGPL-3.0 - TBD, should match GNOME ecosystem norms)
  • Hosted on GitHub (for ecosystem familiarity; could be GNOME GitLab later)
  • BDFL model initially, transitioning to maintainer team as community grows

Contribution Areas

  • Rust developers: core logic, daemon, CLI
  • GTK4/UI designers: views, widgets, UX refinement
  • Packagers: Flatpak, AUR, RPM, DEB, Nix
  • Translators: via Weblate
  • Documentation writers: user guides, developer docs
  • Testers: distro testing matrix, edge cases

Communication

  • GitHub Issues for bugs and feature requests
  • GitHub Discussions for design conversations
  • Matrix/IRC channel for real-time chat
  • Blog posts for major releases

30. Appendix: Technical Reference

A. AppImage Magic Bytes

Type 2: bytes at offset 8 in ELF: 0x41 0x49 0x02 (AI\x02)
Type 1: ISO 9660 magic: 0x43 0x44 0x30 0x30 0x31 (CD001) at offset 32769

B. Key Environment Variables

Variable Purpose Values
APPIMAGE Set by runtime; points to AppImage path /path/to/app.AppImage
APPDIR Set by runtime; points to mounted/extracted dir /tmp/.mount_appXXX/
APPIMAGE_EXTRACT_AND_RUN Force extract-and-run mode 1
WAYLAND_DISPLAY Wayland compositor socket wayland-0
DISPLAY X11 display :0
GDK_BACKEND Force GTK backend wayland, x11
QT_QPA_PLATFORM Force Qt platform wayland, xcb
XDG_SESSION_TYPE Session type wayland, x11
XDG_CURRENT_DESKTOP Desktop environment GNOME, KDE, etc.

C. Key File Paths

Path Purpose
~/.local/share/applications/ User desktop entries
~/.local/share/icons/hicolor/ User icon theme
~/.local/share/driftwood/driftwood.db Driftwood SQLite database
~/.config/driftwood/ Driftwood configuration
~/.config/autostart/driftwoodd.desktop Daemon autostart entry
/usr/lib/x86_64-linux-gnu/libfuse.so.2 libfuse2 library (Debian)
/usr/lib/libfuse.so.2 libfuse2 library (Arch/Fedora)

D. D-Bus Services

Service Purpose
io.github.Driftwood.Manager Driftwood daemon interface
org.freedesktop.Notifications Desktop notifications
org.gnome.Shell.Introspect GNOME window inspection (Wayland audit)
org.kde.KWin KDE window inspection (Wayland audit)

E. Database Schema (SQLite)

CREATE TABLE appimages (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    path            TEXT NOT NULL UNIQUE,
    filename        TEXT NOT NULL,
    app_name        TEXT,
    app_version     TEXT,
    appimage_type   INTEGER,  -- 1 or 2
    size_bytes      INTEGER,
    sha256          TEXT,
    icon_path       TEXT,
    desktop_file    TEXT,     -- path to generated .desktop
    integrated      BOOLEAN DEFAULT FALSE,
    integrated_at   TIMESTAMP,
    fuse_status     TEXT,
    wayland_status  TEXT,
    update_info     TEXT,     -- raw update info string
    update_type     TEXT,     -- zsync, github, gitlab, ocs, none
    latest_version  TEXT,
    update_checked  TIMESTAMP,
    update_url      TEXT,
    sandbox_profile TEXT,
    first_seen      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_scanned    TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    file_modified   TIMESTAMP,
    notes           TEXT      -- user notes
);

CREATE TABLE orphaned_entries (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    desktop_file    TEXT NOT NULL,
    original_path   TEXT,
    detected_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    cleaned         BOOLEAN DEFAULT FALSE,
    cleaned_at      TIMESTAMP
);

CREATE TABLE update_history (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    appimage_id     INTEGER REFERENCES appimages(id),
    from_version    TEXT,
    to_version      TEXT,
    update_method   TEXT,    -- zsync, full_download
    download_size   INTEGER,
    updated_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    success         BOOLEAN
);

CREATE TABLE scan_log (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    scan_type       TEXT,    -- full, incremental, inotify
    directories     TEXT,    -- JSON array of scanned dirs
    found_count     INTEGER,
    new_count       INTEGER,
    removed_count   INTEGER,
    duration_ms     INTEGER,
    scanned_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Bundled library security scanner
CREATE TABLE bundled_libraries (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    appimage_id     INTEGER REFERENCES appimages(id),
    soname          TEXT NOT NULL,
    library_name    TEXT,     -- normalized: "openssl", "curl", etc.
    detected_version TEXT,
    file_path       TEXT,     -- path within the squashfs
    file_size       INTEGER,
    scanned_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE cve_database (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    cve_id          TEXT NOT NULL UNIQUE,  -- e.g., "CVE-2024-0727"
    library_name    TEXT NOT NULL,
    affected_versions TEXT,  -- JSON version range
    fixed_version   TEXT,
    cvss_score      REAL,
    severity        TEXT,    -- critical, high, medium, low
    description     TEXT,
    source          TEXT,    -- nvd, osv, debian
    published_at    TIMESTAMP,
    fetched_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE cve_matches (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    appimage_id     INTEGER REFERENCES appimages(id),
    library_id      INTEGER REFERENCES bundled_libraries(id),
    cve_id          TEXT REFERENCES cve_database(cve_id),
    matched_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Usage tracking
CREATE TABLE launch_events (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    appimage_id     INTEGER REFERENCES appimages(id),
    launched_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    source          TEXT     -- desktop_entry, cli, detected
);

-- Orphaned config/data tracking
CREATE TABLE app_data_paths (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    appimage_id     INTEGER REFERENCES appimages(id),
    path            TEXT NOT NULL,
    path_type       TEXT,    -- config, data, cache, state, dotfile, user_data
    discovery_method TEXT,   -- fanotify, proc_scan, heuristic
    confidence      TEXT,    -- high, medium, low
    size_bytes      INTEGER,
    first_seen      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_accessed   TIMESTAMP
);

-- Duplicate detection
CREATE TABLE duplicate_groups (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    canonical_name  TEXT NOT NULL,
    duplicate_type  TEXT,    -- exact, multi_version, channel_split
    detected_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE duplicate_members (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    group_id        INTEGER REFERENCES duplicate_groups(id),
    appimage_id     INTEGER REFERENCES appimages(id),
    is_recommended  BOOLEAN DEFAULT FALSE  -- the one to keep
);

Summary

Driftwood fills the critical gap between "I downloaded an AppImage" and "this app is a first-class citizen on my desktop." It addresses every pain point in the current AppImage experience - discovery, integration, updates, FUSE compatibility, Wayland awareness, orphan cleanup, bundled library CVE scanning, usage tracking, disk space reclamation, config/data lifecycle management, and duplicate detection - all through a modern, native GTK4/libadwaita interface that respects GNOME HIG and works well on KDE.

Built in Rust for safety and performance, distributed as an AppImage that can update itself, and designed from the ground up for the Wayland-only era that arrived in 2026.

The timing is right: GNOME 50 ships Wayland-only in March 2026. Ubuntu 26.04 LTS follows in April. KDE Plasma 6.8 drops X11 in October. The new AppImage type2-runtime is stabilizing with fuse2/fuse3 support. Existing tools (Gear Lever v4.4.6, AppManager v3.0.0, AppImageLauncher v3.0.0) each solve pieces of the puzzle but none provide the complete, system-aware, Wayland-auditing, FUSE-managing experience that Driftwood delivers.

This is the app that makes AppImage actually work the way it was always supposed to.