Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Git
.git
.gitignore
.github

# Build artifacts
bin/
*.db
*.out
coverage.html

# IDE
.vscode
.idea
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Documentation
*.md
!README.md
docs/

# Docker
Dockerfile*
docker-compose*.yml
.dockerignore

# CI/CD
.travis.yml
.gitlab-ci.yml

# Test files
*_test.go
testdata/

# Temporary files
tmp/
temp/
*.tmp
24 changes: 24 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Server Configuration
SERVER_HOST=localhost
SERVER_PORT=8080
SERVER_READ_TIMEOUT=15s
SERVER_WRITE_TIMEOUT=15s
SERVER_IDLE_TIMEOUT=60s

# Database Configuration
DB_PATH=./useragent.db
DB_MAX_OPEN_CONNS=25
DB_MAX_IDLE_CONNS=5
DB_CONN_MAX_LIFETIME=5m

# Application Configuration
APP_ENV=development # Options: development, staging, production
LOG_LEVEL=info # Options: debug, info, warn, error
MAX_REQUESTS_PER_MINUTE=100

# Production Example (uncomment and modify for production):
# APP_ENV=production
# LOG_LEVEL=warn
# SERVER_HOST=0.0.0.0
# DB_PATH=/data/useragent.db
# MAX_REQUESTS_PER_MINUTE=50
76 changes: 76 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Multi-stage build for security and size optimization
FROM golang:1.22-alpine AS builder

# Install build dependencies
RUN apk add --no-cache git ca-certificates tzdata

# Create appuser for running the application
RUN adduser -D -g '' appuser

WORKDIR /build

# Copy go mod files first for better caching
COPY go.mod go.sum* ./
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dockerfile copies go.sum* with a wildcard (line 13), which will silently succeed even if go.sum doesn't exist. Since go.sum is critical for dependency verification and reproducible builds, consider removing the wildcard to ensure the build fails if go.sum is missing:

COPY go.mod go.sum ./
Suggested change
COPY go.mod go.sum* ./
COPY go.mod go.sum ./

Copilot uses AI. Check for mistakes.

# Download dependencies
RUN go mod download
RUN go mod verify

# Copy source code
COPY . .

# Build the application with security flags
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build \
-ldflags='-w -s -extldflags "-static"' \
-a -installsuffix cgo \
-o /app/useragent-demo \
./cmd/demo

# Final stage - minimal runtime image
FROM alpine:latest

# Install runtime dependencies and security updates
RUN apk --no-cache add ca-certificates tzdata && \
apk --no-cache upgrade

# Create non-root user
RUN adduser -D -g '' appuser

# Create directory for database with proper permissions
RUN mkdir -p /data && chown appuser:appuser /data

# Copy timezone data and certificates
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Copy the binary from builder
COPY --from=builder /app/useragent-demo /app/useragent-demo

# Set ownership
RUN chown -R appuser:appuser /app

# Switch to non-root user
USER appuser

# Set working directory
WORKDIR /app

# Environment variables
ENV SERVER_HOST=0.0.0.0 \
SERVER_PORT=8080 \
DB_PATH=/data/useragent.db \
APP_ENV=production \
LOG_LEVEL=info

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1
Comment on lines +69 to +70
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The health check uses wget but the Alpine image may not have it installed by default. The Dockerfile installs ca-certificates and tzdata (line 33) but not wget. This will cause the health check to fail. Consider either:

  1. Installing wget: RUN apk --no-cache add ca-certificates tzdata wget
  2. Using nc (netcat) which is available in Alpine: CMD nc -z localhost 8080 || exit 1
  3. Building a custom health check binary in the multi-stage build

Copilot uses AI. Check for mistakes.

# Volume for persistent data
VOLUME ["/data"]

# Run the application
ENTRYPOINT ["/app/useragent-demo"]
130 changes: 130 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
.PHONY: help build test test-coverage test-race clean run run-demo docker-build docker-run lint security-scan fmt vet install-tools

# Variables
APP_NAME=useragent-demo
BINARY_NAME=useragent-demo
GO=go
GOFLAGS=-v
LDFLAGS=-ldflags "-w -s"
COVERAGE_FILE=coverage.out
DOCKER_IMAGE=commonuseragent:latest

# Colors for output
GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
RESET := $(shell tput -Txterm sgr0)

help: ## Show this help message
@echo '$(GREEN)Available targets:$(RESET)'
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(YELLOW)%-20s$(RESET) %s\n", $$1, $$2}'

