Metadata-Version: 2.4
Name: offliner-py
Version: 0.2.6
Summary: CLI tool for creating and maintaining air-gapped Python projects
Project-URL: Homepage, https://gitlab.com/wookdev/offliner
Project-URL: Source, https://gitlab.com/wookdev/offliner
Project-URL: Issues, https://gitlab.com/wookdev/offliner/-/issues
Author-email: Lukasz Fronc <lukasz@onegamedev.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: air-gap,devops,offline,packaging,vendoring
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: System :: Archiving :: Packaging
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: jinja2>=3.1
Provides-Extra: dev
Requires-Dist: bandit[toml]>=1.7; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pip-tools>=7.0; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# offliner

[![PyPI](https://img.shields.io/pypi/v/offliner-py)](https://pypi.org/project/offliner-py/)

A CLI tool that automates the creation and maintenance of air-gapped Python projects. It scaffolds new projects, generates a self-contained transfer script for downloading vendored dependencies on a networked machine (without transmitting source code), writes back hash-pinned lock files, verifies vendor integrity, and regenerates project boilerplate from templates.

## Workflow

All steps up to and including `bundle export` run with zero network access. The networked machine only receives the generated scripts — no source code is transmitted.

```
[offline] offliner init <name>                  scaffold a new project
[offline] offliner lock                         write requirements stubs from pyproject.toml
[offline] offliner bundle export                embed dep specs in download.sh
[ONLINE]  bash download.sh                      resolve, pin, hash, download → vendor.tar.gz
[offline] offliner bundle apply vendor.tar.gz   extract vendor/, write hash-pinned lock files
[offline] make install-offline                  pip install --require-hashes from vendor/
[offline] offliner audit                        verify hashes + static security scan
```

### Container image bundle (optional)

If your project needs container images on the air-gapped machine, declare them in `pyproject.toml` (or pass `--images` to `offliner init`) and follow the same export/apply pattern:

```toml
# pyproject.toml
[tool.offliner]
images = [
    "registry.access.redhat.com/ubi9/python-311:latest",
    "docker.io/library/postgres:16-alpine",
]
```

```
[offline] offliner bundle image export              generate pull-images.sh (reads [tool.offliner].images)
[ONLINE]  bash pull-images.sh                       pull images, save as .tar, SHA-256 → images.tar.gz
[offline] offliner bundle image apply images.tar.gz verify SHA-256, load into podman/docker
```

## Commands

```
offliner init <name>                    Scaffold a new air-gap-ready Python project
offliner lock                           Write plain requirements stubs from pyproject.toml
offliner bundle export                  Generate a self-contained download.sh
offliner bundle apply <tar>             Extract vendor.tar.gz, write lock files, verify SHA-256
offliner bundle image export            Generate a self-contained pull-images.sh
offliner bundle image apply <tar>       Verify SHA-256 and load images into podman/docker
offliner audit [--online]               bandit + hash verify [+ pip-audit if --online]
offliner generate makefile              (Re)generate Makefile from template
offliner generate pyproject             (Re)generate pyproject.toml from template
```

### `offliner init`

```bash
offliner init myproject \
  --python 3.11 \
  --author "Alice" \
  --email "alice@example.com" \
  --description "My air-gapped service" \
  --version 0.1.0

# With container images (non-interactive; --images may be repeated)
offliner init myproject \
  --images "registry.access.redhat.com/ubi9/python-311:latest" \
  --images "docker.io/library/postgres:16-alpine"
```

Generates a full project skeleton: `pyproject.toml`, `Makefile`, `Containerfile`, `Containerfile.build`, `.pre-commit-config.yaml`, `.gitignore`, and `src/<package>/__init__.py` — all configured for offline operation once `vendor/` is populated. When `--images` is provided the interactive prompt is skipped and the image list is written directly to `[tool.offliner].images` in `pyproject.toml`.

### `offliner bundle export`

Reads `[project.dependencies]` and `[project.optional-dependencies.dev]` directly from `pyproject.toml` and embeds them into `download.sh`. No existing lock files or network access required.

On the networked machine `download.sh` runs `pip-compile --generate-hashes`, downloads all wheels into `vendor/`, and packs `vendor.tar.gz` (which also contains the generated hash-pinned lock files).

### `offliner bundle apply`

Extracts `vendor.tar.gz`, writes `requirements.txt` and `requirements-dev.txt` to the project directory, and verifies every wheel's SHA-256 against the embedded hashes. Exits non-zero on any mismatch.

### `offliner bundle image export` / `apply`

`bundle image export` reads `[tool.offliner].images` from `pyproject.toml` and writes a self-contained `pull-images.sh`. No network access or container runtime is required.

On the networked machine `pull-images.sh` pulls each image, saves it as a `.tar` file, records OCI digests in `images.manifest`, computes SHA-256 checksums, and packs everything into `images.tar.gz`.

`bundle image apply` extracts `images.tar.gz`, verifies every `.tar`'s SHA-256 against the embedded checksums, and loads each image via `podman load` (or `docker load`). Exits non-zero on any verification failure or load error.

```bash
offliner bundle image apply images.tar.gz
offliner bundle image apply images.tar.gz --runtime docker
offliner bundle image apply images.tar.gz --skip-verify   # not recommended
```

### `offliner audit`

Offline: `bandit` static analysis on `src/` + SHA-256 hash verification of `vendor/` against lock files.
Online (`--online`): also runs `pip-audit` against `requirements.txt` for CVE lookups.

## Installation

```bash
# First time (requires network for vendor/)
make venv && make vendor

# Install dev environment from vendor/ (offline from here)
make install-offline
make pre-commit-install

# Day-to-day checks (all offline)
make check          # fmt + lint + typecheck + test
make fmt            # ruff format
make lint           # ruff check
make typecheck      # mypy --strict
make test           # pytest + coverage (80% branch minimum)
```

## Container

```bash
# Runtime image (offline — vendor/ required)
make build-image
podman run --rm offliner:latest --help

# Build environment image (needs network for apt-get)
make build-build-image
make check-in-container     # run make check inside the build container
make build-in-container     # build wheel inside the container; artifacts → dist/
```

## Design notes

- **`pyproject.toml` is the single dependency source** — `[project.dependencies]` and `[project.optional-dependencies.dev]` are canonical. No `.in` files in generated projects.
- **No source code leaves the air-gapped machine** — `download.sh` contains only dependency specs.
- **Hash-pinned everywhere** — `pip install --require-hashes` is enforced in the generated `Makefile`.
- **Pre-commit hooks use `language: system`** — all hooks run from the project venv; nothing is downloaded at install or commit time.
- **Multi-stage Containerfile** — built with `--network=none` to verify no network leakage.
- **`tomllib` (stdlib 3.11+)** — no third-party TOML dependency.
