Complete guide to maintaining code quality in the Unraid Management Agent project.
This project uses automated code quality checks via pre-commit hooks to ensure consistency, security, and best practices before code is committed.
# Clone the repository
git clone https://github.com/ruaan-deysel/unraid-management-agent.git
cd unraid-management-agent
# Run the setup script
./scripts/setup-pre-commit.shThis script:
- Installs Python and pip (if needed)
- Installs pre-commit via pip
- Configures pre-commit hooks
- Verifies the installation
# Install pre-commit
pip install pre-commit
# Install the git hooks
make pre-commit-install
# Or directly:
pre-commit installThe project uses the following pre-commit hooks (configured in .pre-commit-config.yaml):
Automatically formats Go code to match Go standards.
What it checks:
- Proper indentation (tabs)
- Consistent spacing
- Import organization
How to run manually:
gofmt -w .Manages Go import statements automatically.
What it checks:
- Unused imports
- Import grouping (stdlib, external, internal)
- Alphabetical ordering
How to run manually:
goimports -w .Comprehensive linter running 50+ checks.
What it checks:
- Code complexity
- Dead code
- Inefficient assignments
- Error handling
- Security issues
- Style violations
- And much more...
Configuration: .golangci.yml
How to run manually:
make lint
# Or directly:
golangci-lint runSecurity-focused static analysis tool.
What it checks:
- SQL injection vulnerabilities
- Command injection risks
- Path traversal attacks
- Hardcoded credentials
- Weak cryptography
- File permission issues
How to run manually:
make security-check
# Or directly:
gosec ./...Official Go static analysis tool.
What it checks:
- Suspicious constructs
- Printf format strings
- Unreachable code
- Nil pointer dereferences
- Invalid interface implementations
How to run manually:
go vet ./...Removes trailing whitespace from all text files.
Ensures files end with a newline.
Validates YAML and JSON file syntax.
Once installed, pre-commit runs automatically when you commit:
git add .
git commit -m "Your commit message"
# Pre-commit hooks run hereIf any check fails, the commit is aborted with error messages.
Run checks without committing:
# Run all hooks
make pre-commit-run
# Run on all files
pre-commit run --all-files
# Run specific hook
pre-commit run golangci-lint --all-files# Skip all hooks for a commit (use sparingly)
git commit -m "Message" --no-verifyWarning: Only skip hooks if absolutely necessary (e.g., work-in-progress commits for backup).
make lint # Run golangci-lint
make security-check # Run gosec security scan
make pre-commit-run # Run all pre-commit checks
make pre-commit-install # Install pre-commit hooksmake test # Run all tests with race detection
make test-coverage # Generate coverage report (coverage.html)make deps # Install/update dependencies
make local # Build for current architecture
make release # Build for Linux/amd64 (Unraid)
make clean # Clean build artifactsFollow official Go conventions:
// Good: Clear naming, proper error handling
func (c *SystemCollector) Collect() error {
info, err := c.collectSystemInfo()
if err != nil {
return fmt.Errorf("failed to collect system info: %w", err)
}
c.ctx.Hub.Pub(info, "system_update")
return nil
}
// Bad: Unclear naming, poor error handling
func (c *SystemCollector) C() error {
i, e := c.csi()
if e != nil {
return e
}
c.ctx.Hub.Pub(i, "system_update")
return nil
}Always wrap errors with context:
// Good: Provides context
if err := someFunction(); err != nil {
return fmt.Errorf("failed to execute someFunction: %w", err)
}
// Bad: Loses context
if err := someFunction(); err != nil {
return err
}From .github/copilot-instructions.md:
-
Always validate user input using
lib/validation.go:if err := lib.ValidateContainerID(containerID); err != nil { return err }
-
Never use
exec.Commanddirectly — uselib.ExecCommand()orlib.ExecCommandOutput() -
Path traversal protection — validate all file paths:
if err := lib.ValidateShareName(shareName); err != nil { return err }
The project uses a strict linting configuration in .golangci.yml:
- errcheck: Check for unchecked errors
- gosimple: Suggest code simplifications
- govet: Official Go static analysis
- ineffassign: Detect ineffectual assignments
- staticcheck: Advanced static analysis
- typecheck: Type checking
- unused: Detect unused code
- gosec: Security vulnerabilities
- misspell: Common spelling mistakes
- gofmt: Code formatting
- goimports: Import management
- revive: Extended linting rules
- goconst: Find repeated strings that could be constants
- dupl: Code duplication detection
linters-settings:
errcheck:
check-blank: true
check-type-assertions: true
govet:
enable-all: true
gosec:
severity: medium
confidence: mediumPre-commit checks also run in GitHub Actions CI:
name: Pre-commit
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
- uses: pre-commit/action@v3.0.0# Update pip
pip install --upgrade pip
# Reinstall pre-commit
pip install --force-reinstall pre-commit
# Verify installation
pre-commit --version# View detailed error
pre-commit run --all-files --verbose
# Update hooks
pre-commit autoupdate
# Clean and reinstall
pre-commit uninstall
pre-commit install# Run only fast linters
golangci-lint run --fast
# Run on changed files only
golangci-lint run --new-from-rev=mainEdit .golangci.yml to exclude specific issues:
linters-settings:
gosec:
excludes:
- G304 # File path provided as taint input (if validated elsewhere)Important: Only exclude after verifying it's a false positive.
Maintain test coverage above 70%:
# Generate coverage report
make test-coverage
# View coverage.html in browser
open coverage.htmlUse table-driven tests:
func TestValidateContainerID(t *testing.T) {
tests := []struct {
name string
id string
wantErr bool
}{
{"valid short ID", "bbb57ffa3c50", false},
{"empty ID", "", true},
{"SQL injection", "'; DROP TABLE--", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateContainerID(tt.id)
if (err != nil) != tt.wantErr {
t.Errorf("got error=%v, wantErr=%v", err, tt.wantErr)
}
})
}
}- Run pre-commit before pushing:
make pre-commit-run - Write tests for new code: Aim for 70%+ coverage
- Handle errors properly: Always wrap with context
- Validate all user input: Use validation functions
- Document complex code: Clear comments explain "why"
- Keep functions small: Max 50 lines recommended
- Follow Go conventions: Use
gofmt,goimports
- Contributing Guide - Contribution workflow
- Testing Guide - Comprehensive testing guide
- Architecture - System architecture overview
Last Updated: January 2026
Pre-commit Version: 3.5.0+
Golangci-lint Version: 1.55.0+