Fix registry login by passing FORGEJO_REGISTRY_USER and FORGEJO_REGISTRY_TOKEN via environment variables instead of direct secret interpolation. Uses the proven pattern from riotpiao.com reference commit. This prevents credentials from being exposed in logs or shell history while keeping the standard docker login approach. After merge + org-level secrets configured: - All repos inherit FORGEJO_REGISTRY_USER and FORGEJO_REGISTRY_TOKEN - CI validates credentials exist before docker login - Image pushed to registry on main push --------- Co-authored-by: Admin Bot <[email protected]> Reviewed-on: #2
homelab-frontend
Production API gateway for the homelab cluster. Single entry point (api.riotpiao.com) for all services: LLM inference, workflows, queues, memory, and cluster operations.
Status: Live in production. Replaced Kong OSS entirely.
Quick Links
- API Reference: See API.md — how to call every service
- Base URL:
https://api.riotpiao.com - Source:
ssh://git.riotpiao.com:2222/rock/homelab-frontend.git
Architecture
┌─────────────┬──────────────┬────────────┐
│ Browser │ SDK │ CLI │
└──────┬──────┴──────┬───────┴────┬───────┘
│ │ │
└─────────────┼────────────┘
│
HTTPS/TLS
│
┌─────────────┼────────────┐
│ Cloudflare Edge │
│ (DDoS, caching) │
└──────────┬──────────────┘
│
ingress-nginx
(SSL termination)
│
┌─────────────────────────────┐
│ homelab-frontend Gateway │
│ (routing, auth, limits) │
└──────┬───────────────────────┘
│
┌──────┴──────────────────────────────────┐
│ │
/v1/* /workflow /sqs /
(LLM) (Temporal gRPC) (Queues) (X-Service)
│ │ │ │
▼ ▼ ▼ ▼
llm-serving temporal:7233 kmsvc/Kafka IAM, S3
(vLLM, Ollama) (WorkflowService) Memory
(TEI) (gRPC bridge) (poimen)
Design principles:
- ✅ Single hostname, multiple path prefixes
- ✅ HTTP REST gateway → gRPC Temporal bridge
- ✅ Bearer token auth via Authentik (JWT + RBAC)
- ✅ Streaming unbuffered (SSE, WebSocket)
- ✅ Per-route timeouts & rate limits
- ✅ No cluster credentials held by gateway
Services & Capabilities
| Service | Prefix | Upstream | Status |
|---|---|---|---|
| LLM Chat | /v1/chat/completions |
llm-serving (vLLM) | ✅ Live |
| Embeddings | /v1/embeddings |
llm-serving (TEI) | ✅ Live |
| Reranking | /v1/rerank |
llm-serving (TEI) | ✅ Live |
| Workflows | /workflow |
Temporal gRPC (7233) | ✅ Live (START, DESCRIBE, SIGNAL, QUERY, etc) |
| Queues | / + X-Service: sqs |
kmsvc/Kafka | ⏳ Ready (ServiceAdapter) |
| Memory | / + X-Service: memory |
poimen-memory | ✅ Live |
| IAM | / + X-Service: iam |
Authentik API | ✅ Live |
| S3 | / + X-Service: s3 |
MinIO | ✅ Live |
How to Use
1. Get a token
Human (OIDC device code):
core auth login
export TOKEN=$(cat ~/.cache/talos/authentik_id_token)
Service account (client credentials):
core mwinit login --username sa-name --password secret
export TOKEN=$(cat ~/.talos/.riotpiao-auth)
2. Call any service
Chat:
curl -X POST https://api.riotpiao.com/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" \
-d '{
"model": "reasoning",
"messages": [{"role": "user", "content": "What is 2+2?"}]
}'
Workflow:
curl -X POST https://api.riotpiao.com/workflow \
-H "Authorization: Bearer $TOKEN" \
-d '{
"action": "START_WORKFLOW",
"namespace": "default",
"payload": {
"workflow_id": "my-workflow",
"workflow_type": "MyWorkflow",
"task_queue": "default"
}
}'
Memory:
curl -X GET https://api.riotpiao.com/ \
-H "Authorization: Bearer $TOKEN" \
-H 'X-Service: memory' \
-H 'X-Resource: query' \
-G --data-urlencode 'query=explain machine learning'
Full examples: See API.md
Available Models
LLM (Chat & Reasoning)
reasoning— DeepSeek-R1-Distill-Qwen-32B (8 concurrent slots)ornith:35b— Ollama 35Bqwen2.5:3b-instruct— Qwen 2.5 3B
Embeddings
nomic-ai/nomic-embed-text-v2-moe— Fast, multilingual
Reranking
BAAI/bge-reranker-base— Document relevance scoring
Authentication
All endpoints (except /healthz, /readyz) require:
Authorization: Bearer <jwt-token>
Tokens validated via Authentik JWKS (runtime fetched, cached, auto-rotated).
Capabilities (RBAC):
llm:inference—/v1/*chat/embeddings/rerankworkflow:execute—/workflowoperationsmemory:read/memory:write— Memory operationssqs:access— Queue operationss3:access— S3 operationsiam:admin— User/group management
Error Handling
All errors return RFC 9457 application/problem+json:
{
"type": "https://api.example.com/problems/unknown-model",
"title": "Unknown Model",
"status": 400,
"detail": "Model 'gpt-4' is not available",
"valid_models": ["reasoning", "ornith:35b", ...]
}
Common status codes:
- 200 OK
- 400 Bad Request (validation, unknown model)
- 401 Unauthorized (missing/invalid token)
- 403 Forbidden (insufficient capability)
- 404 Not Found (workflow, resource)
- 429 Too Many Requests (rate limit)
- 503 Service Unavailable (backend down)
Rate Limits
| Endpoint | Limit | Retry-After |
|---|---|---|
/v1/chat/completions |
8 concurrent | Yes |
/v1/embeddings |
10 concurrent | Yes |
/v1/rerank |
10 concurrent | Yes |
/workflow |
100 concurrent | Yes |
Hitting limit returns 429 with Retry-After header.
Timeouts
| Endpoint | Connect | Read | Write |
|---|---|---|---|
/v1/chat |
10s | 1h | 1h |
/v1/embeddings |
10s | 10m | 10m |
/v1/rerank |
10s | 10m | 10m |
/workflow |
10s | 30s | 10s |
Client disconnects cancel upstream request immediately (no orphaned slots).
Local Development
Run without cluster, no credentials needed:
# Build
go build ./cmd/gateway
# Run locally
./gateway
# Test in another terminal
curl http://localhost:8080/healthz
Points upstreams at local stubs if not connected to cluster (see internal/config).
Code Layout
cmd/gateway/ Server entrypoint
internal/
server/ Router, health checks
proxy/ Reverse proxy, streaming, timeouts
temporal/ Workflow handler + gRPC bridge
serviceadapter/ X-Service dispatcher (CRD-driven)
config/ Route + upstream configuration
auth/ Authentik JWT validation
observability/ Metrics, structured logging
k8s/
configmap.yaml Route definitions
rbac.yaml Service account, roles
deployment.yaml Pod spec
networkpolicy.yaml Ingress/egress rules
testdata/ Fixtures for offline tests
Deployment
Deployed to Kubernetes via ArgoCD:
# Check deployment
kubectl -n api get deployment homelab-frontend
# View logs
kubectl -n api logs -l app=homelab-frontend -f
# Restart
kubectl -n api rollout restart deployment/homelab-frontend
Configuration mounted as ConfigMap (k8s/configmap.yaml).
Health Checks
# Liveness (always succeeds)
curl https://api.riotpiao.com/healthz
# Readiness (waits for config + JWKS)
curl https://api.riotpiao.com/readyz
# List available models
curl https://api.riotpiao.com/v1/models
Support
Issues: Check pod logs
kubectl -n api logs deployment/homelab-frontend
Debug config:
kubectl -n api get configmap homelab-frontend-config -o yaml
Restart pod:
kubectl -n api rollout restart deployment/homelab-frontend
Test endpoint directly:
kubectl -n api port-forward svc/homelab-frontend 8080:8080
curl http://localhost:8080/healthz
See Also
- API.md — Complete API reference with examples
internal/— Source code (handlers, routing, auth)k8s/— Kubernetes manifests