# VenvDrop

**Deploy massive Python environments — `torch`, `llama_cpp`, anything — to completely offline, air-gapped machines without breaking paths or triggering compiler errors.**

[![PyPI](https://img.shields.io/pypi/v/venvdrop)](https://pypi.org/project/venvdrop/)
[![Python](https://img.shields.io/pypi/pyversions/venvdrop)](https://pypi.org/project/venvdrop/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Zero Dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen)](pyproject.toml)

VenvDrop uses the **Offline Wheelhouse** method: `pip download` fetches pre-built binary wheels on your internet-connected machine and zips them — along with your source code and platform metadata — into a single portable archive. On the target machine a single `venvdrop unpack` command creates a fresh virtual environment and installs everything entirely offline.

---

## Installation

```bash
pip install venvdrop
```

Adds the `venvdrop` command globally — no virtual environment activation required.

```bash
venvdrop --version   # venvdrop 0.1.0
venvdrop --help      # full usage
```

---

## Quick Start

### Step 1 — Pack (internet-connected machine)

**Option A: Download everything fresh**
```bash
venvdrop pack \
  --requirements requirements.txt \
  --source ./src \
  --output my_app.zip
```

**Option B: Reuse your existing venv (faster — no re-download)**
```bash
venvdrop pack \
  --requirements requirements.txt \
  --source ./src \
  --output my_app.zip \
  --from-venv .venv
```
VenvDrop reads pip's local wheel cache from the venv, collects everything offline, and only downloads packages genuinely missing from the cache.

### Step 2 — Transfer

Copy `my_app.zip` to the target machine via USB, shared drive, SCP, etc.

### Step 3 — Unpack (offline / air-gapped machine)

```bash
venvdrop unpack \
  --archive my_app.zip \
  --dest ./deployed_app
```

Then activate and run:

```bash
# Windows
.\deployed_app\venv\Scripts\activate

# Linux / macOS
source ./deployed_app/venv/bin/activate
```

---

## Commands & Flags

### `venvdrop pack` — Create a deployment archive

| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--requirements` | `-r` | `requirements.txt` | Path to pip requirements file |
| `--source` | `-s` | `.` | Root directory of project source code |
| `--output` | `-o` | `app_bundle.zip` | Output archive filename |
| `--from-venv` | | *(none)* | Reuse an existing venv's pip wheel cache |

**What `pack` does:**
1. AST-scans every `.py` file for hardcoded absolute paths — warns before bundling
2. Downloads all pip dependencies as pre-built binary wheels (or reuses from cache)
3. Detects source distributions (`.tar.gz`) that would require a compiler on the target
4. Embeds platform metadata (Python version, OS, CPU arch) → `bundle_meta.json`
5. Generates SHA-256 checksums for all bundled files → `manifest.json`
6. Creates the ZIP containing: `bundle_cache/` + `source/` + `requirements.txt` + metadata

**Auto-excluded from source:**
`venv/`, `.venv/`, `.git/`, `__pycache__/`, `node_modules/`, `dist/`, `build/`, `.pytest_cache/`, `.mypy_cache/`, `.pyc`, `.pyo`, `.pyd`, `.so`, `.dll`

---

### `venvdrop unpack` — Deploy on the offline machine

| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--archive` | `-a` | *(required)* | Path to the `.zip` created by `venvdrop pack` |
| `--dest` | `-d` | `./deployed_app` | Destination directory |

**What `unpack` does:**
1. Reads `bundle_meta.json` — validates OS, CPU architecture, Python version
2. Auto-discovers matching Python interpreter if version mismatches (prompts if not found)
3. Windows MAX_PATH guard — warns if destination path > 200 chars
4. Venv collision handling — interactive: Overwrite / Backup / Abort
5. Extracts archive and verifies SHA-256 integrity of every bundled file
6. Creates a fresh `venv/` using the correct Python interpreter
7. Upgrades pip inside the new venv (prevents PEP 517 compatibility issues)
8. Installs all packages offline: `pip install --no-index --find-links bundle_cache/`
9. Prints a post-install checklist for system-level dependencies (CUDA, VC++ Redist, etc.)

---

## What Problems Does VenvDrop Solve?

| Problem | How VenvDrop Handles It |
|---------|------------------------|
| No internet on target machine | Downloads all wheels upfront; installs with `--no-index` |
| Re-downloading slow / wasteful | `--from-venv` reuses pip's local wheel cache — no download needed |
| Source distribution / compile trap | Detects `.tar.gz` sdists and warns: use `--only-binary=:all:` |
| Hardcoded absolute paths | AST-scans every `.py` file and warns before bundling |
| Python version mismatch | Auto-discovers the right interpreter via `py -3.x` (Windows) or `python3.x` (Unix) |
| OS / arch mismatch | Blocks deployment if wheels won't run (e.g. x86\_64 → ARM) |
| Windows MAX\_PATH (260 chars) | Warns if destination path exceeds 200 characters |
| Windows command-line too long | Wheel cache is staged into a single flat dir; one `--find-links` arg |
| Existing venv collision | Interactive prompt: Overwrite / Backup / Abort |
| Bundle corruption in transit | SHA-256 manifest verified on every unpack |
| Outdated pip in new venv | Bootstraps `pip install --upgrade pip` before offline install |
| Missing system drivers | Post-install checklist: CUDA, VC++ Redistributable, OpenSSL, ODBC |

---

## Why Not Just Copy the `venv` Folder?

Copying a `venv` directory breaks because:
- Absolute paths baked into scripts and `.pth` files point to the source machine
- Native extensions (`.pyd`, `.so`) are compiled for a specific OS + arch
- The Python interpreter path inside the venv is hardcoded

VenvDrop creates a **fresh** venv on the target machine and populates it from pre-built wheels, so all paths are correct from the start.

---

## Troubleshooting

| Issue | Fix |
|-------|-----|
| sdist warning during pack | Re-run pip download with `--only-binary=:all:` |
| Python version mismatch | Install required Python on target; VenvDrop will prompt for path |
| OS / arch mismatch on unpack | Pack on a machine that matches the target OS and CPU |
| Windows MAX_PATH error | Choose a shorter `--dest`, e.g. `C:\app` |
| `bundle_meta.json` missing | Archive wasn't created by VenvDrop; re-pack with `venvdrop pack` |
| Integrity check failures | Archive was corrupted in transit; re-transfer the ZIP |
| `--from-venv` can't find python | Pass the venv ROOT (folder containing `Scripts/` or `bin/`) |

---

## Requirements

- Python ≥ 3.8
- **Zero runtime dependencies** — pure Python stdlib only

---

## Development

```bash
git clone https://github.com/ashtharthasarathi/venvdrop.git
cd venvdrop
pip install -e .          # editable install — changes apply immediately
venvdrop --version
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for full dev guide and PyPI release steps.

---

## License

MIT — see [LICENSE](LICENSE).
