# CI Actions Reusable Forgejo/Gitea actions for toph's infrastructure. ## Available Actions ### `deploy-nix-site` (Recommended) Deploy a static site built with Nix flake to Attic binary cache and Nomad. Provides proper isolation and reproducibility. **Requirements:** - Repository must have a `flake.nix` with a default package output - Runner label: `nix` (uses `docker://registry.toph.so/nix-runner:latest`) **Usage:** ```yaml 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: 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 `.#` - `cache-name` (optional): Attic cache name, defaults to `ci` - `s3-endpoint` (optional): S3 endpoint (for deployment artifacts), defaults to `https://s3.toph.so` **Environment variables:** - `ATTIC_TOKEN`: Attic CI token for pushing to cache (set via Forgejo secrets) - `NOMAD_TOKEN`: Nomad ACL token for the `static-sites` namespace **Notes:** - Build artifacts are automatically pushed to the Attic binary cache at `cache.toph.so/ci` - Uses the `ci` cache by default (lower trust, shorter retention) - Pass `cache-name: toph` to use the main cache for trusted repos **Example flake.nix:** ```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:** ```yaml 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/.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:** ```yaml traefik-rule: Host(`example.com`) ``` **Multiple domains (with www):** ```yaml traefik-rule: Host(`example.com`) || Host(`www.example.com`) ``` **Subdomain:** ```yaml traefik-rule: Host(`blog.example.com`) ``` **toph.so domain:** ```yaml traefik-rule: Host(`mysite.toph.so`) ``` **Path-based routing:** ```yaml traefik-rule: Host(`example.com`) && PathPrefix(`/docs`) ``` ## Setup ### 1. Deploy S3 service (SeaweedFS) ```bash cd /srv/infra nix run .#bosun -- run s3 ``` ### 2. Bootstrap Attic and create CI cache Deploy Attic and run the bootstrap script: ```bash cd /srv/infra nix run .#bosun -- run attic nix run .#attic-bootstrap ``` This creates: - `toph` cache: High-trust cache for manual/trusted builds (priority 40, 180d retention) - `ci` cache: Lower-trust cache for CI builds (priority 30, 90d retention) ### 3. Get the CI token ```bash nomad var get nomad/jobs/attic/tokens | jq -r '.ci' ``` ### 4. Add Forgejo secrets **Security Model:** - The `ATTIC_TOKEN` must be explicitly provided as a Forgejo secret - This prevents untrusted repositories from pushing arbitrary binaries to your cache - Only repositories with the secret configured can push to the cache **Per-repository secrets** (Settings → Secrets): - `ATTIC_TOKEN`: CI token from step 3 (for `ci` cache access) - `NOMAD_TOKEN`: Auto-synced by `nomad-acl-forgejo-sync` on alvin **Organization-wide secrets** (for trusted internal repos): Set these at the organization level to avoid configuring each repo individually. **Cache selection:** - Use `cache-name: ci` (default) for untrusted/external repos → 90-day retention - Use `cache-name: toph` for trusted repos → 180-day retention, requires root token ## Examples ### Simple HTML site ```yaml 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 ```yaml 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 ```yaml 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 - **API endpoint**: https://s3.toph.so - **Console**: https://s3-console.toph.so (restricted to Tailscale) - **Credentials**: Stored in Nomad variables at `nomad/jobs/s3` - **Backend**: SeaweedFS (S3-compatible) ## 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 .toph.so` 4. Certificate is valid: `curl -v https://.toph.so` ## Development To add a new action: ```bash mkdir -p new-action cd new-action vim action.yaml ``` Then commit and push to `main`. ## License MIT