Version: 0.1.0 Last Updated: 2025-12-09
This guide covers local development setup, testing, building, and contributing to the BoardingPass project. For deployment information, see deployment.md. For API documentation, see api.md.
- Go 1.25+: Primary development language
- Podman or Docker: Container runtime for reproducible builds
- Git: Source control
- Make: Build automation (optional but recommended)
- golangci-lint 2.7.1: Code linting and security scanning
- GoReleaser: Build orchestration and packaging
- Linux: Full native development support (RHEL 9+, Rocky Linux 9+, AlmaLinux 9+, Debian 12+, Ubuntu 22.04+ LTS)
- macOS: Container-based development only
- Windows: WSL2 + Podman/Docker
RHEL 9+ / Rocky Linux / AlmaLinux:
sudo dnf install -y go-toolsetDebian 12+ / Ubuntu 22.04+:
wget https://go.dev/dl/go1.25.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrccurl -sSfL https://github.com/golangci/golangci-lint/releases/download/v2.7.1/golangci-lint-2.7.1-linux-amd64.tar.gz | \
sudo tar -xz -C /usr/local/bin --strip-components=1 golangci-lint-2.7.1-linux-amd64/golangci-lintcurl -sSfL https://github.com/goreleaser/goreleaser/releases/download/v1.23.0/goreleaser_Linux_x86_64.tar.gz | \
sudo tar -xz -C /usr/local/bin goreleasergo version # Should show go1.25 or higher
golangci-lint version # Should show v2.7.1
goreleaser --version # Should show v1.23.0 or higherThis approach uses a containerized build environment with UBI9 Go Toolset, ensuring consistent builds across all platforms.
RHEL 9+ / Rocky Linux / AlmaLinux:
sudo dnf install -y podmanDebian 12+ / Ubuntu 22.04+:
sudo apt update
sudo apt install -y podmanmacOS:
brew install podman
podman machine init
podman machine startpodman build -t boardingpass-builder -f build/Containerfile .This creates a reproducible build environment based on registry.access.redhat.com/ubi9/go-toolset:1.25 with Go 1.25, golangci-lint v2.7.1, and GoReleaser pre-installed.
# Clone the repository
git clone https://github.com/fzdarsky/boardingpass.git
cd boardingpass
# Install Git hooks (optional)
git config core.hooksPath .githooks
# Download dependencies
go mod download
go mod verifyboardingpass/
├── cmd/boardingpass/ # Main service binary entry point
├── internal/ # Private packages (not importable externally)
│ ├── api/ # HTTP handlers, middleware, server lifecycle
│ ├── auth/ # SRP-6a, session tokens, rate limiting
│ ├── command/ # Allow-listed command execution
│ ├── config/ # YAML config loading and validation
│ ├── inventory/ # System info extraction (TPM, board, CPU, OS, FIPS)
│ ├── lifecycle/ # Sentinel file, inactivity timeout, graceful shutdown
│ ├── logging/ # JSON logging with secret redaction
│ ├── network/ # Interface enumeration, link state, IP addresses
│ ├── provisioning/ # Config bundle parsing, atomic file ops
│ ├── tls/ # Self-signed cert generation, TLS 1.3+ config
│ └── transport/ # WiFi AP, Bluetooth PAN, USB transport handlers
├── pkg/protocol/ # Shared types for API and mobile app
├── tests/ # Unit, integration, contract, and e2e tests
├── build/ # systemd unit, sudoers config, Containerfile
├── _output/ # Build artifacts (gitignored)
├── docs/ # Documentation
├── specs/ # Feature specifications (SpecKit workflow)
├── .golangci.yaml # Linter config (v2 format, includes gosec)
├── .goreleaser.yaml # Build orchestration
├── go.mod # Go module definition
├── Makefile # Build automation
└── README.md # Project overview
make build # Build the BoardingPass service binary
make build-cli # Build the boarding CLI tool
make build-all # Build both service and CLI binaries
make release # Build release packages (RPM, DEB, archives)
make deploy # Build RPM and deploy to local RHEL bootc container
make undeploy # Stop and remove the bootc container
make test # Run unit tests
make lint # Run linters
make coverage # Generate test coverage report
make clean # Clean build artifacts (includes undeploy)
make generate # Generate mocks (uses go tool mockgen)# Build for current architecture only (fastest for local development)
goreleaser build --snapshot --clean --single-target
# Build for all targets (linux/amd64, linux/arm64)
goreleaser build --snapshot --clean
# Build release packages (RPM, DEB, archives) without publishing
goreleaser release --snapshot --clean --skip=publish
# Or use the Makefile target
make releaseOutput location: _output/dist/
What gets built:
- Binaries for Linux (amd64/arm64), Darwin (amd64/arm64), Windows (amd64/arm64)
- RPM packages:
boardingpass_*_linux_{amd64,arm64}.rpm - DEB packages:
boardingpass_*_linux_{amd64,arm64}.deb - Archives:
*.tar.gz(Linux/Darwin),*.zip(Windows)
# Build for current architecture only
podman run --rm -v $(pwd):/workspace:Z boardingpass-builder \
goreleaser build --snapshot --clean --single-target
# Build for all targets (linux/amd64, linux/arm64)
podman run --rm -v $(pwd):/workspace:Z boardingpass-builder \
goreleaser build --snapshot --clean
# Build release packages (RPM, DEB, archives)
podman run --rm -v $(pwd):/workspace:Z boardingpass-builder \
goreleaser release --snapshot --clean --skip=publishThe make deploy target provides a complete end-user testing experience by building the RPM and deploying it in a local RHEL bootc container:
# Build RPM and deploy to local container
make deploy
# Container will be running with systemd
# Access at https://localhost:9455
# View logs: podman exec -it boardingpass-bootc journalctl -u boardingpass
# Stop and remove container
make undeployThis deployment includes:
- Full RPM installation (binary, systemd unit, sudoers config, password generators)
- Pre-configured verifier using
primary_macgenerator - Systemd service management
- Port 9455 exposed to localhost
Architecture Detection: The deploy target automatically detects your system architecture (x86_64 → amd64, aarch64 → arm64) and builds the appropriate RPM.
Container Requirements: Requires Podman installed and RHEL subscription (for pulling registry.redhat.io/rhel9/rhel-bootc:9.7 base image).
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run tests with race detection
go test -race ./...
# Run tests with coverage
go test -cover ./...
# Run specific package tests
go test -v ./internal/auth
# Run specific test
go test -v -run TestSessionManager_CreateSession ./internal/auth
# Generate coverage report
make coverage
# Opens _output/coverage/coverage.html in browser-
Unit Tests: Co-located with source files (e.g.,
internal/auth/session_test.go)- Test individual packages in isolation
- Use table-driven tests for logic
- Use
testify/assertfor readability
-
Integration Tests:
tests/integration/- Test API endpoints with httptest
- Mock external dependencies using interfaces
-
Contract Tests:
tests/contract/- Validate API responses against OpenAPI spec
-
E2E Tests:
tests/e2e/- Full workflow tests in containerized systemd environment
- Run with:
go test -v ./tests/e2e -short=false
# Generate mocks for all interfaces
make generate# Run all linters (including gosec, govulncheck)
make lint
# Or directly:
golangci-lint run
# Run with verbose output
golangci-lint run --verbose
# Fix auto-fixable issues
golangci-lint run --fixLinters are configured in .golangci.yaml:
- gosec: Security analysis (G101-G404 checks)
- govulncheck: Vulnerability scanning
- errcheck: Unchecked error detection
- govet: Standard Go vet checks
- staticcheck: Static analysis
- gofmt, goimports: Code formatting
- revive: Linting for Go best practices
- gocritic: Opinionated linter
Unchecked errors:
// Bad
file.Close()
// Good
if err := file.Close(); err != nil {
return fmt.Errorf("failed to close file: %w", err)
}Exported functions without comments:
// Bad
func DoSomething() error {
// Good
// DoSomething performs a specific operation and returns an error if it fails.
func DoSomething() error {sudo mkdir -p /etc/boardingpass/tls
sudo mkdir -p /var/lib/boardingpass/staging
sudo chown -R $USER:$USER /var/lib/boardingpasssudo openssl req -x509 -newkey rsa:2048 -nodes \
-keyout /etc/boardingpass/tls/server.key \
-out /etc/boardingpass/tls/server.crt \
-days 365 \
-subj "/CN=localhost"
sudo chmod 600 /etc/boardingpass/tls/server.key
sudo chmod 644 /etc/boardingpass/tls/server.crtsudo mkdir -p /usr/local/bin
cat <<'EOF' | sudo tee /usr/local/bin/boardingpass-password-generator
#!/bin/bash
# Test password generator - outputs static password for development
echo "test-password-12345"
EOF
sudo chmod 500 /usr/local/bin/boardingpass-password-generatorcat <<'EOF' | sudo tee /etc/boardingpass/verifier
{
"username": "boardingpass",
"salt": "dGVzdHNhbHQxMjM0NTY3ODkw",
"password_generator": "/usr/local/bin/boardingpass-password-generator"
}
EOF
sudo chmod 400 /etc/boardingpass/verifiercat <<'EOF' | sudo tee /etc/boardingpass/config.yaml
service:
inactivity_timeout: "10m"
session_ttl: "30m"
sentinel_file: "/tmp/boardingpass-issued" # Use /tmp for testing
transports:
ethernet:
enabled: true
interfaces: []
address: "127.0.0.1"
port: 9455
tls_cert: "/etc/boardingpass/tls/server.crt"
tls_key: "/etc/boardingpass/tls/server.key"
provisioning:
allowed_paths:
- /tmp/boardingpass-test/
commands:
- id: "echo-test"
path: "/usr/bin/echo"
args: ["test successful"]
- id: "hostname"
path: "/usr/bin/hostname"
args: []
logging:
level: "debug"
format: "human"
EOF# Run service in foreground (for development/debugging)
sudo ./_output/bin/boardingpass --config=/etc/boardingpass/config.yaml
# Service will bind to https://127.0.0.1:9455
# In another terminal, test the service:
curl -k https://127.0.0.1:9455/info
# Should return: {"error":"unauthorized","message":"Invalid or expired session token"}Set logging level to debug in /etc/boardingpass/config.yaml:
logging:
level: "debug"
format: "human"# Install Delve
go install github.com/go-delve/delve/cmd/dlv@latest
# Run service with debugger
sudo dlv exec ./_output/bin/boardingpass -- --config=/etc/boardingpass/config.yaml
# In Delve console:
(dlv) break main.main
(dlv) continueThe repository includes two CI/CD workflows:
- service-ci.yaml: Lint, test, build on every push/PR
- release.yaml: Build and publish packages on git tags
Run the same checks that GitHub Actions runs:
# Step 1: Lint
make lint
# Step 2: Test with coverage threshold
make test
# Step 3: Build
make build
# Step 4: Check binary size
SIZE=$(stat -c%s _output/dist/boardingpass_linux_amd64_v1/boardingpass)
SIZE_MB=$(echo "scale=2; $SIZE / 1048576" | bc)
echo "Binary size: ${SIZE_MB} MB (must be < 10 MB)"git checkout -b feature/my-new-feature# Write code
vim internal/mypackage/myfile.go
# Write tests
vim internal/mypackage/myfile_test.go
# Run tests
go test -v ./internal/mypackage
# Run linters
make lint# Stage changes
git add .
# Commit with descriptive message
git commit -m "feat: add new feature X
- Implements functionality Y
- Adds test coverage for Z
- Updates documentation
Closes #123"# Push branch
git push origin feature/my-new-feature
# Create pull request on GitHub
# CI will automatically run lint, test, and build checksFollow conventional commits format:
feat:New featurefix:Bug fixdocs:Documentation changestest:Adding or updating testsrefactor:Code refactoringchore:Maintenance tasksperf:Performance improvements
Solution: Run with sudo or configure capabilities:
sudo setcap 'cap_net_bind_service=+ep' ./_output/bin/boardingpassSolution: Ensure certificate files exist and have correct permissions:
ls -l /etc/boardingpass/tls/
# Should show:
# -rw-r--r-- server.crt
# -rw------- server.keySolution: Remove sentinel file for testing:
sudo rm /tmp/boardingpass-issuedSolution: Ensure NetworkManager is running:
systemctl status NetworkManager
# If not running:
sudo systemctl start NetworkManagerSolution: Ensure SELinux labels are correct (:Z flag):
podman run --rm -v $(pwd):/workspace:Z boardingpass-builder ...Solution: Run integration tests in containerized environment or with sudo:
# Run unit tests only (no permission issues)
go test -short ./...
# Run all tests with sudo (for integration tests)
sudo -E go test ./...# Build with profiling enabled
go test -cpuprofile=cpu.prof -bench=. ./internal/auth
# Analyze profile
go tool pprof cpu.prof# Build with memory profiling enabled
go test -memprofile=mem.prof -bench=. ./internal/auth
# Analyze profile
go tool pprof mem.prof# Enable pprof in development mode
# Add to main.go:
import _ "net/http/pprof"
# Run service
sudo ./_output/bin/boardingpass --config=/etc/boardingpass/config.yaml
# In another terminal, access pprof:
go tool pprof http://localhost:9455/debug/pprof/heap- Follow Effective Go
- Use
gofmtfor formatting (enforced by linters) - Keep functions small and focused
- Use guard clauses to handle errors early
- Always wrap errors with context:
fmt.Errorf("context: %w", err) - Use table-driven tests for logic
- Short, descriptive names:
cfor client, notmyClient - Exported functions must have comments
- Use consistent receiver names: Use short abbreviations (e.g.,
smfor SessionManager)
// Good
if err := doSomething(); err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
// Bad
if err := doSomething(); err != nil {
return err // Missing context
}Always propagate context.Context as the first argument in async/I-O functions:
func ProcessRequest(ctx context.Context, req *Request) error {
// ...
}- Project README: README.md
- Mobile App Development: mobile/README.md
- Deployment Guide: deployment.md
- API Documentation: api.md
- Security Guide: security.md
- CLI Reference: cli-reference.md
- Service Configuration: configuring-the-service.md
- OpenAPI Specification: specs/001-boardingpass-api/contracts/openapi.yaml
- Implementation Plan: specs/001-boardingpass-api/plan.md
- Feature Specification: specs/001-boardingpass-api/spec.md
Document Status: Complete Last Updated: 2025-12-09