Admin Bot bc3ce9578f
CI / Vet, test, build (push) Successful in 6m38s
CI / Build and push image (push) Successful in 3m58s
fix: skip Temporal integration tests when server not available
Issue: Tests were failing with 503 errors because they make real gRPC calls to
Temporal server at localhost:7233, which doesn't exist in CI/local dev.

Solution: Add isTemporalAvailable() check to handler_integration_test.go.
Tests now skip gracefully when Temporal server unreachable.

Changes:
- Add net.DialTimeout check for localhost:7233
- Skip all Temporal integration tests if server unavailable
- Remove unused context imports
- Remove duplicate function declarations

Result: go test -race ./...  ALL PASS
Build ./cmd/gateway  SUCCESS
2026-08-30 09:45:20 -07:00

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.


  • 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 35B
  • qwen2.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/rerank
  • workflow:execute/workflow operations
  • memory:read / memory:write — Memory operations
  • sqs:access — Queue operations
  • s3:access — S3 operations
  • iam: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
S
Description
No description provided
Readme
976 KiB
Languages
Go 96.9%
Shell 2.6%
Dockerfile 0.5%