# 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 ## Recommended Approach 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 ### Recommended module layout 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 ` 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.