aim/.plans/002-per-distro-installation/2026-03-19-per-distro-installation-design.md

9 KiB

Per-Distro Installation Design

Goal

Implement actual AppImage installation for aim across a broad Linux distro set, while preserving a single reusable install engine in aim-core, keeping aim-cli thin, and respecting distro policy constraints instead of fighting them.

Agreed Product Shape

Supported distro families

  • Debian
  • Fedora
  • Arch
  • openSUSE
  • Alpine
  • Nix
  • Immutable
  • Generic Linux fallback

Detection model

  • Detect distro family from /etc/os-release
  • Detect policy constraints such as immutable or Nix-style environments
  • Probe runtime capabilities such as directory writability and optional helper availability
  • Let distro family define intended policy, then let capabilities refine the safe final action

Support posture

  • Broad Linux coverage is the target
  • Deep integration is preferred for mainstream distro families
  • Immutable systems should get best-effort user installs with explicit warnings
  • System installs should prefer native shared integration locations when policy allows, with fallback to aim-managed locations where appropriate

Use a hybrid family-plus-capability model.

This was chosen over a family-only matrix because distro identity alone is not enough to determine what the current host will allow. It was also chosen over capability-only probing because distro family still matters for native path expectations and default policy.

The model is:

  • distro family selects the intended install policy
  • host capabilities determine whether that policy can be executed fully, degraded, or not at all
  • one generic install engine executes the workflow using the resolved policy

Install Policy Model

Debian, Fedora, Arch, openSUSE

  • --user installs use aim-managed user paths
  • --system prefers native shared desktop integration locations and system icon locations
  • AppImage payloads remain in an aim-managed system payload directory unless a later distro-specific need proves otherwise
  • Desktop caches are refreshed only when helper tools are present

Alpine

  • Treat Alpine as a lightweight Linux with shallower integration assumptions
  • Prefer aim-managed paths for both user and system installs
  • Run optional helpers only when present

Nix

  • Default to conservative behavior
  • Allow --user installs into aim-managed user paths, with a warning that the install is outside declarative package management
  • Deny --system installs until a Nix-native strategy exists
  • Never attempt to write into the Nix store or emulate a declarative install

Immutable

  • Treat --user as the primary supported path
  • If --system is requested, first test whether the host genuinely permits the required writes
  • If policy blocks system writes, either downgrade to user scope with an explicit warning or fail clearly when downgrade is not allowed by policy

Generic fallback

  • Use aim-managed paths only
  • Apply standards-based XDG desktop integration only
  • Skip distro-specific helper assumptions

Cross-cutting rule

Payload storage and integration targets are separate concerns.

For example, a system install may place the AppImage payload under /opt/aim/appimages, while writing the .desktop file to /usr/share/applications and icons to /usr/share/icons. This keeps artifact replacement and rollback simple while still integrating natively.

Execution Pipeline

Use one install engine with policy injected into it rather than separate installers per distro.

1. Resolve policy

  • Detect distro family
  • Detect capabilities and policy markers
  • Resolve an InstallPolicy describing:
    • allowed scope
    • payload root
    • desktop entry root
    • icon root
    • integration mode
    • helper refresh actions
    • warnings and fallback notes

2. Stage artifact

  • Download into a temporary staging path
  • Validate that the payload is an AppImage
  • Derive stable identity and final filenames before touching permanent locations
  • Mark the staged artifact executable before final commit

3. Commit payload atomically

  • Move the staged AppImage into the final managed payload location
  • Use replacement semantics suitable for safe updates
  • Keep the old installed payload until the new one is validated and committed

4. Commit integration

  • Generate a normalized .desktop file
  • Extract and install icons when available
  • Write integration artifacts into policy-selected locations
  • Keep these artifacts generated by aim, not manually managed

5. Refresh caches best-effort

  • Run optional helpers only when present and relevant
  • Cache refresh failures should produce warnings, not rollback a valid install

6. Persist registry last

  • Only persist the final AppRecord after payload and required integration steps succeed
  • The registry should represent completed installs, not partial attempts

Reliability Model

Preflight failures

Stop early for:

  • unsupported scope for the resolved host policy
  • unwritable required target directories with no allowed fallback
  • payloads that are not valid AppImages
  • missing required normalized metadata for integration

Transaction boundary

Treat install as three phases:

  • preflight and staging
  • payload commit
  • integration commit

Nothing is persisted in the registry until the required phases succeed.

Rollback rules

  • If staging fails, clean temporary files only
  • If payload commit succeeds but required integration fails, remove the new payload unless degraded payload-only success was explicitly allowed
  • If integration partially succeeds, remove generated .desktop and icon artifacts before returning failure
  • Cache refresh failures do not trigger rollback

Degraded success

Success with warnings is acceptable only when:

  • optional cache refresh helpers are missing or fail
  • desktop integration was optional and the host lacks the helper stack to finish niceties
  • immutable or policy-heavy systems forced a user-scope fallback that policy explicitly allows

Update safety

  • Updates stage side-by-side and replace atomically
  • The previous working AppImage stays active until the new one is validated and committed
  • Failed updates should leave the previous installed version intact

Verification Strategy

Unit tests

  • distro detection
  • immutable and Nix marker detection
  • helper capability probing
  • writable path probing
  • install policy resolution per family and scope
  • degraded and denied policy decisions

Integration tests

  • successful user install
  • successful system install using managed payload plus native integration paths
  • denied Nix system install
  • immutable system fallback to user install with warning
  • payload commit failure cleanup
  • integration failure rollback
  • cache refresh warning-only behavior

Fixture-driven filesystem tests should cover most of this. First implementation should not depend on real distro images in CI.

Architecture Slice

Core types

DistroFamily

  • Debian
  • Fedora
  • Arch
  • OpenSuse
  • Alpine
  • Nix
  • Immutable
  • Generic

HostCapabilities

  • parsed os-release identity
  • immutable and Nix policy markers
  • writable status for candidate directories
  • desktop-session presence
  • helper availability such as update-desktop-database and gtk-update-icon-cache

InstallPolicy

  • resolved scope
  • payload path root
  • desktop entry root
  • icon path root
  • integration mode: full, degraded, payload-only, or denied
  • fallback notes and warnings for UI surfaces

InstallPlan

  • selected artifact URL and expected identity
  • staging paths
  • final payload path
  • desktop file path
  • icon path
  • helper refresh actions to attempt after commit

InstallOutcome

  • installed scope
  • resolved mode
  • written paths
  • warnings
  • rollback status

Within crates/aim-core/src/:

  • platform/distro.rs
  • platform/capabilities.rs
  • integration/policy.rs
  • integration/install.rs
  • integration/desktop.rs
  • integration/refresh.rs

Responsibilities:

  • platform/distro.rs detects distro families and policy markers
  • platform/capabilities.rs probes writability and helper availability
  • integration/policy.rs converts requested scope plus host facts into InstallPolicy
  • integration/install.rs owns the transactional install pipeline
  • integration/desktop.rs owns desktop file generation and icon handling
  • integration/refresh.rs owns best-effort helper execution

CLI behavior

  • aim <QUERY> should become a real install path rather than registry-only tracking
  • The CLI should print the resolved install mode before commit
  • If the plan degrades, the CLI should explain that before confirmation
  • Bare aim can later reuse the same policy summary when showing pending updates

Summary

The agreed design is a single install engine in aim-core driven by a resolved per-host InstallPolicy. Distro families provide intended policy, runtime capabilities decide what is safe, and transactional install semantics keep installs recoverable. This preserves the thin-CLI architecture while allowing broad Linux coverage without scattering distro-specific conditionals across the entire codebase.