← pag

Commit f0fc22e

2
plików
+409
dodanych
-84
usuniętych
@@ -0,0 +1,409 @@
1 +# pag – PaganOS Package Manager
2 +
3 +[![Version](https://img.shields.io/badge/pag-3.3.20-blue)](https://git.paganlinux.eu/pag)
4 +[![Platform](https://img.shields.io/badge/PaganOS-Linux-success)](https://paganlinux.eu)
5 +
6 +Package manager for [PaganOS](https://paganlinux.eu). Atomic installation
7 +(staging → rename), GPG signature verification with fingerprint pinning,
8 +per-file SHA256, full transaction rollback, hooks and triggers, and support for
9 +immutable systems with deployments.
10 +
11 +Diagrams in this README are rendered with Mermaid – supported by the
12 +`git.paganlinux.eu` portal (and GitHub/Gitea).
13 +
14 +```bash
15 +pag --version # client version (e.g. pag 3.3.20)
16 +pag # full help (command list)
17 +pag --help # same as above
18 +```
19 +
20 +---
21 +
22 +## Table of contents
23 +
24 +- [Quick start](#quick-start)
25 +- [How it works](#how-it-works)
26 + - [`pag install` flow](#pag-install-flow)
27 + - [Trust chain](#trust-chain)
28 + - [Immutable system](#immutable-system)
29 + - [Rollback](#rollback)
30 +- [Commands](#commands)
31 +- [Environment variables](#environment-variables)
32 +- [Paths and files](#paths-and-files)
33 +- [Package format](#package-format)
34 +- [Hooks and triggers](#hooks-and-triggers)
35 +- [`/etc` configuration – `.pacnew` / `.pacsave`](#etc-configuration--pacnew--pacsave)
36 +- [Examples](#examples)
37 +- [Troubleshooting](#troubleshooting)
38 +- [Install and update](#install-and-update)
39 +- [License](#license)
40 +
41 +---
42 +
43 +## Quick start
44 +
45 +```bash
46 +# Refresh repository indexes (and see what is pending an update)
47 +sudo pag sync
48 +
49 +# Install / remove
50 +sudo pag install firefox gimp
51 +sudo pag remove gimp
52 +
53 +# Update packages only…
54 +sudo pag update
55 +# …or the whole system: packages + kernel / initramfs / GRUB
56 +sudo pag upgrade
57 +
58 +# Verify the integrity of all files
59 +sudo pag verify --deep
60 +```
61 +
62 +---
63 +
64 +## How it works
65 +
66 +### `pag install` flow
67 +
68 +```mermaid
69 +graph TD
70 + A["pag install pkg"] --> B["Dependency resolution (DFS + provides)"]
71 + B --> C{"Missing dependencies?"}
72 + C -->|yes| C1["Error – abort"]
73 + C -->|no| D["ABI verification (so-name)"]
74 + D --> E["Pre-flight: free space + RW mount"]
75 + E --> F["flock + database snapshot"]
76 + F --> G["Parallel package download"]
77 + G --> H["GPG + package SHA256 verification"]
78 + H --> I["Staging: extract + per-file sums"]
79 + I --> J["Atomic rename of files into the system"]
80 + J --> K["pre/post-install hooks"]
81 + K --> L["Write to SQLite + transaction history"]
82 + L --> M{"PAG_IMMUTABLE=1?"}
83 + M -->|yes| N["New deployment + GRUB entries"]
84 + M -->|no| O["ldconfig + triggers"]
85 + H -->|error| R["Rollback of the whole transaction"]
86 + I -->|error| R
87 + J -->|error| R
88 + R --> S["Restore database and files"]
89 +```
90 +
91 +Key properties:
92 +
93 +- **Atomicity** – files first go to staging on the same partition as `/`, then
94 + are moved with `rename()` (no half-install).
95 +- **Transactionality** – if any package fails, the whole transaction is rolled
96 + back (`installed.json`, files, backups).
97 +- **File sharing** – a file owned by two packages is not removed when one of
98 + them is removed (only the database entry is removed).
99 +
100 +### Trust chain
101 +
102 +```mermaid
103 +graph TD
104 + A["repo.json + repo.json.asc"] --> B["GPG: VALIDSIG line"]
105 + B --> C["Repo fingerprint pinning (TOFU → pin)"]
106 + C --> D["Package index"]
107 + D --> E["package .pag + .asc"]
108 + E --> F["GPG: signature matches repo pin"]
109 + F --> G["Whole-package SHA256"]
110 + G --> H["Safe extraction (anti-traversal)"]
111 + H --> I["sums.json: per-file SHA256"]
112 + I --> J["Strip SUID bit"]
113 + J --> K["Install"]
114 + F -->|"missing / bad signature"| X["Reject package"]
115 + G -->|"SHA256 mismatch"| X
116 + H -->|"traversal / symlink escape"| X
117 +```
118 +
119 +- First use of a key = **TOFU**, afterwards the fingerprint is **pinned**
120 + (`pag key-trust` / `pag key-untrust`).
121 +- Verification is **fail-closed**: missing/bad signature = no installation
122 + (override only with `PAG_INSECURE=1`, build/dev only).
123 +- `pag self-update` goes through the same path: GPG → SHA256 → syntax check →
124 + atomic client replacement.
125 +
126 +### Immutable system
127 +
128 +With `PAG_IMMUTABLE=1`, installation does not mutate `/`; it creates a new
129 +deployment. Rollback is just switching the `active` symlink.
130 +
131 +```mermaid
132 +graph TD
133 + R["/"] --> D["/.deployments"]
134 + D --> A["active → 20260723T120000"]
135 + D --> D1["20260723T120000 (new)"]
136 + D --> D2["20260722T090000 (previous)"]
137 + D1 --> U["usr/ bin/ lib/ – system"]
138 + D1 --> S1["var → /var"]
139 + D1 --> S2["etc → /etc"]
140 + D1 --> S3["home → /home"]
141 + D1 --> S4["boot → /boot"]
142 + D2 -.->|"deploy-rollback"| A
143 +```
144 +
145 +- `/var`, `/etc`, `/home`, `/boot` (and other `SHARED_PATHS`) are **shared**
146 + between deployments – they are not duplicated.
147 +- `/boot` shares the kernel and initramfs, saving space per deployment.
148 +- Rollback from the bootloader: each deployment has its own GRUB entry
149 + (`pag grub-update`).
150 +
151 +### Rollback
152 +
153 +```mermaid
154 +graph TD
155 + T["Transaction"] --> OK{"Success?"}
156 + OK -->|yes| H["History + snapshot + file journal"]
157 + OK -->|no| RB["Automatic transaction rollback"]
158 + H --> R["pag rollback"]
159 + R --> R1["Restore installed.json from snapshot"]
160 + R --> R2["Remove new files from the journal"]
161 + R --> R3["Restore overwritten files from backup"]
162 + R --> R4["Clean up empty directories"]
163 + RB --> R1
164 + RB --> R2
165 + RB --> R3
166 +```
167 +
168 +`pag history` shows recent transactions (including executed hooks), and
169 +`pag rollback` reverts the **last successful** transaction with a snapshot.
170 +
171 +---
172 +
173 +## Commands
174 +
175 +### BASICS
176 +
177 +| Command | Description |
178 +|---|---|
179 +| `pag install <pkg>...` | Install packages (together with dependencies) |
180 +| `pag install -f <pkg>...` | **Force reinstall** (even the same version) – restores files, empty directories and hooks |
181 +| `pag remove <pkg>...` | Remove packages |
182 +| `pag update` | Update **packages** to newer versions |
183 +| `pag sync` | Refresh repository indexes + show how many packages await update |
184 +| `pag upgrade` | **System** update: packages + kernel / initramfs / GRUB |
185 +| `pag list` | List packages available in the repository |
186 +| `pag list --installed` | List installed packages |
187 +| `pag search <query>` | Search packages in the repo (+ Flathub) |
188 +| `pag info <pkg>` | Package details (version, dependencies, size, signature) |
189 +| `pag files <pkg>` | List files owned by a package |
190 +| `pag verify` | Verify the integrity of installed files |
191 +| `pag verify --deep` | Full per-file SHA256 verification |
192 +| `pag clean` | Clear the download cache |
193 +| `pag stats` | System statistics (package count, size, cache…) |
194 +| `pag download <pkg>...` | Download packages to cache (offline mode) |
195 +
196 +**Smart search:** `pag <name>` (any unknown command) searches the repo and
197 +Flathub and suggests names – e.g. `pag firefox` will find the package.
198 +
199 +### SECURITY / GPG KEYS
200 +
201 +| Command | Description |
202 +|---|---|
203 +| `pag key-add <url\|file>` | Import a repository GPG key |
204 +| `pag key-list` | List trusted keys |
205 +| `pag key-remove <id>` | Remove a key |
206 +| `pag key-trust <repo>` | Pin the repo key fingerprint (TOFU disabled) |
207 +| `pag key-untrust <repo>` | Forget the fingerprint (back to TOFU) |
208 +| `pag key-trusted` | List pinned repo fingerprints |
209 +
210 +### ADVANCED
211 +
212 +| Command | Description |
213 +|---|---|
214 +| `pag why <pkg>` | Why a package is installed (who depends on it) |
215 +| `pag autoremove` | Remove orphaned dependencies |
216 +| `pag remove-orphans` | Remove orphaned dependencies (alias) |
217 +| `pag pin <pkg> [version]` | Pin a package to a version (block updates) |
218 +| `pag unpin <pkg>` | Unpin |
219 +| `pag pinned` | List pinned packages |
220 +| `pag history` | Transaction history |
221 +| `pag rollback` | Revert the **last** transaction (restore files from backup) |
222 +| `pag repo-add <url> [name]` | Add a repository (drop-in in `/etc/pag/repos/`) |
223 +| `pag repo-list` | List configured repositories |
224 +| `pag sbom export [spdx\|cyclonedx]` | Export an SBOM manifest of all installed components |
225 +| `pag self-update` | Update the `pag` client itself (GPG signature + SHA256 + syntax, atomically) |
226 +
227 +### FLATPAK
228 +
229 +| Command | Description |
230 +|---|---|
231 +| `pag flatpak [<query>]` | Search and install from Flathub |
232 +| `pag flatpak search <query>` | Search Flathub |
233 +| `pag flatpak install <id>` | Install a flatpak |
234 +| `pag flatpak remove <id>` | Remove a flatpak |
235 +| `pag flatpak list` | List installed flatpaks |
236 +| `pag flatpak update` | Update all flatpaks |
237 +| `pag flatpak info <id>` | Flatpak details |
238 +
239 +> Installation as root forces the **system** scope (`--system`), so the
240 +> application is visible to all users, not just root.
241 +
242 +### IMMUTABLE SYSTEM (`PAG_IMMUTABLE=1`)
243 +
244 +| Command | Description |
245 +|---|---|
246 +| `pag deploy-list` | List deployments |
247 +| `pag deploy-rollback` | Switch to the previous deployment |
248 +| `pag deploy-cleanup [N]` | Remove old deployments (keep N, default 3) |
249 +| `pag initramfs-update` | Rebuild initramfs |
250 +| `pag grub-update` | Regenerate GRUB entries for all deployments |
251 +
252 +---
253 +
254 +## Environment variables
255 +
256 +| Variable | Meaning |
257 +|---|---|
258 +| `PAG_ROOT` | Alternative operation root (tests/chroot); default `/` |
259 +| `PAG_IMMUTABLE=1` | Immutable mode – installation creates a new deployment |
260 +| `PAG_YES=1` | Automatic confirmation (same as `-y` / `--yes`) |
261 +| `PAG_INSECURE=1` | Disables the HTTPS requirement and fail-closed GPG (**build/dev only!**) |
262 +| `PAG_NO_HOOKS=1` | Skip hooks and triggers |
263 +| `PAG_HOOK_TIMEOUT` | Hook timeout in seconds (default `60`) |
264 +| `PAG_LANG_DIR` | Directory with translation files (`pl.json`, `en.json`) |
265 +| `PAG_LANG_NO_FILES=1` | Ignore translation files (export built-ins) |
266 +| `PAG_ROOT_DEVICE` / `PAG_GRUB_ROOT` | Explicit `root=` for GRUB (ISO/IMG build) |
267 +| `PAG_IN_CHROOT=1` | Force chroot mode when detecting the root device |
268 +
269 +---
270 +
271 +## Paths and files
272 +
273 +| Path | Purpose |
274 +|---|---|
275 +| `/var/lib/pag/` | State database: `installed.json`, `files.db`, `world`, `pinned.json`, `history.json`, `pag.lock`, `hooks/` |
276 +| `/var/lib/pag/files.db` | SQLite: file owners + SHA256 sums |
277 +| `/var/cache/pag/` | Downloaded package cache |
278 +| `/var/cache/pag/repos/` | Repository index cache (JSON + ETag + timestamp) |
279 +| `/etc/pag/repos.conf` and `/etc/pag/repos/*.conf` | Repositories (drop-in) |
280 +| `/etc/pag/trusted.json` | Pinned repo key fingerprints |
281 +| `/etc/pag/gpg/` | Isolated GPG keyring |
282 +| `/etc/pag/triggers/*.json` | Custom triggers |
283 +| `/etc/pag/lang/`, `/usr/share/pag/lang/` | Translations (files override built-ins) |
284 +| `/var/log/pag/audit.log` | Audit: hooks, self-update |
285 +| `/.pag_staging` | Staging (same partition as `/` – no `EXDEV`) |
286 +| `/.deployments/` | Deployments (immutable mode) |
287 +
288 +---
289 +
290 +## Package format
291 +
292 +A `.pag` package is an archive containing:
293 +
294 +```
295 +metadata.json – name, version, release, dependencies, provides/requires (so-name)
296 +data.tar.xz – system files + sums.json (per-file SHA256)
297 +hooks/ – optional: pre-install, post-install, pre-remove, post-remove
298 +```
299 +
300 +Installation is **verified per file** against `sums.json`, and extraction is
301 +protected against *directory traversal* and escape via malicious symlinks.
302 +
303 +---
304 +
305 +## Hooks and triggers
306 +
307 +**Hooks** run as `root` (like apt/pacman), in a restricted environment:
308 +
309 +- `pre-install`, `post-install`, `pre-remove`, `post-remove`
310 +- clean env: `PATH`, `HOME=/root`, `LANG/LC_ALL=C.UTF-8`, `PKG_NAME`,
311 + `PKG_VERSION`, `PKG_ACTION`, `PKG_HOOK_API=1`
312 +- timeout (`PAG_HOOK_TIMEOUT`), optional disable (`PAG_NO_HOOKS=1`), entries in
313 + `/var/log/pag/audit.log` and in the transaction history
314 +
315 +You install code you trust – hooks have full privileges.
316 +
317 +**Triggers** run **once per transaction**, when matching paths were touched
318 +(only if the given binary exists):
319 +
320 +| Trigger | Paths | Command |
321 +|---|---|---|
322 +| `font-cache` | `/usr/share/fonts/`, `/usr/local/share/fonts/` | `fc-cache -fs` |
323 +| `glib-schemas` | `/usr/share/glib-2.0/schemas/` | `glib-compile-schemas …` |
324 +| `desktop-database` | `/usr/share/applications/` | `update-desktop-database -q …` |
325 +| `mime-database` | `/usr/share/mime/` | `update-mime-database …` |
326 +
327 +You can add your own triggers as `/etc/pag/triggers/*.json`.
328 +
329 +---
330 +
331 +## `/etc` configuration – `.pacnew` / `.pacsave`
332 +
333 +The `/etc` directory is shared between deployments (it is not reverted by
334 +`deploy-rollback`). To avoid losing user changes:
335 +
336 +- **Updating** a configuration file that the user modified: the new version is
337 + written as `<file>.pacnew`, and the user's file **is kept**.
338 +- **Removing** a package with a modified configuration file: the file becomes
339 + `<file>.pacsave` instead of being deleted.
340 +
341 +The comparison uses the SHA256 sum recorded at install time.
342 +
343 +---
344 +
345 +## Examples
346 +
347 +```bash
348 +# Install and updates
349 +sudo pag install firefox gimp
350 +sudo pag update # packages only
351 +sudo pag upgrade # packages + kernel/initramfs/GRUB
352 +sudo pag sync # refresh indexes + update info
353 +
354 +# Repair a package (missing files/directories, e.g. /etc/pulse/default.pa.d)
355 +sudo pag install -f pulseaudio
356 +
357 +# Application icons after installing an icon theme (cache built automatically)
358 +sudo pag install papirus-icon-theme
359 +
360 +# Security
361 +sudo pag key-add https://repo.paganlinux.eu/stable/paganos.asc
362 +sudo pag key-trust https://repo.paganlinux.eu/stable/
363 +sudo pag verify --deep
364 +
365 +# Dependencies and package provenance
366 +sudo pag why libjpeg-turbo
367 +sudo pag autoremove
368 +
369 +# SBOM for audit / compliance
370 +sudo pag sbom export cyclonedx > sbom.json
371 +
372 +# Revert the last transaction
373 +sudo pag rollback
374 +```
375 +
376 +---
377 +
378 +## Troubleshooting
379 +
380 +| Symptom | Solution |
381 +|---|---|
382 +| "Another pag instance is running" | Another `pag` process is active (`flock` lock). Wait or check `pgrep -af pag`. |
383 +| "Cannot refresh the index" | The repo cache is read-only for a regular user – run as root: `sudo pag sync`. |
384 +| "Invalid signature / missing signature" | Import the key: `sudo pag key-add <url>`, then verify `sudo pag key-trusted`. |
385 +| Missing files/directories after install | `sudo pag install -f <pkg>` (restores files, empty directories and hooks). |
386 +| System does not boot after `grub-update` | Set the root device explicitly: `PAG_ROOT_DEVICE=/dev/sda2 sudo pag grub-update`. |
387 +| Flatpak app not visible in the menu | Add `/var/lib/flatpak/exports/share` to `XDG_DATA_DIRS` and log in again. |
388 +
389 +---
390 +
391 +## Install and update
392 +
393 +```bash
394 +# On a running PaganOS, pag is in the base repository:
395 +sudo pag install pag # install from the repository
396 +sudo pag self-update # update the client itself (signed)
397 +
398 +# pag is also managed by the package manager – after a new version is
399 +# published in the repo:
400 +sudo pag update
401 +sudo pag upgrade
402 +```
403 +
404 +---
405 +
406 +## License
407 +
408 +PaganOS / pag – an open source project. See the distribution repository for
409 +details.
0 410 deleted file mode 100644
@@ -1,84 +0,0 @@
1 -#!/usr/bin/env python3
2 -"""extract-pag-lang.py – wyciąga wbudowane tłumaczenia z klienta `pag/pag`
3 -do plików `pag/pag-lang/{pl,en}.json` (klucz "usage" = tekst --help).
4 -
5 -Użycie:
6 - python3 pag/extract-pag-lang.py # odśwież pag-lang/*.json
7 - python3 pag/extract-pag-lang.py --check # tylko pokaż liczby kluczy
8 -
9 -Katalog projektu pag (odpowiada 1:1 repo git.paganlinux.eu/git/pag):
10 - pag/ – klient (PAG_VERSION)
11 - pag/pag-lang/ – tłumaczenia (generowane tym skryptem)
12 - pag/README.md, README-EN.md
13 - pag/archive/ – stare szkice klienta (build ich nie używa)
14 -
15 -Potem builder instaluje je do /usr/share/pag/lang (install_pag + respin-iso.sh),
16 -a `pag` czyta je w kolejności: PAG_LANG_DIR → /etc/pag/lang →
17 -/usr/share/pag/lang → ./pag-lang (dev).
18 -"""
19 -from __future__ import annotations
20 -
21 -import importlib.util
22 -import json
23 -import os
24 -import sys
25 -from importlib.machinery import SourceFileLoader
26 -
27 -HERE = os.path.dirname(os.path.abspath(__file__))
28 -PAG = os.path.join(HERE, "pag")
29 -OUT = os.path.join(HERE, "pag-lang")
30 -LANGS = ("pl", "en")
31 -
32 -
33 -def load_pag():
34 - # Eksportujemy CZYSTE wbudowane słowniki (bez nadpisań ze starych plików)
35 - os.environ["PAG_LANG_NO_FILES"] = "1"
36 - # Plik nie ma rozszerzenia .py → trzeba podać loader jawnie
37 - loader = SourceFileLoader("pagsrc", PAG)
38 - spec = importlib.util.spec_from_loader("pagsrc", loader)
39 - mod = importlib.util.module_from_spec(spec)
40 - sys.modules["pagsrc"] = mod
41 - # __name__ != "__main__" → main() z paga się NIE uruchomi
42 - loader.exec_module(mod)
43 - return mod
44 -
45 -
46 -def main() -> int:
47 - check = "--check" in sys.argv
48 - if not os.path.isfile(PAG):
49 - print(f"Brak pliku: {PAG}", file=sys.stderr)
50 - return 1
51 - mod = load_pag()
52 - tables = getattr(mod, "T", {})
53 - # Kontrola spójności PL/EN (pomijamy klucze „*_pl” – to celowe warianty PL)
54 - _pk = {k for k in tables.get("pl", {}) if not k.endswith("_pl")}
55 - _ek = {k for k in tables.get("en", {}) if not k.endswith("_pl")}
56 - _miss_en = sorted(_pk - _ek)
57 - _miss_pl = sorted(_ek - _pk)
58 - if _miss_en:
59 - print(f" ! en: brakuje {len(_miss_en)} kluczy: {', '.join(_miss_en[:10])}"
60 - + (" …" if len(_miss_en) > 10 else ""))
61 - if _miss_pl:
62 - print(f" ! pl: brakuje {len(_miss_pl)} kluczy: {', '.join(_miss_pl[:10])}"
63 - + (" …" if len(_miss_pl) > 10 else ""))
64 - os.makedirs(OUT, exist_ok=True)
65 - for code in LANGS:
66 - data = dict(tables.get(code, {}))
67 - usage = getattr(mod, f"USAGE_{code.upper()}", "")
68 - if usage:
69 - data["usage"] = usage
70 - n = len(data)
71 - if check:
72 - print(f" [{code}] {n} kluczy")
73 - continue
74 - path = os.path.join(OUT, f"{code}.json")
75 - with open(path, "w", encoding="utf-8") as fh:
76 - json.dump(data, fh, ensure_ascii=False, indent=2, sort_keys=True)
77 - print(f"✓ {path} ({n} kluczy)")
78 - if not check:
79 - print("Gotowe. Edytuj JSON-y, a builder/ISO wezmą je automatycznie.")
80 - return 0
81 -
82 -
83 -if __name__ == "__main__":
84 - sys.exit(main())