Replace NestJS framework with Hono + Bun, eliminating the last
NestJS service from the stack. All business logic preserved:
- CAS upload with SHA-256 dedup
- BullMQ image processing (Sharp thumbnails/variants)
- Matrix MXC URL import
- EXIF extraction
- File streaming/transforms
- Prometheus metrics
23 NestJS files → 12 Hono files. Zero NestJS in the monorepo.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Chat, Todo, Calendar, Contacts, Storage, ManaDeck, NutriPhi, Mukke
backends were removed in previous sessions but their web services
still had depends_on references, breaking docker compose.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add mana-user (3062), mana-subscriptions (3063), mana-analytics (3064)
to docker-compose with health checks and traefik labels
- Replace old NestJS Tier 3 app backends (~300 lines) with comment
placeholder for Hono compute servers (need shared Dockerfile)
- Create docker/Dockerfile.hono-server — shared Bun Dockerfile for
all 14 app compute servers (ARG APP for build context)
- Add 5 new databases to setup-databases.sh: mana_auth, mana_credits,
mana_user, mana_subscriptions, mana_analytics, mana_sync
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix runner image: code.forgejo.org/forgejo/runner:6.3.1
- Add command: forgejo-runner daemon
- Run as root for Docker socket access on macOS
- Runner registered and running on Mac Mini
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the entire NestJS-based mana-core-auth service (~36,000 lines
including tests, config, and package files). The new mana-auth service
(Hono + Bun, ~1,900 LOC) is the complete replacement on the same port.
Deleted:
- services/mana-core-auth/ — 169 files, 36,123 lines
(NestJS 10, Express, class-validator, all NestJS infrastructure)
Updated:
- docker-compose.macmini.yml: mana-auth now builds from services/mana-auth
with Bun healthcheck, simplified env vars (no Redis, no DuckDB needed)
- CLAUDE.md: mana-core-auth → mana-auth in services list
- Overview plan: marked Phase 4+5 as DONE, updated next steps
The ManaCore auth ecosystem is now:
- mana-auth (3001) — Auth, JWT, SSO, OIDC, Guilds, API Keys, GDPR
- mana-credits (3061) — Credits, Gifts, Guild Pools, Stripe
- mana-user (3062) — Settings, Tags, Storage
- mana-subscriptions (3063) — Plans, Billing, Invoices
- mana-analytics (3064) — Feedback, Voting
Total: ~6,600 LOC across 5 Hono+Bun services
Replaces: ~20,000 LOC in 1 NestJS service (67% reduction)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Forgejo v11 on port 3041 (git.mana.how via Cloudflare Tunnel)
- Forgejo Runner for CI/CD (GitHub Actions compatible)
- Built-in Docker registry and LFS support
- Registration disabled (admin-only)
- SSH access on port 2222
- Go Services CI workflow (.forgejo/workflows/go-services.yml)
- Setup script: scripts/mac-mini/setup-forgejo.sh
Replaces GitHub dependency for CI/CD. GitHub can remain as
mirror/backup while Forgejo becomes the primary Git host.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract feedback, analytics, and AI modules from mana-core-auth into
standalone mana-analytics service (Hono + Bun, Port 3064).
New service (services/mana-analytics/):
- User feedback CRUD with voting
- AI-powered feedback title generation via mana-llm
- Simplified from DuckDB analytics to pure PostgreSQL
- ~550 LOC
Removed from mana-core-auth:
- feedback/ module (6 files)
- analytics/ module (4 files)
- ai/ module (3 files)
- db/schema/feedback.schema.ts
mana-core-auth now contains ONLY pure auth:
- Better Auth (JWT, Sessions, 2FA, Passkeys, OIDC, Magic Links)
- Organizations/Guilds (membership management)
- API Keys, Security, Me (GDPR), Health, Metrics
- Ready for Phase 5: Hono rewrite
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Photos NestJS backend was a proxy to mana-media that enriched
responses with local album/favorite/tag data. Now:
- Albums store → local-first via albumCollection + albumItemCollection
- Favorites → local-first via favoriteCollection (toggle in IndexedDB)
- Photo tags → local-first via photoTagCollection
- Photo listing/stats → direct mana-media API calls from frontend
- Upload → direct mana-media upload from frontend
- Delete → direct mana-media delete from frontend
Removed 27 TypeScript files, 1 Docker container, 1 port (3039).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract subscription billing into standalone mana-subscriptions service
(Hono + Bun, Port 3063). Also removes Stripe module from mana-core-auth
since subscription webhooks are the last consumer.
New service (services/mana-subscriptions/):
- Plans CRUD, subscription management, Stripe Checkout + Portal
- Invoice tracking, webhook handlers for sub/invoice events
- Internal API for plan limits (consumed by guilds service)
- ~990 LOC (vs ~1,700 in NestJS incl. Stripe module)
Removed from mana-core-auth:
- subscriptions/ module (6 files)
- stripe/ module (4 files) — no longer needed in auth
- db/schema/subscriptions.schema.ts
- guilds.service.ts: replaced direct DB plan limit query with
HTTP call to mana-subscriptions internal API
mana-core-auth now contains only:
- Auth (Better Auth, JWT, Sessions, 2FA, Passkeys, OIDC)
- Organizations/Guilds (membership only, no credits/plans)
- API Keys, Security, Me (GDPR), Health, Metrics
- Feedback + Analytics (next extraction target)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Presi NestJS backend (40 source files, 50 deps) was a CRUD wrapper
around decks, slides, and themes — all now handled by local-first sync.
Only the share-link feature requires server-side state (public URLs
without auth), so a minimal Hono + Bun server replaces the entire
NestJS backend:
- apps/presi/apps/server/ — Hono server with share routes + GDPR admin
Uses @manacore/shared-hono for auth (JWKS), health, admin, errors
- Web app API client stripped to share-only (was 270 lines → 90 lines)
- Removed from docker-compose, CI/CD, Prometheus, env generation
- NestJS backend deleted (40 TS files, 8 test specs, 3038 lines)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clock app was migrated to local-first (mana-sync), remove stale
depends_on and environment references to clock-backend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- mana-sync on port 3051 (Go sync server for local-first apps)
- mana-notify-go on port 3040 (Go notification service)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port 3060 was already taken by api-gateway. Updated mana-credits to 3061.
Changes:
- docker-compose.macmini.yml: Add mana-credits service with health check,
traefik labels for credits.mana.how, depends on postgres
- docker-compose.macmini.yml: Add MANA_CREDITS_URL to mana-auth env
- Update all port references from 3060 to 3061 (config, Dockerfile, CLAUDE.md)
- Update better-auth.service.ts fallback URLs to 3061
- Update .env.development MANA_CREDITS_URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the NestJS mana-search service with a Go implementation for
lower resource usage and faster startup. All 7 API endpoints are 1:1
compatible (search, extract, bulk extract, engines, health, metrics,
cache clear). Uses go-readability for content extraction and
html-to-markdown for Markdown conversion. Redis cache with graceful
degradation, Prometheus metrics, and structured JSON logging.
Binary: 22 MB vs ~200+ MB node_modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 21 separate NestJS Matrix bot processes (~2.1 GB RAM, ~4.2 GB Docker images)
with a single Go binary using plugin architecture (8.6 MB binary, ~30 MB RAM).
New services:
- services/mana-matrix-bot/ — Go Matrix bot with 21 plugins (mautrix-go, Redis sessions)
- services/mana-api-gateway-go/ — Go API gateway (rate limiting, API keys, credit billing)
Deleted:
- 21 services/matrix-*-bot/ directories
- packages/bot-services/ and packages/matrix-bot-common/
- Legacy deploy scripts and CI build jobs
Updated:
- docker-compose.macmini.yml: new Go services, legacy bots removed
- CI/CD: change detection + build jobs for Go services
- Root package.json: new dev:matrix, build:matrix, test:matrix scripts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PasskeyManager.svelte: reusable component for listing, registering,
renaming, and deleting passkeys (German defaults, fully translatable)
- Production env: WEBAUTHN_RP_ID=mana.how and WEBAUTHN_ORIGINS for all
*.mana.how subdomains in docker-compose.macmini.yml
- Local DB: passkeys table created via direct SQL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create web Dockerfile and add both manadeck-backend (port 3009) and
manadeck-web (port 5023) to docker-compose.macmini.yml. Add Cloudflare
tunnel routes for manadeck.mana.how and manadeck-api.mana.how.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Distribute start_period values across service groups to prevent
thundering herd of simultaneous health checks. Backends: 40-60s,
web: 20-45s, bots: 15-35s, monitoring: 10-30s.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two infrastructure improvements for tech independence:
1. Cloudflare Fallback Documentation (docs/CLOUDFLARE_FALLBACK.md):
- Plan B: WireGuard + Caddy on Hetzner VPS (€3.79/mo)
- Complete Caddyfile with all 30+ subdomains
- Step-by-step failover checklist (~15 min to switch)
- Plan C: Direct IP with ISP
2. Self-Hosted Landing Pages (eliminates Cloudflare Pages dependency):
- Nginx container (mana-infra-landings) on port 4400
- Multi-site config: each subdomain → separate dist/ folder
- Build script: scripts/mac-mini/build-landings.sh
- Cloudflare Tunnel ingress rules for 10 landing page domains
- Storage: /Volumes/ManaData/landings/ on external SSD
- Domains: it, chats, pics, zitares, presis, clocks,
manadeck, nutriphi, citycorners, docs
Migration path: Build landings locally, set Cloudflare DNS to
tunnel instead of Pages, then decommission CF Pages projects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The config_file override replaced the entire default PostgreSQL config
including listen_addresses, breaking inter-container communication.
Use inline -c flags instead which only override specific parameters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add LocalImageGenService that routes to the self-hosted FLUX.2 klein
model on the Mac Mini, eliminating Replicate API dependency for basic
image generation.
Changes:
- LocalImageGenService: wraps mana-image-gen HTTP API (/generate)
with health checking, timeout handling, and GenerationResult compat
- GenerateService: routes to local or Replicate based on model config
(replicateId starting with "local/" → LocalImageGenService)
- Local models always use sync mode (no webhooks needed, ~0.8s)
- Seed: add "FLUX.2 Klein (Lokal)" model with sortOrder -1 (shown first)
- costPerGeneration: 0 (free, runs locally)
- estimatedTimeSeconds: 1
- docker-compose: add IMAGE_GEN_SERVICE_URL env var for picture backend
Replicate remains available for premium models (Seedream, Nano Banana).
Local FLUX.2 klein becomes the default free option.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All backend containers were sending error reports to glitchtip.mana.how
(via Cloudflare tunnel), creating ~6000 unnecessary external TCP connections
contributing to port exhaustion. Now routes directly to glitchtip:8020
within the Docker network.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mac Mini had 25k+ TIME_WAIT sockets exhausting the 16k ephemeral port range,
blocking all outgoing TCP connections. Root cause: ~50 health checks at 30s
intervals + n8n automation creating excessive short-lived connections.
- Remove n8n service and volume (no longer needed)
- Increase health check intervals: 30s → 120s (app services), 10s → 30s (infra)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Google Gemini as a fallback provider that activates automatically
when Ollama is overloaded or unavailable, ensuring LLM requests always
succeed even under load.
New provider (src/providers/google.py):
- Full LLMProvider implementation using google-genai SDK
- Chat completions (streaming + non-streaming)
- Vision/multimodal support (base64 images)
- Embeddings via text-embedding-004
- Model mapping: Ollama models → Gemini equivalents
(gemma3:4b → gemini-2.0-flash, llava:7b → gemini-2.0-flash, etc.)
Auto-fallback routing (src/providers/router.py):
- Concurrent request tracking for Ollama (OLLAMA_MAX_CONCURRENT=3)
- When Ollama concurrent > max: route to Google automatically
- When Ollama fails: retry on Google with model mapping
- Health check caching (5s TTL) to avoid hammering Ollama
- Non-Ollama providers (openrouter, groq, together) are never fallback-routed
- Fallback info included in /health endpoint response
New config (src/config.py):
- GOOGLE_API_KEY: enables Google provider
- GOOGLE_DEFAULT_MODEL: default gemini-2.0-flash
- AUTO_FALLBACK_ENABLED: toggle fallback (default: true)
- OLLAMA_MAX_CONCURRENT: concurrent request threshold (default: 3)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New service that generates static Astro landing pages for organizations
and deploys them to Cloudflare Pages at {slug}.mana.how.
Components:
- Landing Builder Service (NestJS, port 3030) with Astro template
- Admin UI in Manacore web dashboard at /organizations/[id]/landing
- TeamSection + ContactSection for shared-landing-ui
- Two org themes (classic dark, warm light)
- LandingPageConfig types in shared-types
- Docker + CI/CD integration for Mac Mini deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: GET /locations/lookup?q= endpoint that searches via mana-search, extracts content from top results, auto-detects address and category, returns pre-filled data with source links.
Frontend: /add page now has a two-step flow:
1. Search step: user enters a place name, backend scrapes the web
2. Edit step: form pre-filled with found data (name, description, address, category), user can review/edit before submitting. Shows source links.
Also fixed all API paths to use /api/v1/ prefix via centralized api() helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The LLM playground is a SvelteKit web app (frontend), not a backend
microservice. Moving it to apps/ follows the monorepo convention where
all user-facing apps live under apps/.
- Moved services/llm-playground/ → apps/playground/apps/web/
- Renamed package from @mana-llm/playground to @playground/web
- Updated Dockerfile paths for new location
- Updated docker-compose.macmini.yml build context
- Removed unused concurrently dependency
- Added parent package.json
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mana-media uses NestJS 11 while shared-nestjs-metrics targets NestJS 10,
causing DynamicModule type incompatibility. Use prom-client directly with
a simple MetricsController to expose /metrics endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New project with three apps:
- Landing (Astro): static site with SVG illustrations, location data
- Backend (NestJS, port 3025): CRUD API for locations + favorites, Drizzle ORM, auth via mana-core-auth
- Web (SvelteKit, port 5196): Tailwind 4, PillNav, auth (login/register/SSO), Leaflet map, favorites with optimistic updates, theme/settings
Infrastructure: DB init SQL, setup-databases.sh, generate-env.mjs, root package.json scripts, Dockerfiles, docker-compose.macmini.yml (backend:3025, web:5022), Cloudflare wrangler.toml.
Branding: registered in shared-branding (AppId, APP_BRANDING, APP_ICONS, MANA_APPS, CitycornersLogo).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set crossOrigin='anonymous' on audio element for Web Audio API
compatibility with cross-origin sources. Add MINIO_API_CORS_ALLOW_ORIGIN
to allow browser requests from all web apps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add build context to storage-web in docker-compose (was pulling from
GHCR, now builds locally like other services)
- Add storage-backend and storage-web to CD change detection and deploy
- Fix mukke health check URLs (were using wrong ports 3035/5015)
- Remove hardcoded port from Dockerfile (use PORT env var from compose)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The GlitchTip Docker image doesn't include wget or curl, causing 692+
consecutive health check failures. Switch to python3 urllib which is
available in the image.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The NestJS app uses a global /api/v1 prefix, but the health check was
hitting /health causing 5600+ consecutive failures and unhealthy status.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exclude /health from global prefix so healthchecks hit /health instead of
/api/v1/health. Update Dockerfile EXPOSE and healthcheck to use port 3040
matching docker-compose config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dockerfile, docker-compose service (port 5100), Caddy and cloudflared
routing for the WhoPixels game. PORT is now configurable via env var.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>