about summary refs log tree commit diff
path: root/ops
diff options
context:
space:
mode:
Diffstat (limited to 'ops')
-rw-r--r--ops/besadii/main.go16
-rw-r--r--ops/buildkite/steps-tvix.yml4
-rw-r--r--ops/buildkite/tvl.tf26
-rw-r--r--ops/dns/default.nix2
-rw-r--r--ops/gerrit-autosubmit/.gitignore1
-rw-r--r--ops/gerrit-autosubmit/Cargo.lock302
-rw-r--r--ops/gerrit-autosubmit/Cargo.toml12
-rw-r--r--ops/gerrit-autosubmit/default.nix7
-rw-r--r--ops/gerrit-autosubmit/src/main.rs194
-rw-r--r--ops/glesys/dns-nixery-dev.tf9
-rw-r--r--ops/glesys/dns-tvix-dev.tf54
-rw-r--r--ops/glesys/dns-tvl-fyi.tf14
-rw-r--r--ops/glesys/dns-tvl-su.tf16
-rw-r--r--ops/glesys/main.tf13
-rw-r--r--ops/journaldriver/Cargo.lock433
-rw-r--r--ops/journaldriver/Cargo.toml15
-rw-r--r--ops/journaldriver/default.nix2
-rw-r--r--ops/keycloak/clients.tf21
-rw-r--r--ops/keycloak/main.tf2
-rw-r--r--ops/machines/all-systems.nix4
-rw-r--r--ops/machines/nixery-01/default.nix40
-rw-r--r--ops/machines/sanduny/default.nix15
-rw-r--r--ops/machines/whitby/default.nix169
-rw-r--r--ops/modules/automatic-gc.nix5
-rw-r--r--ops/modules/btrfs-auto-scrub.nix25
-rw-r--r--ops/modules/clbot.nix4
-rw-r--r--ops/modules/depot-inbox.nix148
-rw-r--r--ops/modules/gerrit-autosubmit.nix43
-rw-r--r--ops/modules/gerrit-queue.nix52
-rw-r--r--ops/modules/irccat.nix2
-rw-r--r--ops/modules/josh.nix2
-rw-r--r--ops/modules/livegrep.nix106
-rw-r--r--ops/modules/monorepo-gerrit.nix14
-rw-r--r--ops/modules/nixery.nix5
-rw-r--r--ops/modules/oauth2_proxy.nix60
-rw-r--r--ops/modules/open_eid.nix59
-rw-r--r--ops/modules/panettone.nix11
-rw-r--r--ops/modules/quassel.nix6
-rw-r--r--ops/modules/teleirc.nix40
-rw-r--r--ops/modules/tvl-buildkite.nix5
-rw-r--r--ops/modules/tvl-headscale.nix62
-rw-r--r--ops/modules/tvl-users.nix37
-rw-r--r--ops/modules/v4l2loopback.nix12
-rw-r--r--ops/modules/www/auth.tvl.fyi.nix6
-rw-r--r--ops/modules/www/cl.tvl.fyi.nix4
-rw-r--r--ops/modules/www/code.tvl.fyi.nix47
-rw-r--r--ops/modules/www/grep.tvl.fyi.nix19
-rw-r--r--ops/modules/www/images.tvl.fyi.nix22
-rw-r--r--ops/modules/www/inbox.tvl.su.nix31
-rw-r--r--ops/modules/www/signup.tvl.fyi.nix (renamed from ops/modules/www/tvixbolt.tvl.su.nix)4
-rw-r--r--ops/modules/www/status.tvl.su.nix2
-rw-r--r--ops/modules/www/tazj.in.nix5
-rw-r--r--ops/modules/www/tvix.dev.nix46
-rw-r--r--ops/modules/www/volgasprint.org.nix15
-rw-r--r--ops/modules/www/wigglydonke.rs.nix2
-rw-r--r--ops/modules/yandex-cloud.nix78
-rw-r--r--ops/nixos.nix8
-rw-r--r--ops/pipelines/depot.nix11
-rw-r--r--ops/pipelines/static-pipeline.yaml22
-rw-r--r--ops/secrets/besadii.agebin1173 -> 1186 bytes
-rw-r--r--ops/secrets/buildkite-agent-token.agebin736 -> 743 bytes
-rw-r--r--ops/secrets/buildkite-graphql-token.age30
-rw-r--r--ops/secrets/buildkite-ssh-private-key.agebin1166 -> 1194 bytes
-rw-r--r--ops/secrets/clbot-ssh.agebin1090 -> 1162 bytes
-rw-r--r--ops/secrets/clbot.age28
-rw-r--r--ops/secrets/depot-inbox-imap.age15
-rw-r--r--ops/secrets/depot-replica-key.agebin1162 -> 1208 bytes
-rw-r--r--ops/secrets/gerrit-autosubmit.agebin0 -> 853 bytes
-rw-r--r--ops/secrets/gerrit-queue.age17
-rw-r--r--ops/secrets/gerrit-secrets.agebin895 -> 913 bytes
-rw-r--r--ops/secrets/grafana.age31
-rw-r--r--ops/secrets/irccat.agebin880 -> 825 bytes
-rw-r--r--ops/secrets/journaldriver.agebin3228 -> 3202 bytes
-rw-r--r--ops/secrets/keycloak-db.age28
-rw-r--r--ops/secrets/nix-cache-priv.agebin848 -> 786 bytes
-rw-r--r--ops/secrets/nix-cache-pub.age30
-rw-r--r--ops/secrets/oauth2_proxy.age16
-rw-r--r--ops/secrets/owothia.agebin754 -> 838 bytes
-rw-r--r--ops/secrets/panettone.age30
-rw-r--r--ops/secrets/secrets.nix26
-rw-r--r--ops/secrets/smtprelay.age30
-rw-r--r--ops/secrets/teleirc.agebin0 -> 1006 bytes
-rw-r--r--ops/secrets/tf-buildkite.agebin973 -> 943 bytes
-rw-r--r--ops/secrets/tf-glesys.agebin874 -> 959 bytes
-rw-r--r--ops/secrets/tf-keycloak.agebin1001 -> 962 bytes
-rw-r--r--ops/secrets/tvl-alerts-bot-telegram-token.age29
-rw-r--r--ops/terraform/README.md5
-rw-r--r--ops/terraform/deploy-nixos/README.md50
-rw-r--r--ops/terraform/deploy-nixos/main.tf113
-rwxr-xr-xops/terraform/deploy-nixos/nix-eval.sh47
-rwxr-xr-xops/terraform/deploy-nixos/nixos-copy.sh32
-rw-r--r--ops/users/default.nix80
-rw-r--r--ops/yandex-base-image/default.nix9
-rw-r--r--ops/yandex-cloud-rs/.gitignore5
-rw-r--r--ops/yandex-cloud-rs/Cargo.lock1368
-rw-r--r--ops/yandex-cloud-rs/Cargo.toml24
-rw-r--r--ops/yandex-cloud-rs/README.md49
-rw-r--r--ops/yandex-cloud-rs/build.rs43
-rw-r--r--ops/yandex-cloud-rs/default.nix22
-rw-r--r--ops/yandex-cloud-rs/examples/log-write.rs37
-rw-r--r--ops/yandex-cloud-rs/src/lib.rs108
101 files changed, 3989 insertions, 704 deletions
diff --git a/ops/besadii/main.go b/ops/besadii/main.go
index f850b53645..809acc29e8 100644
--- a/ops/besadii/main.go
+++ b/ops/besadii/main.go
@@ -19,7 +19,7 @@ import (
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log/syslog"
 	"net/http"
 	"net/mail"
@@ -130,7 +130,7 @@ func loadConfig() (*config, error) {
 		}
 	}
 
-	configJson, err := ioutil.ReadFile(configPath)
+	configJson, err := os.ReadFile(configPath)
 	if err != nil {
 		return nil, fmt.Errorf("failed to load besadii config: %w", err)
 	}
@@ -182,12 +182,12 @@ func linkToChange(cfg *config, changeId, patchset string) string {
 // updateGerrit posts a comment on a Gerrit CL to indicate the current build status.
 func updateGerrit(cfg *config, review reviewInput, changeId, patchset string) {
 	body, _ := json.Marshal(review)
-	reader := ioutil.NopCloser(bytes.NewReader(body))
+	reader := io.NopCloser(bytes.NewReader(body))
 
 	url := fmt.Sprintf("%s/a/changes/%s/revisions/%s/review", cfg.GerritUrl, changeId, patchset)
 	req, err := http.NewRequest("POST", url, reader)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "failed to create an HTTP request: %w", err)
+		fmt.Fprintf(os.Stderr, "failed to create an HTTP request: %s", err)
 		os.Exit(1)
 	}
 
@@ -196,12 +196,12 @@ func updateGerrit(cfg *config, review reviewInput, changeId, patchset string) {
 
 	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
-		fmt.Errorf("failed to update %s on %s: %w", cfg.GerritChangeName, cfg.GerritUrl, err)
+		fmt.Fprintf(os.Stderr, "failed to update %s on %s: %s", cfg.GerritChangeName, cfg.GerritUrl, err)
 	}
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		respBody, _ := ioutil.ReadAll(resp.Body)
+		respBody, _ := io.ReadAll(resp.Body)
 		fmt.Fprintf(os.Stderr, "received non-success response from Gerrit: %s (%v)", respBody, resp.Status)
 	} else {
 		fmt.Printf("Added CI status comment on %s", linkToChange(cfg, changeId, patchset))
@@ -241,7 +241,7 @@ func triggerBuild(cfg *config, log *syslog.Writer, trigger *buildTrigger) error
 	}
 
 	body, _ := json.Marshal(build)
-	reader := ioutil.NopCloser(bytes.NewReader(body))
+	reader := io.NopCloser(bytes.NewReader(body))
 
 	bkUrl := fmt.Sprintf("https://api.buildkite.com/v2/organizations/%s/pipelines/%s/builds", cfg.BuildkiteOrg, cfg.BuildkiteProject)
 	req, err := http.NewRequest("POST", bkUrl, reader)
@@ -259,7 +259,7 @@ func triggerBuild(cfg *config, log *syslog.Writer, trigger *buildTrigger) error
 	}
 	defer resp.Body.Close()
 
-	respBody, err := ioutil.ReadAll(resp.Body)
+	respBody, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return fmt.Errorf("failed to read Buildkite response body: %w", err)
 	}
diff --git a/ops/buildkite/steps-tvix.yml b/ops/buildkite/steps-tvix.yml
new file mode 100644
index 0000000000..a6e9f13b16
--- /dev/null
+++ b/ops/buildkite/steps-tvix.yml
@@ -0,0 +1,4 @@
+---
+steps:
+  - label: ":buildkite: Upload pipeline"
+    command: "buildkite-agent pipeline upload"
diff --git a/ops/buildkite/tvl.tf b/ops/buildkite/tvl.tf
index c789756b57..4c45909a0c 100644
--- a/ops/buildkite/tvl.tf
+++ b/ops/buildkite/tvl.tf
@@ -24,15 +24,25 @@ provider "buildkite" {
 }
 
 resource "buildkite_pipeline" "depot" {
-  name        = "depot"
-  description = "Run full CI pipeline of the depot, TVL's monorepo."
-  repository  = "https://cl.tvl.fyi/depot"
-  steps       = file("./steps-depot.yml")
+  name           = "depot"
+  description    = "Run full CI pipeline of the depot, TVL's monorepo."
+  repository     = "https://cl.tvl.fyi/depot"
+  steps          = file("./steps-depot.yml")
+  default_branch = "refs/heads/canon"
+}
+
+resource "buildkite_pipeline" "tvix" {
+  name           = "tvix"
+  description    = "Tvix, an exported subset of TVL depot"
+  repository     = "https://code.tvl.fyi/depot.git:workspace=views/tvix.git"
+  steps          = file("./steps-tvix.yml")
+  default_branch = "canon"
 }
 
 resource "buildkite_pipeline" "tvl_kit" {
-  name        = "tvl-kit"
-  description = "TVL Kit, an exported subset of TVL depot"
-  repository  = "https://code.tvl.fyi/depot.git:workspace=views/kit.git"
-  steps       = file("./steps-tvl-kit.yml")
+  name           = "tvl-kit"
+  description    = "TVL Kit, an exported subset of TVL depot"
+  repository     = "https://code.tvl.fyi/depot.git:workspace=views/kit.git"
+  steps          = file("./steps-tvl-kit.yml")
+  default_branch = "canon"
 }
diff --git a/ops/dns/default.nix b/ops/dns/default.nix
index ad6e136f27..33fe6d6fe7 100644
--- a/ops/dns/default.nix
+++ b/ops/dns/default.nix
@@ -2,7 +2,7 @@
 { depot, pkgs, ... }:
 
 let
-  checkZone = zone: file: pkgs.runCommandNoCC "${zone}-check" { } ''
+  checkZone = zone: file: pkgs.runCommand "${zone}-check" { } ''
     ${pkgs.bind}/bin/named-checkzone -i local ${zone} ${file} | tee $out
   '';
 
