Hesperida Architecture
This page reflects the current architecture and is based on the root ARCHITECTURE.md in the repository.
Purpose
Hesperida is a Docker-first, self-hosted web scanning platform with:
- SurrealDB as source of truth
- Bun orchestrator for scanner execution
- SvelteKit web service for API + dashboard
- PDF and notification delivery integrations
Runtime Topology
Main services:
db(SurrealDB)orchestratorweb(/api/v1/*+ dashboard)apprise(notification target delivery)pdf(Gotenberg)- on-demand tool containers:
probe,seo,ssl,wcag,whois,domain,security,stress,mail
Compose profile intent:
aio: full stackbackend: web + orchestrator + integration servicesdatabase: db onlydev: dev stack fromdocker-compose.dev.yamltools: direct tool image runs
Database Bootstrap
Schema/bootstrap are handled by the web app startup init:
- imports
web/src/lib/server/schema.surql - tracks schema application via
$schemaVersion - ensures bootstrap superuser exists
No standalone db-init service is used now.
Job Lifecycle and Queue Flow
High-level flow:
- Create
jobsrow (typesselected). - Orchestrator picks pending job.
proberuns first.- Follow-up tasks fan out in
job_queue. - Tool containers run and write to
*_results. - DB events link outputs and advance status.
- Job is marked complete when all required outputs exist.
Queue statuses:
pending,waiting,processing,completed,failed,canceled
Queue/container maintenance (orchestrator):
- daily queue retention cleanup removes
job_queuerows older thanJOB_QUEUE_RETENTIONdays (default365) - startup orphan cleanup removes managed tool containers labeled
com.hesperida.managed=true - startup tool image preparation:
NODE_ENV=development: build local tool images from/tools/*- non-development: pull GHCR tool images using the resolved orchestrator image tag token, then retag for local runtime names
Web Service Modes
APP_MODE controls routing:
bothapidashboard
API protections:
x-api-keyrequired on protected/api/v1/*routes- user auth by cookie/bearer token
- OpenAPI generated to
web/static/openapi.json
Dashboard:
- server-side API calls through internal helper
- SSE streams for queue and notifications
- CRUD surfaces for users/websites/jobs/job-queue
ACL and Multi-Tenancy
User fields:
role:admin | editor | viewergroupis_superuser
Access summary:
is_superuser: globaladmin: group-scoped administrationeditor: own/member website scopeviewer: member-only scope
Safeguards:
- viewers cannot own websites
- cannot downgrade owner users to viewer
- superuser role constrained to admin
Website Verification Model
Verification records are shared by (group, registrable_domain) in website_verifications:
verification_codeverified_atverification_method(dns|file)
Check order:
- DNS TXT:
hesperida.<registrable-domain> - HTTP fallback:
hesperida-<code>.txtwith status200
Job creation is gated on verification.
Notifications (Split Architecture)
System emails:
- Nodemailer + SMTP (
SMTP_*) - used for forgot/invite/transfer/onboarding
- missing SMTP ->
503 smtp_not_configured - send failure ->
502 notification_failed+ rollback semantics - this is transactional app mail and separate from the
mailscan tool output (mail_results)
User notifications:
- Apprise-backed
- stored in
notification_channelsandwebsite_notifications - configured in dashboard/API and delivered by orchestrator on job transitions/scores
Reporting / PDF
- SSR report route:
/jobs/{id}/pdf(completed jobs only) - dashboard action calls Gotenberg URL conversion
- PDF is streamed back as download
Data Model Overview
Core:
users,websites,website_verifications,jobs,job_queue
Results:
probe_results,seo_results,ssl_results,wcag_results,whois_results,domain_results,security_results,stress_results,mail_results
Release and Docs Pipeline
- Version source of truth:
web/package.json - Auto-tag workflow creates
v<version>tags onmain - GHCR workflow builds/pushes multi-arch images
- Docs workflow publishes Docusaurus to GitHub Pages