axlib 1.0.7
Pages and sections ↓

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#

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:

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#

  1. 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 finds uv.
  2. Creates the netops group if it does not exist.
  3. Installs Python into /opt/shared/python. It sets UV_PYTHON_INSTALL_DIR and UV_MANAGED_PYTHON=1 so uv cannot pick an interpreter under someone's home directory, which other users could not read.
  4. Copies the release to /opt/shared/axlib/releases/axlib-<version>/, builds its wheel, and adds the wheel to the /opt/shared/axlib uv project with uv add. uv.lock records the exact versions installed. A part turned off with --no-tui, --no-textfsm, or --no-markdown is removed from the project, and uv sync uninstalls the packages that nothing else needs.
  5. Links /usr/local/bin/axlib and checks that axlib version runs. Only then does it point current at the new release, so if the build, uv add, or the check fails, the installer stops with an error and current still names the release installed before. A failure before uv add has finished also restores the project's pyproject.toml and uv.lock (changed when a part is turned off) and removes the copy and wheel of a release that was never installed. Then it copies axuv to /usr/local/sbin.
  6. Creates /etc/axlib and /var/lib/axlib, or resets the owner, group, and mode of existing ones, and with --with-textfsm creates /etc/axlib/textfsm if it is missing. It writes axlib.toml only if it does not already exist, with the SQLite store enabled, or disabled with --no-store.
  7. Records its options in /etc/axlib/install.env, and sets AXLIB_CONFIG_FILE for all logins.
  8. Unless --no-store is given, runs axlib credential-db init --generate-key when /etc/axlib/sqlite-aes256.key does not exist yet. For an existing store it runs axlib credential-db init, which verifies the key and database and changes no records. An existing key is never replaced.
  9. 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 in axlib.toml is 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:

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:

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#

  1. Be a member of netops: id should list it. Ask an administrator if not, then log in again.

  2. Have a credential record. Check it (values are never shown):

    $ axlib credentials network
    netuser=set
    netpass=set
    netenable=missing
    

    If it says missing for netuser or netpass, add your record: axlib credential-db add --service "$USER" --set netuser="$USER" --prompt netpass --prompt netenable, or use axlib credential-tui if it is installed.

  3. 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.

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:

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

Rules for credentials in scripts#

When axlib is upgraded#

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/.