Monorepo for Tangled tangled.org

sites: cloudflare worker for git sites routing via kv and r2

Compares the incoming host to the domain in the KV map, and fetches the
relevant site from R2. Written in Rust, compiled to wasm using
`worker-build`.

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

wip: repo pages settings and cf r2/kv setup

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

anirudh.fi 64f1f53c 70ed337e

verified
+1017 -3
+42 -3
flake.lock
··· 16 16 "url": "https://tangled.org/@jakelazaroff.com/actor-typeahead" 17 17 } 18 18 }, 19 + "fenix": { 20 + "inputs": { 21 + "nixpkgs": [ 22 + "nixpkgs" 23 + ], 24 + "rust-analyzer-src": "rust-analyzer-src" 25 + }, 26 + "locked": { 27 + "lastModified": 1772176312, 28 + "narHash": "sha256-Yjo/QCJvY9GUhAzwac/m6Rx3oxvRyEaiT5DQ5o+T6g4=", 29 + "owner": "nix-community", 30 + "repo": "fenix", 31 + "rev": "92d91250c1acd59beabc51208192adc92f31aeb5", 32 + "type": "github" 33 + }, 34 + "original": { 35 + "owner": "nix-community", 36 + "repo": "fenix", 37 + "type": "github" 38 + } 39 + }, 19 40 "flake-compat": { 20 41 "flake": false, 21 42 "locked": { ··· 162 183 }, 163 184 "nixpkgs": { 164 185 "locked": { 165 - "lastModified": 1766070988, 166 - "narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=", 186 + "lastModified": 1771848320, 187 + "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", 167 188 "owner": "nixos", 168 189 "repo": "nixpkgs", 169 - "rev": "c6245e83d836d0433170a16eb185cefe0572f8b8", 190 + "rev": "2fc6539b481e1d2569f25f8799236694180c0993", 170 191 "type": "github" 171 192 }, 172 193 "original": { ··· 179 200 "root": { 180 201 "inputs": { 181 202 "actor-typeahead-src": "actor-typeahead-src", 203 + "fenix": "fenix", 182 204 "flake-compat": "flake-compat", 183 205 "gomod2nix": "gomod2nix", 184 206 "htmx-src": "htmx-src", ··· 190 212 "mermaid-src": "mermaid-src", 191 213 "nixpkgs": "nixpkgs", 192 214 "sqlite-lib-src": "sqlite-lib-src" 215 + } 216 + }, 217 + "rust-analyzer-src": { 218 + "flake": false, 219 + "locked": { 220 + "lastModified": 1772094427, 221 + "narHash": "sha256-TiVs6OUBJEvajHdJZ5nIq0KognNJooUWuLGPFfQacSw=", 222 + "owner": "rust-lang", 223 + "repo": "rust-analyzer", 224 + "rev": "56b59a832858329c2f947f9b7bdf1a49da39c981", 225 + "type": "github" 226 + }, 227 + "original": { 228 + "owner": "rust-lang", 229 + "ref": "nightly", 230 + "repo": "rust-analyzer", 231 + "type": "github" 193 232 } 194 233 }, 195 234 "sqlite-lib-src": {
+15
flake.nix
··· 3 3 4 4 inputs = { 5 5 nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 + fenix = { 7 + url = "github:nix-community/fenix"; 8 + inputs.nixpkgs.follows = "nixpkgs"; 9 + }; 6 10 gomod2nix = { 7 11 url = "github:nix-community/gomod2nix"; 8 12 inputs.nixpkgs.follows = "nixpkgs"; ··· 54 58 outputs = { 55 59 self, 56 60 nixpkgs, 61 + fenix, 57 62 gomod2nix, 58 63 indigo, 59 64 htmx-src, ··· 185 190 pkgs.tailwindcss 186 191 pkgs.nixos-shell 187 192 pkgs.redis 193 + pkgs.worker-build 194 + pkgs.cargo-generate 195 + (fenix.packages.${system}.combine [ 196 + fenix.packages.${system}.stable.cargo 197 + fenix.packages.${system}.stable.rustc 198 + fenix.packages.${system}.stable.rust-src 199 + fenix.packages.${system}.stable.clippy 200 + fenix.packages.${system}.stable.rustfmt 201 + fenix.packages.${system}.targets.wasm32-unknown-unknown.stable.rust-std 202 + ]) 188 203 pkgs.coreutils # for those of us who are on systems that use busybox (alpine) 189 204 packages'.lexgen 190 205 packages'.treefmt-wrapper
+4
sites/.gitignore
··· 1 + target 2 + node_modules 3 + .wrangler 4 + build
+758
sites/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "async-trait" 7 + version = "0.1.89" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 10 + dependencies = [ 11 + "proc-macro2", 12 + "quote", 13 + "syn", 14 + ] 15 + 16 + [[package]] 17 + name = "autocfg" 18 + version = "1.5.0" 19 + source = "registry+https://github.com/rust-lang/crates.io-index" 20 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 21 + 22 + [[package]] 23 + name = "bumpalo" 24 + version = "3.20.2" 25 + source = "registry+https://github.com/rust-lang/crates.io-index" 26 + checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" 27 + 28 + [[package]] 29 + name = "bytes" 30 + version = "1.11.1" 31 + source = "registry+https://github.com/rust-lang/crates.io-index" 32 + checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" 33 + 34 + [[package]] 35 + name = "cfg-if" 36 + version = "1.0.4" 37 + source = "registry+https://github.com/rust-lang/crates.io-index" 38 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 39 + 40 + [[package]] 41 + name = "chrono" 42 + version = "0.4.44" 43 + source = "registry+https://github.com/rust-lang/crates.io-index" 44 + checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" 45 + dependencies = [ 46 + "js-sys", 47 + "num-traits", 48 + "wasm-bindgen", 49 + ] 50 + 51 + [[package]] 52 + name = "displaydoc" 53 + version = "0.2.5" 54 + source = "registry+https://github.com/rust-lang/crates.io-index" 55 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 56 + dependencies = [ 57 + "proc-macro2", 58 + "quote", 59 + "syn", 60 + ] 61 + 62 + [[package]] 63 + name = "form_urlencoded" 64 + version = "1.2.2" 65 + source = "registry+https://github.com/rust-lang/crates.io-index" 66 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 67 + dependencies = [ 68 + "percent-encoding", 69 + ] 70 + 71 + [[package]] 72 + name = "futures-channel" 73 + version = "0.3.32" 74 + source = "registry+https://github.com/rust-lang/crates.io-index" 75 + checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" 76 + dependencies = [ 77 + "futures-core", 78 + ] 79 + 80 + [[package]] 81 + name = "futures-core" 82 + version = "0.3.32" 83 + source = "registry+https://github.com/rust-lang/crates.io-index" 84 + checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 85 + 86 + [[package]] 87 + name = "futures-io" 88 + version = "0.3.32" 89 + source = "registry+https://github.com/rust-lang/crates.io-index" 90 + checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" 91 + 92 + [[package]] 93 + name = "futures-macro" 94 + version = "0.3.32" 95 + source = "registry+https://github.com/rust-lang/crates.io-index" 96 + checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" 97 + dependencies = [ 98 + "proc-macro2", 99 + "quote", 100 + "syn", 101 + ] 102 + 103 + [[package]] 104 + name = "futures-sink" 105 + version = "0.3.32" 106 + source = "registry+https://github.com/rust-lang/crates.io-index" 107 + checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" 108 + 109 + [[package]] 110 + name = "futures-task" 111 + version = "0.3.32" 112 + source = "registry+https://github.com/rust-lang/crates.io-index" 113 + checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" 114 + 115 + [[package]] 116 + name = "futures-util" 117 + version = "0.3.32" 118 + source = "registry+https://github.com/rust-lang/crates.io-index" 119 + checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" 120 + dependencies = [ 121 + "futures-core", 122 + "futures-io", 123 + "futures-macro", 124 + "futures-sink", 125 + "futures-task", 126 + "memchr", 127 + "pin-project-lite", 128 + "slab", 129 + ] 130 + 131 + [[package]] 132 + name = "http" 133 + version = "1.4.0" 134 + source = "registry+https://github.com/rust-lang/crates.io-index" 135 + checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 136 + dependencies = [ 137 + "bytes", 138 + "itoa", 139 + ] 140 + 141 + [[package]] 142 + name = "http-body" 143 + version = "1.0.1" 144 + source = "registry+https://github.com/rust-lang/crates.io-index" 145 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 146 + dependencies = [ 147 + "bytes", 148 + "http", 149 + ] 150 + 151 + [[package]] 152 + name = "icu_collections" 153 + version = "2.1.1" 154 + source = "registry+https://github.com/rust-lang/crates.io-index" 155 + checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 156 + dependencies = [ 157 + "displaydoc", 158 + "potential_utf", 159 + "yoke", 160 + "zerofrom", 161 + "zerovec", 162 + ] 163 + 164 + [[package]] 165 + name = "icu_locale_core" 166 + version = "2.1.1" 167 + source = "registry+https://github.com/rust-lang/crates.io-index" 168 + checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 169 + dependencies = [ 170 + "displaydoc", 171 + "litemap", 172 + "tinystr", 173 + "writeable", 174 + "zerovec", 175 + ] 176 + 177 + [[package]] 178 + name = "icu_normalizer" 179 + version = "2.1.1" 180 + source = "registry+https://github.com/rust-lang/crates.io-index" 181 + checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 182 + dependencies = [ 183 + "icu_collections", 184 + "icu_normalizer_data", 185 + "icu_properties", 186 + "icu_provider", 187 + "smallvec", 188 + "zerovec", 189 + ] 190 + 191 + [[package]] 192 + name = "icu_normalizer_data" 193 + version = "2.1.1" 194 + source = "registry+https://github.com/rust-lang/crates.io-index" 195 + checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 196 + 197 + [[package]] 198 + name = "icu_properties" 199 + version = "2.1.2" 200 + source = "registry+https://github.com/rust-lang/crates.io-index" 201 + checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" 202 + dependencies = [ 203 + "icu_collections", 204 + "icu_locale_core", 205 + "icu_properties_data", 206 + "icu_provider", 207 + "zerotrie", 208 + "zerovec", 209 + ] 210 + 211 + [[package]] 212 + name = "icu_properties_data" 213 + version = "2.1.2" 214 + source = "registry+https://github.com/rust-lang/crates.io-index" 215 + checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" 216 + 217 + [[package]] 218 + name = "icu_provider" 219 + version = "2.1.1" 220 + source = "registry+https://github.com/rust-lang/crates.io-index" 221 + checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 222 + dependencies = [ 223 + "displaydoc", 224 + "icu_locale_core", 225 + "writeable", 226 + "yoke", 227 + "zerofrom", 228 + "zerotrie", 229 + "zerovec", 230 + ] 231 + 232 + [[package]] 233 + name = "idna" 234 + version = "1.1.0" 235 + source = "registry+https://github.com/rust-lang/crates.io-index" 236 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 237 + dependencies = [ 238 + "idna_adapter", 239 + "smallvec", 240 + "utf8_iter", 241 + ] 242 + 243 + [[package]] 244 + name = "idna_adapter" 245 + version = "1.2.1" 246 + source = "registry+https://github.com/rust-lang/crates.io-index" 247 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 248 + dependencies = [ 249 + "icu_normalizer", 250 + "icu_properties", 251 + ] 252 + 253 + [[package]] 254 + name = "itoa" 255 + version = "1.0.17" 256 + source = "registry+https://github.com/rust-lang/crates.io-index" 257 + checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 258 + 259 + [[package]] 260 + name = "js-sys" 261 + version = "0.3.90" 262 + source = "registry+https://github.com/rust-lang/crates.io-index" 263 + checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" 264 + dependencies = [ 265 + "once_cell", 266 + "wasm-bindgen", 267 + ] 268 + 269 + [[package]] 270 + name = "litemap" 271 + version = "0.8.1" 272 + source = "registry+https://github.com/rust-lang/crates.io-index" 273 + checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 274 + 275 + [[package]] 276 + name = "matchit" 277 + version = "0.7.3" 278 + source = "registry+https://github.com/rust-lang/crates.io-index" 279 + checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 280 + 281 + [[package]] 282 + name = "memchr" 283 + version = "2.8.0" 284 + source = "registry+https://github.com/rust-lang/crates.io-index" 285 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 286 + 287 + [[package]] 288 + name = "num-traits" 289 + version = "0.2.19" 290 + source = "registry+https://github.com/rust-lang/crates.io-index" 291 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 292 + dependencies = [ 293 + "autocfg", 294 + ] 295 + 296 + [[package]] 297 + name = "once_cell" 298 + version = "1.21.3" 299 + source = "registry+https://github.com/rust-lang/crates.io-index" 300 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 301 + 302 + [[package]] 303 + name = "percent-encoding" 304 + version = "2.3.2" 305 + source = "registry+https://github.com/rust-lang/crates.io-index" 306 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 307 + 308 + [[package]] 309 + name = "pin-project" 310 + version = "1.1.10" 311 + source = "registry+https://github.com/rust-lang/crates.io-index" 312 + checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 313 + dependencies = [ 314 + "pin-project-internal", 315 + ] 316 + 317 + [[package]] 318 + name = "pin-project-internal" 319 + version = "1.1.10" 320 + source = "registry+https://github.com/rust-lang/crates.io-index" 321 + checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 322 + dependencies = [ 323 + "proc-macro2", 324 + "quote", 325 + "syn", 326 + ] 327 + 328 + [[package]] 329 + name = "pin-project-lite" 330 + version = "0.2.16" 331 + source = "registry+https://github.com/rust-lang/crates.io-index" 332 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 333 + 334 + [[package]] 335 + name = "potential_utf" 336 + version = "0.1.4" 337 + source = "registry+https://github.com/rust-lang/crates.io-index" 338 + checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 339 + dependencies = [ 340 + "zerovec", 341 + ] 342 + 343 + [[package]] 344 + name = "proc-macro2" 345 + version = "1.0.106" 346 + source = "registry+https://github.com/rust-lang/crates.io-index" 347 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 348 + dependencies = [ 349 + "unicode-ident", 350 + ] 351 + 352 + [[package]] 353 + name = "quote" 354 + version = "1.0.44" 355 + source = "registry+https://github.com/rust-lang/crates.io-index" 356 + checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 357 + dependencies = [ 358 + "proc-macro2", 359 + ] 360 + 361 + [[package]] 362 + name = "rustversion" 363 + version = "1.0.22" 364 + source = "registry+https://github.com/rust-lang/crates.io-index" 365 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 366 + 367 + [[package]] 368 + name = "ryu" 369 + version = "1.0.23" 370 + source = "registry+https://github.com/rust-lang/crates.io-index" 371 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 372 + 373 + [[package]] 374 + name = "serde" 375 + version = "1.0.228" 376 + source = "registry+https://github.com/rust-lang/crates.io-index" 377 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 378 + dependencies = [ 379 + "serde_core", 380 + "serde_derive", 381 + ] 382 + 383 + [[package]] 384 + name = "serde-wasm-bindgen" 385 + version = "0.6.5" 386 + source = "registry+https://github.com/rust-lang/crates.io-index" 387 + checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" 388 + dependencies = [ 389 + "js-sys", 390 + "serde", 391 + "wasm-bindgen", 392 + ] 393 + 394 + [[package]] 395 + name = "serde_core" 396 + version = "1.0.228" 397 + source = "registry+https://github.com/rust-lang/crates.io-index" 398 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 399 + dependencies = [ 400 + "serde_derive", 401 + ] 402 + 403 + [[package]] 404 + name = "serde_derive" 405 + version = "1.0.228" 406 + source = "registry+https://github.com/rust-lang/crates.io-index" 407 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 408 + dependencies = [ 409 + "proc-macro2", 410 + "quote", 411 + "syn", 412 + ] 413 + 414 + [[package]] 415 + name = "serde_json" 416 + version = "1.0.149" 417 + source = "registry+https://github.com/rust-lang/crates.io-index" 418 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 419 + dependencies = [ 420 + "itoa", 421 + "memchr", 422 + "serde", 423 + "serde_core", 424 + "zmij", 425 + ] 426 + 427 + [[package]] 428 + name = "serde_urlencoded" 429 + version = "0.7.1" 430 + source = "registry+https://github.com/rust-lang/crates.io-index" 431 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 432 + dependencies = [ 433 + "form_urlencoded", 434 + "itoa", 435 + "ryu", 436 + "serde", 437 + ] 438 + 439 + [[package]] 440 + name = "sites" 441 + version = "0.1.0" 442 + dependencies = [ 443 + "serde", 444 + "serde_json", 445 + "worker", 446 + ] 447 + 448 + [[package]] 449 + name = "slab" 450 + version = "0.4.12" 451 + source = "registry+https://github.com/rust-lang/crates.io-index" 452 + checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" 453 + 454 + [[package]] 455 + name = "smallvec" 456 + version = "1.15.1" 457 + source = "registry+https://github.com/rust-lang/crates.io-index" 458 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 459 + 460 + [[package]] 461 + name = "stable_deref_trait" 462 + version = "1.2.1" 463 + source = "registry+https://github.com/rust-lang/crates.io-index" 464 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 465 + 466 + [[package]] 467 + name = "syn" 468 + version = "2.0.117" 469 + source = "registry+https://github.com/rust-lang/crates.io-index" 470 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 471 + dependencies = [ 472 + "proc-macro2", 473 + "quote", 474 + "unicode-ident", 475 + ] 476 + 477 + [[package]] 478 + name = "synstructure" 479 + version = "0.13.2" 480 + source = "registry+https://github.com/rust-lang/crates.io-index" 481 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 482 + dependencies = [ 483 + "proc-macro2", 484 + "quote", 485 + "syn", 486 + ] 487 + 488 + [[package]] 489 + name = "tinystr" 490 + version = "0.8.2" 491 + source = "registry+https://github.com/rust-lang/crates.io-index" 492 + checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 493 + dependencies = [ 494 + "displaydoc", 495 + "zerovec", 496 + ] 497 + 498 + [[package]] 499 + name = "tokio" 500 + version = "1.49.0" 501 + source = "registry+https://github.com/rust-lang/crates.io-index" 502 + checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 503 + dependencies = [ 504 + "pin-project-lite", 505 + ] 506 + 507 + [[package]] 508 + name = "unicode-ident" 509 + version = "1.0.24" 510 + source = "registry+https://github.com/rust-lang/crates.io-index" 511 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 512 + 513 + [[package]] 514 + name = "url" 515 + version = "2.5.8" 516 + source = "registry+https://github.com/rust-lang/crates.io-index" 517 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 518 + dependencies = [ 519 + "form_urlencoded", 520 + "idna", 521 + "percent-encoding", 522 + "serde", 523 + ] 524 + 525 + [[package]] 526 + name = "utf8_iter" 527 + version = "1.0.4" 528 + source = "registry+https://github.com/rust-lang/crates.io-index" 529 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 530 + 531 + [[package]] 532 + name = "wasm-bindgen" 533 + version = "0.2.113" 534 + source = "registry+https://github.com/rust-lang/crates.io-index" 535 + checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" 536 + dependencies = [ 537 + "cfg-if", 538 + "once_cell", 539 + "rustversion", 540 + "wasm-bindgen-macro", 541 + "wasm-bindgen-shared", 542 + ] 543 + 544 + [[package]] 545 + name = "wasm-bindgen-futures" 546 + version = "0.4.63" 547 + source = "registry+https://github.com/rust-lang/crates.io-index" 548 + checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" 549 + dependencies = [ 550 + "cfg-if", 551 + "futures-util", 552 + "js-sys", 553 + "once_cell", 554 + "wasm-bindgen", 555 + "web-sys", 556 + ] 557 + 558 + [[package]] 559 + name = "wasm-bindgen-macro" 560 + version = "0.2.113" 561 + source = "registry+https://github.com/rust-lang/crates.io-index" 562 + checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" 563 + dependencies = [ 564 + "quote", 565 + "wasm-bindgen-macro-support", 566 + ] 567 + 568 + [[package]] 569 + name = "wasm-bindgen-macro-support" 570 + version = "0.2.113" 571 + source = "registry+https://github.com/rust-lang/crates.io-index" 572 + checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" 573 + dependencies = [ 574 + "bumpalo", 575 + "proc-macro2", 576 + "quote", 577 + "syn", 578 + "wasm-bindgen-shared", 579 + ] 580 + 581 + [[package]] 582 + name = "wasm-bindgen-shared" 583 + version = "0.2.113" 584 + source = "registry+https://github.com/rust-lang/crates.io-index" 585 + checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" 586 + dependencies = [ 587 + "unicode-ident", 588 + ] 589 + 590 + [[package]] 591 + name = "wasm-streams" 592 + version = "0.5.0" 593 + source = "registry+https://github.com/rust-lang/crates.io-index" 594 + checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" 595 + dependencies = [ 596 + "futures-util", 597 + "js-sys", 598 + "wasm-bindgen", 599 + "wasm-bindgen-futures", 600 + "web-sys", 601 + ] 602 + 603 + [[package]] 604 + name = "web-sys" 605 + version = "0.3.90" 606 + source = "registry+https://github.com/rust-lang/crates.io-index" 607 + checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" 608 + dependencies = [ 609 + "js-sys", 610 + "wasm-bindgen", 611 + ] 612 + 613 + [[package]] 614 + name = "worker" 615 + version = "0.7.5" 616 + source = "registry+https://github.com/rust-lang/crates.io-index" 617 + checksum = "7267f3baa986254a8dace6f6a7c6ab88aef59f00c03aaad6749e048b5faaf6f6" 618 + dependencies = [ 619 + "async-trait", 620 + "bytes", 621 + "chrono", 622 + "futures-channel", 623 + "futures-util", 624 + "http", 625 + "http-body", 626 + "js-sys", 627 + "matchit", 628 + "pin-project", 629 + "serde", 630 + "serde-wasm-bindgen", 631 + "serde_json", 632 + "serde_urlencoded", 633 + "tokio", 634 + "url", 635 + "wasm-bindgen", 636 + "wasm-bindgen-futures", 637 + "wasm-streams", 638 + "web-sys", 639 + "worker-macros", 640 + "worker-sys", 641 + ] 642 + 643 + [[package]] 644 + name = "worker-macros" 645 + version = "0.7.5" 646 + source = "registry+https://github.com/rust-lang/crates.io-index" 647 + checksum = "7410081121531ec2fa111ab17b911efc601d7b6d590c0a92b847874ebeff0030" 648 + dependencies = [ 649 + "async-trait", 650 + "proc-macro2", 651 + "quote", 652 + "syn", 653 + "wasm-bindgen", 654 + "wasm-bindgen-futures", 655 + "wasm-bindgen-macro-support", 656 + "worker-sys", 657 + ] 658 + 659 + [[package]] 660 + name = "worker-sys" 661 + version = "0.7.5" 662 + source = "registry+https://github.com/rust-lang/crates.io-index" 663 + checksum = "4777582bf8a04174a034cb336f3702eb0e5cb444a67fdaa4fd44454ff7e2dd95" 664 + dependencies = [ 665 + "cfg-if", 666 + "js-sys", 667 + "wasm-bindgen", 668 + "web-sys", 669 + ] 670 + 671 + [[package]] 672 + name = "writeable" 673 + version = "0.6.2" 674 + source = "registry+https://github.com/rust-lang/crates.io-index" 675 + checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 676 + 677 + [[package]] 678 + name = "yoke" 679 + version = "0.8.1" 680 + source = "registry+https://github.com/rust-lang/crates.io-index" 681 + checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 682 + dependencies = [ 683 + "stable_deref_trait", 684 + "yoke-derive", 685 + "zerofrom", 686 + ] 687 + 688 + [[package]] 689 + name = "yoke-derive" 690 + version = "0.8.1" 691 + source = "registry+https://github.com/rust-lang/crates.io-index" 692 + checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 693 + dependencies = [ 694 + "proc-macro2", 695 + "quote", 696 + "syn", 697 + "synstructure", 698 + ] 699 + 700 + [[package]] 701 + name = "zerofrom" 702 + version = "0.1.6" 703 + source = "registry+https://github.com/rust-lang/crates.io-index" 704 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 705 + dependencies = [ 706 + "zerofrom-derive", 707 + ] 708 + 709 + [[package]] 710 + name = "zerofrom-derive" 711 + version = "0.1.6" 712 + source = "registry+https://github.com/rust-lang/crates.io-index" 713 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 714 + dependencies = [ 715 + "proc-macro2", 716 + "quote", 717 + "syn", 718 + "synstructure", 719 + ] 720 + 721 + [[package]] 722 + name = "zerotrie" 723 + version = "0.2.3" 724 + source = "registry+https://github.com/rust-lang/crates.io-index" 725 + checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 726 + dependencies = [ 727 + "displaydoc", 728 + "yoke", 729 + "zerofrom", 730 + ] 731 + 732 + [[package]] 733 + name = "zerovec" 734 + version = "0.11.5" 735 + source = "registry+https://github.com/rust-lang/crates.io-index" 736 + checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 737 + dependencies = [ 738 + "yoke", 739 + "zerofrom", 740 + "zerovec-derive", 741 + ] 742 + 743 + [[package]] 744 + name = "zerovec-derive" 745 + version = "0.11.2" 746 + source = "registry+https://github.com/rust-lang/crates.io-index" 747 + checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 748 + dependencies = [ 749 + "proc-macro2", 750 + "quote", 751 + "syn", 752 + ] 753 + 754 + [[package]] 755 + name = "zmij" 756 + version = "1.0.21" 757 + source = "registry+https://github.com/rust-lang/crates.io-index" 758 + checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+18
sites/Cargo.toml
··· 1 + [package] 2 + name = "sites" 3 + version = "0.1.0" 4 + edition = "2024" 5 + authors = ["Anirudh Oppiliappan <anirudh@tangled.org>"] 6 + 7 + [lib] 8 + crate-type = ["cdylib"] 9 + path = "src/lib.rs" 10 + 11 + [dependencies] 12 + worker = "0.7.0" 13 + serde = { version = "1", features = ["derive"] } 14 + serde_json = "1" 15 + 16 + [profile.release] 17 + opt-level = "s" 18 + lto = true
+153
sites/src/lib.rs
··· 1 + use std::collections::HashMap; 2 + 3 + use serde::Deserialize; 4 + use worker::*; 5 + 6 + /// The JSON value stored in Workers KV, keyed by domain. 7 + /// 8 + /// Example KV entry: 9 + /// key: "foo.example.com" 10 + /// value: {"did": "did:plc:...", "repos": {"my_repo": true, "other_repo": false}} 11 + /// 12 + /// The boolean on each repo indicates whether it is the index site for the 13 + /// domain (true) or a sub-path site (false). At most one repo may be true. 14 + #[derive(Deserialize)] 15 + struct DomainMapping { 16 + did: String, 17 + /// repo name → is_index 18 + repos: HashMap<String, bool>, 19 + } 20 + 21 + impl DomainMapping { 22 + /// Returns the repo that is marked as the index site, if any. 23 + fn index_repo(&self) -> Option<&str> { 24 + self.repos 25 + .iter() 26 + .find_map(|(name, &is_index)| if is_index { Some(name.as_str()) } else { None }) 27 + } 28 + } 29 + 30 + /// Build the R2 object key for a given did/repo and intra-site path. 31 + /// `site_path` should start with a `/` or be empty. 32 + fn r2_key(did: &str, repo: &str, site_path: &str) -> String { 33 + let base = format!("{}/{}/", did, repo); 34 + if site_path.is_empty() || site_path == "/" { 35 + format!("{}index.html", base) 36 + } else { 37 + let trimmed = site_path.trim_start_matches('/'); 38 + if trimmed.is_empty() || trimmed.ends_with('/') { 39 + format!("{}{}index.html", base, trimmed) 40 + } else { 41 + format!("{}{}", base, trimmed) 42 + } 43 + } 44 + } 45 + 46 + /// Fetch an object from R2, falling back to appending /index.html if the 47 + /// key looks like a directory (no file extension in the last segment). 48 + async fn fetch_from_r2(bucket: &Bucket, key: &str) -> Result<Option<Object>> { 49 + if let Some(obj) = bucket.get(key).execute().await? { 50 + return Ok(Some(obj)); 51 + } 52 + 53 + let last_segment = key.rsplit('/').next().unwrap_or(key); 54 + if !last_segment.contains('.') { 55 + let index_key = format!("{}/index.html", key.trim_end_matches('/')); 56 + if let Some(obj) = bucket.get(&index_key).execute().await? { 57 + return Ok(Some(obj)); 58 + } 59 + } 60 + 61 + Ok(None) 62 + } 63 + 64 + /// Build a Response from an R2 Object, forwarding the content-type header. 65 + fn response_from_object(obj: Object) -> Result<Response> { 66 + let content_type = obj 67 + .http_metadata() 68 + .content_type 69 + .unwrap_or_else(|| "application/octet-stream".to_string()); 70 + 71 + let body = obj.body().ok_or_else(|| Error::RustError("empty R2 body".into()))?; 72 + let mut resp = Response::from_body(body.response_body()?)?; 73 + resp.headers_mut().set("Content-Type", &content_type)?; 74 + resp.headers_mut().set("Cache-Control", "public, max-age=60")?; 75 + Ok(resp) 76 + } 77 + 78 + fn is_excluded(path: &str) -> bool { 79 + let excluded = ["/.well-known/atproto-did"]; 80 + excluded.iter().any(|&prefix| path.starts_with(prefix)) 81 + } 82 + 83 + #[event(fetch)] 84 + async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> { 85 + let kv = env.kv("SITES")?; 86 + let bucket = env.bucket("SITES_BUCKET")?; 87 + 88 + // Extract host, stripping any port. 89 + let host = req.headers().get("host")?.unwrap_or_default(); 90 + let host = host.split(':').next().unwrap_or("").to_string(); 91 + 92 + if host.is_empty() { 93 + return Response::error("Bad Request: missing host", 400); 94 + } 95 + 96 + let url = req.url()?; 97 + let path = url.path(); 98 + 99 + if is_excluded(path) { 100 + return Fetch::Request(req).send().await; 101 + } 102 + 103 + // Single KV lookup for the whole domain. 104 + let mapping = match kv.get(&host).text().await? { 105 + Some(raw) => match serde_json::from_str::<DomainMapping>(&raw) { 106 + Ok(m) => m, 107 + Err(_) => return Response::error("Internal Error: bad mapping", 500), 108 + }, 109 + None => return Response::error("site not found!", 404), 110 + }; 111 + 112 + let path = url.path(); // always starts with "/" 113 + 114 + // First path segment, e.g. "my_repo" from "/my_repo/page.html" 115 + let first_segment = path 116 + .trim_start_matches('/') 117 + .split('/') 118 + .next() 119 + .unwrap_or("") 120 + .to_string(); 121 + 122 + // 1. sub-path site 123 + // If the first path segment matches a non-index repo, serve from it. 124 + if !first_segment.is_empty() { 125 + if let Some(&is_index) = mapping.repos.get(&first_segment) { 126 + if !is_index { 127 + // Strip the leading "/{first_segment}" to get the intra-site path. 128 + let site_path = path 129 + .trim_start_matches('/') 130 + .trim_start_matches(&first_segment) 131 + .to_string(); 132 + 133 + let key = r2_key(&mapping.did, &first_segment, &site_path); 134 + return match fetch_from_r2(&bucket, &key).await? { 135 + Some(obj) => response_from_object(obj), 136 + None => Response::error("Not Found", 404), 137 + }; 138 + } 139 + } 140 + } 141 + 142 + // 2. index site 143 + // Fall back to the repo marked as the index site, serving the full path. 144 + if let Some(index_repo) = mapping.index_repo() { 145 + let key = r2_key(&mapping.did, index_repo, path); 146 + return match fetch_from_r2(&bucket, &key).await? { 147 + Some(obj) => response_from_object(obj), 148 + None => Response::error("Not Found", 404), 149 + }; 150 + } 151 + 152 + Response::error("Not Found", 404) 153 + }
+27
sites/wrangler.toml
··· 1 + name = "sites" 2 + main = "build/index.js" 3 + compatibility_date = "2026-02-27" 4 + 5 + [build] 6 + command = "worker-build --release" 7 + 8 + [[routes]] 9 + pattern = "*.tngl.io/*" 10 + zone_name = "tngl.io" 11 + 12 + [[routes]] 13 + pattern = "*.tngl.sh/*" 14 + zone_name = "tngl.sh" 15 + 16 + [[kv_namespaces]] 17 + binding = "SITES" 18 + id = "252122843e3a4b6396e90f15d204659c" 19 + 20 + [[r2_buckets]] 21 + binding = "SITES_BUCKET" 22 + bucket_name = "tangled-sites" 23 + 24 + [observability] 25 + [observability.logs] 26 + enabled = true 27 + invocation_logs = true