# pag – PaganOS Package Manager [![Version](https://img.shields.io/badge/pag-3.3.20-blue)](https://git.paganlinux.eu/pag) [![Platform](https://img.shields.io/badge/PaganOS-Linux-success)](https://paganlinux.eu) Package manager for [PaganOS](https://paganlinux.eu). Atomic installation (staging → rename), GPG signature verification with fingerprint pinning, per-file SHA256, full transaction rollback, hooks and triggers, and support for immutable systems with deployments. Diagrams in this README are rendered with Mermaid – supported by the `git.paganlinux.eu` portal (and GitHub/Gitea). ```bash pag --version # client version (e.g. pag 3.3.20) pag # full help (command list) pag --help # same as above ``` --- ## Table of contents - [Quick start](#quick-start) - [How it works](#how-it-works) - [`pag install` flow](#pag-install-flow) - [Trust chain](#trust-chain) - [Immutable system](#immutable-system) - [Rollback](#rollback) - [Commands](#commands) - [Environment variables](#environment-variables) - [Paths and files](#paths-and-files) - [Package format](#package-format) - [Hooks and triggers](#hooks-and-triggers) - [`/etc` configuration – `.pacnew` / `.pacsave`](#etc-configuration--pacnew--pacsave) - [Examples](#examples) - [Troubleshooting](#troubleshooting) - [Install and update](#install-and-update) - [License](#license) --- ## Quick start ```bash # Refresh repository indexes (and see what is pending an update) sudo pag sync # Install / remove sudo pag install firefox gimp sudo pag remove gimp # Update packages only… sudo pag update # …or the whole system: packages + kernel / initramfs / GRUB sudo pag upgrade # Verify the integrity of all files sudo pag verify --deep ``` --- ## How it works ### `pag install` flow ```mermaid graph TD A["pag install pkg"] --> B["Dependency resolution (DFS + provides)"] B --> C{"Missing dependencies?"} C -->|yes| C1["Error – abort"] C -->|no| D["ABI verification (so-name)"] D --> E["Pre-flight: free space + RW mount"] E --> F["flock + database snapshot"] F --> G["Parallel package download"] G --> H["GPG + package SHA256 verification"] H --> I["Staging: extract + per-file sums"] I --> J["Atomic rename of files into the system"] J --> K["pre/post-install hooks"] K --> L["Write to SQLite + transaction history"] L --> M{"PAG_IMMUTABLE=1?"} M -->|yes| N["New deployment + GRUB entries"] M -->|no| O["ldconfig + triggers"] H -->|error| R["Rollback of the whole transaction"] I -->|error| R J -->|error| R R --> S["Restore database and files"] ``` Key properties: - **Atomicity** – files first go to staging on the same partition as `/`, then are moved with `rename()` (no half-install). - **Transactionality** – if any package fails, the whole transaction is rolled back (`installed.json`, files, backups). - **File sharing** – a file owned by two packages is not removed when one of them is removed (only the database entry is removed). ### Trust chain ```mermaid graph TD A["repo.json + repo.json.asc"] --> B["GPG: VALIDSIG line"] B --> C["Repo fingerprint pinning (TOFU → pin)"] C --> D["Package index"] D --> E["package .pag + .asc"] E --> F["GPG: signature matches repo pin"] F --> G["Whole-package SHA256"] G --> H["Safe extraction (anti-traversal)"] H --> I["sums.json: per-file SHA256"] I --> J["Strip SUID bit"] J --> K["Install"] F -->|"missing / bad signature"| X["Reject package"] G -->|"SHA256 mismatch"| X H -->|"traversal / symlink escape"| X ``` - First use of a key = **TOFU**, afterwards the fingerprint is **pinned** (`pag key-trust` / `pag key-untrust`). - Verification is **fail-closed**: missing/bad signature = no installation (override only with `PAG_INSECURE=1`, build/dev only). - `pag self-update` goes through the same path: GPG → SHA256 → syntax check → atomic client replacement. ### Immutable system With `PAG_IMMUTABLE=1`, installation does not mutate `/`; it creates a new deployment. Rollback is just switching the `active` symlink. ```mermaid graph TD R["/"] --> D["/.deployments"] D --> A["active → 20260723T120000"] D --> D1["20260723T120000 (new)"] D --> D2["20260722T090000 (previous)"] D1 --> U["usr/ bin/ lib/ – system"] D1 --> S1["var → /var"] D1 --> S2["etc → /etc"] D1 --> S3["home → /home"] D1 --> S4["boot → /boot"] D2 -.->|"deploy-rollback"| A ``` - `/var`, `/etc`, `/home`, `/boot` (and other `SHARED_PATHS`) are **shared** between deployments – they are not duplicated. - `/boot` shares the kernel and initramfs, saving space per deployment. - Rollback from the bootloader: each deployment has its own GRUB entry (`pag grub-update`). ### Rollback ```mermaid graph TD T["Transaction"] --> OK{"Success?"} OK -->|yes| H["History + snapshot + file journal"] OK -->|no| RB["Automatic transaction rollback"] H --> R["pag rollback"] R --> R1["Restore installed.json from snapshot"] R --> R2["Remove new files from the journal"] R --> R3["Restore overwritten files from backup"] R --> R4["Clean up empty directories"] RB --> R1 RB --> R2 RB --> R3 ``` `pag history` shows recent transactions (including executed hooks), and `pag rollback` reverts the **last successful** transaction with a snapshot. --- ## Commands ### BASICS | Command | Description | |---|---| | `pag install ...` | Install packages (together with dependencies) | | `pag install -f ...` | **Force reinstall** (even the same version) – restores files, empty directories and hooks | | `pag remove ...` | Remove packages | | `pag update` | Update **packages** to newer versions | | `pag sync` | Refresh repository indexes + show how many packages await update | | `pag upgrade` | **System** update: packages + kernel / initramfs / GRUB | | `pag list` | List packages available in the repository | | `pag list --installed` | List installed packages | | `pag search ` | Search packages in the repo (+ Flathub) | | `pag info ` | Package details (version, dependencies, size, signature) | | `pag files ` | List files owned by a package | | `pag verify` | Verify the integrity of installed files | | `pag verify --deep` | Full per-file SHA256 verification | | `pag clean` | Clear the download cache | | `pag stats` | System statistics (package count, size, cache…) | | `pag download ...` | Download packages to cache (offline mode) | **Smart search:** `pag ` (any unknown command) searches the repo and Flathub and suggests names – e.g. `pag firefox` will find the package. ### SECURITY / GPG KEYS | Command | Description | |---|---| | `pag key-add ` | Import a repository GPG key | | `pag key-list` | List trusted keys | | `pag key-remove ` | Remove a key | | `pag key-trust ` | Pin the repo key fingerprint (TOFU disabled) | | `pag key-untrust ` | Forget the fingerprint (back to TOFU) | | `pag key-trusted` | List pinned repo fingerprints | ### ADVANCED | Command | Description | |---|---| | `pag why ` | Why a package is installed (who depends on it) | | `pag autoremove` | Remove orphaned dependencies | | `pag remove-orphans` | Remove orphaned dependencies (alias) | | `pag pin [version]` | Pin a package to a version (block updates) | | `pag unpin ` | Unpin | | `pag pinned` | List pinned packages | | `pag history` | Transaction history | | `pag rollback` | Revert the **last** transaction (restore files from backup) | | `pag repo-add [name]` | Add a repository (drop-in in `/etc/pag/repos/`) | | `pag repo-list` | List configured repositories | | `pag sbom export [spdx\|cyclonedx]` | Export an SBOM manifest of all installed components | | `pag self-update` | Update the `pag` client itself (GPG signature + SHA256 + syntax, atomically) | ### FLATPAK | Command | Description | |---|---| | `pag flatpak []` | Search and install from Flathub | | `pag flatpak search ` | Search Flathub | | `pag flatpak install ` | Install a flatpak | | `pag flatpak remove ` | Remove a flatpak | | `pag flatpak list` | List installed flatpaks | | `pag flatpak update` | Update all flatpaks | | `pag flatpak info ` | Flatpak details | > Installation as root forces the **system** scope (`--system`), so the > application is visible to all users, not just root. ### IMMUTABLE SYSTEM (`PAG_IMMUTABLE=1`) | Command | Description | |---|---| | `pag deploy-list` | List deployments | | `pag deploy-rollback` | Switch to the previous deployment | | `pag deploy-cleanup [N]` | Remove old deployments (keep N, default 3) | | `pag initramfs-update` | Rebuild initramfs | | `pag grub-update` | Regenerate GRUB entries for all deployments | --- ## Environment variables | Variable | Meaning | |---|---| | `PAG_ROOT` | Alternative operation root (tests/chroot); default `/` | | `PAG_IMMUTABLE=1` | Immutable mode – installation creates a new deployment | | `PAG_YES=1` | Automatic confirmation (same as `-y` / `--yes`) | | `PAG_INSECURE=1` | Disables the HTTPS requirement and fail-closed GPG (**build/dev only!**) | | `PAG_NO_HOOKS=1` | Skip hooks and triggers | | `PAG_HOOK_TIMEOUT` | Hook timeout in seconds (default `60`) | | `PAG_LANG_DIR` | Directory with translation files (`pl.json`, `en.json`) | | `PAG_LANG_NO_FILES=1` | Ignore translation files (export built-ins) | | `PAG_ROOT_DEVICE` / `PAG_GRUB_ROOT` | Explicit `root=` for GRUB (ISO/IMG build) | | `PAG_IN_CHROOT=1` | Force chroot mode when detecting the root device | --- ## Paths and files | Path | Purpose | |---|---| | `/var/lib/pag/` | State database: `installed.json`, `files.db`, `world`, `pinned.json`, `history.json`, `pag.lock`, `hooks/` | | `/var/lib/pag/files.db` | SQLite: file owners + SHA256 sums | | `/var/cache/pag/` | Downloaded package cache | | `/var/cache/pag/repos/` | Repository index cache (JSON + ETag + timestamp) | | `/etc/pag/repos.conf` and `/etc/pag/repos/*.conf` | Repositories (drop-in) | | `/etc/pag/trusted.json` | Pinned repo key fingerprints | | `/etc/pag/gpg/` | Isolated GPG keyring | | `/etc/pag/triggers/*.json` | Custom triggers | | `/etc/pag/lang/`, `/usr/share/pag/lang/` | Translations (files override built-ins) | | `/var/log/pag/audit.log` | Audit: hooks, self-update | | `/.pag_staging` | Staging (same partition as `/` – no `EXDEV`) | | `/.deployments/` | Deployments (immutable mode) | --- ## Package format A `.pag` package is an archive containing: ``` metadata.json – name, version, release, dependencies, provides/requires (so-name) data.tar.xz – system files + sums.json (per-file SHA256) hooks/ – optional: pre-install, post-install, pre-remove, post-remove ``` Installation is **verified per file** against `sums.json`, and extraction is protected against *directory traversal* and escape via malicious symlinks. --- ## Hooks and triggers **Hooks** run as `root` (like apt/pacman), in a restricted environment: - `pre-install`, `post-install`, `pre-remove`, `post-remove` - clean env: `PATH`, `HOME=/root`, `LANG/LC_ALL=C.UTF-8`, `PKG_NAME`, `PKG_VERSION`, `PKG_ACTION`, `PKG_HOOK_API=1` - timeout (`PAG_HOOK_TIMEOUT`), optional disable (`PAG_NO_HOOKS=1`), entries in `/var/log/pag/audit.log` and in the transaction history You install code you trust – hooks have full privileges. **Triggers** run **once per transaction**, when matching paths were touched (only if the given binary exists): | Trigger | Paths | Command | |---|---|---| | `font-cache` | `/usr/share/fonts/`, `/usr/local/share/fonts/` | `fc-cache -fs` | | `glib-schemas` | `/usr/share/glib-2.0/schemas/` | `glib-compile-schemas …` | | `desktop-database` | `/usr/share/applications/` | `update-desktop-database -q …` | | `mime-database` | `/usr/share/mime/` | `update-mime-database …` | You can add your own triggers as `/etc/pag/triggers/*.json`. --- ## `/etc` configuration – `.pacnew` / `.pacsave` The `/etc` directory is shared between deployments (it is not reverted by `deploy-rollback`). To avoid losing user changes: - **Updating** a configuration file that the user modified: the new version is written as `.pacnew`, and the user's file **is kept**. - **Removing** a package with a modified configuration file: the file becomes `.pacsave` instead of being deleted. The comparison uses the SHA256 sum recorded at install time. --- ## Examples ```bash # Install and updates sudo pag install firefox gimp sudo pag update # packages only sudo pag upgrade # packages + kernel/initramfs/GRUB sudo pag sync # refresh indexes + update info # Repair a package (missing files/directories, e.g. /etc/pulse/default.pa.d) sudo pag install -f pulseaudio # Application icons after installing an icon theme (cache built automatically) sudo pag install papirus-icon-theme # Security sudo pag key-add https://repo.paganlinux.eu/stable/paganos.asc sudo pag key-trust https://repo.paganlinux.eu/stable/ sudo pag verify --deep # Dependencies and package provenance sudo pag why libjpeg-turbo sudo pag autoremove # SBOM for audit / compliance sudo pag sbom export cyclonedx > sbom.json # Revert the last transaction sudo pag rollback ``` --- ## Troubleshooting | Symptom | Solution | |---|---| | "Another pag instance is running" | Another `pag` process is active (`flock` lock). Wait or check `pgrep -af pag`. | | "Cannot refresh the index" | The repo cache is read-only for a regular user – run as root: `sudo pag sync`. | | "Invalid signature / missing signature" | Import the key: `sudo pag key-add `, then verify `sudo pag key-trusted`. | | Missing files/directories after install | `sudo pag install -f ` (restores files, empty directories and hooks). | | System does not boot after `grub-update` | Set the root device explicitly: `PAG_ROOT_DEVICE=/dev/sda2 sudo pag grub-update`. | | Flatpak app not visible in the menu | Add `/var/lib/flatpak/exports/share` to `XDG_DATA_DIRS` and log in again. | --- ## Install and update ```bash # On a running PaganOS, pag is in the base repository: sudo pag install pag # install from the repository sudo pag self-update # update the client itself (signed) # pag is also managed by the package manager – after a new version is # published in the repo: sudo pag update sudo pag upgrade ``` --- ## License PaganOS / pag – an open source project. See the distribution repository for details.