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"; 6 7 # The estate's package set and facts, for the Cloudflare credentials: 8 # wrangler runs through a wrapper that reads the API token from 1Password 9 # per run, as ~/jevstrudel's does. Nothing is logged in and nothing is on disk. 10 nix-pkgs.url = "git+ssh://git@github.com/deizel/nix-pkgs"; 11 nix-pkgs.inputs.nixpkgs.follows = "nixpkgs"; 12 nix-facts.url = "git+ssh://git@github.com/deizel/nix-facts"; 13 }; 14 15 outputs = 16 { 17 nixpkgs, 18 nix-pkgs, 19 nix-facts, 20 ... 21 }: 22 let 23 system = "x86_64-linux"; 24 pkgs = nixpkgs.legacyPackages.${system}; 25 26 cloudflare = { 27 itemRef = nix-facts.facts.onePassword.cloudflareMasterKey; 28 accountId = nix-facts.facts.cloudflare.accounts.deizel; 29 }; 30 # `lmjtfy-wrangler <dir> [wrangler args]`: wrangler with the account's 31 # token in its environment. Workers AI has no local emulation, so even 32 # `dev` needs it once the Worker calls a model. 33 wrangler = nix-pkgs.lib.infra.mkWranglerDeploy { 34 inherit pkgs; 35 inherit (cloudflare) itemRef accountId; 36 name = "lmjtfy-wrangler"; 37 }; 38 39 # `lmjtfy-eval [args]`: tools/eval with the account, and where in 40 # 1Password its token is, in its own environment only. The reference is 41 # an `op://` URL, and `op-env-run` resolves every such value it finds 42 # in the environment it is run from, with an account that cannot read 43 # this one. So it must not be a devshell variable (2026-10-02: it was, 44 # and the dev server's op-env-run refused to start). 45 evalTool = pkgs.writeShellApplication { 46 name = "lmjtfy-eval"; 47 text = '' 48 export CLOUDFLARE_ACCOUNT_ID=${cloudflare.accountId} 49 export LMJTFY_OP_ITEM_REF=${pkgs.lib.escapeShellArg cloudflare.itemRef} 50 exec cargo run -q -p eval -- "$@" 51 ''; 52 }; 53 54 # `lmjtfy-secret <which> [environment]`: sets one of the deployed 55 # Worker's secrets, or of its `staging` environment's. 56 # op-env-run -- lmjtfy-secret jev LMJTFY_TYPESAFE_API_KEY, from the environment 57 # lmjtfy-secret account CLOUDFLARE_ACCOUNT_ID, from the estate's facts 58 # … | lmjtfy-secret analytics CLOUDFLARE_ANALYTICS_TOKEN, from stdin: 59 # nix run ~/config#infra-core -- output -raw lmjtfy-analytics-token-value | tail -n 1 | lmjtfy-secret analytics 60 # op-env-run -- lmjtfy-secret github LMJTFY_GITHUB_TOKEN, from the environment: the clone 61 # proxy's read-only fine-grained token (made on GitHub; the API cannot mint one) 62 # 63 # Not `printf value | lmjtfy-wrangler … secret put`: that wrapper runs 64 # op.exe to fetch the Cloudflare token before it runs wrangler, op.exe 65 # reads the stdin it is given, and the value is gone by the time 66 # wrangler looks. That uploaded an empty secret on 2026-10-02 and the 67 # live site said Jev was offline. Here op gets no stdin. 68 secretTool = pkgs.writeShellApplication { 69 name = "lmjtfy-secret"; 70 runtimeInputs = [ pkgs.wrangler ]; 71 text = '' 72 case "''${1:-}" in 73 jev) 74 name=LMJTFY_TYPESAFE_API_KEY 75 value=''${LMJTFY_TYPESAFE_API_KEY:-} 76 ;; 77 account) 78 name=CLOUDFLARE_ACCOUNT_ID 79 value=${cloudflare.accountId} 80 ;; 81 analytics) 82 name=CLOUDFLARE_ANALYTICS_TOKEN 83 value=$(cat) 84 ;; 85 github) 86 name=LMJTFY_GITHUB_TOKEN 87 value=''${LMJTFY_GITHUB_TOKEN:-} 88 ;; 89 *) 90 echo "usage: lmjtfy-secret jev | account | analytics | github [environment]" >&2 91 exit 2 92 ;; 93 esac 94 if [ -z "$value" ]; then 95 echo "lmjtfy-secret: no value for $name" >&2 96 exit 1 97 fi 98 op=op 99 if command -v op.exe >/dev/null; then op=op.exe; fi 100 CLOUDFLARE_API_TOKEN=$("$op" read ${pkgs.lib.escapeShellArg cloudflare.itemRef}/Token </dev/null | tr -d '\r') 101 if [ -z "$CLOUDFLARE_API_TOKEN" ]; then 102 echo "lmjtfy-secret: no Cloudflare token from 1Password" >&2 103 exit 1 104 fi 105 export CLOUDFLARE_API_TOKEN 106 export CLOUDFLARE_ACCOUNT_ID=${cloudflare.accountId} 107 cd "$(git rev-parse --show-toplevel)/apps/lmjtfy" 108 # A second word names the environment: `lmjtfy-secret jev staging`. 109 if [ -n "''${2:-}" ]; then 110 printf %s "$value" | wrangler secret put "$name" --env "$2" 111 else 112 printf %s "$value" | wrangler secret put "$name" 113 fi 114 ''; 115 }; 116 117 # The toolchain is mise's, written in mise.toml (Rust, wasm-bindgen, worker-build, node, wrangler, hk, 118 # pkl, pitchfork, fnox); anyone can use it without nix: see README.md. The shells only wrap it: mise 119 # itself, plus the native things a downloaded toolchain cannot bring (a C compiler and linker, 120 # pkg-config, OpenSSL for crates that link it, curl, unzip and bzip2, git). 121 # 122 # Entering one puts whatever `mise install` has already installed on PATH (it installs nothing itself: 123 # the first install is large). Downloaded programs are ordinary dynamically linked binaries; NixOS runs 124 # them through nix-ld (NIX_LD_LIBRARY_PATH below tells nix-ld where its libraries are). Where nix-ld is 125 # not enabled, use `nix develop .#fhs`. 126 # 127 # Nix fetches a locked input only when an output uses it (measured on Nix 2.34, 2026-10-02), so anyone 128 # who clones can enter this shell without access to nix-pkgs or nix-facts. 129 native = [ 130 pkgs.mise 131 pkgs.gcc 132 pkgs.pkg-config 133 pkgs.openssl 134 pkgs.curl 135 pkgs.unzip 136 pkgs.bzip2 137 pkgs.git 138 pkgs.cacert 139 ]; 140 shellHook = '' 141 root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" 142 if [ -f "$root/mise.toml" ]; then 143 export MISE_TRUSTED_CONFIG_PATHS="$root''${MISE_TRUSTED_CONFIG_PATHS:+:$MISE_TRUSTED_CONFIG_PATHS}" 144 eval "$(mise env -s bash 2>/dev/null)" || true 145 fi 146 ''; 147 nixLd = pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib pkgs.zlib pkgs.openssl ]; 148 in 149 { 150 packages.${system}.wrangler = wrangler; 151 152 devShells.${system} = { 153 # `nix develop`: mise and its native needs. 154 default = pkgs.mkShell { 155 packages = native; 156 NIX_LD_LIBRARY_PATH = nixLd; 157 inherit shellHook; 158 }; 159 160 # The same, inside an FHS root (bubblewrap), for a machine without nix-ld. 161 fhs = 162 (pkgs.buildFHSEnv { 163 name = "lmjtfy-fhs"; 164 targetPkgs = p: native ++ [ p.zlib p.stdenv.cc.cc.lib ]; 165 runScript = "bash"; 166 }).env; 167 168 # `nix develop .#owner`: the same, and the owner's tools, which read the Cloudflare account and the 169 # 1Password item from the private nix-facts: the credentialed wrangler (tools/wrangler prefers it 170 # when it is on PATH), the secrets and the eval. 171 owner = pkgs.mkShell { 172 packages = native ++ [ 173 wrangler 174 secretTool 175 evalTool 176 ]; 177 NIX_LD_LIBRARY_PATH = nixLd; 178 inherit shellHook; 179 }; 180 }; 181 }; 182}