Home Portal¶
Unified dashboard for managing and accessing all homelab services.
Quick Links¶
Production¶
- External URL: https://portal.bogocat.com (via VPS Caddy)
- Internal URL: http://home.internal (via NGINX Ingress)
- Namespace:
home-portal - Ingress: nginx class, hostname
home.internal
Development¶
- Dev Server: http://localhost:3000 (run from host)
- Host Path:
/root/projects/home-portal - tmux Session:
tmux attach -t home-portal
Overview¶
Home Portal is the central hub for accessing and managing all homelab infrastructure and services. It provides a unified interface for:
- Service status monitoring
- Quick access to all dashboards
- Infrastructure management
- Blog/notes system
- Widget-based customizable dashboard
Tech Stack¶
- Framework: Next.js 16 (App Router)
- UI Library: React 19
- Styling: Tailwind CSS v4
- Database: Supabase (PostgreSQL)
- Authentication: Supabase Auth (shared user pool)
- Deployment: Kubernetes (k3s)
Database¶
Schema¶
Tables are organized in the home_portal PostgreSQL schema:
-- Example schema structure
home_portal.pages
home_portal.widgets
home_portal.services
home_portal.blog_posts
home_portal.user_settings
Supabase Connection¶
Production (Kubernetes):
Development (LXC):
See Multi-App Supabase Architecture for details on schema isolation.
Development¶
Access Project¶
Running Dev Server¶
Local Supabase¶
# Start Supabase (first time takes 2-3 minutes)
npx supabase start
# Access local Studio
open http://localhost:54323
Database Migrations¶
# Create new migration
npx supabase migration new migration_name
# Ensure schema is set in migration
echo "SET search_path TO home_portal;" > supabase/migrations/xxx_migration_name.sql
# Apply migrations locally
npx supabase db reset
# Apply to production (k8s Supabase)
/root/tower-fleet/scripts/migrate-app.sh home-portal
Supabase Storage¶
Buckets:
- home-portal-service-icons - Custom service icons (public bucket)
- home-portal-travel-photos - Travel blog photos (public bucket)
- home-portal-gpx-tracks - GPS tracks (public bucket)
- home-portal-avatars - User avatars (public bucket)
Documentation: See Supabase Storage Guide
Deployment¶
Kubernetes Manifests¶
Located at: /root/home-portal-k8s/k8s/
Deploy to Production¶
cd /root/home-portal-k8s
# Apply all manifests
kubectl apply -f k8s/
# Check deployment status
kubectl get pods -n home-portal
kubectl get svc -n home-portal
# View logs
kubectl logs -n home-portal -l app=home-portal -f
Update Deployment¶
# After pushing new image or updating config
kubectl rollout restart deployment/home-portal -n home-portal
# Watch rollout
kubectl rollout status deployment/home-portal -n home-portal
Architecture¶
Production (Kubernetes)¶
Client Request (http://home.internal)
↓
DNS Resolution (10.89.97.220)
↓
NGINX Ingress Controller (10.89.97.220)
↓ (routes based on Host: home.internal)
home-portal Service (ClusterIP)
↓
home-portal Pod(s)
↓
Supabase API Gateway (Kong: 10.89.97.214:8000)
↓
PostgreSQL (home_portal schema)
Development (Host-Based)¶
Developer
↓
Proxmox Host (localhost:3000)
↓
Next.js Dev Server
↓
K8s Supabase Sandbox (10.89.97.221:8000)
↓
PostgreSQL (home_portal schema)
Features¶
Core Features¶
- Service Dashboard: Monitor and access all homelab services
- Status Monitoring: Real-time health checks for critical services
- Quick Links: Fast access to frequently used dashboards
- Widget System: Customizable dashboard with draggable widgets
- Blog/Notes: Personal knowledge base and documentation
Upcoming Features¶
- Custom Service Icons: Upload custom icons for services via Supabase Storage (see Implementation Plan)
- Integration with Proxmox API for VM/container management
- Alert notifications for service failures
- Resource usage graphs and monitoring
- Mobile-responsive design improvements
API Endpoints¶
All API endpoints are available through Supabase PostgREST:
Base URL (Production)¶
Available Endpoints¶
# Get all pages
GET /rest/v1/pages?select=*
# Get user widgets
GET /rest/v1/widgets?select=*&user_id=eq.{uuid}
# Get monitored services
GET /rest/v1/services?select=*&active=eq.true
# Get blog posts
GET /rest/v1/blog_posts?select=*&published=eq.true
All requests require authentication via Authorization: Bearer <token> header.
Configuration¶
Environment Variables¶
Required:
- NEXT_PUBLIC_SUPABASE_URL - Supabase API URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY - Supabase anonymous key
- SUPABASE_SERVICE_ROLE_KEY - Supabase service role key (server-side only)
Optional:
- JELLYFIN_URL - Jellyfin server URL for integration
- PLEX_URL - Plex server URL for integration
- PROXMOX_URL - Proxmox API URL for VM management
- SERVICE_POLL_INTERVAL - Health check interval in seconds (default: 30)
Troubleshooting¶
Common Issues¶
Cannot connect to Supabase:
# Check Supabase services are running
kubectl get pods -n supabase
# Test API connectivity
curl http://10.89.97.214:8000/rest/v1/
Database migrations not applying:
# Verify schema exists
kubectl exec -n supabase postgres-0 -- psql -U postgres -c "\dn"
# Check if tables exist in home_portal schema
kubectl exec -n supabase postgres-0 -- psql -U postgres -c "\dt home_portal.*"
Dev server won't start:
# Check if port 3000 is in use
lsof -i :3000
# Check tmux session
tmux ls
tmux attach -t home-portal
# View logs
journalctl -u home-portal -f # If running as systemd service
Icons Not Loading in Production¶
Symptoms: Custom service icons (uploaded via Supabase Storage) don't load when accessing via portal.bogocat.com, but CDN icons (Jellyfin, Plex, etc.) work fine.
Cause: Browser clients can't access internal Supabase URL (http://10.89.97.214:8000).
Solution: Configure public storage endpoint in .env.production:
Infrastructure:
- VPS Caddy: storage.bogocat.com → K8s Ingress
- K8s Ingress: storage.bogocat.com → Kong/Supabase Storage
Rebuild required after changing .env.production:
Documentation: See Supabase Storage - Production External Access
Duplicate Dashboard Routes (/ and /slug)¶
Symptoms: Same dashboard appears at both / and /default (or similar slug).
Cause: Database has a layout with is_default = true AND a slug that matches a URL path (e.g., slug = "default").
How routing works:
- / - Shows layout where is_default = true
- /[layoutSlug] - Shows layout matching the slug
If the default layout has slug = "default", both routes show the same content.
Solution: Rename the default layout's slug to something non-conflicting:
# Check current layouts
kubectl exec -n supabase postgres-0 -- psql -U postgres -d postgres -c \
"SELECT id, name, slug, is_default FROM home_portal.layouts ORDER BY is_default DESC;"
# Rename default layout slug to "home"
kubectl exec -n supabase postgres-0 -- psql -U postgres -d postgres -c \
"UPDATE home_portal.layouts SET slug = 'home' WHERE is_default = true;"
# Ensure only ONE layout is default
kubectl exec -n supabase postgres-0 -- psql -U postgres -d postgres -c \
"UPDATE home_portal.layouts SET is_default = false WHERE id != '<your-default-layout-id>';"
Best Practice: Use unique, descriptive slugs that don't conflict with common words:
- Good: home, work, media-center
- Avoid: default, main, dashboard
Support¶
- Documentation: This page and Infrastructure Docs
- Source Code:
/root/projects/home-portalon Proxmox host - Database Studio: http://10.89.97.215:3000
- Kubernetes:
kubectl get all -n home-portal