blumeops/docs/how-to/operations/devpi-on-indri.md

74 lines
2.9 KiB
Markdown
Raw Normal View History

Migrate devpi from minikube to indri (launchd) (#341) ## Summary Devpi was crash-looping under memory pressure on the minikube StatefulSet, breaking the Python toolchain across the repo (`mise run docs-mikado`, `prek`, every `uv pip install`). It moves to indri as a native LaunchAgent. ## What changed - **New ansible role** `ansible/roles/devpi/`: installs `devpi-server` + `devpi-web` into a uv-managed venv, initializes the server-dir on first run via 1Password root password, runs as a LaunchAgent (`mcquack.eblume.devpi`) bound to `127.0.0.1:3141`. Bootstraps from upstream PyPI (so devpi can install itself on a fresh box). - **Caddy**: `pypi.ops.eblu.me` now proxies to `http://localhost:3141`. - **Playbook**: `indri.yml` gains pre_tasks for the root password and the new role. - **service-versions.yaml**: devpi flipped from `type: argocd` to `type: ansible`. - **ArgoCD**: removed `apps/devpi.yaml` and `manifests/devpi/`. The in-cluster Application, namespace, and PVC have been deleted. - **Docs**: new how-to `docs/how-to/operations/devpi-on-indri.md`; `restart-indri.md` lists devpi in the LaunchAgent stop list. ## Already deployed (live on indri) - Service running: `launchctl list mcquack.eblume.devpi` → PID 53888 - `curl https://pypi.ops.eblu.me/+api` returns 200 ✅ - `mise run docs-mikado` works again ✅ - 1.0G of cached PyPI data was migrated from the PVC to `~erichblume/devpi/server-dir/` - Minikube namespace and PVC fully reclaimed ## Test plan - [ ] `mise run services-check` (after merge) - [ ] CI workflows that use devpi succeed - [ ] No regressions in tools that depend on `pypi.ops.eblu.me` (prek, uv-script tasks, dagger pipelines) ## Context This is the C1 prelude to a planned C2 chain (`mikado/retire-minikube-indri`) to retire minikube on indri entirely. Doing devpi as a standalone C1 was the right call because (a) it was urgent — it was breaking the toolchain — and (b) it shakes out the migration recipe before we commit to a multi-leaf chain. Reviewed-on: https://forge.eblu.me/eblume/blumeops/pulls/341
2026-04-29 13:38:36 -07:00
---
title: Devpi on Indri
modified: 2026-04-29
last-reviewed: 2026-04-29
tags:
- how-to
- operations
---
# Devpi on Indri
How devpi (the PyPI caching mirror at `pypi.ops.eblu.me`) is deployed on indri as a launchd-managed native service. Replaces the prior minikube StatefulSet.
## Why native, not Kubernetes
Devpi has no runtime dependencies beyond a Python interpreter, a writable directory, and outbound HTTPS to upstream PyPI. Running it on indri natively removes a layer of operational complexity, frees minikube resources, and decouples this critical-path tooling (used by every Python build, including `mise run docs-mikado` itself) from cluster health.
## Layout
| Concern | Path / detail |
|---|---|
| Service binary | `/Users/erichblume/devpi/venv/bin/devpi-server` |
| Server-dir (data) | `/Users/erichblume/devpi/server-dir/` |
| Logs | `/Users/erichblume/Library/Logs/mcquack.devpi.{out,err}.log` |
| LaunchAgent label | `mcquack.eblume.devpi` |
| LaunchAgent plist | `~/Library/LaunchAgents/mcquack.eblume.devpi.plist` |
| Listen address | `127.0.0.1:3141` (loopback only) |
| Public URL | `https://pypi.ops.eblu.me` (via Caddy reverse proxy) |
| Root password secret | 1Password item `devpi`, field `root password` |
The venv is built fresh by ansible from a pinned `devpi-server` and `devpi-web` version; bumping versions is a config change in `ansible/roles/devpi/defaults/main.yml`.
## Deploy
```fish
mise run provision-indri -- --tags devpi
```
Ansible will:
1. Fetch the root password from 1Password (in playbook `pre_tasks`)
2. Create the venv at `~/devpi/venv` if absent and install/upgrade `devpi-server` + `devpi-web` to the pinned versions
3. Initialize the server-dir (only on first run, when `.serverversion` is missing)
4. Render and load the LaunchAgent plist
5. Restart the service if the plist or config changed
Caddy already proxies `pypi.ops.eblu.me``127.0.0.1:3141`; nothing else routes traffic.
## Verify
```fish
ssh indri 'launchctl list mcquack.eblume.devpi'
curl -fsS https://pypi.ops.eblu.me/+api | jq
uv pip install --index-url https://pypi.ops.eblu.me/root/pypi/+simple/ requests
```
## Logs
```fish
ssh indri 'tail -f ~/Library/Logs/mcquack.devpi.err.log'
```
## Bumping devpi versions
Edit `devpi_server_version` / `devpi_web_version` in `ansible/roles/devpi/defaults/main.yml`, then re-run the playbook with `--tags devpi`. The role rebuilds the venv in-place; the server-dir survives.
## Backup
The server-dir is **not** in `borgmatic_source_directories` and is not backed up. The PyPI cache (`+files/`) is re-fetchable from upstream on first request; the local `eblume/dev` index can be republished from source. If retention becomes important, add `/Users/erichblume/devpi/server-dir/` to the borgmatic source list.
## Related
- [[restart-indri]] — devpi is one of the LaunchAgents to stop on graceful shutdown
- [[connect-to-postgres]] — pattern for indri-native services (different stack, similar shape)