diff --git a/ops/gerrit-autosubmit/.gitignore b/ops/gerrit-autosubmit/.gitignore
new file mode 100644
index 0000000000..2f7896d1d1
--- /dev/null
+++ b/ops/gerrit-autosubmit/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/ops/gerrit-autosubmit/Cargo.lock b/ops/gerrit-autosubmit/Cargo.lock
new file mode 100644
index 0000000000..7516c74034
--- /dev/null
+++ b/ops/gerrit-autosubmit/Cargo.lock
@@ -0,0 +1,302 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crimp"
+version = "4087.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ead2c83f7d1f9b8e5a6f7a25985d0d1759ccd2cd72abb1eee2db65d05e12b39"
+dependencies = [
+ "curl",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "curl"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
+dependencies = [
+ "curl-sys",
+ "libc",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "socket2",
+ "winapi",
+]
+
+[[package]]
+name = "curl-sys"
+version = "0.4.68+curl-8.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+ "windows-sys",
+]
+
+[[package]]
+name = "gerrit-autosubmit"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "crimp",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "libc"
+version = "0.2.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+
+[[package]]
+name = "libz-sys"
+version = "1.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "schannel"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[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"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/ops/gerrit-autosubmit/Cargo.toml b/ops/gerrit-autosubmit/Cargo.toml
new file mode 100644
index 0000000000..fa51614a08
--- /dev/null
+++ b/ops/gerrit-autosubmit/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "gerrit-autosubmit"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.75"
+crimp = "4087.0.0"
+serde = { version = "1.0.193", features = ["derive"] }
+serde_json = "1.0.108"
diff --git a/ops/gerrit-autosubmit/default.nix b/ops/gerrit-autosubmit/default.nix
new file mode 100644
index 0000000000..f69a9248e3
--- /dev/null
+++ b/ops/gerrit-autosubmit/default.nix
@@ -0,0 +1,7 @@
+{ depot, pkgs, ... }:
+
+depot.third_party.naersk.buildPackage {
+  src = ./.;
+  nativeBuildInputs = [ pkgs.pkg-config ];
+  buildInputs = [ pkgs.openssl ];
+}
diff --git a/ops/gerrit-autosubmit/src/main.rs b/ops/gerrit-autosubmit/src/main.rs
new file mode 100644
index 0000000000..85d8a6af61
--- /dev/null
+++ b/ops/gerrit-autosubmit/src/main.rs
@@ -0,0 +1,194 @@
+//! gerrit-autosubmit connects to a Gerrit instance and submits the
+//! longest chain of changes in which all ancestors are ready and
+//! marked for autosubmit.
+//!
+//! It works like this:
+//!
+//! * it fetches all changes the Gerrit query API considers
+//!   submittable (i.e. all requirements fulfilled), and that have the
+//!   `Autosubmit` label set
+//!
+//! * it filters these changes down to those that are _actually_
+//!   submittable (in Gerrit API terms: that have an active Submit button)
+//!
+//! * it filters out those that would submit ancestors that are *not*
+//!   marked with the `Autosubmit` label
+//!
+//! * it submits the longest chain
+//!
+//! After that it just loops.
+
+use anyhow::{Context, Result};
+use std::collections::{BTreeMap, HashMap, HashSet};
+use std::{thread, time};
+
+mod gerrit {
+    use anyhow::{anyhow, Context, Result};
+    use serde::Deserialize;
+    use serde_json::Value;
+    use std::collections::HashMap;
+    use std::env;
+
+    pub struct Config {
+        gerrit_url: String,
+        username: String,
+        password: String,
+    }
+
+    impl Config {
+        pub fn from_env() -> Result<Self> {
+            Ok(Config {
+                gerrit_url: env::var("GERRIT_URL")
+                    .context("Gerrit base URL (no trailing slash) must be set in GERRIT_URL")?,
+                username: env::var("GERRIT_USERNAME")
+                    .context("Gerrit username must be set in GERRIT_USERNAME")?,
+                password: env::var("GERRIT_PASSWORD")
+                    .context("Gerrit password must be set in GERRIT_PASSWORD")?,
+            })
+        }
+    }
+
+    #[derive(Deserialize)]
+    pub struct ChangeInfo {
+        pub id: String,
+        pub revisions: HashMap<String, Value>,
+    }
+
+    #[derive(Deserialize)]
+    pub struct Action {
+        #[serde(default)]
+        pub enabled: bool,
+    }
+
+    const GERRIT_RESPONSE_PREFIX: &str = ")]}'";
+
+    pub fn get<T: serde::de::DeserializeOwned>(cfg: &Config, endpoint: &str) -> Result<T> {
+        let response = crimp::Request::get(&format!("{}/a{}", cfg.gerrit_url, endpoint))
+            .user_agent("gerrit-autosubmit")?
+            .basic_auth(&cfg.username, &cfg.password)?
+            .send()?
+            .error_for_status(|r| anyhow!("request failed with status {}", r.status))?;
+
+        let result: T = serde_json::from_slice(&response.body[GERRIT_RESPONSE_PREFIX.len()..])?;
+        Ok(result)
+    }
+
+    pub fn submit(cfg: &Config, change_id: &str) -> Result<()> {
+        crimp::Request::post(&format!(
+            "{}/a/changes/{}/submit",
+            cfg.gerrit_url, change_id
+        ))
+        .user_agent("gerrit-autosubmit")?
+        .basic_auth(&cfg.username, &cfg.password)?
+        .send()?
+        .error_for_status(|r| anyhow!("submit failed with status {}", r.status))?;
+
+        Ok(())
+    }
+}
+
+#[derive(Debug)]
+struct SubmittableChange {
+    id: String,
+    revision: String,
+}
+
+fn list_submittable(cfg: &gerrit::Config) -> Result<Vec<SubmittableChange>> {
+    let mut out = Vec::new();
+
+    let changes: Vec<gerrit::ChangeInfo> = gerrit::get(
+        &cfg,
+        "/changes/?q=is:submittable+label:Autosubmit+-is:wip+is:open&o=SKIP_DIFFSTAT&o=CURRENT_REVISION",
+    )
+    .context("failed to list submittable changes")?;
+
+    for change in changes.into_iter() {
+        out.push(SubmittableChange {
+            id: change.id,
+            revision: change
+                .revisions
+                .into_keys()
+                .next()
+                .context("change had no current revision")?,
+        });
+    }
+
+    Ok(out)
+}
+
+fn is_submittable(cfg: &gerrit::Config, change: &SubmittableChange) -> Result<bool> {
+    let response: HashMap<String, gerrit::Action> = gerrit::get(
+        cfg,
+        &format!(
+            "/changes/{}/revisions/{}/actions",
+            change.id, change.revision
+        ),
+    )
+    .context("failed to fetch actions for change")?;
+
+    match response.get("submit") {
+        None => Ok(false),
+        Some(action) => Ok(action.enabled),
+    }
+}
+
+fn submitted_with(cfg: &gerrit::Config, change_id: &str) -> Result<HashSet<String>> {
+    let response: Vec<gerrit::ChangeInfo> =
+        gerrit::get(cfg, &format!("/changes/{}/submitted_together", change_id))
+            .context("failed to fetch related change list")?;
+
+    Ok(response.into_iter().map(|c| c.id).collect())
+}
+
+fn autosubmit(cfg: &gerrit::Config) -> Result<bool> {
+    let mut submittable_changes: HashSet<String> = Default::default();
+
+    for change in list_submittable(&cfg)? {
+        if !is_submittable(&cfg, &change)? {
+            continue;
+        }
+
+        submittable_changes.insert(change.id.clone());
+    }
+
+    let mut chains: BTreeMap<usize, String> = Default::default();
+    for change_id in &submittable_changes {
+        let ancestors = submitted_with(&cfg, &change_id)?;
+        if ancestors.is_subset(&submittable_changes) {
+            chains.insert(
+                if ancestors.is_empty() {
+                    1
+                } else {
+                    ancestors.len()
+                },
+                change_id.clone(),
+            );
+        }
+    }
+
+    // BTreeMap::last_key_value gives us the value associated with the
+    // largest key, i.e. with the longest submittable chain of changes.
+    if let Some((count, change_id)) = chains.last_key_value() {
+        println!(
+            "submitting change {} with chain length {}",
+            change_id, count
+        );
+
+        gerrit::submit(cfg, change_id).context("while submitting")?;
+
+        Ok(true)
+    } else {
+        println!("nothing ready for autosubmit, waiting ...");
+        Ok(false)
+    }
+}
+
+fn main() -> Result<()> {
+    let cfg = gerrit::Config::from_env()?;
+
+    loop {
+        if !autosubmit(&cfg)? {
+            thread::sleep(time::Duration::from_secs(30));
+        }
+    }
+}
diff --git a/ops/glesys/dns-nixery-dev.tf b/ops/glesys/dns-nixery-dev.tf
index 53a421d20e..42bcec7e21 100644
--- a/ops/glesys/dns-nixery-dev.tf
+++ b/ops/glesys/dns-nixery-dev.tf
@@ -12,14 +12,7 @@ resource "glesys_dnsdomain_record" "nixery_dev_apex_A" {
   domain = glesys_dnsdomain.nixery_dev.id
   host   = "@"
   type   = "A"
-  data   = var.whitby_ipv4
-}
-
-resource "glesys_dnsdomain_record" "nixery_dev_apex_AAAA" {
-  domain = glesys_dnsdomain.nixery_dev.id
-  host   = "@"
-  type   = "AAAA"
-  data   = var.whitby_ipv6
+  data   = "51.250.51.78" # nixery-01.tvl.fyi
 }
 
 resource "glesys_dnsdomain_record" "nixery_dev_NS1" {
diff --git a/ops/glesys/dns-tvix-dev.tf b/ops/glesys/dns-tvix-dev.tf
new file mode 100644
index 0000000000..296532a02b
--- /dev/null
+++ b/ops/glesys/dns-tvix-dev.tf
@@ -0,0 +1,54 @@
+# DNS configuration for tvix.dev
+
+resource "glesys_dnsdomain" "tvix_dev" {
+  name = "tvix.dev"
+}
+
+resource "glesys_dnsdomain_record" "tvix_dev_apex_A" {
+  domain = glesys_dnsdomain.tvix_dev.id
+  host   = "@"
+  type   = "A"
+  data   = var.whitby_ipv4
+}
+
+resource "glesys_dnsdomain_record" "tvix_dev_apex_AAAA" {
+  domain = glesys_dnsdomain.tvix_dev.id
+  host   = "@"
+  type   = "AAAA"
+  data   = var.whitby_ipv6
+}
+
+resource "glesys_dnsdomain_record" "tvix_dev_bolt_CNAME" {
+  domain = glesys_dnsdomain.tvix_dev.id
+  host   = "bolt"
+  type   = "CNAME"
+  data   = "whitby.tvl.su."
+}
+
+resource "glesys_dnsdomain_record" "tvix_dev_docs_CNAME" {
+  domain = glesys_dnsdomain.tvix_dev.id
+  host   = "docs"
+  type   = "CNAME"
+  data   = "whitby.tvl.fyi."
+}
+
+resource "glesys_dnsdomain_record" "tvix_dev_NS1" {
+  domain = glesys_dnsdomain.tvix_dev.id
+  host   = "@"
+  type   = "NS"
+  data   = "ns1.namesystem.se."
+}
+
+resource "glesys_dnsdomain_record" "tvix_dev_NS2" {
+  domain = glesys_dnsdomain.tvix_dev.id
+  host   = "@"
+  type   = "NS"
+  data   = "ns2.namesystem.se."
+}
+
+resource "glesys_dnsdomain_record" "tvix_dev_NS3" {
+  domain = glesys_dnsdomain.tvix_dev.id
+  host   = "@"
+  type   = "NS"
+  data   = "ns3.namesystem.se."
+}
diff --git a/ops/glesys/dns-tvl-fyi.tf b/ops/glesys/dns-tvl-fyi.tf
index 26105e9fdc..9d7972c412 100644
--- a/ops/glesys/dns-tvl-fyi.tf
+++ b/ops/glesys/dns-tvl-fyi.tf
@@ -53,6 +53,13 @@ resource "glesys_dnsdomain_record" "tvl_fyi_whitby_AAAA" {
   data   = var.whitby_ipv6
 }
 
+resource "glesys_dnsdomain_record" "tvl_fyi_nixery-01_A" {
+  domain = glesys_dnsdomain.tvl_fyi.id
+  host   = "nixery-01"
+  type   = "A"
+  data   = "51.250.51.78"
+}
+
 # Explicit records for all services running on whitby
 resource "glesys_dnsdomain_record" "tvl_fyi_whitby_services" {
   domain   = glesys_dnsdomain.tvl_fyi.id
@@ -62,6 +69,13 @@ resource "glesys_dnsdomain_record" "tvl_fyi_whitby_services" {
   for_each = toset(local.whitby_services)
 }
 
+resource "glesys_dnsdomain_record" "tvl_fyi_net_CNAME" {
+  domain = glesys_dnsdomain.tvl_fyi.id
+  type   = "CNAME"
+  data   = "sanduny.tvl.su."
+  host   = "net"
+}
+
 # Google Domains mail forwarding configuration (no sending)
 resource "glesys_dnsdomain_record" "tvl_fyi_MX_5" {
   domain = glesys_dnsdomain.tvl_fyi.id
diff --git a/ops/glesys/dns-tvl-su.tf b/ops/glesys/dns-tvl-su.tf
index deeb9b39ea..f2286cf1cf 100644
--- a/ops/glesys/dns-tvl-su.tf
+++ b/ops/glesys/dns-tvl-su.tf
@@ -76,15 +76,19 @@ resource "glesys_dnsdomain_record" "tvl_su_whitby_services" {
   for_each = toset(local.whitby_services)
 }
 
-# Explicit records for corp-only services running on whitby.
-resource "glesys_dnsdomain_record" "tvl_su_corp_whitby_services" {
+# historical tvixbolt.tvl.su record, redirects to bolt.tvix.dev
+resource "glesys_dnsdomain_record" "tvix_su_tvixbolt_CNAME" {
   domain = glesys_dnsdomain.tvl_su.id
+  host   = "tvixbolt"
   type   = "CNAME"
   data   = "whitby.tvl.su."
-  host   = each.key
-  for_each = toset([
-    "tvixbolt",
-  ])
+}
+
+resource "glesys_dnsdomain_record" "tvl_su_inbox_CNAME" {
+  domain = glesys_dnsdomain.tvl_su.id
+  type   = "CNAME"
+  data   = "sanduny.tvl.su."
+  host   = "inbox.tvl.su."
 }
 
 resource "glesys_dnsdomain_record" "tvl_su_TXT_google_site" {
diff --git a/ops/glesys/main.tf b/ops/glesys/main.tf
index cd5ea9f4fd..ec6bb7c397 100644
--- a/ops/glesys/main.tf
+++ b/ops/glesys/main.tf
@@ -12,14 +12,18 @@ terraform {
   }
 
   backend "s3" {
-    endpoint = "https://objects.dc-sto1.glesys.net"
-    bucket   = "tvl-state"
-    key      = "terraform/tvl-glesys"
-    region   = "glesys"
+    endpoints = {
+      s3 = "https://objects.dc-sto1.glesys.net"
+    }
+    bucket = "tvl-state"
+    key    = "terraform/tvl-glesys"
+    region = "glesys"
 
     skip_credentials_validation = true
     skip_region_validation      = true
     skip_metadata_api_check     = true
+    skip_requesting_account_id  = true
+    skip_s3_checksum            = true
   }
 }
 
@@ -80,6 +84,7 @@ locals {
     "cs",
     "deploys",
     "images",
+    "signup",
     "static",
     "status",
     "todo",
diff --git a/ops/journaldriver/Cargo.lock b/ops/journaldriver/Cargo.lock
index 0b7afd9932..97bbe16ceb 100644
--- a/ops/journaldriver/Cargo.lock
+++ b/ops/journaldriver/Cargo.lock
@@ -4,59 +4,45 @@ version = 3
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.18"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
 dependencies = [
- "memchr 2.4.1",
+ "memchr",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.56"
+version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
-
-[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
 [[package]]
 name = "base64"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "bitflags"
-version = "1.3.2"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 
 [[package]]
-name = "cc"
-version = "1.0.73"
+name = "build-env"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+checksum = "e068f31938f954b695423ecaf756179597627d0828c0d3e48c0a722a8b23cf9e"
 
 [[package]]
-name = "cfg-if"
-version = "0.1.10"
+name = "cc"
+version = "1.0.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "cfg-if"
@@ -66,9 +52,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "crimp"
-version = "0.2.2"
+version = "4087.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbe8f9a320ad9c1a2e3bacedaa281587bd297fb10a10179fd39f777049d04794"
+checksum = "0ead2c83f7d1f9b8e5a6f7a25985d0d1759ccd2cd72abb1eee2db65d05e12b39"
 dependencies = [
  "curl",
  "serde",
@@ -77,19 +63,19 @@ dependencies = [
 
 [[package]]
 name = "cstr-argument"
-version = "0.0.2"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "514570a4b719329df37f93448a70df2baac553020d0eb43a8dfa9c1f5ba7b658"
+checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40"
 dependencies = [
- "cfg-if 0.1.10",
- "memchr 1.0.2",
+ "cfg-if",
+ "memchr",
 ]
 
 [[package]]
 name = "curl"
-version = "0.4.43"
+version = "0.4.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f"
+checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
 dependencies = [
  "curl-sys",
  "libc",
@@ -102,9 +88,9 @@ dependencies = [
 
 [[package]]
 name = "curl-sys"
-version = "0.4.53+curl-7.82.0"
+version = "0.4.68+curl-8.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8092905a5a9502c312f223b2775f57ec5c5b715f9a15ee9d2a8591d1364a0352"
+checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f"
 dependencies = [
  "cc",
  "libc",
@@ -112,29 +98,70 @@ dependencies = [
  "openssl-sys",
  "pkg-config",
  "vcpkg",
- "winapi",
+ "windows-sys",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
+dependencies = [
+ "powerfmt",
+ "serde",
 ]
 
 [[package]]
 name = "env_logger"
-version = "0.5.13"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
+checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
 dependencies = [
- "atty",
  "humantime",
+ "is-terminal",
  "log",
  "regex",
  "termcolor",
 ]
 
 [[package]]
+name = "errno"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
 name = "foreign-types"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
 dependencies = [
- "foreign-types-shared",
+ "foreign-types-shared 0.1.1",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared 0.3.1",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -144,32 +171,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
 [[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
 name = "hermit-abi"
-version = "0.1.19"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
 [[package]]
 name = "humantime"
-version = "1.3.0"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
- "quick-error",
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.1"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 
 [[package]]
 name = "journaldriver"
-version = "1.1.0"
+version = "5656.0.0"
 dependencies = [
  "anyhow",
  "crimp",
@@ -179,7 +217,6 @@ dependencies = [
  "medallion",
  "pkg-config",
  "serde",
- "serde_derive",
  "serde_json",
  "systemd",
  "time",
@@ -193,25 +230,26 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.123"
+version = "0.2.150"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
 
 [[package]]
 name = "libsystemd-sys"
-version = "0.2.2"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7b98458cd04a5c3aacba6f1a3a3c4b9abcb0ae4d66a055eee502e0d52dc226b"
+checksum = "d28ad38d7bee81aabd41201ee7d36df8d7f76aa0a455c77d5c365c4669b4b4b6"
 dependencies = [
+ "build-env",
  "libc",
  "pkg-config",
 ]
 
 [[package]]
 name = "libz-sys"
-version = "1.1.5"
+version = "1.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859"
+checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
 dependencies = [
  "cc",
  "libc",
@@ -220,13 +258,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "linux-raw-sys"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+
+[[package]]
 name = "log"
-version = "0.4.16"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
-dependencies = [
- "cfg-if 1.0.0",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
 [[package]]
 name = "medallion"
@@ -244,49 +285,43 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "memchr"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
-
-[[package]]
-name = "num_threads"
-version = "0.1.5"
+version = "2.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
-dependencies = [
- "libc",
-]
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
 [[package]]
 name = "once_cell"
-version = "1.10.0"
+version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
 name = "openssl"
-version = "0.10.38"
+version = "0.10.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
+checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33"
 dependencies = [
  "bitflags",
- "cfg-if 1.0.0",
- "foreign-types",
+ "cfg-if",
+ "foreign-types 0.3.2",
  "libc",
  "once_cell",
+ "openssl-macros",
  "openssl-sys",
 ]
 
 [[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "openssl-probe"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -294,11 +329,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.72"
+version = "0.9.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
+checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9"
 dependencies = [
- "autocfg",
  "cc",
  "libc",
  "pkg-config",
@@ -307,81 +341,105 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
-version = "0.3.25"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
 
 [[package]]
-name = "proc-macro2"
-version = "1.0.37"
+name = "powerfmt"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
-dependencies = [
- "unicode-xid",
-]
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
 
 [[package]]
-name = "quick-error"
-version = "1.2.3"
+name = "proc-macro2"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
 
 [[package]]
 name = "quote"
-version = "1.0.18"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "regex"
-version = "1.5.5"
+version = "1.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
 dependencies = [
  "aho-corasick",
- "memchr 2.4.1",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
  "regex-syntax",
 ]
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.25"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustix"
+version = "0.38.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
 
 [[package]]
 name = "ryu"
-version = "1.0.9"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "schannel"
-version = "0.1.19"
+version = "0.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
 dependencies = [
- "lazy_static",
- "winapi",
+ "windows-sys",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.136"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.136"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -390,9 +448,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.79"
+version = "1.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
 dependencies = [
  "itoa",
  "ryu",
@@ -401,9 +459,9 @@ dependencies = [
 
 [[package]]
 name = "socket2"
-version = "0.4.4"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
 dependencies = [
  "libc",
  "winapi",
@@ -411,22 +469,23 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.91"
+version = "2.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
 dependencies = [
  "proc-macro2",
  "quote",
- "unicode-xid",
+ "unicode-ident",
 ]
 
 [[package]]
 name = "systemd"
-version = "0.3.0"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b62a732355787f960c25536210ae0a981aca2e5dae9dab8491bdae39613ce48"
+checksum = "da95085b9c6eedbcf0b828302a3483a84bdbf772158e586b787092112008fd1f"
 dependencies = [
  "cstr-argument",
+ "foreign-types 0.5.0",
  "libc",
  "libsystemd-sys",
  "log",
@@ -435,37 +494,47 @@ dependencies = [
 
 [[package]]
 name = "termcolor"
-version = "1.1.3"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
 dependencies = [
  "winapi-util",
 ]
 
 [[package]]
 name = "time"
-version = "0.3.9"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
+checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
 dependencies = [
+ "deranged",
  "itoa",
- "libc",
- "num_threads",
+ "powerfmt",
  "serde",
+ "time-core",
  "time-macros",
 ]
 
 [[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
 name = "time-macros"
-version = "0.2.4"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
+checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
+dependencies = [
+ "time-core",
+]
 
 [[package]]
-name = "unicode-xid"
-version = "0.2.2"
+name = "unicode-ident"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "utf8-cstr"
@@ -497,9 +566,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
 dependencies = [
  "winapi",
 ]
@@ -509,3 +578,69 @@ name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/ops/journaldriver/Cargo.toml b/ops/journaldriver/Cargo.toml
index 4c32b893f7..65510d8705 100644
--- a/ops/journaldriver/Cargo.toml
+++ b/ops/journaldriver/Cargo.toml
@@ -1,21 +1,20 @@
 [package]
 name = "journaldriver"
-version = "1.1.0"
-authors = ["Vincent Ambo <mail@tazj.in>"]
+version = "5656.0.0"
+authors = ["Vincent Ambo <tazjin@tvl.su>"]
 license = "GPL-3.0-or-later"
 edition = "2021"
 
 [dependencies]
 anyhow = "1.0"
-crimp = "0.2"
-env_logger = "0.5"
-lazy_static = "1.0"
+crimp = "4087.0"
+env_logger = "0.10"
+lazy_static = "1.4"
 log = "0.4"
 medallion = "2.5"
-serde = "1.0"
-serde_derive = "1.0"
+serde = { version = "1.0", features = [ "derive" ] }
 serde_json = "1.0"
-systemd = "0.3"
+systemd = "0.5"
 time = { version = "0.3", features = [ "serde-well-known", "macros" ]}
 
 [build-dependencies]
diff --git a/ops/journaldriver/default.nix b/ops/journaldriver/default.nix
index a06a858fa1..2a3836c358 100644
--- a/ops/journaldriver/default.nix
+++ b/ops/journaldriver/default.nix
@@ -4,7 +4,7 @@ depot.third_party.naersk.buildPackage {
   src = ./.;
 
   buildInputs = with pkgs; [
-    pkgconfig
+    pkg-config
     openssl
     systemd.dev
   ];
diff --git a/ops/keycloak/clients.tf b/ops/keycloak/clients.tf
index 9506bd4aa0..178971ae36 100644
--- a/ops/keycloak/clients.tf
+++ b/ops/keycloak/clients.tf
@@ -70,27 +70,6 @@ resource "keycloak_saml_user_attribute_protocol_mapper" "buildkite_name" {
   saml_attribute_name_format = "Unspecified"
 }
 
-resource "keycloak_openid_client" "oauth2_proxy" {
-  realm_id              = keycloak_realm.tvl.id
-  client_id             = "oauth2-proxy"
-  name                  = "TVL OAuth2 Proxy"
-  enabled               = true
-  access_type           = "CONFIDENTIAL"
-  standard_flow_enabled = true
-
-  valid_redirect_uris = [
-    "https://login.tvl.fyi/oauth2/callback",
-    "http://localhost:4774/oauth2/callback",
-  ]
-}
-
-resource "keycloak_openid_audience_protocol_mapper" "oauth2_proxy_audience" {
-  realm_id                 = keycloak_realm.tvl.id
-  client_id                = keycloak_openid_client.oauth2_proxy.id
-  name                     = "oauth2-proxy-audience"
-  included_custom_audience = keycloak_openid_client.oauth2_proxy.client_id
-}
-
 resource "keycloak_openid_client" "panettone" {
   realm_id              = keycloak_realm.tvl.id
   client_id             = "panettone"
diff --git a/ops/keycloak/main.tf b/ops/keycloak/main.tf
index c18f4a1789..923ac19397 100644
--- a/ops/keycloak/main.tf
+++ b/ops/keycloak/main.tf
@@ -37,7 +37,7 @@ resource "keycloak_realm" "tvl" {
     from_display_name = "The Virus Lounge"
     host              = "127.0.0.1"
     port              = "25"
-    reply_to          = "depot@tazj.in"
+    reply_to          = "depot@tvl.su"
     ssl               = false
     starttls          = false
   }
diff --git a/ops/machines/all-systems.nix b/ops/machines/all-systems.nix
index f11b0e06b6..c4382fbddb 100644
--- a/ops/machines/all-systems.nix
+++ b/ops/machines/all-systems.nix
@@ -12,14 +12,16 @@
   zamalek
 ]) ++
 
-(with depot.users.grfn.system.system; [
+(with depot.users.aspen.system.system; [
   yeren
   mugwump
   ogopogo
+  lusca
 ]) ++
 
 (with depot.users.wpcarro.nixos; [
   ava
+  kyoko
   marcus
   tarasco
 ])
diff --git a/ops/machines/nixery-01/default.nix b/ops/machines/nixery-01/default.nix
new file mode 100644
index 0000000000..c99db214d8
--- /dev/null
+++ b/ops/machines/nixery-01/default.nix
@@ -0,0 +1,40 @@
+# nixery.dev backing host in ru-central1-b
+{ depot, lib, pkgs, ... }: # readTree options
+{ config, ... }: # passed by module system
+
+let
+  mod = name: depot.path.origSrc + ("/ops/modules/" + name);
+in
+{
+  imports = [
+    (mod "known-hosts.nix")
+    (mod "nixery.nix")
+    (mod "tvl-users.nix")
+    (mod "www/nixery.dev.nix")
+    (mod "yandex-cloud.nix")
+
+    (depot.third_party.agenix.src + "/modules/age.nix")
+  ];
+
+  networking = {
+    hostName = "nixery-01";
+    domain = "tvl.fyi";
+    firewall.allowedTCPPorts = [ 22 80 443 ];
+  };
+
+  security.sudo.extraRules = lib.singleton {
+    groups = [ "wheel" ];
+    commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
+  };
+
+  services.depot.nixery.enable = true;
+
+  # Automatically collect garbage from the Nix store.
+  services.depot.automatic-gc = {
+    enable = true;
+    interval = "1 hour";
+    diskThreshold = 25; # GiB
+    maxFreed = 150; # GiB
+    preserveGenerations = "30d";
+  };
+}
diff --git a/ops/machines/sanduny/default.nix b/ops/machines/sanduny/default.nix
index 886a3a1be7..af2dfb02a5 100644
--- a/ops/machines/sanduny/default.nix
+++ b/ops/machines/sanduny/default.nix
@@ -15,12 +15,16 @@ in
 {
   imports = [
     (mod "cgit.nix")
+    (mod "depot-inbox.nix")
     (mod "depot-replica.nix")
     (mod "journaldriver.nix")
     (mod "known-hosts.nix")
     (mod "tvl-cache.nix")
+    (mod "tvl-headscale.nix")
     (mod "tvl-users.nix")
+    (mod "www/inbox.tvl.su.nix")
     (mod "www/self-redirect.nix")
+    (mod "www/volgasprint.org.nix")
   ];
 
   networking = {
@@ -69,6 +73,13 @@ in
   services.openssh.enable = true;
   services.fail2ban.enable = true;
 
+  # Run tailscale for the TVL net.tvl.fyi network.
+  # tailscale up --login-server https://net.tvl.fyi --accept-dns=false --advertise-exit-node
+  services.tailscale = {
+    enable = true;
+    useRoutingFeatures = "server"; # for exit-node usage
+  };
+
   # Automatically collect garbage from the Nix store.
   services.depot.automatic-gc = {
     enable = true;
@@ -87,13 +98,15 @@ in
     repo = "/var/lib/depot";
   };
 
+  # Serve public-inbox ...
+  services.depot.inbox.enable = true;
+
   time.timeZone = "UTC";
 
   # GRUB does not actually need to be installed on disk; Bitfolk have
   # their own way of booting systems as long as config is in place.
   boot.loader.grub.device = "nodev";
   boot.loader.grub.enable = true;
-  boot.loader.grub.version = 2;
   boot.initrd.availableKernelModules = [ "xen_blkfront" ];
 
   hardware.cpu.intel.updateMicrocode = true;
diff --git a/ops/machines/whitby/default.nix b/ops/machines/whitby/default.nix
index a7688423cb..41391c8c0b 100644
--- a/ops/machines/whitby/default.nix
+++ b/ops/machines/whitby/default.nix
@@ -12,20 +12,20 @@ in
     (mod "atward.nix")
     (mod "cgit.nix")
     (mod "clbot.nix")
-    (mod "gerrit-queue.nix")
+    (mod "gerrit-autosubmit.nix")
     (mod "irccat.nix")
     (mod "josh.nix")
     (mod "journaldriver.nix")
     (mod "known-hosts.nix")
+    (mod "livegrep.nix")
     (mod "monorepo-gerrit.nix")
-    (mod "nixery.nix")
-    (mod "oauth2_proxy.nix")
     (mod "owothia.nix")
     (mod "panettone.nix")
     (mod "paroxysm.nix")
     (mod "restic.nix")
     (mod "smtprelay.nix")
     (mod "sourcegraph.nix")
+    (mod "teleirc.nix")
     (mod "tvl-buildkite.nix")
     (mod "tvl-slapd/default.nix")
     (mod "tvl-users.nix")
@@ -37,18 +37,19 @@ in
     (mod "www/code.tvl.fyi.nix")
     (mod "www/cs.tvl.fyi.nix")
     (mod "www/deploys.tvl.fyi.nix")
-    (mod "www/images.tvl.fyi.nix")
-    (mod "www/nixery.dev.nix")
     (mod "www/self-redirect.nix")
+    (mod "www/signup.tvl.fyi.nix")
     (mod "www/static.tvl.fyi.nix")
     (mod "www/status.tvl.su.nix")
-    (mod "www/tazj.in.nix")
     (mod "www/todo.tvl.fyi.nix")
-    (mod "www/tvixbolt.tvl.su.nix")
+    (mod "www/tvix.dev.nix")
     (mod "www/tvl.fyi.nix")
     (mod "www/tvl.su.nix")
     (mod "www/wigglydonke.rs.nix")
 
+    # experimental!
+    (mod "www/grep.tvl.fyi.nix")
+
     (depot.third_party.agenix.src + "/modules/age.nix")
   ];
 
@@ -58,7 +59,7 @@ in
   };
 
   boot = {
-    tmpOnTmpfs = true;
+    tmp.useTmpfs = true;
     kernelModules = [ "kvm-amd" ];
     supportedFilesystems = [ "zfs" ];
 
@@ -83,7 +84,7 @@ in
           authorizedKeys =
             depot.users.tazjin.keys.all
             ++ depot.users.lukegb.keys.all
-            ++ [ depot.users.grfn.keys.whitby ];
+            ++ [ depot.users.aspen.keys.whitby ];
 
           hostKeys = [
             /etc/secrets/initrd_host_ed25519_key
@@ -104,7 +105,6 @@ in
 
     loader.grub = {
       enable = true;
-      version = 2;
       efiSupport = true;
       efiInstallAsRemovable = true;
       device = "/dev/disk/by-id/nvme-SAMSUNG_MZQLB1T9HAJR-00007_S439NA0N201620";
@@ -190,7 +190,7 @@ in
       secret-key-files = "/run/agenix/nix-cache-priv";
 
       trusted-users = [
-        "grfn"
+        "aspen"
         "lukegb"
         "tazjin"
         "sterni"
@@ -202,7 +202,7 @@ in
       keys = with depot.users;
         tazjin.keys.all
         ++ lukegb.keys.all
-        ++ [ grfn.keys.whitby ]
+        ++ [ aspen.keys.whitby ]
         ++ sterni.keys.all
       ;
     };
@@ -212,8 +212,10 @@ in
   programs.mosh.enable = true;
   services.openssh = {
     enable = true;
-    passwordAuthentication = false;
-    challengeResponseAuthentication = false;
+    settings = {
+      PasswordAuthentication = false;
+      KbdInteractiveAuthentication = false;
+    };
   };
 
   # Configure secrets for services that need them.
@@ -223,15 +225,15 @@ in
     in
     {
       clbot.file = secretFile "clbot";
-      gerrit-queue.file = secretFile "gerrit-queue";
+      gerrit-autosubmit.file = secretFile "gerrit-autosubmit";
       grafana.file = secretFile "grafana";
       irccat.file = secretFile "irccat";
       keycloak-db.file = secretFile "keycloak-db";
       nix-cache-priv.file = secretFile "nix-cache-priv";
-      oauth2_proxy.file = secretFile "oauth2_proxy";
       owothia.file = secretFile "owothia";
       panettone.file = secretFile "panettone";
       smtprelay.file = secretFile "smtprelay";
+      teleirc.file = secretFile "teleirc";
 
       buildkite-agent-token = {
         file = secretFile "buildkite-agent-token";
@@ -345,7 +347,7 @@ in
   # Start the Gerrit->IRC bot
   services.depot.clbot = {
     enable = true;
-    channels = [ "#tvl" ];
+    channels = [ "#tvix-dev" "#tvl" ];
 
     # See //fun/clbot for details.
     flags = {
@@ -370,6 +372,9 @@ in
     # Run a SourceGraph code search instance
     sourcegraph.enable = true;
 
+    # Run a livegrep code search instance
+    livegrep.enable = true;
+
     # Run the Panettone issue tracker
     panettone = {
       enable = true;
@@ -407,12 +412,12 @@ in
       };
     };
 
+    # Run the Telegram<>IRC bridge for Volga Sprint.
+    teleirc.enable = true;
+
     # Run atward, the search engine redirection thing.
     atward.enable = true;
 
-    # Run a Nixery instance
-    nixery.enable = true;
-
     # Run cgit & josh to serve git
     cgit = {
       enable = true;
@@ -432,15 +437,13 @@ in
     };
 
     # Run autosubmit bot for Gerrit
-    gerrit-queue.enable = true;
-
-    # Run oauth2_proxy for internal service auth
-    oauth2_proxy.enable = true;
+    gerrit-autosubmit.enable = true;
   };
 
   services.postgresql = {
     enable = true;
     enableTCPIP = true;
+    package = pkgs.postgresql_16;
 
     authentication = lib.mkForce ''
       local all all trust
@@ -456,9 +459,7 @@ in
 
     ensureUsers = [{
       name = "panettone";
-      ensurePermissions = {
-        "DATABASE panettone" = "ALL PRIVILEGES";
-      };
+      ensureDBOwnership = true;
     }];
   };
 
@@ -548,70 +549,52 @@ in
 
   services.grafana = {
     enable = true;
-    port = 4723; # "graf" on phone keyboard
-    domain = "status.tvl.su";
-    rootUrl = "https://status.tvl.su";
-    analytics.reporting.enable = false;
-    extraOptions =
-      let
-        options = {
-          auth = {
-            generic_oauth = {
-              enabled = true;
-              client_id = "grafana";
-              scopes = "openid profile email";
-              name = "TVL";
-              email_attribute_path = "mail";
-              login_attribute_path = "sub";
-              name_attribute_path = "displayName";
-              auth_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/auth";
-              token_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/token";
-              api_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/userinfo";
-
-              # Give lukegb, grfn, tazjin "Admin" rights.
-              role_attribute_path = "((sub == 'lukegb' || sub == 'grfn' || sub == 'tazjin') && 'Admin') || 'Editor'";
-
-              # Allow creating new Grafana accounts from OAuth accounts.
-              allow_sign_up = true;
-            };
-
-            anonymous = {
-              enabled = true;
-              org_name = "The Virus Lounge";
-              org_role = "Viewer";
-            };
-
-            basic.enabled = false;
-            oauth_auto_login = true;
-            disable_login_form = true;
-          };
-        };
-        inherit (builtins) typeOf replaceStrings listToAttrs concatLists;
-        inherit (lib) toUpper mapAttrsToList nameValuePair concatStringsSep;
-
-        # Take ["auth" "generic_oauth" "enabled"] and turn it into OPTIONS_GENERIC_OAUTH_ENABLED.
-        encodeName = raw: replaceStrings [ "." ] [ "_" ] (toUpper (concatStringsSep "_" raw));
-
-        # Turn an option value into a string, but we want bools to be sensible strings and not "1" or "".
-        optionToString = value:
-          if (typeOf value) == "bool" then
-            if value then "true" else "false"
-          else builtins.toString value;
-
-        # Turn an nested options attrset into a flat listToAttrs-compatible list.
-        encodeOptions = prefix: inp: concatLists (mapAttrsToList
-          (name: value:
-            if (typeOf value) == "set"
-            then encodeOptions (prefix ++ [ name ]) value
-            else [ (nameValuePair (encodeName (prefix ++ [ name ])) (optionToString value)) ]
-          )
-          inp);
-      in
-      listToAttrs (encodeOptions [ ] options);
+
+    settings = {
+      server = {
+        http_port = 4723; # "graf" on phone keyboard
+        domain = "status.tvl.su";
+        root_url = "https://status.tvl.su";
+      };
+
+      analytics.reporting_enabled = false;
+
+      "auth.generic_oauth" = {
+        enabled = true;
+        client_id = "grafana";
+        scopes = "openid profile email";
+        name = "TVL";
+        email_attribute_path = "mail";
+        login_attribute_path = "sub";
+        name_attribute_path = "displayName";
+        auth_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/auth";
+        token_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/token";
+        api_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/userinfo";
+
+        # Give lukegb, aspen, tazjin "Admin" rights.
+        role_attribute_path = "((sub == 'lukegb' || sub == 'aspen' || sub == 'tazjin') && 'Admin') || 'Editor'";
+
+        # Allow creating new Grafana accounts from OAuth accounts.
+        allow_sign_up = true;
+      };
+
+      "auth.anonymous" = {
+        enabled = true;
+        org_name = "The Virus Lounge";
+        org_role = "Viewer";
+      };
+
+      "auth.basic".enabled = false;
+
+      auth = {
+        oauth_auto_login = true;
+        disable_login_form = true;
+      };
+    };
 
     provision = {
       enable = true;
-      datasources = [{
+      datasources.settings.datasources = [{
         name = "Prometheus";
         type = "prometheus";
         url = "http://localhost:9090";
@@ -624,9 +607,9 @@ in
 
   services.keycloak = {
     enable = true;
-    httpPort = "5925"; # "kycl"
 
     settings = {
+      http-port = 5925; # kycl
       hostname = "auth.tvl.fyi";
       http-relative-path = "/auth";
       proxy = "edge";
@@ -639,6 +622,12 @@ in
     };
   };
 
+  # Join TVL Tailscale network at net.tvl.fyi
+  services.tailscale = {
+    enable = true;
+    useRoutingFeatures = "server"; # for exit-node usage
+  };
+
   # Allow Keycloak access to the LDAP module by forcing in the JVM
   # configuration
   systemd.services.keycloak.environment.PREPEND_JAVA_OPTS =
@@ -662,5 +651,7 @@ in
     };
   };
 
+  zramSwap.enable = true;
+
   system.stateVersion = "20.03";
 }
diff --git a/ops/modules/automatic-gc.nix b/ops/modules/automatic-gc.nix
index ad53a63f7f..003f160919 100644
--- a/ops/modules/automatic-gc.nix
+++ b/ops/modules/automatic-gc.nix
@@ -13,6 +13,11 @@ let
   gcScript = pkgs.writeShellScript "automatic-nix-gc" ''
     set -ueo pipefail
 
+    if [ -e /run/stop-automatic-gc ]; then
+      echo "GC is disabled through /run/stop-automatic-gc"
+      exit 0
+    fi
+
     readonly MIN_THRESHOLD_KIB="${toString (GiBtoKiB cfg.diskThreshold)}"
     readonly MAX_FREED_BYTES="${toString (GiBtoBytes cfg.maxFreed)}"
     readonly GEN_THRESHOLD="${cfg.preserveGenerations}"
diff --git a/ops/modules/btrfs-auto-scrub.nix b/ops/modules/btrfs-auto-scrub.nix
new file mode 100644
index 0000000000..748bb75c5f
--- /dev/null
+++ b/ops/modules/btrfs-auto-scrub.nix
@@ -0,0 +1,25 @@
+# Automatically performs a scrub on all btrfs filesystems configured in
+# `config.fileSystems` on a daily schedule (by default). Activated by importing.
+{ config, lib, ... }:
+
+{
+  config = {
+    services = {
+      btrfs.autoScrub = {
+        enable = true;
+        interval = lib.mkDefault "*-*-* 03:30:00";
+        # gather all btrfs fileSystems, extra ones can be added via the NixOS
+        # module merging mechanism, of course.
+        fileSystems = lib.concatLists (
+          lib.mapAttrsToList
+            (
+              _:
+              { fsType, mountPoint, ... }:
+              if fsType == "btrfs" then [ mountPoint ] else [ ]
+            )
+            config.fileSystems
+        );
+      };
+    };
+  };
+}
diff --git a/ops/modules/clbot.nix b/ops/modules/clbot.nix
index 84575ed072..bdddff6c81 100644
--- a/ops/modules/clbot.nix
+++ b/ops/modules/clbot.nix
@@ -3,7 +3,7 @@
 
 let
   inherit (builtins) attrValues concatStringsSep mapAttrs readFile;
-  inherit (pkgs) runCommandNoCC;
+  inherit (pkgs) runCommand;
 
   inherit (lib)
     listToAttrs
@@ -21,7 +21,7 @@ let
       (attrValues (mapAttrs (key: value: "-${key} \"${toString value}\"") flags));
 
   # Escapes a unit name for use in systemd
-  systemdEscape = name: removeSuffix "\n" (readFile (runCommandNoCC "unit-name" { } ''
+  systemdEscape = name: removeSuffix "\n" (readFile (runCommand "unit-name" { } ''
     ${pkgs.systemd}/bin/systemd-escape '${name}' >> $out
   ''));
 
diff --git a/ops/modules/depot-inbox.nix b/ops/modules/depot-inbox.nix
new file mode 100644
index 0000000000..14fc646a9a
--- /dev/null
+++ b/ops/modules/depot-inbox.nix
@@ -0,0 +1,148 @@
+# public-inbox configuration for depot@tvl.su
+#
+# The account itself is a Yandex 360 account in the tvl.su organisation, which
+# is accessed via IMAP. Yandex takes care of spam filtering for us, so there is
+# no particular SpamAssassin or other configuration.
+{ config, depot, lib, pkgs, ... }:
+
+let
+  cfg = config.services.depot.inbox;
+
+  imapConfig = pkgs.writeText "offlineimaprc" ''
+    [general]
+    accounts = depot
+
+    [Account depot]
+    localrepository = Local
+    remoterepository = Remote
+
+    [Repository Local]
+    type = Maildir
+    localfolders = /var/lib/public-inbox/depot-imap
+
+    [Repository Remote]
+    type = IMAP
+    ssl = yes
+    sslcacertfile = /etc/ssl/certs/ca-bundle.crt
+    remotehost = imap.yandex.ru
+    remoteuser = depot@tvl.su
+    remotepassfile = /var/run/agenix/depot-inbox-imap
+  '';
+in
+{
+  options.services.depot.inbox = with lib; {
+    enable = mkEnableOption "Enable public-inbox for depot@tvl.su";
+
+    depotPath = mkOption {
+      description = "path to local depot replica";
+      type = types.str;
+      default = "/var/lib/depot";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # Having nginx *and* other services use ACME certificates for the
+    # same hostname is unsupported in NixOS without resorting to doing
+    # all ACME configuration manually.
+    #
+    # To work around this, we duplicate the TLS certificate used by
+    # nginx to a location that is readable by public-inbox daemons.
+    systemd.services.inbox-cert-sync = {
+      startAt = "daily";
+
+      script = ''
+        ${pkgs.coreutils}/bin/install -D -g ${config.users.groups."public-inbox".name} -m 0440 \
+          /var/lib/acme/inbox.tvl.su/fullchain.pem /var/lib/public-inbox/tls/fullchain.pem
+
+        ${pkgs.coreutils}/bin/install -D -g ${config.users.groups."public-inbox".name} -m 0440 \
+          /var/lib/acme/inbox.tvl.su/key.pem /var/lib/public-inbox/tls/key.pem
+      '';
+    };
+
+    services.public-inbox = {
+      enable = true;
+
+      http.enable = true;
+      http.port = 8053;
+
+      imap = {
+        enable = true;
+        port = 993;
+        cert = "/var/lib/public-inbox/tls/fullchain.pem";
+        key = "/var/lib/public-inbox/tls/key.pem";
+      };
+
+      nntp = {
+        enable = true;
+        port = 563;
+        cert = "/var/lib/public-inbox/tls/fullchain.pem";
+        key = "/var/lib/public-inbox/tls/key.pem";
+      };
+
+      inboxes.depot = rec {
+        address = [
+          "depot@tvl.su" # primary address
+          "depot@tazj.in" # legacy address
+        ];
+
+        description = "TVL depot development (mail to depot@tvl.su)";
+        coderepo = [ "depot" ];
+        url = "https://inbox.tvl.su/depot";
+
+        watch = [
+          "maildir:/var/lib/public-inbox/depot-imap/INBOX/"
+        ];
+
+        newsgroup = "su.tvl.depot";
+      };
+
+      settings.coderepo.depot = {
+        dir = cfg.depotPath;
+        cgitUrl = "https://code.tvl.fyi";
+      };
+
+      settings.publicinbox = {
+        wwwlisting = "all";
+        nntpserver = [ "inbox.tvl.su" ];
+        imapserver = [ "inbox.tvl.su" ];
+
+        depot.obfuscate = true;
+        noObfuscate = [
+          "tvl.su"
+          "tvl.fyi"
+        ];
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [
+      993 # imap
+      563 # nntp
+    ];
+
+    age.secrets.depot-inbox-imap = {
+      file = depot.ops.secrets."depot-inbox-imap.age";
+      mode = "0440";
+      group = config.users.groups."public-inbox".name;
+    };
+
+    systemd.services.offlineimap-depot = {
+      description = "download mail for depot@tvl.su";
+      wantedBy = [ "multi-user.target" ];
+      startAt = "minutely";
+
+      script = ''
+        mkdir -p /var/lib/public-inbox/depot-imap
+        ${pkgs.offlineimap}/bin/offlineimap -c ${imapConfig}
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+
+        # Run in the same user context as public-inbox itself to avoid
+        # permissions trouble.
+        User = config.users.users."public-inbox".name;
+        Group = config.users.groups."public-inbox".name;
+      };
+    };
+  };
+}
diff --git a/ops/modules/gerrit-autosubmit.nix b/ops/modules/gerrit-autosubmit.nix
new file mode 100644
index 0000000000..34342c8d55
--- /dev/null
+++ b/ops/modules/gerrit-autosubmit.nix
@@ -0,0 +1,43 @@
+# Configuration for the Gerrit autosubmit bot (//ops/gerrit-autosubmit)
+{ depot, pkgs, config, lib, ... }:
+
+let
+  cfg = config.services.depot.gerrit-autosubmit;
+  description = "gerrit-autosubmit - autosubmit bot for Gerrit";
+  mkStringOption = default: lib.mkOption {
+    inherit default;
+    type = lib.types.str;
+  };
+in
+{
+  options.services.depot.gerrit-autosubmit = {
+    enable = lib.mkEnableOption description;
+    gerritUrl = mkStringOption "https://cl.tvl.fyi";
+
+    secretsFile = with lib; mkOption {
+      description = "Path to a systemd EnvironmentFile containing secrets";
+      default = config.age.secretsDir + "/gerrit-autosubmit";
+      type = types.str;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.gerrit-autosubmit = {
+      inherit description;
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = "${depot.ops.gerrit-autosubmit}/bin/gerrit-autosubmit";
+        DynamicUser = true;
+        Restart = "always";
+        EnvironmentFile = cfg.secretsFile;
+      };
+
+      environment = {
+        GERRIT_URL = cfg.gerritUrl;
+      };
+    };
+  };
+}
diff --git a/ops/modules/gerrit-queue.nix b/ops/modules/gerrit-queue.nix
deleted file mode 100644
index 4468bcf1c5..0000000000
--- a/ops/modules/gerrit-queue.nix
+++ /dev/null
@@ -1,52 +0,0 @@
-# Configuration for the Gerrit autosubmit bot (//third_party/gerrit-queue)
-{ depot, pkgs, config, lib, ... }:
-
-let
-  cfg = config.services.depot.gerrit-queue;
-  description = "gerrit-queue - autosubmit bot for Gerrit";
-  mkStringOption = default: lib.mkOption {
-    inherit default;
-    type = lib.types.str;
-  };
-in
-{
-  options.services.depot.gerrit-queue = {
-    enable = lib.mkEnableOption description;
-    gerritUrl = mkStringOption "https://cl.tvl.fyi";
-    gerritProject = mkStringOption "depot";
-    gerritBranch = mkStringOption "canon";
-
-    interval = with lib; mkOption {
-      type = types.int;
-      default = 60;
-      description = "Interval (in seconds) for submit queue checks";
-    };
-
-    secretsFile = with lib; mkOption {
-      description = "Path to a systemd EnvironmentFile containing secrets";
-      default = config.age.secretsDir + "/gerrit-queue";
-      type = types.str;
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.gerrit-queue = {
-      inherit description;
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        ExecStart = "${depot.third_party.gerrit-queue}/bin/gerrit-queue";
-        DynamicUser = true;
-        Restart = "always";
-        EnvironmentFile = cfg.secretsFile;
-      };
-
-      environment = {
-        GERRIT_URL = cfg.gerritUrl;
-        GERRIT_PROJECT = cfg.gerritProject;
-        GERRIT_BRANCH = cfg.gerritBranch;
-        SUBMIT_QUEUE_TRIGGER_INTERVAL = toString cfg.interval;
-      };
-    };
-  };
-}
diff --git a/ops/modules/irccat.nix b/ops/modules/irccat.nix
index 0819c52a8d..2263118d99 100644
--- a/ops/modules/irccat.nix
+++ b/ops/modules/irccat.nix
@@ -33,7 +33,7 @@ in
     enable = lib.mkEnableOption description;
 
     config = lib.mkOption {
-      type = lib.types.attrs; # varying value types
+      type = lib.types.attrsOf lib.types.anything; # varying value types
       description = "Configuration structure (unchecked!)";
     };
 
diff --git a/ops/modules/josh.nix b/ops/modules/josh.nix
index be9e9e966e..3c37d0fec3 100644
--- a/ops/modules/josh.nix
+++ b/ops/modules/josh.nix
@@ -26,7 +26,7 @@ in
         DynamicUser = true;
         StateDirectory = "josh";
         Restart = "always";
-        ExecStart = "${depot.third_party.josh}/bin/josh-proxy --no-background --local /var/lib/josh --port ${toString cfg.port} --remote https://cl.tvl.fyi/";
+        ExecStart = "${pkgs.josh}/bin/josh-proxy --no-background --local /var/lib/josh --port ${toString cfg.port} --remote https://cl.tvl.fyi/ --require-auth";
       };
     };
   };
diff --git a/ops/modules/livegrep.nix b/ops/modules/livegrep.nix
new file mode 100644
index 0000000000..e25a301829
--- /dev/null
+++ b/ops/modules/livegrep.nix
@@ -0,0 +1,106 @@
+# Configures a code search instance using Livegrep.
+#
+# We do not currently build Livegrep in Nix, because it's a complex,
+# multi-language Bazel build and doesn't play nicely with Nix.
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.depot.livegrep;
+
+  livegrepConfig = {
+    name = "livegrep";
+
+    fs_paths = [{
+      name = "depot";
+      path = "/depot";
+      metadata.url_pattern = "https://code.tvl.fyi/tree/{path}?id={version}#n{lno}";
+    }];
+
+    repositories = [{
+      name = "depot";
+      path = "/depot";
+      revisions = [ "HEAD" ];
+
+      metadata = {
+        url_pattern = "https://code.tvl.fyi/tree/{path}?id={version}#n{lno}";
+        remote = "https://cl.tvl.fyi/depot.git";
+      };
+    }];
+  };
+
+  configFile = pkgs.writeText "livegrep-config.json" (builtins.toJSON livegrepConfig);
+
+  # latest as of 2024-02-17
+  image = "ghcr.io/livegrep/livegrep/base:033fa0e93c";
+in
+{
+  options.services.depot.livegrep = with lib; {
+    enable = mkEnableOption "Run livegrep code search for depot";
+
+    port = mkOption {
+      description = "Port on which livegrep web UI should listen";
+      type = types.int;
+      default = 5477; # lgrp
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    virtualisation.oci-containers.containers.livegrep-codesearch = {
+      inherit image;
+      extraOptions = [ "--net=host" ];
+
+      volumes = [
+        "${configFile}:/etc/livegrep-config.json:ro"
+        "/var/lib/gerrit/git/depot.git:/depot:ro"
+      ];
+
+      entrypoint = "/livegrep/bin/codesearch";
+      cmd = [
+        "-grpc"
+        "0.0.0.0:5427" # lgcs
+        "-reload_rpc"
+        "-revparse"
+        "/etc/livegrep-config.json"
+      ];
+    };
+
+    virtualisation.oci-containers.containers.livegrep-frontend = {
+      inherit image;
+      dependsOn = [ "livegrep-codesearch" ];
+      extraOptions = [ "--net=host" ];
+
+      entrypoint = "/livegrep/bin/livegrep";
+      cmd = [
+        "-listen"
+        "0.0.0.0:${toString cfg.port}"
+        "-reload"
+        "-connect"
+        "localhost:5427"
+        "-docroot"
+        "/livegrep/web"
+        # TODO(tazjin): docroot with styles etc.
+      ];
+    };
+
+    systemd.services.livegrep-reindex = {
+      script = "${pkgs.docker}/bin/docker exec livegrep-codesearch /livegrep/bin/livegrep-reload localhost:5427";
+      serviceConfig.Type = "oneshot";
+    };
+
+    systemd.paths.livegrep-reindex = {
+      description = "Executes a livegrep reindex if depot refs change";
+      wantedBy = [ "multi-user.target" ];
+
+      pathConfig = {
+        PathChanged = [
+          "/var/lib/gerrit/git/depot.git/packed-refs"
+          "/var/lib/gerrit/git/depot.git/refs"
+        ];
+      };
+    };
+  };
+}
+
+
+# sudo docker exec -ti livegrep /livegrep/bin/codesearch -reload_rpc -revparse /var/lib/livegrep/config.jsno
+# sudo docker run -d --ip 172.17.0.3 --name livegrep -v /var/lib/livegrep:/varlib/livegrep -v /var/lib/gerrit/git/depot.git:/depot:ro -v /home/tazjin/livegrep-web:/livegrep/web:ro ghcr.io/livegrep/livegrep/base /livegrep/bin/livegrep -listen 0.0.0.0:8910 -reload -docroot /livegrep/webbsudo docker run -d --ip 172.17.0.3 --name livegrep -v /var/lib/livegrep:/varlib/livegrep -v /var/lib/gerrit/git/depot.git:/depot:ro -v /home/tazjin/livegrep-web:/livegrep/web:ro ghcr.io/livegrep/livegrep/base /livegrep/bin/livegrep -listen 0.0.0.0:8910 -reload -docroot /livegrep/webb
diff --git a/ops/modules/monorepo-gerrit.nix b/ops/modules/monorepo-gerrit.nix
index d41e02764c..b335fe61d5 100644
--- a/ops/modules/monorepo-gerrit.nix
+++ b/ops/modules/monorepo-gerrit.nix
@@ -9,7 +9,7 @@ let
     exec -a ${name} ${depot.ops.besadii}/bin/besadii "$@"
   '';
 
-  gerritHooks = pkgs.runCommandNoCC "gerrit-hooks" { } ''
+  gerritHooks = pkgs.runCommand "gerrit-hooks" { } ''
     mkdir -p $out
     ln -s ${besadiiWithConfig "change-merged"} $out/change-merged
     ln -s ${besadiiWithConfig "patchset-created"} $out/patchset-created
@@ -42,7 +42,7 @@ in
     # Gerrit.
     #
     # TODO(tazjin): Update Gerrit and remove this when possible.
-    jvmPackage = pkgs.openjdk11_headless;
+    jvmPackage = pkgs.openjdk17_headless;
 
     settings = {
       core.packedGitLimit = "100m";
@@ -87,21 +87,21 @@ in
 
       # Auto-link panettone bug links
       commentlink.panettone = {
-        match = "b/(\\\\d+)";
-        html = "<a href=\"https://b.tvl.fyi/issues/$1\">b/$1</a>";
+        match = "b/(\\d+)";
+        link = "https://b.tvl.fyi/issues/$1";
       };
 
       # Auto-link other CLs
       commentlink.gerrit = {
-        match = "cl/(\\\\d+)";
-        html = "<a href=\"https://cl.tvl.fyi/$1\">cl/$1</a>";
+        match = "cl/(\\d+)";
+        link = "https://cl.tvl.fyi/$1";
       };
 
       # Configures integration with Keycloak, which then integrates with a
       # variety of backends.
       auth.type = "OAUTH";
       plugin.gerrit-oauth-provider-keycloak-oauth = {
-        root-url = "https://auth.tvl.fyi";
+        root-url = "https://auth.tvl.fyi/auth";
         realm = "TVL";
         client-id = "gerrit";
         # client-secret is set in /var/lib/gerrit/etc/secure.config.
diff --git a/ops/modules/nixery.nix b/ops/modules/nixery.nix
index 4122f9ebbf..29da46cc1d 100644
--- a/ops/modules/nixery.nix
+++ b/ops/modules/nixery.nix
@@ -5,7 +5,8 @@
 let
   cfg = config.services.depot.nixery;
   description = "Nixery - container images on-demand";
-  storagePath = "/var/lib/nixery/${pkgs.nixpkgsCommits.unstable}";
+  nixpkgsSrc = depot.third_party.sources.nixpkgs-stable;
+  storagePath = "/var/lib/nixery/${nixpkgsSrc.rev}";
 in
 {
   options.services.depot.nixery = {
@@ -33,7 +34,7 @@ in
 
       environment = {
         PORT = toString cfg.port;
-        NIXERY_PKGS_PATH = pkgs.path;
+        NIXERY_PKGS_PATH = nixpkgsSrc.outPath;
         NIXERY_STORAGE_BACKEND = "filesystem";
         NIX_TIMEOUT = "60"; # seconds
         STORAGE_PATH = storagePath;
diff --git a/ops/modules/oauth2_proxy.nix b/ops/modules/oauth2_proxy.nix
deleted file mode 100644
index 23afa7bce0..0000000000
--- a/ops/modules/oauth2_proxy.nix
+++ /dev/null
@@ -1,60 +0,0 @@
-# Configuration for oauth2_proxy, which is used as a handler for nginx
-# auth-request setups.
-#
-# This module exports a helper function at
-# `config.services.depot.oauth2_proxy.withAuth` that can be wrapped
-# around nginx server configuration blocks to configure their
-# authentication setup.
-{ config, depot, pkgs, lib, ... }:
-
-let
-  description = "OAuth2 proxy to authenticate TVL services";
-  cfg = config.services.depot.oauth2_proxy;
-  configFile = pkgs.writeText "oauth2_proxy.cfg" ''
-    email_domains = [ "*" ]
-    http_address = "127.0.0.1:${toString cfg.port}"
-    provider = "keycloak-oidc"
-    client_id = "oauth2-proxy"
-    oidc_issuer_url = "https://auth.tvl.fyi/auth/realms/TVL"
-    reverse_proxy = true
-    set_xauthrequest = true
-  '';
-
-  # Depend on the Keycloak service if it is running on the same
-  # machine.
-  depends_on = lib.optional config.services.keycloak.enable "keycloak.service";
-in
-{
-  options.services.depot.oauth2_proxy = {
-    enable = lib.mkEnableOption description;
-
-    port = lib.mkOption {
-      description = "Port to listen on";
-      type = lib.types.int;
-      default = 2884; # "auth"
-    };
-
-    secretsFile = lib.mkOption {
-      type = lib.types.str;
-      description = "EnvironmentFile from which to load secrets";
-      default = config.age.secretsDir + "/oauth2_proxy";
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.oauth2_proxy = {
-      inherit description;
-      after = depends_on;
-      wants = depends_on;
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        Restart = "always";
-        RestartSec = "5s";
-        DynamicUser = true;
-        EnvironmentFile = cfg.secretsFile;
-        ExecStart = "${pkgs.oauth2_proxy}/bin/oauth2-proxy --config ${configFile}";
-      };
-    };
-  };
-}
diff --git a/ops/modules/open_eid.nix b/ops/modules/open_eid.nix
index 4bc35e298c..fa577f0f57 100644
--- a/ops/modules/open_eid.nix
+++ b/ops/modules/open_eid.nix
@@ -1,25 +1,6 @@
 # NixOS module to configure the Estonian e-ID software.
 { pkgs, ... }:
 
-let
-  # Wrapper script to tell to Chrome/Chromium to use p11-kit-proxy to load
-  # security devices.
-  # Each user needs to run this themselves, it does not work on a system level
-  # due to a bug in Chromium:
-  #
-  # https://bugs.chromium.org/p/chromium/issues/detail?id=16387
-  #
-  # Firefox users can just set
-  # extraPolicies.SecurityDevices.p11-kit-proxy "${pkgs.p11-kit}/lib/p11-kit-proxy.so";
-  # when overriding the firefox derivation.
-  setup-browser-eid = pkgs.writeShellScriptBin "setup-browser-eid" ''
-    NSSDB="''${HOME}/.pki/nssdb"
-    mkdir -p ''${NSSDB}
-
-    ${pkgs.nssTools}/bin/modutil -force -dbdir sql:$NSSDB -add p11-kit-proxy \
-      -libfile ${pkgs.p11-kit}/lib/p11-kit-proxy.so
-  '';
-in
 {
   services.pcscd.enable = true;
 
@@ -29,9 +10,45 @@ in
     module: ${pkgs.opensc}/lib/opensc-pkcs11.so
   '';
 
+  # Configure Firefox (in case users set `programs.firefox.enable = true;`)
+  programs.firefox = {
+    # Allow a possibly installed "Web eID" extension to do native messaging with
+    # the "web-eid-app" native component.
+    # Users not using `programs.firefox.enable` can override their firefox
+    # derivation, by setting `extraNativeMessagingHosts = [ pkgs.web-eid-app ]`.
+    nativeMessagingHosts.packages = [ pkgs.web-eid-app ];
+    # Configure Firefox to load smartcards via p11kit-proxy.
+    # Users not using `programs.firefox.enable` can override their firefox
+    # derivation, by setting
+    # `extraPolicies.SecurityDevices.p11-kit-proxy "${pkgs.p11-kit}/lib/p11-kit-proxy.so"`.
+    policies.SecurityDevices.p11-kit-proxy = "${pkgs.p11-kit}/lib/p11-kit-proxy.so";
+  };
+
+  # Chromium users need a symlink to their (slightly different) .json file
+  # in the native messaging hosts' manifest file location.
+  environment.etc."chromium/native-messaging-hosts/eu.webeid.json".source = "${pkgs.web-eid-app}/share/web-eid/eu.webeid.json";
+  environment.etc."opt/chrome/native-messaging-hosts/eu.webeid.json".source = "${pkgs.web-eid-app}/share/web-eid/eu.webeid.json";
+
   environment.systemPackages = with pkgs; [
-    libdigidocpp # provides digidoc-tool(1)
+    libdigidocpp.bin # provides digidoc-tool(1)
     qdigidoc
-    setup-browser-eid
+
+    # Wrapper script to tell to Chrome/Chromium to use p11-kit-proxy to load
+    # security devices, so they can be used for TLS client auth.
+    # Each user needs to run this themselves, it does not work on a system level
+    # due to a bug in Chromium:
+    #
+    # https://bugs.chromium.org/p/chromium/issues/detail?id=16387
+    #
+    # Firefox users can just set
+    # extraPolicies.SecurityDevices.p11-kit-proxy "${pkgs.p11-kit}/lib/p11-kit-proxy.so";
+    # when overriding the firefox derivation.
+    (pkgs.writeShellScriptBin "setup-browser-eid" ''
+      NSSDB="''${HOME}/.pki/nssdb"
+      mkdir -p ''${NSSDB}
+
+      ${pkgs.nssTools}/bin/modutil -force -dbdir sql:$NSSDB -add p11-kit-proxy \
+        -libfile ${pkgs.p11-kit}/lib/p11-kit-proxy.so
+    '')
   ];
 }
diff --git a/ops/modules/panettone.nix b/ops/modules/panettone.nix
index 2576ab16c5..e23dd028ab 100644
--- a/ops/modules/panettone.nix
+++ b/ops/modules/panettone.nix
@@ -104,5 +104,16 @@ in
         ISSUECHANNEL = cfg.irccatChannel;
       };
     };
+
+    systemd.services.panettone-fixer = {
+      description = "Restart panettone regularly to work around b/225";
+      wantedBy = [ "multi-user.target" ];
+      script = "${pkgs.systemd}/bin/systemctl restart panettone";
+      serviceConfig.Type = "oneshot";
+
+      # We don't exactly know how frequently this occurs, but
+      # _probably_ not more than hourly.
+      startAt = "hourly";
+    };
   };
 }
diff --git a/ops/modules/quassel.nix b/ops/modules/quassel.nix
index 275e2809d7..6acb0615f4 100644
--- a/ops/modules/quassel.nix
+++ b/ops/modules/quassel.nix
@@ -55,7 +55,7 @@ in
         "--port=${toString cfg.port}"
         "--configdir=/var/lib/quassel"
         "--require-ssl"
-        "--ssl-cert=/var/lib/acme/${cfg.acmeHost}/full.pem"
+        "--ssl-cert=$CREDENTIALS_DIRECTORY/quassel.pem"
         "--loglevel=${cfg.logLevel}"
       ];
 
@@ -64,6 +64,10 @@ in
         User = "quassel";
         Group = "quassel";
         StateDirectory = "quassel";
+
+        # Avoid trouble with the ACME file permissions by using the
+        # systemd credentials feature.
+        LoadCredential = "quassel.pem:/var/lib/acme/${cfg.acmeHost}/full.pem";
       };
     };
 
diff --git a/ops/modules/teleirc.nix b/ops/modules/teleirc.nix
new file mode 100644
index 0000000000..9f9ac059ce
--- /dev/null
+++ b/ops/modules/teleirc.nix
@@ -0,0 +1,40 @@
+# Run the Telegram<>IRC sync bot for the Volga Sprint channel.
+#
+# This module is written in a pretty ad-hoc style, as it is sort of a
+# throwaway thing (will be removed again after the event).
+{ depot, config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.depot.owothia;
+  description = "IRC<>Telegram sync for Volga Sprint channel";
+  configFile = builtins.toFile "teleirc.env" ''
+    # connect through tvlbot's ZNC bouncer
+    IRC_SERVER="localhost"
+    IRC_PORT=2627
+    IRC_USE_SSL=false
+    IRC_CHANNEL="#volgasprint"
+    IRC_BLACKLIST="tvlbot"
+    IRC_BOT_NAME="tvlbot"
+    IRC_BOT_REALNAME="TVL bot for Volga Sprint"
+    IRC_BOT_IDENT="tvlbot"
+    IRC_SEND_STICKER_EMOJI=false # look into this
+    TELEGRAM_CHAT_ID=-1002153072030
+  '';
+in
+{
+  options.services.depot.teleirc.enable = lib.mkEnableOption description;
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.teleirc = {
+      inherit description;
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        Restart = "always";
+        EnvironmentFile = "/run/agenix/teleirc";
+        ExecStart = "${depot.third_party.teleirc}/bin/teleirc -conf ${configFile}";
+      };
+    };
+  };
+}
diff --git a/ops/modules/tvl-buildkite.nix b/ops/modules/tvl-buildkite.nix
index 4341ef01d7..3c6d88404f 100644
--- a/ops/modules/tvl-buildkite.nix
+++ b/ops/modules/tvl-buildkite.nix
@@ -13,7 +13,7 @@ let
 
   # All Buildkite hooks are actually besadii, but it's being invoked
   # with different names.
-  buildkiteHooks = pkgs.runCommandNoCC "buildkite-hooks" { } ''
+  buildkiteHooks = pkgs.runCommand "buildkite-hooks" { } ''
     mkdir -p $out/bin
     ln -s ${besadiiWithConfig "post-command"} $out/bin/post-command
   '';
@@ -43,6 +43,9 @@ in
           tokenPath = config.age.secretsDir + "/buildkite-agent-token";
           privateSshKeyPath = config.age.secretsDir + "/buildkite-private-key";
           hooks.post-command = "${buildkiteHooks}/bin/post-command";
+          hooks.environment = ''
+            export PATH=$PATH:/run/wrappers/bin
+          '';
 
           runtimePackages = with pkgs; [
             bash
diff --git a/ops/modules/tvl-headscale.nix b/ops/modules/tvl-headscale.nix
new file mode 100644
index 0000000000..a07021c788
--- /dev/null
+++ b/ops/modules/tvl-headscale.nix
@@ -0,0 +1,62 @@
+# Configuration for the coordination server for net.tvl.fyi, a
+# tailscale network run using headscale.
+#
+# All TVL members can join this network, which provides several exit
+# nodes through which traffic can be routed.
+#
+# The coordination server is currently run on sanduny.tvl.su. It is
+# managed manually, ping somebody with access ... for access.
+#
+# Servers should join using approximately this command:
+#   tailscale up --login-server https://net.tvl.fyi --accept-dns=false --advertise-exit-node
+#
+# Clients should join using approximately this command:
+#   tailscale up --login-server https://net.tvl.fyi --accept-dns=false
+{ config, pkgs, ... }:
+
+{
+  # TODO(tazjin): run embedded DERP server
+  services.headscale = {
+    enable = true;
+    port = 4725; # hscl
+
+    settings = {
+      server_url = "https://net.tvl.fyi";
+      dns_config.nameservers = [
+        "8.8.8.8"
+        "1.1.1.1"
+        "77.88.8.8"
+      ];
+
+      # TLS is handled by nginx
+      tls_cert_path = null;
+      tls_key_path = null;
+    };
+  };
+
+  environment.systemPackages = [ pkgs.headscale ]; # admin CLI
+
+  services.nginx.virtualHosts."net.tvl.fyi" = {
+    serverName = "net.tvl.fyi";
+    enableACME = true;
+    forceSSL = true;
+
+    # See https://github.com/juanfont/headscale/blob/v0.22.3/docs/reverse-proxy.md#nginx
+    extraConfig = ''
+      location / {
+        proxy_pass http://localhost:${toString config.services.headscale.port};
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $connection_upgrade;
+        proxy_set_header Host $server_name;
+        proxy_redirect http:// https://;
+        proxy_buffering off;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
+        add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+      }
+    '';
+  };
+
+}
diff --git a/ops/modules/tvl-users.nix b/ops/modules/tvl-users.nix
index 988b9eed8a..ea83b435f4 100644
--- a/ops/modules/tvl-users.nix
+++ b/ops/modules/tvl-users.nix
@@ -17,12 +17,10 @@
       openssh.authorizedKeys.keys = depot.users.lukegb.keys.all;
     };
 
-    users.grfn = {
+    users.aspen = {
       isNormalUser = true;
       extraGroups = [ "git" "wheel" ];
-      openssh.authorizedKeys.keys = [
-        depot.users.grfn.keys.whitby
-      ];
+      openssh.authorizedKeys.keys = [ depot.users.aspen.keys.whitby ];
     };
 
     users.edef = {
@@ -33,6 +31,7 @@
 
     users.qyliss = {
       isNormalUser = true;
+      description = "Alyssa Ross";
       extraGroups = [ "git" ];
       openssh.authorizedKeys.keys = depot.users.qyliss.keys.all;
     };
@@ -63,32 +62,22 @@
 
     users.flokli = {
       isNormalUser = true;
-      extraGroups = [ "git" ];
+      extraGroups = [ "git" "wheel" ];
       openssh.authorizedKeys.keys = depot.users.flokli.keys.all;
     };
-
-    # Temporarily disabled (inactive) users.
-    users.isomer = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      shell = "${pkgs.shadow}/bin/nologin";
-      openssh.authorizedKeys.keys = depot.users.isomer.keys.all;
-    };
-
-    users.riking = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      shell = "${pkgs.shadow}/bin/nologin";
-      openssh.authorizedKeys.keys = depot.users.riking.keys.u2f ++ depot.users.riking.keys.passworded;
-    };
   };
 
+  programs.fish.enable = true;
+
   environment.systemPackages = with pkgs; [
     alacritty.terminfo
     foot.terminfo
-    rxvt_unicode.terminfo
-
-    # TODO(sterni): re-enable when the kitty build is fixed upstreams
-    # kitty.terminfo
+    rxvt-unicode-unwrapped.terminfo
+    kitty.terminfo
   ];
+
+  security.sudo.extraRules = [{
+    groups = [ "wheel" ];
+    commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
+  }];
 }
diff --git a/ops/modules/v4l2loopback.nix b/ops/modules/v4l2loopback.nix
deleted file mode 100644
index 636b2ff6cf..0000000000
--- a/ops/modules/v4l2loopback.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-{
-  boot = {
-    extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
-    kernelModules = [ "v4l2loopback" ];
-    extraModprobeConfig = ''
-      options v4l2loopback exclusive_caps=1
-    '';
-  };
-}
-
diff --git a/ops/modules/www/auth.tvl.fyi.nix b/ops/modules/www/auth.tvl.fyi.nix
index e0c031bf70..a068f02365 100644
--- a/ops/modules/www/auth.tvl.fyi.nix
+++ b/ops/modules/www/auth.tvl.fyi.nix
@@ -12,8 +12,12 @@
       forceSSL = true;
 
       extraConfig = ''
+        # increase buffer size for large headers
+        proxy_buffers 8 16k;
+        proxy_buffer_size 16k;
+
         location / {
-          proxy_pass http://localhost:${config.services.keycloak.httpPort};
+          proxy_pass http://localhost:${toString config.services.keycloak.settings.http-port};
           proxy_set_header X-Forwarded-For $remote_addr;
           proxy_set_header X-Forwarded-Proto https;
           proxy_set_header Host $host;
diff --git a/ops/modules/www/cl.tvl.fyi.nix b/ops/modules/www/cl.tvl.fyi.nix
index 470122c395..36422a6c4e 100644
--- a/ops/modules/www/cl.tvl.fyi.nix
+++ b/ops/modules/www/cl.tvl.fyi.nix
@@ -24,6 +24,10 @@
           # The :443 suffix is a workaround for https://b.tvl.fyi/issues/88.
           proxy_set_header  Host $host:443;
         }
+
+        location = /robots.txt {
+          return 200 'User-agent: *\nAllow: /';
+        }
       '';
     };
   };
diff --git a/ops/modules/www/code.tvl.fyi.nix b/ops/modules/www/code.tvl.fyi.nix
index 3f34a9422c..ee0211990d 100644
--- a/ops/modules/www/code.tvl.fyi.nix
+++ b/ops/modules/www/code.tvl.fyi.nix
@@ -1,4 +1,4 @@
-{ depot, config, ... }:
+{ depot, pkgs, config, ... }:
 
 {
   imports = [
@@ -13,16 +13,49 @@
       forceSSL = true;
 
       extraConfig = ''
-        # Serve the rendered Tvix component SVG.
-        #
-        # TODO(tazjin): Implement a way of serving this dynamically
-        location = /about/tvix/docs/component-flow.svg {
-            alias ${depot.tvix.docs.svg}/component-flow.svg;
+        location = /go-get/tvix/build-go {
+            alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/build-go git https://code.tvl.fyi/depot.git:/tvix/build-go.git"></html>''};
+        }
+
+        location = /go-get/tvix/castore-go {
+            alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/castore-go git https://code.tvl.fyi/depot.git:/tvix/castore-go.git"></html>''};
+        }
+
+        location = /go-get/tvix/store-go {
+            alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/store-go git https://code.tvl.fyi/depot.git:/tvix/store-go.git"></html>''};
+        }
+
+        location = /go-get/tvix/nar-bridge {
+            alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/nar-bridge git https://code.tvl.fyi/depot.git:/tvix/nar-bridge.git"></html>''};
+        }
+
+        location = /tvix/build-go {
+            if ($args ~* "/?go-get=1") {
+                return 302 /go-get/tvix/build-go;
+            }
+        }
+
+        location = /tvix/castore-go {
+            if ($args ~* "/?go-get=1") {
+                return 302 /go-get/tvix/castore-go;
+            }
+        }
+
+        location = /tvix/store-go {
+            if ($args ~* "/?go-get=1") {
+                return 302 /go-get/tvix/store-go;
+            }
+        }
+
+        location = /tvix/nar-bridge {
+            if ($args ~* "/?go-get=1") {
+                return 302 /go-get/tvix/nar-bridge;
+            }
         }
 
         # Git operations on depot.git hit josh
         location /depot.git {
-            proxy_pass http://localhost:${toString config.services.depot.josh.port};
+            proxy_pass http://127.0.0.1:${toString config.services.depot.josh.port};
         }
 
         # Git clone operations on '/' should be redirected to josh now.
diff --git a/ops/modules/www/grep.tvl.fyi.nix b/ops/modules/www/grep.tvl.fyi.nix
new file mode 100644
index 0000000000..93ef5eabd2
--- /dev/null
+++ b/ops/modules/www/grep.tvl.fyi.nix
@@ -0,0 +1,19 @@
+# Experimental configuration for manually Livegrep.
+{ config, ... }:
+
+{
+  imports = [
+    ./base.nix
+  ];
+
+  config = {
+    services.nginx.virtualHosts."grep.tvl.fyi" = {
+      enableACME = true;
+      forceSSL = true;
+
+      locations."/" = {
+        proxyPass = "http://127.0.0.1:${toString config.services.depot.livegrep.port}";
+      };
+    };
+  };
+}
diff --git a/ops/modules/www/images.tvl.fyi.nix b/ops/modules/www/images.tvl.fyi.nix
deleted file mode 100644
index 7d027b2991..0000000000
--- a/ops/modules/www/images.tvl.fyi.nix
+++ /dev/null
@@ -1,22 +0,0 @@
-{ config, ... }:
-
-{
-  imports = [
-    ./base.nix
-  ];
-
-  config = {
-    services.nginx.virtualHosts."images.tvl.fyi" = {
-      serverName = "images.tvl.fyi";
-      serverAliases = [ "images.tvl.su" ];
-      enableACME = true;
-      forceSSL = true;
-
-      extraConfig = ''
-        location / {
-          proxy_pass http://localhost:${toString config.services.depot.nixery.port};
-        }
-      '';
-    };
-  };
-}
diff --git a/ops/modules/www/inbox.tvl.su.nix b/ops/modules/www/inbox.tvl.su.nix
new file mode 100644
index 0000000000..38db5d2a8e
--- /dev/null
+++ b/ops/modules/www/inbox.tvl.su.nix
@@ -0,0 +1,31 @@
+{ config, depot, ... }:
+
+{
+  imports = [
+    ./base.nix
+  ];
+
+  config = {
+    services.nginx.virtualHosts."inbox.tvl.su" = {
+      enableACME = true;
+      forceSSL = true;
+
+      extraConfig = ''
+        # nginx is incapable of serving a single file at /, hence this hack:
+        location = / {
+          index /landing-page;
+        }
+
+        location = /landing-page {
+          types { } default_type "text/html; charset=utf-8";
+          alias ${depot.web.inbox};
+        }
+
+        # rest of requests is proxied to public-inbox-httpd
+        location / {
+          proxy_pass http://localhost:${toString config.services.public-inbox.http.port};
+        }
+      '';
+    };
+  };
+}
diff --git a/ops/modules/www/tvixbolt.tvl.su.nix b/ops/modules/www/signup.tvl.fyi.nix
index 7adddd1236..1b193f99a9 100644
--- a/ops/modules/www/tvixbolt.tvl.su.nix
+++ b/ops/modules/www/signup.tvl.fyi.nix
@@ -6,8 +6,8 @@
   ];
 
   config = {
-    services.nginx.virtualHosts."tvixbolt.tvl.su" = {
-      root = depot.corp.tvixbolt;
+    services.nginx.virtualHosts."signup.tvl.fyi" = {
+      root = depot.web.pwcrypt;
       enableACME = true;
       forceSSL = true;
 
diff --git a/ops/modules/www/status.tvl.su.nix b/ops/modules/www/status.tvl.su.nix
index 2bb6093c14..7079c60260 100644
--- a/ops/modules/www/status.tvl.su.nix
+++ b/ops/modules/www/status.tvl.su.nix
@@ -18,7 +18,7 @@
       forceSSL = true;
 
       locations."/" = {
-        proxyPass = "http://localhost:${toString config.services.grafana.port}";
+        proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
       };
     };
   };
diff --git a/ops/modules/www/tazj.in.nix b/ops/modules/www/tazj.in.nix
index 3b80222e0d..47eefca2a6 100644
--- a/ops/modules/www/tazj.in.nix
+++ b/ops/modules/www/tazj.in.nix
@@ -33,6 +33,11 @@
           return 302 https://predlozhnik.ru;
         }
 
+        # redirect for easier entry on a TV
+        location = /tv {
+          return 302 https://tazj.in/blobs/play.html;
+        }
+
         # Temporary place for serving static files.
         location /blobs/ {
           alias /var/lib/tazjins-blobs/;
diff --git a/ops/modules/www/tvix.dev.nix b/ops/modules/www/tvix.dev.nix
new file mode 100644
index 0000000000..f884bc30ed
--- /dev/null
+++ b/ops/modules/www/tvix.dev.nix
@@ -0,0 +1,46 @@
+{ depot, ... }:
+
+{
+  imports = [
+    ./base.nix
+  ];
+
+  config = {
+    services.nginx.virtualHosts."tvix.dev" = {
+      serverName = "tvix.dev";
+      enableACME = true;
+      forceSSL = true;
+      root = depot.tvix.website;
+    };
+
+    services.nginx.virtualHosts."bolt.tvix.dev" = {
+      root = depot.web.tvixbolt;
+      enableACME = true;
+      forceSSL = true;
+    };
+
+    # old domain, serve redirect
+    services.nginx.virtualHosts."tvixbolt.tvl.su" = {
+      enableACME = true;
+      forceSSL = true;
+      extraConfig = "return 301 https://bolt.tvix.dev$request_uri;";
+    };
+
+    services.nginx.virtualHosts."docs.tvix.dev" = {
+      serverName = "docs.tvix.dev";
+      enableACME = true;
+      forceSSL = true;
+
+      extraConfig = ''
+        location = / {
+          # until we have a better default page here
+          return 301 https://docs.tvix.dev/rust/tvix_eval/index.html;
+        }
+
+        location /rust/ {
+          alias ${depot.tvix.rust-docs}/;
+        }
+      '';
+    };
+  };
+}
diff --git a/ops/modules/www/volgasprint.org.nix b/ops/modules/www/volgasprint.org.nix
new file mode 100644
index 0000000000..7e5abe5561
--- /dev/null
+++ b/ops/modules/www/volgasprint.org.nix
@@ -0,0 +1,15 @@
+{ depot, ... }:
+
+{
+  imports = [
+    ./base.nix
+  ];
+
+  config = {
+    services.nginx.virtualHosts."volgasprint.org" = {
+      enableACME = true;
+      forceSSL = true;
+      root = "${depot.web.volgasprint}";
+    };
+  };
+}
diff --git a/ops/modules/www/wigglydonke.rs.nix b/ops/modules/www/wigglydonke.rs.nix
index 3d85e4eb98..6440164325 100644
--- a/ops/modules/www/wigglydonke.rs.nix
+++ b/ops/modules/www/wigglydonke.rs.nix
@@ -9,7 +9,7 @@
     services.nginx.virtualHosts."wigglydonke.rs" = {
       enableACME = true;
       forceSSL = true;
-      root = "${depot.path + "/users/grfn/wigglydonke.rs"}";
+      root = "${depot.path + "/users/aspen/wigglydonke.rs"}";
     };
   };
 }
diff --git a/ops/modules/yandex-cloud.nix b/ops/modules/yandex-cloud.nix
new file mode 100644
index 0000000000..cf6d1eb810
--- /dev/null
+++ b/ops/modules/yandex-cloud.nix
@@ -0,0 +1,78 @@
+# Profile for virtual machines on Yandex Cloud, intended for disk
+# images.
+#
+# https://cloud.yandex.com/en/docs/compute/operations/image-create/custom-image
+#
+# TODO(tazjin): Upstream to nixpkgs once it works well.
+{ config, lib, pkgs, modulesPath, ... }:
+
+let
+  cfg = config.virtualisation.yandexCloud;
+
+  # Kernel modules required for interacting with the hypervisor. These
+  # must be available during stage 1 boot and during normal operation,
+  # as disks and network do not work without them.
+  modules = [
+    "virtio-net"
+    "virtio-blk"
+    "virtio-pci"
+    "virtiofs"
+  ];
+in
+{
+  imports = [
+    "${modulesPath}/profiles/headless.nix"
+  ];
+
+  options = {
+    virtualisation.yandexCloud.rootPartitionUuid = with lib; mkOption {
+      type = types.str;
+      default = "C55A5EE2-E5FA-485C-B3AE-CC928429AB6B";
+
+      description = ''
+        UUID to use for the root partition of the disk image. Yandex
+        Cloud requires that root partitions are mounted by UUID.
+
+        Most users do not need to set this to a non-default value.
+      '';
+    };
+  };
+
+  config = {
+    fileSystems."/" = {
+      device = "/dev/disk/by-uuid/${lib.toLower cfg.rootPartitionUuid}";
+      fsType = "ext4";
+      autoResize = true;
+    };
+
+    boot = {
+      loader.grub.device = "/dev/vda";
+
+      initrd.kernelModules = modules;
+      kernelModules = modules;
+      kernelParams = [
+        # Enable support for the serial console
+        "console=ttyS0"
+      ];
+
+      growPartition = true;
+    };
+
+    environment.etc.securetty = {
+      text = "ttyS0";
+      mode = "0644";
+    };
+
+    systemd.services."serial-getty@ttyS0".enable = true;
+
+    services.openssh.enable = true;
+
+    system.build.yandexCloudImage = import (pkgs.path + "/nixos/lib/make-disk-image.nix") {
+      inherit lib config pkgs;
+      additionalSpace = "128M";
+      format = "qcow2";
+      partitionTableType = "legacy+gpt";
+      rootGPUID = cfg.rootPartitionUuid;
+    };
+  };
+}
diff --git a/ops/nixos.nix b/ops/nixos.nix
index 309f122977..1442d89b30 100644
--- a/ops/nixos.nix
+++ b/ops/nixos.nix
@@ -40,7 +40,10 @@ in rec {
       (throw "${hostname} is not a known NixOS host")
       (map nixosFor depot.ops.machines.all-systems));
 
-  rebuild-system = rebuildSystemWith depot.path;
+  rebuild-system = rebuildSystemWith (
+    # HACK: use the string of the original source to avoid copying the whole
+    # depot into the store just for this
+    builtins.toString depot.path.origSrc);
 
   rebuildSystemWith = depotPath: pkgs.writeShellScriptBin "rebuild-system" ''
     set -ue
@@ -59,5 +62,6 @@ in rec {
   # Systems that should be built in CI
   whitbySystem = (nixosFor depot.ops.machines.whitby).system;
   sandunySystem = (nixosFor depot.ops.machines.sanduny).system;
-  meta.ci.targets = [ "sandunySystem" "whitbySystem" ];
+  nixeryDev01System = (nixosFor depot.ops.machines.nixery-01).system;
+  meta.ci.targets = [ "sandunySystem" "whitbySystem" "nixeryDev01System" ];
 }
diff --git a/ops/pipelines/depot.nix b/ops/pipelines/depot.nix
index 6d9e625e04..5eff622671 100644
--- a/ops/pipelines/depot.nix
+++ b/ops/pipelines/depot.nix
@@ -3,18 +3,9 @@
 { depot, pkgs, externalArgs, ... }:
 
 let
-  # Protobuf check step which validates that changes to .proto files
-  # between revisions don't cause backwards-incompatible or otherwise
-  # flawed changes.
-  protoCheck = {
-    command = "${depot.nix.bufCheck}/bin/ci-buf-check";
-    label = ":water_buffalo:";
-  };
-
   pipeline = depot.nix.buildkite.mkPipeline {
     headBranch = "refs/heads/canon";
     drvTargets = depot.ci.targets;
-    additionalSteps = [ protoCheck ];
 
     parentTargetMap =
       if (externalArgs ? parentTargetMap)
@@ -42,7 +33,7 @@ let
 
   drvmap = depot.nix.buildkite.mkDrvmap depot.ci.targets;
 in
-pkgs.runCommandNoCC "depot-pipeline" { } ''
+pkgs.runCommand "depot-pipeline" { } ''
   mkdir $out
   cp -r ${pipeline}/* $out
   cp ${drvmap} $out/drvmap.json
diff --git a/ops/pipelines/static-pipeline.yaml b/ops/pipelines/static-pipeline.yaml
index bd7491110c..af4f9d784e 100644
--- a/ops/pipelines/static-pipeline.yaml
+++ b/ops/pipelines/static-pipeline.yaml
@@ -17,6 +17,16 @@ steps:
     build:
       message: "Verification triggered by ${BUILDKITE_COMMIT}"
 
+  # Run pipeline for tvix when new commits arrive on canon. Since
+  # it is not part of the depot build tree, this is a useful
+  # verification to ensure we don't break external things (too much).
+  - trigger: "tvix"
+    async: true
+    label: ":fork:"
+    branches: "refs/heads/canon"
+    build:
+      message: "Verification triggered by ${BUILDKITE_COMMIT}"
+
   # Create a revision number for the current commit for builds on
   # canon.
   #
@@ -25,6 +35,11 @@ steps:
   #
   # Revision numbers are defined as the number of commits in the
   # lineage of HEAD, following only the first parent of merges.
+  #
+  # Note that git does not fetch these refs by default, instead
+  # you'll have to modify your git config using
+  # `git config --add remote.origin.fetch '+refs/r/*:refs/r/*'`.
+  # The refs are available after the next `git fetch`.
   - label: ":git:"
     branches: "refs/heads/canon"
     command: |
@@ -34,12 +49,14 @@ steps:
   # Generate & upload dynamic build steps
   - label: ":llama:"
     key: "pipeline-gen"
+    concurrency_group: 'depot-nix-eval'
+    concurrency: 5 # much more than this and whitby will OOM
     command: |
       set -ue
 
       if test -n "$${GERRIT_CHANGE_URL-}"; then
         echo "This is a build of [cl/$$GERRIT_CHANGE_ID]($$GERRIT_CHANGE_URL) (at patchset #$$GERRIT_PATCHSET)" | \
-          buildkite-agent annotate
+          buildkite-agent annotate --context cl-annotation
       fi
 
       # Attempt to fetch a target map from a parent commit on canon,
@@ -52,7 +69,8 @@ steps:
         PIPELINE_ARGS="--arg parentTargetMap tmp/parent-target-map.json"
       fi
 
-      nix-build --option restrict-eval true --include "depot=$${PWD}"\
+      nix-build --option restrict-eval true --include "depot=$${PWD}" \
+        --include "store=/nix/store" \
         --allowed-uris 'https://' \
         -A ops.pipelines.depot \
         -o pipeline --show-trace $$PIPELINE_ARGS
diff --git a/ops/secrets/besadii.age b/ops/secrets/besadii.age
index cfbe27b972..50c2d1442d 100644
--- a/ops/secrets/besadii.age
+++ b/ops/secrets/besadii.age
Binary files differdiff --git a/ops/secrets/buildkite-agent-token.age b/ops/secrets/buildkite-agent-token.age
index aef7b142b6..66802310bb 100644
--- a/ops/secrets/buildkite-agent-token.age
+++ b/ops/secrets/buildkite-agent-token.age
Binary files differdiff --git a/ops/secrets/buildkite-graphql-token.age b/ops/secrets/buildkite-graphql-token.age
index e656a6e04d..6ebf3efca7 100644
--- a/ops/secrets/buildkite-graphql-token.age
+++ b/ops/secrets/buildkite-graphql-token.age
@@ -1,16 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw L31em0JneG6XJikTp2LlYLSMDfsbDWjrNgQPQimIqWk
-3CJid3K/8RsE4cYEeZpqqaTmggMKH12GCDyalQMaK8s
--> ssh-ed25519 zcCuhA LKq27N4Hx8OQ3eu0TDdBiXO0BcOdSfRZO0YNNG1Y8xE
-PQjl1SErWej6e7jwsddoj06TWQQwp2J/m8zvxR1pRhg
--> ssh-ed25519 CpJBgQ dRMHEzXCpKPppncOBF4AmOYDZOSxZn+ta0o2H0zyAT0
-qNQFHL0QFxGlm7ZYnJ0H22iyVN3Ya7KYO596j2mN03Y
--> ssh-ed25519 aXKGcg z31fIwcokphDOcPLNfBZB3ZN9nzG71pMmC68R60nWnU
-3U32x1lxd7brCQj9V8eglSzQ1lCwraxDnjLl68EIR18
--> ssh-ed25519 OkGqLg 2jyx2iccmCeaXxs7pajP1WkRswZRwxrwVhNUKs1HzxE
-LjScnNDoWArkBXKWtSlJKnIlbnv0892nwn5aRyrF+sA
--> 8Y8-grease \ObI# /"xHCp uyu Gn&q
-mLNOU8cvH8SB5PCkgKkBmxTb/cgwiQEBUbPI6GmMxvXy/8EMg5K1h3kpKSawW849
-jtLtHeLrM8FLeNtwZyIWpG4
---- wnNSrutHnL4Trg5hNkuIHPguKl3JYjfEiJVCH4ScnVo
-๎:฿ภŠ-$mเY:้yOฏL์VLG”dฤQgชโMศฐbฒฒกๅฝ๙Jร„€‚:Oา!6O5ษชทOZ8฿*sA
\ No newline at end of file
+-> ssh-ed25519 dcsaLw X7cI9stdU1F8M8Mhk/5a4UwU2Ze6rBXuwRDxUTKCTHw
+CnksXNl+VEs2CYiucBeIgfpzpA05VshlECkbmTUZSpI
+-> ssh-ed25519 zcCuhA 7KOsie4KRM0pPKZk8MeDISuX4tT9MAw/5mehSQcNOE8
+UfbpAlKJVhZOH5j4YIw5CVDen7UebTO/S55sLT9tVyc
+-> ssh-ed25519 CpJBgQ EiDs9pCdSnPb4T4HvgF+gdyJ9f5orhtn1OVUp45e3jM
+SlMWEzpi/mMlhfBPzVBn6jZknvjWCbRQMLoJEklJV2w
+-> ssh-ed25519 aXKGcg kiuat73hEcxKvRZ9Gk115LjB3WVgd0h5KrjMOyTRLzw
+CwEmQX6vmi6DnJp/TeYFOSdsfrprHylXAzhnAaQ3aKw
+-> ssh-ed25519 OkGqLg R+moPPGckVPXrAnwQXFPqsizUwK+8UlL2VAA1965d1Y
+J0sxPR2PDqK3k39dSLOzFQkUUZ5cfYqww6NHQ7E4ql4
+-> lb6ND/-grease !D$d P~ Tj.
+HjRsXF0B07o957mq0zRgyHlckismT8UI8KcyFN55ff9FlWpci3+LEcPCb08wtraP
+DSRvOi4
+--- AomJrDQJ4VQghgD6b7ItcPNyiu+cDmNQM31FOqYBbEk
+
0:“เนนXดฎ0bฅ™^บ(ม๒:ŒฐำVฆr%GTฏh์ม>~ทถฟ…บq๏กฺ*ผๅ	›ืชฝ;}$๘
\ No newline at end of file
diff --git a/ops/secrets/buildkite-ssh-private-key.age b/ops/secrets/buildkite-ssh-private-key.age
index 485c90a9b7..c9aa988277 100644
--- a/ops/secrets/buildkite-ssh-private-key.age
+++ b/ops/secrets/buildkite-ssh-private-key.age
Binary files differdiff --git a/ops/secrets/clbot-ssh.age b/ops/secrets/clbot-ssh.age
index a5019e7b87..c24f8f45d3 100644
--- a/ops/secrets/clbot-ssh.age
+++ b/ops/secrets/clbot-ssh.age
Binary files differdiff --git a/ops/secrets/clbot.age b/ops/secrets/clbot.age
index d5d5ae2f08..2cec1f7f36 100644
--- a/ops/secrets/clbot.age
+++ b/ops/secrets/clbot.age
@@ -1,15 +1,15 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw aKWeIQEoQpPT9lPUsV7tK/ySf/0WmFWw7xr7ic4RDFM
-OLRVTC6qVuhNhkYbGQwrxq4sQnqmuQEclKeQ9VPJrOw
--> ssh-ed25519 zcCuhA j3JAw3UyZHR/x3O7pOTNkytbk5bTGnfBtsM030NolQk
-nt+9a3tJkO7j2nGI9C6S5YlYWYOCMqNOETU77PI4b10
--> ssh-ed25519 CpJBgQ ScLyIj1cdn0wAwgaOSVGsusx/y3PD5/rDy7+OvjGIiU
-5tYuoEfVn0i1RtZ5XP+1HgyTSWkkRN4m36u6Fj3PkC4
--> ssh-ed25519 aXKGcg 9p2LQFtV1X7jzG7n//GRUGmHGAsbGSCz6Q6SyBOZWwY
-wdOPCOHYkplGEoUOOTs99Kgde15xuJq8uzkZxudUo24
--> ssh-ed25519 OkGqLg oLEc1KdRriCWobe5DF9OKVwDqQaW9RyjWDft1h5M4x8
-i/UEbhITzk3IOYme/xKuTfdbNMFNhLgRHbiiCAgKFBI
--> %-grease 0 \^g*
-8aTar8xKZk24swVi7NVE0UN19BrexqAGcMWOeovRmQ
---- N/kNOLE5d+yk7fAPRZmj8E1qMggLha56uKb9oj0/uHQ
--ขงแ ข>I1f9NFฟ	wKl๚xป05ื5O๏Zzš…ู~yว€ีs!gQtี‡l1Wœ…f9\้sฮฐัp.nฑ
\ No newline at end of file
+-> ssh-ed25519 dcsaLw ZkAwxhi/ckHaVTnF7bmzOXhQG3HHqw1CpMe6nQL0rHc
+9qnf0AY/inCEvk1VBd4RC3M0kATM/JuIyWxqisjersY
+-> ssh-ed25519 zcCuhA o3PRUMcah5zjj39LtDWpgmBPFtHyx1N9WQz++lFrFEI
+7K1kZHKfmlV5G/xVbgeOuLAO2iXKqcEyRYm+YfTvURs
+-> ssh-ed25519 CpJBgQ pFnL2XmxzppshipadVltN/zSgiRiMh6emu6O8EZTpxI
+K/RPjooKVSwqxc2aAUBtdTnkKoZvXDi+2NPB2NPXT9E
+-> ssh-ed25519 aXKGcg sTN4w5iMnwxmp/E7OKu5I3pUc695OXBYmfOY8/hs1AM
+DguaArDGVn7scD0NrDntgePjN1LFlfrPKfjEd1T9iOI
+-> ssh-ed25519 OkGqLg xuRTDdql+UBNW2go+XxkC/FJZa+N/e6Kj/Fjm7MzG3E
+KC39o7+WV+d/psN4mYSxeUSHsSCxPWTJgYjY1f1Dd3w
+-> J:e-grease
+CISPWfdtr4GKDU+lhCFk6B/EVyOmYwDxhChu
+--- nwu3QYk6rfvIJWJrTB8RSBsWjS1uok8rSxc9FCzoA9k
+WSMrฎ
g#MSB๗}A"ึž˜–๚Ž๘จw›„}†คŠูฏ“๓วอ-่ลZ”แ1ศร๑oo„Go8๗าจwรำ…
\ No newline at end of file
diff --git a/ops/secrets/depot-inbox-imap.age b/ops/secrets/depot-inbox-imap.age
new file mode 100644
index 0000000000..9bce1845cb
--- /dev/null
+++ b/ops/secrets/depot-inbox-imap.age
@@ -0,0 +1,15 @@
+age-encryption.org/v1
+-> ssh-ed25519 dcsaLw cpeIOVtFcfaHZpIAp495fkQLJoT++h1v6p0crBeuzFM
++zomKCg7UVNl/FlfcZflVPbo48C45uGoGoR1tbetEdk
+-> ssh-ed25519 zcCuhA loSmQUCnO0EBaGg+wFYYkXOdLBQ6Z+pPl4Y3oGx6xzw
++RdXNYYtIDDXGr1Z0Mh28psvF9gzg12M3EJTUqmdFtU
+-> ssh-ed25519 CpJBgQ 0W0LWu8WW6pQzUhK21CeNDUtW0srwR5gNCRjwTy94B4
+A02F+AyP+DajnVTJakx+0jynYRDix9I/9uZUDPjXpis
+-> ssh-ed25519 aXKGcg SVBo2urAYGSYrlj3ieoi9nkrffcZ9ZroCn86pZkn4nI
+xQRrLNeNcI9cpQY+X2xfLDoBqLNQixGjaYtMDWtHio4
+-> ssh-ed25519 BXptmQ UKNJPPjIiqPQndZ6/yASSg+5PQIn2N9nUy2hQMREq1Y
+X9zM/ji9R3jLOEDGLpIVESjU13VU0e3cTAR1xEMhY5I
+-> B-grease Y
+vUOYknqY0okoUOKZD/8MpnpwkOU31sszuUZfeSVsuVyUMPEbFjWQT74
+--- ymKMaoUQXFPRc9U0ZvULBEC0Az0ew2oEyHwH/kR9ETI
+ŠEu”…	ซฏญxงแอำe_)zPบๅh‡ำำส๙ˆ–sฃžGเ่ดส•BLQ
\ No newline at end of file
diff --git a/ops/secrets/depot-replica-key.age b/ops/secrets/depot-replica-key.age
index 38c1cb5a23..5e8ce94d5d 100644
--- a/ops/secrets/depot-replica-key.age
+++ b/ops/secrets/depot-replica-key.age
Binary files differdiff --git a/ops/secrets/gerrit-autosubmit.age b/ops/secrets/gerrit-autosubmit.age
new file mode 100644
index 0000000000..2e04be952d
--- /dev/null
+++ b/ops/secrets/gerrit-autosubmit.age
Binary files differdiff --git a/ops/secrets/gerrit-queue.age b/ops/secrets/gerrit-queue.age
deleted file mode 100644
index eb9828847c..0000000000
--- a/ops/secrets/gerrit-queue.age
+++ /dev/null
@@ -1,17 +0,0 @@
-age-encryption.org/v1
--> ssh-ed25519 dcsaLw qywg/yigMgYkhxORSqfuVsggQUMmQSPp6T9BjlEogGk
-+vVPOuG9MqK/K5lkn/dTjd2RLJYL9F3uYnsK3I2r6nk
--> ssh-ed25519 zcCuhA w1iPgVkUx3U/r64ooH4UhUMnrHC+Kqs5oooDIL+pbyA
-zUDp/32Hj3pEEXeL/8BJ0J5qQLqCOjpzbmQdsXGA9qk
--> ssh-ed25519 CpJBgQ kRl0KlOJtcHsnNyJfyWlm9cW6ZQMrzmhgKaT+zYr03A
-lTprX0AfgP68w5towNfJw/YO3LoZFZYm0Y26Lb2La50
--> ssh-ed25519 aXKGcg 4T+HCfrAPXDQORxNFm3lR9qJBfd4WcCQ/ny7bBs4mT8
-zKu2W42LJl6jUS6vYFJj30x+SaQQarx7OALCJ7fUTac
--> ssh-ed25519 OkGqLg EEpq+VV3LC55VErd92bKnj7KqEzQqS6S60EZuCgb5Co
-XiyO6rELbfgj+2S3SQDu4Csz0Bw1NIGos69ixDPIEMU
--> GY`K*hZ-grease VW)6 t.El^< @P
-dS5BLWUWe5RDzdf4uWzEOwW7lLrWtD8hqISTSWzFOFGnQgWX6cqZhtUlCmciRlCq
-RLXx5Nu3sSIEBX6FZR30PjmjyDQ7qArxc/Up0pkJ+ntG1d2lobyeB3qXsn8femUU
-Ku76
---- 7KKYqquKMip1Qht63i2YH/9lGTv+MMso2YtIzF+6eis
-b>™าw~I‚รRผjŸWแไ=อณ๎?ค1:‰ZMJˆ’ึgฆ๙ผอ๕Jโ‚2ฮ*nz ิEึผwgˆคžq[3๎ฒŒย๔๛.Ž๕ฃ‘ะ^ๆŽ8iซ%!#ณ|‡ub๓2dูaง•rnฺ=€ก๕ำปํื/›T‹ปI-ฆถึMฯT‡E๚‹ยธ‰‹ห•Nใ'0ว๏\Kรd~๘-€kษœฺfบ)งํ
\ No newline at end of file
diff --git a/ops/secrets/gerrit-secrets.age b/ops/secrets/gerrit-secrets.age
index 9869b0d46a..9ad123d578 100644
--- a/ops/secrets/gerrit-secrets.age
+++ b/ops/secrets/gerrit-secrets.age
Binary files differdiff --git a/ops/secrets/grafana.age b/ops/secrets/grafana.age
index d6022b4ea5..eef349d64c 100644
--- a/ops/secrets/grafana.age
+++ b/ops/secrets/grafana.age
@@ -1,17 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw FAneL6Ra+ipVGA37rsEOIbObsDK5L93n1tk6vsDiq08
-HcEABCYv388oK0Fk3zcCXdnpi+arLHvYWjqS+vMwlWg
--> ssh-ed25519 zcCuhA n0FaAavgxFkJ1Lbd7bdDihV3m0aQ6IrD30G4N0NsNXU
-YumH3OYrbM/r/vgTFzJ8vEEWd7I/2yYdk6uBF4FLzG0
--> ssh-ed25519 CpJBgQ +80Q06PTyeX+lnPZf1o5v4jBDoSfuIudOD49c72i5gc
-gNXrdBhVicCa0j7uGmvFrbZFMgN+4NQ5wxyojQUI8JE
--> ssh-ed25519 aXKGcg cB4hgrcG47MEbgdvRQdJLBgQtGpyAw7rZTHQnE8mF2U
-vF46NzfPXjodk081WEd9D8LHMwB33Emswx65k2xiiQw
--> ssh-ed25519 OkGqLg H4abrPcW2U+0h9ChEANdCoaYgIXW/2GMOfaPXc142lk
-OYQyK4tSDsyRIbqLhXxWc6ZgnS4/9YS8FD/M3N8ctG8
--> 2UpS,n-grease 2@ A F$+@#Lk\ C4|Pa
-WKOTNBDihEkbp8U9elitxCVbpwa+RUXIUkWDKDdcLalK7no6DtfJVMyPAyPPymWg
-QOXPnkx1mw16wzj6elS86QU
---- vEbbqmuObg1gVHyfCb+6CN3bkeNyyWam3r7uG5KiHec
-อ่m2‰๘“6ณNRชภ69ฆ.l@๔(_์ฯŽดผ็YดUฮํตMนDม™๕'ํNq€฿ศ๘ุ%y%‡ต(2yไฏแJใม%๑Ÿู Co	“ั๐๖ƒ)ๆ๗า๏mซปำ
-Šลึ
\ No newline at end of file
+-> ssh-ed25519 dcsaLw 0h55HIHm0kf6LqtI99LFUWBCoERBmpoF+anfnxjhDBU
+0bHlgfRABn51BoMwAIjUlaVnCr3ZDXkQPmFOiIV3TvI
+-> ssh-ed25519 zcCuhA 0vFMP1qFEiN4MUt+1qQCqtEovmO2d6QHj+KjHBrvqB4
+CUM2MDNPEKpksyCQmfDg/k/CKz7/ckgafw4aj0FLcmE
+-> ssh-ed25519 CpJBgQ Y971kTqyElTHpOw4D7mUfkIQFWELOBeuGPUE6bqSrXQ
+zt3ju2cqDfQJg9BsSsWcOGfPu5Q4XuIz0k2gasaRCPE
+-> ssh-ed25519 aXKGcg eNxh3cCMbxG/u4luhlE2WQVzFMlZIcDKDx4dcpK43hY
+HGJZYkWbYA0I7HtArCz9ErXwAAfOBHe20JH1J5Bx904
+-> ssh-ed25519 OkGqLg a1+l3dkThz8LLp7C1D9l7CzdB8Q4hxjNzaY7B6HMSnQ
+du3nw0b61TGdF91Mq7C/PpjDlnIIph1dVEIivcDpM7M
+-> \gwpw]-grease p#:x#sA ^S5*A/ ZpY
+1rTU2Rc5MnpJj8zwOK4yR9HvDPOiKjCKHOURq6ak4SUmEgqqyqoujzRaL4I0cKf0
+zMFTkoKnLXjjLiHyvJWqCGwCRq9veUsTiJ6jqs+y6L+YaT71qDzDXi3YfX2p
+--- hraNRaUxkHCnhk6AC/3jyxaAj1gyyIi0Q7cqoupcRrA
+ก๛:ถ'ƒ!ซ37ซ ›s+0ป@มใืฏจฟd๊ ?๏!%๏lฌุดภอŽภ;ล๘๛ม2ขฟห๎‚กBพ—!†/gฝุใฑ/Žฐ:wuี‰ฏ๒ไ[ฉ~˜Žฅณภั๗p‹ฉFต
\ No newline at end of file
diff --git a/ops/secrets/irccat.age b/ops/secrets/irccat.age
index b70abf636c..2002b15c49 100644
--- a/ops/secrets/irccat.age
+++ b/ops/secrets/irccat.age
Binary files differdiff --git a/ops/secrets/journaldriver.age b/ops/secrets/journaldriver.age
index 823b527880..c58773f36b 100644
--- a/ops/secrets/journaldriver.age
+++ b/ops/secrets/journaldriver.age
Binary files differdiff --git a/ops/secrets/keycloak-db.age b/ops/secrets/keycloak-db.age
index 185f79da8b..54194df183 100644
--- a/ops/secrets/keycloak-db.age
+++ b/ops/secrets/keycloak-db.age
@@ -1,15 +1,15 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw rG0ThGyx3bkL/WOz1K1iP3CmrKORLjsUrLNJbtb1WB0
-xbkyt7EUb1BhBKUYt3hh93kEU1avcqlCLKfHc3x+BEU
--> ssh-ed25519 zcCuhA mwSN0urAXmA4vPCWIkzvCuDoE/LcA3eWpXr24Qab/lY
-Esa4Rfn55KYpIdYxsxGhBpPs40o28PJHbn8AEDn1n78
--> ssh-ed25519 CpJBgQ ODm3P+PymrXBxEejSDi2YUTEadBVzJiIt6vYHpzH1C4
-nC9FY8yilVG65HXmRTtpvjKj2awE9SI1qp8duskNP7M
--> ssh-ed25519 aXKGcg cdO7r0WCOktOmldIqvjVogyCximfA9sWd2Vq+bBgF2U
-1INC04f5PDwQgSQVeDpJomL5iZmyQfTwzHVu7BG+UUw
--> ssh-ed25519 OkGqLg D6x2fkkNeoZToQrOhNVh69Y3kWN5NqZzXkUc2556nBY
-ZC4asUqTT6ZnQdnYV9Xn0yqTgLFt14Vo+3RncxWingU
--> R^R|CZso-grease xq76HV<!
-MQSwHZCAIj24PlpplrTWjrZPAe5I31NC3xnWU80Q7Gk7FHUavAw
---- NG3cBfD3zeP6McHAXxhPuWZVrC9au95/+r6fMi01Gjs
-`$ฌ|แmRจ_Œ€!z[|ไผญ2†ˆs็ฏ"hฮฮ0›๘วลพ’*0(ถ๋๗์แฅล-&
\ No newline at end of file
+-> ssh-ed25519 dcsaLw tWBrwZf6FNYAHRjoVV9/X6gJCXPqxZSoA01dvIrIOzg
+6W2A3smrrosM3sJgl5CT9vkCWqVKR3SaSxWS2nnwKJU
+-> ssh-ed25519 zcCuhA IS0OcHfEfb01xe+FJUe1poruK+uuP0MaJpeoGYyVAFY
+eEzcEYcW4KoKZZUEH/ha1nn9NudeK9HgPRgmrCWMjug
+-> ssh-ed25519 CpJBgQ 4mjCHMHfnGu2bhANPBNmcrZQrKBcPgZU+ll8opmvGCk
+0+Vd6pRPovUcKa9i37JVU/DUeYAmJ9D88MR4flA8gY8
+-> ssh-ed25519 aXKGcg WGCgCoViKLqndC35OTaExqZlPBDRwXRBJFuS7fw8n3Q
+kUHunOUgIsxXmOzMCwUFF/0dYiae8YZGmgZaz8gXPJo
+-> ssh-ed25519 OkGqLg LLIDJkImcqMjwRitnGevcav5YjDwYsQ//elx7fgbCQ4
+EnYTppSr/GKug9T+bFLGxrxUnNiXD5ODhB75OcH/h24
+-> j@-grease @:arA
+8EFNz7i8N3gbZEMaQw
+--- RkHJIg9pif/R47lgqrZD/XgkTETxXWkwW9QnFFsmfOA
+ซoโ]ู~ฟ…6ห+j๘n]Žlี+๚ฺK=สฝ	Zp9ข๓ฟยR์๐zVg u2ฬฬๆ‘_
\ No newline at end of file
diff --git a/ops/secrets/nix-cache-priv.age b/ops/secrets/nix-cache-priv.age
index cc8513071a..0381fb1290 100644
--- a/ops/secrets/nix-cache-priv.age
+++ b/ops/secrets/nix-cache-priv.age
Binary files differdiff --git a/ops/secrets/nix-cache-pub.age b/ops/secrets/nix-cache-pub.age
index f628f2bbe4..ae06f49d69 100644
--- a/ops/secrets/nix-cache-pub.age
+++ b/ops/secrets/nix-cache-pub.age
@@ -1,16 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw j+RSQPvmBUL+/tJpoZqbMyh//yPYelDkS8rGMBDeYBg
-w9XLo36I+Fh8yCgL9aL1V2dHA5PFIhA/mi+inpA0vO0
--> ssh-ed25519 zcCuhA KTfCgCjc38/NRthB4ttrQV7aXbBgvs0Bgxitspo1TTo
-Zj7ZcjNxdiXgasq0pACRL6E3PvRsjsYsZeHFbX1mNYY
--> ssh-ed25519 CpJBgQ 4nH14KX8d5AYlQOYpAq77Oz6QLLcqh+We7WT0yXx3EA
-YCIc6wFk++uaankNET+SATIRMPXh1C2NemJssGUexXA
--> ssh-ed25519 aXKGcg x2izNmR+I9+2sRoHye4YUXU/6EZA8ZicIKUbjARVR28
-AV28t/cAwP6Js4lfYedJ88dCyAuKLq7RJU9SlhBx1FA
--> ssh-ed25519 OkGqLg PpKqeVlQ015Qv2zvvrR8kTj+7kDHirLz4Zk8f32NoTA
-huaUh3Q3uJmsi9yWyuJgnEhgmsVjspfpR+IN6uT8FgA
--> R2aR1C?^-grease
-7rumeWTufR7m6GRBOwKKVfzmMG8QRHzmt103vQfgmylhzGa2r6z2L3qSfFTqCW7T
-gMdbpgVvvTO+5aROt+iieBz9KFkHD3l/NXAhyZf8ydWRQlmDXcomY7QmSC3jLAE
---- RX4Cux3g3rn4jdCZMpP8XenZ45uol6W4+wBk8jofI0E
-ผใ=ใ๐งึ[ํญ๐„ีื๒แ$ึ˜ฮKฝ๎ณlmฅdฃh‹e๙&ม๎ฤ*ภEŒปR๐ฬ็t๓ฮŸบ์Dๅ:ฒฏนฃฎž‹ว;-ะ=;—Wป$•0
\ No newline at end of file
+-> ssh-ed25519 dcsaLw +jfxfM1YDu5CoYtFeRWtpkUQhmFWn/kNBYsBnie7BVg
+XxL9l87hXD0zCUEwbSR9OHSYgpOw89Km5iyxPPnVDGQ
+-> ssh-ed25519 zcCuhA VAoDkN2gwErUFE/59V4IF9PbSBSleOjt2gosvYnHxWg
+Pf6eh8EfAdATjZIkQfhhqOXuJXIdwIpybITcn+rcutI
+-> ssh-ed25519 CpJBgQ C6zIv78gu+wBeAjhmXANegSNqGHnugemXBPQcTimgxg
+80109g83Hk+smWuZkTIZJ6VFQqJ+LU1boWKQIH1AHjc
+-> ssh-ed25519 aXKGcg lPb+kGr0vuJkQO6VutAm4Yh1CVi/XfqNdGbAh/B7ZRk
+h4xb++7I9iv8208oqY0xLruA1r62mepISFcusczdbgs
+-> ssh-ed25519 OkGqLg aOHt9OR8JChtYpclkgn9wCFnlayFje7WsMGQb8AqChU
+3VRTDMUwFtDcoxGU/wiBzTvS0SB/xOpBG6s+ENvAXVE
+-> Kow$7|\-grease
+8OGnQnY7gm4vMJRXjnBogA0HRU7hqIxs2sErFc7sV1CUNkZlFjdK8tZomlNwshjc
+p18HgtjJnaGhSqg1LyP7cJAo/XnSwDYCeNna/6vdlKBR3JeuOGTmx1NIG/cGSg
+--- w+jJplb/J3av+UcltcFf4qSqHoQ8Ol8lH/fFB3051Gw
+qIํe:1*`j8๕ฑsบnHcyฮเ7ฃฒ™ศรตๅ(ชใพ.•˜xžDธ_}‚%๓)P,Dๆำ6ซSอ้Hล๊รU9ฐ๋”ฌิ0ํ8อิํ\ณ๖—'
\ No newline at end of file
diff --git a/ops/secrets/oauth2_proxy.age b/ops/secrets/oauth2_proxy.age
deleted file mode 100644
index 816944684a..0000000000
--- a/ops/secrets/oauth2_proxy.age
+++ /dev/null
@@ -1,16 +0,0 @@
-age-encryption.org/v1
--> ssh-ed25519 dcsaLw pkxciQfQ/yrexMq/Djpq1KNLFYBRTnJSi3fo4iQ0MDk
-FAlEvIgT+h/7Lcj5E0BeEbaWlZAg1THoiqsQg6Sy1oI
--> ssh-ed25519 zcCuhA sey8T2EXLHh5TF726U0DSn+MfXYYjimQxdsE67iflTc
-lPWYa9jrmwkac8KkCUypfZ5D3GCZwtdQPaXQRiM5xMo
--> ssh-ed25519 CpJBgQ 6EzBbhxLD1Cjy1LRWnfum+tFvPRzxMoPT6P2HDN7qBs
-BPWNJiFIrAPdcOOK0um+RzclUGgrS7yJwCjx8X0pYTk
--> ssh-ed25519 aXKGcg kMVeXntSlq3E5hbuNtu7e+iKoJpQDRR4isbx/WCYc0g
-fWvCPlcnjunuQ2LB02eQ51gr6SK2leaNuHttQOjJOyw
--> ssh-ed25519 OkGqLg QFU47rj1sU5JuQtehbxyymEOpZYl0bWY6dRo81KrQxE
-5TXNy6e2sM5b+K5lSXEkLdJ8F4ZDJfYEetJ7/jsxAIY
--> `!"O*HV-grease 1YD XwG${5; #Pr \7G
-CD72odW0Q4DMW6SGY+cUpBPhFePtjebkf1rpZJz0Twl8YrzbrXQfIgWv+tCUbr2d
-PKZKtlc9u0F+B6BKfVpZn0s0PD4/XGQ1PNLL/ZajxvYSB/w+UWbE67s
---- U0nGetyOZONCTw7TQJ5QNUScp6v2noSVkrWCMJeROH8
-๒‘๎zsอฎ~=๓H'์เQศ|d› Œ’L_Bฌ™ฅx0;๚7˜Cgร
*ฦ๋˜B3‰ฟฉนkฮp˜ๅ์8{P+aคฐ๗ู%ฒุฝแฎ)ชEmแFๆ'˜ พ`าุA๐ทUธ•vฆึ•๒ฤUDzcL›ไ๘W๖่ณไสo”x๗ธโ๚๘7‹PTฒ(Š	ฏn~aฑ
\ No newline at end of file
diff --git a/ops/secrets/owothia.age b/ops/secrets/owothia.age
index c3ad07d232..177ee61383 100644
--- a/ops/secrets/owothia.age
+++ b/ops/secrets/owothia.age
Binary files differdiff --git a/ops/secrets/panettone.age b/ops/secrets/panettone.age
index 542c866d61..0be42dc0a7 100644
--- a/ops/secrets/panettone.age
+++ b/ops/secrets/panettone.age
@@ -1,17 +1,15 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw 0vXqVyiNwKAvIjBi1PPPWYzapFFuwFAGQqohfdaaThc
-cp+oevy9hbMvviVNTxKpws1Fsyirxr/nKZltlA08cWI
--> ssh-ed25519 zcCuhA bFhpOsXo7H8GF3xLFwLs84aJegWj50+pEQDbyYYpwE0
-Y5iRW6/dhBNUHgNmObUEJu991Ms0RU1Y7xkeoz16A0U
--> ssh-ed25519 CpJBgQ 5y0eXpmerwxRtySanRSBQeHCkMt96BOLVgR8S2lDSH4
-+Z+3b9d8B5HZRVOL76SCNPIh9nhXKPSWq4lj0X2k2eg
--> ssh-ed25519 aXKGcg HK5KeRoc+fhbYQ9RZTnum5x2y+vvyEQNKRpnNOISFn0
-TxZplwFO2e1YgY/V9tkLSVGxh9407xsxsT09N3jfcv4
--> ssh-ed25519 OkGqLg otifGzPJ9Ykwdx9AkwlFW9AHAQL5OXnDexp8N4lJ6ys
-dFVgPNi8p3wQYbVbokxGqiNKUd3POXBs49LO3FAR6Js
--> e"s'-grease :{S#]YZ MyRj r['U^ 0
-+qc7
---- Gnh5iyD6drHbPt2bE9JCGlXcPAPDPhkJl8A9+5SHNz4
-!กํuซฐ
-9wIV~„”Ep| พžŸ‹*ภa€Gc3฿ZฦฉผSQศาพฮ\”ร้)f[ุทท)7gถฃฏ˜ลืภ฿ืกษPYŠ‰EE5มWั$ุigL๗ฒทLC๋ผF=N
tณ
-b7ลyิFxšŸ‚;d9ะฝผั+<rพ๕(U^P1๖๓W/%w่ร#“๑cWK๒k|•ฆ)M†rrYzฤL2้/‘ฬ!พ]๛ :DryaŽปฬณG
\ No newline at end of file
+-> ssh-ed25519 dcsaLw zzUe0JqhICtd/kgZnXFpwaQ1Ma6nqy/hMWaOJpRHmDs
+4cR+OnWShG6MpB/u0yfsSxplEch7x7DbygfBiJGxOOs
+-> ssh-ed25519 zcCuhA 0RZEYC9IuazO9fROalwoOCIgc0j+rNBP3gw7SKG0yEw
+mPRhN0hvccEr1A9ihWAFMH4/24vpBKpxBVq4BKBMmYM
+-> ssh-ed25519 CpJBgQ VrmfTtTVxuQmpUxMxtXtCnr8pFyqwtdyLHdbzYrlKlM
+kHgEdPmoIOLnGuMF5F5Ol1yZWcactSE4OZI0BSmDN+g
+-> ssh-ed25519 aXKGcg On4jwgsH504ZjYRwfw5oAfIDk3wU0+xgd43ryAn9H0I
+fayzht1ZPPiFCjuYTdwVtJu2nOUg4wtp5IipOR4oJm8
+-> ssh-ed25519 OkGqLg mubp0xI0fvsKOAUaNaftFkHJ+bxgFHbgjn+A7sR8XVs
+X68Zr8HvC4/XPC0AFIA5f1SKu7NSR/23oeX8cW1qfis
+-> ?`-grease
+hOy2Rwvk6+vXpHWWA49Wp10wKbw9TfsLXw
+--- 9MLGx6BVm40C0CSV3bq6dnXrpy3QunBlh2/uO5OisUU
+วณGž<ีๅมะYืA๗Vsณ๐/-%gช๚.e@†,Z๑‹ๆ•F˜Wๆ”ถ&ๆ๎ง๓<O๖q@พ>wๅ‡ฬ›Q‡>™-gว“'ฉฬ†`กถจX๖าŸฯP8—ณx<RNvท9ื#'/)ภฆg‚ฆ๚่m2๕ฉิv๐<,฿7…๗ใษ้‚ขะวqฏฆชv็„QปทAOฮ-๓ฺ˜†+gๅcส#ต—ๅฝ๎ข*–ขฐŸeํ -งท)า ๙;
\ No newline at end of file
diff --git a/ops/secrets/secrets.nix b/ops/secrets/secrets.nix
index e71ce00981..660fe5a79c 100644
--- a/ops/secrets/secrets.nix
+++ b/ops/secrets/secrets.nix
@@ -1,13 +1,20 @@
 let
+  flokli = [
+    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTVTXOutUZZjXLB0lUSgeKcSY/8mxKkC0ingGK1whD2 flokli"
+  ];
+
   tazjin = [
     # tverskoy
     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM1fGWz/gsq+ZeZXjvUrV+pBlanw1c3zJ9kLTax9FWQy"
 
     # zamalek
     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDBRXeb8EuecLHP0bW4zuebXp4KRnXgJTZfeVWXQ1n1R"
+
+    # khamovnik
+    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID1ptE5HvGSXxSXo+aHBTKa5PBlAM1HqmpzWz0yAhHLj"
   ];
 
-  grfn = [
+  aspen = [
     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMcBGBoWd5pPIIQQP52rcFOQN3wAY0J/+K2fuU6SffjA "
   ];
 
@@ -18,8 +25,10 @@ let
   sanduny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOag0XhylaTVhmT6HB8EN2Fv5Ymrc4ZfypOXONUkykTX";
   whitby = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNh/w4BSKov0jdz3gKBc98tpoLta5bb87fQXWBhAl2I";
 
-  whitbyDefault.publicKeys = tazjin ++ grfn ++ sterni ++ [ whitby ];
-  allDefault.publicKeys = tazjin ++ grfn ++ sterni ++ [ sanduny whitby ];
+  terraform.publicKeys = tazjin ++ aspen ++ sterni ++ flokli;
+  whitbyDefault.publicKeys = tazjin ++ aspen ++ sterni ++ [ whitby ];
+  allDefault.publicKeys = tazjin ++ aspen ++ sterni ++ [ sanduny whitby ];
+  sandunyDefault.publicKeys = tazjin ++ aspen ++ sterni ++ [ sanduny ];
 in
 {
   "besadii.age" = whitbyDefault;
@@ -28,8 +37,9 @@ in
   "buildkite-ssh-private-key.age" = whitbyDefault;
   "clbot-ssh.age" = whitbyDefault;
   "clbot.age" = whitbyDefault;
+  "depot-inbox-imap.age" = sandunyDefault;
   "depot-replica-key.age" = whitbyDefault;
-  "gerrit-queue.age" = whitbyDefault;
+  "gerrit-autosubmit.age" = whitbyDefault;
   "gerrit-secrets.age" = whitbyDefault;
   "grafana.age" = whitbyDefault;
   "irccat.age" = whitbyDefault;
@@ -37,12 +47,12 @@ in
   "keycloak-db.age" = whitbyDefault;
   "nix-cache-priv.age" = whitbyDefault;
   "nix-cache-pub.age" = whitbyDefault;
-  "oauth2_proxy.age" = whitbyDefault;
   "owothia.age" = whitbyDefault;
   "panettone.age" = whitbyDefault;
   "smtprelay.age" = whitbyDefault;
-  "tf-buildkite.age" = whitbyDefault;
-  "tf-glesys.age" = whitbyDefault;
-  "tf-keycloak.age" = whitbyDefault;
+  "teleirc.age" = whitbyDefault;
+  "tf-buildkite.age" = terraform;
+  "tf-glesys.age" = terraform;
+  "tf-keycloak.age" = terraform;
   "tvl-alerts-bot-telegram-token.age" = whitbyDefault;
 }
diff --git a/ops/secrets/smtprelay.age b/ops/secrets/smtprelay.age
index 3904107261..62fbaffadf 100644
--- a/ops/secrets/smtprelay.age
+++ b/ops/secrets/smtprelay.age
@@ -1,16 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw mqDmOqzDl7BY8xj7TuCHcIGrkiqURHK4Y4NkmUesyQE
-sfNvq6kuQUWuza3B6feUQtcWYhYh/aiN89fTOHdhHSY
--> ssh-ed25519 zcCuhA rBlPiVmj7dSYHljc4/fhL7a9GSeCp/8FqG1R2f1kPgw
-o6Za8zm3n2LBnj9jQAU7Xtvt5ULIUesdiTi11DeRMY8
--> ssh-ed25519 CpJBgQ vM3qI0XMQJY/ExxE3a0mmHhc5hY9rPDBzdJ4v9oZBlY
-lLHtL9j8ltx86eWwlPkyblcWjRd2iMjimwMXZptsRAc
--> ssh-ed25519 aXKGcg +6heNooQufYnntQ1PJHlW/8aG4vijzY/CfXHUGPKMQE
-T95bxZSRC9Cdx9ZTaTnHWdeq0wKOkRL9mQxNo8j9SfA
--> ssh-ed25519 OkGqLg HvpZmHz0DZIqWHiXvUsJ/OILlRhptl4WMDDiVF6dxko
-FoTSc84FRFnBh0rOYFX3M7t9p/hvn4DZMHZfU9jy0zo
--> $<0F{v-grease
-blva6tBLrd967p8hOMGy0JT6Y19zWNdgowASEEBpoFzsmNlyKdbaYyMbxKTuqmCy
-8Wy5TpBj99pcUsEB
---- DTMNC/wQr8xtJKIPPKjx90PmAZ15eimydKbYGnEa7Jc
-ญ๐ึิถ"โ๔R๊A†‡๘ˆ!]*ˆ‹ผ	ฎ˜†ค)ล๏
ํ2)7๊—ž3ณAjำค์สะLฎอ5E๓‚N"1:จ4tŒนะpซ.‹๗jpqฦG2’†(‘\แmq฿‘Fฬ@้อ1aลŒ%—
\ No newline at end of file
+-> ssh-ed25519 dcsaLw CW2Lgm0tSWUDwKSNSX/aLkVzQ/QeEeQgU3NITpz2D0M
+F7dA+zWdCz21s443bj9zCz6lBsRlFIxiG+l8CdbuPFk
+-> ssh-ed25519 zcCuhA l8rsBoYDwhUB5stbeGXYTQ4Fz745ywXFCOQZn2cMBW0
+TycVcUZjR2TDv5DPC54+RwoU6Fj4QpRUJj1j0HM/JCE
+-> ssh-ed25519 CpJBgQ CbwZO5LmSxd0HRYkf+lV+ymFcXSn/49GAPHG4l1I7gw
+xSmab5+BnAZF/B0n32xX1qZPdHgfoEMGIuZqlpnISjc
+-> ssh-ed25519 aXKGcg Tr+odf9p1RBrQK1guR6ToeN4wG1KLA3jwiPIkgyEjws
+TaeCnjiRp8VZoMS5qs+OfVbBc6zudayD693h/eGvVOo
+-> ssh-ed25519 OkGqLg Dmnsqz6PKzMd6w4t+l6+EWuia+stPwSEtu00KVuAojo
+rZ/i1WJhrCM/ZQTAroRRSjzUVJw2UJlPUe1uHYqSscw
+-> w!^Z-grease i86O2 i0.Rch
+/zsRadAGYzAY6F/J5m6lMjmojkN7NbY3TbfQbA
+--- /rQgwuY9SVGLKeUzY5P6c+sGQ1I1aw5cQxmO46QKDSQ
+ ้(`ฏฏคU ฌ๙‹š,ใรcผ้|า‘Pๆ็• ฟ9แ@&	ซวgM฿’
+CHโž3ik๗มฤ3#|ๅึgžธMาึณA•ด—gขAึ๚nZ๓วY—โtจุ๛ฏฬ2น‰ฑK2˜…Yฺ
\ No newline at end of file
diff --git a/ops/secrets/teleirc.age b/ops/secrets/teleirc.age
new file mode 100644
index 0000000000..ebc88fc9ef
--- /dev/null
+++ b/ops/secrets/teleirc.age
Binary files differdiff --git a/ops/secrets/tf-buildkite.age b/ops/secrets/tf-buildkite.age
index 5ce558136d..0cf6066fa6 100644
--- a/ops/secrets/tf-buildkite.age
+++ b/ops/secrets/tf-buildkite.age
Binary files differdiff --git a/ops/secrets/tf-glesys.age b/ops/secrets/tf-glesys.age
index caeac0b1ee..4e50454b62 100644
--- a/ops/secrets/tf-glesys.age
+++ b/ops/secrets/tf-glesys.age
Binary files differdiff --git a/ops/secrets/tf-keycloak.age b/ops/secrets/tf-keycloak.age
index c916dcd2a3..237b9377bd 100644
--- a/ops/secrets/tf-keycloak.age
+++ b/ops/secrets/tf-keycloak.age
Binary files differdiff --git a/ops/secrets/tvl-alerts-bot-telegram-token.age b/ops/secrets/tvl-alerts-bot-telegram-token.age
index d9562ce924..e897fedc03 100644
--- a/ops/secrets/tvl-alerts-bot-telegram-token.age
+++ b/ops/secrets/tvl-alerts-bot-telegram-token.age
@@ -1,16 +1,15 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw 14nPZssvAKQSzPdL+1iyz0BVA1DOdFDafdCyRfcmSWo
-+ENcKRKyUN3G9+kd/Y9IpQbO3rIZdYiznqGO1cfVNZE
--> ssh-ed25519 zcCuhA i/ag/HD84XrTpYigStOfwnWBLjOSypCnVuIYjtdVc2o
-T+dN0nl3H6J6OaMyLNHLgy99H8YJtSjgintxogJkWjo
--> ssh-ed25519 CpJBgQ bbyerpmjpTkMmSaLnV5OuMQzqqtGao4eqE4kiFzm+Dw
-0Hskm4/Cks4Eu/Jr4Eh6302jWo64rdInvvJH6XJFyBk
--> ssh-ed25519 aXKGcg sqdfN/2YLFmdhEWgn5Z/OAsmXwMORX/dPrmD4O7MlCE
-h/ej9LjZHn04rkEbvIaGAcLT3dMs9RdL3vFA+Rgdp3g
--> ssh-ed25519 OkGqLg fK2cPxfOupCIfC1giMj2CFg/K/+4XX+fLpkqUmQHzDY
-uXTHT30ytEvliNAvmwlPyaySsYDVLarZgouV9Tfo6qo
--> Me?Ykt-grease 4S m!3LR ^/)u#tFR
-1A
---- UP4D68fCAMJC+1T1zbIiGCah3Ph+pJf7Z6wv2YJaOCQ
-๋ขโjs—]U-Jฮณิ6๚Y†#^‹
-$$L์1pํ๊wปa:qwgq3๏ิ’ฎb“0™zH%ฎf!.๕0›ฮด'็ึ˜๙!
\ No newline at end of file
+-> ssh-ed25519 dcsaLw JGXCnhez0LnlUV8eOitxizmxw/gV+1taBRhNvwvVcms
+qsRTOpifnoc0eorFjd4UlP7O3hkRR3KjDUcImASK0jY
+-> ssh-ed25519 zcCuhA KUcyaHcmuqCGtJBzvc2UK17gRrjzuzIxll+TS9Q4nWs
+CAJ19ClA9Tqj1fcYySq+K9gdZe6Uv0toZLnhlovr3tM
+-> ssh-ed25519 CpJBgQ OAE+u9JuC6KoefjCOTj4NkQElZRe6/EEIAGBN/XelnU
+M9MHlKxbEBJ+gACo2FiYqmm1cAoYW31+nP16qnVZ7Zw
+-> ssh-ed25519 aXKGcg Ll6v6v5HpUIEuOzjpVsPMmPQMnNkmyB4fz/YwNXfCHU
+MmFQy2WkKn5SM0bhe4NNe/lMnneKoOF+Ufq0t0QjNbw
+-> ssh-ed25519 OkGqLg PS6KLwat1z2BSQ9sIKDaryVU39EJR+iiAaKSP/KSPk0
+qUQP2f4MFk83zQ9edlSNC8jwpJvmp2xhOysd8rnYzW4
+-> >NI-grease @mOcHT z|%,s- mw^c *
+zu0M2pS6v3zehnLg
+--- jltBYy9brAtpkEIqPoGmIVe3s5XnWtpa9EmuXlAf91c
+št”dX2-น"ฤำ#ฦ1›ํn'ƒ\‰๘'{Dlw;Pึดะ@ฺฬ™{๙฿B	!yฃ+™x๕หะํWตถฤB:wtูqph
\ No newline at end of file
diff --git a/ops/terraform/README.md b/ops/terraform/README.md
new file mode 100644
index 0000000000..9ff6c23d47
--- /dev/null
+++ b/ops/terraform/README.md
@@ -0,0 +1,5 @@
+//ops/terraform
+===============
+
+This folder contains Terraform modules and other related
+Terraform-tooling by TVL.
diff --git a/ops/terraform/deploy-nixos/README.md b/ops/terraform/deploy-nixos/README.md
new file mode 100644
index 0000000000..fd0bd1b442
--- /dev/null
+++ b/ops/terraform/deploy-nixos/README.md
@@ -0,0 +1,50 @@
+<!--
+SPDX-FileCopyrightText: 2023 The TVL Authors
+
+SPDX-License-Identifier: MIT
+-->
+
+deploy-nixos
+============
+
+This is a Terraform module to deploy a NixOS system closure to a
+remote machine.
+
+The system closure must be accessible by Nix-importing the repository
+root and building a specific attribute
+(e.g. `nix-build -A ops.machines.machine-name`).
+
+The target machine must be accessible normally over SSH, and an SSH
+key must be used for access.
+
+Notably this module separates the evaluation of the system closure from building
+and deploying it, and uses the closure's derivation hash to determine whether a
+deploy is necessary.
+
+## Usage example:
+
+```terraform
+module "deploy_somehost" {
+  source              = "git::https://code.tvl.fyi/depot.git:/ops/terraform/deploy-nixos.git"
+  attrpath            = "ops.nixos.somehost"
+  target_host         = "somehost.tvl.su"
+  target_user         = "someone"
+  target_user_ssh_key = tls_private_key.somehost.private_key_pem
+}
+```
+
+## Future work
+
+Several things can be improved about this module, for example:
+
+* The repository root (relative to which the attribute path is evaluated) could
+  be made configurable.
+
+* The remote system closure could be discovered to restore remote system state
+  after manual deploys on the target (i.e. "stomping" of changes).
+
+More ideas and contributions are, of course, welcome.
+
+## Acknowledgements
+
+Development of this module was sponsored by [Resoptima](https://resoptima.com/).
diff --git a/ops/terraform/deploy-nixos/main.tf b/ops/terraform/deploy-nixos/main.tf
new file mode 100644
index 0000000000..50278b248e
--- /dev/null
+++ b/ops/terraform/deploy-nixos/main.tf
@@ -0,0 +1,113 @@
+# SPDX-FileCopyrightText: 2023 The TVL Authors
+#
+# SPDX-License-Identifier: MIT
+
+# This module deploys a NixOS host by building a system closure
+# located at the specified attribute in the current repository.
+#
+# The closure's derivation path is persisted in the Terraform state to
+# determine after Nix evaluation whether the system closure has
+# changed and needs to be built/deployed.
+#
+# The system configuration is then built (or substituted) on the
+# machine that runs `terraform apply`, then copied and activated on
+# the target machine using `nix-copy-closure`.
+
+variable "attrpath" {
+  description = "attribute set path pointing to the NixOS system closure"
+  type        = string
+}
+
+variable "target_host" {
+  description = "address (IP or hostname) at which the target is reachable"
+  type        = string
+}
+
+variable "entrypoint" {
+  description = <<EOT
+    Path to a .nix file (or directory containing `default.nix` file)
+    that provides the attrset specified in `closure`.
+    If unset, asks git for the root of the repository.
+  EOT
+  type        = string
+  default     = ""
+}
+
+variable "target_user" {
+  description = "username on the target machine"
+  type        = string
+}
+
+variable "target_user_ssh_key" {
+  description = "SSH key to use for connecting to the target"
+  type        = string
+  default     = ""
+  sensitive   = true
+}
+
+variable "triggers" {
+  type        = map(string)
+  description = "Triggers for deploy"
+  default     = {}
+}
+
+# Fetch the derivation hash for the NixOS system.
+data "external" "nixos_system" {
+  program = ["${path.module}/nix-eval.sh"]
+
+  query = {
+    attrpath   = var.attrpath
+    entrypoint = var.entrypoint
+  }
+}
+
+# Deploy the NixOS configuration if anything changed.
+resource "null_resource" "nixos_deploy" {
+  connection {
+    type        = "ssh"
+    host        = var.target_host
+    user        = var.target_user
+    private_key = var.target_user_ssh_key
+  }
+
+  # 1. Wait for SSH to become available.
+  provisioner "remote-exec" {
+    inline = ["true"]
+  }
+
+  # 2. Build NixOS system.
+  provisioner "local-exec" {
+    command = "nix-build ${data.external.nixos_system.result.drv} --no-out-link"
+  }
+
+  # 3. Copy closure to the target.
+  provisioner "local-exec" {
+    command = "${path.module}/nixos-copy.sh"
+
+    environment = {
+      SYSTEM_DRV  = data.external.nixos_system.result.drv
+      TARGET_HOST = var.target_host
+      DEPLOY_KEY  = var.target_user_ssh_key
+      TARGET_USER = var.target_user
+    }
+  }
+
+  # 4. Activate closure on the target.
+  provisioner "remote-exec" {
+    inline = [
+      "set -eu",
+      "SYSTEM=$(nix-build ${data.external.nixos_system.result.drv} --no-out-link)",
+      "sudo nix-env --profile /nix/var/nix/profiles/system --set $SYSTEM",
+      "sudo $SYSTEM/bin/switch-to-configuration switch",
+    ]
+  }
+
+  triggers = merge({
+    nixos_drv   = data.external.nixos_system.result.drv
+    target_host = var.target_host
+  }, var.triggers)
+}
+
+output "nixos_drv" {
+  value = data.external.nixos_system.result
+}
diff --git a/ops/terraform/deploy-nixos/nix-eval.sh b/ops/terraform/deploy-nixos/nix-eval.sh
new file mode 100755
index 0000000000..65f534180b
--- /dev/null
+++ b/ops/terraform/deploy-nixos/nix-eval.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+# SPDX-FileCopyrightText: 2023 The TVL Authors
+#
+# SPDX-License-Identifier: MIT
+set -ueo pipefail
+
+# Evaluates a Nix expression.
+#
+# Receives input parameters as JSON from stdin.
+# It expects a dict with the following keys:
+#
+#  - `attrpath`: the attribute.path pointing to the expression to instantiate.
+#    Required.
+#  - `entrypoint`: the path to the Nix file to invoke.
+#    Optional. If omitted, will shell out to git to determine the repo root,
+#    and Nix will use `default.nix` in there.
+#  - `argstr_json`: A string JSON-encoding a map containing string keys and
+#    values which should be passed to Nix as `--argstr $key $value`.
+#    command line args. Optional.
+#  - `build`: A boolean (or string being "true" or "false") stating whether the
+#    expression should also be built/substituted on the machine executing this script.
+#
+# jq's @sh format takes care of escaping.
+eval "$(jq -r '@sh "attrpath=\(.attrpath) && entrypoint=\(.entrypoint) && argstr=\((.argstr_json // "{}"|fromjson) | to_entries | map ("--argstr", .key, .value) | join(" ")) build=\(.build)"')"
+
+# Evaluate the expression.
+[[ -z "$entrypoint" ]] && entrypoint=$(git rev-parse --show-toplevel)
+# shellcheck disable=SC2086,SC2154
+drv=$(nix-instantiate -A "${attrpath}" "${entrypoint}" ${argstr})
+
+# If `build` is set to true, invoke nix-build on the .drv.
+# We need to swallow all stdout, to not garble the JSON printed later.
+# shellcheck disable=SC2154
+if [ "${build}" == "true" ]; then
+  nix-build --no-out-link "${drv}" > /dev/null
+fi
+
+# Determine the output path.
+outPath=$(nix show-derivation "${drv}" | jq -r ".\"${drv}\".outputs.out.path")
+
+# Return a JSON back to stdout.
+# It contains the following keys:
+#
+# - `drv`: the store path of the Derivation that has been instantiated.
+# - `outPath`: the output store path.
+jq -n --arg drv "$drv" --arg outPath "$outPath" '{"drv":$drv, "outPath":$outPath}'
diff --git a/ops/terraform/deploy-nixos/nixos-copy.sh b/ops/terraform/deploy-nixos/nixos-copy.sh
new file mode 100755
index 0000000000..6b843c3a49
--- /dev/null
+++ b/ops/terraform/deploy-nixos/nixos-copy.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+# SPDX-FileCopyrightText: 2023 The TVL Authors
+#
+# SPDX-License-Identifier: MIT
+
+#
+# Copies a NixOS system to a target host, using the provided key,
+# or whatever ambient key is configured if the key is not set.
+set -ueo pipefail
+
+export NIX_SSHOPTS="\
+    -o StrictHostKeyChecking=no\
+    -o UserKnownHostsFile=/dev/null\
+    -o GlobalKnownHostsFile=/dev/null"
+
+# If DEPLOY_KEY was passed, write it to $scratch/id_deploy
+if [ -n "${DEPLOY_KEY-}" ]; then
+  scratch="$(mktemp -d)"
+  trap 'rm -rf -- "${scratch}"' EXIT
+
+  echo -n "$DEPLOY_KEY" > $scratch/id_deploy
+  chmod 0600 $scratch/id_deploy
+  export NIX_SSHOPTS="$NIX_SSHOPTS -o IdentityFile=$scratch/id_deploy"
+fi
+
+nix-copy-closure \
+  --to ${TARGET_USER}@${TARGET_HOST} \
+  ${SYSTEM_DRV} \
+  --gzip \
+  --include-outputs \
+  --use-substitutes
diff --git a/ops/users/default.nix b/ops/users/default.nix
index aa9252093e..f9d277ab2c 100644
--- a/ops/users/default.nix
+++ b/ops/users/default.nix
@@ -2,6 +2,11 @@
 
 [
   {
+    username = "aaqaishtyaq";
+    email = "aaqaishtyaq@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IpWJeEYTYEsrgGBNQcnbWA$w4+gQmeJlhddeaHvmbpNa3hDVg1BkJESZSVAd2eSOs4";
+  }
+  {
     username = "adisbladis";
     email = "adisbladis@gmail.com";
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$wdgoLRrUgZuz0Kin9YiNgQ$E40VIgzgpMpylZqkfByTKiWQnerupfuf7LDgOsU8tJA";
@@ -12,6 +17,16 @@
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$8lefg7+8UPAEh9Ott8zH0A$7YuLRraTC1IgxTNTxFJF03AWmqBS3GX2+vfD4XVTrb0";
   }
   {
+    username = "aspen";
+    email = "root@gws.fyi";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$5NEYPJ19nDITK5sGr4bzhQ$Xzpzth6y4w+HGvioHiYgzqFiwMDx0B7HAh+PVbkRuuk";
+  }
+  {
+    username = "chickadee";
+    email = "matthewktromp@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=19456,t=2,p=1$HoZjVdJ90JmTEJf1MMLuDg$5Pa8kpJdFVsIxgoOTDsH0gv6CLumSIkMqYEn5UVfjwU";
+  }
+  {
     username = "cschilling";
     email = "christian.schilling.de@gmail.com";
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$9VN3IS6ViW5FFbVKWOZI6Q$gZxuYAYk0Opq4E5i8cbcNjfznCQNc+RiP7Xv1CUnrQU";
@@ -52,9 +67,9 @@
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$TrezbwIY5TKLnJiii0wafQ$K0S2p9I8tiqP907nkgoK6IbG9ia4IuDiylTcIs5pesw";
   }
   {
-    username = "grfn";
-    email = "grfn@gws.fyi";
-    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$5NEYPJ19nDITK5sGr4bzhQ$Xzpzth6y4w+HGvioHiYgzqFiwMDx0B7HAh+PVbkRuuk";
+    username = "ghuntley";
+    email = "ghuntley@ghuntley.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$ciCuQHeA7csqrFUv7+asgw$7GUC5fLJWWVoHP8DvpA+C1u4+iFdV2E311kwTFwGzaQ";
   }
   {
     username = "htbf";
@@ -62,6 +77,11 @@
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$2iVXQQfd26icaIguHJg/CQ$hA9ziqn7kQ06AV6uQxJCGXoG8f+LWmH+nVlk00a1n/c";
   }
   {
+    username = "IslandUsurper";
+    email = "lyle@menteeth.us";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$rNSsa8aYU4qvxeFnADgW1g$Zu6B6Al2usRRNfAKhWXzCAfiTfV3XQb0W6Op5TYN1oI";
+  }
+  {
     username = "isomer";
     email = "isomer@tvl.fyi";
     password = "{SSHA}OhWQkPJgH1rRJqYIaMUbbKC4iLEzvCev";
@@ -77,6 +97,11 @@
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$kA19gDabD1Fjy82olcmnsA$TTbkpAc0WYaA4DT2vc7+NAGXhC4Os1tPqZVpHFkzecE";
   }
   {
+    username = "jrhahn";
+    email = "mail.jhahn@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$giiu99hS7CzfsDZgxMNvKg$JiZZnFxOGHZRlUziYd3TkEiUplMz7Emy8fXfyLawPS0";
+  }
+  {
     username = "kn";
     email = "klemens@posteo.de";
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$CoRZInysud4sduDoMjVOCw$/bdvAvyPO2DPxOcHlBiG2+rbTGF9XAcHUhPurxiIpZM";
@@ -87,6 +112,11 @@
     password = "{SSHA}7a85VNhpFElFw+N5xcjgGmt4HnBsaGp4";
   }
   {
+    username = "noteed";
+    email = "noteed@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$rcLfF9xXysSx5sahVQLiMA$EgRgAVXn8+r2Csa3XgIHIEBf3hX4Y58pOHf2eDaBUnA";
+  }
+  {
     username = "nyanotech";
     email = "nyanotechnology@gmail.com";
     password = "{SSHA}NIJ2RCRb1+Q4Bs63cyE91VZyiN47DG6y";
@@ -170,8 +200,48 @@
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$NQdBVPNwh2ioDq9zWfMusA$2cABJGI8cU2JZirnVU5E5C28sTiePkiOPEAaqNUp/Fk";
   }
   {
-    username = "zseri";
-    email = "zseri.devel@ytrizja.de";
+    username = "fogti";
+    email = "fogti+devel@ytrizja.de";
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$wVNkImXloXIkCycnecdFeA$ECAdGdNzUUEq9sFGsIl0jb7AALGsHE+ndWRn6ilSmdE";
   }
+  {
+    username = "brainrake";
+    email = "martonboros@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$f4/ewdyRBQbClL4KzqypHg$6Ql/xkmfIr60Qp1XMaFherqhh4cekLIbsi7KMM6izfE";
+  }
+  {
+    username = "raitobezarius";
+    email = "tvl@lahfa.xyz";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$3NZTBbF5dZssAHC/ktcA/Q$AZxHGG0ycNMOkIxC/ONYbyhNxC9hb6cpWvnsNH8LWZk";
+  }
+  {
+    username = "hsjobeki";
+    email = "hsjobeki@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$jez9eVa2v0BznIJMOhw+hw$wUbwCS+Bfcjjzr08saQE6NNTPWNXWWaxv+UtBCdYC2s";
+  }
+  {
+    username = "totikom";
+    email = "eugene.lomov@protonmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=19456,t=2,p=1$r/EsEGkqCcv8ccjQ84pX7Q$ebpWno7LI1RXkWKBjnkDHZM1gPuPj1LSMoFUsX0j6AU";
+  }
+  {
+    username = "espes";
+    email = "espes@pequalsnp.com";
+    password = "{ARGON2}$argon2id$v=19$m=19456,t=2,p=1$eXeFrbNxuKn/JCpQr5VmxA$NtMNBceNg/JtqMfHk/qHxEHsEVsTWmHJbpq4ve/+XYg";
+  }
+  {
+    username = "caralice";
+    email = "tvl@alice-carroll.pet";
+    password = "{ARGON2}$argon2id$v=19$m=19456,t=2,p=1$mt/0RzKw4RHxm7ybpMHP5Q$P/SDBMv5si9D98NFO/eZgh2+InlByqYxqAvQWhl+p0c";
+  }
+  {
+    username = "yuka";
+    email = "tvl@yuka.dev";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$aEyiAIuynQMwfY7xE+pMxg$QdghylHO2JZMR/YyYf4UAnhhb/gBdAkoDeANEwdixxU";
+  }
+  {
+    username = "benjaminedwardwebb";
+    email = "benjaminedwardwebb@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=19456,t=2,p=1$kdFNmxgIGsF8TkB/GoPy1A$GUXd3M35Jqxqlfra4gPCcFW3ehE0RVrlHOzaoD7Pu7s";
+  }
 ]
diff --git a/ops/yandex-base-image/default.nix b/ops/yandex-base-image/default.nix
new file mode 100644
index 0000000000..3dc4b8f589
--- /dev/null
+++ b/ops/yandex-base-image/default.nix
@@ -0,0 +1,9 @@
+# Base image for Yandex Cloud VMs.
+{ depot, ... }:
+
+(depot.ops.nixos.nixosFor {
+  imports = [
+    (depot.path.origSrc + ("/ops/modules/yandex-cloud.nix"))
+    (depot.path.origSrc + ("/ops/modules/tvl-users.nix"))
+  ];
+}).config.system.build.yandexCloudImage
diff --git a/ops/yandex-cloud-rs/.gitignore b/ops/yandex-cloud-rs/.gitignore
new file mode 100644
index 0000000000..ab3f21a96e
--- /dev/null
+++ b/ops/yandex-cloud-rs/.gitignore
@@ -0,0 +1,5 @@
+target/
+result/
+# Ignore everything under src (except for lib.rs)
+src/*
+!src/lib.rs
diff --git a/ops/yandex-cloud-rs/Cargo.lock b/ops/yandex-cloud-rs/Cargo.lock
new file mode 100644
index 0000000000..0015d43106
--- /dev/null
+++ b/ops/yandex-cloud-rs/Cargo.lock
@@ -0,0 +1,1368 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "anyhow"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "axum"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "sync_wrapper",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bumpalo"
+version = "3.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "flate2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "futures-channel"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+
+[[package]]
+name = "futures-util"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "http"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-timeout"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
+dependencies = [
+ "hyper",
+ "pin-project-lite",
+ "tokio",
+ "tokio-io-timeout",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "js-sys"
+version = "0.3.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.146"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "log"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+
+[[package]]
+name = "matchit"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "petgraph"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "prettyplease"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
+dependencies = [
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prost"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270"
+dependencies = [
+ "bytes",
+ "heck",
+ "itertools",
+ "lazy_static",
+ "log",
+ "multimap",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 1.0.109",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki",
+ "sct",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
+dependencies = [
+ "base64",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.100.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
+dependencies = [
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.164"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
+
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "tempfile"
+version = "3.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio"
+version = "1.28.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-io-timeout"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
+dependencies = [
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tonic"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum",
+ "base64",
+ "bytes",
+ "flate2",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-timeout",
+ "percent-encoding",
+ "pin-project",
+ "prost",
+ "rustls-native-certs",
+ "rustls-pemfile",
+ "tokio",
+ "tokio-rustls",
+ "tokio-stream",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tonic-build"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "prost-build",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "indexmap",
+ "pin-project",
+ "pin-project-lite",
+ "rand",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+
+[[package]]
+name = "web-sys"
+version = "0.3.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "which"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
+dependencies = [
+ "either",
+ "libc",
+ "once_cell",
+]
+
+[[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-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "yandex-cloud"
+version = "2023.9.4"
+dependencies = [
+ "prost",
+ "prost-types",
+ "tokio",
+ "tonic",
+ "tonic-build",
+ "walkdir",
+]
diff --git a/ops/yandex-cloud-rs/Cargo.toml b/ops/yandex-cloud-rs/Cargo.toml
new file mode 100644
index 0000000000..a72d11d59a
--- /dev/null
+++ b/ops/yandex-cloud-rs/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "yandex-cloud"
+description = "Generated gRPC clients for the Yandex Cloud API"
+license = "MIT"
+version = "2023.9.4"
+edition = "2021"
+homepage = "https://cs.tvl.fyi/depot/-/tree/ops/yandex-cloud-rs"
+repository = "https://code.tvl.fyi/depot.git:/ops/yandex-cloud-rs.git"
+include = [ "/src", "README.md" ]
+
+[dependencies]
+prost = "0.11"
+prost-types = "0.11"
+
+[dependencies.tonic]
+version = "0.9"
+features = [ "tls", "tls-roots", "gzip" ]
+
+[build-dependencies]
+tonic-build = "0.9"
+walkdir = "2.3.3"
+
+[dev-dependencies]
+tokio = "1.28" # check when updating tonic
diff --git a/ops/yandex-cloud-rs/README.md b/ops/yandex-cloud-rs/README.md
new file mode 100644
index 0000000000..a80fa83163
--- /dev/null
+++ b/ops/yandex-cloud-rs/README.md
@@ -0,0 +1,49 @@
+yandex-cloud-rs
+===============
+
+Client library for Yandex Cloud gRPC APIs, as published in their
+[GitHub repository][repo].
+
+Please see the [online documentation][docs] for user-facing
+information, this README is intended for library developers.
+
+The source code of the library lives [in the TVL repository][code].
+
+-------------
+
+In order to build this library, the gRPC API definitions need to be
+fetched from GitHub. By default this is done by Nix (see
+`default.nix`), which then injects the location of the API definitions
+through the `YANDEX_CLOUD_PROTOS` environment variable.
+
+The actual code generation happens through the calls in `build.rs`.
+
+Releases of this library are done from *dirty* trees, meaning that the
+version on crates.io should already contain all the generated code. In
+order to do this, after bumping the version in `Cargo.toml` and the
+API commit in `default.nix`, the following release procedure should be
+used:
+
+```
+# Get rid of all generated source files
+find src | grep '.rs$' | grep -v '^src/lib.rs$' | xargs rm
+
+# Get rid of all old artefacts
+cargo clean
+
+# Verify that a clean build works as intended
+cargo build
+
+# Verify that all documentation builds, and verify that it looks fine:
+#
+# - Is the version correct (current date)?
+# - Are all the services included (i.e. not an accidental empty build)?
+cargo doc --open
+
+# If everything looks fine, release:
+cargo publish --allow-dirty
+```
+
+[repo]: https://github.com/yandex-cloud/cloudapi
+[docs]: https://docs.rs/yandex-cloud/latest/yandex_cloud/
+[code]: https://cs.tvl.fyi/depot/-/tree/ops/yandex-cloud-rs
diff --git a/ops/yandex-cloud-rs/build.rs b/ops/yandex-cloud-rs/build.rs
new file mode 100644
index 0000000000..e9a96ef9df
--- /dev/null
+++ b/ops/yandex-cloud-rs/build.rs
@@ -0,0 +1,43 @@
+use std::path::PathBuf;
+use walkdir::{DirEntry, WalkDir};
+
+fn proto_files(proto_dir: &str) -> Vec<PathBuf> {
+    let mut out = vec![];
+
+    fn is_proto(entry: &DirEntry) -> bool {
+        entry.file_type().is_file()
+            && entry
+                .path()
+                .extension()
+                .map(|e| e.to_string_lossy() == "proto")
+                .unwrap_or(false)
+    }
+
+    for entry in WalkDir::new(format!("{}/yandex", proto_dir)).into_iter() {
+        let entry = entry.expect("failed to list proto files");
+
+        if is_proto(&entry) {
+            out.push(entry.into_path())
+        }
+    }
+
+    out
+}
+
+fn main() {
+    if let Some(proto_dir) = option_env!("YANDEX_CLOUD_PROTOS") {
+        tonic_build::configure()
+            .build_client(true)
+            .build_server(false)
+            .out_dir("src/")
+            .include_file("includes.rs")
+            .compile(
+                &proto_files(proto_dir),
+                &[
+                    format!("{}", proto_dir),
+                    format!("{}/third_party/googleapis", proto_dir),
+                ],
+            )
+            .expect("failed to generate gRPC clients for Yandex Cloud")
+    }
+}
diff --git a/ops/yandex-cloud-rs/default.nix b/ops/yandex-cloud-rs/default.nix
new file mode 100644
index 0000000000..6a8b263dee
--- /dev/null
+++ b/ops/yandex-cloud-rs/default.nix
@@ -0,0 +1,22 @@
+{ depot, lib, pkgs, ... }:
+
+let
+  protoSrc = pkgs.fetchFromGitHub {
+    owner = "yandex-cloud";
+    repo = "cloudapi";
+    rev = "b4383be5ebe360bd946e49c8eaf647a73e9c44c0";
+    sha256 = "0z4jyw2cylvyrq5ja8pcaqnlf6lf6ximj85hgjag6ckawayk1rzx";
+  };
+in
+pkgs.rustPlatform.buildRustPackage rec {
+  name = "yandex-cloud-rs";
+  src = depot.third_party.gitignoreSource ./.;
+  cargoLock.lockFile = ./Cargo.lock;
+  YANDEX_CLOUD_PROTOS = "${protoSrc}";
+  nativeBuildInputs = [ pkgs.protobuf ];
+
+  # The generated doc comments contain lots of things that rustc
+  # *thinks* are doctests, but are actually just garbage leading to
+  # compiler errors.
+  doCheck = false;
+}
diff --git a/ops/yandex-cloud-rs/examples/log-write.rs b/ops/yandex-cloud-rs/examples/log-write.rs
new file mode 100644
index 0000000000..84d183421a
--- /dev/null
+++ b/ops/yandex-cloud-rs/examples/log-write.rs
@@ -0,0 +1,37 @@
+//! This example uses the Yandex Cloud Logging API to write a log entry.
+
+use prost_types::Timestamp;
+use tonic::transport::channel::Endpoint;
+use yandex_cloud::yandex::cloud::logging::v1::destination::Destination;
+use yandex_cloud::yandex::cloud::logging::v1::log_ingestion_service_client::LogIngestionServiceClient;
+use yandex_cloud::yandex::cloud::logging::v1::Destination as OuterDestination;
+use yandex_cloud::yandex::cloud::logging::v1::IncomingLogEntry;
+use yandex_cloud::yandex::cloud::logging::v1::WriteRequest;
+use yandex_cloud::AuthInterceptor;
+
+#[tokio::main(flavor = "current_thread")]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let channel = Endpoint::from_static("https://ingester.logging.yandexcloud.net")
+        .connect()
+        .await?;
+
+    let mut client = LogIngestionServiceClient::with_interceptor(
+        channel,
+        AuthInterceptor::new("YOUR_TOKEN_HERE"),
+    );
+
+    let request = WriteRequest {
+        destination: Some(OuterDestination {
+            destination: Some(Destination::LogGroupId("YOUR_LOG_GROUP_ID".into())),
+        }),
+        entries: vec![IncomingLogEntry {
+            timestamp: Some(Timestamp::date_time(2023, 04, 24, 23, 44, 30).unwrap()),
+            message: "test log message".into(),
+            ..Default::default()
+        }],
+        ..Default::default()
+    };
+
+    client.write(request).await.unwrap();
+    Ok(())
+}
diff --git a/ops/yandex-cloud-rs/src/lib.rs b/ops/yandex-cloud-rs/src/lib.rs
new file mode 100644
index 0000000000..e7f79c75be
--- /dev/null
+++ b/ops/yandex-cloud-rs/src/lib.rs
@@ -0,0 +1,108 @@
+//! This module provides low-level generated gRPC clients for the
+//! Yandex Cloud APIs.
+//!
+//! The clients are generated using the [tonic][] and [prost][]
+//! crates and have default configuration.
+//!
+//! Documentation present in the protos is retained into the generated
+//! Rust types, but for detailed API information you should visit the
+//! official Yandex Cloud Documentation pages:
+//!
+//! * [in English](https://cloud.yandex.com/en-ru/docs/overview/api)
+//! * [in Russian](https://cloud.yandex.ru/docs/overview/api)
+//!
+//! The proto sources are available on the [Yandex Cloud GitHub][protos].
+//!
+//! [tonic]: https://docs.rs/tonic/latest/tonic/
+//! [prost]: https://docs.rs/prost/latest/prost/
+//! [protos]: https://github.com/yandex-cloud/cloudapi
+//!
+//! The majority of user-facing structures can be found in the
+//! [`yandex::cloud`] module.
+//!
+//! ## Usage
+//!
+//! Typically to use these APIs, you need to provide an authentication
+//! credential and an endpoint to connect to. The full list of
+//! Yandex's endpoints is [available online][endpoints] and you should
+//! look up the service you plan to use and pick the correct endpoint
+//! from the list.
+//!
+//! Authentication is done via an HTTP header using an IAM token,
+//! which can be done in Tonic using [interceptors][]. The
+//! [`AuthInterceptor`] provided by this crate can be used for that
+//! purpose.
+//!
+//! Full usage examples are [available here][examples].
+//!
+//! [endpoints]: https://cloud.yandex.com/en/docs/api-design-guide/concepts/endpoints
+//! [interceptors]: https://docs.rs/tonic/latest/tonic/service/trait.Interceptor.html
+//! [examples]: https://code.tvl.fyi/tree/ops/yandex-cloud-rs/examples
+
+use tonic::metadata::{Ascii, MetadataValue};
+use tonic::service::Interceptor;
+
+/// Publicly re-export some types from tonic which users might need
+/// for implementing traits, or for naming concrete client types.
+pub mod tonic_exports {
+    pub use tonic::service::interceptor::InterceptedService;
+    pub use tonic::transport::Channel;
+    pub use tonic::transport::Endpoint;
+    pub use tonic::Status;
+}
+
+/// Helper trait for types or closures that can provide authentication
+/// tokens for Yandex Cloud.
+pub trait TokenProvider {
+    /// Fetch a currently valid authentication token for Yandex Cloud.
+    fn get_token<'a>(&'a mut self) -> Result<&'a str, tonic::Status>;
+}
+
+impl TokenProvider for String {
+    fn get_token<'a>(&'a mut self) -> Result<&'a str, tonic::Status> {
+        Ok(self.as_str())
+    }
+}
+
+impl TokenProvider for &'static str {
+    fn get_token(&mut self) -> Result<&'static str, tonic::Status> {
+        Ok(*self)
+    }
+}
+
+/// Interceptor for adding authentication headers to gRPC requests.
+/// This is constructed with a callable that returns authentication
+/// tokens.
+///
+/// This callable is responsible for ensuring that the returned tokens
+/// are valid at the given time, i.e. it should take care of
+/// refreshing and so on.
+pub struct AuthInterceptor<T: TokenProvider> {
+    token_provider: T,
+}
+
+impl<T: TokenProvider> AuthInterceptor<T> {
+    pub fn new(token_provider: T) -> Self {
+        Self { token_provider }
+    }
+}
+
+impl<T: TokenProvider> Interceptor for AuthInterceptor<T> {
+    fn call(
+        &mut self,
+        mut request: tonic::Request<()>,
+    ) -> Result<tonic::Request<()>, tonic::Status> {
+        let token: MetadataValue<Ascii> = format!("Bearer {}", self.token_provider.get_token()?)
+            .try_into()
+            .map_err(|_| {
+                tonic::Status::invalid_argument("authorization token contained invalid characters")
+            })?;
+
+        request.metadata_mut().insert("authorization", token);
+
+        Ok(request)
+    }
+}
+
+// The rest of this file is generated by the build script at ../build.rs.
+include!("includes.rs");