# pragent pilot — central dashboard service. # # Read-only overview + per-repo / per-PR drilldown over the same SQLite # feedback DB the webhook writes. Also mutates `.pr-review.json` on covered # repos via the Gitea contents API (Tasks C+D in pilot/dashboard.py). Same # image as the webhook (`pragent-webhook:optin`) — all pilot modules are # baked in at /app/pilot/. # # Routes: GET / (overview), GET /r// (repo), GET /r/// (PR), # GET /r////raw (PR markdown raw), GET /login, GET /static/style.css, # POST /login, POST /r///edit. # # Auth: PRAGENT_DASHBOARD_TOKEN in the pragent-webhook Secret, cookie # `pragent_dash=`, single-user. Empty / unset = no auth (tailnet-only). # # NodePort 30082 — only reachable on the Tailscale / LAN side of kubernets # (100.74.17.70 / 192.168.1.80) until/unconfigured. Mirrors pragent-webhook.yaml # in every other respect (uid 10001, nodeSelector, /data PVC). apiVersion: apps/v1 kind: Deployment metadata: name: pragent-dashboard namespace: pragent labels: app: pragent-dashboard spec: replicas: 1 selector: matchLabels: app: pragent-dashboard template: metadata: labels: app: pragent-dashboard spec: # Same node as the webhook — holds the headroom proxy + the /data PVC. nodeSelector: kubernetes.io/hostname: kubernets # Dashboard is read-only over /data and only mutates Gitea (not local # files), so unprivileged is fine. fsGroup matches the image's USER # directive (10001) so the RO mount is readable. securityContext: runAsNonRoot: true runAsUser: 10001 runAsGroup: 10001 fsGroup: 10001 containers: - name: dashboard image: pragent-webhook:optin imagePullPolicy: Never workingDir: /app command: ["python3", "-m", "pilot.dashboard"] ports: - name: http containerPort: 8081 env: - name: PRAGENT_FEEDBACK_DB value: /data/feedback.db - name: PRAGENT_GITEA_API value: http://gitea-http.gitea.svc.cluster.local:3000 # Dashboard reads DASHBOARD_PORT (not PORT) — verified in # pilot/dashboard.py:51. Default 8081 if unset. - name: DASHBOARD_PORT value: "8081" # Used by /r///edit to PUT updated JSON to the repo's # contents API. Reuses the same bot token the webhook uses. - name: PRAGENT_BOT_TOKEN valueFrom: secretKeyRef: name: pragent-webhook key: PRAGENT_BOT_TOKEN # Auth cookie value. Add to the pragent-webhook Secret with: # kubectl patch secret pragent-webhook -n pragent --type=json \ # -p='[{"op":"add","path":"/data/PRAGENT_DASHBOARD_TOKEN","value":""}]' - name: PRAGENT_DASHBOARD_TOKEN valueFrom: secretKeyRef: name: pragent-webhook key: PRAGENT_DASHBOARD_TOKEN # /data is read-only — the dashboard doesn't write the SQLite file; # .pr-review.json mutations go through the Gitea contents API, not # local fs. RO avoids any chance of two pods racing the same RWO PVC. volumeMounts: - name: feedback-data mountPath: /data readOnly: true # No /health route in dashboard.py (returns 404 on unknown paths). # Probes omitted intentionally — see pilot/dashboard.py:687-721. # Resources: dashboard is read-heavy + tiny writes. /data RO + no # subprocess fan-out (no opencode) keeps footprint small. resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi volumes: - name: feedback-data persistentVolumeClaim: claimName: pragent-feedback-data --- apiVersion: v1 kind: Service metadata: name: pragent-dashboard namespace: pragent spec: selector: app: pragent-dashboard ports: - name: http port: 80 targetPort: http type: NodePort # 30082 — between pages-proxy (30081) and browserless (30100), outside the # 30096..30969 media range. Tailscale / LAN only until a Caddy route is set.