# Felhom Hub — Multi-customer dashboard # Dashboard: https://hub.felhom.eu # API: POST /api/v1/report (Bearer token auth) # # Receives health reports from customer controllers and displays # a centralized overview dashboard for the operator (Viktor). # # Namespace: felhom-system (shared with healthchecks and other felhom infra) # # PREREQUISITES: # 1. Build and push the hub image: # cd ~/build/felhom-hub && ./build.sh v0.2.0 --push # # 2. Generate a bcrypt password hash for dashboard login: # htpasswd -nbBC 10 "" "your-password" | cut -d: -f2 # Update the ConfigMap password_hash field below. # # 3. Generate a report API key (shared secret for controllers): # openssl rand -hex 32 # Update the ConfigMap report_api_key field below. # Then add the same key to each customer's controller.yaml: # hub: # enabled: true # url: "https://hub.felhom.eu" # api_key: "" # # 4. Apply this manifest: # kubectl apply -f manifests/hub.yaml # # 5. Configure DNS: # Add hub.felhom.eu → k3s cluster IP in Cloudflare # # DEBUGGING: # kubectl logs -n felhom-system deploy/hub -f # kubectl exec -it -n felhom-system deploy/hub -- ls /data/ # kubectl describe ingress -n felhom-system hub # ============================================================================= # PERSISTENT STORAGE # ============================================================================= --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: hub-data namespace: felhom-system labels: app: hub recurring-job-group.longhorn.io/default: disabled spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 1Gi # ============================================================================= # CONFIGURATION # ============================================================================= --- apiVersion: v1 kind: ConfigMap metadata: name: hub-config namespace: felhom-system data: hub.yaml: | auth: # Bcrypt hash for dashboard login (Viktor only) # Generate: htpasswd -nbBC 10 "" "your-password" | cut -d: -f2 password_hash: "$2y$10$N5.O9jBnc.1tIlJT/irx3OlVjJQemlCHRnfqIJg/EyZofnzXSCpeG" api: # Shared secret for controller → hub report push # Generate: openssl rand -hex 32 # Must match hub.api_key in each customer's controller.yaml report_api_key: "094091de545ce28795c47ac2158fc30750db5c24a621c49329b001ee8db57fb8" retention: max_days: 90 prune_schedule: "04:30" alerting: stale_threshold: "30m" notifications: resend_api_key: "re_XZZenCJs_LyJnU12jZWfEn9rK85Gc83DK" registry: image: "gitea.dooplex.hu/admin/felhom-controller" # username + token injected via REGISTRY_USERNAME / REGISTRY_TOKEN env vars # from Secret/gitea-creds (see Deployment below) check_interval: "6h" template_interval: "1h" server: listen: ":8080" data_dir: "/data" # ============================================================================= # DEPLOYMENT # ============================================================================= --- apiVersion: apps/v1 kind: Deployment metadata: name: hub namespace: felhom-system labels: app: hub spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: hub template: metadata: labels: app: hub spec: containers: - name: hub image: gitea.dooplex.hu/admin/felhom-hub:v0.6.3 ports: - containerPort: 8080 name: http env: - name: TZ value: "Europe/Budapest" - name: REGISTRY_USERNAME valueFrom: secretKeyRef: name: gitea-creds key: username - name: REGISTRY_TOKEN valueFrom: secretKeyRef: name: gitea-creds key: password resources: requests: memory: "64Mi" cpu: "50m" limits: memory: "256Mi" cpu: "500m" volumeMounts: - name: data mountPath: /data - name: config mountPath: /etc/felhom-hub livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 30 timeoutSeconds: 5 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 10 timeoutSeconds: 3 volumes: - name: data persistentVolumeClaim: claimName: hub-data - name: config configMap: name: hub-config # ============================================================================= # SERVICE # ============================================================================= --- apiVersion: v1 kind: Service metadata: name: hub namespace: felhom-system labels: app: hub spec: selector: app: hub ports: - port: 8080 targetPort: 8080 name: http # ============================================================================= # INGRESS — hub.felhom.eu # ============================================================================= --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: hub namespace: felhom-system annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/proxy-body-size: "2m" # Geo-restrict to Hungary (operator-only dashboard) # NOTE: /api/v1/report must also be reachable — all customers are in HU nginx.ingress.kubernetes.io/configuration-snippet: | set $geo_allowed 0; if ($remote_addr ~ "^192\.168\.") { set $geo_allowed 1; } if ($remote_addr ~ "^10\.") { set $geo_allowed 1; } if ($geoip2_country_code = "HU") { set $geo_allowed 1; } if ($geo_allowed = 0) { return 403 "Access restricted to Hungary"; } spec: ingressClassName: nginx-internal tls: - hosts: - hub.felhom.eu secretName: hub-felhom-eu-tls rules: - host: hub.felhom.eu http: paths: - path: / pathType: Prefix backend: service: name: hub port: number: 8080