1{2 config,3 lib,4 withSystem,5 ...6}:7let8 inherit (lib)9 mkOption10 mkIf11 types12 concatStringsSep13 ;14 cfg = config.hercules-ci.post-comment;15in16{17 options.hercules-ci.post-comment = {18 enable = mkOption {19 type = types.bool;20 default = false;21 description = ''22 Whether to post a GitHub commit comment for every commit Hercules CI runs on.23 '';24 };25 script = mkOption {26 type = types.lines;27 description = ''28 Bash snippet that writes the comment body to `$out`. Runs as part of the effect29 (after secrets are loaded), so the helpers below are in scope:3031 - `nixTar <store-path>` — prints a signed deltarocks URL that streams the path32 as a tar.zst, realised through the configured caches.33 - `nixRender <store-path>` — prints a signed deltarocks URL that renders the34 path's AsciiDoc content as HTML.35 '';36 example = lib.literalExpression ''37 '''38 {39 echo "Render: $(nixRender ''${benchmarks})"40 echo "Tar: $(nixTar ''${binary})"41 } > $out42 '''43 '';44 };45 system = mkOption {46 type = types.str;47 default = "x86_64-linux";48 description = ''49 System on which the effect runs.50 '';51 };52 baseUrl = mkOption {53 type = types.str;54 default = "https://delta.rocks";55 description = ''56 Base URL of the deltarocks signing service.57 '';58 };59 caches = mkOption {60 type = types.listOf types.str;61 default = [ ];62 example = [ "jrsonnet.cachix.org" ];63 description = ''64 Cache hosts the signing service should use as substituters when realising the65 signed store path.66 '';67 };68 signSecret = mkOption {69 type = types.str;70 default = "deltarocks-nix-sign";71 description = ''72 Name of the Hercules CI agent secret that holds the deltarocks signing key.73 Its `data` must have a field named `ogSecret`.74 '';75 };76 };7778 config = mkIf cfg.enable {79 herculesCI =80 { config, ... }:81 {82 onPush.default.outputs.effects.post-comment = withSystem cfg.system (83 { pkgs, hci-effects, ... }:84 hci-effects.mkEffect {85 name = "post-comment";86 inputs = [ pkgs.openssl ];87 secretsMap = {88 token = {89 type = "GitToken";90 };91 ogSecret = cfg.signSecret;92 };93 owner = config.repo.owner;94 repoName = config.repo.name;95 rev = config.repo.rev;96 baseUrl = cfg.baseUrl;97 caches = concatStringsSep " " cfg.caches;98 effectScript = ''99 set -euo pipefail100101 token=$(readSecretString token .token)102 ogSecret=$(readSecretString ogSecret .ogSecret)103 read -ra cacheArr <<<"$caches"104 if [[ ''${105 echo "hercules-ci.post-comment: at least one cache host is required" >&2106 exit 1107 fi108 sortedCaches=$(printf '%s\n' "''${cacheArr[@]}" | LC_ALL=C sort | paste -sd,)109110 _hmacHex() {111 printf '%s' "$1" \112 | openssl dgst -sha256 -hmac "$ogSecret" -hex \113 | sed 's/^.*= //'114 }115116 _uri() {117 jq -nj --arg s "$1" '$s|@uri'118 }119120 _signedUrl() {121 local endpoint=$1 drv=$2122 local sig123 sig=$(_hmacHex "''${endpoint}:''${sortedCaches}:''${drv}")124 local query=""125 for c in "''${cacheArr[@]}"; do126 query+="cache=$(_uri "$c")&"127 done128 query+="drv=$(_uri "$drv")&sig=''${sig}"129 printf '%s/%s?%s' "$baseUrl" "$endpoint" "$query"130 }131132 nixTar() { _signedUrl nixTar "$1"; }133 nixRender() { _signedUrl nixRender "$1"; }134135 out=$(mktemp)136 ${cfg.script}137138 jq -n --rawfile content "$out" '{body: $content}' \139 | curl -fsSL -X POST \140 -H "Authorization: Bearer $token" \141 -H "Accept: application/vnd.github+json" \142 -H "X-GitHub-Api-Version: 2022-11-28" \143 --data-binary @- \144 "https://api.github.com/repos/$owner/$repoName/commits/$rev/comments"145 '';146 }147 );148 };149 };150}