No description
Find a file
Christopher Mühl b094952455
Some checks failed
Build and Push static-server Image / build (push) Failing after 1m45s
feat: add push-nix-cache action, wire into deploy-static-site and build-static-server
Extracts sign+push logic into a reusable push-nix-cache action.
Both the site deploy and the image build now use it.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 13:38:06 +01:00
.forgejo/workflows feat: add push-nix-cache action, wire into deploy-static-site and build-static-server 2026-02-18 13:38:06 +01:00
deploy-nix-site fix: forward NOMAD_TOKEN to $GITHUB_ENV for shell access 2026-02-18 01:22:08 +01:00
deploy-site feat: add NOMAD_TOKEN support and static-sites namespace 2026-02-18 00:18:34 +01:00
deploy-static-site feat: add push-nix-cache action, wire into deploy-static-site and build-static-server 2026-02-18 13:38:06 +01:00
push-nix-cache feat: add push-nix-cache action, wire into deploy-static-site and build-static-server 2026-02-18 13:38:06 +01:00
site-lib feat: add deploy-static-site action, site-lib, images; remove deploy-oci-site 2026-02-18 11:27:27 +01:00
.gitignore Initial commit: deploy-site action 2026-02-16 11:05:35 +01:00
README.md feat: add NOMAD_TOKEN support and static-sites namespace 2026-02-18 00:18:34 +01:00

CI Actions

Reusable Forgejo/Gitea actions for toph's infrastructure.

Available Actions

Deploy a static site built with Nix flake to S3 and Nomad. Provides proper isolation and reproducibility.

Requirements:

  • Repository must have a flake.nix with a default package output
  • Runner label: nix (uses docker://nixos/nix:latest)

Usage:

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: nix
    steps:
      - uses: actions/checkout@v4

      - uses: https://git.toph.so/toph/ci-actions/deploy-nix-site@main
        with:
          site-name: mysite
          traefik-rule: Host(`mysite.example.com`)
        env:
          S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
          S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
          NIX_SIGNING_KEY: ${{ secrets.NIX_SIGNING_KEY }}
          NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}

Inputs:

  • site-name (required): Site identifier
  • traefik-rule (required): Traefik routing rule
  • flake-output (optional): Nix flake output, defaults to .#
  • s3-endpoint (optional): S3 endpoint, defaults to https://s3.toph.so

Example flake.nix:

{
  description = "My static site";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      packages.${system}.default = pkgs.stdenv.mkDerivation {
        name = "my-site";
        src = ./.;

        buildInputs = [ pkgs.nodejs_20 ];

        buildPhase = ''
          npm ci
          npm run build
        '';

        installPhase = ''
          cp -r dist $out
        '';
      };

      devShells.${system}.default = pkgs.mkShell {
        packages = [ pkgs.nodejs_20 pkgs.vite ];
        shellHook = "echo 'Run: vite'";
      };
    };
}

deploy-site

Deploy a static site to production via S3 and Nomad (non-Nix).

Usage:

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4

      - name: Build site (if needed)
        run: npm run build

      - name: Deploy
        uses: https://git.toph.so/toph/ci-actions/deploy-site@main
        with:
          site-name: mysite
          traefik-rule: Host(`mysite.example.com`)
          source-dir: dist  # optional, defaults to current dir
        env:
          S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
          S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
          NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}

Inputs:

  • site-name (required): Site identifier used as Nomad service name
  • traefik-rule (required): Traefik routing rule (see examples below)
  • source-dir (optional): Directory with built files, defaults to .
  • s3-endpoint (optional): S3 endpoint, defaults to https://s3.toph.so

Environment variables:

  • S3_ACCESS_KEY: S3 access key (set via Forgejo secrets)
  • S3_SECRET_KEY: S3 secret key (set via Forgejo secrets)
  • NOMAD_TOKEN: Nomad ACL token for the static-sites namespace (set via Forgejo secrets, auto-synced by nomad-acl-forgejo-sync)

What it does:

  1. Packages the site directory as a tarball
  2. Uploads to S3 at s3://artifacts/<commit-sha>.tar.gz
  3. Sets public-read ACL on the artifact
  4. Dispatches Nomad job to deploy the site
  5. Site becomes available via the specified Traefik rule with Let's Encrypt SSL

Infrastructure handles:

  • Docker containers (static-web-server)
  • Resource limits (100 CPU, 64MB RAM)
  • Traefik routing & Let's Encrypt SSL
  • Automatic restarts

Traefik Rule Examples

Single domain:

traefik-rule: Host(`example.com`)

Multiple domains (with www):

