diff --git a/argocd/apps/devpi.yaml b/argocd/apps/devpi.yaml new file mode 100644 index 0000000..07e8250 --- /dev/null +++ b/argocd/apps/devpi.yaml @@ -0,0 +1,30 @@ +# devpi PyPI Caching Proxy +# Provides PyPI cache and private package hosting +# +# After first deployment, initialize devpi: +# kubectl -n devpi exec -it devpi-0 -- devpi-init --serverdir /devpi --root-passwd +# kubectl -n devpi rollout restart statefulset devpi +# +# Then create user/index: +# uvx devpi use https://pypi.tail8d86e.ts.net +# uvx devpi login root +# uvx devpi user -c eblume email=blume.erich@gmail.com +# uvx devpi index -c eblume/dev bases=root/pypi +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: devpi + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + targetRevision: main + path: argocd/manifests/devpi + destination: + server: https://kubernetes.default.svc + namespace: devpi + syncPolicy: + syncOptions: + - CreateNamespace=true + # Manual sync only - no automated sync on git push diff --git a/argocd/manifests/devpi/Dockerfile b/argocd/manifests/devpi/Dockerfile new file mode 100644 index 0000000..31e7d7c --- /dev/null +++ b/argocd/manifests/devpi/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.12-slim + +# Install devpi-server and devpi-web +RUN pip install --no-cache-dir devpi-server devpi-web + +# Create non-root user +RUN useradd -r -u 1000 devpi && mkdir -p /devpi && chown devpi:devpi /devpi + +USER devpi +WORKDIR /devpi + +# Expose default port +EXPOSE 3141 + +# Use ENTRYPOINT for flexibility +ENTRYPOINT ["devpi-server"] + +# Default args (can be overridden) +CMD ["--serverdir", "/devpi", "--host", "0.0.0.0", "--port", "3141"] diff --git a/argocd/manifests/devpi/README.md b/argocd/manifests/devpi/README.md new file mode 100644 index 0000000..b68de74 --- /dev/null +++ b/argocd/manifests/devpi/README.md @@ -0,0 +1,70 @@ +# devpi PyPI Caching Proxy + +devpi-server running in Kubernetes, providing: +- PyPI caching proxy at `root/pypi` +- Private package hosting at `eblume/dev` + +## Setup + +### 1. Deploy via ArgoCD + +```bash +argocd app sync apps +argocd app sync devpi +``` + +### 2. Initialize devpi (first time only) + +After the StatefulSet is running, initialize devpi with a root password: + +```bash +# Get the root password from 1Password +ROOT_PASSWORD=$(op --vault blumeops item get --fields password --reveal) + +# Initialize devpi +kubectl -n devpi exec -it devpi-0 -- devpi-init --serverdir /devpi --root-passwd "$ROOT_PASSWORD" + +# Restart the pod to pick up the initialized state +kubectl -n devpi rollout restart statefulset devpi +``` + +### 3. Create user and index + +```bash +# Login to devpi +uvx devpi use https://pypi.tail8d86e.ts.net +uvx devpi login root + +# Create user +uvx devpi user -c eblume email=blume.erich@gmail.com + +# Create private index inheriting from PyPI +uvx devpi index -c eblume/dev bases=root/pypi +``` + +## Usage + +### As pip index (caching proxy) + +Configure `~/.config/pip/pip.conf`: + +```ini +[global] +index-url = https://pypi.tail8d86e.ts.net/root/pypi/+simple/ +trusted-host = pypi.tail8d86e.ts.net +``` + +### Upload private packages + +```bash +# Build and publish +cd ~/code/personal/your-package +uv build +uv publish --publish-url https://pypi.tail8d86e.ts.net/eblume/dev/ +``` + +## URLs + +- Web UI: https://pypi.tail8d86e.ts.net +- PyPI cache: https://pypi.tail8d86e.ts.net/root/pypi/+simple/ +- Private index: https://pypi.tail8d86e.ts.net/eblume/dev/+simple/ diff --git a/argocd/manifests/devpi/ingress-tailscale.yaml b/argocd/manifests/devpi/ingress-tailscale.yaml new file mode 100644 index 0000000..2a1c659 --- /dev/null +++ b/argocd/manifests/devpi/ingress-tailscale.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: devpi-tailscale + namespace: devpi + annotations: + tailscale.com/proxy-class: "crio-compat" +spec: + ingressClassName: tailscale + defaultBackend: + service: + name: devpi + port: + number: 3141 + tls: + - hosts: + - pypi diff --git a/argocd/manifests/devpi/kustomization.yaml b/argocd/manifests/devpi/kustomization.yaml new file mode 100644 index 0000000..6bc7579 --- /dev/null +++ b/argocd/manifests/devpi/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: devpi + +resources: + - statefulset.yaml + - service.yaml + - ingress-tailscale.yaml diff --git a/argocd/manifests/devpi/service.yaml b/argocd/manifests/devpi/service.yaml new file mode 100644 index 0000000..42e1543 --- /dev/null +++ b/argocd/manifests/devpi/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: devpi + namespace: devpi +spec: + selector: + app: devpi + ports: + - name: http + port: 3141 + targetPort: 3141 + protocol: TCP diff --git a/argocd/manifests/devpi/statefulset.yaml b/argocd/manifests/devpi/statefulset.yaml new file mode 100644 index 0000000..21dac52 --- /dev/null +++ b/argocd/manifests/devpi/statefulset.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: devpi + namespace: devpi +spec: + serviceName: devpi + replicas: 1 + selector: + matchLabels: + app: devpi + template: + metadata: + labels: + app: devpi + spec: + securityContext: + fsGroup: 1000 + containers: + - name: devpi + image: registry.tail8d86e.ts.net/blumeops/devpi:latest + args: + - "--serverdir" + - "/devpi" + - "--host" + - "0.0.0.0" + - "--port" + - "3141" + - "--outside-url" + - "https://pypi.tail8d86e.ts.net" + ports: + - containerPort: 3141 + name: http + volumeMounts: + - name: data + mountPath: /devpi + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /+api + port: 3141 + initialDelaySeconds: 30 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /+api + port: 3141 + initialDelaySeconds: 10 + periodSeconds: 10 + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 50Gi