# CLAUDE.md Obsidian: Projects/cockpit-pacman This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Related Documentation **Project notes**: `/home/josie/Documents/Obsidian Vault/Projects/cockpit-pacman/` - `README.md` - Synced from project README - `todo.md` - Synced from project todo.txt **ALPM ecosystem notes**: `/home/josie/Documents/Obsidian Vault/40 Arch Linux/alpm/` - `alpm.rs.md` - Rust alpm crate documentation - `libalpm.md` - Core libalpm C library - `pacman.md` - Pacman package manager - `pacmanconf.rs.md` - Rust pacmanconf crate - `pacman-key.rs.md` - Rust pacman-key crate (used in this project) - `pacman-log.rs.md` - Rust pacman-log crate (used in this project) - `cockpit-pacman.md` - This project's architecture notes - `Arch Package Management Ecosystem.md` - Overview of the ecosystem ## Project Overview cockpit-pacman is a Cockpit plugin for Arch Linux package management. It provides a web UI for viewing installed packages, checking/applying updates, searching repositories, managing orphans, cleaning cache, and viewing pacman history. ## Architecture **Two-component system:** - **Frontend**: React 18/TypeScript app using PatternFly 6 components, bundled with esbuild - **Backend**: Rust binary using alpm 5 for direct pacman database access **Communication**: Frontend spawns backend via Cockpit's `cockpit.spawn()` API. Backend outputs JSON to stdout. Streaming operations (sync, upgrade, keyring, orphan removal, cache cleaning, downgrade) emit newline-delimited JSON events. **Configuration**: Ignored packages (excluded from upgrades) are persisted to `/etc/cockpit-pacman/config.json`. ## Project Structure ``` src/ api.ts # Frontend API layer, TypeScript interfaces, cockpit.spawn wrapper index.tsx # Entry point, theme detection, React root constants.ts # Shared constants (PER_PAGE_OPTIONS, SEARCH_DEBOUNCE_MS, BACKEND_TIMEOUT_MS, STREAMING_TIMEOUT_SECS, MAX_LOG_SIZE_BYTES, LOG_CONTAINER_HEIGHT) utils.ts # Input sanitization utilities (sanitizeSearchInput, sanitizeUrl) types/ cockpit.d.ts # Cockpit API type definitions components/ App.tsx # Main app with 7 tabs: Updates, Installed Packages, Search Packages, History, Orphans, Cache, Keyring UpdatesView.tsx # Check/apply updates with preflight, streaming progress, ignored package management PackageList.tsx # Browse installed packages with filtering/pagination/sorting SearchView.tsx # Search available packages across repos with debounced input KeyringView.tsx # Manage pacman keyring and PGP keys OrphansView.tsx # Display and remove orphan packages CacheView.tsx # Package cache management and cleanup with configurable retention HistoryView.tsx # View pacman.log with filtering by action type PackageDetailsModal.tsx # Shared package info modal (dependencies, conflicts, provides) IgnoredPackagesModal.tsx # Modal for managing ignored packages during upgrades DowngradeModal.tsx # Modal for downgrading packages to cached versions ErrorBoundary.tsx # React error boundary wrapper with fallback UI hooks/ useDebounce.ts # useDebouncedValue hook usePackageDetails.ts # Shared hook for fetching package details (local, sync, local-then-sync strategies) useSortableTable.ts # Table sorting state management hook test/ setup.ts # Vitest setup with cockpit mock mocks.ts # Mock data for all API responses backend/src/ lib.rs # Library root exports main.rs # CLI entry point, command dispatch (21 commands) models.rs # Rust structs matching frontend TypeScript interfaces config.rs # AppConfig struct, JSON persistence to /etc/cockpit-pacman/config.json db.rs # Repo map caching (Arc with Mutex) util.rs # Signal handling, event emission, timeout guards, sort utilities validation.rs # Input validation (package names, search, pagination) tests.rs # Serialization and validation tests alpm/ mod.rs # ALPM handle initialization, enum string conversions transaction.rs # TransactionGuard RAII wrapper callbacks.rs # Log and download callback setup handlers/ mod.rs # Handler module exports (7 submodules) query.rs # Read-only: list, search, check-updates, info, list-orphans mutation.rs # Write ops: sync-database, upgrade, preflight, remove-orphans keyring.rs # Keyring status, refresh, init cache.rs # Cache info and cleanup operations log.rs # History view handler using pacman-log crate downgrade.rs # List cached versions and downgrade packages config.rs # List/add/remove ignored packages ``` ## Build Commands ```bash make build # Build both frontend and backend make build-frontend # npm ci && npm run build make build-backend # cd backend && cargo build --release make lint # Run all linters make lint-frontend # npm run lint (eslint) make lint-backend # cargo clippy -- -D warnings make test # Run all tests make test-frontend # npm test (vitest) make test-backend # cd backend && cargo test make check # lint + typecheck + test npm run typecheck # TypeScript type checking npm run watch # Dev mode with auto-rebuild ``` ## Development Setup ```bash make devel-install # Symlinks dist/ to ~/.local/share/cockpit/pacman npm run watch # Watch mode for frontend changes ``` Backend binary is at `backend/target/release/cockpit-pacman-backend`. For development it symlinks to `~/.local/libexec/cockpit-pacman/`. ## Backend Patterns **Transaction management**: Uses `TransactionGuard` RAII wrapper for alpm transactions. The guard initializes the transaction on construction and automatically releases it on drop, ensuring cleanup on all exit paths (success, error, cancellation). Note: `remove_orphans()` uses manual transaction handling due to alpm borrow checker limitations (see "Upstream Work" section). **Single-pass filtering**: `list_installed()` uses a single `fold` operation to collect repository info, apply filters, and count packages by reason in one pass over the package list. **Repo map caching**: `get_repo_map()` returns `Arc>` from a static cache, avoiding repeated HashMap construction and cloning. **Signal handling**: `ctrlc` handler sets atomic bool checked between ALPM operations. Cannot interrupt blocking ALPM calls. **Timeout guards**: `TimeoutGuard` struct tracks elapsed time for long operations, checked between ALPM phases. **Structured errors**: `BackendError` type with `ErrorCode` enum for typed error handling. Frontend can match on error codes (database_locked, timeout, network_error, etc.) for user-friendly messages. **Config persistence**: `AppConfig` struct with `ignored_packages` list, serialized to JSON at `/etc/cockpit-pacman/config.json`. ## Backend CLI The backend supports 21 commands: **Query commands (read-only):** - `list-installed [offset] [limit] [search] [filter] [repo] [sort_by] [sort_dir]` - `check-updates` - `local-package-info NAME` - `sync-package-info NAME [REPO]` - `search QUERY [offset] [limit] [installed] [sort_by] [sort_dir]` - `list-orphans` - `list-ignored` - `cache-info` - `history [offset] [limit] [filter]` - `list-downgrades NAME` - `keyring-status` **Mutation commands (require root, stream events):** - `preflight-upgrade [ignore]` - dry-run, returns conflicts/replacements/keys to import - `sync-database [force] [timeout]` - `upgrade [ignore] [timeout]` - `remove-orphans [timeout]` - `clean-cache [KEEP]` - keep N versions, remove older - `downgrade NAME VERSION [timeout]` - `refresh-keyring` - `init-keyring` - `add-ignored NAME` - `remove-ignored NAME` ## Frontend Patterns **State flow**: Components maintain local state (useState), call API functions from api.ts, which spawn backend processes via cockpit.spawn(). **Debouncing**: Search inputs use `useDebouncedValue` hook with 300ms delay to avoid excessive backend calls. **Streaming**: `runStreamingBackend()` in api.ts handles newline-delimited JSON events for progress/log updates during sync, upgrade, orphan removal, cache cleaning, and keyring operations. **Error boundaries**: `ErrorBoundary` component wraps each tab to catch React rendering errors gracefully. **Input sanitization**: `sanitizeSearchInput()` and `sanitizeUrl()` utilities for security. Unicode normalization (NFC) prevents Unicode-based attacks. **Typed errors**: `BackendError` class with static helpers (`isTimeout()`, `isLocked()`, etc.) for structured error handling. **Number formatting**: `formatNumber()` utility in api.ts formats numbers with locale-aware thousands separators for consistent display across stat boxes and counts. ## Keyring Management **Current approach**: Keyring operations use the `pacman-key` crate (https://github.com/pfeifferj/pacman-key-rs) for native Rust keyring management. The keyring lives at `/etc/pacman.d/gnupg`. **Future direction**: The ALPM Rust project is developing VOA (Verification of OS Artifacts) as a stateless replacement for GnuPG keyrings. When pacman/libalpm adopts VOA, we should migrate to the voa-* crates. See: - https://gitlab.archlinux.org/archlinux/alpm/alpm - https://devblog.archlinux.page/2026/a-year-of-work-on-the-alpm-project/ ## Testing **Frontend**: Vitest with jsdom and React Testing Library. Test files are `*.test.ts(x)` alongside source files. Uses `vi.mock()` for API mocking, `cleanup()` in afterEach. **Backend**: Tests in `backend/src/tests.rs` cover serialization and validation. No ALPM integration tests (would require live system access). **E2E**: Playwright infrastructure for end-to-end testing. **Test data**: Mock responses defined in `src/test/mocks.ts` for all API types. ## Known Issues and Technical Debt ### Critical (requires immediate attention) **Backend:** - TOCTOU race in config file operations (config.rs:87-100) - file existence check and open are not atomic, lock acquired after file open **Frontend:** - Race condition in timeout handling (api.ts:257-288) - `settled` flag check and set is not atomic, timeout could fire simultaneously with promise resolution - Memory leak in SearchView debounce (SearchView.tsx:100-156) - async operation continues after unmount, no cancellation token/AbortController ### High Priority **Backend:** - Unvalidated JSON parsing from CLI (main.rs:304-306) - save-mirrorlist parses arbitrary JSON before validation, memory exhaustion risk - Unbounded memory in repo map cache (db.rs:33-42) - static HashMap lives for process lifetime, clones repo_name per package **Frontend:** - Stale closure in selected packages initialization (UpdatesView.tsx:273-278) - rapid state changes overwrite user selections - Unsafe URL rendering (PackageDetailsModal.tsx:109-111) - original URL displayed as link text while href is sanitized, potential social engineering - Missing component tests for UpdatesView, SearchView, CacheView, OrphansView, KeyringView - UpdatesView has many useState calls - consider useReducer ### Medium Priority **Backend:** - Inconsistent rollback error handling in config (config.rs:193-215) - uses `let _ = ...` to ignore rollback errors - No cleanup of orphaned temp files (mirrors.rs:281-286) - temp file with PID in name not cleaned on crash - Missing validation in downgrade filename parsing (downgrade.rs:236-259) - no bounds on cache directory iteration - String cloning in db.rs build_repo_map_uncached - clones repo_name per package instead of Arc - Repo map cache only invalidated in sync_database - No test coverage for mutation operations - No negative test cases for validation - Upgrade auto-answers conflicts/removals/replacements **Frontend:** - Silent error swallowing loading ignored packages (UpdatesView.tsx:246-253) - catch block does nothing - Array.includes performance with configIgnored (UpdatesView.tsx:855) - O(n) lookup in render loop, should be Set - Non-exhaustive StreamEvent type handling (api.ts:476-494) - unknown event types silently ignored - Duplicate inline stat box styling across UpdatesView, CacheView, HistoryView - useSortableTable hook not used in CacheView, HistoryView, DowngradeModal - Accessibility - missing aria-labels, color-only indicators - Magic numbers for column indices (UpdatesView.tsx:126-128, SearchView.tsx:61-64) - Incomplete type definitions - Cockpit API defined in both cockpit.d.ts and api.ts inline - Missing null checks in array operations (PackageDetailsModal.tsx:172-178) - Weak keys in list rendering (UpdatesView.tsx:666, 716) ### Low Priority **Frontend:** - Insufficient input validation feedback (utils.ts:4-6) - truncation at 256 chars without user notification - Inconsistent error handling in SearchView (SearchView.tsx:282-284) - shows error at component level instead of modal ## Upstream Work ### Bug Reports (alpm crate) Issues and limitations encountered with the `alpm` Rust crate that should be addressed upstream at https://gitlab.archlinux.org/archlinux/alpm/alpm: **1. TransactionGuard design vs alpm API** - **Issue**: Our `TransactionGuard` holds `&'a mut Alpm` for its entire lifetime, which conflicts with `localdb()` lookups during removal operations - **Details**: The alpm crate intentionally has `trans_init` not require `&mut self`, allowing package references to be passed to `trans_remove_pkg` then consumed. Our RAII guard fights against this design by holding the mutable borrow too long. - **Current approach**: Use manual `trans_init`/`trans_release` for `remove_orphans()`, which works correctly - **Resolution**: This is a design tradeoff in our abstraction, not an alpm bug. PR #63 proposing `trans_*_by_name` methods was declined - the alpm API is intentional. See Obsidian notes `alpm-upstream-issues.md` for full discussion. **2. Error enum variants are unit types** - **Issue**: `alpm::Error` variants like `Retrieve`, `PkgInvalid`, `Io` are unit variants, not tuple variants - **Details**: Cannot pattern match on error details (e.g., `Retrieve(_)` doesn't work) - **Impact**: Limits ability to extract detailed error information for user-friendly messages - **Suggested fix**: Consider wrapping error variants with additional context where applicable **3. vercmp signature requires Into>** - **Issue**: `alpm::vercmp()` has signature `fn vercmp>>(a: S, b: S) -> Ordering` - **Details**: `&String` doesn't implement `Into>`, requiring `.as_str()` conversion - **Suggested fix**: Accept `AsRef` or `&str` directly since version strings are always valid UTF-8 ### Potential Contributions to alpm-utils The `alpm-utils` crate currently provides: config re-export, dependency checking, Target types, and Alpm initialization helpers. Our backend implements several utilities that could be contributed: **High value candidates:** | Function | Location | Description | |----------|----------|-------------| | `parse_package_filename()` | util.rs:153 | Parses `{name}-{ver}-{rel}-{arch}.pkg.tar.{ext}` format - useful for any cache tooling | | Orphan detection | query.rs:377-383 | Pattern: `reason == Depend && required_by.is_empty() && optional_for.is_empty()` | | `check_updates()` | query.rs:140-170 | Local vs sync version comparison - common operation | | Repo map building | db.rs | Maps package names to source repositories | **Lower priority:** | Function | Location | Notes | |----------|----------|-------| | `get_cache_dir()` | util.rs:122 | Reads from env, pacman.conf, or default | | `get_log_path()` | util.rs:137 | Reads from env, pacman.conf, or default | ### Extracted Crate: pacman-log The `pacman-log` crate (https://github.com/pfeifferj/pacman-log.rs) was extracted from this project's original log parsing code. It provides: - Forward and reverse log reading (optimized for large files >10MB) - Typed log entries with action, package, and version fields - Action filtering: Installed, Removed, Upgraded, Downgraded, Reinstalled - Time range filtering with since/until - Package name filtering (substring and exact match) Non-package log lines (frontend messages, scriptlet output) are automatically skipped - the crate focuses on package operations only. ## Key Dependencies **Frontend**: - react 18.3, react-dom 18.3 - @patternfly/react-core 6.1, @patternfly/react-table 6.1 - esbuild 0.24 (bundler) - vitest 2.1, @testing-library/react 16 (testing) - typescript 5.6, eslint 9 **Backend**: - alpm 5, alpm-utils 5 (pacman library bindings) - pacmanconf 3 (config parser) - pacman-key 0.1.0 (keyring management) - pacman-log 0.1.0 (log parsing) - tokio 1 (async runtime for keyring ops) - serde 1, serde_json 1 (JSON serialization) - anyhow 1 (error handling) - ctrlc 3 (signal handling)