refactor: replace generate-job.py with nomad-job.nix, add flake-output input

Use nix eval --raw --impure + builtins.getEnv instead of Python for
Nomad job JSON generation. Add flake-output input (default: default)
so projects can build non-default outputs like docs.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Christopher Mühl 2026-02-18 13:16:08 +01:00
parent 95bf5517f7
commit 4af132296e
No known key found for this signature in database
GPG key ID: 925AC7D69955293F
3 changed files with 96 additions and 110 deletions

View file

@ -11,6 +11,11 @@ inputs:
description: 'Domain the site is served at (e.g. toph.so)' description: 'Domain the site is served at (e.g. toph.so)'
required: true required: true
flake-output:
description: 'Flake output to build (e.g. default, docs)'
required: false
default: 'default'
nomad-addr: nomad-addr:
description: 'Nomad API address' description: 'Nomad API address'
required: false required: false
@ -50,7 +55,7 @@ runs:
- name: Build site - name: Build site
shell: bash shell: bash
run: nix build .#default --out-link result-site run: nix build ".#${{ inputs.flake-output }}" --out-link result-site
- name: Sign and push Nix closure to S3 cache - name: Sign and push Nix closure to S3 cache
shell: bash shell: bash
@ -76,10 +81,13 @@ runs:
- name: Deploy Nomad job - name: Deploy Nomad job
shell: bash shell: bash
run: | run: |
python3 "${{ github.action_path }}/generate-job.py" | nomad job run -json - nix eval --raw --impure \
--expr "import ${{ github.action_path }}/nomad-job.nix" \
| nomad job run -json -
env: env:
NOMAD_ADDR: ${{ inputs.nomad-addr }} NOMAD_ADDR: ${{ inputs.nomad-addr }}
DOMAIN: ${{ inputs.domain }} DOMAIN: ${{ inputs.domain }}
SITE_HASH: ${{ env.SITE_HASH }}
SERVER_IMAGE: ${{ inputs.server-image }} SERVER_IMAGE: ${{ inputs.server-image }}
DATACENTER: ${{ inputs.datacenter }} DATACENTER: ${{ inputs.datacenter }}
S3_BUCKET: ${{ inputs.s3-bucket }} S3_BUCKET: ${{ inputs.s3-bucket }}

View file

@ -1,108 +0,0 @@
#!/usr/bin/env python3
"""
Generate a Nomad job JSON for a static site deployment.
Reads from environment variables, prints JSON to stdout.
Pipe to: nomad job run -json -
"""
import json
import os
import sys
domain = os.environ["DOMAIN"]
site_hash = os.environ["SITE_HASH"]
server_image = os.environ.get("SERVER_IMAGE", "registry.toph.so/static-server:latest")
datacenter = os.environ.get("DATACENTER", "contabo")
s3_bucket = os.environ["S3_BUCKET"]
job_id = "site-" + domain.replace(".", "-")
startup_cmd = (
f"mkdir -p /var/www && "
f"aws s3 cp s3://{s3_bucket}/sites/{domain}/{site_hash}.tar.gz - "
f"| tar xz -C /var/www/ && "
f"exec static-web-server --port 8080 --root /var/www"
)
nomad_template_data = (
'{{ with nomadVar "static-sites/s3" }}'
"AWS_ACCESS_KEY_ID={{ .access_key }}\n"
"AWS_SECRET_ACCESS_KEY={{ .secret_key }}\n"
"AWS_ENDPOINT_URL={{ .endpoint }}\n"
"{{ end }}"
)
job = {
"Job": {
"ID": job_id,
"Name": job_id,
"Namespace": "static-sites",
"Type": "service",
"Datacenters": [datacenter],
"Update": {
"MinHealthyTime": 5000000000, # 5s in nanoseconds
"HealthyDeadline": 60000000000, # 60s in nanoseconds
"MaxParallel": 1,
},
"TaskGroups": [
{
"Name": "site",
"Count": 1,
"Networks": [
{
"DynamicPorts": [
{"Label": "http", "To": 8080}
]
}
],
"Services": [
{
"Name": job_id,
"Provider": "nomad",
"PortLabel": "http",
"Tags": [
"traefik.enable=true",
f"traefik.http.routers.{job_id}.rule=Host(`{domain}`)",
f"traefik.http.routers.{job_id}.entrypoints=websecure",
f"traefik.http.routers.{job_id}.tls.certresolver=letsencrypt",
],
"Checks": [
{
"Type": "http",
"Path": "/",
"Interval": 30000000000, # 30s
"Timeout": 5000000000, # 5s
}
],
}
],
"Tasks": [
{
"Name": "server",
"Driver": "docker",
"Config": {
"image": server_image,
"command": "/bin/bash",
"args": ["-c", startup_cmd],
"ports": ["http"],
},
"Templates": [
{
"EmbeddedTmpl": nomad_template_data,
"DestPath": "secrets/s3.env",
"Envvars": True,
}
],
"Resources": {
"CPU": 100,
"MemoryMB": 128,
},
}
],
}
],
}
}
json.dump(job, sys.stdout, indent=2)
print()

View file

@ -0,0 +1,86 @@
let
domain = builtins.getEnv "DOMAIN";
siteHash = builtins.getEnv "SITE_HASH";
serverImage = builtins.getEnv "SERVER_IMAGE";
datacenter = builtins.getEnv "DATACENTER";
s3Bucket = builtins.getEnv "S3_BUCKET";
jobId = "site-" + builtins.replaceStrings [ "." ] [ "-" ] domain;
startupCmd =
"mkdir -p /var/www && " +
"aws s3 cp s3://${s3Bucket}/sites/${domain}/${siteHash}.tar.gz - " +
"| tar xz -C /var/www/ && " +
"exec static-web-server --port 8080 --root /var/www";
templateData =
"{{ with nomadVar \"static-sites/s3\" }}" +
"AWS_ACCESS_KEY_ID={{ .access_key }}\n" +
"AWS_SECRET_ACCESS_KEY={{ .secret_key }}\n" +
"AWS_ENDPOINT_URL={{ .endpoint }}\n" +
"{{ end }}";
job = {
Job = {
ID = jobId;
Name = jobId;
Namespace = "static-sites";
Type = "service";
Datacenters = [ datacenter ];
Update = {
MinHealthyTime = 5000000000;
HealthyDeadline = 60000000000;
MaxParallel = 1;
};
TaskGroups = [
{
Name = "site";
Count = 1;
Networks = [
{ DynamicPorts = [ { Label = "http"; To = 8080; } ]; }
];
Services = [
{
Name = jobId;
Provider = "nomad";
PortLabel = "http";
Tags = [
"traefik.enable=true"
"traefik.http.routers.${jobId}.rule=Host(`${domain}`)"
"traefik.http.routers.${jobId}.entrypoints=websecure"
"traefik.http.routers.${jobId}.tls.certresolver=letsencrypt"
];
Checks = [
{ Type = "http"; Path = "/"; Interval = 30000000000; Timeout = 5000000000; }
];
}
];
Tasks = [
{
Name = "server";
Driver = "docker";
Config = {
image = serverImage;
command = "/bin/bash";
args = [ "-c" startupCmd ];
ports = [ "http" ];
};
Templates = [
{
EmbeddedTmpl = templateData;
DestPath = "secrets/s3.env";
Envvars = true;
}
];
Resources = {
CPU = 100;
MemoryMB = 128;
};
}
];
}
];
};
};
in
builtins.toJSON job