Critical security fix: 5 financial POST endpoints were missing
authentication checks. Added requireAuth() middleware to:
- /api/flh/hibah/send
- /api/flh/hibah/accept
- /api/flh/sadaqah/send
- /api/flh/waqf/contribute
- /api/flh/zakat/pay
These involve FLH token transfers and must verify user identity.
- New branch for mobile2.falah-os.com with Casdoor OIDC
- Auth page with 'Sign in with Casdoor' button
- OIDC callback: exchanges code via server-side API route
- env vars configured on Netlify
- DNS auto-configured via Netlify (falah-os.com zone)
The RUN printf approach had a POSIX printf bug where \\c stopped
output, truncating the startup script. Now start.sh is a proper
file COPYed into the image.
- Add audioPath for 3 legacy courses (Basics of Prayer, Understanding Fiqh, Quranic Arabic)
- Convert new course TTS from M4A to MP3 for consistency
- Fix generate-tts-local.js ext replacement bug (works with .mp3 now)
- All 7 courses now have 32 MP3 modules total (~74MB)
- Build passes (0 errors, 0 warnings)
- 15 new modules with 2000-3000 char markdown content
- 1 quiz question per module with explanation
- audioPath set to .m4a for all 15 modules (TTS-ready)
- Added local TTS generation script (scripts/generate-tts-local.js)
- Added .gitignore for generated audio files
- Total: 7 courses, 32 modules in seed-learn.json
Note: Audio files are generated locally via generate-tts-local.js.
They are excluded from git (see .gitignore) due to large binary size.
For production MP3s, use Azure/ElevenLabs via Gitea's generate-audio.py.
- Port full markdown lesson content from Gitea (courses/daily-fiqh-beginner)
into seed-learn.json module content fields
- Fix quizData format: wrap questions in {questions: [...]} object to match
LessonReaderPage parseQuizData() expectations
- Add quiz explanations from Gitea quiz.json
- Generate TTS audio for all 5 modules using Edge TTS (en-US-AriaNeural)
- Store audio files in public/audio/learn/daily-fiqh-beginner/
- Add audioPath references pointing to /audio/learn/... MP3s
Closes next steps from HERMES_INSTRUCTIONS.md content + audio gap.
- FIXED core bug: log() used tee -a, writing to both log file AND stdout,
corrupting DIAGNOSIS=$(detect_fault) and causing infinite fault loops
- ADDED 120s cooldown after restore to prevent detection loops during startup
- ADDED swarm service detection to auto-healer (handles both standalone
containers and Docker Swarm services)
- FIXED swarm service detection: awk now uses $2 (service name) not $1 (ID)
- FIXED exact match for service names (grep -x)
- FIXED restore protocol to use docker service update --force for swarm services
- DEPLOYED auto-healers to both Synology (v15) and Contabo (v15)
- DNS healer also deployed to Contabo (v3)
- DNS healer has swarm health monitoring (checks every 2 cycles)
- All 3 nodes in Docker Swarm: Synology (manager), colima (worker), Contabo (leader)
- Hermes is connected via herdr --remote (client_id=4)
- Signal file schema for inter-agent communication
- Shared workspace etiquette and task board
- Emergency contact channels
- Rejoined Synology to Contabo-led swarm as manager (reachable)
- Swarm now has 2 managers: Synology (DS3617xs) + Contabo (vmi3361598)
- Saved join token to /var/state/falah/swarm-token for auto-rejoin
- DNS healer v3: checks swarm membership every 2 cycles (~2 min)
- If swarm inactive, auto-rejoins using saved token
- Reports node status (Contabo present, Mac Mini offline)
- Mac Mini (colima) was permanently removed from swarm while offline
(needs manual re-join when back online with same token)
Current swarm:
DS3617xs (Synology) → Manager, Reachable, Engine 20.10.3
vmi3361598 (Contabo) → Manager, Leader, Engine 29.5.3
colima (Mac Mini) → GONE (offline >20d, need re-join)
- App healer (v7): checks DNS healer every 30s, restarts if down
- DNS healer (v2): checks app healer every 60s, restarts if down
- scripts/watchdog.sh: ultimate fallback cron job (every 5 min)
revives both healers if both are down simultaneously
- Installed to /usr/local/bin/falah-watchdog + /etc/cron.d/falah-watchdog
Three-layer defense:
Layer 1: Docker --restart unless-stopped (instant)
Layer 2: Cross-heal between the two healers (30-60s)
Layer 3: Cron watchdog (5 min, last resort)
- rollback was using --network bridge explicitly, which fails on this Synology's
Docker daemon when the bridge network is associated with a Swarm overlay
- Fixed: omit --network flag entirely, let Docker use default bridge behavior
- Also found and fixed: gateway container on Swarm overlay network couldn't
route to 192.168.0.11:4014 — attached to bridge network to resolve
- Fixed blocking command in incident reporting (added timeout 5s + --until now)
Was causing the entire agent script to hang forever after saving each incident
- Full SRE flow now works end-to-end:
1. Detect fault → 2. Fetch logs → 3. Analyze against knowledge base
4. Save incident with full context (logs, inspect, events, resources)
5. Execute restore protocol (rollback to :rollback image)
6. Verify service restored
- 3 incidents successfully captured and logged with diagnostics
- All containers healthy, auto-healer monitoring every 30s
- Removed set -e — agent no longer crashes when a remediation step fails
- SIGKILL/OOM → restart first, only rollback if exceeding max restarts/hour
- Added HEALTHY_CYCLES counter: after 10 consecutive healthy cycles (5 min),
reset restart tracking state (system considered stable)
- Rollback now handles the case where Docker restart policy already revived
the container (rm -f before re-creating)
- Webhook polar/route: block direct ?userId=&tier= upgrades in production (NODE_ENV guard)
- Seed route: require ADMIN_SECRET via x-admin-secret header
- Feedback route: require FEEDBACK_ADMIN_TOKEN env var in production
- CORS middleware: add Vary: Origin header for caching correctness
- MOCK_PAYMENTS: add NODE_ENV production guard
- Wallet top-up: add production guard for mock mode when POLAR_ACCESS_TOKEN unset
fetch('/api/marketplace/listings?...') was hitting the wrong URL
(404) because the app is deployed under /mobile basePath. Changed
to fetch('/mobile/api/marketplace/listings?...') which matches the
correct API route.
The prayer API returns timings like '06:03 (MYT)' but parseTimeToMs
didn't strip '(MYT)' suffix, causing Number('03 (MYT)') → NaN.
Now regex-strips timezone annotations before parsing.
Also: updated Playwright visual QA test to authenticate before
testing auth-gated pages, uses domcontentloaded for Leaflet map page.
Grade A — 84 pass, 0 fail, 1 warn on production browser QA.
- Replace hardcoded KL mock data with live OpenStreetMap Overpass API
- Support 15+ major cities worldwide (London, Dubai, Tokyo, NY, etc.)
- Bounding-box queries (faster than radius search)
- HTTP/1.1 fix for Overpass Apache compat
- Nominatim geocoding for city search
- 6-hour file-based cache per grid cell
- 3-mirror Overpass fallback with retry on timeout
- User-contributed places with yellow (⭐) markers
- 'Add Place' modal with name/type/address/notes form
- Map tap-to-pin coordinates for new places
- Delete own submissions from detail modal
- Prisma UserPlace model + API routes
Closes: Halal Monitor showing only KL data