276 lines
No EOL
9 KiB
Markdown
276 lines
No EOL
9 KiB
Markdown
# 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 <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. |