Metadata-Version: 2.5
Name: slackware-pkgcheck
Version: 0.3.0
Summary: Verifies that the files recorded by each Slackware package exist on the system.
Project-URL: Repository, https://github.com/yukiteruamano/slackware-pkgcheck
Project-URL: Homepage, https://github.com/yukiteruamano/slackware-pkgcheck
Author: José Maldonado "YukiteruAmano"
License-Expression: BSD-3-Clause
License-File: LICENSE
Keywords: cli,files,integrity,packages,slackware,verification
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Python: >=3.12
Requires-Dist: rich<16,>=15.0.0
Description-Content-Type: text/markdown

# pkgcheck

[![CI](https://github.com/yukiteruamano/slackware-pkgcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/yukiteruamano/slackware-pkgcheck/actions/workflows/ci.yml)
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-green.svg)](LICENSE)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)

Integrity checker for Slackware Linux: verifies that the files recorded by each package
in `/var/log/packages/` really exist on the system.

- Bulk extraction of `FILE LIST:` sections with **ripgrep** in a single pass (Python fallback if `rg` not found).
- Parallel verification with `ThreadPoolExecutor` (broken symbolic links count as present:
  `os.lstat` is used).
- Distinguishes **missing** files, **backup-only** (`.bak`/`.orig`) and **no access**
  (requires root), as well as **`.new` configs pending review**.
- Decodes the octal escapes (`\NNN`) Slackware uses for names with non-ASCII bytes and
  discards sections after the FILE LIST (e.g. `REQUIRES:`).
- Excludes install scripts (`install/`) and pseudo-filesystems (`dev/`, `sys/`, `proc/`, `run/`, `tmp/`, `var/tmp`, `var/cache`, `var/spool`, `var/log`, `mnt`, `media`, …) with `var/log/packages` exception.
- Reports files with **verification errors** too (JSON `files_errors` / `ERRORS` log section).
- Finds **orphan** files not owned by any package (`--orphans`) and diffs two runs (`--diff` with `--list-logs`).
- Safe `readelf -d NEEDED` mode (`--safe-ldd`) as alternative to `ldd` (no execution).
- Shell completion for bash/zsh/fish (`--completion`).
- Live progress and report with **rich**; summary + breakdown per package.
- Automatic log in `/var/log/pkgcheck/pkgcheck-<date>.log` (`.json` with `--json`).
- Internationalized interface (7 languages) with automatic OS locale detection.

## Requirements

