name: Deploy Nix Site description: Deploy static site built with Nix flake to S3 and Nomad inputs: site-name: description: 'Site identifier (used as service name in Nomad)' required: true traefik-rule: description: 'Traefik routing rule (e.g., Host(`example.com`) or Host(`example.com`) || Host(`www.example.com`))' required: true flake-output: description: 'Nix flake output to build (e.g., .#packages.x86_64-linux.default or .#)' required: false default: '.#' s3-endpoint: description: 'S3 endpoint' required: false default: 'https://s3.toph.so' cache-name: description: 'Attic cache name' required: false default: 'ci' runs: using: composite steps: - name: Set environment shell: bash run: | # Set Nomad connection echo "NOMAD_ADDR=http://alvin:4646" >> $GITHUB_ENV echo "NOMAD_TOKEN=${{ env.NOMAD_TOKEN }}" >> $GITHUB_ENV - name: Build site with Nix shell: bash run: | # Build with Attic cache as substituter (pre-configured in nix.conf) nix build ${{ inputs.flake-output }} --print-build-logs # Get the store path STORE_PATH=$(readlink -f result) STORE_HASH=$(basename "$STORE_PATH") echo "STORE_PATH=$STORE_PATH" >> $GITHUB_ENV echo "STORE_HASH=$STORE_HASH" >> $GITHUB_ENV echo "📦 Built: $STORE_PATH" - name: Push to Attic cache shell: bash env: ATTIC_TOKEN: ${{ env.ATTIC_TOKEN }} run: | # Configure attic client with explicit token (not relying on mounted config) mkdir -p ~/.config/attic cat > ~/.config/attic/config.toml < /tmp/deploy-${{ inputs.site-name }}.nomad.json <<'NOMAD_EOF' { "Job": { "ID": "${{ inputs.site-name }}", "Name": "${{ inputs.site-name }}", "Namespace": "static-sites", "Type": "service", "Datacenters": ["contabo"], "Constraints": [{ "LTarget": "${node.unique.name}", "RTarget": "alvin", "Operand": "=" }], "TaskGroups": [{ "Name": "web", "Count": 1, "Networks": [{ "Mode": "bridge", "DynamicPorts": [{ "Label": "http", "To": 8080 }] }], "Services": [{ "Name": "${{ inputs.site-name }}", "PortLabel": "http", "Provider": "nomad", "Tags": [ "traefik.enable=true", "traefik.http.routers.${{ inputs.site-name }}.rule=${{ inputs.traefik-rule }}", "traefik.http.routers.${{ inputs.site-name }}.entrypoints=websecure", "traefik.http.routers.${{ inputs.site-name }}.tls.certresolver=letsencrypt" ] }], "Volumes": { "site-data": { "Type": "host", "Source": "site-data", "ReadOnly": false } }, "Tasks": [ { "Name": "fetch", "Driver": "docker", "Lifecycle": { "Hook": "prestart", "Sidecar": false }, "Config": { "image": "nixos/nix:latest", "command": "/bin/sh", "args": [ "-c", "nix copy --from 'https://cache.toph.so/${{ inputs.cache-name }}' '${STORE_PATH}' && cp -r ${STORE_PATH}/* /alloc/data/" ] }, "Env": { "STORE_PATH": "${{ env.STORE_PATH }}" }, "VolumeMounts": [{ "Volume": "site-data", "Destination": "/alloc/data" }], "Resources": { "CPU": 200, "MemoryMB": 256 } }, { "Name": "server", "Driver": "docker", "Config": { "image": "joseluisq/static-web-server:2", "ports": ["http"] }, "Env": { "SERVER_ROOT": "/var/www", "SERVER_LOG_LEVEL": "info" }, "VolumeMounts": [{ "Volume": "site-data", "Destination": "/var/www", "ReadOnly": true }], "Resources": { "CPU": 100, "MemoryMB": 64 } } ] }] } } NOMAD_EOF nomad job run -json /tmp/deploy-${{ inputs.site-name }}.nomad.json - name: Deployment summary shell: bash run: | echo "✅ Deployed ${{ inputs.site-name }}" echo "📋 Traefik rule: ${{ inputs.traefik-rule }}" echo "📦 Store path: $STORE_PATH"