diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..68876ba --- /dev/null +++ b/.dockerignore @@ -0,0 +1,42 @@ +# Build directories +builddir/ +testbuild/ + +# Git +.git/ +.gitignore + +# Documentation +README.md +*.md + +# CI/CD +.github/ + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Docker files +Dockerfile +.dockerignore +docker-compose.yml + +# Coverage reports +.codecov.yml +coverage/ +*.gcov +*.gcda +*.gcno + +# Temporary files +*.tmp +*.temp +*.log diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..8925608 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,124 @@ +name: Build and Publish Docker Image + +on: + push: + branches: [ "main" ] + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "main" ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + meson \ + ninja-build \ + pkg-config \ + libboost-all-dev \ + libgtest-dev \ + libgmock-dev + + - name: Setup build directory + run: meson setup builddir --buildtype=release + + - name: Build project + run: meson compile -C builddir + + - name: Run tests + run: meson test -C builddir + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: builddir/meson-logs/testlog.txt + + build-and-push-image: + runs-on: ubuntu-latest + needs: build-and-test + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Generate artifact attestation + if: github.event_name != 'pull_request' + uses: actions/attest-build-provenance@v1 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true + + security-scan: + runs-on: ubuntu-latest + needs: build-and-push-image + if: github.event_name != 'pull_request' + permissions: + contents: read + security-events: write + + steps: + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-results.sarif' diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..4b9801a --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,365 @@ +# Docker Setup and Deployment + +This document describes how to build, run, and deploy the finger service using Docker and GitHub Actions. + +## Quick Start + +### Using Docker Compose (Recommended) + +1. **Clone the repository:** + ```bash + git clone https://github.com/waffle2k/finger.git + cd finger + ``` + +2. **Start the service:** + ```bash + docker compose up -d + ``` + +3. **Test the service:** + ```bash + # Test with the example user + finger john@localhost + + # Or using telnet + telnet localhost 79 + # Then type: john + ``` + +4. **Add your own users:** + ```bash + # Create a status file for a user + echo "Your status message here" > users/yourusername + + # Test it + finger yourusername@localhost + ``` + +### Using Docker directly + +1. **Build the image:** + ```bash + docker build -t finger-service . + ``` + +2. **Run the container:** + ```bash + docker run -d \ + --name finger \ + -p 79:79 \ + -v $(pwd)/users:/var/finger/users \ + finger-service + ``` + +### Using Pre-built Images + +You can also use the automatically built images from GitHub Container Registry: + +```bash +docker run -d \ + --name finger \ + -p 79:79 \ + -v $(pwd)/users:/var/finger/users \ + ghcr.io/waffle2k/finger:latest +``` + +## Docker Architecture + +### Multi-stage Build + +The Dockerfile uses a multi-stage build approach: + +1. **Builder Stage (Ubuntu 24.04):** + - Installs all build dependencies (meson, ninja, boost, gtest, etc.) + - Compiles the C++20 source code + - Runs all tests to ensure quality + - Creates a statically linked binary + +2. **Runtime Stage (Alpine Linux):** + - Minimal base image (~5MB) + - Only includes runtime dependencies + - Runs as non-root user for security + - Includes health checks + +### Security Features + +- **Non-root execution:** Runs as user `finger` (UID 1000) +- **Minimal attack surface:** Alpine Linux base with minimal packages +- **Health checks:** Built-in container health monitoring +- **Read-only filesystem:** Application doesn't write to filesystem + +### Image Size + +- **Final image:** ~15MB (Alpine + binary + minimal runtime deps) +- **Build image:** ~2GB (includes all build tools, discarded after build) + +## GitHub Actions CI/CD + +### Automated Workflow + +The repository includes a comprehensive GitHub Actions workflow (`.github/workflows/docker-publish.yml`) that: + +1. **Build and Test:** + - Builds the project with meson + - Runs all unit tests + - Uploads test results as artifacts + +2. **Multi-platform Docker Build:** + - Builds for `linux/amd64` and `linux/arm64` + - Uses Docker Buildx for cross-platform support + - Implements build caching for faster builds + +3. **Container Registry Publishing:** + - Publishes to GitHub Container Registry (`ghcr.io`) + - Tags with multiple strategies: + - `latest` for main branch + - `v1.2.3` for semantic version tags + - `main-abc1234` for commit SHA + - `pr-123` for pull requests + +4. **Security Scanning:** + - Runs Trivy vulnerability scanner + - Uploads results to GitHub Security tab + - Fails on high-severity vulnerabilities + +5. **Supply Chain Security:** + - Generates SLSA build provenance attestations + - Signs container images + - Provides build transparency + +### Triggering Builds + +The workflow triggers on: +- **Push to main branch:** Builds and publishes `latest` tag +- **Version tags:** Builds and publishes semantic version tags (`v1.0.0`) +- **Pull requests:** Builds but doesn't publish (security) + +### Using Published Images + +Images are available at: `ghcr.io/waffle2k/finger` + +Available tags: +- `latest` - Latest stable build from main branch +- `v1.0.0` - Specific version releases +- `main-abc1234` - Specific commit builds + +## Configuration + +### Environment Variables + +The container supports these environment variables: + +- `FINGER_PORT`: Port to listen on (default: 79) +- `FINGER_DATA_DIR`: Directory for user files (default: /var/finger/users) + +### Volume Mounts + +- `/var/finger/users`: Directory containing user status files + - Mount your local `users/` directory here + - Each file represents a user (filename = username) + - File contents = user's status message + +### Health Checks + +The container includes built-in health checks: +- **Check:** TCP connection to port 79 +- **Interval:** Every 30 seconds +- **Timeout:** 10 seconds +- **Retries:** 3 attempts +- **Start period:** 40 seconds + +## Development + +### Local Development with Docker + +1. **Build development image:** + ```bash + docker build --target builder -t finger-dev . + ``` + +2. **Run tests in container:** + ```bash + docker run --rm finger-dev meson test -C builddir + ``` + +3. **Interactive development:** + ```bash + docker run -it --rm \ + -v $(pwd):/app \ + -w /app \ + finger-dev bash + ``` + +### Debugging + +1. **View container logs:** + ```bash + docker logs finger + ``` + +2. **Execute into running container:** + ```bash + docker exec -it finger sh + ``` + +3. **Check health status:** + ```bash + docker inspect finger | grep -A 10 Health + ``` + +## Production Deployment + +### Docker Swarm + +```yaml +version: '3.8' +services: + finger: + image: ghcr.io/waffle2k/finger:latest + ports: + - "79:79" + volumes: + - finger_data:/var/finger/users + deploy: + replicas: 2 + restart_policy: + condition: on-failure + healthcheck: + test: ["CMD", "nc", "-z", "localhost", "79"] + interval: 30s + timeout: 10s + retries: 3 + +volumes: + finger_data: +``` + +### Kubernetes + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: finger-service +spec: + replicas: 3 + selector: + matchLabels: + app: finger + template: + metadata: + labels: + app: finger + spec: + containers: + - name: finger + image: ghcr.io/waffle2k/finger:latest + ports: + - containerPort: 79 + volumeMounts: + - name: user-data + mountPath: /var/finger/users + livenessProbe: + tcpSocket: + port: 79 + initialDelaySeconds: 30 + periodSeconds: 10 + volumes: + - name: user-data + configMap: + name: finger-users +--- +apiVersion: v1 +kind: Service +metadata: + name: finger-service +spec: + selector: + app: finger + ports: + - port: 79 + targetPort: 79 + type: LoadBalancer +``` + +## Troubleshooting + +### Common Issues + +1. **Permission denied on user files:** + ```bash + # Fix file permissions + chmod 644 users/* + ``` + +2. **Port 79 requires root:** + ```bash + # Use a different port + docker run -p 8079:79 finger-service + ``` + +3. **Container won't start:** + ```bash + # Check logs + docker logs finger + + # Check if port is available + netstat -ln | grep :79 + ``` + +4. **Health check failing:** + ```bash + # Test manually + docker exec finger nc -z localhost 79 + + # Check if service is running + docker exec finger ps aux + ``` + +### Performance Tuning + +1. **Resource limits:** + ```yaml + services: + finger: + deploy: + resources: + limits: + memory: 64M + cpus: '0.1' + ``` + +2. **Connection limits:** + - The service handles concurrent connections efficiently + - Default OS limits should be sufficient for most use cases + - Monitor with `docker stats` for resource usage + +## Security Considerations + +1. **Network Security:** + - Finger protocol sends data in plain text + - Consider using behind a reverse proxy with TLS + - Restrict access with firewall rules + +2. **Data Security:** + - User files are readable by the finger user + - Don't store sensitive information in status files + - Consider file permissions on the host + +3. **Container Security:** + - Runs as non-root user + - Uses minimal base image + - Regular security scanning in CI/CD + - Keep images updated + +## Contributing + +When contributing Docker-related changes: + +1. Test locally with `docker build` +2. Ensure all tests pass in the container +3. Update this documentation if needed +4. The CI/CD pipeline will automatically test your changes + +For more information, see the main [README.md](README.md). diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8353adb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,68 @@ +# Multi-stage build for C++ finger service +# Build stage - use Ubuntu with all development dependencies +FROM ubuntu:24.04 AS builder + +# Avoid interactive prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + meson \ + ninja-build \ + pkg-config \ + libboost-all-dev \ + libgtest-dev \ + libgmock-dev \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy source files +COPY . . + +# Build the project +RUN meson setup builddir --buildtype=release +RUN meson compile -C builddir + +# Run tests to ensure quality +RUN meson test -C builddir + +# Runtime stage - minimal Alpine Linux +FROM alpine:latest + +# Install runtime dependencies (if any) +RUN apk add --no-cache \ + libstdc++ \ + && addgroup -g 1000 finger \ + && adduser -D -s /bin/sh -u 1000 -G finger finger + +# Copy the compiled binary from builder stage +COPY --from=builder /app/builddir/finger /usr/local/bin/finger + +# Make the binary executable +RUN chmod +x /usr/local/bin/finger + +# Create the directory for user data with proper permissions +RUN mkdir -p /var/finger/users && \ + chown -R finger:finger /var/finger + +# Switch to non-root user +USER finger + +# Expose port 79 (finger protocol) +EXPOSE 79 + +# Add health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD nc -z localhost 79 || exit 1 + +# Set metadata labels +LABEL org.opencontainers.image.title="finger" +LABEL org.opencontainers.image.description="A silly finger service written in C++20" +LABEL org.opencontainers.image.source="https://github.com/waffle2k/finger" +LABEL org.opencontainers.image.licenses="MIT" + +# Set the binary as the default command +CMD ["finger"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7bade87 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + finger: + build: . + ports: + - "79:79" + volumes: + - ./users:/var/finger/users + restart: unless-stopped + healthcheck: + test: ["CMD", "nc", "-z", "localhost", "79"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Example using published image instead of building locally + # finger: + # image: ghcr.io/waffle2k/finger:latest + # ports: + # - "79:79" + # volumes: + # - ./users:/var/finger/users + # restart: unless-stopped diff --git a/users/john b/users/john new file mode 100644 index 0000000..2170495 --- /dev/null +++ b/users/john @@ -0,0 +1,6 @@ +John Doe is currently working on the finger service project. +He's available for collaboration and can be reached via email. + +Current status: Online and coding! 🚀 + +Last updated: January 2025