- Python 3.12+
- [uv](https://docs.astral.sh/uv/)
- ripgrep (`rg`) recommended (falls back to Python scan if missing)

## Getting started

```sh
uv sync                # creates .venv and installs the project and its dependencies
uv run pkgcheck --version
```

It can also be run as a module: `uv run python -m pkgcheck`.

## Usage

```sh
sudo uv run pkgcheck                          # analyzes /var/log/packages and writes the log
sudo uv run pkgcheck --json                   # writes the log in JSON format (.json)
uv run pkgcheck --no-elevate --json           # without root: only prints the JSON to stdout
uv run pkgcheck --workers 16                  # adjusts the parallelism
uv run pkgcheck --packages-dir /mnt/root/var/log/packages
sudo uv run pkgcheck --check-libs-deps        # also checks library dependencies (ldd)
sudo uv run pkgcheck --check-libs-deps --safe-ldd  # safe readelf mode (no execution)
sudo uv run pkgcheck --check-libs-deps --check-libs-symbols   # + undefined symbols
uv run pkgcheck --orphans --orphans-root /    # lists untracked files
uv run pkgcheck --list-logs                   # lists existing logs
uv run pkgcheck --diff --from latest --to /var/log/pkgcheck/pkgcheck-...json --json
uv run pkgcheck --completion bash > /etc/bash_completion.d/pkgcheck
```

### Library dependencies (`--check-libs-deps`)

Inspired by Gentoo's `revdep-rebuild`, this optional check verifies that every
installed ELF binary and shared library has **all** of its dynamic library
dependencies present on the system. For each ELF file `ldd` is run (in parallel)
and any `not found` dependency is flagged:

- The **broken binary/library** and the **owning package** are reported.
- A **best-effort** guess is made for which installed package should provide each
  missing library (matched by library basename; it may be `None` when the soname
  does not match any installed file).
- Results are grouped by package in the `broken_libs` section of the JSON report
  (`{package: [{binary, missing, provided_by}]}`) and in the text log under
  `BROKEN LIBRARY DEPS:`.

Requires `ldd` (present on Slackware/glibc). It is opt-in because running `ldd`
over the whole system is expensive.

### Undefined symbols (`--check-libs-symbols`)

An optional, extra mode (mirrors revdep-rebuild's `-u` / `SEARCH_SYMBOLS`). After
collecting the set of dynamic symbols exported by the installed libraries, it
flags binaries that import symbols no installed library provides. It **requires**
`--check-libs-deps` and the `readelf` tool, and is **prone to false positives**
(lazy binding, `dlopen`-loaded libraries, symbol versioning). Results appear in
the `undefined_symbols` JSON key and the `UNDEFINED SYMBOLS` log section.

### Root privileges

Some protected files (e.g. `/var/spool/atjobs`, `/root`) can only be checked with root
privileges. If the process is not running as root:

- In an interactive terminal you are asked whether to **re-run with sudo**.
- `--elevate` re-runs with `sudo` without asking.
- `--no-elevate` (or `--quiet`, or non-interactive output) verifies only what is
  accessible and warns.

### Install scripts and pseudo-filesystems

Entries under `install/` (`install/doinst.sh`, `install/slack-desc`,
`install/douninst.sh`, `install/slack-required`) are metadata that Slackware does not
leave on disk, so they are **excluded** from the analysis and shown as an informational
counter.

The pseudo-filesystems and ephemeral paths (`dev/`, `sys/`, `proc/`, `run/`, `tmp/`, `var/run/`, `var/tmp/`, `var/cache/`, `var/spool/`, `var/lock/`, `var/log/` (except `var/log/packages/`), `var/lib/slackpkg/`, `mnt/`, `media/`, `srv/`, `lost+found/`) are also
not tracked: their entries (e.g. the device nodes of the `devs` package) are dynamic and
do not persist. More prefixes can be added with `--exclude`. Ripgrep is preferred but a pure-Python fallback is used if `rg` is not found.

### Pending new configs (`.new`)

Slackware records configs with the `.new` suffix in `FILE LIST` (e.g.
`etc/ssl/openssl.cnf.new`). On install, the package script **renames** the file to the
name without suffix (`.new` → ``); if that file already existed on the system, it keeps
the `.new` suffix and it is left **pending review** by the user.

pkgcheck honors this semantics:

- `foo.conf.new` recorded and `foo.conf` present → installed correctly (not reported).
- `foo.conf.new` present on disk → reported as **pending review** (the new version awaits
  your decision).
- `foo.conf.new` recorded but with no trace → missing.

The suffix is adjusted with `--new-suffix` (default `.new`). The detail appears in the
report (key `files_pending_new` in JSON) and in the console tree.

### Backup-only files

If a recorded path does not exist but a backup-suffix variant (`.bak` or `.orig`) does,
it is reported as **backup-only** instead of missing. The set of suffixes is adjusted with
`--backup-suffixes`. The per-package detail appears in the report (key `files_backup` in
JSON), not in the console tree.

### Orphan files (`--orphans`)

With `--orphans` pkgcheck also reports files present on disk but not owned by any package (e.g. `make install` leftovers). The scan walks `--orphans-root` (default `/`) and excludes the pseudo-filesystems above plus `home/` and any `--exclude`. Results appear in `summary.orphans` and `orphans` JSON key and in the `ORPHANS` text section and console tree.

### Log listing and diff (`--list-logs`, `--diff`)

- `--list-logs` prints a table of existing logs in `/var/log/pkgcheck/` (idx, date, fmt, size, path).
- `--diff --from PATH --to PATH` (or `latest`/`latest-1` aliases) diffs two JSON reports (`missing`, `files_backup`, ..., `orphans`, `broken_libs`). In text mode it shows `+ added`/`- removed` per package; with `--json` it prints a JSON diff. Text logs (`.log`) are not diffable.

### Safe library deps (`--safe-ldd`)

`--check-libs-deps` uses `ldd` which executes the binary. With `--safe-ldd` the check uses `readelf -d NEEDED` instead (no execution) and reports a library as missing if no installed package provides it (best-effort via `owner_index`). Requires `readelf`.

### Shell completion (`--completion`)

`--completion bash|zsh|fish` prints a completion script to stdout. Example: `pkgcheck --completion bash > /etc/bash_completion.d/pkgcheck`.

### Automatic log

Each run writes a log in `/var/log/pkgcheck/` (the directory is created if it does not
exist) with the convention:

```sh
pkgcheck-dd-mm-yyyy-hh-mm-ss.log    # plain text (default)
pkgcheck-dd-mm-yyyy-hh-mm-ss.json   # with --json
```

- Requires root privileges: the default run uses `sudo`, so the log is saved
  automatically. If it cannot be written, a warning is shown and the analysis continues.
- With `--no-elevate` (or non-interactive output without root) **no file is saved**: the
  result is only shown on screen (rich in text mode; with `--json` only the JSON document
  is printed to stdout).
- With `--json`, progress and process messages go to **stderr** (scan phases, progress bar
  and completion line), so **stdout** is reserved for the JSON document (no-elevate) or
  the confirmation of the saved log (root). In text mode progress is shown in the console
  itself.
- If two runs collide on the same second, a numeric suffix is added
  (`pkgcheck-<date>-1.log`, `-2`, ...).

The JSON document includes a `timestamp` (ISO 8601), `generator`, `version`, `summary`
and the per-package indexes `missing`, `files_backup`, `files_pending_new`, `no_access`
and `files_errors`. With `--check-libs-deps` the `broken_libs` index is added, and with
`--check-libs-symbols` also `undefined_symbols`. These keys are stable and never localized.

### Internationalization

The interface, the help and the text log are localized according to the operating system
language. Supported languages: `en` (base), `es`, `pt`, `fr`, `de`, `zh` (Simplified
Chinese) and `ja`.

Precedence for choosing the language:

1. `--lang {en,es,pt,fr,de,zh,ja}`
2. `PKGCHECK_LANG` environment variable
3. OS locale (`LC_ALL`, `LC_MESSAGES` or `LANG`)
4. English (default)

Translations live in `src/pkgcheck/locales/{lang}.json` (English → language mapping). To
add a new language, create `locales/xx.json` with the same keys translated; if a key is
missing, the English text is shown. The report JSON keys (`missing`, `files_backup`, ...)
are not localized: they are the stable API.

### Arguments

| Argument           | Description                                                              |
|--------------------|--------------------------------------------------------------------------|
| `--packages-dir`   | Directory with the records (default `/var/log/packages`).                |
| `--workers N`      | Verification threads (default: auto).                                    |
| `--json`           | Writes the log in JSON format (`.json`) instead of text.                 |
| `--max-rows N`     | Limits the per-package console breakdown.                                |
| `--exclude PREFIX` | Additional prefix to exclude (repeatable or comma-separated).            |
| `--backup-suffixes`| Backup suffixes detected as backup-only (default `.bak,.orig`).          |
| `--new-suffix`     | New-config suffix pending review (default `.new`).                       |
| `--lang LANG`      | Interface language (`en,es,pt,fr,de,zh,ja`); detects the OS language.    |
| `--check-libs-deps`| Also checks that every installed ELF binary/library has all its dynamic  |
|                    | library dependencies present (`ldd`; revdep-rebuild style).               |
| `--check-libs-symbols`| Also checks installed binaries for undefined dynamic symbols not        |
|                    | provided by any installed library (requires `--check-libs-deps`; may      |
|                    | report false positives).                                                  |
| `--safe-ldd`       | Use `readelf -d NEEDED` instead of `ldd` (no execution, safe mode).     |
| `--orphans`        | Also list orphan files not owned by any package.                           |
| `--orphans-root`   | Root for `--orphans` scan (default `/`).                                   |
| `--list-logs`      | List existing logs in `/var/log/pkgcheck` and exit.                       |
| `--diff`           | Diff two JSON logs (requires `--from`/`--to`, supports `latest`).        |
| `--from PATH`      | First log for `--diff`.                                                    |
| `--to PATH`        | Second log for `--diff`.                                                   |
| `--completion`     | Generate shell completion (`bash`/`zsh`/`fish`) and exit.                |
| `--quiet`          | Hides progress and breakdown; only prints the summary.                   |
| `--elevate`        | Re-runs with sudo if root privileges are not available.                  |
| `--no-elevate`     | Does not ask for root privileges; only verifies what is accessible.      |
| `--version`        | Shows the version.                                                       |

## Code quality

```sh
make check          # lint + format-check + typecheck + test
make coverage       # tests with coverage (>=80%)
make audit          # pip-audit
uv run ruff check
uv run ruff format --check
uv run mypy src
uv run coverage run -m unittest discover -s tests && uv run coverage report
```

Tooling: `ruff` (`E,F,W,I,UP,B,SIM,C4,RET,ARG,RUF,S,ANN,PTH,T20,D`), `mypy --strict`,
`coverage` (branch, fail_under 90, total 93%), `pip-audit`, `pre-commit`.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, `uv sync --group dev`,
and `pre-commit install`.

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## Security

`--check-libs-deps` runs `ldd` which executes the target via the dynamic loader
(`LD_TRACE`). Only run on trusted installations; see `src/pkgcheck/libdeps.py` note.
