Skip to content

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

bash
# 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-quick

The application will start at http://localhost:8080 with SQLite storage at ./data/jagad.db.


Development Environment Setup

Prerequisites

ToolVersionPurpose
Go1.25+Backend runtime
Git2.xVersion control
Docker + ComposeLatestContainerized development and testing
PostgreSQL client15+Testing PostgreSQL backups
MySQL client8.0+Testing MySQL backups
MariaDB client10.2+Testing MariaDB backups

Optional but recommended:

ToolPurpose
pgbackrestTesting incremental backup engine
xtrabackupTesting MySQL incremental backups
mariabackupTesting MariaDB incremental backups
golangci-lintCode quality and style checking
pre-commitAutomated pre-commit hooks

Go Version Management

Jagad uses Go 1.25+. You can check your Go version:

bash
go version

If 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:

bash
# 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 :8080

Project 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 definition

Building from Source

Development Build

bash
# Quick build
make build
# Binary at: dist/jagad

# Run directly (hot-reload via 'go run')
make run-quick

Production Build

bash
# Build for current platform
make build

# Cross-compile for multiple platforms
make dist
# Outputs:
#   dist/jagad-linux-amd64
#   dist/jagad-linux-arm64

Docker Build

bash
# Build and run with Docker Compose
make docker-run

# Build images only
make docker-build

Build Tags

The build supports the following ldflags:

bash
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

bash
make test

This runs all tests with the race detector and code coverage:

bash
go test -race -cover ./...

Test by Package

bash
# 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:

bash
# 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 integration build 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

go
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:

  1. Formatting: Use gofmt (or go fmt) exclusively. No exceptions.
  2. Linting: Run golangci-lint before submitting. Configuration is in .golangci.yml.
  3. Naming:
    • Use short but descriptive variable names (conn not connection, db not database)
    • Avoid stutter (backup.Service not backup.BackupService)
    • Use CamelCase for exported, camelCase for unexported
  4. Comments:
    • Every exported type, function, and constant must have a doc comment
    • Comments should explain why, not what (the code says what)
  5. Error handling:
    • Always check errors
    • Wrap errors with context: fmt.Errorf("s3 upload: %w", err)
    • Use %w for error wrapping (Go 1.13+)
  6. Imports:
    • Group: standard library → third-party → internal packages
    • Use gofumpt style import ordering

Go File Template

go
// 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)

  1. Formatting: Use standard JavaScript conventions (2-space indentation)
  2. Naming: camelCase for variables and functions, PascalCase for classes
  3. No transpilation: The frontend is vanilla JS — no build step
  4. API calls: Use fetch() with async/await pattern
  5. DOM manipulation: Direct DOM manipulation (no framework)

CSS

  1. Custom properties: Use CSS custom properties for theming (dark/light mode)
  2. Class naming: Semantic class names with BEM-like conventions
  3. Responsive: Mobile-first approach with media queries at 768px breakpoint

Pull Request Workflow

1. Fork and Clone

bash
git clone https://github.com/your-username/jagad.git
cd jagad
git remote add upstream https://github.com/edsuwarna/jagad.git

2. Create a Branch

bash
git checkout -b feat/your-feature-name

Branch naming convention:

PrefixUse 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 guide

4. Run Tests

bash
# Run all tests
make test

# Run linter
golangci-lint run ./...

# Run vet
make vet

5. Push and Create PR

bash
git push origin feat/your-feature-name

Then 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

  1. At least one maintainer must review the PR
  2. Address all review comments
  3. Keep the PR focused — one feature/fix per PR
  4. Squash commits before merge if needed

7. After Merge

  • Delete your feature branch
  • Celebrate your contribution 🎉

Development Guidelines

Adding a New Database Type

  1. Add dump command in internal/backup/service.go buildDumpCmd()
  2. Add restore logic in internal/restore/service.go executeRestore()
  3. Add incremental engine implementing IncrementalEngine interface
  4. Register the engine in cmd/jagad/main.go
  5. Add connection form fields in web/index.html
  6. Add validation in internal/connection/service.go
  7. Update documentation

Adding a New Storage Provider

  1. Add provider constant in internal/storage/provider.go
  2. Add provider configuration form in web/index.html
  3. The existing S3Client works for any S3-compatible service

Adding a New Notification Channel

  1. Add notification type constant in internal/notification/model.go
  2. Add sender method in internal/notification/service.go
  3. Add configuration form in web/index.html
  4. 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:

  1. Add the schema version constant
  2. Add the migration SQL to the migration function
  3. Migrations are idempotent (safe to run multiple times)

Documentation

Documentation is built with VitePress.

bash
# Install documentation dependencies
cd site
npm install

# Development server (hot reload)
npm run dev

# Build for production
npm run build

Documentation conventions:

  • Markdown files in site/ directory
  • Frontmatter with title for 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.

Released under the Apache 2.0 License.