lmjtfy.git / flake.nix
1{
2  description = "lmjtfy — let me Jev that for you: a Rust Cloudflare Worker that puts typed questions to Jev (TypeSafe AI) and shows the call";
3
4  inputs = {
5    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

The Rust toolchain comes from fenix, as in ~/jevsnes: one stable toolchain with the wasm32-unknown-unknown rust-std the Worker builds for.

9    fenix = {
10      url = "github:nix-community/fenix";
11      inputs.nixpkgs.follows = "nixpkgs";
12    };

The estate's package set and facts, for the Cloudflare credentials: wrangler runs through a wrapper that reads the API token from 1Password per run, as ~/jevstrudel's does. Nothing is logged in and nothing is on disk.

17    nix-pkgs.url = "git+ssh://git@github.com/deizel/nix-pkgs";
18    nix-pkgs.inputs.nixpkgs.follows = "nixpkgs";
19    nix-facts.url = "git+ssh://git@github.com/deizel/nix-facts";
20  };
22  outputs =
23    {
24      nixpkgs,
25      fenix,
26      nix-pkgs,
27      nix-facts,
28      ...
29    }:
30    let
31      system = "x86_64-linux";
32      pkgs = nixpkgs.legacyPackages.${system};
33      rust = fenix.packages.${system};
34
35      rustToolchain = rust.combine [
36        rust.stable.rustc
37        rust.stable.cargo
38        rust.stable.clippy
39        rust.stable.rustfmt
40        rust.stable.rust-src
41        rust.stable.rust-analyzer
42        rust.targets.wasm32-unknown-unknown.stable.rust-std
43      ];
44
45      cloudflare = {
46        itemRef = nix-facts.facts.onePassword.cloudflareMasterKey;
47        accountId = nix-facts.facts.cloudflare.accounts.deizel;
48      };
49      # `lmjtfy-wrangler <dir> [wrangler args]`: wrangler with the account's
50      # token in its environment. Workers AI has no local emulation, so even
51      # `dev` needs it once the Worker calls a model.
52      wrangler = nix-pkgs.lib.infra.mkWranglerDeploy {
53        inherit pkgs;
54        inherit (cloudflare) itemRef accountId;
55        name = "lmjtfy-wrangler";
56      };

lmjtfy-eval [args]: tools/eval with the account, and where in 1Password its token is, in its own environment only. The reference is an op:// URL, and op-env-run resolves every such value it finds in the environment it is run from, with an account that cannot read this one. So it must not be a devshell variable (2026-10-02: it was, and the dev server's op-env-run refused to start).

64      evalTool = pkgs.writeShellApplication {
65        name = "lmjtfy-eval";
66        text = ''
67          export CLOUDFLARE_ACCOUNT_ID=${cloudflare.accountId}
68          export LMJTFY_OP_ITEM_REF=${pkgs.lib.escapeShellArg cloudflare.itemRef}
69          exec cargo run -q -p eval -- "$@"
70        '';
71      };

lmjtfy-secret <which> [environment]: sets one of the deployed Worker's secrets, or of its staging environment's. op-env-run -- lmjtfy-secret jev LMJTFY_TYPESAFE_API_KEY, from the environment lmjtfy-secret account CLOUDFLARE_ACCOUNT_ID, from the estate's facts … | lmjtfy-secret analytics CLOUDFLARE_ANALYTICS_TOKEN, from stdin: nix run ~/config#infra-core -- output -raw lmjtfy-analytics-token-value | tail -n 1 | lmjtfy-secret analytics op-env-run -- lmjtfy-secret github LMJTFY_GITHUB_TOKEN, from the environment: the clone proxy's read-only fine-grained token (made on GitHub; the API cannot mint one)

Not printf value | lmjtfy-wrangler … secret put: that wrapper runs op.exe to fetch the Cloudflare token before it runs wrangler, op.exe reads the stdin it is given, and the value is gone by the time wrangler looks. That uploaded an empty secret on 2026-10-02 and the live site said Jev was offline. Here op gets no stdin.

87      secretTool = pkgs.writeShellApplication {
88        name = "lmjtfy-secret";
89        runtimeInputs = [ pkgs.wrangler ];
90        text = ''
91          case "''${1:-}" in
92            jev)
93              name=LMJTFY_TYPESAFE_API_KEY
94              value=''${LMJTFY_TYPESAFE_API_KEY:-}
95              ;;
96            account)
97              name=CLOUDFLARE_ACCOUNT_ID
98              value=${cloudflare.accountId}
99              ;;
100            analytics)
101              name=CLOUDFLARE_ANALYTICS_TOKEN
102              value=$(cat)
103              ;;
104            github)
105              name=LMJTFY_GITHUB_TOKEN
106              value=''${LMJTFY_GITHUB_TOKEN:-}
107              ;;
108            *)
109              echo "usage: lmjtfy-secret jev | account | analytics | github [environment]" >&2
110              exit 2
111              ;;
112          esac
113          if [ -z "$value" ]; then
114            echo "lmjtfy-secret: no value for $name" >&2
115            exit 1
116          fi
117          op=op
118          if command -v op.exe >/dev/null; then op=op.exe; fi
119          CLOUDFLARE_API_TOKEN=$("$op" read ${pkgs.lib.escapeShellArg cloudflare.itemRef}/Token </dev/null | tr -d '\r')
120          if [ -z "$CLOUDFLARE_API_TOKEN" ]; then
121            echo "lmjtfy-secret: no Cloudflare token from 1Password" >&2
122            exit 1
123          fi
124          export CLOUDFLARE_API_TOKEN
125          export CLOUDFLARE_ACCOUNT_ID=${cloudflare.accountId}
126          cd "$(git rev-parse --show-toplevel)/apps/lmjtfy"
127          # A second word names the environment: `lmjtfy-secret jev staging`.
128          if [ -n "''${2:-}" ]; then
129            printf %s "$value" | wrangler secret put "$name" --env "$2"
130          else
131            printf %s "$value" | wrangler secret put "$name"
132          fi
133        '';
134      };
136      # Everything the Worker builds and tests with, from public inputs only.
137      # Nix fetches a locked input only when an output uses it (measured on
138      # Nix 2.34, 2026-10-02), so anyone who clones can enter this shell
139      # without access to nix-pkgs or nix-facts.
140      public = [
141        rustToolchain
142
143        # The Worker build: worker-build runs cargo for wasm32, then
144        # wasm-bindgen, then writes build/index.js for wrangler.
145        pkgs.worker-build
146        pkgs.wasm-bindgen-cli
147        pkgs.binaryen
148        pkgs.esbuild
149        pkgs.wrangler
150
151        pkgs.curl
152        pkgs.jq
153      ];
154
155      # worker-build otherwise downloads its own wasm-bindgen, wasm-opt and
156      # esbuild into a cache. These are nix's. Cargo.toml pins the
157      # wasm-bindgen crate to this CLI's exact version, because the two
158      # must match.
159      buildTools = {
160        WASM_BINDGEN_BIN = "${pkgs.wasm-bindgen-cli}/bin/wasm-bindgen";
161        WASM_OPT_BIN = "${pkgs.binaryen}/bin/wasm-opt";
162        ESBUILD_BIN = "${pkgs.esbuild}/bin/esbuild";
163      };
164    in
165    {
166      packages.${system}.wrangler = wrangler;
167
168      devShells.${system} = {
169        # `nix develop`: build and test.
170        default = pkgs.mkShell ({ packages = public; } // buildTools);
171
172        # `nix develop .#owner`: the same, and the owner's tools, which read
173        # the Cloudflare account and the 1Password item from the private
174        # nix-facts: the credentialed wrangler, the secrets, the eval, and the
175        # pitchfork that supervises the dev server (from nix-pkgs, which
176        # carries a newer release than nixpkgs).
177        owner = pkgs.mkShell (
178          {
179            packages = public ++ [
180              wrangler
181              secretTool
182              evalTool
183              nix-pkgs.packages.${system}.pitchfork
184            ];
185          }
186          // buildTools
187        );
188      };
189    };
190}