Installing axlib on a shared Linux host#
install.sh, included in every release tarball, installs axlib for all
operators on a host with no manual configuration. It sets up a shared Python,
a locked axlib environment, the standard configuration and key locations, and
an encrypted credential store for the netops group.
Quick install#
# 1. uv is needed to install and upgrade (not to run axlib)
curl -LsSf https://astral.sh/uv/install.sh | sudo env UV_INSTALL_DIR=/usr/local/bin sh
# 2. Unpack the release anywhere and run its installer as root
tar xzf axlib-<version>.tar.gz
sudo ./axlib-<version>/install.sh
# 3. Give operators access and a record each
sudo usermod -aG netops alice
sudo axlib credential-db add --service alice --set netuser=alice --prompt netpass --prompt netenable
When alice next logs in, axlib credentials network reports netuser=set and
netpass=set, and her scripts' calls to ax.getkeys() return her login.
What gets installed#
| Path | Contents | Owner and mode |
|---|---|---|
/opt/shared/python/ |
uv-managed CPython (about 110 MB) | root, world-readable |
/opt/shared/axlib/ |
uv project: pyproject.toml, uv.lock, .venv/ |
root, world-readable |
/opt/shared/axlib/releases/ |
Each release's wheel plus a copy of its unpacked tarball (docs, examples, install.sh) |
root, world-readable |
/opt/shared/axlib/current |
Symlink to the installed release's files | |
/usr/local/bin/axlib |
Symlink to /opt/shared/axlib/.venv/bin/axlib |
|
/usr/local/sbin/axuv |
uv, pointed at the shared environment (see The axuv command) |
root, 0755 |
/etc/axlib/ |
Configuration and AES-256 key | root:netops 2750 |
/etc/axlib/axlib.toml |
Configuration (no secrets) | root:netops 0640 |
/etc/axlib/install.env |
The installer's options (see Recorded options) | root:root 0644 |
/etc/axlib/sqlite-aes256.key |
Store encryption key | root:netops 0640 |
/etc/axlib/textfsm/ |
The team's TextFSM templates for axlib parse; only with --with-textfsm (see Shared TextFSM templates) |
root:netops 2775 |
/var/lib/axlib/ |
Credential database directory | root:netops 2770 |
/var/lib/axlib/credentials.db |
Encrypted SQLite credential store | root:netops 0660 |
/etc/environment |
Adds AXLIB_CONFIG_FILE=/etc/axlib/axlib.toml |
|
/etc/profile.d/axlib-config.sh |
Exports AXLIB_CONFIG_FILE if it is still unset |
0644 |
/etc/profile.d/axlib-netenv.sh |
Only with --with-netenv |
0644 |
axlib never searches directories for a configuration file. It uses the file
given with --config, else the one named by AXLIB_CONFIG_FILE, else the
CONFIG_FILE in /etc/axlib/install.env. The last step means the axlib
command and ax.getkeys() scripts find /etc/axlib/axlib.toml everywhere on
this host: under sudo without -E, in cron, and in systemd services.
The installer also sets AXLIB_CONFIG_FILE for every login, so programs and
shell snippets that read the variable directly see it too.
/etc/environment is read through PAM for SSH logins and, on most
distributions, for cron jobs. /etc/profile.d/axlib-config.sh covers login
shells on hosts where sshd does not use PAM.
The group permissions are what let operators share the store. The key is
group-readable and the database is group-writable, so every member of
netops can read and update records. The directory is group-writable so
SQLite can create its journal file beside the database, and its setgid bit
keeps new files in the netops group.
Requirements#
- Linux, and root access (
sudo) for installing. Operators need no privileges beyond membership innetops. uvon root'sPATH, or passed withsudo UV=/path/to/uv ./install.sh. Installing it into/usr/local/bin(step 1 above) keeps a root-run binary out of anyone's home directory.- Network access during installation. uv downloads the Python build from
releases.astral.sh(older uv releases use GitHub), andcryptography,redis, andsetuptoolsfrom the package index, plus the packages of each optional part chosen below (textualandrich;textfsmandntc-templates;markdown-it-py,linkify-it-py, andmdit-py-plugins). See Offline hosts. - About 150 MB in
/opt/shared.
Options#
sudo ./install.sh [options]
| Option | Default | Purpose |
|---|---|---|
--prefix DIR |
/opt/shared/axlib |
axlib uv project and environment |
--python-dir DIR |
/opt/shared/python |
Shared uv-managed Python |
--python VERSION |
3.14 |
Python version (3.11 or newer) |
--group NAME |
netops |
Group allowed to use the store; created if missing |
--bin-dir DIR |
/usr/local/bin |
Where the axlib command is linked |
--sbin-dir DIR |
/usr/local/sbin |
Where the axuv command is copied |
--config-dir DIR |
/etc/axlib |
Configuration and key directory |
--data-dir DIR |
/var/lib/axlib |
Database directory |
--with-tui / --no-tui |
off | Install or remove the full-screen credential manager (axlib credential-tui) |
--with-textfsm / --no-textfsm |
off | Install or remove show-command parsing (axlib parse: TextFSM and the ntc-templates collection, about 5 MB), and create /etc/axlib/textfsm for shared templates. --no-textfsm keeps the templates. |
--with-markdown / --no-markdown |
off | Install or remove Markdown-to-HTML conversion (axlib md2html: markdown-it-py and two small companions, under 1 MB). See MARKDOWN.md. |
--with-netenv / --no-netenv |
off | Install or remove the login snippet that exports NETUSER/NETPASS/NETENABLE in every interactive login. /etc/profile.d is read by login shells, such as an SSH session. Read NETENV.md first. |
--no-store / --store |
store on | Skip, or go back to, creating and verifying the SQLite store and key. Skip it when using the encrypted text store instead. With --no-store, a new axlib.toml has enabled = false under [sqlite]. An existing axlib.toml is never changed, so edit its enabled line yourself. To add the store later, set enabled = true and run the installer with --store. |
The defaults in this table apply to the first install. Every later run starts from the options of the run before it (see Recorded options), so options are only needed to change something.
Directories must be absolute paths without a single quote ('), because the
record stores them in single quotes and other programs read them from any
directory. While it runs, the installer ignores every AXLIB_* variable in
root's environment, such as one kept by sudo -E or sudo -i, and sets
AXLIB_CONFIG_FILE to the axlib.toml in --config-dir. Root's own
settings therefore cannot redirect or reshape the store it creates: its key,
paths, and group, and whether Redis is used, all come from axlib.toml.
The environment variable UV names the uv binary to use; without it, the
installer uses the uv of the previous run, then uv on PATH.
--with-netenv is off by default because it puts every operator's password
in the environment of every program they start. That is a policy decision
for the host owner, not an installer default.
Recorded options#
Every run writes its options to /etc/axlib/install.env. The file is always
at this path, whatever --config-dir says, because it is how the next run
finds the options of this one:
PREFIX='/opt/shared/axlib'
PYTHON_DIR='/opt/shared/python'
PYTHON_VERSION='3.14'
GROUP='netops'
BIN_DIR='/usr/local/bin'
SBIN_DIR='/usr/local/sbin'
CONFIG_DIR='/etc/axlib'
DATA_DIR='/var/lib/axlib'
CONFIG_FILE='/etc/axlib/axlib.toml'
WITH_TUI='1'
WITH_TEXTFSM='1'
WITH_MARKDOWN='1'
WITH_NETENV='0'
CREATE_STORE='1'
UV_BIN='/usr/local/bin/uv'
Three programs read it:
install.shuses it as its defaults, sosudo ./install.shwith no options upgrades a host exactly as it was installed.sudo ./install.sh --helpshows the directories, Python version, and group the next run would use.axuvtakes the environment's location and the shared Python from it.- The
axlibcommand andax.getkeys()useCONFIG_FILEwhen neither--confignorAXLIB_CONFIG_FILEnames a configuration file.
To change an option, run the installer again with the new option rather than
editing the file. On a host installed by an older installer that wrote no
record, the first run without --with-tui/--no-tui or
--with-netenv/--no-netenv keeps the TUI and the login snippet as they are.
The same goes for a record written before --with-textfsm or
--with-markdown existed: the first run without the option, or its --no-
form, keeps that part installed or not installed, as it finds it.
What the installer does#
- Reads the options of the previous run from
/etc/axlib/install.env, if it exists. Then it checks that it runs as root from an unpacked axlib release, and findsuv. - Creates the
netopsgroup if it does not exist. - Installs Python into
/opt/shared/python. It setsUV_PYTHON_INSTALL_DIRandUV_MANAGED_PYTHON=1so uv cannot pick an interpreter under someone's home directory, which other users could not read. - Copies the release to
/opt/shared/axlib/releases/axlib-<version>/, builds its wheel, and adds the wheel to the/opt/shared/axlibuv project withuv add.uv.lockrecords the exact versions installed. A part turned off with--no-tui,--no-textfsm, or--no-markdownis removed from the project, anduv syncuninstalls the packages that nothing else needs. - Links
/usr/local/bin/axliband checks thataxlib versionruns. Only then does it pointcurrentat the new release, so if the build,uv add, or the check fails, the installer stops with an error andcurrentstill names the release installed before. A failure beforeuv addhas finished also restores the project'spyproject.tomlanduv.lock(changed when a part is turned off) and removes the copy and wheel of a release that was never installed. Then it copiesaxuvto/usr/local/sbin. - Creates
/etc/axliband/var/lib/axlib, or resets the owner, group, and mode of existing ones, and with--with-textfsmcreates/etc/axlib/textfsmif it is missing. It writesaxlib.tomlonly if it does not already exist, with the SQLite store enabled, or disabled with--no-store. - Records its options in
/etc/axlib/install.env, and setsAXLIB_CONFIG_FILEfor all logins. - Unless
--no-storeis given, runsaxlib credential-db init --generate-keywhen/etc/axlib/sqlite-aes256.keydoes not exist yet. For an existing store it runsaxlib credential-db init, which verifies the key and database and changes no records. An existing key is never replaced. - Installs the netenv login snippet with
--with-netenv, or removes it without, then prints a store health check (not with--no-store). If a store enabled inaxlib.tomlis not ready, it prints a warning that says so and how to run the check again, then the summary. axlib is installed at that point, so the exit status is still 0.
The generated configuration is minimal. Everything not listed uses axlib's
defaults (0660/0640 modes, 5-second lock timeout, Redis off):
# [credentials]
# shared_service = "network-shared" # fallback account for ax.getkeys()
[sqlite]
enabled = true
database = "/var/lib/axlib/credentials.db"
key_file = "/etc/axlib/sqlite-aes256.key"
group = "netops"
# axlib parse (installed with --with-textfsm); see docs/PARSING.md.
# [textfsm]
# platform = "cisco_ios" # used when --platform is not given
# template_dirs = ["textfsm"] # default: the textfsm directory beside this file
With --no-store, the file says enabled = false instead, so lookups skip
the store that was not created. The other [sqlite] lines stay, so turning
the store on later means setting enabled = true and running the installer
with --store.
Edit it to add a shared fallback account, Redis caching, or the encrypted text
store. CREDENTIALS.md describes every
option. Uncomment [textfsm] to give every operator a default platform for
axlib parse. axlib md2html needs no settings.
For administrators: maintaining the shared environment#
This section is for the people with root access who install axlib and keep
the shared environment up to date. Everything under /opt/shared is owned by
root and read-only for everyone else. Operators use what you install but
cannot change it. /opt/shared/axlib/pyproject.toml and uv.lock are the
complete, exact record of what is installed.
The axuv command#
Every uv command in /opt/shared/axlib must run as root with the same
settings as install.sh. The installer puts axuv in /usr/local/sbin to do
exactly that: sudo axuv <uv arguments> is uv <uv arguments> run on the
shared environment. It reads the paths from /etc/axlib/install.env, so it
works however axlib was installed, and it refuses to run as anyone but root.
Before starting uv, axuv sets up three things, and each one prevents a
specific failure:
UV_PYTHON_INSTALL_DIRandUV_MANAGED_PYTHON=1keep uv on the shared Python. Without them uv may rebuild.venvon an interpreter under root's home directory, and every operator's scripts stop working.umask 022makes new files world-readable even if your own umask is stricter (for example077). Without it, a newly added library is unreadable by everyone except root. That damage persists: uv hardlinks installed files from root's uv cache, so the cached copies keep the restrictive mode, and simply reinstalling does not fix it (see Troubleshooting).--directoryruns uv in the axlib project, whatever the current directory.
The examples below run in a root shell (sudo -i). From your own account,
put sudo in front of each axuv.
Onboarding operators#
usermod -aG netops alice # applies from alice's next login
axlib credential-db add --service alice --set netuser=alice --prompt netpass --prompt netenable
/opt/shared/axlib/.venv/bin/python -m axlib.credentials.admin # store health check
Operators can also add and update their own records with
axlib credential-db add --service "$USER" ... and
axlib credential-db update --service "$USER" ..., or with
axlib credential-tui if you installed with --with-tui.
Shared TextFSM templates#
With --with-textfsm, every operator's axlib parse searches
/etc/axlib/textfsm before the ntc-templates collection, because it sits
beside axlib.toml. Nothing needs configuring. A template file named after the
platform and the command, such as cisco_ios_show_ip_pim_neighbor.textfsm,
works as soon as it is copied in. PARSING.md explains how to write one.
The directory is root:netops with mode 2775, so members of netops can
add templates and new files stay in the group. For root-only changes:
chmod 2755 /etc/axlib/textfsm # the installer keeps a mode you set
/etc/axlib itself is 2750, so the templates are visible only to netops
members, like the rest of the configuration. --no-textfsm removes the
packages but never the templates. Keep the directory in git, as described in
Keeping a record of changes, to see who
changed a template and when:
cd /etc/axlib/textfsm && git init && git add -A && git commit -m "Shared TextFSM templates"
Adding libraries for everyone (netmiko, scrapli, ...)#
Libraries added to the shared environment are available to every script that
uses #!/opt/shared/axlib/.venv/bin/python, with no setup by the operators:
axuv add netmiko scrapli
axuv tree --depth 1
axlib-site v0.1.0
├── axlib v<version>
├── netmiko v4.8.0
└── scrapli v2026.2.20
Check the result as an ordinary user:
su - alice -c '/opt/shared/axlib/.venv/bin/python -c "import netmiko, scrapli, axlib"'
Other libraries commonly added for network automation:
| Need | Command |
|---|---|
TextFSM/NTC templates for parsing show output |
Included with netmiko. install.sh --with-textfsm installs them without netmiko, with the shared template directory for axlib parse. |
| Jinja2 configuration templates | axuv add jinja2 |
| NETCONF | axuv add ncclient |
| REST APIs (NetBox, controllers) | axuv add requests or axuv add pynetbox |
| Multi-device task runner | axuv add nornir nornir-netmiko nornir-scrapli |
| Spreadsheets of devices | axuv add openpyxl |
Adding a library needs internet access, or an internal mirror (see Offline hosts).
Upgrading, pinning, and removing libraries#
axuv add 'netmiko>=4.8,<5' # pin a range (the team relies on v4 behavior)
axuv lock --upgrade-package netmiko && axuv sync # newest netmiko allowed by the pin
axuv lock --upgrade --dry-run # preview upgrades for everything
axuv lock --upgrade && axuv sync # upgrade everything
axuv remove scrapli # remove a library
Every change applies to every operator the next time their script starts. A library upgrade can break scripts that depended on old behavior, so:
- announce upgrades to the team, and upgrade one library at a time when you can
- pin major versions of the libraries that scripts rely on most
- keep a record so you can undo a change (next section)
Upgrading axlib with install.sh does not remove or upgrade these libraries.
The installer only replaces the axlib entry, and everything you added stays
as it was.
Keeping a record of changes#
pyproject.toml and uv.lock are small text files that completely describe
the environment. Tracking them in git gives you history and a one-command
undo:
cd /opt/shared/axlib
git init -q && printf '.venv/\nreleases/\ncurrent\n' > .gitignore
git add -A && git commit -qm "Baseline"
axuv add nornir && git add -A && git commit -qm "Add nornir for the backup jobs"
# Undo the last change:
git revert --no-edit HEAD && axuv sync
axuv sync makes .venv match uv.lock exactly, removing anything that is no
longer listed.
Upgrading axlib#
Unpack the new release and run its installer. No options are needed: it starts from the options recorded by the previous run (see Recorded options).
tar xzf axlib-<new-version>.tar.gz
sudo ./axlib-<new-version>/install.sh
The configuration, key, database, records, and the libraries you added are
untouched. Only axlib itself, the current symlink, axuv, the recorded
options, the netenv snippet (if installed), and any part you turn on or off
change. Running the same release's installer again is also safe: it rebuilds
and reinstalls that release. If the new release fails to build or install,
the installer stops with an error, and current still points at the release
installed before.
Operators with their own projects keep using the wheel they pinned until they update it (see When axlib is upgraded). Tell them when a new release arrives.
Rolling back axlib#
Older wheels stay in /opt/shared/axlib/releases/. To switch back:
axuv add --upgrade-package axlib --reinstall-package axlib \
./releases/axlib-<old-version>-py3-none-any.whl
ln -sfn releases/axlib-<old-version> /opt/shared/axlib/current
axlib version
Storage formats are versioned and never migrated. A release that changes a
format says so in RELEASE_NOTES.md, and older releases then refuse the newer
store instead of misreading it.
Backups#
Back up both /etc/axlib/sqlite-aes256.key and
/var/lib/axlib/credentials.db, and keep the key backup separate from the
database backup. Without the key, the database cannot be decrypted, and
there is no recovery. Anyone with both files can read every credential.
To replace the key periodically, run
sudo axlib credential-db rotate-key --generate-key --yes. It re-encrypts every record and replaces the key
file only after the database is updated.
The environment itself doesn't need a backup: pyproject.toml, uv.lock,
and the wheels in releases/ are enough to rebuild it.
Offline hosts#
Installing and adding libraries download packages. On a host without internet
access, point uv at internal mirrors in uv's system-wide configuration file,
/etc/uv/uv.toml. Every uv run on the host reads it, including install.sh,
axuv, and operators' own projects:
# /etc/uv/uv.toml
python-install-mirror = "https://mirror.example.internal/python-build-standalone"
[[index]]
url = "https://pypi.example.internal/simple"
default = true
The installer has no fully offline mode.
Both /opt/shared/python and /opt/shared/axlib must stay where they were
installed. The environment refers to its Python and its own files by absolute
path, so neither directory can be moved, renamed, or copied to a different
path.
Uninstalling#
sudo rm -f /usr/local/bin/axlib /usr/local/sbin/axuv
sudo rm -f /etc/profile.d/axlib-config.sh /etc/profile.d/axlib-netenv.sh
sudo sed -i '/^AXLIB_CONFIG_FILE=/d' /etc/environment
sudo rm -rf /opt/shared/axlib
sudo rm -rf /opt/shared/python # only if nothing else uses it
sudo rm -f /etc/axlib/install.env # or keep it: a reinstall then reuses its options
# Credentials: remove only after you have exported or no longer need them.
# sudo rm -rf /etc/axlib /var/lib/axlib
For network operations staff: using axlib in your scripts#
This section is for anyone writing scripts on the server that log in to network devices. axlib gives every script your credentials from the encrypted store, so passwords never appear in a script, a config file, or your shell history.
Before you start#
-
Be a member of
netops:idshould list it. Ask an administrator if not, then log in again. -
Have a credential record. Check it (values are never shown):
$ axlib credentials network netuser=set netpass=set netenable=missingIf it says
missingfornetuserornetpass, add your record:axlib credential-db add --service "$USER" --set netuser="$USER" --prompt netpass --prompt netenable, or useaxlib credential-tuiif it is installed. -
Nothing else. axlib finds the host's configuration by itself.
ax.getkeys() looks up the record named by $USER and returns
(netuser, netpass, netenable). netenable is None when you have no enable
secret.
Option 1: the shared environment (recommended)#
Start the script with the shared interpreter. It works from any directory, needs no setup and no internet access, and includes every library the administrators have added:
#!/opt/shared/axlib/.venv/bin/python
"""Print "show version" from one Cisco IOS device: ./show_version.py edge-01"""
import sys
import axlib as ax
from netmiko import ConnectHandler
netuser, netpass, netenable = ax.getkeys()
if not (netuser and netpass):
sys.exit("No network credentials; check with: axlib credentials network")
device = {
"device_type": "cisco_ios",
"host": sys.argv[1],
"username": netuser,
"password": netpass,
"secret": netenable or "",
}
with ConnectHandler(**device) as connection:
if netenable:
connection.enable()
print(connection.send_command("show version"))
chmod +x show_version.py
./show_version.py edge-01
The same pattern with scrapli:
#!/opt/shared/axlib/.venv/bin/python
import sys
import axlib as ax
from scrapli import Scrapli
netuser, netpass, netenable = ax.getkeys()
device = {
"host": sys.argv[1],
"platform": "cisco_iosxe",
"auth_username": netuser,
"auth_password": netpass,
"auth_secondary": netenable or "",
}
with Scrapli(**device) as connection:
print(connection.send_command("show version").result)
scrapli checks SSH host keys by default. Connect once with ssh so the device
is in ~/.ssh/known_hosts, rather than turning the check off.
You can also run a script without the #! line:
/opt/shared/axlib/.venv/bin/python script.py.
What's installed: cat /opt/shared/axlib/pyproject.toml lists the
libraries added to the shared environment. If you need one that isn't there,
ask an administrator to add it so everyone benefits. You cannot install into
the shared environment yourself.
Option 2: your own uv project#
Use your own environment when a script needs libraries or versions that aren't in the shared one. It uses the shared Python, so you don't download one of your own, and gets axlib either from the release wheel on the server or from PyPI:
export UV_PYTHON_INSTALL_DIR=/opt/shared/python UV_MANAGED_PYTHON=1
mkdir -p ~/projects/backups && cd ~/projects/backups
uv init --bare
uv add /opt/shared/axlib/releases/axlib-<version>-py3-none-any.whl # or: uv add 'axlib>=1.0'
uv add netmiko jinja2
uv run python backup.py
Things to know:
- Put the
exportline in your~/.bashrcso every project uses the shared Python. - The first
uv adddownloadscryptography,redis, and your libraries into your own cache (~/.cache/uv), so it needs internet access or the team's mirror. - Choose where axlib comes from. The wheel in
/opt/shared/axlib/releases/is exactly the release the server runs.uv add 'axlib>=1.0'installs the latest release from PyPI instead. Both read the server's store. If you use PyPI, keep your version in step withaxlib versionon the server. A release that changes a storage format says so in its release notes. - Keep the
>=1.0floor. PyPI no longer offers releases before 1.0, but the 0.1.x releases, which predate the credential stores, can still be served by an internal mirror or cache that copied them before they were removed. The floor makes such a source fail clearly instead of installing a release that cannot read the server's store. - To run the script from anywhere or from cron, use the project's interpreter
by full path:
~/projects/backups/.venv/bin/python ~/projects/backups/backup.py, oruv run --project ~/projects/backups python ~/projects/backups/backup.py.
Option 3: a single-file uv script#
For a standalone script with its own libraries and no project folder, declare everything in the file. uv builds and caches the environment on first run:
#!/usr/bin/env -S uv run --quiet --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["axlib", "scrapli"]
#
# [tool.uv.sources]
# axlib = { path = "/opt/shared/axlib/releases/axlib-<version>-py3-none-any.whl" }
# ///
"""Collect interface status from every device named on the command line."""
import axlib as ax
from scrapli import Scrapli
...
Run it with ./script.py. It works from any directory and needs internet
access the first time you run it.
To take axlib from PyPI instead of the server's wheel, list
"axlib>=1.0" in dependencies and leave out the [tool.uv.sources] lines.
Option 4: credentials without importing axlib#
If your administrator enabled --with-netenv, or you load it yourself with
netenv-set, your shell already has NETUSER, NETPASS, and NETENABLE
(see NETENV.md). Any program can read them, in any language or
environment, without axlib installed:
import os
netuser = os.environ["NETUSER"]
netpass = os.environ["NETPASS"]
netenable = os.environ.get("NETENABLE")
ax.getkeys() also checks these variables first, so Options 1 to 3 give the
same result in such a shell. Remember that after a password change, the
variables keep the old value until you run netenv-set again.
Scheduled jobs (cron and systemd)#
Scheduled jobs don't start a login shell. axlib still finds the host's
configuration (through /etc/axlib/install.env), but other settings need care:
# m h dom mon dow command
15 2 * * * /home/alice/jobs/backup_configs.py >> /home/alice/jobs/backup.log 2>&1
-
cron may not set
USER, and without itax.getkeys()cannot tell whose record to load. It then falls back to the shared account, if one is configured. In scripts meant for cron, name the account explicitly:import getpass netuser, netpass, netenable = ax.getkeys(getpass.getuser())getpass.getuser()also checksLOGNAME, which cron always sets, and finally the account database. -
Use full paths for the interpreter and every file, because cron's working directory and
PATHare minimal. -
For a systemd service, set
User=in the unit to an operator innetops.
Rules for credentials in scripts#
- Get credentials from
ax.getkeys()(orNET*variables) every time. Never paste a password into a script, a config file, an inventory, or a command line. Command lines are visible to every user inps. - Never print, log, or write credentials to a file, including in debug output
and exception messages. Log
netuserif you must, nevernetpassornetenable. - Don't copy the key file or the database. Access comes from the
netopsgroup, and a copy is an unmanaged backup of everyone's passwords. - Check for missing credentials before connecting, as in the examples above. Otherwise a device login attempt with an empty password counts against your lockout limit.
When axlib is upgraded#
-
Option 1 scripts get the new release immediately. Nothing to do.
-
Option 2 and 3 keep the axlib wheel they point to, so the server upgrade cannot break them. To move to the new release,
uv addthe new wheel (Option 2) or edit the path in the script header (Option 3):ls /opt/shared/axlib/releases/*.whl # available releases uv add /opt/shared/axlib/releases/axlib-<new-version>-py3-none-any.whl
axlib version shows the release installed in the shared environment, and
uv run python -c "import axlib; print(axlib.__version__)" shows the one in
your project.
Troubleshooting#
| Symptom | Cause and fix |
|---|---|
install.sh: error: run as root |
Run with sudo. |
install.sh: error: uv was not found |
sudo resets PATH. Install uv into /usr/local/bin (step 1), or run sudo UV=$(command -v uv) ./install.sh. |
No SQLite encryption key is configured when adding a record |
axlib found no [sqlite] settings. AXLIB_CONFIG_FILE points at a file without a [sqlite] section, or /etc/axlib/install.env is missing or unreadable (the user is not in netops). Check with id and ls -l /etc/axlib, or pass --config /etc/axlib/axlib.toml. |
Credential configuration file does not exist: /etc/axlib/axlib.toml, or Permission denied on the key or database, for an operator |
The user is not in netops yet, or has not logged in again since being added. Only netops members can open /etc/axlib, so to anyone else the file looks missing. Check with id. |
axlib: command not found |
/usr/local/bin is not on PATH, or the symlink was removed. Run /opt/shared/axlib/.venv/bin/axlib directly. |
bad interpreter / No such file or directory running axlib |
/opt/shared/python or /opt/shared/axlib was moved, renamed, or deleted. Reinstall to the original paths. |
axuv: error: uv was not found; pass its location: sudo UV=/path/to/uv axuv ... |
The uv recorded by the installer is gone and uv is not on root's PATH. Reinstall uv into /usr/local/bin, or pass UV=. axuv uses UV from the environment first, then the recorded uv, then PATH. |
axuv: error: /etc/axlib/install.env not found |
axlib was installed by an older installer that wrote no record, or the record was deleted. Run the installer again with the options you used. |
Health check shows file ... not-configured and file: No encrypted credential file is configured |
Expected. Only the SQLite store (db) is enabled by default. |
install.sh: warning: the store health check failed |
axlib is installed, but a store enabled in axlib.toml is not ready. The table above the warning shows which one. A text store ([credential_file]) enabled before it was created needs axlib credential-file init --generate-key. With no store enabled at all, such as enabled = false under [sqlite] from an earlier --no-store install, enable one. Then run /opt/shared/axlib/.venv/bin/python -m axlib.credentials.admin again. |
Every lookup prints SQLite credential database does not exist on a host installed with --no-store |
axlib.toml has enabled = true under [sqlite]. Older installers wrote that even with --no-store, and the installer never changes an existing axlib.toml. Set enabled = false, or create the store with sudo ./install.sh --store. |
ModuleNotFoundError: No module named 'netmiko' (or another library) |
The library is not in the environment the script runs in. For the shared environment, ask an administrator to axuv add it. For your own project, run uv add in the project. |
A library works for root, but other users get cannot import name ... (unknown location), PermissionError, or ModuleNotFoundError |
It was installed by plain uv under a restrictive umask. Python treats the unreadable directory as an empty namespace package, which produces these errors. The same restrictive modes are also in root's uv cache, so clear the cache before reinstalling: axuv cache clean <name> <its-dependencies> then axuv sync --reinstall-package <name>. To reset everything, run axuv cache clean && axuv sync --reinstall (this downloads all packages again). |
| A cron job gets the shared account or no credentials | cron did not set USER. Use ax.getkeys(getpass.getuser()) (see Scheduled jobs). |
AttributeError: module 'axlib' has no attribute 'getkeys' or No module named 'axlib.credentials' in your own project |
The project has a 0.1.x release, which contains only the text filters and none of the credential code. PyPI no longer offers 0.1.x, so it came from an internal mirror or cache that kept an old copy, or from an existing environment. Check with uv run python -c "import importlib.metadata as m; print(m.version('axlib'))", then run uv add 'axlib>=1.0' or add the wheel from /opt/shared/axlib/releases/. |