Architecture
Last updated
HakoRun's module layers and the components a request passes through.
System Overview
graph TB
Main[cmd/api entry] --> Checker[checker dependency check]
Main --> DB[database init]
Main --> Slice[sandbox.NewSlice]
Main --> Router[internal router]
Client[Client] --> Router
Router --> Handler[handler Upload / Run / RunNow]
Handler --> Store[(ToriiDB / Redis)]
Handler --> Sandbox[sandbox.SandboxCommand]
Sandbox --> Wrapper[internal/resource wrapper]
Wrapper --> Handler
Handler --> Client
Layers
| Layer | Path | Responsibility |
|---|---|---|
| Entry | cmd/api/main.go |
Runs the dependency check, storage init, slice setup, and HTTP server in order; handles signals with a 5-second shutdown |
| Router | internal/router.go |
Builds the Gin engine, registers /upload, /run/*targetPath, /run-now, and binds HTTP_PORT |
| Handler | internal/handler/ |
Validates requests, loads scripts, picks one-shot or SSE execution, tags the response type |
| Storage | internal/database/ |
backend interface; a build tag selects ToriiDB or Redis |
| Sandbox | internal/sandbox/ |
Builds systemd-run + bwrap or sandbox-exec commands per platform; writes hakorun.slice on Linux |
| Checker | internal/checker/ |
Detects and installs runtimes on Linux; a no-op on macOS |
| Wrapper | internal/resource/ |
Each language reads {code, input} from stdin and runs the user code |
Cross-cutting Principles
- Platform differences split by Go build tags (
_linux.go/_darwin.go), not runtime checks. - Storage backends split by build tags (
!redis/redis) and implement the samebackendinterface. - User code always reaches the wrapper through stdin and never touches the filesystem.
- Every run is a fresh child process; no warm processes or state carry over.