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>: sets one of the deployed Worker's secrets. 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.

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