Contributing
Thank you for your interest in contributing to Jagad! This document provides guidelines for setting up your development environment, building from source, running tests, and submitting changes.
Quick Start
# Clone the repository
git clone https://github.com/edsuwarna/jagad.git
cd jagad
# Install dependencies
make deps
# Run the application in development mode
make run-quickThe application will start at http://localhost:8080 with SQLite storage at ./data/jagad.db.
Development Environment Setup
Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Go | 1.25+ | Backend runtime |
| Git | 2.x | Version control |
| Docker + Compose | Latest | Containerized development and testing |
| PostgreSQL client | 15+ | Testing PostgreSQL backups |
| MySQL client | 8.0+ | Testing MySQL backups |
| MariaDB client | 10.2+ | Testing MariaDB backups |
Optional but recommended:
| Tool | Purpose |
|---|---|
pgbackrest | Testing incremental backup engine |
xtrabackup | Testing MySQL incremental backups |
mariabackup | Testing MariaDB incremental backups |
golangci-lint | Code quality and style checking |
pre-commit | Automated pre-commit hooks |
Go Version Management
Jagad uses Go 1.25+. You can check your Go version:
go versionIf you need a different version, use go install or a version manager like g or goenv.
Docker Development
For a full environment with database servers for testing:
# Start PostgreSQL, MySQL, and MinIO for development
docker compose -f docker-compose.dev.yml up -d
# This gives you:
# - PostgreSQL on :5432
# - MySQL on :3306
# - MinIO (S3-compatible) on :9000
# - Jagad on :8080Project Structure
jagad/
├── cmd/jagad/ # Main entrypoint
│ └── main.go # Application bootstrap, dependency injection
├── internal/
│ ├── api/ # HTTP router, middleware, response helpers
│ │ ├── router.go # Route registration
│ │ └── response.go # JSON response helpers
│ ├── auth/ # Authentication and session management
│ │ └── service.go # Login, logout, session validation, middleware
│ ├── backup/ # Backup execution engine
│ │ ├── service.go # Full backup streaming pipeline
│ │ ├── handler.go # HTTP handlers for backup operations
│ │ ├── model.go # Backup domain model
│ │ ├── incremental.go # IncrementalEngine interface + registry
│ │ ├── pgbackrest.go # PostgreSQL incremental via pgBackRest
│ │ ├── xtrabackup.go # MySQL incremental via XtraBackup
│ │ └── mariabackup.go # MariaDB incremental via Mariabackup
│ ├── config/ # Environment variable configuration
│ │ └── config.go # Config struct + Load() from env
│ ├── connection/ # Database connection management
│ │ ├── service.go # CRUD + test connection + auto-discover
│ │ ├── handler.go # HTTP handlers
│ │ └── model.go # Connection and Database domain models
│ ├── encryption/ # AES-256-GCM encryption service
│ │ └── service.go # EncryptStream, DecryptStream, key derivation
│ ├── httputil/ # Shared HTTP utilities
│ │ └── response.go # JSON encoding, error responses
│ ├── notification/ # Multi-channel notification service
│ │ ├── service.go # Telegram, Discord, Slack senders
│ │ ├── handler.go # HTTP handlers
│ │ └── model.go # Notification target domain model
│ ├── repository/ # SQLite data access layer
│ │ ├── db.go # Database connection and schema migration
│ │ ├── backup.go # Backup CRUD
│ │ ├── connection.go # Connection CRUD
│ │ ├── schedule.go # Schedule CRUD
│ │ ├── storage_provider.go # Storage provider CRUD
│ │ ├── restore.go # Restore CRUD
│ │ └── notification.go # Notification target CRUD
│ ├── restore/ # Restore engine
│ │ ├── service.go # Download → decrypt → decompress → restore
│ │ └── model.go # Restore domain model
│ ├── schedule/ # Cron scheduler
│ │ ├── scheduler.go # robfig/cron wrapper, job management
│ │ ├── service.go # Schedule CRUD
│ │ ├── handler.go # HTTP handlers
│ │ └── model.go # Schedule domain model
│ ├── settings/ # Application settings
│ │ └── service.go # Theme preferences, etc.
│ └── storage/ # S3-compatible storage abstraction
│ ├── provider.go # Provider domain model + repository interface
│ ├── provider_service.go # Provider CRUD with credential encryption
│ ├── provider_handler.go # HTTP handlers
│ ├── s3.go # S3Client (UploadStream, Download, List, Delete)
│ ├── service.go # Storage service interface
│ └── crypto.go # CredentialEncryptor for S3 keys at rest
├── web/ # Frontend (Vanilla JS SPA)
│ ├── index.html # Single HTML page
│ ├── css/
│ │ └── style.css # All styles with CSS custom properties
│ └── js/
│ └── app.js # SPA client with routing and API calls
├── site/ # Documentation site (VitePress)
│ ├── index.md # Landing page
│ ├── .vitepress/
│ │ ├── config.ts # Site configuration
│ │ └── theme/ # Custom theme
│ ├── guide/ # User guides
│ └── architecture/ # Architecture docs
├── prd/ # Product requirements documents
├── sketches/ # UI mockups and wireframes
├── Dockerfile # Backend Docker image
├── Dockerfile.frontend # Frontend Nginx image
├── docker-compose.yml # Production Docker compose
├── Makefile # Build, test, run targets
└── go.mod # Go module definitionBuilding from Source
Development Build
# Quick build
make build
# Binary at: dist/jagad
# Run directly (hot-reload via 'go run')
make run-quickProduction Build
# Build for current platform
make build
# Cross-compile for multiple platforms
make dist
# Outputs:
# dist/jagad-linux-amd64
# dist/jagad-linux-arm64Docker Build
# Build and run with Docker Compose
make docker-run
# Build images only
make docker-buildBuild Tags
The build supports the following ldflags:
go build -ldflags="-s -w -X main.Version=$(git describe --tags --always --dirty)"-s -w: Strip debug information (smaller binary)-X main.Version: Embed version string
Running Tests
All Tests
make testThis runs all tests with the race detector and code coverage:
go test -race -cover ./...Test by Package
# Test a specific package
go test -race -cover ./internal/backup/...
# Test with verbose output
go test -v -race -cover ./internal/encryption/...
# Test a single function
go test -v -run TestEncryptStream ./internal/encryption/Integration Tests
Integration tests require database servers and S3-compatible storage:
# Start test infrastructure
docker compose -f docker-compose.dev.yml up -d
# Run integration tests
go test -tags=integration -v ./internal/backup/...Test Guidelines
- Unit tests should not require external services (mock interfaces)
- Integration tests use the
integrationbuild tag - Aim for >70% code coverage for backend packages
- Test error paths, not just happy paths
- Use
t.Parallel()where safe to speed up test suites
Writing Tests
func TestStreamingPipeline(t *testing.T) {
t.Parallel()
// Create a mock dump source
dumpData := []byte("CREATE TABLE test (id INT);")
// Create a pipe for S3
pr, pw := io.Pipe()
// Simulate the streaming pipeline
go func() {
gw := gzip.NewWriter(pw)
gw.Write(dumpData)
gw.Close()
pw.Close()
}()
// Read the compressed output
compressed, err := io.ReadAll(pr)
assert.NoError(t, err)
assert.True(t, len(compressed) > 0)
// Verify decompression
gr, _ := gzip.NewReader(bytes.NewReader(compressed))
decompressed, _ := io.ReadAll(gr)
assert.Equal(t, dumpData, decompressed)
}Code Style
Go Code
Jagad follows standard Go conventions with some specific guidelines:
- Formatting: Use
gofmt(orgo fmt) exclusively. No exceptions. - Linting: Run
golangci-lintbefore submitting. Configuration is in.golangci.yml. - Naming:
- Use short but descriptive variable names (
connnotconnection,dbnotdatabase) - Avoid stutter (
backup.Servicenotbackup.BackupService) - Use
CamelCasefor exported,camelCasefor unexported
- Use short but descriptive variable names (
- Comments:
- Every exported type, function, and constant must have a doc comment
- Comments should explain why, not what (the code says what)
- Error handling:
- Always check errors
- Wrap errors with context:
fmt.Errorf("s3 upload: %w", err) - Use
%wfor error wrapping (Go 1.13+)
- Imports:
- Group: standard library → third-party → internal packages
- Use
gofumptstyle import ordering
Go File Template
// Package backup handles database backup execution.
package backup
import (
"context"
"fmt"
"io"
"os/exec"
)
// Service handles backup execution and management.
type Service struct {
repo Repository
}
// NewService creates a new backup service.
func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
// StartBackup initiates a backup for the given database.
func (s *Service) StartBackup(dbName string) error {
// ...
}Frontend Code (JavaScript)
- Formatting: Use standard JavaScript conventions (2-space indentation)
- Naming:
camelCasefor variables and functions,PascalCasefor classes - No transpilation: The frontend is vanilla JS — no build step
- API calls: Use
fetch()with async/await pattern - DOM manipulation: Direct DOM manipulation (no framework)
CSS
- Custom properties: Use CSS custom properties for theming (dark/light mode)
- Class naming: Semantic class names with BEM-like conventions
- Responsive: Mobile-first approach with media queries at 768px breakpoint
Pull Request Workflow
1. Fork and Clone
git clone https://github.com/your-username/jagad.git
cd jagad
git remote add upstream https://github.com/edsuwarna/jagad.git2. Create a Branch
git checkout -b feat/your-feature-nameBranch naming convention:
| Prefix | Use Case |
|---|---|
feat/ | New feature |
fix/ | Bug fix |
docs/ | Documentation |
refactor/ | Code refactoring |
test/ | Adding or updating tests |
chore/ | Maintenance, dependencies |
3. Make Changes
- Write code following the Code Style guidelines
- Add tests for new functionality
- Update or add documentation for user-facing changes
- Keep commits atomic and well-described
Commit message format:
<type>(<scope>): <description>
[optional body]
[optional footer]Examples:
feat(backup): add streaming encryption pipeline
fix(scheduler): handle daylight saving time transitions
docs(security): add encryption key management guide4. Run Tests
# Run all tests
make test
# Run linter
golangci-lint run ./...
# Run vet
make vet5. Push and Create PR
git push origin feat/your-feature-nameThen create a Pull Request on GitHub with:
- Clear title describing the change
- Description explaining what and why
- Related issues referenced (e.g., "Closes #123")
- Checklist:
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] Code formatted with
go fmt - [ ] All tests pass
- [ ] Lint checks pass
6. Review Process
- At least one maintainer must review the PR
- Address all review comments
- Keep the PR focused — one feature/fix per PR
- Squash commits before merge if needed
7. After Merge
- Delete your feature branch
- Celebrate your contribution 🎉
Development Guidelines
Adding a New Database Type
- Add dump command in
internal/backup/service.gobuildDumpCmd() - Add restore logic in
internal/restore/service.goexecuteRestore() - Add incremental engine implementing
IncrementalEngineinterface - Register the engine in
cmd/jagad/main.go - Add connection form fields in
web/index.html - Add validation in
internal/connection/service.go - Update documentation
Adding a New Storage Provider
- Add provider constant in
internal/storage/provider.go - Add provider configuration form in
web/index.html - The existing
S3Clientworks for any S3-compatible service
Adding a New Notification Channel
- Add notification type constant in
internal/notification/model.go - Add sender method in
internal/notification/service.go - Add configuration form in
web/index.html - Add repository methods if new config fields are needed
Working with SQLite Migrations
Database migrations are handled automatically at startup in internal/repository/db.go. To add a new table or column:
- Add the schema version constant
- Add the migration SQL to the migration function
- Migrations are idempotent (safe to run multiple times)
Documentation
Documentation is built with VitePress.
# Install documentation dependencies
cd site
npm install
# Development server (hot reload)
npm run dev
# Build for production
npm run buildDocumentation conventions:
- Markdown files in
site/directory - Frontmatter with
titlefor page titles - Relative links between pages (e.g.,
../guide/getting-started) - Code blocks with language annotations
- ASCII diagrams for architecture concepts
Community
- GitHub Issues: Bug reports and feature requests
- Pull Requests: Code contributions
- Discussions: Questions and ideas
Code of Conduct: Be respectful, inclusive, and constructive. All participants in this project are expected to follow the Contributor Covenant.
License
By contributing to Jagad, you agree that your contributions will be licensed under the Apache License 2.0.