Skip to main content

Offline install

This page is part of the operator guide. A checkout install that uses the network is in Getting started. This is the procedure for installing Etalon from a local wheelhouse while the install process has no network. It is evidence about that install. It does not determine that a regulation or an internal control has been met. Package version is 0.0.1.

What --no-index does not prove

--no-index and --find-links tell pip to read a local wheelhouse instead of a package index. They do not disable the machine’s network. A successful install with those flags alone is not a claim that the host, or the install process, was offline.

How the network is disabled

examples/offline_install.sh does two steps.
  1. With the caller’s network, it downloads requirements.txt (hash-checked) and builds the project wheel into a wheelhouse. pip wheel build isolation may download hatchling during this step. That traffic is part of the fetch, not the install.
  2. It re-executes the install in a new mount and network namespace:
Passwordless sudo is preferred because it does not depend on unprivileged user namespaces. If sudo is not available, the script uses unshare --propagation private --mount --net --map-root-user. If neither can create a namespace, the script exits. It does not install with the host network still attached. --propagation private keeps the sysfs mount inside the namespace. The host’s interfaces stay where they are. After the install process exits, the script connects to 1.1.1.1:443 again from the host. Before pip install, inside the namespace, the script mounts a fresh sysfs and requires all of the following:
  • that sysfs class/net lists only lo
  • a TCP connect to 1.1.1.1:443 fails with ENETUNREACH (errno 101)
  • resolving pypi.org fails within 5 seconds
The same TCP connect must have succeeded on the host before the fetch. The install environment is a new virtual environment. Its pip is not upgraded. Install environment variables are PIP_DISABLE_PIP_VERSION_CHECK=1, PIP_NO_CACHE_DIR=1, and PIP_NO_INDEX=1, so pip does not read a download cache and does not query an index for a version notice. Proxy variables are unset for the install commands.
The project wheel is built locally, so it is not covered by the hashes in requirements.txt. --no-deps installs that wheel without resolving anything else. The script prints the SHA-256 of the wheel it just built. That archive includes build timestamps, so a later rebuild can have a different digest. The digest is not a lock. Dependency locks stay in requirements.txt. A fresh sysfs mount is required for the interface check. Without it, /sys/class/net can still list the host’s interfaces after unshare --net, while sockets in the namespace still cannot use them. On Linux, pip ignores the Windows-only colorama line in the lock. Run it from a checkout:
The host needs Python 3.11+ with the stdlib venv module (python3-venv on Debian/Ubuntu), unshare from util-linux, and timeout from coreutils. GitHub-hosted Ubuntu runners provide passwordless sudo; the workflow below uses that path.

Observed run

Date: 2026-09-24. Host: Linux x86_64, kernel 6.12.94+, CPython 3.12.3. Command: bash examples/offline_install.sh. The fetch step used the network. pip printed Downloading for the locked requirements and upgraded the fetch environment to pip 26.2.1. pip wheel reported Installing build dependencies while that network was still up (pyproject.toml requires hatchling to build). The wheelhouse held 23 files. pip printed Ignoring colorama because the lock’s sys_platform == "win32" marker does not match Linux. Isolation was:
Inside the namespace, before pip install:
The install environment’s pip was not upgraded. pip then installed only from /tmp/etalon-wheels (Looking in links: /tmp/etalon-wheels and Processing /tmp/etalon-wheels/... for each locked distribution and for etalon-0.0.1-py3-none-any.whl) and printed Successfully installed etalon-0.0.1.
After that process exited, the host sysfs list was lo docker0 enp0s5 and host connect 1.1.1.1:443: ok ('1.1.1.1', 443). The install phase was also started in a mount namespace that did not drop the host network (sudo unshare --propagation private --mount, without --net). It printed sysfs net devices: docker0 enp0s5 lo, printed expected only lo in the network namespace, and exited 1. It did not create the install virtual environment.

GitHub Actions

.github/workflows/offline-install.yml runs the same script on ubuntu-latest with CPython 3.12. The job fails if the namespace cannot be created, if the egress check does not return ENETUNREACH, or if etalon pack validate / etalon verify do not succeed. A green run is the same class of evidence for that runner.