b4e6bbf79fc4660af1b13aa6093497acd996c00c
install ok unpacked
Both cases come from a fleet-wide Wazuh rollback. Of thirteen hosts carrying the agent, seven sat at `install ok unpacked` with the unit failed — an upgrade whose postinst could not reach a manager that had been decommissioned. `apt-get remove` cannot help there. apt configures a package before removing it, and configuring is precisely what was broken. On one host only `dpkg --purge --force-all` got it out. So `uninstall_package` takes `purge: bool = False`, and falls back to a forced dpkg purge **after apt has failed** — never as a routine second step. Forcing dpkg past its own consistency checks is a bigger hammer than apt, and a caller who reaches for it every time will eventually break something apt would rightly have refused. `purge` is off by default: configuration somebody may want back is not this function's to delete unless asked. It matters for more than tidiness — a package's apt source survives a plain remove, so the repository keeps being fetched on every update long after the package is gone, which is what the agent left behind on all thirteen. Found while writing the fallback test, and older than this change: the success check read apt's commonest failure as a success. `E: Sub-process /usr/bin/dpkg returned an error code (1)` contains neither "error:" nor "failed", so a removal that did not happen was reported as one that did — and the caller then records the package as gone. `_uninstall_failed` now also treats a line starting with `e: ` as failure, matched at line start because "note: " ends in "e: ". Reading success out of prose stays guesswork; the exit status is the real answer and `_sudo`'s `|| true` throws it away before anyone can read it. That is netork#267, deliberately not fixed here. apk and pacman have no separate purge. Asking for one there is not an error, it simply has nothing extra to do.
napalm-linux
NAPALM driver for generic Linux systems — Debian, Ubuntu, RHEL, Rocky, Alpine, Arch and any other distribution reachable via SSH.
Connects over SSH using Netmiko (linux device type) and
automatically detects the installed package manager (apt, dnf, yum, apk, pacman).
Requirements
| Dependency | Minimum version |
|---|---|
| Python | 3.9 |
| NAPALM | 4.0 |
| Netmiko | 4.0 |
| napalm-device-types | 0.2.0 |
Installation
pip install napalm napalm-linux
Or from source:
git clone https://github.com/chrismanivong/napalm-linux
pip install -e napalm-linux/
When working in the NetOrk monorepo, install both packages as editable:
pip install -e vendor/napalm-device-types/ -e vendor/napalm-linux/
Quick start
from napalm import get_network_driver
Driver = get_network_driver("linux")
with Driver(
"10.0.0.5",
"admin",
"s3cr3t",
optional_args={
# "port": 22,
# "pkg_manager": "apt", # force package manager; auto-detected by default
# "secret": "sudo-pass", # password for sudo / enable (defaults to login password)
# "debugging": True, # enable verbose logging
},
) as dev:
facts = dev.get_facts()
print(facts)
# OS-specific methods (from napalm-device-types OSDriver)
packages = dev.get_packages()
updates = dev.get_pending_updates()
services = dev.get_services()
users = dev.get_users()
procs = dev.get_processes()
jobs = dev.get_cron_jobs()
# Upgrade specific packages
result = dev.apply_updates(["openssh-server", "curl"])
print(result) # {"success": True, "output": "..."}
# Upgrade everything with pending updates
result = dev.apply_updates([])
Supported NAPALM methods
Standard NAPALM
| Method | Supported | Notes |
|---|---|---|
open() / close() |
✅ | SSH via netmiko linux |
is_alive() |
✅ | |
get_facts() |
✅ | DMI / /proc/uptime / ip link |
get_interfaces() |
✅ | ip link show |
get_interfaces_ip() |
✅ | ip addr show |
get_arp_table() |
✅ | ip neigh show |
get_config() |
✅ | Returns ip addr + ip route output |
ping() |
✅ | Executes ping on the remote host |
load_merge_candidate() |
❌ | Not applicable for generic Linux |
load_replace_candidate() |
❌ | Not applicable for generic Linux |
compare_config() |
❌ | Not applicable for generic Linux |
commit_config() |
❌ | Not applicable for generic Linux |
discard_config() |
❌ | Not applicable for generic Linux |
rollback() |
❌ | Not applicable for generic Linux |
OSDriver extensions (napalm-device-types)
| Method | Supported | Package managers |
|---|---|---|
get_packages() |
✅ | apt, dnf, yum, apk, pacman |
get_pending_updates() |
✅ | apt, dnf, yum, apk, pacman |
apply_updates(packages) |
✅ | apt, dnf, yum, apk, pacman |
get_services() |
✅ | systemd (fallback: SysV service) |
get_users() |
✅ | /etc/passwd + /etc/group |
get_processes() |
✅ | ps axo |
get_cron_jobs() |
✅ | user crontabs + /etc/cron.d/ |
Package manager auto-detection
The driver probes for each binary in order via command -v:
apt → dnf → yum → apk → pacman
Force a specific package manager:
optional_args={"pkg_manager": "dnf"}
SSH user permissions
The SSH user needs read access to:
| Data | Required permission |
|---|---|
/etc/passwd, /etc/group |
world-readable (default) |
/proc/uptime, /sys/class/dmi/… |
world-readable (default) |
User crontabs (/var/spool/cron/…) |
root or sudo required |
systemctl is-enabled <unit> |
unprivileged on most distros |
apt list --upgradable |
may require apt-get update (root) |
dnf check-update / yum check-update |
unprivileged, but slower without cache |
For full functionality it is recommended to run as root or grant passwordless sudo for
the above commands.
Tested distributions
| Distribution | Version | Package manager | Tested |
|---|---|---|---|
| Debian | 12 (Bookworm) | apt | ✅ |
| Ubuntu | 22.04 LTS | apt | ✅ |
| Rocky Linux | 9 | dnf | planned |
| Alpine Linux | 3.19 | apk | planned |
| Arch Linux | rolling | pacman | planned |
Contributions for additional distributions and versions are welcome.
Development
# Create venv
python -m venv .venv
source .venv/bin/activate
# Install in editable mode with dev dependencies
pip install -e ../napalm-device-types/ -e ".[dev]"
# Run tests
pytest tests/ -v
# Lint / format
ruff check napalm_linux/
ruff format napalm_linux/
License
Apache 2.0
Languages
Python
100%