install-tools: ## Install development tools
@echo '$(GREEN)Installing development tools...$(RESET)'
@which golangci-lint > /dev/null || (echo 'Installing golangci-lint...' && go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest)
@which gosec > /dev/null || (echo 'Installing gosec...' && go install github.com/securego/gosec/v2/cmd/gosec@latest)
@which staticcheck > /dev/null || (echo 'Installing staticcheck...' && go install honnef.co/go/tools/cmd/staticcheck@latest)

build: ## Build the library
@echo '$(GREEN)Building library...$(RESET)'
$(GO) build $(GOFLAGS) ./...

build-demo: ## Build the demo application
@echo '$(GREEN)Building demo application...$(RESET)'
$(GO) build $(GOFLAGS) $(LDFLAGS) -o bin/$(BINARY_NAME) ./cmd/demo

test: ## Run tests
@echo '$(GREEN)Running tests...$(RESET)'
$(GO) test $(GOFLAGS) -timeout 30s ./...

test-coverage: ## Run tests with coverage
@echo '$(GREEN)Running tests with coverage...$(RESET)'
$(GO) test -v -race -coverprofile=$(COVERAGE_FILE) -covermode=atomic ./...
$(GO) tool cover -html=$(COVERAGE_FILE) -o coverage.html
@echo '$(GREEN)Coverage report generated: coverage.html$(RESET)'

test-race: ## Run tests with race detector
@echo '$(GREEN)Running tests with race detector...$(RESET)'
$(GO) test -v -race -timeout 60s ./...

bench: ## Run benchmarks
@echo '$(GREEN)Running benchmarks...$(RESET)'
$(GO) test -bench=. -benchmem ./...

fmt: ## Format Go code
@echo '$(GREEN)Formatting code...$(RESET)'
$(GO) fmt ./...

vet: ## Run go vet
@echo '$(GREEN)Running go vet...$(RESET)'
$(GO) vet ./...

lint: install-tools ## Run linter
@echo '$(GREEN)Running linter...$(RESET)'
golangci-lint run ./...

security-scan: install-tools ## Run security scanner
@echo '$(GREEN)Running security scanner...$(RESET)'
gosec -exclude-generated ./...

staticcheck: install-tools ## Run staticcheck
@echo '$(GREEN)Running staticcheck...$(RESET)'
staticcheck ./...

check: fmt vet lint staticcheck security-scan ## Run all checks (fmt, vet, lint, staticcheck, security)
@echo '$(GREEN)All checks passed!$(RESET)'

run-demo: build-demo ## Run the demo application
@echo '$(GREEN)Starting demo application...$(RESET)'
./bin/$(BINARY_NAME)

run-demo-dev: ## Run the demo application in development mode
@echo '$(GREEN)Starting demo application (development mode)...$(RESET)'
APP_ENV=development LOG_LEVEL=debug $(GO) run ./cmd/demo

clean: ## Clean build artifacts
@echo '$(GREEN)Cleaning build artifacts...$(RESET)'
rm -rf bin/
rm -f $(COVERAGE_FILE) coverage.html
rm -f *.db
$(GO) clean -cache -testcache

docker-build: ## Build Docker image
@echo '$(GREEN)Building Docker image...$(RESET)'
docker build -t $(DOCKER_IMAGE) .

docker-run: ## Run Docker container
@echo '$(GREEN)Running Docker container...$(RESET)'
docker run -p 8080:8080 --rm $(DOCKER_IMAGE)

docker-compose-up: ## Start services with docker-compose
@echo '$(GREEN)Starting services with docker-compose...$(RESET)'
docker-compose up -d

docker-compose-down: ## Stop services with docker-compose
@echo '$(GREEN)Stopping services with docker-compose...$(RESET)'
docker-compose down

docker-compose-logs: ## View docker-compose logs
docker-compose logs -f

mod-download: ## Download dependencies
@echo '$(GREEN)Downloading dependencies...$(RESET)'
$(GO) mod download

mod-tidy: ## Tidy dependencies
@echo '$(GREEN)Tidying dependencies...$(RESET)'
$(GO) mod tidy

mod-verify: ## Verify dependencies
@echo '$(GREEN)Verifying dependencies...$(RESET)'
$(GO) mod verify

deps: mod-download mod-tidy mod-verify ## Manage dependencies (download, tidy, verify)

all: clean deps check test build build-demo ## Run all checks, tests, and build everything
@echo '$(GREEN)All tasks completed successfully!$(RESET)'

ci: deps check test-coverage ## Run CI pipeline
@echo '$(GREEN)CI pipeline completed!$(RESET)'

.DEFAULT_GOAL := help
Loading
Loading