Metadata-Version: 2.4
Name: viper-pm
Version: 0.3.1
Summary: Production-grade process manager for Python services on Linux servers
Author: Adloggs
License: MIT
Project-URL: Homepage, https://github.com/ramakrishnan2808/viper-pm
Keywords: process-manager,supervisor,daemon,pm2,production
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: psutil>=5.9
Requires-Dist: click>=8.1
Requires-Dist: rich>=13.0
Requires-Dist: PyYAML>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# viper

**Production-grade process manager for Python services on Linux servers.**
Start apps from the terminal, keep them alive, watch their memory, tail their
logs — and never touch a process it didn't start itself.

Built for teams running Python/AI services (model servers, workers, APIs) who
are tired of tmux sessions, `nohup`, and 3 a.m. surprises.

```bash
viper start "uvicorn app:app --port 8000" --name api -i 2 --max-memory 2G
viper ls
viper logs api -f
```

## Why not pm2?

pm2 is great — and written in Node.js. `viper` is pure Python (psutil +
asyncio), designed around what Python/AI services actually need: virtualenv
auto-detection, memory watchdogs for leaky inference processes, a daemon that
survives its own crash, and (on the roadmap) readiness probes for
slow-loading models and GPU awareness.

## Install

```bash
# apt (Debian/Ubuntu servers) — one-shot, installs python3 + all deps automatically:
curl -fsSL https://raw.githubusercontent.com/ramakrishnan2808/viper-pm/main/install.sh | sudo sh

# or pip/pipx (any Linux):
pipx install viper-pm        # or: pip install viper-pm
```

(The installer adds the project's signed apt repository once — apt can only
install from repositories it knows, and viper-pm isn't in the official
Debian/Ubuntu archive yet. After that, updates arrive via normal `apt upgrade`.)

From source:

```bash
git clone <repo> && cd viper && pip install -e .
```

## Quickstart

```bash
# start anything: a command, a .py file, or a config file
viper start "uvicorn app:app --port 8000" --name api -i 2 --max-memory 1G
viper start worker.py --name worker
viper start viper.yml

viper ls                 # table of apps, workers, cpu, memory, uptime, restarts
viper logs api -f        # follow logs (per-worker prefixes)
viper events api         # audit trail: every start/exit/restart and *why*
viper reload api         # rolling restart: workers restart one at a time
viper stop api           # graceful stop (SIGTERM, grace period, then SIGKILL)
viper delete api         # stop + remove from management
viper kill               # stop everything and shut the daemon down
```

The daemon starts automatically on first use and runs per-user. If the daemon
itself is killed, **your apps keep running** — the next `viper` command
respawns it and it re-attaches to every live worker from its journal.

## The team workflow: `viper apply`

Keep a `viper.yml` in each project repo; deploys become:

```bash
git pull && viper apply viper.yml
```

`apply` converges the server to the file: new apps start, changed apps
restart with the new config, unchanged apps are left alone
(`--prune` also removes apps missing from the file).

```yaml
apps:
  - name: api
    cmd: uvicorn app:app --host 0.0.0.0 --port 8000
    cwd: /srv/api            # relative paths resolve against this file
    venv: auto               # finds .venv/ or venv/ in cwd (or give a path)
    workers: 2               # each worker gets VIPER_WORKER_ID=0,1,...
    max_memory: 2G           # restart a worker whose process tree exceeds this
    env_file: .env           # loaded fresh at every (re)start; env: below wins
    env:
      MODEL_PATH: /models/base
    stop_signal: SIGTERM
    stop_grace: 30           # seconds before SIGKILL
    autorestart: true
    max_restarts: 10         # consecutive fast crashes before giving up
    min_uptime: 10           # seconds that count as a "stable" run

  - name: worker
    cmd: celery -A tasks worker
    cwd: /srv/pipeline
```

`cmd` supports `$VAR` / `${VAR}` from the final environment (including
`env_file`), e.g. `cmd: uvicorn app:app --port ${API_PORT}` with `API_PORT`
in `.env`. Apps can be addressed by name or by the numeric id from
`viper ls` (e.g. `viper restart 0`).

## Cluster mode (pm2-style, one port, N workers)

Give the app a `port:` and viper creates the listening socket itself, then
hands it to every worker — the kernel load-balances connections across them,
and `viper reload` is truly zero-downtime because the port never closes:

```yaml
apps:
  - name: api
    cmd: uvicorn app:app --fd ${VIPER_SOCKET_FD}   # uvicorn binds the shared socket
    port: 8000
    workers: 4
```

```bash
viper start "uvicorn app:app --fd \${VIPER_SOCKET_FD}" --name api -i 4 -p 8000
```

Works with anything that accepts an inherited socket fd (uvicorn `--fd`,
hypercorn `--fd`, or `socket.socket(fileno=...)` in your own code).
gunicorn users don't need this — gunicorn is its own master/worker cluster;
run it as a single viper app (`workers: 1`) and size it with `gunicorn -w N`.

## Reboot persistence

```bash
viper startup     # one-time: installs a systemd service for the daemon
```

That's all a server needs: after a reboot, systemd starts the viper daemon
and the daemon automatically restores every app that was running, from its
journal. Your apps never need individual systemd services.

`viper save` / `viper resurrect` also exist (pm2-style) for explicit
snapshots — e.g. save a known-good set before experimenting, resurrect to
return to it. `viper unstartup` removes the boot service.

## Guarantees

- **Never touches foreign processes.** Every managed PID is stored with its
  process create-time and both are re-verified before any signal is sent — a
  recycled PID is never signalled. Workers run in their own process group, so
  signals reach the worker's own tree and nothing else.
- **Daemon crashes are non-events.** Workers write logs straight to files and
  keep running; a restarted daemon re-attaches from the journal.
- **Honest restart behaviour.** Exponential backoff (0.5s → 30s cap), a
  circuit breaker after `max_restarts` consecutive fast crashes (state
  `errored`, visible in `viper ls`), and every restart's reason recorded in
  `viper events`.

## Environment your app sees

| Variable | Meaning |
|---|---|
| `VIPER_APP_NAME` | the app's name |
| `VIPER_WORKER_ID` | worker index `0..N-1` (use it to fan out ports) |
| `PYTHONUNBUFFERED=1` | set by default so logs stream live |
| `VIRTUAL_ENV`, `PATH` | pointed at the detected/configured virtualenv |

Files live under `~/.viper/` (override with `VIPER_HOME`): per-worker logs
in `logs/`, the state journal, the events audit log, and the daemon log.

## Roadmap

Readiness/liveness health checks with `startup_grace` for slow model loads →
readiness-gated zero-downtime reload → `viper monit` live TUI → alert
webhooks (Slack) → Prometheus metrics → GPU awareness (CUDA_VISIBLE_DEVICES
assignment, GPU-memory watchdog) → `viper startup` systemd generation → apt
repo + snap. See `PLAN.md` for the full plan and `docs/PACKAGING.md` for the
apt/snap path.

## Development

```bash
python3 -m venv .venv && .venv/bin/pip install -e .[dev]
.venv/bin/python -m pytest tests/
```

## License

MIT — see [LICENSE](LICENSE).
