YAML ends a block scalar when it sees content less indented than the first content line. The JSON heredoc at column 0 caused the parser to bail out mid-block. Indenting to 8 spaces keeps it inside the run: | scalar; YAML strips that indentation before handing the script to the shell, so the NOMAD_EOF terminator lands at column 0 as required. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
185 lines
6.2 KiB
YAML
185 lines
6.2 KiB
YAML
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'
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- name: Install tools
|
|
shell: bash
|
|
run: |
|
|
# Install AWS CLI
|
|
nix profile install nixpkgs#awscli2
|
|
|
|
# Set Nomad address
|
|
echo "NOMAD_ADDR=http://alvin:4646" >> $GITHUB_ENV
|
|
|
|
- name: Build site with Nix
|
|
shell: bash
|
|
run: |
|
|
# Configure S3 as substituter to pull cached dependencies
|
|
export AWS_ACCESS_KEY_ID="${{ env.S3_ACCESS_KEY }}"
|
|
export AWS_SECRET_ACCESS_KEY="${{ env.S3_SECRET_KEY }}"
|
|
|
|
# Build with S3 cache as substituter (fetches cached deps)
|
|
nix build ${{ inputs.flake-output }} \
|
|
--print-build-logs \
|
|
--option substituters "https://cache.nixos.org s3://nix-cache?endpoint=${{ inputs.s3-endpoint }}&scheme=https" \
|
|
--option trusted-public-keys "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= $(cat /tmp/cache-pub-key.pem 2>/dev/null || echo '')"
|
|
|
|
# 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 binary cache
|
|
shell: bash
|
|
run: |
|
|
# Configure S3 binary cache
|
|
export AWS_ACCESS_KEY_ID="${{ env.S3_ACCESS_KEY }}"
|
|
export AWS_SECRET_ACCESS_KEY="${{ env.S3_SECRET_KEY }}"
|
|
|
|
# Write signing key to temporary file
|
|
echo "${{ env.NIX_SIGNING_KEY }}" > /tmp/nix-signing-key.pem
|
|
chmod 600 /tmp/nix-signing-key.pem
|
|
|
|
# Push entire closure (derivation + all dependencies) to cache
|
|
nix copy \
|
|
--to "s3://nix-cache?endpoint=${{ inputs.s3-endpoint }}&scheme=https&secret-key=/tmp/nix-signing-key.pem" \
|
|
--derivation \
|
|
"$STORE_PATH"
|
|
|
|
# Clean up key file
|
|
rm -f /tmp/nix-signing-key.pem
|
|
|
|
echo "✅ Pushed to binary cache: $STORE_HASH (with all dependencies)"
|
|
|
|
- name: Deploy via Nomad
|
|
shell: bash
|
|
run: |
|
|
cat > /tmp/deploy-${{ inputs.site-name }}.nomad.json <<'NOMAD_EOF'
|
|
{
|
|
"Job": {
|
|
"ID": "${{ inputs.site-name }}",
|
|
"Name": "${{ inputs.site-name }}",
|
|
"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 's3://nix-cache?endpoint=${{ inputs.s3-endpoint }}&scheme=https' '${STORE_PATH}' && cp -r ${STORE_PATH}/* /alloc/data/"
|
|
]
|
|
},
|
|
"Env": {
|
|
"AWS_ACCESS_KEY_ID": "${{ env.S3_ACCESS_KEY }}",
|
|
"AWS_SECRET_ACCESS_KEY": "${{ env.S3_SECRET_KEY }}",
|
|
"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"
|