- Go 1.26.0
- Node.js 22+ and npm (for frontend assets)
- PostgreSQL 17+ (via Docker or native install)
- MinIO (S3-compatible storage, via Docker)
- Veloria binary built from
./cmd/veloria(single binary with subcommands)
docker compose up -dThis starts PostgreSQL, MinIO, and Mailpit. See docker-compose.yml for port mappings.
Copy the example env file and adjust values:
cp .env.example .envKey values for local development:
ENV=development
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=veloria
DB_USERNAME=postgres
DB_PASSWORD=postgres
S3_ENDPOINT=localhost:9000
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadminIn development mode (ENV=development), the .env file is loaded automatically and DB_SSLMODE defaults to disable.
go run ./cmd/veloria migrate upThe migrate command loads your existing environment config (.env in development), builds the DB connection string, and runs migrations without manual DSN input.
# Build frontend assets (required after CSS/template/dependency changes)
go generate ./assets/...
# Build all binaries
go build ./...
# Run the server
go run ./cmd/veloria
# Run with debug logging
APP_DEBUG=true go run ./cmd/veloriaThe server starts on port 9071 by default (configurable via PORT env var).
cmd/
└── veloria/ # Single binary with subcommands (serve, index, migrate, version)
internal/ # All application packages (see architecture.md)
migrations/ # SQL migration files (embedded in binary)
docs/ # Developer documentation
The server invokes itself as a subprocess (veloria index) for indexing, so there is no separate indexer binary to build or put on PATH.
Create a new migration file following the naming convention:
go run ./cmd/veloria migrate create <description> sqlThis creates a file like migrations/20260222000001_<description>.sql with -- +goose Up and -- +goose Down sections.
- Create a handler function in the appropriate package (
internal/plugin/,internal/theme/,internal/core/,internal/search/) - The handler should accept its dependencies as parameters (DB, stats provider, etc.) and return
http.Handler - Use
api.WriteJSON()andapi.WriteSuccessJSON()for consistent JSON responses - Use
api.APIErrorhelpers (api.ErrBadRequest,api.ErrNotFound, etc.) for error responses - Register the route in
internal/router/router.go
- Create a template in
templates/ - Add a handler in the appropriate package that takes
*web.Deps - Use
d.PageData(r)to get common template data (user, search status, etc.) - Register the route in
internal/router/router.go
The stores follow a consistent pattern. Each concrete store (PluginStore, ThemeStore, CoreStore) embeds ExtensionStore[T] and implements DataSource:
// Creating a store (done in app.go)
pr := repo.NewPluginStore(ctx, db, cfg, logger, cache, apiClient)
// The store satisfies DataSource
var ds repo.DataSource = prKey methods on DataSource:
Load()— load from DB + disk indexes (called during startup)PrepareUpdates()— discover new/changed extensions, return index tasksSearch(term, opts, progressFn)— search across all indexed extensionsGetExtension(slug)— look up a single extension by slugMakeReindexTaskBySlug(slug)— create an ad-hoc reindex task
If a handler needs access to a new capability from the Manager:
- Define a narrow interface in
internal/web/interfaces.go(1-2 methods) - Add the interface as a field on
web.Deps - Implement the interface on
*Manager - Wire it in
app.New()when constructingweb.NewDeps()
- Error handling: Use
fmt.Errorf("context: %w", err)for wrapping. Return errors to callers; don't log and return. - Logging: Use the
*zerolog.Loggerpassed via constructor. Debug-level for routine operations, Info for lifecycle events, Error for failures. - Concurrency: Use explicit variable capture in goroutine closures. Use
sync.RWMutexfor read-heavy shared state. - Interfaces: Define at the consumer site. Keep them narrow (1-3 methods). Use hand-written fakes for testing.
- Configuration: All config comes from environment variables via struct tags. No config files.
# Run all unit tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run integration tests (requires Docker services running)
go test -tags integration ./...
# Run tests for a specific package
go test ./internal/manager/
# Check for issues
go vet ./...
# Build all binaries
go build ./...