Add .gitea/workflows/build.yaml that:
- Builds Docker image on push to master
- Pushes to ghcr.io/unchainedio/manoon-headless
- Tags with commit SHA and 'latest'
- Updates k8s/kustomization.yaml with new image tag
- Commits and pushes the tag update back to repo
Requires Gitea Actions runner to be configured.
- Remove init containers (clone, install, build)
- Use ghcr.io/unchainedio/manoon-headless:latest image
- Faster pod startup, less resource usage
- Image built by GitHub Actions on push to master
Apply same fix from master branch:
- Check if workspace exists before cloning
- Fetch and reset if .git directory exists
- Clean and clone fresh if not
Prevents CrashLoopBackOff on pod restarts.
The clone init container was failing with 'destination path already exists'
when the pod restarted. EmptyDir volumes persist across container restarts
but init containers run again.
Now checks if workspace exists:
- If .git directory exists: fetch and reset to latest master
- If not: clean and clone fresh
This fixes the CrashLoopBackOff caused by failed clone attempts.
- Email capture popup with scroll (10%) and exit intent triggers
- First name field and full tracking (UTM, device, time on page)
- Mautic API integration for contact creation
- GeoIP detection for country/region
- 4 locale support (sr, en, de, fr)
- Mautic tracking script in layout
Changed Rybbit script loading from server-side rewrite to client-side direct
connection. This prevents Next.js from proxying the request, which was causing
all visitor IPs to show as the Hetzner server IP (138.201.11.251).
Before:
- Browser → Next.js → Rybbit (server-side proxy, loses client IP)
After:
- Browser → Rybbit (direct connection, real IP preserved)
Changes:
- layout.tsx: Use direct Rybbit URL for script src
- next.config.ts: Remove /api/script.js rewrite
Change storefront service from ClusterIP to NodePort with externalTrafficPolicy: Local.
This preserves the real client source IP instead of NATing to the node IP.
Fixes analytics tracking showing Hetzner IP (138.201.11.251) instead of real visitor IPs.
Same fix previously applied to Rybbit backend service.
Note: On single-node clusters, this works seamlessly. Traefik routes directly
to the node where the pod is running, preserving the original source IP.
- Add permanent redirects for /products/manoon to /products
- Strip malformed /contact suffix from product URLs
- Create custom branded 404 page with product navigation
- Add NotFound translations for en and sr locales
- Create new API route /api/rybbit/track to proxy Rybbit tracking requests
- Extract real client IP from Cloudflare headers (cf-connecting-ip)
- Forward X-Forwarded-For and X-Real-IP headers to analytics backends
- Update OpenPanel proxy to also forward client IP
- Update next.config.ts rewrite to use internal API route
This fixes geo-location issues where all traffic appeared to come from
Cloudflare edge locations instead of actual visitor countries.
- Create new API route /api/rybbit/track to proxy Rybbit tracking requests
- Extract real client IP from Cloudflare headers (cf-connecting-ip)
- Forward X-Forwarded-For and X-Real-IP headers to analytics backends
- Update OpenPanel proxy to also forward client IP
- Update next.config.ts rewrite to use internal API route
This fixes geo-location issues where all traffic appeared to come from
Cloudflare edge locations instead of actual visitor countries.
- Add revalidate=3600 to homepage and products page (1hr ISR)
- Add middleware to set cache headers for HTML pages
- Bypass cache for checkout and cart pages
- Add type-safe AnalyticsEvent union types
- Create AnalyticsProvider interface for pluggable analytics backends
- Implement OpenPanelProvider and RybbitProvider adapters
- Create AnalyticsTracker that fans out events to all providers
- Simplifies adding new analytics platforms in the future