blumeops/pulumi/tailscale/policy.hujson
Erich Blume 14ca0160ba 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: #341
2026-04-29 13:38:36 -07:00

200 lines
5.7 KiB
Text

// Tailnet ACL policy for tail8d86e.ts.net
// Managed by blumeops-pulumi
{
// ============== Groups ==============
"groups": {
// Placeholder for future Jellyfin media access
"group:allisonflix": [
"blume.erich@gmail.com",
"acmdavis@gmail.com",
],
},
// ============== Access Grants ==============
"grants": [
// --- Admins: full access to all infrastructure ---
{
"src": ["autogroup:admin"],
"dst": ["*"],
"ip": ["*"],
},
// --- Members: user-facing services only ---
// Kiwix, Forge, Miniflux, PostgreSQL
// (devpi moved off-cluster to indri; reachable via Caddy on tag:flyio-target)
{
"src": ["autogroup:member"],
"dst": ["tag:kiwix"],
"ip": ["tcp:443"],
},
{
"src": ["autogroup:member"],
"dst": ["tag:forge"],
"ip": ["tcp:443", "tcp:22"],
},
{
"src": ["autogroup:member"],
"dst": ["tag:feed"],
"ip": ["tcp:443"],
},
{
"src": ["autogroup:member"],
"dst": ["tag:pg"],
"ip": ["tcp:5432"],
},
// Note: No member access to grafana, loki, or NAS
// --- Infrastructure ---
{
"src": ["tag:homelab"],
"dst": ["tag:homelab"],
"ip": ["*"],
},
{
"src": ["tag:homelab"],
"dst": ["tag:nas"],
"ip": ["*"],
},
// --- Fly.io proxy ---
// Public reverse proxy can only reach explicitly tagged endpoints
{
"src": ["tag:flyio-proxy"],
"dst": ["tag:flyio-target"],
"ip": ["tcp:443"],
},
// --- CI Gateway ---
// Ephemeral CI containers can push images to registry
{
"src": ["tag:ci-gateway"],
"dst": ["tag:registry"],
"ip": ["tcp:443"],
},
// --- Kubernetes workloads ---
// k8s workloads (e.g., Woodpecker CI) can push/pull from registry
{
"src": ["tag:k8s"],
"dst": ["tag:registry"],
"ip": ["tcp:443"],
},
// k8s workloads (e.g., ArgoCD) can access forge on indri for GitOps
// HTTP on 3001, SSH on 2200
{
"src": ["tag:k8s"],
"dst": ["tag:homelab"],
"ip": ["tcp:3001", "tcp:2200"],
},
// Homelab can reach k8s services: PostgreSQL, CNPG metrics, Prometheus/Loki
{
"src": ["tag:homelab"],
"dst": ["tag:k8s"],
"ip": ["tcp:443", "tcp:5432", "tcp:9187"],
},
],
// ============== SSH Access ==============
"ssh": [
// Members can SSH to their own devices
{
"action": "check",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot"],
},
// Admins can SSH to homelab (for ansible)
{
"action": "check",
"src": ["autogroup:admin"],
"dst": ["tag:homelab"],
"users": ["autogroup:nonroot"],
"checkPeriod": "12h0m0s",
},
// Admins can SSH to NAS
{
"action": "check",
"src": ["autogroup:admin"],
"dst": ["tag:nas"],
"users": ["autogroup:nonroot"],
"checkPeriod": "12h0m0s",
},
// Homelab can SSH to homelab (for ansible, cross-host management)
// Tagged devices can't do interactive "check" auth, so use "accept".
{
"action": "accept",
"src": ["tag:homelab"],
"dst": ["tag:homelab"],
"users": ["autogroup:nonroot"],
},
],
// ============== Auto Approvers ==============
// Allow ProxyGroup pods (tag:k8s) to auto-approve VIP Services
// Required for multi-cluster Ingress per Tailscale docs
"autoApprovers": {
"services": {
"tag:k8s": ["tag:k8s"],
},
},
// ============== Tag Owners ==============
"tagOwners": {
"tag:blumeops": ["autogroup:admin", "tag:blumeops"],
"tag:homelab": ["autogroup:admin", "tag:blumeops"],
"tag:workstation": ["autogroup:admin", "tag:blumeops"],
"tag:nas": ["autogroup:admin", "tag:blumeops"],
"tag:grafana": ["autogroup:admin", "tag:blumeops"],
"tag:kiwix": ["autogroup:admin", "tag:blumeops"],
"tag:forge": ["autogroup:admin", "tag:blumeops"],
"tag:loki": ["autogroup:admin", "tag:blumeops"],
"tag:pg": ["autogroup:admin", "tag:blumeops"],
"tag:feed": ["autogroup:admin", "tag:blumeops"],
"tag:registry": ["autogroup:admin", "tag:blumeops"],
"tag:k8s-api": ["autogroup:admin", "tag:blumeops"],
"tag:k8s-operator": ["autogroup:admin", "tag:blumeops", "tag:k8s-operator"],
"tag:k8s": ["autogroup:admin", "tag:blumeops", "tag:k8s-operator"],
"tag:ci-gateway": ["autogroup:admin", "tag:blumeops"],
"tag:flyio-proxy": ["autogroup:admin", "tag:blumeops"],
"tag:flyio-target": ["autogroup:admin", "tag:blumeops", "tag:k8s-operator"],
},
// ============== ACL Tests ==============
"tests": [
// Erich can access everything
{
"src": "blume.erich@gmail.com",
"accept": ["tag:grafana:443", "tag:kiwix:443", "tag:feed:443", "tag:loki:3100", "tag:pg:5432", "tag:homelab:22", "tag:registry:443", "tag:k8s-api:443"],
},
// Allison can access user services but NOT grafana, loki, or NAS
{
"src": "acmdavis@gmail.com",
"accept": ["tag:kiwix:443", "tag:forge:443", "tag:feed:443", "tag:pg:5432"],
"deny": ["tag:grafana:443", "tag:loki:3100", "tag:nas:445", "tag:registry:443", "tag:k8s-api:443"],
},
// Homelab can reach homelab, NAS, and k8s services (postgres, metrics, prometheus/loki)
{
"src": "tag:homelab",
"accept": ["tag:homelab:22", "tag:nas:445", "tag:k8s:443", "tag:k8s:5432", "tag:k8s:9187"],
},
// K8s workloads can reach registry and forge (on indri:3001 HTTP, :2200 SSH)
{
"src": "tag:k8s",
"accept": ["tag:registry:443", "tag:homelab:3001", "tag:homelab:2200"],
},
// CI gateway can push to registry
{
"src": "tag:ci-gateway",
"accept": ["tag:registry:443"],
},
// Fly.io proxy can only reach flyio-target tagged endpoints, nothing else.
// indri has tag:flyio-target (Caddy) so tag:homelab:443 is reachable on
// indri specifically but not other homelab devices.
{
"src": "tag:flyio-proxy",
"accept": ["tag:flyio-target:443"],
"deny": ["tag:k8s:443", "tag:homelab:22", "tag:nas:445", "tag:registry:443"],
},
],
}