From 9b981e854590f6e28671bd5e390ac82ba9ef4ff4 Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Wed, 5 Nov 2025 02:07:36 +0100 Subject: [PATCH] feat: add CI for backend --- .github/workflows/README.md | 217 +++++++++++++++++++++++++++ .github/workflows/publish-docker.yml | 123 +++++++++++++++ backend/README.md | 161 +++++++++++++++++++- backend/nix/shell.nix | 2 +- flake.nix | 10 +- frontend/shell.nix | 1 + 6 files changed, 507 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/publish-docker.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..b15d698 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,217 @@ +# GitHub Actions Workflows + +## Docker Image Publishing + +The `publish-docker.yml` workflow automatically builds and publishes Docker images for the backend service using Nix. + +### Triggers and Tagging Strategy + +| Event | Condition | Published Tags | Example | +|--------------+-----------------------------+------------------------+-------------------| +| Tag push | Tag pushed to `main` branch | `latest` + version tag | `latest`, `1.0.0` | +| Branch push | Push to `develop` branch | `develop` | `develop` | +| Pull request | PR opened or updated | `pr` | `pr12` | +| Branch push | Push to `main` (no tag) | `latest` | `latest` | + +### Required Secrets + +Configure these secrets in your repository settings (`Settings` → `Secrets and variables` → `Actions`): + +| Secret Name | Description | Example Value | +|---------------------+---------------------------------------------+-----------------------------------------| +| `DOCKER_USERNAME` | Username for Docker registry authentication | `phundrak` | +| `DOCKER_PASSWORD` | Password or token for Docker registry | Personal Access Token (PAT) or password | +| `CACHIX_AUTH_TOKEN` | (Optional) Token for Cachix caching | Your Cachix auth token | + +#### For GitHub Container Registry (ghcr.io) + +1. Create a Personal Access Token (PAT): + - Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic) + - Click "Generate new token (classic)" + - Select scopes: `write:packages`, `read:packages`, `delete:packages` + - Copy the generated token + +2. Add secrets: + - `DOCKER_USERNAME`: Your GitHub username + - `DOCKER_PASSWORD`: The PAT you just created + +#### For Docker Hub + +1. Create an access token: + - Go to Docker Hub → Account Settings → Security → Access Tokens + - Click "New Access Token" + - Set permissions to "Read, Write, Delete" + - Copy the generated token + +2. Add secrets: + - `DOCKER_USERNAME`: Your Docker Hub username + - `DOCKER_PASSWORD`: The access token you just created + +#### For Gitea Registry (e.g., labs.phundrak.com) + +1. Create an access token in Gitea: + - Log in to your Gitea instance + - Go to Settings (click your avatar → Settings) + - Navigate to Applications → Manage Access Tokens + - Click "Generate New Token" + - Give it a descriptive name (e.g., "Phundrak Labs Docker Registry") + - Select the required permissions: + - `write:package` - Required to publish packages + - `read:package` - Required to pull packages + - Click "Generate Token" + - Copy the generated token immediately (it won't be shown again) + +2. Add secrets: + - `DOCKER_USERNAME`: Your Gitea username + - `DOCKER_PASSWORD`: The access token you just created + +Note: Gitea's container registry is accessed at `https://your-gitea-instance/username/-/packages` + +#### For Other Custom Registries + +1. Obtain credentials from your registry administrator + +2. Add secrets: + - `DOCKER_USERNAME`: Your registry username + - `DOCKER_PASSWORD`: Your registry password or token + +### Configuring Cachix (Build Caching) + +Cachix is a Nix binary cache that dramatically speeds up builds by caching build artifacts. The workflow supports configurable Cachix settings. + +#### Environment Variables + +Configure these in the workflow's `env` section or as repository variables: + +| Variable | Description | Default Value | Example | +|--------------------+------------------------------------------------+---------------+--------------------| +| `CACHIX_NAME` | Name of the Cachix cache to use | `devenv` | `phundrak-dot-com` | +| `CACHIX_SKIP_PUSH` | Whether to skip pushing artifacts to the cache | `true` | `false` | + +#### Option 1: Pull from Public Cache Only + +If you only want to pull from a public cache (no pushing): + +1. Set environment variables in the workflow: + ```yaml + env: + CACHIX_NAME: devenv # or any public cache name + CACHIX_SKIP_PUSH: true + ``` + +2. No `CACHIX_AUTH_TOKEN` secret is needed + +This is useful when using public caches like `devenv` or `nix-community`. + +#### Option 2: Use Your Own Cache (Recommended for Faster Builds) + +To cache your own build artifacts for faster subsequent builds: + +1. Create a Cachix cache: + - Go to https://app.cachix.org + - Sign up and create a new cache (e.g., `your-project-name`) + - Free for public/open-source projects + +2. Get your auth token: + - In Cachix, go to your cache settings + - Find your auth token under "Auth tokens" + - Copy the token + +3. Add your cache configuration to `flake.nix`: + ```nix + nixConfig = { + extra-trusted-public-keys = [ + "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" + "your-cache-name.cachix.org-1:YOUR_PUBLIC_KEY_HERE" + ]; + extra-substituters = [ + "https://devenv.cachix.org" + "https://your-cache-name.cachix.org" + ]; + }; + ``` + +4. Configure the workflow: + - Edit `.github/workflows/publish-docker.yml`: + ```yaml + env: + CACHIX_NAME: your-cache-name + CACHIX_SKIP_PUSH: false + ``` + - Or set as repository variables in GitHub/Gitea + +5. Add your auth token as a secret: + - Go to repository `Settings` → `Secrets and variables` → `Actions` + - Add secret `CACHIX_AUTH_TOKEN` with your token + +#### Benefits of Using Your Own Cache + +- **Faster builds**: Subsequent builds reuse cached artifacts (Rust dependencies, compiled binaries) +- **Reduced CI time**: Can reduce build time from 10+ minutes to under 1 minute +- **Cost savings**: Less compute time means lower CI costs +- **Shared across branches**: All branches benefit from the same cache + +### Configuring the Docker Registry + +The target registry is set via the `DOCKER_REGISTRY` environment variable in the workflow file. To change it: + +1. Edit `.github/workflows/publish-docker.yml` +2. Modify the `env` section: + +```yaml +env: + DOCKER_REGISTRY: ghcr.io # Change to your registry (e.g., docker.io, labs.phundrak.com) + IMAGE_NAME: phundrak/phundrak-dot-com-backend +``` + +Or set it as a repository variable: +- Go to `Settings` → `Secrets and variables` → `Actions` → `Variables` tab +- Add `DOCKER_REGISTRY` with your desired registry URL + +### Image Naming + +Images are published with the name: `${DOCKER_REGISTRY}/${IMAGE_NAME}:${TAG}` + +For example: +- `labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest` +- `labs.phundrak.com/phundrak/phundrak-dot-com-backend:1.0.0` +- `labs.phundrak.com/phundrak/phundrak-dot-com-backend:develop` +- `labs.phundrak.com/phundrak/phundrak-dot-com-backend:pr12` + +### Local Testing + +To test the Docker image build locally: + +```bash +# Build the image with Nix +nix build .#backendDockerLatest + +# Load it into Docker +docker load < result + +# Run the container (image name comes from Cargo.toml package.name) +docker run -p 3100:3100 phundrak/phundrak-dot-com-backend:latest +``` + +### Troubleshooting + +#### Authentication Failures + +If you see authentication errors: +1. Verify your `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets are correct +2. For ghcr.io, ensure your PAT has the correct permissions +3. Check that the `DOCKER_REGISTRY` matches your credentials + +#### Build Failures + +If the Nix build fails: +1. Test the build locally first: `nix build .#backendDockerLatest` +2. Check the GitHub Actions logs for specific error messages +3. Ensure all dependencies in `flake.nix` are correctly specified + +#### Image Not Appearing in Registry + +1. Verify the workflow completed successfully in the Actions tab +2. Check that the registry URL is correct +3. For ghcr.io, images appear at: `https://github.com/users/USERNAME/packages/container/IMAGE_NAME` +4. Ensure your token has write permissions diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml new file mode 100644 index 0000000..2ad3e3c --- /dev/null +++ b/.github/workflows/publish-docker.yml @@ -0,0 +1,123 @@ +name: Publish Docker Images + +on: + push: + branches: + - main + - develop + tags: + - 'v*.*.*' + pull_request: + types: [opened, synchronize, reopened] + +env: + CACHIX_NAME: devenv + CACHIX_SKIP_PUSH: true + DOCKER_REGISTRY: labs.phundrak.com # Override in repository settings if needed + IMAGE_NAME: phundrak/phundrak-dot-com-backend + +jobs: + build-and-publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write # Required for pushing to Phundrak Labs registry + pull-requests: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v27 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: '${{ env.CACHIX_NAME }}' + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + skipPush: ${{ env.CACHIX_SKIP_PUSH }} + + - name: Build Docker image with Nix + run: | + echo "Building Docker image..." + nix build .#backendDockerLatest --accept-flake-config + + - name: Load Docker image + run: | + echo "Loading Docker image into Docker daemon..." + docker load < result + + - name: Log in to Docker Registry + run: | + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ env.DOCKER_REGISTRY }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + + - name: Determine tags and push images + run: | + set -euo pipefail + + REGISTRY="${{ env.DOCKER_REGISTRY }}" + IMAGE_NAME="${{ env.IMAGE_NAME }}" + + # The locally built image from Nix (name comes from Cargo.toml package.name) + LOCAL_IMAGE="phundrak/phundrak-dot-com-backend:latest" + + echo "Event: ${{ github.event_name }}" + echo "Ref: ${{ github.ref }}" + echo "Ref type: ${{ github.ref_type }}" + + # Determine which tags to push based on the event + if [[ "${{ github.event_name }}" == "push" && "${{ github.ref_type }}" == "tag" ]]; then + # Tag push on main branch → publish 'latest' and versioned tag + echo "Tag push detected" + TAG_VERSION="${{ github.ref_name }}" + # Remove 'v' prefix if present (v1.0.0 → 1.0.0) + TAG_VERSION="${TAG_VERSION#v}" + + echo "Tagging and pushing: ${REGISTRY}/${IMAGE_NAME}:latest" + docker tag "${LOCAL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:latest" + docker push "${REGISTRY}/${IMAGE_NAME}:latest" + + echo "Tagging and pushing: ${REGISTRY}/${IMAGE_NAME}:${TAG_VERSION}" + docker tag "${LOCAL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:${TAG_VERSION}" + docker push "${REGISTRY}/${IMAGE_NAME}:${TAG_VERSION}" + + elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/develop" ]]; then + # Push on develop branch → publish 'develop' tag + echo "Push to develop branch detected" + + echo "Tagging and pushing: ${REGISTRY}/${IMAGE_NAME}:develop" + docker tag "${LOCAL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:develop" + docker push "${REGISTRY}/${IMAGE_NAME}:develop" + + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + # Pull request → publish 'pr' tag + echo "Pull request detected" + PR_NUMBER="${{ github.event.pull_request.number }}" + + echo "Tagging and pushing: ${REGISTRY}/${IMAGE_NAME}:pr${PR_NUMBER}" + docker tag "${LOCAL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:pr${PR_NUMBER}" + docker push "${REGISTRY}/${IMAGE_NAME}:pr${PR_NUMBER}" + + elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then + # Push to main branch (not a tag) → publish 'latest' + echo "Push to main branch detected" + + echo "Tagging and pushing: ${REGISTRY}/${IMAGE_NAME}:latest" + docker tag "${LOCAL_IMAGE}" "${REGISTRY}/${IMAGE_NAME}:latest" + docker push "${REGISTRY}/${IMAGE_NAME}:latest" + + else + echo "Unknown event or ref, skipping push" + exit 1 + fi + + - name: Log out from Docker Registry + if: always() + run: docker logout ${{ env.DOCKER_REGISTRY }} + + - name: Image published successfully + run: | + echo "✅ Docker image(s) published successfully to ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}" diff --git a/backend/README.md b/backend/README.md index e1bcea2..87c34e4 100644 --- a/backend/README.md +++ b/backend/README.md @@ -80,35 +80,68 @@ To disable rate limiting, set `rate_limit.enabled: false` in your configuration. ### Prerequisites +**Option 1: Native Development** - Rust (latest stable version recommended) - Cargo (comes with Rust) +**Option 2: Nix Development (Recommended)** +- [Nix](https://nixos.org/download) with flakes enabled +- All dependencies managed automatically + ### Running the Server -To start the development server: - +**With Cargo:** ```bash cargo run ``` +**With Nix development shell:** +```bash +nix develop .#backend +cargo run +``` + The server will start on the configured port (default: 3100). ### Building -For development builds: +**With Cargo:** +For development builds: ```bash cargo build ``` For optimized production builds: - ```bash cargo build --release ``` The compiled binary will be at `target/release/backend`. +**With Nix:** + +Build the backend binary: +```bash +nix build .#backend +# Binary available at: ./result/bin/backend +``` + +Build Docker images: +```bash +# Build versioned Docker image (e.g., 0.1.0) +nix build .#backendDocker + +# Build latest Docker image +nix build .#backendDockerLatest + +# Load into Docker +docker load < result +# Image will be available as: localhost/phundrak/backend-rust:latest +``` + +The Nix build ensures reproducible builds with all dependencies pinned. + ## Testing Run all tests: @@ -266,6 +299,126 @@ The contact form supports multiple SMTP configurations: The `SmtpTransport` is built dynamically from `EmailSettings` based on TLS/STARTTLS configuration. +## Docker Deployment + +### Using Pre-built Images + +Docker images are automatically built and published via GitHub Actions to the configured container registry. + +Pull and run the latest image: +```bash +# Pull from Phundrak Labs (labs.phundrak.com) +docker pull labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest + +# Run the container +docker run -d \ + --name phundrak-backend \ + -p 3100:3100 \ + -e APP__APPLICATION__PORT=3100 \ + -e APP__EMAIL__HOST=smtp.example.com \ + -e APP__EMAIL__PORT=587 \ + -e APP__EMAIL__USER=user@example.com \ + -e APP__EMAIL__PASSWORD=your_password \ + -e APP__EMAIL__FROM="Contact Form " \ + -e APP__EMAIL__RECIPIENT="Admin " \ + labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest +``` + +### Available Image Tags + +The following tags are automatically published: + +- `latest` - Latest stable release (from tagged commits on `main`) +- `` - Specific version (e.g., `1.0.0`, from tagged commits like `v1.0.0`) +- `develop` - Latest development build (from `develop` branch) +- `pr` - Pull request preview builds (e.g., `pr12`) + +### Building Images Locally + +Build with Nix (recommended for reproducibility): +```bash +nix build .#backendDockerLatest +docker load < result +docker run -p 3100:3100 localhost/phundrak/backend-rust:latest +``` + +Build with Docker directly: +```bash +# Note: This requires a Dockerfile (not included in this project) +# Use Nix builds for containerization +``` + +### Docker Compose Example + +```yaml +version: '3.8' + +services: + backend: + image: labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest + ports: + - "3100:3100" + environment: + APP__APPLICATION__PORT: 3100 + APP__EMAIL__HOST: smtp.example.com + APP__EMAIL__PORT: 587 + APP__EMAIL__USER: ${SMTP_USER} + APP__EMAIL__PASSWORD: ${SMTP_PASSWORD} + APP__EMAIL__FROM: "Contact Form " + APP__EMAIL__RECIPIENT: "Admin " + APP__EMAIL__STARTTLS: true + APP__RATE_LIMIT__ENABLED: true + APP__RATE_LIMIT__BURST_SIZE: 10 + APP__RATE_LIMIT__PER_SECONDS: 60 + restart: unless-stopped +``` + +## CI/CD Pipeline + +### Automated Docker Publishing + +GitHub Actions automatically builds and publishes Docker images based on repository events: + +| Event Type | Trigger | Published Tags | +|-----------------|------------------------------|-------------------------------| +| Tag push | `v*.*.*` tag on `main` | `latest`, `` | +| Branch push | Push to `develop` | `develop` | +| Pull request | PR opened/updated | `pr` | +| Branch push | Push to `main` (no tag) | `latest` | + +### Workflow Details + +The CI/CD pipeline (`.github/workflows/publish-docker.yml`): + +1. **Checks out the repository** +2. **Installs Nix** with flakes enabled +3. **Builds the Docker image** using Nix for reproducibility +4. **Authenticates** with the configured Docker registry +5. **Tags and pushes** images based on the event type + +### Registry Configuration + +Images are published to the registry specified by the `DOCKER_REGISTRY` environment variable in the workflow (default: `labs.phundrak.com`). + +To use the published images, authenticate with the registry: + +```bash +# For Phundrak Labs (labs.phundrak.com) +echo $GITHUB_TOKEN | docker login labs.phundrak.com -u USERNAME --password-stdin + +# Pull the image +docker pull labs.phundrak.com/phundrak/phundrak-dot-com-backend:latest +``` + +### Required Secrets + +The workflow requires these GitHub secrets: +- `DOCKER_USERNAME` - Registry username +- `DOCKER_PASSWORD` - Registry password or token +- `CACHIX_AUTH_TOKEN` - (Optional) For Nix build caching + +See [.github/workflows/README.md](../.github/workflows/README.md) for detailed setup instructions. + ## License AGPL-3.0-only - See the root repository for full license information. diff --git a/backend/nix/shell.nix b/backend/nix/shell.nix index 203a2ed..6c8fc58 100644 --- a/backend/nix/shell.nix +++ b/backend/nix/shell.nix @@ -34,8 +34,8 @@ in cargo-watch flyctl just + marksman tombi # TOML lsp server - vscode-langservers-extracted ]; services.mailpit = { diff --git a/flake.nix b/flake.nix index b8542bd..baecf74 100644 --- a/flake.nix +++ b/flake.nix @@ -17,8 +17,14 @@ }; nixConfig = { - extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; - extra-substituters = "https://devenv.cachix.org"; + extra-trusted-public-keys = [ + "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" + "phundrak-dot-com.cachix.org-1:c02/xlCknJIDoaQPUzEWSJHPoXcmIXYzCa+hVRhbDgE=" + ]; + extra-substituters = [ + "https://devenv.cachix.org" + "https://phundrak-dot-com.cachix.org" + ]; }; outputs = { diff --git a/frontend/shell.nix b/frontend/shell.nix index ab051fa..b390b75 100644 --- a/frontend/shell.nix +++ b/frontend/shell.nix @@ -18,6 +18,7 @@ inputs.devenv.lib.mkShell { packages = with pkgs; [ # LSP + marksman nodePackages."@tailwindcss/language-server" vscode-langservers-extracted vue-language-server