about summary refs log tree commit diff
path: root/web/atward
diff options
context:
space:
mode:
Diffstat (limited to 'web/atward')
-rw-r--r--web/atward/.gitignore3
-rw-r--r--web/atward/Cargo.lock639
-rw-r--r--web/atward/Cargo.toml9
-rw-r--r--web/atward/build.rs55
-rw-r--r--web/atward/default.nix8
-rw-r--r--web/atward/indexHtml/default.nix99
-rw-r--r--web/atward/src/main.rs388
-rw-r--r--web/atward/src/opensearch.xml8
8 files changed, 1209 insertions, 0 deletions
diff --git a/web/atward/.gitignore b/web/atward/.gitignore
new file mode 100644
index 000000000000..29e65519ba35
--- /dev/null
+++ b/web/atward/.gitignore
@@ -0,0 +1,3 @@
+result
+/target
+**/*.rs.bk
diff --git a/web/atward/Cargo.lock b/web/atward/Cargo.lock
new file mode 100644
index 000000000000..0eb7ae950881
--- /dev/null
+++ b/web/atward/Cargo.lock
@@ -0,0 +1,639 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "adler32"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "ascii"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109"
+
+[[package]]
+name = "atward"
+version = "0.1.0"
+dependencies = [
+ "regex",
+ "rouille",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "brotli"
+version = "3.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71cb90ade945043d3d53597b2fc359bb063db8ade2bcffe7997351d0756e9d50"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+dependencies = [
+ "memchr",
+ "safemem",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "winapi",
+]
+
+[[package]]
+name = "chunked_transfer"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
+
+[[package]]
+name = "crc32fast"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "deflate"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f95bf05dffba6e6cce8dfbb30def788154949ccd9aed761b472119c21e01c70"
+dependencies = [
+ "adler32",
+ "gzip-header",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "winapi",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gzip-header"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0131feb3d3bb2a5a238d8a4d09f6353b7ebfdc52e77bccbf4ea6eaa751dde639"
+dependencies = [
+ "crc32fast",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "httparse"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
+
+[[package]]
+name = "libc"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "multipart"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
+dependencies = [
+ "buf_redux",
+ "httparse",
+ "log",
+ "mime",
+ "mime_guess",
+ "quick-error",
+ "rand",
+ "safemem",
+ "tempfile",
+ "twoway",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quote"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rouille"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18b2380c42510ef4a28b5f228a174c801e0dec590103e215e60812e2e2f34d05"
+dependencies = [
+ "base64",
+ "brotli",
+ "chrono",
+ "deflate",
+ "filetime",
+ "multipart",
+ "num_cpus",
+ "percent-encoding",
+ "rand",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha1",
+ "threadpool",
+ "time",
+ "tiny_http",
+ "url",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "serde"
+version = "1.0.132"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.132"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+
+[[package]]
+name = "syn"
+version = "1.0.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "time"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "tiny_http"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce51b50006056f590c9b7c3808c3bd70f0d1101666629713866c227d6e58d39"
+dependencies = [
+ "ascii",
+ "chrono",
+ "chunked_transfer",
+ "log",
+ "url",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "twoway"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/web/atward/Cargo.toml b/web/atward/Cargo.toml
new file mode 100644
index 000000000000..2ecd10f96f68
--- /dev/null
+++ b/web/atward/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "atward"
+version = "0.1.0"
+authors = ["Vincent Ambo <mail@tazj.in>"]
+edition = "2018"
+
+[dependencies]
+regex = "1.5"
+rouille = "3.5"
diff --git a/web/atward/build.rs b/web/atward/build.rs
new file mode 100644
index 000000000000..90a2f35cd1a9
--- /dev/null
+++ b/web/atward/build.rs
@@ -0,0 +1,55 @@
+//! Build script that can be used outside of Nix builds to inject the
+//! ATWARD_INDEX_HTML variable when building in development mode.
+//!
+//! Note that this script assumes that atward is in a checkout of the
+//! TVL depot.
+
+use std::process::Command;
+
+static ATWARD_INDEX_HTML: &str = "ATWARD_INDEX_HTML";
+static ERROR_MESSAGE: &str = r#"Failed to build index page.
+
+When building during development, atward expects to be in a checkout
+of the TVL depot. This is required to automatically build the index
+page that is needed at compile time.
+
+As atward can not automatically detect the location of the page,
+you must set the `ATWARD_INDEX_HTML` environment variable to the
+right path.
+
+The expected page is build using the files in //web/atward/indexHtml
+in the depot."#;
+
+fn main() {
+    // Do nothing if the variable is already set (e.g. via Nix)
+    if let Ok(_) = std::env::var(ATWARD_INDEX_HTML) {
+        return;
+    }
+
+    // Otherwise ask Nix to build it and inject the result.
+    let output = Command::new("nix-build")
+        .arg("-A")
+        .arg("web.atward.indexHtml")
+        // ... assuming atward is at //web/atward ...
+        .arg("../..")
+        .output()
+        .expect(ERROR_MESSAGE);
+
+    if !output.status.success() {
+        eprintln!(
+            "{}\nNix output: {}",
+            ERROR_MESSAGE,
+            String::from_utf8_lossy(&output.stderr)
+        );
+        return;
+    }
+
+    let out_path = String::from_utf8(output.stdout)
+        .expect("Nix returned invalid output after building index page");
+
+    // Return an instruction to Cargo that will set the environment
+    // variable during rustc calls.
+    //
+    // https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-envvarvalue
+    println!("cargo:rustc-env={}={}", ATWARD_INDEX_HTML, out_path.trim());
+}
diff --git a/web/atward/default.nix b/web/atward/default.nix
new file mode 100644
index 000000000000..f3ed04345c32
--- /dev/null
+++ b/web/atward/default.nix
@@ -0,0 +1,8 @@
+{ depot, ... }:
+
+depot.third_party.naersk.buildPackage {
+  src = ./.;
+  override = x: {
+    ATWARD_INDEX_HTML = depot.web.atward.indexHtml;
+  };
+}
diff --git a/web/atward/indexHtml/default.nix b/web/atward/indexHtml/default.nix
new file mode 100644
index 000000000000..3af808b89831
--- /dev/null
+++ b/web/atward/indexHtml/default.nix
@@ -0,0 +1,99 @@
+{ depot, ... }:
+
+depot.web.tvl.template {
+  title = "atward";
+  content = ''
+    atward
+    ======
+
+    ----------
+
+    **atward** is [TVL's](https://tvl.fyi/) search
+    service. It can be configured as a browser search engine for easy
+    access to TVL bugs, code reviews, code paths and more.
+
+    ### Setting up atward
+
+    To configure atward, add a search engine to your browser with the
+    following search string: `https://at.tvl.fyi/?q=%s`
+    Consider setting a shortcut, for example **t** or **tvl**.
+    You can now quickly access TVL resources by typing something
+    like <kbd>t b/42</kbd> in your URL bar to get to the bug with ID
+    42.
+
+
+    ### Supported queries
+
+    The following query types are supported in atward:
+
+    * <kbd>b/42</kbd> - access bugs with ID 42
+    * <kbd>cl/3087</kbd> - access changelist with ID 3087
+    * <kbd>//web/atward</kbd> - open the **//web/atward** path in TVLs monorepo
+    * <kbd>r/3002</kbd> - access revision 3002 in cgit
+
+    When given a short host name (e.g. <kbd>todo</kbd> or
+    <kbd>cl</kbd>), atward will redirect to the appropriate `tvl.fyi`
+    domain.
+
+    ### Configuration
+
+    Some behaviour of atward can be configured by adding query
+    parameters to the search string:
+
+    * <kbd>cs=true</kbd> - use Sourcegraph instead of cgit to view code
+
+
+    In some browsers (like Firefox) users can not edit query
+    parameters for search engines. As an alternative configuration can
+    be supplied via cookies with the same names as the configuration
+    parameters.
+
+    The form below can set this configuration:
+    <form class="cheddar-callout cheddar-todo">
+      <input type="checkbox"
+             id="cs-setting"
+             name="cs-setting"
+             onchange="saveSetting(this, 'cs');">
+      <label for="cs-setting">Use Sourcegraph instead of cgit</label>
+    </form>
+
+    <noscript>
+      <p class="cheddar-callout cheddar-warning">
+        The form above only works with Javascript enabled. Only a few
+        lines of Javascript are used, and they are licensed under a
+        free-software license (MIT).
+      </p>
+    </noscript>
+
+    ### Source code
+
+    atward's source code lives at
+    [//web/atward](https://at.tvl.fyi/?q=%2F%2Fweb%2Fatward).
+  '';
+  extraHead = ''
+    <script>
+      /* Initialise the state of all settings. */
+      function loadSettings() {
+          loadSetting(document.getElementById('cs-setting'), 'cs');
+      }
+
+      /* Initialise the state of a setting from a cookie. */
+      function loadSetting(checkbox, name) {
+          if (document.cookie.split(';').some(function(cookie) {
+              return cookie.indexOf(`''${name}=true`) >= 0;
+          })) {
+              checkbox.checked = true;
+          }
+      }
+
+      /* Persist the state of a checkbox in a cookie */
+      function saveSetting(checkbox, name) {
+          console.log(`setting atward parameter '''''${name}' to ''${checkbox.checked.toString()}`);
+          document.cookie = `''${name}=''${checkbox.checked.toString()};`;
+      }
+
+      document.addEventListener('DOMContentLoaded', loadSettings);
+    </script>
+    <link rel="search" type="application/opensearchdescription+xml" title="TVL Search" href="https://at.tvl.fyi/opensearch.xml">
+  '';
+}
diff --git a/web/atward/src/main.rs b/web/atward/src/main.rs
new file mode 100644
index 000000000000..26d79cde1a1b
--- /dev/null
+++ b/web/atward/src/main.rs
@@ -0,0 +1,388 @@
+//! Atward implements TVL's redirection service, living at
+//! atward.tvl.fyi
+//!
+//! This service is designed to be added as a search engine to web
+//! browsers and attempts to send users to useful locations based on
+//! their search query (falling back to another search engine).
+use regex::Regex;
+use rouille::input::cookies;
+use rouille::{Request, Response};
+
+/// A query handler supported by atward. It consists of a pattern on
+/// which to match and trigger the query, and a function to execute
+/// that returns the target URL.
+struct Handler {
+    /// Regular expression on which to match the query string.
+    pattern: Regex,
+
+    /// Function to construct the target URL. If the pattern matches,
+    /// this is invoked with the captured matches and the entire URI.
+    ///
+    /// Returning `None` causes atward to fall through to the next
+    /// query (and eventually to the default search engine).
+    target: for<'s> fn(&Query, regex::Captures<'s>) -> Option<String>,
+}
+
+/// An Atward query supplied by a user.
+#[derive(Debug, PartialEq)]
+struct Query {
+    /// Query string itself.
+    query: String,
+
+    /// Should Sourcegraph be used instead of cgit?
+    cs: bool,
+}
+
+/// Helper function for setting a parameter based on a query
+/// parameter.
+fn query_setting(req: &Request, config: &mut bool, param: &str) {
+    match req.get_param(param) {
+        Some(s) if s == "true" => *config = true,
+        Some(s) if s == "false" => *config = false,
+        _ => {}
+    }
+}
+
+impl Query {
+    fn from_request(req: &Request) -> Option<Query> {
+        // First extract the actual search query ...
+        let mut query = match req.get_param("q") {
+            Some(query) => Query { query, cs: false },
+            None => return None,
+        };
+
+        // ... then apply settings to it. Settings in query parameters
+        // take precedence over cookies.
+        for cookie in cookies(req) {
+            match cookie {
+                ("cs", "true") => {
+                    query.cs = true;
+                }
+                _ => {}
+            }
+        }
+
+        query_setting(req, &mut query.cs, "cs");
+
+        Some(query)
+    }
+}
+
+#[cfg(test)]
+impl From<&str> for Query {
+    fn from(query: &str) -> Query {
+        Query {
+            query: query.to_string(),
+            cs: false,
+        }
+    }
+}
+
+/// Create a URL to a file (and, optionally, specific line) in cgit.
+fn cgit_url(path: &str) -> String {
+    if path.ends_with(".md") {
+        format!("https://code.tvl.fyi/about/{}", path)
+    } else {
+        format!("https://code.tvl.fyi/tree/{}", path)
+    }
+}
+
+/// Create a URL to a path in Sourcegraph.
+fn sourcegraph_path_url(path: &str) -> String {
+    format!("https://cs.tvl.fyi/depot/-/tree/{}", path)
+}
+/// Definition of all supported query handlers in atward.
+fn handlers() -> Vec<Handler> {
+    vec![
+        // Bug IDs (e.g. b/123)
+        Handler {
+            pattern: Regex::new("^b/(?P<bug>\\d+)$").unwrap(),
+            target: |_, captures| Some(format!("https://b.tvl.fyi/{}", &captures["bug"])),
+        },
+        // Changelists (e.g. cl/42)
+        Handler {
+            pattern: Regex::new("^cl/(?P<cl>\\d+)$").unwrap(),
+            target: |_, captures| Some(format!("https://cl.tvl.fyi/{}", &captures["cl"])),
+        },
+        // Non-parameterised short hostnames should redirect to $host.tvl.fyi
+        Handler {
+            pattern: Regex::new("^(?P<host>b|cl|cs|code|at|todo)$").unwrap(),
+            target: |_, captures| Some(format!("https://{}.tvl.fyi/", &captures["host"])),
+        },
+        // Depot revisions (e.g. r/3002)
+        Handler {
+            pattern: Regex::new("^r/(?P<rev>\\d+)$").unwrap(),
+            target: |_, captures| {
+                Some(format!(
+                    "https://code.tvl.fyi/commit/?id=refs/r/{}",
+                    &captures["rev"]
+                ))
+            },
+        },
+        // Depot paths (e.g. //web/atward or //ops/nixos/whitby/default.nix)
+        // TODO(tazjin): Add support for specifying lines in a query parameter
+        Handler {
+            pattern: Regex::new("^//(?P<path>[a-zA-Z].*)?$").unwrap(),
+            target: |query, captures| {
+                // Pass an empty string if the path is missing, to
+                // redirect to the depot root.
+                let path = captures.name("path").map(|m| m.as_str()).unwrap_or("");
+
+                if query.cs {
+                    Some(sourcegraph_path_url(path))
+                } else {
+                    Some(cgit_url(path))
+                }
+            },
+        },
+    ]
+}
+
+/// Attempt to match against all known query types, and return the
+/// destination URL if one is found.
+fn dispatch(handlers: &[Handler], query: &Query) -> Option<String> {
+    for handler in handlers {
+        if let Some(captures) = handler.pattern.captures(&query.query) {
+            if let Some(destination) = (handler.target)(query, captures) {
+                return Some(destination);
+            }
+        }
+    }
+
+    None
+}
+
+/// Return the opensearch.xml file which is required for adding atward
+/// as a search engine in Firefox.
+fn opensearch() -> Response {
+    Response::text(include_str!("opensearch.xml"))
+        .with_unique_header("Content-Type", "application/opensearchdescription+xml")
+}
+
+/// Render the atward index page which gives users some information
+/// about how to use the service.
+fn index() -> Response {
+    Response::html(include_str!(env!("ATWARD_INDEX_HTML")))
+}
+
+/// Render the fallback page which informs users that their query is
+/// unsupported.
+fn fallback() -> Response {
+    Response::text("error for emphasis that i am angery and the query whimchst i angery atward")
+        .with_status_code(404)
+}
+
+fn main() {
+    let queries = handlers();
+    let address = std::env::var("ATWARD_LISTEN_ADDRESS")
+        .expect("ATWARD_LISTEN_ADDRESS environment variable must be set");
+
+    rouille::start_server(&address, move |request| {
+        rouille::log(&request, std::io::stderr(), || {
+            if request.url() == "/opensearch.xml" {
+                return opensearch();
+            }
+
+            let query = match Query::from_request(&request) {
+                Some(q) => q,
+                None => return index(),
+            };
+
+            match dispatch(&queries, &query) {
+                None => fallback(),
+                Some(destination) => Response::redirect_303(destination),
+            }
+        })
+    });
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn bug_query() {
+        assert_eq!(
+            dispatch(&handlers(), &"b/42".into()),
+            Some("https://b.tvl.fyi/42".to_string())
+        );
+
+        assert_eq!(
+            dispatch(&handlers(), &"something only mentioning b/42".into()),
+            None,
+        );
+        assert_eq!(dispatch(&handlers(), &"b/invalid".into()), None,);
+    }
+
+    #[test]
+    fn cl_query() {
+        assert_eq!(
+            dispatch(&handlers(), &"cl/42".into()),
+            Some("https://cl.tvl.fyi/42".to_string())
+        );
+
+        assert_eq!(
+            dispatch(&handlers(), &"something only mentioning cl/42".into()),
+            None,
+        );
+        assert_eq!(dispatch(&handlers(), &"cl/invalid".into()), None,);
+    }
+
+    #[test]
+    fn depot_path_cgit_query() {
+        assert_eq!(
+            dispatch(&handlers(), &"//web/atward/default.nix".into()),
+            Some("https://code.tvl.fyi/tree/web/atward/default.nix".to_string()),
+        );
+
+        assert_eq!(
+            dispatch(&handlers(), &"//nix/readTree/README.md".into()),
+            Some("https://code.tvl.fyi/about/nix/readTree/README.md".to_string()),
+        );
+
+        assert_eq!(dispatch(&handlers(), &"/not/a/depot/path".into()), None);
+    }
+
+    #[test]
+    fn depot_path_sourcegraph_query() {
+        assert_eq!(
+            dispatch(
+                &handlers(),
+                &Query {
+                    query: "//web/atward/default.nix".to_string(),
+                    cs: true,
+                }
+            ),
+            Some("https://cs.tvl.fyi/depot/-/tree/web/atward/default.nix".to_string()),
+        );
+
+        assert_eq!(
+            dispatch(
+                &handlers(),
+                &Query {
+                    query: "/not/a/depot/path".to_string(),
+                    cs: true,
+                }
+            ),
+            None
+        );
+    }
+
+    #[test]
+    fn depot_root_cgit_query() {
+        assert_eq!(
+            dispatch(
+                &handlers(),
+                &Query {
+                    query: "//".to_string(),
+                    cs: false,
+                }
+            ),
+            Some("https://code.tvl.fyi/tree/".to_string()),
+        );
+    }
+
+    #[test]
+    fn plain_host_queries() {
+        assert_eq!(
+            dispatch(&handlers(), &"cs".into()),
+            Some("https://cs.tvl.fyi/".to_string()),
+        );
+
+        assert_eq!(
+            dispatch(&handlers(), &"cl".into()),
+            Some("https://cl.tvl.fyi/".to_string()),
+        );
+
+        assert_eq!(
+            dispatch(&handlers(), &"b".into()),
+            Some("https://b.tvl.fyi/".to_string()),
+        );
+
+        assert_eq!(
+            dispatch(&handlers(), &"todo".into()),
+            Some("https://todo.tvl.fyi/".to_string()),
+        );
+    }
+
+    #[test]
+    fn request_to_query() {
+        assert_eq!(
+            Query::from_request(&Request::fake_http("GET", "/?q=b%2F42", vec![], vec![]))
+                .expect("request should parse to a query"),
+            Query {
+                query: "b/42".to_string(),
+                cs: false,
+            },
+        );
+
+        assert_eq!(
+            Query::from_request(&Request::fake_http("GET", "/", vec![], vec![])),
+            None
+        );
+    }
+
+    #[test]
+    fn settings_from_cookie() {
+        assert_eq!(
+            Query::from_request(&Request::fake_http(
+                "GET",
+                "/?q=b%2F42",
+                vec![("Cookie".to_string(), "cs=true;".to_string())],
+                vec![]
+            ))
+            .expect("request should parse to a query"),
+            Query {
+                query: "b/42".to_string(),
+                cs: true,
+            },
+        );
+    }
+
+    #[test]
+    fn settings_from_query_parameter() {
+        assert_eq!(
+            Query::from_request(&Request::fake_http(
+                "GET",
+                "/?q=b%2F42&cs=true",
+                vec![],
+                vec![]
+            ))
+            .expect("request should parse to a query"),
+            Query {
+                query: "b/42".to_string(),
+                cs: true,
+            },
+        );
+
+        // Query parameter should override cookie
+        assert_eq!(
+            Query::from_request(&Request::fake_http(
+                "GET",
+                "/?q=b%2F42&cs=false",
+                vec![("Cookie".to_string(), "cs=true;".to_string())],
+                vec![]
+            ))
+            .expect("request should parse to a query"),
+            Query {
+                query: "b/42".to_string(),
+                cs: false,
+            },
+        );
+    }
+
+    #[test]
+    fn depot_revision_query() {
+        assert_eq!(
+            dispatch(&handlers(), &"r/3002".into()),
+            Some("https://code.tvl.fyi/commit/?id=refs/r/3002".to_string())
+        );
+
+        assert_eq!(
+            dispatch(&handlers(), &"something only mentioning r/3002".into()),
+            None,
+        );
+
+        assert_eq!(dispatch(&handlers(), &"r/invalid".into()), None,);
+    }
+}
diff --git a/web/atward/src/opensearch.xml b/web/atward/src/opensearch.xml
new file mode 100644
index 000000000000..6033987f5b4b
--- /dev/null
+++ b/web/atward/src/opensearch.xml
@@ -0,0 +1,8 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+  <ShortName>TVL</ShortName>
+  <Description>The Virus Lounge Search</Description>
+  <InputEncoding>UTF-8</InputEncoding>
+  <Url type="text/html" template="https://at.tvl.fyi/">
+    <Param name="q" value="{searchTerms}"/>
+  </Url>
+</OpenSearchDescription>