traefik-rule: Host(`example.com`) || Host(`www.example.com`)

Subdomain:

traefik-rule: Host(`blog.example.com`)

toph.so domain:

traefik-rule: Host(`mysite.toph.so`)

Path-based routing:

traefik-rule: Host(`example.com`) && PathPrefix(`/docs`)

Setup

1. Deploy S3 service (SeaweedFS)

cd /srv/infra
nix run .#bosun -- run s3

2. Generate binary cache signing keys

nix-store --generate-binary-cache-key cache.toph.so cache-priv-key.pem cache-pub-key.pem

# Public key (add to nix.conf trusted-public-keys on hosts that will fetch):
cat cache-pub-key.pem
# Example: cache.toph.so:9zFo64TPnxaQeyFM6NS9ou2Fd8OQv4Ia+MuLMjLBYjY=

# Private key (store in Forgejo secrets):
cat cache-priv-key.pem
# Keep this secret!

3. Create S3 buckets

# Configure AWS CLI
export AWS_ACCESS_KEY_ID=<your-access-key>
export AWS_SECRET_ACCESS_KEY=<your-secret-key>
export AWS_ENDPOINT_URL=https://s3.toph.so
export AWS_EC2_METADATA_DISABLED=true

# Create binary cache bucket
aws s3 mb s3://nix-cache

# (Optional) Create artifacts bucket for non-Nix deployments
aws s3 mb s3://artifacts
aws s3api put-bucket-acl --bucket artifacts --acl public-read

4. Configure alvin to trust the binary cache

Add to /srv/infra/hosts/alvin/default.nix:

nix.settings = {
  substituters = [
    "https://cache.nixos.org"
    "s3://nix-cache?endpoint=s3.toph.so&scheme=https"
  ];
  trusted-public-keys = [
    "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
    "cache.toph.so:9zFo64TPnxaQeyFM6NS9ou2Fd8OQv4Ia+MuLMjLBYjY="  # Your public key
  ];
};

Then deploy: sudo nixos-rebuild switch --flake .#alvin

5. Add Forgejo secrets

In your repository settings (or organization settings for global secrets):

  • S3_ACCESS_KEY: S3 access key
  • S3_SECRET_KEY: S3 secret key
  • NIX_SIGNING_KEY: Contents of cache-priv-key.pem
  • NOMAD_TOKEN: Auto-synced by nomad-acl-forgejo-sync on alvin (or set manually from cat /var/lib/nomad-acl/ci.token)

6. Configure SSH access from runner to alvin

The runner needs to pull store paths to alvin's /nix/store. Add the runner's SSH key to alvin or use an agent socket mount.

Examples

Simple HTML site

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4

      - uses: https://git.toph.so/toph/ci-actions/deploy-site@main
        with:
          site-name: mysite
          traefik-rule: Host(`mysite.toph.so`)
          source-dir: .  # HTML files in repo root
        env:
          S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
          S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
          NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}

Node.js/Vite site with custom domain

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install & build
        run: |
          npm ci
          npm run build

      - uses: https://git.toph.so/toph/ci-actions/deploy-site@main
        with:
          site-name: myapp
          traefik-rule: Host(`app.example.com`) || Host(`www.app.example.com`)
          source-dir: dist
        env:
          S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
          S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
          NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}

Hugo site

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true  # For Hugo themes

      - name: Setup Hugo
        run: |
          wget https://github.com/gohugoio/hugo/releases/download/v0.121.0/hugo_extended_0.121.0_linux-amd64.tar.gz
          tar xzf hugo_extended_0.121.0_linux-amd64.tar.gz
          sudo mv hugo /usr/local/bin/

      - name: Build
        run: hugo --minify

      - uses: https://git.toph.so/toph/ci-actions/deploy-site@main
        with:
          site-name: myblog
          traefik-rule: Host(`blog.example.com`)
          source-dir: public
        env:
          S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
          S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
          NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}

S3 Access

Troubleshooting

AWS CLI not found

The action automatically installs AWS CLI if not present on the runner.

Access denied during upload

Check that:

  1. S3_ACCESS_KEY and S3_SECRET_KEY are set in Forgejo secrets
  2. The credentials match those in the S3 Nomad job config
  3. S3 service is running: nomad job status s3

Site not accessible

Check:

  1. Nomad allocation is running: nomad job status static-site
  2. Traefik has picked up the service: check Traefik dashboard
  3. DNS resolves: dig <site-name>.toph.so
  4. Certificate is valid: curl -v https://<site-name>.toph.so

Development

To add a new action:

mkdir -p new-action
cd new-action
vim action.yaml

Then commit and push to main.

License

MIT