23 KiB
Repository Audit: aim (AppImage Manager)
Timestamp: 2026-03-21T02:02:28Z (UTC)
Commit: f260790d91
Branch: main
Repo Path: /home/stoorps/repos/aim
Audit Scope: Full repository — product completeness, provider support, registry operations, docs-vs-code mismatches, missing tests, v1.0 readiness
Executive Summary
The aim AppImage manager has a solid architectural foundation with clean domain separation, good test coverage for GitHub flows, and working install/update/remove operations. However, critical gaps exist that block production v1.0 readiness:
- Download reliability is insufficient — entire files loaded into memory, no resume/retry, no timeouts, risking OOM on 500MB+ AppImages
- Registry safety is weak — no atomic writes, no backups, no corruption recovery, vulnerable to concurrent access
- Documentation is stale — README omits SourceForge, zsync, custom-json providers already in code
- Missing product features — no search, no version pinning, no dry-run, no info command, no rollback
- Checksum verification exists but is unused — metadata contains SHA-512 checksums that are parsed but never validated during downloads
- CustomJsonAdapter is a non-functional stub — declared but all methods return errors
v1.0 Readiness Verdict: Not ready. Core reliability gaps (download safety, registry atomicity, checksum validation) must be closed before production use. Feature gaps (search, info, dry-run) are acceptable for v1.0 but limit user expectations.
Surface Map
Entry Points
- CLI:
aim <query>,aim,aim update,aim list,aim remove <query> - Scope Overrides:
--user,--system - Environment Variables:
AIM_REGISTRY_PATH,AIM_GITHUB_TOKEN,GITHUB_TOKEN,AIM_GITHUB_API_BASE,HOME
Public Interfaces
- Supported Query Forms:
- GitHub shorthand:
owner/repo - GitHub URLs: repository, release, asset
- GitLab URLs: repository, release-like, ambiguous candidates
- SourceForge URLs: project, file download paths
- Direct HTTPS/HTTP URLs
- Local file imports:
file://...
- GitHub shorthand:
Implemented Providers
- Repository-backed: GitHub, GitLab, SourceForge
- Exact-resolution: DirectUrl, File
- Metadata-only: Zsync (update channels), Electron Builder (checksums)
- Stub: CustomJson (all operations return errors)
State & Data Layers
- Registry: TOML file at
~/.local/share/aim/registry.toml, stores installed apps with source, version, update strategy - Install Paths:
- User scope:
~/.local/lib/aim/appimages,~/.local/share/applications,~/.local/share/icons - System scope:
/opt/aim/appimages,/usr/share/applications,/usr/share/icons
- User scope:
- Staging:
.local/share/aim/stagingfor download-before-commit
Cross-Cutting Concerns
- Platform Detection: Distro family (Debian, RedHat, Arch, Immutable, Nix, Alpine, Other), desktop session presence
- Error Handling: Distinct unsupported-query vs no-installable-artifact vs transport-failure errors
- Progress Reporting: Live spinners, byte progress, staged events (resolveQuery, discoverRelease, downloadArtifact, etc.)
Findings (Prioritised Backlog)
GAP-001: Download Reliability — Memory Exhaustion Risk
Type: Reliability / Performance
Severity: Critical
Impact: Production blocker. Large AppImages (500MB+) are loaded entirely into memory before writing to disk. Risky on low-memory systems, can trigger OOM kills.
Evidence:
- crates/aim-core/src/app/add.rs:
download_artifact_bytes_with_reporterallocates aVec<u8>, reads 16KB chunks in a loop, thenextend_from_slice. Full payload lives in heap beforestage_and_commit_payloadwrites it. - No streaming-to-disk, no memory pressure handling.
Repro/Trace:
- Attempt
aim <large-appimage-url>where payload is 1GB - Watch process RSS grow to 1GB+ before any file write
Suggested Fix:
- Stream directly to staged file path using
io::copyor chunked writes with temp file + atomic rename - Alternatively, use memory-mapped I/O for very large files
- Add download timeout via
reqwest::ClientBuilder::timeout
Acceptance Criteria:
- Download writes directly to disk without intermediate full-buffer accumulation
- Memory usage stays constant regardless of AppImage size
- Configurable timeout (default 5 minutes)
GAP-002: Download Reliability — No Resume, Retry, or Timeout
Type: Reliability
Severity: High
Impact: Network interruptions, slow connections, or server timeouts cause complete download restart. Poor UX for large files or unstable networks.
Evidence:
- crates/aim-core/src/app/add.rs: Single
reqwest::blocking::get(url)call, no retry loop, no partial-download resume via Range headers - No timeout configured on
reqwest::blocking::ClientinReqwestGitHubTransport::new()— hangs indefinitely on stalled connections
Suggested Fix:
- Add retry logic (3 attempts with exponential backoff)
- Support HTTP Range requests for resume if server supports it (check
Accept-Rangesheader) - Configure request timeout (30s connect, 5m total)
- Provide
--no-resumeflag to skip range logic if needed
Acceptance Criteria:
- Failed downloads retry up to 3 times
- Partial downloads resume from last byte if server supports Range
- Timeout after 5 minutes of inactivity or 30s connect timeout
GAP-003: Checksum Verification Not Used
Type: Security / Reliability
Severity: High
Impact: Silent corruption or supply-chain attacks. Metadata includes SHA-512 checksums (electron-builder) but downloads are never verified. Corrupted or tampered payloads install successfully.
Evidence:
- crates/aim-core/src/metadata/electron_builder.rs: Parses
sha512:field fromlatest-linux.yml - crates/aim-core/src/domain/update.rs:
MetadataHintsstruct includeschecksum: Option<String> - No verification step in crates/aim-core/src/app/add.rs
download_artifact_bytes_with_reporteror crates/aim-core/src/integration/install.rsstage_and_commit_payload
Repro/Trace:
- Install app with known electron-builder metadata:
aim owner/repo - Inject corrupt bytes into download stream (or MITM proxy)
- Installation succeeds without checksum mismatch error
Suggested Fix:
- After download completes, compute SHA-512 of payload if
checksumhint exists - Fail installation with clear error if mismatch
- Add
--skip-checksumflag for advanced users bypassing validation
Acceptance Criteria:
- SHA-512 checksums from metadata are verified post-download
- Mismatch triggers clear error with expected vs actual hash
- Installs without metadata checksums proceed with warning
GAP-004: Registry Corruption — No Atomic Writes or Backups
Type: Reliability
Severity: High
Impact: Registry corruption if process killed mid-save. No recovery mechanism; complete data loss of installed apps list.
Evidence:
- crates/aim-core/src/registry/store.rs:
RegistryStore::savedirectly writes toself.pathviafs::write. Not atomic (no temp file + rename). - crates/aim-core/src/registry/store.rs:
loadreturnstoml::de::Erroron corrupt file, no fallback to backup
Repro/Trace:
- Start
aim sharkdp/bat - Kill process (
kill -9) during registry save stage - Attempt
aim list— registry file is truncated or corrupt, operation fails
Suggested Fix:
- Atomic save: write to
registry.toml.new, thenfs::renametoregistry.toml - Backup previous registry to
registry.toml.bakbefore overwrite - On load failure, attempt restore from
.bak - Add corruption detection (signature header) for faster failure
Acceptance Criteria:
- Registry saves are atomic (temp file + rename)
- Previous registry backed up before save
- Corrupt registry auto-restores from backup with warning
- Test: kill process mid-save, verify registry intact
GAP-005: Concurrent Access — No Registry Locking
Type: Reliability
Severity: Medium
Impact: Running multiple aim commands simultaneously can corrupt registry (race on read-modify-write). Data loss or duplicate entries possible.
Evidence:
- crates/aim-cli/src/lib.rs:
dispatch_with_reporterloads registry, mutates in-memory copy, saves back. No file lock. - No
flockor lock file mechanism in crates/aim-core/src/registry/store.rs - Grep for
Mutexfound zero results incrates/— no concurrency control
Repro/Trace:
- Terminal A:
aim sharkdp/bat(slow network download) - Terminal B:
aim update(starts while A is downloading) - Both save registry simultaneously, one clobbers the other
Suggested Fix:
- Advisory file lock on
registry.toml.lockusingfs2crate - Acquire lock in
load(), release insave() - Fail fast with clear error if lock unavailable after 5s
Acceptance Criteria:
- Only one
aimprocess can modify registry at a time - Second process waits or fails cleanly with "registry locked" message
- Lock automatically released on crash (advisory lock semantics)
GAP-006: Documentation — Missing Providers in README
Type: Documentation / DX
Severity: Medium
Impact: Users unaware of supported sources. SourceForge, zsync, custom-json implemented but undocumented.
Evidence:
- README.md: Documents GitHub, GitLab, direct URLs, file imports
- crates/aim-core/src/adapters/mod.rs:
all_adapter_kinds()includessourceforge,zsync,custom-json - crates/aim-core/src/source/input.rs:
classify_sourceforge_httpactively classifies SourceForge URLs - grep for "SourceForge" in README.md: no matches
Suggested Fix:
- Update README "Query Forms" section:
- Add SourceForge project URLs and file download URLs
- Note zsync as discovered metadata only (not install source)
- Document or remove custom-json (currently nonfunctional)
- Add examples:
aim https://sourceforge.net/projects/app/files/v1.0/App.AppImage/download
Acceptance Criteria:
- README documents all functional source types
- SourceForge examples included
- Zsync role clarified (metadata vs source)
- Custom-json either documented as experimental or removed from public list
GAP-007: CustomJsonAdapter Is Non-Functional Stub
Type: Missing Feature / Code Quality
Severity: Medium
Impact: Adapter listed in all_adapter_kinds() but completely unusable. Misleading.
Evidence:
- crates/aim-core/src/adapters/custom_json.rs:
normalize()returnsErr(AdapterError::UnsupportedQuery)resolve()returnsErr(AdapterError::UnsupportedSource)- Zero tests for this adapter in
crates/aim-core/tests/
Suggested Fix:
- Option A (remove): Delete adapter, remove from
all_adapter_kinds(), document as future work - Option B (implement): Define JSON schema for custom update metadata, implement resolution logic, add tests
- Option C (mark experimental): Add comment and feature flag, exclude from default builds
Acceptance Criteria:
- Either adapter works with documented schema, or removed from public API
- No adapter listed in
all_adapter_kinds()that returns errors for all operations
GAP-008: Missing CLI Commands — Search, Info, Show
Type: Missing Feature
Severity: Medium
Impact: Users must know exact owner/repo or URL beforehand. No discovery workflow.
Evidence:
- crates/aim-cli/src/cli/args.rs: Only defines
Remove,List,Updatesubcommands - No
aim search,aim info <query>, oraim show <stable-id>commands - GitHub releases API supports search, but no CLI exposure
Suggested Fix:
aim search <keyword>— search GitHub for AppImages (requires GitHub API search endpoint or external index)aim info <query>oraim show <stable-id>— display app details pre-install (version, description, assets) or post-install (installed paths, source, update status)
Acceptance Criteria:
aim searchreturns list of candidate apps with descriptionsaim infoshows app metadata without installingaim show batdisplays installed app details (version, files, source)
GAP-009: Missing CLI Features — Dry-Run, Force, Verbose
Type: Missing Feature
Severity: Medium
Impact: Limited user control over operations. No preview mode, no way to debug issues, no forced reinstall.
Evidence:
- crates/aim-cli/src/cli/args.rs: No
--dry-run,--force,--verbose,--no-desktopflags - All operations execute immediately without preview except update plan (
aimbare shows pending updates but requiresaim updateto execute)
Suggested Fix:
- Add
--dry-run/-n: Show what would be done without mutating state - Add
--force/-f: Reinstall even if already installed at same version - Add
--verbose/-v: Show detailed logs (API requests, file operations) - Add
--no-desktop: Skip desktop integration, install payload only
Acceptance Criteria:
aim --dry-run <query>prints plan without installingaim --force <query>reinstalls existing appaim --verbose listshows debug outputaim --no-desktop <query>installs without .desktop file
GAP-010: No Version Pinning or Update Blocking
Type: Missing Feature
Severity: Medium
Impact: Users cannot pin to specific versions or exclude from updates. All apps track latest by default (unless installed from specific release URL).
Evidence:
- crates/aim-core/src/domain/app.rs:
AppRecordhasinstalled_version,update_strategy, but nopinnedorupdate_policyfield aim updateblindly updates all apps without user filtering
Suggested Fix:
- Add
aim pin <stable-id>andaim unpin <stable-id>commands - Store
pinned: boolorupdate_policy: Auto | Manual | PinnedinAppRecord aim updateskips pinned apps, logs "X apps pinned, skipping"- Add
--ignore <stable-id>flag toaim updatefor one-time exclusion
Acceptance Criteria:
aim pin batpreventsaim updatefrom updating bataim listshows pinned statusaim unpin batre-enables updates
GAP-011: No Rollback or Update Failure Recovery
Type: Reliability
Severity: Medium
Impact: Failed updates leave old binary deleted but registry updated, or vice versa. No way to revert to previous version.
Evidence:
- crates/aim-core/src/app/update.rs:
execute_updates_with_reportercatches errors and recordsUpdateExecutionStatus::Failed, but old payload is already overwritten by crates/aim-core/src/integration/install.rsfs::renamesteps - No backup of old
.AppImagefile before update - grep for
rollbackorbackupfound zero matches
Repro/Trace:
aim sharkdp/bat(install v1.0)- Tamper with network to force update download failure
aim update— old bat.AppImage replaced with corrupt download, registry shows v1.1 but binary is broken
Suggested Fix:
- Before update, rename
app.AppImagetoapp.AppImage.backup - On success, delete backup
- On failure, restore from backup and log rollback
- Add
aim rollback <stable-id>command to restore previous version
Acceptance Criteria:
- Failed updates restore previous payload automatically
- Registry remains consistent with installed payload
- Manual rollback command available for user-initiated revert
GAP-012: No Proxy Support or Network Configuration
Type: Missing Feature
Severity: Low
Impact: Corporate or privacy-conscious users behind proxies cannot use aim.
Evidence:
- crates/aim-core/src/source/github.rs:
ReqwestGitHubTransport::new()uses defaultreqwest::blocking::Clientbuilder, no proxy configuration - grep for
proxy,http_proxy,https_proxy: zero matches
Suggested Fix:
- Respect
HTTP_PROXY,HTTPS_PROXY,NO_PROXYenvironment variables - Use
reqwest::ClientBuilder::proxy()to configure - Add
--proxy <url>CLI flag for override
Acceptance Criteria:
HTTP_PROXY=http://proxy:8080 aim <query>uses proxy- Corporate proxy users can install apps
GAP-013: No Offline or Cache-First Mode
Type: Missing Feature
Severity: Low
Impact: Cannot use aim in air-gapped or intermittent-connectivity environments. Every operation requires network.
Evidence:
- All provider adapters make live HTTP requests, no local cache
Suggested Fix:
- Cache GitHub release metadata in
~/.cache/aim/metadata/for 1 hour - Add
--offlineflag to use cached data only - Add
--refreshto force cache invalidation
Acceptance Criteria:
aim --offline listworks without network if metadata cached- Metadata cached for 1 hour, revalidated on next request
GAP-014: No Help Text or Usage Examples
Type: Documentation / DX
Severity: Low
Impact: Users must read README or guess commands. Poor discoverability.
Evidence:
- crates/aim-cli/src/cli/args.rs: Clap definitions exist but no detailed
abouttext or examples aim --helpoutput is minimal (auto-generated clap summary)
Suggested Fix:
- Add
#[command(long_about = "...")]with detailed usage examples - Add
#[arg(help = "...")]for each flag - Include examples in help:
aim sharkdp/bat,aim list,aim remove bat
Acceptance Criteria:
aim --helpshows examples and detailed descriptions- Each subcommand has helpful usage text
GAP-015: No CI-Friendly Output or Machine-Parseable Formats
Type: Missing Feature
Severity: Low
Impact: Cannot integrate aim into automation or scripts. Output is human-readable only.
Evidence:
- crates/aim-cli/src/ui/render.rs: Output is styled text tables, no JSON or CSV option
Suggested Fix:
- Add
--format json|yaml|csvflag - Add
--quiet/-qfor errors-only output
Acceptance Criteria:
aim list --format jsonreturns machine-parseable outputaim --quiet <query>suppresses progress spinners
GAP-016: Test Coverage — Missing Tests
Type: Testing / Quality
Severity: Medium
Impact: Gaps in reliability verification.
Evidence:
- CustomJsonAdapter: Zero tests in crates/aim-core/tests/
- Concurrency: No tests simulating concurrent registry access
- Corruption recovery: No tests for registry load failures + backup restore
- Large files: No tests for memory usage on 500MB+ downloads
- Network failures: Limited retry/timeout tests
- grep for
custom_jsonintests/: zero matches
Suggested Fix:
- Add
custom_json_adapter_contract_testif adapter is retained - Add
concurrent_registry_access_testusing threads - Add
registry_corruption_recovery_testwith intentionally corrupt TOML - Add
large_file_download_memory_testmocking 1GB payload - Add
network_failure_retry_testwith flaky mock server
Acceptance Criteria:
- All adapters in
all_adapter_kinds()have contract tests - Concurrency edge cases covered
- Corruption recovery path validated
Quick Wins
Fixes that deliver high user value with low implementation cost:
- GAP-006 (Documentation) — Update README with SourceForge, zsync usage (~30 minutes)
- GAP-014 (Help Text) — Add detailed clap help strings (~1 hour)
- GAP-009 (Dry-Run Flag) — Add
--dry-runflag that skips final save (~2 hours) - GAP-007 (CustomJson Cleanup) — Remove non-functional adapter or add experimental marker (~30 minutes)
- GAP-004 (Registry Backups) — Copy registry to
.bakbefore save (~1 hour)
Open Questions
- Checksums: Should aim fail-closed (reject unverified downloads) or warn-only? Some sources lack checksums.
- Update Policy: Default to auto-update all apps, or require explicit
aim update <stable-id>? - Version Pinning: Should pinned apps show in
aim listwith special marker, or hidden? - Search Scope: Limit search to apps with AppImage releases, or show all repos?
- CustomJson: Is this intended for user-defined update feeds? Should it support local JSON files or HTTP endpoints?
- Offline Mode: Cache release metadata only, or cache payloads too?
- Platform Support: Current code is Linux-only (by design). Document macOS/Windows as non-goals?
- Concurrency: Should
aimsupport lock wait timeout (fail fast) or infinite retry?
Appendix
Notable Search Patterns
TODO|FIXME|HACK: 0 matches in Rust source (clean codebase)unimplemented!: 0 matchespanic!: Minimal use, only in test fixtures.unwrap(): Mostly in tests, production code usesResult
Files Reviewed
- All
.rsfiles incrates/aim-core/src/(app, adapters, domain, integration, platform, registry, source, update, metadata) - All
.rsfiles incrates/aim-cli/src/(cli, ui, lib, main) - All test files in
crates/aim-core/tests/andcrates/aim-cli/tests/ - README.md, Cargo.toml
- Planning docs in .plans/007-source-provider-expansion/
Test Coverage Hotspots
- Strong: GitHub flows (discovery, parsing, install, update), GitLab basic flows, SourceForge URL classification
- Moderate: DirectUrl, file imports, metadata parsing (zsync, electron-builder), platform detection, remove flows
- Weak: CustomJson (zero), concurrency, corruption recovery, network failures, large file handling
v1.0 Readiness Verdict
Status: Pre-Alpha → Alpha (not v1.0-ready)
Blockers for v1.0:
- GAP-001 (Memory exhaustion) — Critical reliability issue
- GAP-003 (Checksum verification) — Critical security issue
- GAP-004 (Registry atomicity) — Critical data safety issue
- GAP-005 (Concurrent access) — High-severity correctness issue
Nice-to-Have for v1.0:
- Search, info commands
- Dry-run mode
- Proxy support
- Better help text
Recommendation:
Close GAP-001, GAP-003, GAP-004, GAP-005 before any production announcement. After those fixes, aim reaches beta status (core safety established). User-facing features (search, pin, rollback) can ship in v1.1+.
Effort Estimate (Blockers Only): ~3-5 engineering days for someone familiar with Rust + reqwest + file I/O patterns.
Current Strengths:
- Clean architecture (adapters, domain, app separation)
- Provider expansion path is well-designed
- Good test coverage for happy paths
- Terminal UX is polished (progress bars, styled output)
- Platform detection handles immutable distros, Nix, etc.
Overall Quality: Well-built for internal/hobby use. Needs production hardening for public v1.0.