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.go20
-rw-r--r--ops/buildkite/.gitignore2
-rw-r--r--ops/buildkite/README.md24
-rw-r--r--ops/buildkite/default.nix14
-rw-r--r--ops/buildkite/steps-depot.yml6
-rw-r--r--ops/buildkite/steps-tvix.yml4
-rw-r--r--ops/buildkite/steps-tvl-kit.yml4
-rw-r--r--ops/buildkite/tvl.tf48
-rw-r--r--ops/dns/default.nix5
-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/gerrit-tvl/static/tvl.js8
-rw-r--r--ops/glesys/default.nix13
-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.tf24
-rw-r--r--ops/glesys/dns-tvl-su.tf81
-rw-r--r--ops/glesys/main.tf46
-rw-r--r--ops/journaldriver/Cargo.lock832
-rw-r--r--ops/journaldriver/Cargo.toml22
-rw-r--r--ops/journaldriver/build.rs3
-rw-r--r--ops/journaldriver/default.nix4
-rw-r--r--ops/journaldriver/src/main.rs171
-rw-r--r--ops/journaldriver/src/tests.rs56
-rw-r--r--ops/keycloak/README.md2
-rw-r--r--ops/keycloak/clients.tf17
-rw-r--r--ops/keycloak/default.nix12
-rw-r--r--ops/keycloak/main.tf12
-rw-r--r--ops/keycloak/user_sources.tf23
-rw-r--r--ops/kontemplate/release.nix22
-rw-r--r--ops/machines/all-systems.nix9
-rw-r--r--ops/machines/nixery-01/default.nix40
-rw-r--r--ops/machines/sanduny/default.nix138
-rw-r--r--ops/machines/whitby/OWNERS7
-rw-r--r--ops/machines/whitby/default.nix435
-rw-r--r--ops/modules/atward.nix3
-rw-r--r--ops/modules/auto-deploy.nix3
-rw-r--r--ops/modules/automatic-gc.nix8
-rw-r--r--ops/modules/btrfs-auto-scrub.nix25
-rw-r--r--ops/modules/cgit.nix55
-rw-r--r--ops/modules/clbot.nix11
-rw-r--r--ops/modules/default.nix2
-rw-r--r--ops/modules/depot-inbox.nix148
-rw-r--r--ops/modules/depot-replica.nix45
-rw-r--r--ops/modules/gerrit-autosubmit.nix43
-rw-r--r--ops/modules/gerrit-queue.nix51
-rw-r--r--ops/modules/git-serving.nix53
-rw-r--r--ops/modules/irccat.nix7
-rw-r--r--ops/modules/josh.nix33
-rw-r--r--ops/modules/journaldriver.nix26
-rw-r--r--ops/modules/known-hosts.nix21
-rw-r--r--ops/modules/livegrep.nix106
-rw-r--r--ops/modules/monorepo-gerrit.nix41
-rw-r--r--ops/modules/nixery.nix10
-rw-r--r--ops/modules/oauth2_proxy.nix52
-rw-r--r--ops/modules/open_eid.nix54
-rw-r--r--ops/modules/owothia.nix5
-rw-r--r--ops/modules/panettone.nix53
-rw-r--r--ops/modules/paroxysm.nix3
-rw-r--r--ops/modules/quassel.nix13
-rw-r--r--ops/modules/restic.nix3
-rw-r--r--ops/modules/smtprelay.nix7
-rw-r--r--ops/modules/sourcegraph.nix8
-rw-r--r--ops/modules/tvl-buildkite.nix71
-rw-r--r--ops/modules/tvl-cache.nix6
-rw-r--r--ops/modules/tvl-headscale.nix62
-rw-r--r--ops/modules/tvl-slapd/default.nix9
-rw-r--r--ops/modules/tvl-users.nix83
-rw-r--r--ops/modules/v4l2loopback.nix12
-rw-r--r--ops/modules/www/auth.tvl.fyi.nix6
-rw-r--r--ops/modules/www/base.nix45
-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/self-redirect.nix27
-rw-r--r--ops/modules/www/signup.tvl.fyi.nix19
-rw-r--r--ops/modules/www/status.tvl.su.nix2
-rw-r--r--ops/modules/www/tazj.in.nix14
-rw-r--r--ops/modules/www/tvix.dev.nix46
-rw-r--r--ops/modules/www/tvl.fyi.nix6
-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/mq_cli/Cargo.lock139
-rw-r--r--ops/mq_cli/Cargo.toml16
-rw-r--r--ops/mq_cli/README.md11
-rw-r--r--ops/mq_cli/src/main.rs76
-rw-r--r--ops/nixos.nix25
-rw-r--r--ops/pipelines/depot.nix17
-rw-r--r--ops/pipelines/static-pipeline.yaml35
-rw-r--r--ops/posix_mq.rs/Cargo.lock57
-rw-r--r--ops/posix_mq.rs/Cargo.toml9
-rw-r--r--ops/posix_mq.rs/README.md13
-rw-r--r--ops/posix_mq.rs/src/error.rs80
-rw-r--r--ops/posix_mq.rs/src/lib.rs62
-rw-r--r--ops/posix_mq.rs/src/tests.rs3
-rw-r--r--ops/secrets/besadii.agebin1050 -> 1186 bytes
-rw-r--r--ops/secrets/buildkite-agent-token.agebin710 -> 743 bytes
-rw-r--r--ops/secrets/buildkite-graphql-token.age29
-rw-r--r--ops/secrets/buildkite-ssh-private-key.agebin0 -> 1194 bytes
-rw-r--r--ops/secrets/clbot-ssh.agebin990 -> 1162 bytes
-rw-r--r--ops/secrets/clbot.age27
-rw-r--r--ops/secrets/depot-inbox-imap.age15
-rw-r--r--ops/secrets/depot-replica-key.agebin0 -> 1208 bytes
-rw-r--r--ops/secrets/gerrit-autosubmit.agebin0 -> 853 bytes
-rw-r--r--ops/secrets/gerrit-queue.agebin803 -> 0 bytes
-rw-r--r--ops/secrets/gerrit-secrets.agebin828 -> 913 bytes
-rw-r--r--ops/secrets/grafana.age27
-rw-r--r--ops/secrets/irccat.agebin673 -> 825 bytes
-rw-r--r--ops/secrets/journaldriver.agebin0 -> 3202 bytes
-rw-r--r--ops/secrets/keycloak-db.agebin589 -> 710 bytes
-rw-r--r--ops/secrets/mkSecrets.nix6
-rw-r--r--ops/secrets/nix-cache-priv.agebin732 -> 786 bytes
-rw-r--r--ops/secrets/nix-cache-pub.age27
-rw-r--r--ops/secrets/oauth2_proxy.age14
-rw-r--r--ops/secrets/owothia.age29
-rw-r--r--ops/secrets/panettone.age29
-rw-r--r--ops/secrets/secrets.nix59
-rw-r--r--ops/secrets/smtprelay.age28
-rw-r--r--ops/secrets/tf-buildkite.agebin0 -> 943 bytes
-rw-r--r--ops/secrets/tf-glesys.agebin822 -> 959 bytes
-rw-r--r--ops/secrets/tf-keycloak.agebin842 -> 962 bytes
-rw-r--r--ops/secrets/tvl-alerts-bot-telegram-token.age15
-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
143 files changed, 5367 insertions, 1671 deletions
diff --git a/ops/besadii/main.go b/ops/besadii/main.go
index e22dbb1e48..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)
 	}
@@ -459,7 +459,7 @@ func gerritHookMain(cfg *config, log *syslog.Writer, trigger *buildTrigger) {
 		log.Err(fmt.Sprintf("failed to trigger Buildkite build: %s", err))
 	}
 
-	if cfg.SourcegraphUrl != "" && trigger.ref == "refs/heads/canon" {
+	if cfg.SourcegraphUrl != "" && trigger.ref == cfg.Branch {
 		err = triggerIndexUpdate(cfg, log)
 		if err != nil {
 			log.Err(fmt.Sprintf("failed to trigger sourcegraph index update: %s", err))
@@ -475,7 +475,7 @@ func postCommandMain(cfg *config) {
 		// If these variables are unset, but the hook was invoked, the
 		// build was most likely for a branch and not for a CL - no status
 		// needs to be reported back to Gerrit!
-		fmt.Println("This isn't a %s build, nothing to do. Have a nice day!", cfg.GerritChangeName)
+		fmt.Printf("This isn't a %s build, nothing to do. Have a nice day!\n", cfg.GerritChangeName)
 		return
 	}
 
diff --git a/ops/buildkite/.gitignore b/ops/buildkite/.gitignore
new file mode 100644
index 0000000000..41c1b33462
--- /dev/null
+++ b/ops/buildkite/.gitignore
@@ -0,0 +1,2 @@
+.envrc
+.terraform*
diff --git a/ops/buildkite/README.md b/ops/buildkite/README.md
new file mode 100644
index 0000000000..9d31a53fd3
--- /dev/null
+++ b/ops/buildkite/README.md
@@ -0,0 +1,24 @@
+Buildkite configuration
+=======================
+
+This contains Terraform configuration for setting up our Buildkite
+pipelines.
+
+Each pipeline (such as the one for depot itself, or exported subsets
+of the depot) needs some static configuration stored in Buildkite.
+
+Through `//tools/depot-deps` a `tf-buildkite` binary is made available
+which contains a Terraform binary pre-configured with the correct
+providers. This is automatically on your `$PATH` through `direnv`.
+
+However, secrets still need to be loaded to access the Terraform state
+and speak to the Buildkite API. These are available to certain users
+through `//ops/secrets`.
+
+This can be done with separate direnv configuration, for example:
+
+```
+# //ops/buildkite/.envrc
+source_up
+eval $(age --decrypt -i ~/.ssh/id_ed25519 $(git rev-parse --show-toplevel)/ops/secrets/tf-buildkite.age)
+```
diff --git a/ops/buildkite/default.nix b/ops/buildkite/default.nix
new file mode 100644
index 0000000000..0d39bc0601
--- /dev/null
+++ b/ops/buildkite/default.nix
@@ -0,0 +1,14 @@
+{ depot, lib, pkgs, ... }:
+
+depot.nix.readTree.drvTargets rec {
+  terraform = pkgs.terraform.withPlugins (p: [
+    p.buildkite
+  ]);
+
+  validate = depot.tools.checks.validateTerraform {
+    inherit terraform;
+    name = "buildkite";
+    src = lib.cleanSource ./.;
+    env.BUILDKITE_API_TOKEN = "ci-dummy";
+  };
+}
diff --git a/ops/buildkite/steps-depot.yml b/ops/buildkite/steps-depot.yml
new file mode 100644
index 0000000000..011b299771
--- /dev/null
+++ b/ops/buildkite/steps-depot.yml
@@ -0,0 +1,6 @@
+---
+steps:
+  - label: ":buildkite:"
+    key: ":init:"
+    command: |
+      buildkite-agent pipeline upload ops/pipelines/static-pipeline.yaml
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/steps-tvl-kit.yml b/ops/buildkite/steps-tvl-kit.yml
new file mode 100644
index 0000000000..a6e9f13b16
--- /dev/null
+++ b/ops/buildkite/steps-tvl-kit.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
new file mode 100644
index 0000000000..4c45909a0c
--- /dev/null
+++ b/ops/buildkite/tvl.tf
@@ -0,0 +1,48 @@
+# Buildkite configuration for TVL.
+
+terraform {
+  required_providers {
+    buildkite = {
+      source = "buildkite/buildkite"
+    }
+  }
+
+  backend "s3" {
+    endpoint = "https://objects.dc-sto1.glesys.net"
+    bucket   = "tvl-state"
+    key      = "terraform/tvl-buildkite"
+    region   = "glesys"
+
+    skip_credentials_validation = true
+    skip_region_validation      = true
+    skip_metadata_api_check     = true
+  }
+}
+
+provider "buildkite" {
+  organization = "tvl"
+}
+
+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")
+  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")
+  default_branch = "canon"
+}
diff --git a/ops/dns/default.nix b/ops/dns/default.nix
index 136a4c58dc..33fe6d6fe7 100644
--- a/ops/dns/default.nix
+++ b/ops/dns/default.nix
@@ -2,11 +2,12 @@
 { 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
   '';
 
-in depot.nix.readTree.drvTargets {
+in
+depot.nix.readTree.drvTargets {
   nixery-dev = checkZone "nixery.dev" ./nixery.dev.zone;
   tvl-fyi = checkZone "tvl.fyi" ./tvl.fyi.zone;
   tvl-su = checkZone "tvl.su" ./tvl.su.zone;
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/gerrit-tvl/static/tvl.js b/ops/gerrit-tvl/static/tvl.js
index 2c4d7ee473..684636de30 100644
--- a/ops/gerrit-tvl/static/tvl.js
+++ b/ops/gerrit-tvl/static/tvl.js
@@ -79,12 +79,11 @@ function jobStateToCheckRunStatus(state) {
 
 const tvlChecksProvider = {
   async fetch(change) {
-    let {changeNumber, patchsetNumber, repo} = change;
+    let {patchsetSha, repo} = change;
 
     const experiments = window.ENABLED_EXPERIMENTS || [];
     if (experiments.includes("UiFeature__tvl_check_debug")) {
-      changeNumber = 2872;
-      patchsetNumber = 4;
+      patchsetSha = '76692104f58b849b1503a8d8a700298003fa7b5f';
       repo = 'depot';
     }
 
@@ -94,8 +93,7 @@ const tvlChecksProvider = {
     }
 
     const params = {
-      // besadii groups different patchsets of the same CL under this fake ref
-      branch: `cl/${changeNumber.toString()}`,
+      commit: patchsetSha,
     };
     const url = `https://api.buildkite.com/v2/organizations/tvl/pipelines/depot/builds?${encodeParams(params)}`;
     const resp = await fetch(url, {
diff --git a/ops/glesys/default.nix b/ops/glesys/default.nix
index f4c0478c5d..e511e1f6b6 100644
--- a/ops/glesys/default.nix
+++ b/ops/glesys/default.nix
@@ -1,8 +1,15 @@
-{ depot, pkgs, ... }:
+{ depot, lib, pkgs, ... }:
 
-depot.nix.readTree.drvTargets {
+depot.nix.readTree.drvTargets rec {
   # Provide a Terraform wrapper with the right provider installed.
-  terraform = pkgs.terraform.withPlugins(_: [
+  terraform = pkgs.terraform.withPlugins (_: [
     depot.third_party.terraform-provider-glesys
   ]);
+
+  validate = depot.tools.checks.validateTerraform {
+    inherit terraform;
+    name = "glesys";
+    src = lib.cleanSource ./.;
+    env.GLESYS_TOKEN = "ci-dummy";
+  };
 }
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 803bfeae08..9d7972c412 100644
--- a/ops/glesys/dns-tvl-fyi.tf
+++ b/ops/glesys/dns-tvl-fyi.tf
@@ -53,13 +53,27 @@ resource "glesys_dnsdomain_record" "tvl_fyi_whitby_AAAA" {
   data   = var.whitby_ipv6
 }
 
-# This record is responsible for hosting ~all TVL services. Be
-# mindful!
-resource "glesys_dnsdomain_record" "tvl_fyi_wildcard" {
+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
+  type     = "CNAME"
+  data     = "whitby.tvl.fyi."
+  host     = each.key
+  for_each = toset(local.whitby_services)
+}
+
+resource "glesys_dnsdomain_record" "tvl_fyi_net_CNAME" {
   domain = glesys_dnsdomain.tvl_fyi.id
-  host   = "*"
   type   = "CNAME"
-  data   = "whitby.tvl.fyi."
+  data   = "sanduny.tvl.su."
+  host   = "net"
 }
 
 # Google Domains mail forwarding configuration (no sending)
diff --git a/ops/glesys/dns-tvl-su.tf b/ops/glesys/dns-tvl-su.tf
index 9b8bcd9a87..f2286cf1cf 100644
--- a/ops/glesys/dns-tvl-su.tf
+++ b/ops/glesys/dns-tvl-su.tf
@@ -53,68 +53,85 @@ resource "glesys_dnsdomain_record" "tvl_su_whitby_AAAA" {
   data   = var.whitby_ipv6
 }
 
-# This record is responsible for hosting ~all TVL services. Be
-# mindful!
-resource "glesys_dnsdomain_record" "tvl_su_wildcard" {
+resource "glesys_dnsdomain_record" "tvl_su_sanduny_A" {
   domain = glesys_dnsdomain.tvl_su.id
-  host   = "*"
-  type   = "CNAME"
-  data   = "whitby.tvl.su."
+  host   = "sanduny"
+  type   = "A"
+  data   = var.sanduny_ipv4
 }
 
-# # Google Domains mail forwarding configuration (no sending)
-resource "glesys_dnsdomain_record" "tvl_su_MX_aspmx" {
+resource "glesys_dnsdomain_record" "tvl_su_sanduny_AAAA" {
   domain = glesys_dnsdomain.tvl_su.id
-  host   = "@"
-  type   = "MX"
-  data   = "1 aspmx.l.google.com."
+  host   = "sanduny"
+  type   = "AAAA"
+  data   = var.sanduny_ipv6
+}
+
+# Explicit records for all services running on whitby
+resource "glesys_dnsdomain_record" "tvl_su_whitby_services" {
+  domain   = glesys_dnsdomain.tvl_su.id
+  type     = "CNAME"
+  data     = "whitby.tvl.su."
+  host     = each.key
+  for_each = toset(local.whitby_services)
 }
 
-resource "glesys_dnsdomain_record" "tvl_su_MX_alt1" {
+# 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   = "@"
-  type   = "MX"
-  data   = "5 alt1.aspmx.l.google.com."
+  host   = "tvixbolt"
+  type   = "CNAME"
+  data   = "whitby.tvl.su."
 }
 
-resource "glesys_dnsdomain_record" "tvl_su_MX_alt2" {
+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" {
   domain = glesys_dnsdomain.tvl_su.id
   host   = "@"
-  type   = "MX"
-  data   = "5 alt2.aspmx.l.google.com."
+  type   = "TXT"
+  data   = "google-site-verification=3ksTBzFK3lZlzD3ddBfpaHs9qasfAiYBmvbW2T_ejH4"
 }
 
-resource "glesys_dnsdomain_record" "tvl_su_MX_alt3" {
+# Yandex 360 setup
+
+resource "glesys_dnsdomain_record" "tvl_su_TXT_yandex" {
   domain = glesys_dnsdomain.tvl_su.id
   host   = "@"
-  type   = "MX"
-  data   = "10 alt3.aspmx.l.google.com."
+  type   = "TXT"
+  data   = "yandex-verification: b99c43b7838949dc"
 }
 
-resource "glesys_dnsdomain_record" "tvl_su_MX_alt4" {
+resource "glesys_dnsdomain_record" "tvl_su_MX_yandex" {
   domain = glesys_dnsdomain.tvl_su.id
   host   = "@"
   type   = "MX"
-  data   = "10 alt4.aspmx.l.google.com."
+  data   = "10 mx.yandex.net."
 }
 
-resource "glesys_dnsdomain_record" "tvl_su_TXT_google_site" {
+resource "glesys_dnsdomain_record" "tvl_su_TXT_yandex_spf" {
   domain = glesys_dnsdomain.tvl_su.id
   host   = "@"
   type   = "TXT"
-  data   = "google-site-verification=3ksTBzFK3lZlzD3ddBfpaHs9qasfAiYBmvbW2T_ejH4"
+  data   = "v=spf1 redirect=_spf.yandex.net"
+
 }
 
-resource "glesys_dnsdomain_record" "tvl_su_TXT_google_spf" {
+resource "glesys_dnsdomain_record" "tvl_su_TXT_yandex_dkim" {
   domain = glesys_dnsdomain.tvl_su.id
-  host   = "@"
+  host   = "mail._domainkey"
   type   = "TXT"
-  data   = "v=spf1 include:_spf.google.com ~all"
+  data   = "v=DKIM1; k=rsa; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaRdWF8BtCHlTTQN8O+E5Qn27FVIpUEAdk1uq2vdIKh1Un/3NfdWtxStcS1Mf0iEprt1Fb4zgWOkDlPi+hH/UZqiC9QNeNqEBGMB9kgJyfsUt6cDCIVGvn8PT9JcZW1jxSziOj8nUWB4noqbaVcQNqNbwtaHPm3aifwKwScxVO7wIDAQAB"
 }
 
-resource "glesys_dnsdomain_record" "tvl_su_TXT_google_dkim" {
+resource "glesys_dnsdomain_record" "tvl_su_CNAME_yandex_mail" {
   domain = glesys_dnsdomain.tvl_su.id
-  host   = "google._domainkey"
-  type   = "TXT"
-  data   = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlqCbnGa8oPwrudJK60l6MJj3NBnwj8wAPXNGtYy2SXrOBi7FT+ySwW7ATpfv6Xq9zGDUWJsENPUlFmvDiUs7Qi4scnNvSO1L+sDseB9/q1m3gMFVnTuieDO/T+KKkg0+uYgMM7YX5PahsAAJJ+EMb/r4afl3tcBMPR64VveKQ0hiSHA4zIYPsB9FB+b8S5C46uyY0r6WR7IzGjq2Gzb1do0kxvaKItTITWLSImcUu5ZZuXOUKJb441frVBWur5lXaYuedkxb1IRTTK0V/mBODE1D7k73MxGrqlzaMPdCqz+c3hRE18WVUkBTYjANVXDrs3yzBBVxaIAeu++vkO6BvQIDAQAB"
+  host   = "mail"
+  type   = "CNAME"
+  data   = "domain.mail.yandex.net."
 }
diff --git a/ops/glesys/main.tf b/ops/glesys/main.tf
index 857c1677fb..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
   }
 }
 
@@ -35,10 +39,6 @@ resource "glesys_objectstorage_instance" "tvl-backups" {
 resource "glesys_objectstorage_instance" "tvl-state" {
   description = "tvl-state"
   datacenter  = "dc-sto1"
-
-  lifecycle {
-    ignore_changes = [accesskey]
-  }
 }
 
 resource "glesys_objectstorage_credential" "terraform-state" {
@@ -60,3 +60,33 @@ variable "whitby_ipv6" {
   type    = string
   default = "2a01:4f8:242:5b21:0:feed:edef:beef"
 }
+
+variable "sanduny_ipv4" {
+  type    = string
+  default = "85.119.82.231"
+}
+
+variable "sanduny_ipv6" {
+  type    = string
+  default = "2001:ba8:1f1:f109::feed:edef:beef"
+}
+
+locals {
+  # Hostnames of all public services on whitby
+  whitby_services = [
+    "at",
+    "atward",
+    "auth",
+    "b",
+    "cache",
+    "cl",
+    "code",
+    "cs",
+    "deploys",
+    "images",
+    "signup",
+    "static",
+    "status",
+    "todo",
+  ]
+}
diff --git a/ops/journaldriver/Cargo.lock b/ops/journaldriver/Cargo.lock
index f7c697068f..97bbe16ceb 100644
--- a/ops/journaldriver/Cargo.lock
+++ b/ops/journaldriver/Cargo.lock
@@ -1,818 +1,646 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-[[package]]
-name = "aho-corasick"
-version = "0.6.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "ascii"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "atty"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+version = 3
 
 [[package]]
-name = "backtrace"
-version = "0.3.9"
+name = "aho-corasick"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
 dependencies = [
- "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr",
 ]
 
 [[package]]
-name = "backtrace-sys"
-version = "0.1.24"
+name = "anyhow"
+version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
 [[package]]
 name = "base64"
-version = "0.9.3"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "bitflags"
-version = "1.0.4"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
 
 [[package]]
-name = "byteorder"
-version = "1.2.6"
+name = "build-env"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e068f31938f954b695423ecaf756179597627d0828c0d3e48c0a722a8b23cf9e"
 
 [[package]]
 name = "cc"
-version = "1.0.25"
+version = "1.0.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "cfg-if"
-version = "0.1.5"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "chrono"
-version = "0.4.6"
+name = "crimp"
+version = "4087.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ead2c83f7d1f9b8e5a6f7a25985d0d1759ccd2cd72abb1eee2db65d05e12b39"
 dependencies = [
- "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "curl",
+ "serde",
+ "serde_json",
 ]
 
 [[package]]
-name = "chunked_transfer"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "cloudabi"
-version = "0.0.3"
+name = "cstr-argument"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40"
 dependencies = [
- "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "memchr",
 ]
 
 [[package]]
-name = "cookie"
-version = "0.11.0"
+name = "curl"
+version = "0.4.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
 dependencies = [
- "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "curl-sys",
+ "libc",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "socket2",
+ "winapi",
 ]
 
 [[package]]
-name = "core-foundation"
-version = "0.5.1"
+name = "curl-sys"
+version = "0.4.68+curl-8.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f"
 dependencies = [
- "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+ "windows-sys",
 ]
 
 [[package]]
-name = "core-foundation-sys"
-version = "0.5.1"
+name = "deranged"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "powerfmt",
+ "serde",
 ]
 
 [[package]]
-name = "cstr-argument"
-version = "0.0.2"
+name = "env_logger"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
 dependencies = [
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
 ]
 
 [[package]]
-name = "env_logger"
-version = "0.5.13"
+name = "errno"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
 dependencies = [
- "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
+ "windows-sys",
 ]
 
 [[package]]
-name = "failure"
-version = "0.1.2"
+name = "foreign-types"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
 dependencies = [
- "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foreign-types-shared 0.1.1",
 ]
 
 [[package]]
-name = "failure_derive"
-version = "0.1.2"
+name = "foreign-types"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foreign-types-macros",
+ "foreign-types-shared 0.3.1",
 ]
 
 [[package]]
-name = "foreign-types"
-version = "0.3.2"
+name = "foreign-types-macros"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
 dependencies = [
- "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
 name = "foreign-types-shared"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
 [[package]]
-name = "fuchsia-zircon"
-version = "0.3.3"
+name = "foreign-types-shared"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
 
 [[package]]
-name = "fuchsia-zircon-sys"
+name = "hermit-abi"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
 [[package]]
 name = "humantime"
-version = "1.1.1"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 [[package]]
-name = "idna"
-version = "0.1.5"
+name = "is-terminal"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
- "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
 ]
 
 [[package]]
 name = "itoa"
-version = "0.4.3"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 
 [[package]]
 name = "journaldriver"
-version = "1.1.0"
+version = "5656.0.0"
 dependencies = [
- "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "medallion 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
- "systemd 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "ureq 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "anyhow",
+ "crimp",
+ "env_logger",
+ "lazy_static",
+ "log",
+ "medallion",
+ "pkg-config",
+ "serde",
+ "serde_json",
+ "systemd",
+ "time",
 ]
 
 [[package]]
 name = "lazy_static"
-version = "1.1.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.43"
+version = "0.2.150"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
 
 [[package]]
 name = "libsystemd-sys"
-version = "0.2.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d28ad38d7bee81aabd41201ee7d36df8d7f76aa0a455c77d5c365c4669b4b4b6"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "build-env",
+ "libc",
+ "pkg-config",
 ]
 
 [[package]]
-name = "log"
-version = "0.4.5"
+name = "libz-sys"
+version = "1.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
 dependencies = [
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
 ]
 
 [[package]]
-name = "matches"
-version = "0.1.8"
+name = "linux-raw-sys"
+version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
 
 [[package]]
-name = "medallion"
-version = "2.2.3"
+name = "log"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
 [[package]]
-name = "memchr"
-version = "1.0.2"
+name = "medallion"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35b83c0c3277d722b53a6eb24e3c1321172f85b715cc7405add8ffd1f2f06288"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "anyhow",
+ "base64",
+ "openssl",
+ "serde",
+ "serde_json",
+ "time",
 ]
 
 [[package]]
 name = "memchr"
-version = "2.1.0"
+version = "2.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
 [[package]]
-name = "native-tls"
-version = "0.2.1"
+name = "once_cell"
+version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.9.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "security-framework 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "security-framework-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
-name = "num-integer"
-version = "0.1.39"
+name = "openssl"
+version = "0.10.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33"
 dependencies = [
- "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "cfg-if",
+ "foreign-types 0.3.2",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
 ]
 
 [[package]]
-name = "num-traits"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "openssl"
-version = "0.10.12"
+name = "openssl-macros"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
- "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.9.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
 name = "openssl-probe"
-version = "0.1.2"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.36"
+version = "0.9.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9"
 dependencies = [
- "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
 ]
 
 [[package]]
-name = "percent-encoding"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "pkg-config"
-version = "0.3.14"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
 
 [[package]]
-name = "proc-macro2"
-version = "0.4.20"
+name = "powerfmt"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
 
 [[package]]
-name = "qstring"
-version = "0.6.0"
+name = "proc-macro2"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
 dependencies = [
- "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-ident",
 ]
 
 [[package]]
-name = "quick-error"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "quote"
-version = "0.6.8"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
 ]
 
 [[package]]
-name = "rand"
-version = "0.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "redox_syscall"
-version = "0.1.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "redox_termios"
-version = "0.1.1"
+name = "regex"
+version = "1.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
 dependencies = [
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
 ]
 
 [[package]]
-name = "regex"
-version = "1.0.5"
+name = "regex-automata"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
 dependencies = [
- "aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
 ]
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.2"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
-name = "remove_dir_all"
-version = "0.5.1"
+name = "rustix"
+version = "0.38.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
 dependencies = [
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
 ]
 
 [[package]]
-name = "rustc-demangle"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "ryu"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "safemem"
-version = "0.3.0"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "schannel"
-version = "0.1.14"
+version = "0.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
 dependencies = [
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "windows-sys",
 ]
 
 [[package]]
-name = "security-framework"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "security-framework-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "security-framework-sys"
-version = "0.2.1"
+name = "serde"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
 dependencies = [
- "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive",
 ]
 
 [[package]]
-name = "serde"
-version = "1.0.79"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "serde_derive"
-version = "1.0.79"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.32"
+version = "1.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
 dependencies = [
- "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa",
+ "ryu",
+ "serde",
 ]
 
 [[package]]
-name = "syn"
-version = "0.14.9"
+name = "socket2"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
+ "winapi",
 ]
 
 [[package]]
 name = "syn"
-version = "0.15.8"
+version = "2.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
 ]
 
 [[package]]
-name = "synstructure"
-version = "0.9.0"
+name = "systemd"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da95085b9c6eedbcf0b828302a3483a84bdbf772158e586b787092112008fd1f"
 dependencies = [
- "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cstr-argument",
+ "foreign-types 0.5.0",
+ "libc",
+ "libsystemd-sys",
+ "log",
+ "utf8-cstr",
 ]
 
 [[package]]
-name = "systemd"
-version = "0.3.0"
+name = "termcolor"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
 dependencies = [
- "cstr-argument 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "libsystemd-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "utf8-cstr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-util",
 ]
 
 [[package]]
-name = "tempfile"
-version = "3.0.4"
+name = "time"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
 dependencies = [
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "deranged",
+ "itoa",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
 ]
 
 [[package]]
-name = "termcolor"
-version = "1.0.4"
+name = "time-core"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
-name = "termion"
-version = "1.5.1"
+name = "time-macros"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time-core",
 ]
 
 [[package]]
-name = "thread_local"
-version = "0.3.6"
+name = "unicode-ident"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
-name = "time"
-version = "0.1.40"
+name = "utf8-cstr"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628"
 
 [[package]]
-name = "ucd-util"
-version = "0.1.1"
+name = "vcpkg"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
 [[package]]
-name = "unicode-bidi"
-version = "0.3.4"
+name = "winapi"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 dependencies = [
- "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
 ]
 
 [[package]]
-name = "unicode-normalization"
-version = "0.1.7"
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
-name = "unicode-xid"
-version = "0.1.0"
+name = "winapi-util"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
 
 [[package]]
-name = "ureq"
-version = "0.6.2"
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "native-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "qstring 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
-name = "url"
-version = "1.7.1"
+name = "windows-sys"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "windows-targets",
 ]
 
 [[package]]
-name = "utf8-cstr"
-version = "0.1.6"
+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 = "utf8-ranges"
-version = "1.0.1"
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
-name = "vcpkg"
-version = "0.2.6"
+name = "windows_aarch64_msvc"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
-name = "version_check"
-version = "0.1.5"
+name = "windows_i686_gnu"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
-name = "winapi"
-version = "0.3.6"
+name = "windows_i686_msvc"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_x86_64_gnu"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
-name = "winapi-util"
-version = "0.1.1"
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_x86_64_msvc"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "wincolor"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[metadata]
-"checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a"
-"checksum ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5fc969a8ce2c9c0c4b0429bb8431544f6658283c8326ba5ff8c762b75369335"
-"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
-"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
-"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
-"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
-"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
-"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781"
-"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16"
-"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
-"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
-"checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87"
-"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-"checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf"
-"checksum core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "286e0b41c3a20da26536c6000a280585d519fd07b3956b43aed8a79e9edce980"
-"checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa"
-"checksum cstr-argument 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "514570a4b719329df37f93448a70df2baac553020d0eb43a8dfa9c1f5ba7b658"
-"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
-"checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9"
-"checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426"
-"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
-"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
-"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
-"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
-"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
-"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7"
-"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
-"checksum libsystemd-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e751b723417158e0949ba470bee4affd6f1dd6b67622b5240d79186631b6a0d9"
-"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
-"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
-"checksum medallion 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2e6f0713b388174fc3de9b63a0a63dfcee191a8abc8e06c0a9c6d80821c1891"
-"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
-"checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b"
-"checksum native-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8b0a7bd714e83db15676d31caf968ad7318e9cc35f93c85a90231c8f22867549"
-"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
-"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
-"checksum openssl 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2e79eede055813a3ac52fb3915caf8e1c9da2dec1587871aec9f6f7b48508d"
-"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
-"checksum openssl-sys 0.9.36 (registry+https://github.com/rust-lang/crates.io-index)" = "409d77eeb492a1aebd6eb322b2ee72ff7c7496b4434d98b3bf8be038755de65e"
-"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
-"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
-"checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee"
-"checksum qstring 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "545ec057a36a93e25fb5883baed912e4984af4e2543bbf0e3463d962e0408469"
-"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
-"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5"
-"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"
-"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372"
-"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db"
-"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
-"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
-"checksum regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2069749032ea3ec200ca51e4a31df41759190a88edca0d2d86ee8bedf7073341"
-"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
-"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
-"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
-"checksum ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7153dd96dade874ab973e098cb62fcdbb89a03682e46b144fd09550998d4a4a7"
-"checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9"
-"checksum schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "0e1a231dc10abf6749cfa5d7767f25888d484201accbd919b66ab5413c502d56"
-"checksum security-framework 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "697d3f3c23a618272ead9e1fb259c1411102b31c6af8b93f1d64cca9c3b0e8e0"
-"checksum security-framework-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab01dfbe5756785b5b4d46e0289e5a18071dfa9a7c2b24213ea00b9ef9b665bf"
-"checksum serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "84257ccd054dc351472528c8587b4de2dbf0dc0fe2e634030c1a90bfdacebaa9"
-"checksum serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "31569d901045afbff7a9479f793177fe9259819aff10ab4f89ef69bbc5f567fe"
-"checksum serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "43344e7ce05d0d8280c5940cabb4964bea626aa58b1ec0e8c73fa2a8512a38ce"
-"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
-"checksum syn 0.15.8 (registry+https://github.com/rust-lang/crates.io-index)" = "356d1c5043597c40489e9af2d2498c7fefc33e99b7d75b43be336c8a59b3e45e"
-"checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7"
-"checksum systemd 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b62a732355787f960c25536210ae0a981aca2e5dae9dab8491bdae39613ce48"
-"checksum tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "55c1195ef8513f3273d55ff59fe5da6940287a0d7a98331254397f464833675b"
-"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
-"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
-"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
-"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
-"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
-"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
-"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25"
-"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
-"checksum ureq 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f3f941c0434783c82e46d30508834be5f3c1f2c85dd1b98f0681984c7be8e03"
-"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6"
-"checksum utf8-cstr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628"
-"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4"
-"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d"
-"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
-"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
-"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-"checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab"
-"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/ops/journaldriver/Cargo.toml b/ops/journaldriver/Cargo.toml
index 248b22807f..65510d8705 100644
--- a/ops/journaldriver/Cargo.toml
+++ b/ops/journaldriver/Cargo.toml
@@ -1,21 +1,21 @@
 [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]
-chrono = { version = "0.4", features = [ "serde" ]}
-env_logger = "0.5"
-failure = "0.1"
-lazy_static = "1.0"
+anyhow = "1.0"
+crimp = "4087.0"
+env_logger = "0.10"
+lazy_static = "1.4"
 log = "0.4"
-medallion = "2.2"
-serde = "1.0"
-serde_derive = "1.0"
+medallion = "2.5"
+serde = { version = "1.0", features = [ "derive" ] }
 serde_json = "1.0"
-systemd = "0.3"
-ureq = { version = "0.6.2", features = [ "json" ]}
+systemd = "0.5"
+time = { version = "0.3", features = [ "serde-well-known", "macros" ]}
 
 [build-dependencies]
 pkg-config = "0.3"
diff --git a/ops/journaldriver/build.rs b/ops/journaldriver/build.rs
index d64c82a88a..79eb1001bf 100644
--- a/ops/journaldriver/build.rs
+++ b/ops/journaldriver/build.rs
@@ -1,6 +1,5 @@
 extern crate pkg_config;
 
 fn main() {
-    pkg_config::probe_library("libsystemd")
-        .expect("Could not probe libsystemd");
+    pkg_config::probe_library("libsystemd").expect("Could not probe libsystemd");
 }
diff --git a/ops/journaldriver/default.nix b/ops/journaldriver/default.nix
index d2413e74cc..2a3836c358 100644
--- a/ops/journaldriver/default.nix
+++ b/ops/journaldriver/default.nix
@@ -4,6 +4,8 @@ depot.third_party.naersk.buildPackage {
   src = ./.;
 
   buildInputs = with pkgs; [
-    pkgconfig openssl systemd.dev
+    pkg-config
+    openssl
+    systemd.dev
   ];
 }
diff --git a/ops/journaldriver/src/main.rs b/ops/journaldriver/src/main.rs
index 9886af1c36..4c404e607e 100644
--- a/ops/journaldriver/src/main.rs
+++ b/ops/journaldriver/src/main.rs
@@ -31,30 +31,17 @@
 //! `GOOGLE_APPLICATION_CREDENTIALS`, `GOOGLE_CLOUD_PROJECT` and
 //! `LOG_NAME` environment variables.
 
-#[macro_use] extern crate failure;
-#[macro_use] extern crate log;
-#[macro_use] extern crate serde_derive;
-#[macro_use] extern crate serde_json;
-#[macro_use] extern crate lazy_static;
-
-extern crate chrono;
-extern crate env_logger;
-extern crate medallion;
-extern crate serde;
-extern crate systemd;
-extern crate ureq;
-
-use chrono::offset::LocalResult;
-use chrono::prelude::{DateTime, TimeZone, Utc};
-use failure::ResultExt;
-use serde_json::{from_str, Value};
-use std::env;
-use std::fs::{self, File, rename};
-use std::io::{self, Read, ErrorKind, Write};
-use std::mem;
+use anyhow::{bail, Context, Result};
+use lazy_static::lazy_static;
+use log::{debug, error, info, trace};
+use serde::{Deserialize, Serialize};
+use serde_json::{from_str, json, Value};
+use std::convert::TryInto;
+use std::fs::{self, rename, File};
+use std::io::{self, ErrorKind, Read, Write};
 use std::path::PathBuf;
-use std::process;
 use std::time::{Duration, Instant};
+use std::{env, mem, process};
 use systemd::journal::{Journal, JournalFiles, JournalRecord, JournalSeek};
 
 #[cfg(test)]
@@ -62,13 +49,12 @@ mod tests;
 
 const LOGGING_SERVICE: &str = "https://logging.googleapis.com/google.logging.v2.LoggingServiceV2";
 const ENTRIES_WRITE_URL: &str = "https://logging.googleapis.com/v2/entries:write";
-const METADATA_TOKEN_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
+const METADATA_TOKEN_URL: &str =
+    "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
 const METADATA_ID_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/id";
 const METADATA_ZONE_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/zone";
-const METADATA_PROJECT_URL: &str = "http://metadata.google.internal/computeMetadata/v1/project/project-id";
-
-/// Convenience type alias for results using failure's `Error` type.
-type Result<T> = std::result::Result<T, failure::Error>;
+const METADATA_PROJECT_URL: &str =
+    "http://metadata.google.internal/computeMetadata/v1/project/project-id";
 
 /// Representation of static service account credentials for GCP.
 #[derive(Debug, Deserialize)]
@@ -126,32 +112,27 @@ lazy_static! {
 
 /// Convenience helper for retrieving values from the metadata server.
 fn get_metadata(url: &str) -> Result<String> {
-    let response = ureq::get(url)
-        .set("Metadata-Flavor", "Google")
-        .timeout_connect(5000)
-        .timeout_read(5000)
-        .call();
-
-    if response.ok() {
-        // Whitespace is trimmed to remove newlines from responses.
-        let body = response.into_string()
-            .context("Failed to decode metadata response")?
-            .trim().to_string();
-
-        Ok(body)
-    } else {
-        let status = response.status_line().to_string();
-        let body = response.into_string()
-            .unwrap_or_else(|e| format!("Metadata body error: {}", e));
-        bail!("Metadata failure: {} ({})", body, status)
+    let response = crimp::Request::get(url)
+        .header("Metadata-Flavor", "Google")?
+        .timeout(std::time::Duration::from_secs(5))?
+        .send()?
+        .as_string()?;
+
+    if !response.is_success() {
+        bail!(
+            "Error response ({}) from metadata server: {}",
+            response.status,
+            response.body
+        );
     }
+
+    Ok(response.body.trim().to_owned())
 }
 
 /// Convenience helper for determining the project ID.
 fn get_project_id() -> String {
     env::var("GOOGLE_CLOUD_PROJECT")
-        .map_err(Into::into)
-        .or_else(|_: failure::Error| get_metadata(METADATA_PROJECT_URL))
+        .or_else(|_| get_metadata(METADATA_PROJECT_URL))
         .expect("Could not determine project ID")
 }
 
@@ -186,11 +167,9 @@ fn determine_monitored_resource() -> Value {
             }
         })
     } else {
-        let instance_id = get_metadata(METADATA_ID_URL)
-            .expect("Could not determine instance ID");
+        let instance_id = get_metadata(METADATA_ID_URL).expect("Could not determine instance ID");
 
-        let zone = get_metadata(METADATA_ZONE_URL)
-            .expect("Could not determine instance zone");
+        let zone = get_metadata(METADATA_ZONE_URL).expect("Could not determine instance zone");
 
         json!({
             "type": "gce_instance",
@@ -252,9 +231,8 @@ fn get_metadata_token() -> Result<Token> {
 fn sign_service_account_token(credentials: &Credentials) -> Result<Token> {
     use medallion::{Algorithm, Header, Payload};
 
-    let iat = Utc::now();
-    let exp = iat.checked_add_signed(chrono::Duration::seconds(3600))
-        .ok_or_else(|| format_err!("Failed to calculate token expiry"))?;
+    let iat = time::OffsetDateTime::now_utc();
+    let exp = iat + time::Duration::seconds(3600);
 
     let header = Header {
         alg: Algorithm::RS256,
@@ -267,8 +245,8 @@ fn sign_service_account_token(credentials: &Credentials) -> Result<Token> {
         iss: Some(credentials.client_email.clone()),
         sub: Some(credentials.client_email.clone()),
         aud: Some(LOGGING_SERVICE.to_string()),
-        iat: Some(iat.timestamp() as u64),
-        exp: Some(exp.timestamp() as u64),
+        iat: Some(iat.unix_timestamp().try_into().unwrap()),
+        exp: Some(exp.unix_timestamp().try_into().unwrap()),
         ..Default::default()
     };
 
@@ -323,7 +301,9 @@ enum Payload {
 /// text format.
 fn message_to_payload(message: Option<String>) -> Payload {
     match message {
-        None => Payload::TextPayload { text_payload: "empty log entry".into() },
+        None => Payload::TextPayload {
+            text_payload: "empty log entry".into(),
+        },
         Some(text_payload) => {
             // Attempt to deserialize the text payload as a generic
             // JSON value.
@@ -333,7 +313,7 @@ fn message_to_payload(message: Option<String>) -> Payload {
                 // expect other types of JSON payload) and return it
                 // in that case.
                 if json_payload.is_object() {
-                    return Payload::JsonPayload { json_payload }
+                    return Payload::JsonPayload { json_payload };
                 }
             }
 
@@ -348,18 +328,15 @@ fn message_to_payload(message: Option<String>) -> Payload {
 /// Parse errors are dismissed and returned as empty options: There
 /// simply aren't any useful fallback mechanisms other than defaulting
 /// to ingestion time for journaldriver's use-case.
-fn parse_microseconds(input: String) -> Option<DateTime<Utc>> {
+fn parse_microseconds(input: String) -> Option<time::OffsetDateTime> {
     if input.len() != 16 {
         return None;
     }
 
-    let seconds: i64 = (&input[..10]).parse().ok()?;
-    let micros: u32 = (&input[10..]).parse().ok()?;
+    let micros: i128 = input.parse().ok()?;
+    let nanos: i128 = micros * 1000;
 
-    match Utc.timestamp_opt(seconds, micros * 1000) {
-        LocalResult::Single(time) => Some(time),
-        _ => None,
-    }
+    time::OffsetDateTime::from_unix_timestamp_nanos(nanos).ok()
 }
 
 /// Converts a journald log message priority to a
@@ -408,7 +385,8 @@ struct LogEntry {
     labels: Value,
 
     #[serde(skip_serializing_if = "Option::is_none")]
-    timestamp: Option<DateTime<Utc>>,
+    #[serde(with = "time::serde::rfc3339::option")]
+    timestamp: Option<time::OffsetDateTime>,
 
     #[serde(flatten)]
     payload: Payload,
@@ -450,9 +428,7 @@ impl From<JournalRecord> for LogEntry {
         // Journald uses syslogd's concept of priority. No idea if this is
         // always present, but it's optional in the Stackdriver API, so we just
         // omit it if we can't find or parse it.
-        let severity = record
-            .remove("PRIORITY")
-            .and_then(priority_to_severity);
+        let severity = record.remove("PRIORITY").and_then(priority_to_severity);
 
         LogEntry {
             payload,
@@ -468,8 +444,7 @@ impl From<JournalRecord> for LogEntry {
 
 /// Attempt to read from the journal. If no new entry is present,
 /// await the next one up to the specified timeout.
-fn receive_next_record(timeout: Duration, journal: &mut Journal)
-                       -> Result<Option<JournalRecord>> {
+fn receive_next_record(timeout: Duration, journal: &mut Journal) -> Result<Option<JournalRecord>> {
     let next_record = journal.next_record()?;
     if next_record.is_some() {
         return Ok(next_record);
@@ -525,11 +500,10 @@ fn persist_cursor(cursor: String) -> Result<()> {
     if cursor.is_empty() {
         error!("Received empty journald cursor position, refusing to persist!");
         error!("Please report this message at https://github.com/tazjin/journaldriver/issues/2");
-        return Ok(())
+        return Ok(());
     }
 
-    let mut file = File::create(&*CURSOR_TMP_FILE)
-        .context("Failed to create cursor file")?;
+    let mut file = File::create(&*CURSOR_TMP_FILE).context("Failed to create cursor file")?;
 
     write!(file, "{}", cursor).context("Failed to write cursor file")?;
 
@@ -547,9 +521,7 @@ fn persist_cursor(cursor: String) -> Result<()> {
 ///
 /// If flushing is successful the last cursor position will be
 /// persisted to disk.
-fn flush(token: &mut Token,
-         entries: Vec<LogEntry>,
-         cursor: String) -> Result<()> {
+fn flush(token: &mut Token, entries: Vec<LogEntry>, cursor: String) -> Result<()> {
     if token.is_expired() {
         debug!("Refreshing Google metadata access token");
         let new_token = get_token()?;
@@ -583,25 +555,28 @@ fn prepare_request(entries: &[LogEntry]) -> Value {
 
 /// Perform the log entry insertion in Stackdriver Logging.
 fn write_entries(token: &Token, request: Value) -> Result<()> {
-    let response = ureq::post(ENTRIES_WRITE_URL)
-        .set("Authorization", format!("Bearer {}", token.token).as_str())
+    let response = crimp::Request::post(ENTRIES_WRITE_URL)
+        .json(&request)?
+        .header("Authorization", format!("Bearer {}", token.token).as_str())?
         // The timeout values are set relatively high, not because of
         // an expectation of Stackdriver being slow but just to
-        // eventually hit an error case in case of network troubles.
+        // eventually force an error in case of network troubles.
         // Presumably no request in a functioning environment will
         // ever hit these limits.
-        .timeout_connect(2000)
-        .timeout_read(5000)
-        .send_json(request);
+        .timeout(std::time::Duration::from_secs(5))?
+        .send()?;
 
-    if response.ok() {
-        Ok(())
-    } else {
-        let status = response.status_line().to_string();
-        let body = response.into_string()
-            .unwrap_or_else(|_| "no response body".into());
-        bail!("Write failure: {} ({})", body, status)
+    if !response.is_success() {
+        let status = response.status;
+        let body = response
+            .as_string()
+            .map(|r| r.body)
+            .unwrap_or_else(|_| "no valid response body".to_owned());
+
+        bail!("Writing to Stackdriver failed({}): {}", status, body);
     }
+
+    Ok(())
 }
 
 /// Attempt to read the initial cursor position from the configured
@@ -624,14 +599,12 @@ fn initial_cursor() -> Result<JournalSeek> {
         Err(ref err) if err.kind() == ErrorKind::NotFound => {
             info!("No previous cursor position, reading from journal tail");
             Ok(JournalSeek::Tail)
-        },
-        Err(err) => {
-            (Err(err).context("Could not read cursor position"))?
         }
+        Err(err) => (Err(err).context("Could not read cursor position"))?,
     }
 }
 
-fn main () {
+fn main() {
     env_logger::init();
 
     // The directory in which cursor positions are persisted should
@@ -641,17 +614,17 @@ fn main () {
         process::exit(1);
     }
 
-    let cursor_position_dir = CURSOR_FILE.parent()
+    let cursor_position_dir = CURSOR_FILE
+        .parent()
         .expect("Invalid cursor position file path");
 
     fs::create_dir_all(cursor_position_dir)
         .expect("Could not create directory to store cursor position in");
 
-    let mut journal = Journal::open(JournalFiles::All, false, true)
-        .expect("Failed to open systemd journal");
+    let mut journal =
+        Journal::open(JournalFiles::All, false, true).expect("Failed to open systemd journal");
 
-    let seek_position = initial_cursor()
-        .expect("Failed to determine initial cursor position");
+    let seek_position = initial_cursor().expect("Failed to determine initial cursor position");
 
     match journal.seek(seek_position) {
         Ok(cursor) => info!("Opened journal at cursor '{}'", cursor),
diff --git a/ops/journaldriver/src/tests.rs b/ops/journaldriver/src/tests.rs
index 779add7a70..6f5045d6a5 100644
--- a/ops/journaldriver/src/tests.rs
+++ b/ops/journaldriver/src/tests.rs
@@ -1,5 +1,6 @@
 use super::*;
 use serde_json::to_string;
+use time::macros::datetime;
 
 #[test]
 fn test_text_entry_serialization() {
@@ -15,7 +16,31 @@ fn test_text_entry_serialization() {
     let expected = "{\"labels\":null,\"textPayload\":\"test entry\"}";
     let result = to_string(&entry).expect("serialization failed");
 
-    assert_eq!(expected, result, "Plain text payload should serialize correctly")
+    assert_eq!(
+        expected, result,
+        "Plain text payload should serialize correctly"
+    )
+}
+
+#[test]
+fn test_timestamped_entry_serialization() {
+    let entry = LogEntry {
+        labels: Value::Null,
+        timestamp: Some(datetime!(1952-10-07 12:00:00 UTC)),
+        payload: Payload::TextPayload {
+            text_payload: "test entry".into(),
+        },
+        severity: None,
+    };
+
+    let expected =
+        "{\"labels\":null,\"timestamp\":\"1952-10-07T12:00:00Z\",\"textPayload\":\"test entry\"}";
+    let result = to_string(&entry).expect("serialization failed");
+
+    assert_eq!(
+        expected, result,
+        "Plain text payload should serialize correctly"
+    )
 }
 
 #[test]
@@ -26,7 +51,7 @@ fn test_json_entry_serialization() {
         payload: Payload::JsonPayload {
             json_payload: json!({
                 "message": "JSON test"
-            })
+            }),
         },
         severity: None,
     };
@@ -34,7 +59,7 @@ fn test_json_entry_serialization() {
     let expected = "{\"labels\":null,\"jsonPayload\":{\"message\":\"JSON test\"}}";
     let result = to_string(&entry).expect("serialization failed");
 
-    assert_eq!(expected, result, "JSOn payload should serialize correctly")
+    assert_eq!(expected, result, "JSON payload should serialize correctly")
 }
 
 #[test]
@@ -45,7 +70,10 @@ fn test_plain_text_payload() {
         text_payload: "plain text payload".into(),
     };
 
-    assert_eq!(expected, payload, "Plain text payload should be detected correctly");
+    assert_eq!(
+        expected, payload,
+        "Plain text payload should be detected correctly"
+    );
 }
 
 #[test]
@@ -55,7 +83,10 @@ fn test_empty_payload() {
         text_payload: "empty log entry".into(),
     };
 
-    assert_eq!(expected, payload, "Empty payload should be handled correctly");
+    assert_eq!(
+        expected, payload,
+        "Empty payload should be handled correctly"
+    );
 }
 
 #[test]
@@ -66,10 +97,13 @@ fn test_json_payload() {
         json_payload: json!({
             "someKey": "someValue",
             "otherKey": 42
-        })
+        }),
     };
 
-    assert_eq!(expected, payload, "JSON payload should be detected correctly");
+    assert_eq!(
+        expected, payload,
+        "JSON payload should be detected correctly"
+    );
 }
 
 #[test]
@@ -82,14 +116,16 @@ fn test_json_no_object() {
         text_payload: "42".into(),
     };
 
-    assert_eq!(expected, payload, "Non-object JSON payload should be plain text");
+    assert_eq!(
+        expected, payload,
+        "Non-object JSON payload should be plain text"
+    );
 }
 
 #[test]
 fn test_parse_microseconds() {
     let input: String = "1529175149291187".into();
-    let expected: DateTime<Utc> = "2018-06-16T18:52:29.291187Z"
-        .to_string().parse().unwrap();
+    let expected: time::OffsetDateTime = datetime!(2018-06-16 18:52:29.291187 UTC);
 
     assert_eq!(Some(expected), parse_microseconds(input));
 }
diff --git a/ops/keycloak/README.md b/ops/keycloak/README.md
index e8ffd700b5..fd72daa87d 100644
--- a/ops/keycloak/README.md
+++ b/ops/keycloak/README.md
@@ -12,7 +12,7 @@ credentials.
 An example `direnv` configuration used by tazjin is this:
 
 ```
-# //ops/secrets/.envrc
+# //ops/keycloak/.envrc
 source_up
 eval $(age --decrypt -i ~/.ssh/id_ed25519 $(git rev-parse --show-toplevel)/ops/secrets/tf-keycloak.age)
 ```
diff --git a/ops/keycloak/clients.tf b/ops/keycloak/clients.tf
index 5f2fd21a35..178971ae36 100644
--- a/ops/keycloak/clients.tf
+++ b/ops/keycloak/clients.tf
@@ -70,23 +70,16 @@ resource "keycloak_saml_user_attribute_protocol_mapper" "buildkite_name" {
   saml_attribute_name_format = "Unspecified"
 }
 
-resource "keycloak_openid_client" "oauth2_proxy" {
+resource "keycloak_openid_client" "panettone" {
   realm_id              = keycloak_realm.tvl.id
-  client_id             = "oauth2-proxy"
-  name                  = "TVL OAuth2 Proxy"
+  client_id             = "panettone"
+  name                  = "Panettone"
   enabled               = true
   access_type           = "CONFIDENTIAL"
   standard_flow_enabled = true
 
   valid_redirect_uris = [
-    "https://login.tvl.fyi/oauth2/callback",
-    "http://localhost:4774/oauth2/callback",
+    "https://b.tvl.fyi/auth",
+    "http://localhost:6161/auth",
   ]
 }
-
-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
-}
diff --git a/ops/keycloak/default.nix b/ops/keycloak/default.nix
index 96f0c40e5e..94ed912dc9 100644
--- a/ops/keycloak/default.nix
+++ b/ops/keycloak/default.nix
@@ -1,8 +1,14 @@
-{ depot, pkgs, ... }:
+{ depot, lib, pkgs, ... }:
 
-depot.nix.readTree.drvTargets {
+depot.nix.readTree.drvTargets rec {
   # Provide a Terraform wrapper with the right provider installed.
-  terraform = pkgs.terraform.withPlugins(p: [
+  terraform = pkgs.terraform.withPlugins (p: [
     p.keycloak
   ]);
+
+  validate = depot.tools.checks.validateTerraform {
+    inherit terraform;
+    name = "keycloak";
+    src = lib.cleanSource ./.;
+  };
 }
diff --git a/ops/keycloak/main.tf b/ops/keycloak/main.tf
index 819267ff96..923ac19397 100644
--- a/ops/keycloak/main.tf
+++ b/ops/keycloak/main.tf
@@ -1,6 +1,6 @@
 # Configure TVL Keycloak instance.
 #
-# TODO(tazjin): Configure GitHub/GitLab IDP
+# TODO(tazjin): Configure GitLab IDP
 
 terraform {
   required_providers {
@@ -31,4 +31,14 @@ resource "keycloak_realm" "tvl" {
   enabled                     = true
   display_name                = "The Virus Lounge"
   default_signature_algorithm = "RS256"
+
+  smtp_server {
+    from              = "tvlbot@tazj.in"
+    from_display_name = "The Virus Lounge"
+    host              = "127.0.0.1"
+    port              = "25"
+    reply_to          = "depot@tvl.su"
+    ssl               = false
+    starttls          = false
+  }
 }
diff --git a/ops/keycloak/user_sources.tf b/ops/keycloak/user_sources.tf
index 3fde6e07cc..01307fff8d 100644
--- a/ops/keycloak/user_sources.tf
+++ b/ops/keycloak/user_sources.tf
@@ -2,6 +2,10 @@
 # information (either by accessing a system like LDAP or integration
 # through protocols like OIDC).
 
+variable "github_client_secret" {
+  type = string
+}
+
 resource "keycloak_ldap_user_federation" "tvl_ldap" {
   name                    = "tvl-ldap"
   realm_id                = keycloak_realm.tvl.id
@@ -19,3 +23,22 @@ resource "keycloak_ldap_user_federation" "tvl_ldap" {
     "organizationalPerson",
   ]
 }
+
+# keycloak_oidc_identity_provider.github will be destroyed
+# (because keycloak_oidc_identity_provider.github is not in configuration)
+resource "keycloak_oidc_identity_provider" "github" {
+  alias                 = "github"
+  provider_id           = "github"
+  client_id             = "6d7f8bb2e82bb6739556"
+  client_secret         = var.github_client_secret
+  realm                 = keycloak_realm.tvl.id
+  backchannel_supported = false
+  gui_order             = "1"
+  store_token           = false
+  sync_mode             = "IMPORT"
+  trust_email           = true
+
+  # These default to built-in values for the `github` provider_id.
+  authorization_url = ""
+  token_url         = ""
+}
diff --git a/ops/kontemplate/release.nix b/ops/kontemplate/release.nix
index 8a04109526..6a3dbd5efe 100644
--- a/ops/kontemplate/release.nix
+++ b/ops/kontemplate/release.nix
@@ -10,13 +10,17 @@
 # This file is the Nix derivation used to build release binaries for
 # several different architectures and operating systems.
 
-let pkgs = import ((import <nixpkgs> {}).fetchFromGitHub {
-  owner = "NixOS";
-  repo = "nixpkgs-channels";
-  rev = "541d9cce8af7a490fb9085305939569567cb58e6";
-  sha256 = "0jgz72hhzkd5vyq5v69vpljjlnf0lqaz7fh327bvb3cvmwbfxrja";
-}) {};
-in with pkgs; buildGoPackage rec {
+let
+  pkgs = import
+    ((import <nixpkgs> { }).fetchFromGitHub {
+      owner = "NixOS";
+      repo = "nixpkgs-channels";
+      rev = "541d9cce8af7a490fb9085305939569567cb58e6";
+      sha256 = "0jgz72hhzkd5vyq5v69vpljjlnf0lqaz7fh327bvb3cvmwbfxrja";
+    })
+    { };
+in
+with pkgs; buildGoPackage rec {
   name = "kontemplate-${version}";
   version = "canon";
   src = ./.;
@@ -29,8 +33,8 @@ in with pkgs; buildGoPackage rec {
   # reason for setting the 'allowGoReference' flag.
   dontStrip = true; # Linker configuration handles stripping
   allowGoReference = true;
-  CGO_ENABLED="0";
-  GOCACHE="off";
+  CGO_ENABLED = "0";
+  GOCACHE = "off";
 
   # Configure release builds via the "build-matrix" script:
   buildInputs = [ git ];
diff --git a/ops/machines/all-systems.nix b/ops/machines/all-systems.nix
index df1cfa6a48..c4382fbddb 100644
--- a/ops/machines/all-systems.nix
+++ b/ops/machines/all-systems.nix
@@ -1,6 +1,7 @@
 { depot, ... }:
 
 (with depot.ops.machines; [
+  sanduny
   whitby
 ]) ++
 
@@ -8,13 +9,19 @@
   camden
   frog
   tverskoy
+  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
new file mode 100644
index 0000000000..af2dfb02a5
--- /dev/null
+++ b/ops/machines/sanduny/default.nix
@@ -0,0 +1,138 @@
+# sanduny.tvl.su
+#
+# This is a VPS hosted with Bitfolk, intended to additionally serve
+# some of our public services like cgit, josh and the websites.
+#
+# In case of whitby going down, sanduny will keep depot available.
+
+_: # ignore readTree options
+
+{ config, depot, lib, pkgs, ... }:
+
+let
+  mod = name: depot.path.origSrc + ("/ops/modules/" + name);
+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 = {
+    hostName = "sanduny";
+    domain = "tvl.su";
+    useDHCP = false;
+
+    interfaces.eth0 = {
+      ipv4.addresses = lib.singleton {
+        address = "85.119.82.231";
+        prefixLength = 21;
+      };
+
+      ipv6.addresses = lib.singleton {
+        address = "2001:ba8:1f1:f109::feed:edef:beef";
+        prefixLength = 64;
+      };
+    };
+
+    defaultGateway = "85.119.80.1";
+    defaultGateway6.address = "2001:ba8:1f1:f109::1";
+
+    firewall.allowedTCPPorts = [ 22 80 443 ];
+
+    # https://bitfolk.com/customer_information.html#toc_2_DNS
+    nameservers = [
+      "85.119.80.232"
+      "85.119.80.233"
+      "2001:ba8:1f1:f205::53"
+      "2001:ba8:1f1:f206::53"
+    ];
+  };
+
+  security.sudo.wheelNeedsPassword = false;
+
+  environment.systemPackages = with pkgs; [
+    emacs-nox
+    vim
+    curl
+    unzip
+    htop
+  ];
+
+  programs.mtr.enable = true;
+
+  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;
+    interval = "1 hour";
+    diskThreshold = 2; # GiB
+    maxFreed = 5; # GiB
+    preserveGenerations = "90d";
+  };
+
+  # Allow Gerrit to replicate depot to /var/lib/depot
+  services.depot.replica.enable = true;
+
+  # Run git serving tools locally ...
+  services.depot.cgit = {
+    enable = true;
+    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.initrd.availableKernelModules = [ "xen_blkfront" ];
+
+  hardware.cpu.intel.updateMicrocode = true;
+
+  fileSystems = {
+    "/" = {
+      device = "/dev/disk/by-uuid/aabc3638-43ca-45f3-af89-c451e8448e92";
+      fsType = "ext4";
+    };
+
+    "/boot" = {
+      device = "/dev/disk/by-uuid/75aa99d5-fed7-4c5c-8570-7745f6cff9f5";
+      fsType = "ext3";
+    };
+
+    "/nix" = {
+      device = "/dev/disk/by-uuid/d1721678-c294-482b-b72e-3b15f2c56c63";
+      fsType = "ext4";
+    };
+  };
+
+  tvl.cache.enable = true;
+
+  swapDevices = lib.singleton {
+    device = "/dev/disk/by-uuid/df4ad9da-0a06-4c27-93e5-5d44e4750e55";
+  };
+
+  system.stateVersion = "22.05"; # Did you read the comment?
+}
diff --git a/ops/machines/whitby/OWNERS b/ops/machines/whitby/OWNERS
index b1b749e871..4581a80d61 100644
--- a/ops/machines/whitby/OWNERS
+++ b/ops/machines/whitby/OWNERS
@@ -1,6 +1,5 @@
-inherited: false
+set noparent
 
 # Want in on this list? Try paying!
-owners:
-  - lukegb
-  - tazjin
+lukegb
+tazjin
diff --git a/ops/machines/whitby/default.nix b/ops/machines/whitby/default.nix
index dbee33aa3f..6a8ee56abc 100644
--- a/ops/machines/whitby/default.nix
+++ b/ops/machines/whitby/default.nix
@@ -4,43 +4,52 @@
 let
   inherit (builtins) listToAttrs;
   inherit (lib) range;
-in {
+
+  mod = name: depot.path.origSrc + ("/ops/modules/" + name);
+in
+{
   imports = [
-    "${depot.path}/ops/modules/atward.nix"
-    "${depot.path}/ops/modules/clbot.nix"
-    "${depot.path}/ops/modules/gerrit-queue.nix"
-    "${depot.path}/ops/modules/git-serving.nix"
-    "${depot.path}/ops/modules/irccat.nix"
-    "${depot.path}/ops/modules/monorepo-gerrit.nix"
-    "${depot.path}/ops/modules/nixery.nix"
-    "${depot.path}/ops/modules/oauth2_proxy.nix"
-    "${depot.path}/ops/modules/owothia.nix"
-    "${depot.path}/ops/modules/panettone.nix"
-    "${depot.path}/ops/modules/paroxysm.nix"
-    "${depot.path}/ops/modules/restic.nix"
-    "${depot.path}/ops/modules/smtprelay.nix"
-    "${depot.path}/ops/modules/sourcegraph.nix"
-    "${depot.path}/ops/modules/tvl-buildkite.nix"
-    "${depot.path}/ops/modules/tvl-slapd/default.nix"
-    "${depot.path}/ops/modules/www/atward.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/auth.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/b.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/cache.tvl.su.nix"
-    "${depot.path}/ops/modules/www/cl.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/code.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/cs.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/deploys.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/images.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/nixery.dev.nix"
-    "${depot.path}/ops/modules/www/static.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/status.tvl.su.nix"
-    "${depot.path}/ops/modules/www/tazj.in.nix"
-    "${depot.path}/ops/modules/www/todo.tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/tvl.fyi.nix"
-    "${depot.path}/ops/modules/www/tvl.su.nix"
-    "${depot.path}/ops/modules/www/wigglydonke.rs.nix"
-    "${depot.third_party.agenix.src}/modules/age.nix"
-    "${pkgs.path}/nixos/modules/services/web-apps/gerrit.nix"
+    (mod "atward.nix")
+    (mod "cgit.nix")
+    (mod "clbot.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 "owothia.nix")
+    (mod "panettone.nix")
+    (mod "paroxysm.nix")
+    (mod "restic.nix")
+    (mod "smtprelay.nix")
+    (mod "sourcegraph.nix")
+    (mod "tvl-buildkite.nix")
+    (mod "tvl-slapd/default.nix")
+    (mod "tvl-users.nix")
+    (mod "www/atward.tvl.fyi.nix")
+    (mod "www/auth.tvl.fyi.nix")
+    (mod "www/b.tvl.fyi.nix")
+    (mod "www/cache.tvl.su.nix")
+    (mod "www/cl.tvl.fyi.nix")
+    (mod "www/code.tvl.fyi.nix")
+    (mod "www/cs.tvl.fyi.nix")
+    (mod "www/deploys.tvl.fyi.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/todo.tvl.fyi.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")
   ];
 
   hardware = {
@@ -49,13 +58,19 @@ in {
   };
 
   boot = {
-    tmpOnTmpfs = true;
+    tmp.useTmpfs = true;
     kernelModules = [ "kvm-amd" ];
     supportedFilesystems = [ "zfs" ];
 
     initrd = {
       availableKernelModules = [
-        "igb" "xhci_pci" "nvme" "ahci" "usbhid" "usb_storage" "sr_mod"
+        "igb"
+        "xhci_pci"
+        "nvme"
+        "ahci"
+        "usbhid"
+        "usb_storage"
+        "sr_mod"
       ];
 
       # Enable SSH in the initrd so that we can enter disk encryption
@@ -68,7 +83,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
@@ -89,7 +104,6 @@ in {
 
     loader.grub = {
       enable = true;
-      version = 2;
       efiSupport = true;
       efiInstallAsRemovable = true;
       device = "/dev/disk/by-id/nvme-SAMSUNG_MZQLB1T9HAJR-00007_S439NA0N201620";
@@ -170,26 +184,26 @@ in {
 
   nix = {
     nrBuildUsers = 256;
-    maxJobs = lib.mkDefault 64;
-    extraOptions = ''
-      secret-key-files = /run/agenix/nix-cache-priv
-    '';
-
-    trustedUsers = [
-      "grfn"
-      "lukegb"
-      "tazjin"
-      "sterni"
-    ];
+    settings = {
+      max-jobs = lib.mkDefault 64;
+      secret-key-files = "/run/agenix/nix-cache-priv";
+
+      trusted-users = [
+        "aspen"
+        "lukegb"
+        "tazjin"
+        "sterni"
+      ];
+    };
 
     sshServe = {
       enable = true;
       keys = with depot.users;
         tazjin.keys.all
         ++ lukegb.keys.all
-        ++ [ grfn.keys.whitby ]
+        ++ [ aspen.keys.whitby ]
         ++ sterni.keys.all
-        ;
+      ;
     };
   };
 
@@ -197,22 +211,24 @@ 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.
   age.secrets =
     let
       secretFile = name: depot.ops.secrets."${name}.age";
-    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";
@@ -235,6 +251,12 @@ in {
         group = "buildkite-agents";
       };
 
+      buildkite-private-key = {
+        file = secretFile "buildkite-ssh-private-key";
+        mode = "0440";
+        group = "buildkite-agents";
+      };
+
       gerrit-besadii-config = {
         file = secretFile "besadii";
         owner = "git";
@@ -257,6 +279,14 @@ in {
         file = secretFile "nix-cache-pub";
         mode = "0444";
       };
+
+      depot-replica-key = {
+        file = secretFile "depot-replica-key";
+        mode = "0500";
+        owner = "git";
+        group = "git";
+        path = "/var/lib/git/.ssh/id_ed25519";
+      };
     };
 
   # Automatically collect garbage from the Nix store.
@@ -315,13 +345,13 @@ in {
   # Start the Gerrit->IRC bot
   services.depot.clbot = {
     enable = true;
-    channels = [ "#tvl" ];
+    channels = [ "#tvix-dev" "#tvl" ];
 
     # See //fun/clbot for details.
     flags = {
       gerrit_host = "cl.tvl.fyi:29418";
       gerrit_ssh_auth_username = "clbot";
-      gerrit_ssh_auth_key = "/run/agenix/clbot-ssh";
+      gerrit_ssh_auth_key = config.age.secretsDir + "/clbot-ssh";
 
       irc_server = "localhost:${toString config.services.znc.config.Listener.l.Port}";
       irc_user = "tvlbot";
@@ -340,6 +370,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;
@@ -380,11 +413,13 @@ in {
     # Run atward, the search engine redirection thing.
     atward.enable = true;
 
-    # Run a Nixery instance
-    nixery.enable = true;
-
     # Run cgit & josh to serve git
-    git-serving.enable = true;
+    cgit = {
+      enable = true;
+      user = "git"; # run as the same user as gerrit
+    };
+
+    josh.enable = true;
 
     # Configure backups to GleSYS
     restic = {
@@ -397,15 +432,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
@@ -421,9 +454,7 @@ in {
 
     ensureUsers = [{
       name = "panettone";
-      ensurePermissions = {
-        "DATABASE panettone" = "ALL PRIVILEGES";
-      };
+      ensureDBOwnership = true;
     }];
   };
 
@@ -439,30 +470,26 @@ in {
   services.nix-serve = {
     enable = true;
     port = 6443;
-    secretKeyFile = "/run/agenix/nix-cache-priv";
+    secretKeyFile = config.age.secretsDir + "/nix-cache-priv";
     bindAddress = "localhost";
   };
 
   services.fail2ban.enable = true;
 
   environment.systemPackages = (with pkgs; [
-    alacritty.terminfo
     bat
     bb
     curl
     direnv
     emacs-nox
     fd
-    foot.terminfo
     git
     htop
     hyperfine
     jq
-    kitty.terminfo
     nano
     nvd
     ripgrep
-    rxvt_unicode.terminfo
     tree
     unzip
     vim
@@ -472,98 +499,97 @@ in {
     ops.deploy-whitby
   ]);
 
-  services.journaldriver = {
-    enable = true;
-    googleCloudProject = "tvl-fyi";
-    logStream = "whitby";
-    applicationCredentials = "/var/lib/journaldriver/key.json";
-  };
+  # Required for prometheus to be able to scrape stats
+  services.nginx.statusPage = true;
 
   # Configure Prometheus & Grafana. Exporter configuration for
   # Prometheus is inside the respective service modules.
   services.prometheus = {
     enable = true;
-    exporters.node = {
-      enable = true;
+    retentionTime = "90d";
 
-      enabledCollectors = [
-        "logind"
-        "processes"
-        "systemd"
-      ];
+    exporters = {
+      node = {
+        enable = true;
+
+        enabledCollectors = [
+          "logind"
+          "processes"
+          "systemd"
+        ];
+      };
+
+      nginx = {
+        enable = true;
+        sslVerify = false;
+        constLabels = [ "host=whitby" ];
+      };
     };
 
     scrapeConfigs = [{
       job_name = "node";
       scrape_interval = "5s";
       static_configs = [{
-        targets = ["localhost:${toString config.services.prometheus.exporters.node.port}"];
+        targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ];
+      }];
+    }
+      {
+        job_name = "nginx";
+        scrape_interval = "5s";
+        static_configs = [{
+          targets = [ "localhost:${toString config.services.prometheus.exporters.nginx.port}" ];
+        }];
       }];
-    }];
   };
 
   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;
-        };
+
+    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;
       };
-      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);
+
+      "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";
@@ -572,30 +598,29 @@ in {
   };
 
   # Contains GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET.
-  systemd.services.grafana.serviceConfig.EnvironmentFile = "/run/agenix/grafana";
+  systemd.services.grafana.serviceConfig.EnvironmentFile = config.age.secretsDir + "/grafana";
 
   services.keycloak = {
     enable = true;
-    httpPort = "5925"; # "kycl"
-    frontendUrl = "https://auth.tvl.fyi/auth/";
+
+    settings = {
+      http-port = 5925; # kycl
+      hostname = "auth.tvl.fyi";
+      http-relative-path = "/auth";
+      proxy = "edge";
+    };
 
     database = {
       type = "postgresql";
-      passwordFile = "/run/agenix/keycloak-db";
+      passwordFile = config.age.secretsDir + "/keycloak-db";
       createLocally = false;
     };
+  };
 
-    # Configure Keycloak to look at forwarded headers from the reverse
-    # proxy.
-    extraConfig = {
-      "subsystem=undertow" = {
-        "server=default-server" = {
-          "http-listener=default" = {
-            proxy-address-forwarding = "true";
-          };
-        };
-      };
-    };
+  # 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
@@ -605,89 +630,14 @@ in {
 
   security.sudo.extraRules = [
     {
-      groups = ["wheel"];
-      commands = [{ command = "ALL"; options = ["NOPASSWD"]; }];
+      groups = [ "wheel" ];
+      commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
     }
   ];
 
   users = {
-    users.tazjin = {
-      isNormalUser = true;
-      extraGroups = [ "git" "wheel" ];
-      shell = pkgs.fish;
-      openssh.authorizedKeys.keys = depot.users.tazjin.keys.all;
-    };
-
-    users.lukegb = {
-      isNormalUser = true;
-      extraGroups = [ "git" "wheel" ];
-      openssh.authorizedKeys.keys = depot.users.lukegb.keys.all;
-    };
-
-    users.grfn = {
-      isNormalUser = true;
-      extraGroups = [ "git" "wheel" ];
-      openssh.authorizedKeys.keys = [
-        depot.users.grfn.keys.whitby
-      ];
-    };
-
-    users.isomer = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.isomer.keys.all;
-    };
-
-    users.riking = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.riking.keys.u2f ++ depot.users.riking.keys.passworded;
-    };
-
-    users.edef = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.edef.keys.all;
-    };
-
-    users.qyliss = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.qyliss.keys.all;
-    };
-
-    users.eta = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.eta.keys.whitby;
-    };
-
-    users.cynthia = {
-      isNormalUser = true; # I'm normal OwO :3
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.cynthia.keys.all;
-    };
-
-    users.firefly = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.firefly.keys.whitby;
-    };
-
-    users.sterni = {
-      isNormalUser = true;
-      extraGroups = [ "git" "wheel" ];
-      openssh.authorizedKeys.keys = depot.users.sterni.keys.all;
-    };
-
-    users.flokli = {
-      isNormalUser = true;
-      extraGroups = [ "git" ];
-      openssh.authorizedKeys.keys = depot.users.flokli.keys.all;
-    };
-
     # Set up a user & group for git shenanigans
-    groups.git = {};
+    groups.git = { };
     users.git = {
       group = "git";
       isSystemUser = true;
@@ -696,10 +646,7 @@ in {
     };
   };
 
-  security.acme = {
-    acceptTerms = true;
-    defaults.email = "certs@tvl.fyi";
-  };
+  zramSwap.enable = true;
 
   system.stateVersion = "20.03";
 }
diff --git a/ops/modules/atward.nix b/ops/modules/atward.nix
index 354f9ebdd3..f345a08e31 100644
--- a/ops/modules/atward.nix
+++ b/ops/modules/atward.nix
@@ -3,7 +3,8 @@
 let
   cfg = config.services.depot.atward;
   description = "atward - (attempt to) cleverly route queries";
-in {
+in
+{
   options.services.depot.atward = {
     enable = lib.mkEnableOption description;
 
diff --git a/ops/modules/auto-deploy.nix b/ops/modules/auto-deploy.nix
index 83a8273562..c504906b2b 100644
--- a/ops/modules/auto-deploy.nix
+++ b/ops/modules/auto-deploy.nix
@@ -45,7 +45,8 @@ let
     # NixOS in $STATE_DIRECTORY
     (cd / && ${rebuild-system}/bin/rebuild-system)
   '';
-in {
+in
+{
   options.services.depot.auto-deploy = {
     enable = lib.mkEnableOption description;
 
diff --git a/ops/modules/automatic-gc.nix b/ops/modules/automatic-gc.nix
index 6347857210..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}"
@@ -29,7 +34,8 @@ let
       echo "Skipping GC, enough space available"
     fi
   '';
-in {
+in
+{
   options.services.depot.automatic-gc = {
     enable = lib.mkEnableOption description;
 
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/cgit.nix b/ops/modules/cgit.nix
new file mode 100644
index 0000000000..fc3f171585
--- /dev/null
+++ b/ops/modules/cgit.nix
@@ -0,0 +1,55 @@
+# Configuration for running the TVL cgit instance using thttpd.
+{ config, depot, lib, pkgs, ... }:
+
+let
+  cfg = config.services.depot.cgit;
+
+  userConfig =
+    if builtins.isNull cfg.user then {
+      DynamicUser = true;
+    } else {
+      User = cfg.user;
+      Group = cfg.user;
+    };
+in
+{
+  options.services.depot.cgit = with lib; {
+    enable = mkEnableOption "Run cgit web interface for depot";
+
+    port = mkOption {
+      description = "Port on which cgit should listen";
+      type = types.int;
+      default = 2448;
+    };
+
+    repo = mkOption {
+      description = "Path to depot's .git folder on the machine";
+      type = types.str;
+      default = "/var/lib/gerrit/git/depot.git/";
+    };
+
+    user = mkOption {
+      description = ''
+        User to use for the cgit service. It is expected that this is
+        also the name of the user's primary group.
+      '';
+
+      type = with types; nullOr str;
+      default = null;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.cgit = {
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Restart = "on-failure";
+
+        ExecStart = depot.web.cgit-tvl.override {
+          inherit (cfg) port repo;
+        };
+      } // userConfig;
+    };
+  };
+}
diff --git a/ops/modules/clbot.nix b/ops/modules/clbot.nix
index ef4c2ab237..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
   ''));
 
@@ -42,7 +42,8 @@ let
       };
     };
   };
-in {
+in
+{
   options.services.depot.clbot = {
     enable = mkEnableOption description;
 
@@ -59,7 +60,7 @@ in {
     secretsFile = mkOption {
       type = types.str;
       description = "EnvironmentFile from which to load secrets";
-      default = "/run/agenix/clbot";
+      default = config.age.secretsDir + "/clbot";
     };
   };
 
@@ -68,7 +69,7 @@ in {
     # (notably the SSH private key) readable by this user outside of
     # the module.
     users = {
-      groups.clbot = {};
+      groups.clbot = { };
 
       users.clbot = {
         group = "clbot";
diff --git a/ops/modules/default.nix b/ops/modules/default.nix
index 8bdfecdf41..d747e8e131 100644
--- a/ops/modules/default.nix
+++ b/ops/modules/default.nix
@@ -1,2 +1,2 @@
 # Make readTree happy at this level.
-_: {}
+_: { }
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/depot-replica.nix b/ops/modules/depot-replica.nix
new file mode 100644
index 0000000000..b71f10409a
--- /dev/null
+++ b/ops/modules/depot-replica.nix
@@ -0,0 +1,45 @@
+# Configuration for receiving a depot replica from Gerrit's
+# replication plugin.
+#
+# This only prepares the user and folder for receiving the replica,
+# but Gerrit configuration still needs to be modified in addition.
+{ config, depot, lib, pkgs, ... }:
+
+let
+  cfg = config.services.depot.replica;
+in
+{
+  options.services.depot.replica = with lib; {
+    enable = mkEnableOption "Receive depot git replica from Gerrit";
+
+    key = mkOption {
+      description = "Public key to use for replication";
+      type = types.str;
+      default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFFab9O1xaQ1TCyn+CxmXHexdlLzURREG+UR3Qdi3BvH";
+    };
+
+    path = mkOption {
+      description = "Replication destination path (will be created)";
+      type = types.str;
+      default = "/var/lib/depot";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    users.groups.depot = { };
+
+    users.users.depot = {
+      group = "depot";
+      isSystemUser = true;
+      createHome = true;
+      home = cfg.path;
+      homeMode = "755"; # everyone can read depot
+      openssh.authorizedKeys.keys = lib.singleton cfg.key;
+      shell = pkgs.bashInteractive; # gerrit needs to run shell commands
+    };
+
+    environment.systemPackages = [
+      pkgs.git
+    ];
+  };
+}
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 a4b073f856..0000000000
--- a/ops/modules/gerrit-queue.nix
+++ /dev/null
@@ -1,51 +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 = "/run/agenix/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/git-serving.nix b/ops/modules/git-serving.nix
deleted file mode 100644
index 6b8bef29b1..0000000000
--- a/ops/modules/git-serving.nix
+++ /dev/null
@@ -1,53 +0,0 @@
-# Configures public git-serving infrastructure for TVL, this involves:
-#
-# 1. cgit (running at code.tvl.fyi) for web views of the repository
-# 2. josh (for cloning the repository and its distinct subtrees)
-#
-# We also run Sourcegraph for browsing the repository, but this is
-# currently configured in a separate module
-# (//ops/modules/sourcegraph.nix)
-#
-# TODO(tazjin): Move //web/cgit-taz configuration in here instead.
-{ config, depot, lib, pkgs, ... }:
-
-let
-  cfg = config.services.depot.git-serving;
-in {
-  options.services.depot.git-serving = with lib; {
-    enable = mkEnableOption "Enable cgit & josh configuration";
-
-    joshPort = mkOption {
-      description = "Port on which josh should listen";
-      type = types.int;
-      default = 5674;
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    # Run cgit for the depot. The onion here is nginx(thttpd(cgit)).
-    systemd.services.cgit = {
-      wantedBy = [ "multi-user.target" ];
-      script = "${depot.web.cgit-taz}/bin/cgit-launch";
-
-      serviceConfig = {
-        Restart = "on-failure";
-        User = "git";
-        Group = "git";
-      };
-    };
-
-    # Run josh for the depot.
-    systemd.services.josh = {
-      description = "josh - partial cloning of monorepos";
-      wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.git pkgs.bash ];
-
-      serviceConfig = {
-        DynamicUser = true;
-        StateDirectory = "josh";
-        Restart = "always";
-        ExecStart = "${depot.third_party.josh}/bin/josh-proxy --no-background --local /var/lib/josh --port ${toString cfg.joshPort} --remote https://cl.tvl.fyi/";
-      };
-    };
-  };
-}
diff --git a/ops/modules/irccat.nix b/ops/modules/irccat.nix
index deb0b4ecaf..2263118d99 100644
--- a/ops/modules/irccat.nix
+++ b/ops/modules/irccat.nix
@@ -27,19 +27,20 @@ let
 
     exec ${depot.third_party.irccat}/bin/irccat
   '';
-in {
+in
+{
   options.services.depot.irccat = {
     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!)";
     };
 
     secretsFile = lib.mkOption {
       type = lib.types.str;
       description = "Path to the secrets file to be merged";
-      default = "/run/agenix/irccat";
+      default = config.age.secretsDir + "/irccat";
     };
   };
 
diff --git a/ops/modules/josh.nix b/ops/modules/josh.nix
new file mode 100644
index 0000000000..3c37d0fec3
--- /dev/null
+++ b/ops/modules/josh.nix
@@ -0,0 +1,33 @@
+# Configures the public josh instance for serving the depot.
+{ config, depot, lib, pkgs, ... }:
+
+let
+  cfg = config.services.depot.josh;
+in
+{
+  options.services.depot.josh = with lib; {
+    enable = mkEnableOption "Enable josh for serving the depot";
+
+    port = mkOption {
+      description = "Port on which josh should listen";
+      type = types.int;
+      default = 5674;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # Run josh for the depot.
+    systemd.services.josh = {
+      description = "josh - partial cloning of monorepos";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.git pkgs.bash ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "josh";
+        Restart = "always";
+        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/journaldriver.nix b/ops/modules/journaldriver.nix
new file mode 100644
index 0000000000..0d6b0bcc7f
--- /dev/null
+++ b/ops/modules/journaldriver.nix
@@ -0,0 +1,26 @@
+# Configures journaldriver to forward to the tvl-fyi GCP project from
+# TVL machines.
+{ config, depot, lib, pkgs, ... }:
+
+{
+  imports = [
+    (depot.third_party.agenix.src + "/modules/age.nix")
+  ];
+
+  age.secrets.journaldriver.file = depot.ops.secrets."journaldriver.age";
+
+  services.journaldriver = {
+    enable = true;
+    googleCloudProject = "tvl-fyi";
+    logStream = config.networking.hostName;
+  };
+
+  # Override the systemd service defined in the nixpkgs module to use
+  # the credentials provided by agenix.
+  systemd.services.journaldriver = {
+    serviceConfig = {
+      LoadCredential = "journaldriver.json:/run/agenix/journaldriver";
+      ExecStart = lib.mkForce "${pkgs.coreutils}/bin/env GOOGLE_APPLICATION_CREDENTIALS=\"\${CREDENTIALS_DIRECTORY}/journaldriver.json\" ${depot.ops.journaldriver}/bin/journaldriver";
+    };
+  };
+}
diff --git a/ops/modules/known-hosts.nix b/ops/modules/known-hosts.nix
new file mode 100644
index 0000000000..9ea689178e
--- /dev/null
+++ b/ops/modules/known-hosts.nix
@@ -0,0 +1,21 @@
+# Configure public keys for SSH hosts known to TVL.
+{ ... }:
+
+{
+  programs.ssh.knownHosts = {
+    whitby = {
+      hostNames = [ "whitby.tvl.fyi" "whitby.tvl.su" ];
+      publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNh/w4BSKov0jdz3gKBc98tpoLta5bb87fQXWBhAl2I";
+    };
+
+    sanduny = {
+      hostNames = [ "sanduny.tvl.su" ];
+      publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOag0XhylaTVhmT6HB8EN2Fv5Ymrc4ZfypOXONUkykTX";
+    };
+
+    github = {
+      hostNames = [ "github.com" ];
+      publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl";
+    };
+  };
+}
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 6638f30b3f..b335fe61d5 100644
--- a/ops/modules/monorepo-gerrit.nix
+++ b/ops/modules/monorepo-gerrit.nix
@@ -9,23 +9,26 @@ 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
   '';
-in {
+in
+{
   services.gerrit = {
     enable = true;
     listenAddress = "[::]:4778"; # 4778 - grrt
     serverId = "4fdfa107-4df9-4596-8e0a-1d2bbdd96e36";
+
     builtinPlugins = [
       "download-commands"
       "hooks"
+      "replication"
     ];
 
     plugins = with depot.third_party.gerrit_plugins; [
-      owners
+      code-owners
       oauth
       depot.ops.gerrit-tvl
     ];
@@ -39,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";
@@ -84,26 +87,35 @@ 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.
       };
 
+      plugin.code-owners = {
+        # A Code-Review +2 vote is required from a code owner.
+        requiredApproval = "Code-Review+2";
+        # The OWNERS check can be overriden using an Owners-Override vote.
+        overrideApproval = "Owners-Override+1";
+        # People implicitly approve their own changes automatically.
+        enableImplicitApprovals = "TRUE";
+      };
+
       # Allow users to add additional email addresses to their accounts.
       oauth.allowRegisterNewEmail = true;
 
@@ -129,6 +141,17 @@ in {
         smtpServerPort = 2525;
       };
     };
+
+    # Replication of the depot repository to secondary machines, for
+    # serving cgit/josh.
+    replicationSettings = {
+      gerrit.replicateOnStartup = true;
+
+      remote.sanduny = {
+        url = "depot@sanduny.tvl.su:/var/lib/depot";
+        projects = "depot";
+      };
+    };
   };
 
   systemd.services.gerrit = {
diff --git a/ops/modules/nixery.nix b/ops/modules/nixery.nix
index 60d1510457..29da46cc1d 100644
--- a/ops/modules/nixery.nix
+++ b/ops/modules/nixery.nix
@@ -5,8 +5,10 @@
 let
   cfg = config.services.depot.nixery;
   description = "Nixery - container images on-demand";
-  storagePath = "/var/lib/nixery/${pkgs.nixpkgsCommits.unstable}";
-in {
+  nixpkgsSrc = depot.third_party.sources.nixpkgs-stable;
+  storagePath = "/var/lib/nixery/${nixpkgsSrc.rev}";
+in
+{
   options.services.depot.nixery = {
     enable = lib.mkEnableOption description;
 
@@ -27,12 +29,12 @@ in {
         StateDirectory = "nixery";
         Restart = "always";
         ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${storagePath}";
-        ExecStart = "${depot.third_party.nixery.nixery-bin}/bin/nixery";
+        ExecStart = "${depot.tools.nixery.nixery}/bin/server";
       };
 
       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 07ba8861e7..0000000000
--- a/ops/modules/oauth2_proxy.nix
+++ /dev/null
@@ -1,52 +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
-  '';
-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 = "/run/agenix/oauth2_proxy";
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.oauth2_proxy = {
-      inherit description;
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        Restart = "always";
-        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
new file mode 100644
index 0000000000..fa577f0f57
--- /dev/null
+++ b/ops/modules/open_eid.nix
@@ -0,0 +1,54 @@
+# NixOS module to configure the Estonian e-ID software.
+{ pkgs, ... }:
+
+{
+  services.pcscd.enable = true;
+
+  # Tell p11-kit to load/proxy opensc-pkcs11.so, providing all available slots
+  # (PIN1 for authentication/decryption, PIN2 for signing).
+  environment.etc."pkcs11/modules/opensc-pkcs11".text = ''
+    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.bin # provides digidoc-tool(1)
+    qdigidoc
+
+    # 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/owothia.nix b/ops/modules/owothia.nix
index b2a77cddc2..b9746c1720 100644
--- a/ops/modules/owothia.nix
+++ b/ops/modules/owothia.nix
@@ -4,14 +4,15 @@
 let
   cfg = config.services.depot.owothia;
   description = "owothia - i'm a service owo";
-in {
+in
+{
   options.services.depot.owothia = {
     enable = lib.mkEnableOption description;
 
     secretsFile = lib.mkOption {
       type = lib.types.str;
       description = "File path from which systemd should read secrets";
-      default = "/run/agenix/owothia";
+      default = config.age.secretsDir + "/owothia";
     };
 
     owoChance = lib.mkOption {
diff --git a/ops/modules/panettone.nix b/ops/modules/panettone.nix
index 11e934ec2e..e23dd028ab 100644
--- a/ops/modules/panettone.nix
+++ b/ops/modules/panettone.nix
@@ -2,7 +2,8 @@
 
 let
   cfg = config.services.depot.panettone;
-in {
+in
+{
   options.services.depot.panettone = with lib; {
     enable = mkEnableOption "Panettone issue tracker";
 
@@ -36,7 +37,7 @@ in {
         by systemd's EnvironmentFile
       '';
       type = types.str;
-      default = "/run/agenix/panettone";
+      default = config.age.secretsDir + "/panettone";
     };
 
     irccatHost = mkOption {
@@ -62,23 +63,26 @@ in {
       assertion =
         cfg.dbHost != "localhost" || config.services.postgresql.enable;
       message = "Panettone requires a postgresql database";
-    } {
-      assertion =
-        cfg.dbHost != "localhost" || config.services.postgresql.enableTCPIP;
-      message = "Panettone can only connect to the postgresql database over TCP";
-    } {
-      assertion =
-        cfg.dbHost != "localhost" || (lib.any
-          (user: user.name == cfg.dbUser)
-          config.services.postgresql.ensureUsers);
-      message = "Panettone requires a database user";
-    } {
-      assertion =
-        cfg.dbHost != "localhost" || (lib.any
-          (db: db == cfg.dbName)
-          config.services.postgresql.ensureDatabases);
-      message = "Panettone requires a database";
-    }];
+    }
+      {
+        assertion =
+          cfg.dbHost != "localhost" || config.services.postgresql.enableTCPIP;
+        message = "Panettone can only connect to the postgresql database over TCP";
+      }
+      {
+        assertion =
+          cfg.dbHost != "localhost" || (lib.any
+            (user: user.name == cfg.dbUser)
+            config.services.postgresql.ensureUsers);
+        message = "Panettone requires a database user";
+      }
+      {
+        assertion =
+          cfg.dbHost != "localhost" || (lib.any
+            (db: db == cfg.dbName)
+            config.services.postgresql.ensureDatabases);
+        message = "Panettone requires a database";
+      }];
 
     systemd.services.panettone = {
       wantedBy = [ "multi-user.target" ];
@@ -100,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/paroxysm.nix b/ops/modules/paroxysm.nix
index cd9cd3866e..070e7623db 100644
--- a/ops/modules/paroxysm.nix
+++ b/ops/modules/paroxysm.nix
@@ -3,7 +3,8 @@
 let
   cfg = config.services.depot.paroxysm;
   description = "TVL's majestic IRC bot";
-in {
+in
+{
   options.services.depot.paroxysm.enable = lib.mkEnableOption description;
 
   config = lib.mkIf cfg.enable {
diff --git a/ops/modules/quassel.nix b/ops/modules/quassel.nix
index 9c8692629a..6acb0615f4 100644
--- a/ops/modules/quassel.nix
+++ b/ops/modules/quassel.nix
@@ -8,7 +8,8 @@ let
     enableDaemon = true;
     withKDE = false;
   };
-in {
+in
+{
   options.services.depot.quassel = with lib; {
     enable = mkEnableOption "Quassel IRC daemon";
 
@@ -42,6 +43,8 @@ in {
   };
 
   config = with lib; mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = [ cfg.port ];
+
     systemd.services.quassel = {
       description = "Quassel IRC daemon";
       wantedBy = [ "multi-user.target" ];
@@ -52,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}"
       ];
 
@@ -61,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";
       };
     };
 
@@ -70,7 +77,7 @@ in {
         group = "quassel";
       };
 
-      groups.quassel = {};
+      groups.quassel = { };
     };
   };
 }
diff --git a/ops/modules/restic.nix b/ops/modules/restic.nix
index 1aacf68973..8695396035 100644
--- a/ops/modules/restic.nix
+++ b/ops/modules/restic.nix
@@ -14,7 +14,8 @@ let
     inherit default;
     type = lib.types.str;
   };
-in {
+in
+{
   options.services.depot.restic = {
     enable = lib.mkEnableOption description;
     bucketEndpoint = mkStringOption "objects.dc-sto1.glesys.net";
diff --git a/ops/modules/smtprelay.nix b/ops/modules/smtprelay.nix
index 106593fe39..f6ce262175 100644
--- a/ops/modules/smtprelay.nix
+++ b/ops/modules/smtprelay.nix
@@ -27,8 +27,9 @@ let
   prepareArgs = args:
     concatStringsSep " "
       (attrValues (mapAttrs (key: value: "-${key} \"${toString value}\"")
-                            (args // overrideArgs)));
-in {
+        (args // overrideArgs)));
+in
+{
   options.services.depot.smtprelay = {
     enable = mkEnableOption description;
 
@@ -39,7 +40,7 @@ in {
 
     secretsFile = mkOption {
       type = types.str;
-      default = "/run/agenix/smtprelay";
+      default = config.age.secretsDir + "/smtprelay";
     };
   };
 
diff --git a/ops/modules/sourcegraph.nix b/ops/modules/sourcegraph.nix
index a72cd75d47..cbf836ab64 100644
--- a/ops/modules/sourcegraph.nix
+++ b/ops/modules/sourcegraph.nix
@@ -4,7 +4,8 @@
 
 let
   cfg = config.services.depot.sourcegraph;
-in {
+in
+{
   options.services.depot.sourcegraph = with lib; {
     enable = mkEnableOption "SourceGraph code search engine";
 
@@ -34,7 +35,7 @@ in {
     };
 
     virtualisation.oci-containers.containers.sourcegraph = {
-      image = "sourcegraph/server:3.31.2";
+      image = "sourcegraph/server:3.40.0";
 
       ports = [
         "127.0.0.1:${toString cfg.port}:7080"
@@ -51,7 +52,8 @@ in {
       # Sourcegraph needs a higher nofile limit, it logs warnings
       # otherwise (unclear whether it actually affects the service).
       extraOptions = [
-        "--ulimit" "nofile=10000:10000"
+        "--ulimit"
+        "nofile=10000:10000"
       ];
     };
   };
diff --git a/ops/modules/tvl-buildkite.nix b/ops/modules/tvl-buildkite.nix
index aaeb5a0f75..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
   '';
@@ -22,7 +22,8 @@ let
     echo 'username=buildkite'
     echo "password=$(jq -r '.gerritPassword' /run/agenix/buildkite-besadii-config)"
   '';
-in {
+in
+{
   options.services.depot.buildkite = {
     enable = lib.mkEnableOption description;
     agentCount = lib.mkOption {
@@ -33,39 +34,47 @@ in {
 
   config = lib.mkIf cfg.enable {
     # Run the Buildkite agents using the default upstream module.
-    services.buildkite-agents = builtins.listToAttrs (map (n: rec {
-      name = "whitby-${toString n}";
-      value = {
-        inherit name;
-        enable = true;
-        tokenPath = "/run/agenix/buildkite-agent-token";
-        hooks.post-command = "${buildkiteHooks}/bin/post-command";
+    services.buildkite-agents = builtins.listToAttrs (map
+      (n: rec {
+        name = "whitby-${toString n}";
+        value = {
+          inherit name;
+          enable = true;
+          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
-          coreutils
-          credentialHelper
-          curl
-          git
-          gnutar
-          gzip
-          jq
-          nix
-        ];
-      };
-    }) agents);
+          runtimePackages = with pkgs; [
+            bash
+            coreutils
+            credentialHelper
+            curl
+            git
+            gnutar
+            gzip
+            jq
+            nix
+          ];
+        };
+      })
+      agents);
 
     # Set up a group for all Buildkite agent users
     users = {
-      groups.buildkite-agents = {};
-      users = builtins.listToAttrs (map (n: rec {
-        name = "buildkite-agent-whitby-${toString n}";
-        value = {
-          isSystemUser = true;
-          group = lib.mkForce "buildkite-agents";
-          extraGroups = [ name "docker" ];
-        };
-      }) agents);
+      groups.buildkite-agents = { };
+      users = builtins.listToAttrs (map
+        (n: rec {
+          name = "buildkite-agent-whitby-${toString n}";
+          value = {
+            isSystemUser = true;
+            group = lib.mkForce "buildkite-agents";
+            extraGroups = [ name "docker" ];
+          };
+        })
+        agents);
     };
   };
 }
diff --git a/ops/modules/tvl-cache.nix b/ops/modules/tvl-cache.nix
index 4d574821df..683818d103 100644
--- a/ops/modules/tvl-cache.nix
+++ b/ops/modules/tvl-cache.nix
@@ -6,12 +6,12 @@
   };
 
   config = lib.mkIf config.tvl.cache.enable {
-    nix = {
-      binaryCachePublicKeys = [
+    nix.settings = {
+      trusted-public-keys = [
         "cache.tvl.su:kjc6KOMupXc1vHVufJUoDUYeLzbwSr9abcAKdn/U1Jk="
       ];
 
-      binaryCaches = [
+      substituters = [
         "https://cache.tvl.su"
       ];
     };
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-slapd/default.nix b/ops/modules/tvl-slapd/default.nix
index dbcf139338..c08cd30f16 100644
--- a/ops/modules/tvl-slapd/default.nix
+++ b/ops/modules/tvl-slapd/default.nix
@@ -26,7 +26,8 @@ let
 
   inherit (depot.ops) users;
 
-in {
+in
+{
   services.openldap = {
     enable = true;
 
@@ -34,7 +35,7 @@ in {
       "olcDatabase={1}mdb".attrs = {
         objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
         olcDatabase = "{1}mdb";
-        olcDbDirectory = "/var/lib/openldap";
+        olcDbDirectory = "/var/lib/openldap/db";
         olcSuffix = "dc=tvl,dc=fyi";
         olcAccess = "to *  by * read";
         olcRootDN = "cn=admin,dc=tvl,dc=fyi";
@@ -43,12 +44,12 @@ in {
 
       "cn=module{0}".attrs = {
         objectClass = "olcModuleList";
-        olcModuleLoad = "pw-argon2";
+        olcModuleLoad = "argon2";
       };
 
       "cn=schema".includes =
         map (schema: "${pkgs.openldap}/etc/schema/${schema}.ldif")
-            [ "core" "cosine" "inetorgperson" "nis" ];
+          [ "core" "cosine" "inetorgperson" "nis" ];
     };
 
     # Contents are immutable at runtime, and adding user accounts etc.
diff --git a/ops/modules/tvl-users.nix b/ops/modules/tvl-users.nix
new file mode 100644
index 0000000000..ea83b435f4
--- /dev/null
+++ b/ops/modules/tvl-users.nix
@@ -0,0 +1,83 @@
+# Standard NixOS users for TVL machines, as well as configuration that
+# should following along when they are added to a machine.
+{ depot, pkgs, ... }:
+
+{
+  users = {
+    users.tazjin = {
+      isNormalUser = true;
+      extraGroups = [ "git" "wheel" ];
+      shell = pkgs.fish;
+      openssh.authorizedKeys.keys = depot.users.tazjin.keys.all;
+    };
+
+    users.lukegb = {
+      isNormalUser = true;
+      extraGroups = [ "git" "wheel" ];
+      openssh.authorizedKeys.keys = depot.users.lukegb.keys.all;
+    };
+
+    users.aspen = {
+      isNormalUser = true;
+      extraGroups = [ "git" "wheel" ];
+      openssh.authorizedKeys.keys = [ depot.users.aspen.keys.whitby ];
+    };
+
+    users.edef = {
+      isNormalUser = true;
+      extraGroups = [ "git" ];
+      openssh.authorizedKeys.keys = depot.users.edef.keys.all;
+    };
+
+    users.qyliss = {
+      isNormalUser = true;
+      description = "Alyssa Ross";
+      extraGroups = [ "git" ];
+      openssh.authorizedKeys.keys = depot.users.qyliss.keys.all;
+    };
+
+    users.eta = {
+      isNormalUser = true;
+      extraGroups = [ "git" ];
+      openssh.authorizedKeys.keys = depot.users.eta.keys.whitby;
+    };
+
+    users.cynthia = {
+      isNormalUser = true; # I'm normal OwO :3
+      extraGroups = [ "git" ];
+      openssh.authorizedKeys.keys = depot.users.cynthia.keys.all;
+    };
+
+    users.firefly = {
+      isNormalUser = true;
+      extraGroups = [ "git" ];
+      openssh.authorizedKeys.keys = depot.users.firefly.keys.whitby;
+    };
+
+    users.sterni = {
+      isNormalUser = true;
+      extraGroups = [ "git" "wheel" ];
+      openssh.authorizedKeys.keys = depot.users.sterni.keys.all;
+    };
+
+    users.flokli = {
+      isNormalUser = true;
+      extraGroups = [ "git" "wheel" ];
+      openssh.authorizedKeys.keys = depot.users.flokli.keys.all;
+    };
+  };
+
+  programs.fish.enable = true;
+
+  environment.systemPackages = with pkgs; [
+    alacritty.terminfo
+    foot.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/base.nix b/ops/modules/www/base.nix
index cfa9bf0bc6..50fceff0fa 100644
--- a/ops/modules/www/base.nix
+++ b/ops/modules/www/base.nix
@@ -2,6 +2,11 @@
 
 {
   config = {
+    security.acme = {
+      acceptTerms = true;
+      defaults.email = "letsencrypt@tvl.su";
+    };
+
     services.nginx = {
       enable = true;
       enableReload = true;
@@ -10,31 +15,27 @@
       recommendedGzipSettings = true;
       recommendedProxySettings = true;
 
+      commonHttpConfig = ''
+        log_format json_combined escape=json
+        '{'
+            '"remote_addr":"$remote_addr",'
+            '"method":"$request_method",'
+            '"host":"$host",'
+            '"uri":"$request_uri",'
+            '"status":$status,'
+            '"request_size":$request_length,'
+            '"response_size":$body_bytes_sent,'
+            '"response_time":$request_time,'
+            '"referrer":"$http_referer",'
+            '"user_agent":"$http_user_agent"'
+        '}';
+
+        access_log syslog:server=unix:/dev/log,nohostname json_combined;
+      '';
+
       appendHttpConfig = ''
         add_header Permissions-Policy "interest-cohort=()";
       '';
     };
-
-    # NixOS 20.03 broke nginx and I can't be bothered to debug it
-    # anymore, all solution attempts have failed, so here's a
-    # brute-force fix.
-    #
-    # TODO(tazjin): Find a link to the upstream issue and see if
-    # they've sorted it after ~20.09
-    systemd.services.fix-nginx = {
-      script = "${pkgs.coreutils}/bin/chown -f -R nginx: /var/spool/nginx /var/cache/nginx";
-
-      serviceConfig = {
-        User = "root";
-        Type = "oneshot";
-      };
-    };
-
-    systemd.timers.fix-nginx = {
-      wantedBy = [ "multi-user.target" ];
-      timerConfig = {
-        OnCalendar = "minutely";
-      };
-    };
   };
 }
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 4c182d34f2..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.git-serving.joshPort};
+            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/self-redirect.nix b/ops/modules/www/self-redirect.nix
new file mode 100644
index 0000000000..5bf1627be9
--- /dev/null
+++ b/ops/modules/www/self-redirect.nix
@@ -0,0 +1,27 @@
+# Redirect the hostname of a machine to its configuration in a web
+# browser.
+#
+# Works by convention, assuming that the machine has its configuration
+# at //ops/machines/${hostname}.
+{ config, ... }:
+
+let
+  host = "${config.networking.hostName}.${config.networking.domain}";
+in
+{
+  imports = [
+    ./base.nix
+  ];
+
+  config.services.nginx.virtualHosts."${host}" = {
+    serverName = host;
+    addSSL = true; # SSL is not forced on these redirects
+    enableACME = true;
+
+    extraConfig = ''
+      location = / {
+        return 302 https://at.tvl.fyi/?q=%2F%2Fops%2Fmachines%2F${config.networking.hostName};
+      }
+    '';
+  };
+}
diff --git a/ops/modules/www/signup.tvl.fyi.nix b/ops/modules/www/signup.tvl.fyi.nix
new file mode 100644
index 0000000000..1b193f99a9
--- /dev/null
+++ b/ops/modules/www/signup.tvl.fyi.nix
@@ -0,0 +1,19 @@
+{ depot, ... }:
+
+{
+  imports = [
+    ./base.nix
+  ];
+
+  config = {
+    services.nginx.virtualHosts."signup.tvl.fyi" = {
+      root = depot.web.pwcrypt;
+      enableACME = true;
+      forceSSL = true;
+
+      extraConfig = ''
+        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
+      '';
+    };
+  };
+}
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 7d658a5ec4..47eefca2a6 100644
--- a/ops/modules/www/tazj.in.nix
+++ b/ops/modules/www/tazj.in.nix
@@ -11,8 +11,13 @@
       enableACME = true;
       forceSSL = true;
       root = depot.users.tazjin.homepage;
+      serverAliases = [ "www.tazj.in" ];
 
       extraConfig = ''
+        location = /en/rss.xml {
+          return 301 https://tazj.in/feed.atom;
+        }
+
         ${depot.users.tazjin.blog.oldRedirects}
         location /blog/ {
           alias ${depot.users.tazjin.blog.rendered}/;
@@ -24,6 +29,15 @@
           try_files $uri $uri.html $uri/ =404;
         }
 
+        location = /predlozhnik {
+          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/tvl.fyi.nix b/ops/modules/www/tvl.fyi.nix
index f422bb8487..59ee1bc27f 100644
--- a/ops/modules/www/tvl.fyi.nix
+++ b/ops/modules/www/tvl.fyi.nix
@@ -35,7 +35,11 @@
         }
 
         location = /blog {
-          return 302 /;
+          return 302 /#blog;
+        }
+
+        location = /blog/ {
+          return 302 /#blog;
         }
       '';
     };
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/mq_cli/Cargo.lock b/ops/mq_cli/Cargo.lock
index f418d77c34..18fed3621d 100644
--- a/ops/mq_cli/Cargo.lock
+++ b/ops/mq_cli/Cargo.lock
@@ -1,159 +1,168 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "ansi_term"
-version = "0.11.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
 dependencies = [
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi",
 ]
 
 [[package]]
 name = "atty"
 version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 dependencies = [
- "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hermit-abi",
+ "libc",
+ "winapi",
 ]
 
 [[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
 name = "bitflags"
-version = "1.2.1"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "cc"
-version = "1.0.50"
+version = "1.0.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
 
 [[package]]
 name = "cfg-if"
-version = "0.1.10"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "clap"
-version = "2.33.0"
+version = "2.34.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
 dependencies = [
- "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
 ]
 
 [[package]]
 name = "hermit-abi"
-version = "0.1.6"
+version = "0.1.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
 dependencies = [
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
 ]
 
 [[package]]
 name = "libc"
-version = "0.2.66"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
 
 [[package]]
-name = "mq"
-version = "1.0.0"
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
 dependencies = [
- "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "posix_mq 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg",
+]
+
+[[package]]
+name = "mq_cli"
+version = "3773.0.0"
+dependencies = [
+ "clap",
+ "libc",
+ "nix",
+ "posix_mq",
 ]
 
 [[package]]
 name = "nix"
-version = "0.16.1"
+version = "0.23.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "libc",
+ "memoffset",
 ]
 
 [[package]]
 name = "posix_mq"
-version = "0.9.0"
+version = "3771.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f462ad79a99ea13f3ef76d9c271956e924183f5aeb67a8649c8c2b6bdd079da8"
 dependencies = [
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
+ "nix",
 ]
 
 [[package]]
 name = "strsim"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
 
 [[package]]
 name = "textwrap"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
 dependencies = [
- "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width",
 ]
 
 [[package]]
 name = "unicode-width"
-version = "0.1.7"
+version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
 
 [[package]]
 name = "vec_map"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "void"
-version = "1.0.2"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
 
 [[package]]
 name = "winapi"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 dependencies = [
- "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "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"
-
-[metadata]
-"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
-"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
-"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
-"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
-"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
-"checksum nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb"
-"checksum posix_mq 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13ae339e13cc96902a4597a5aab6b76473093969c55d36ba33f6a7bf3268573f"
-"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
-"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
-"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
-"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
-"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/ops/mq_cli/Cargo.toml b/ops/mq_cli/Cargo.toml
index b412d88787..816a370759 100644
--- a/ops/mq_cli/Cargo.toml
+++ b/ops/mq_cli/Cargo.toml
@@ -1,10 +1,14 @@
 [package]
-name = "mq"
-version = "1.0.0"
-authors = ["Vincent Ambo <mail@tazj.in>"]
+name = "mq_cli"
+description = "CLI tool for accessing POSIX message queues (mq_overview(7))"
+license = "MIT"
+version = "3773.0.0"
+authors = ["Vincent Ambo <tazjin@tvl.su>"]
+homepage = "https://cs.tvl.fyi/depot/-/tree/ops/mq_cli"
+repository = "https://code.tvl.fyi/depot.git:/ops/mq_cli.git"
 
 [dependencies]
-clap = "2.33"
+clap = "2.34"
 libc = "0.2"
-nix = "0.16"
-posix_mq = "0.9"
+nix = "0.23"
+posix_mq = "3771.0.0"
diff --git a/ops/mq_cli/README.md b/ops/mq_cli/README.md
index e612553e74..1045de896b 100644
--- a/ops/mq_cli/README.md
+++ b/ops/mq_cli/README.md
@@ -27,5 +27,16 @@ SUBCOMMANDS:
     send       Send a message to a queue
 ```
 
+## Development
+
+Development happens in the [TVL
+monorepo](https://cs.tvl.fyi/depot/-/tree/ops/mq_cli).
+
+Starting from version `3773.0.0`, the version numbers correspond to
+_revisions_ of the TVL repository, available as git refs (e.g.
+`refs/r/3773`).
+
+See the TVL documentation for more information about how to contribute
+to the codebase.
 
 [POSIX message queues]: https://linux.die.net/man/7/mq_overview
diff --git a/ops/mq_cli/src/main.rs b/ops/mq_cli/src/main.rs
index 55ff006429..927993b486 100644
--- a/ops/mq_cli/src/main.rs
+++ b/ops/mq_cli/src/main.rs
@@ -1,36 +1,38 @@
 extern crate clap;
-extern crate posix_mq;
 extern crate libc;
 extern crate nix;
+extern crate posix_mq;
 
-use clap::{App, SubCommand, Arg, ArgMatches, AppSettings};
-use posix_mq::{Name, Queue, Message};
+use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
+use posix_mq::{Message, Name, Queue};
 use std::fs::{read_dir, File};
 use std::io::{self, Read, Write};
 use std::process::exit;
 
 fn run_ls() {
-    let mqueues = read_dir("/dev/mqueue")
-        .expect("Could not read message queues");
+    let mqueues = read_dir("/dev/mqueue").expect("Could not read message queues");
 
     for queue in mqueues {
         let path = queue.unwrap().path();
         let status = {
-            let mut file = File::open(&path)
-                .expect("Could not open queue file");
+            let mut file = File::open(&path).expect("Could not open queue file");
 
             let mut content = String::new();
-            file.read_to_string(&mut content).expect("Could not read queue file");
+            file.read_to_string(&mut content)
+                .expect("Could not read queue file");
 
             content
         };
 
-        let queue_name = path.components().last().unwrap()
+        let queue_name = path
+            .components()
+            .last()
+            .unwrap()
             .as_os_str()
             .to_string_lossy();
 
         println!("/{}: {}", queue_name, status)
-    };
+    }
 }
 
 fn run_inspect(queue_name: &str) {
@@ -47,8 +49,7 @@ fn run_create(cmd: &ArgMatches) {
         set_rlimit(rlimit.parse().expect("Invalid rlimit value"));
     }
 
-    let name = Name::new(cmd.value_of("queue").unwrap())
-        .expect("Invalid queue name");
+    let name = Name::new(cmd.value_of("queue").unwrap()).expect("Invalid queue name");
 
     let max_pending: i64 = cmd.value_of("max-pending").unwrap().parse().unwrap();
     let max_size: i64 = cmd.value_of("max-size").unwrap().parse().unwrap();
@@ -56,11 +57,11 @@ fn run_create(cmd: &ArgMatches) {
     let queue = Queue::create(name, max_pending, max_size * 1024);
 
     match queue {
-        Ok(_)  => println!("Queue created successfully"),
+        Ok(_) => println!("Queue created successfully"),
         Err(e) => {
             writeln!(io::stderr(), "Could not create queue: {}", e).ok();
             exit(1);
-        },
+        }
     };
 }
 
@@ -120,7 +121,12 @@ fn run_rlimit() {
     };
 
     if errno != 0 {
-        writeln!(io::stderr(), "Could not get message queue rlimit: {}", errno).ok();
+        writeln!(
+            io::stderr(),
+            "Could not get message queue rlimit: {}",
+            errno
+        )
+        .ok();
     } else {
         println!("Message queue rlimit:");
         println!("Current limit: {}", rlimit.rlim_cur);
@@ -170,16 +176,20 @@ fn main() {
         .about("Create a new queue")
         .arg(&queue_arg)
         .arg(&rlimit_arg)
-        .arg(Arg::with_name("max-size")
-            .help("maximum message size (in kB)")
-            .long("max-size")
-            .required(true)
-            .takes_value(true))
-        .arg(Arg::with_name("max-pending")
-            .help("maximum # of pending messages")
-            .long("max-pending")
-            .required(true)
-            .takes_value(true));
+        .arg(
+            Arg::with_name("max-size")
+                .help("maximum message size (in kB)")
+                .long("max-size")
+                .required(true)
+                .takes_value(true),
+        )
+        .arg(
+            Arg::with_name("max-pending")
+                .help("maximum # of pending messages")
+                .long("max-pending")
+                .required(true)
+                .takes_value(true),
+        );
 
     let receive = SubCommand::with_name("receive")
         .about("Receive a message from a queue")
@@ -188,9 +198,11 @@ fn main() {
     let send = SubCommand::with_name("send")
         .about("Send a message to a queue")
         .arg(&queue_arg)
-        .arg(Arg::with_name("message")
-            .help("the message to send")
-            .required(true));
+        .arg(
+            Arg::with_name("message")
+                .help("the message to send")
+                .required(true),
+        );
 
     let rlimit = SubCommand::with_name("rlimit")
         .about("Get the message queue rlimit")
@@ -211,13 +223,13 @@ fn main() {
     match matches.subcommand() {
         ("ls", _) => run_ls(),
         ("inspect", Some(cmd)) => run_inspect(cmd.value_of("queue").unwrap()),
-        ("create",  Some(cmd)) => run_create(cmd),
+        ("create", Some(cmd)) => run_create(cmd),
         ("receive", Some(cmd)) => run_receive(cmd.value_of("queue").unwrap()),
-        ("send",    Some(cmd)) => run_send(
+        ("send", Some(cmd)) => run_send(
             cmd.value_of("queue").unwrap(),
-            cmd.value_of("message").unwrap()
+            cmd.value_of("message").unwrap(),
         ),
-        ("rlimit",  _) => run_rlimit(),
+        ("rlimit", _) => run_rlimit(),
         _ => unimplemented!(),
     }
 }
diff --git a/ops/nixos.nix b/ops/nixos.nix
index 66ca188c5b..1442d89b30 100644
--- a/ops/nixos.nix
+++ b/ops/nixos.nix
@@ -7,10 +7,18 @@ in rec {
   baseModule = { ... }: {
     # Ensure that pkgs == third_party.nix
     nixpkgs.pkgs = depot.third_party.nixpkgs;
-    nix.nixPath = [
-      "nixos=${pkgs.path}"
-      "nixpkgs=${pkgs.path}"
-    ];
+    nix.nixPath =
+      let
+        # Due to nixpkgsBisectPath, pkgs.path is not always in the nix store
+        nixpkgsStorePath =
+          if lib.hasPrefix builtins.storeDir (toString pkgs.path)
+          then builtins.storePath pkgs.path # nixpkgs is already in the store
+          else pkgs.path; # we need to dump nixpkgs to the store either way
+      in
+      [
+        ("nixos=" + nixpkgsStorePath)
+        ("nixpkgs=" + nixpkgsStorePath)
+      ];
   };
 
   nixosFor = configuration: (depot.third_party.nixos {
@@ -32,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
@@ -50,5 +61,7 @@ in rec {
 
   # Systems that should be built in CI
   whitbySystem = (nixosFor depot.ops.machines.whitby).system;
-  meta.targets = [ "whitbySystem" ];
+  sandunySystem = (nixosFor depot.ops.machines.sanduny).system;
+  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 b6941ba38a..5eff622671 100644
--- a/ops/pipelines/depot.nix
+++ b/ops/pipelines/depot.nix
@@ -3,22 +3,14 @@
 { 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)
+    parentTargetMap =
+      if (externalArgs ? parentTargetMap)
       then builtins.fromJSON (builtins.readFile externalArgs.parentTargetMap)
-      else {};
+      else { };
 
     postBuildSteps = [
       # After successful builds, create a gcroot for builds on canon.
@@ -40,7 +32,8 @@ let
   };
 
   drvmap = depot.nix.buildkite.mkDrvmap depot.ci.targets;
-in pkgs.runCommandNoCC "depot-pipeline" {} ''
+in
+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 23a1fba4f2..af4f9d784e 100644
--- a/ops/pipelines/static-pipeline.yaml
+++ b/ops/pipelines/static-pipeline.yaml
@@ -4,6 +4,8 @@
 # If something fails during the creation of the pipeline, the fallback
 # is executed instead which will simply report an error to Gerrit.
 ---
+env:
+  BUILDKITE_TOKEN_PATH: /run/agenix/buildkite-graphql-token
 steps:
   # Run pipeline for tvl-kit when new commits arrive on canon. Since
   # it is not part of the depot build tree, this is a useful
@@ -15,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.
   #
@@ -23,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: |
@@ -32,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,
@@ -50,7 +69,11 @@ steps:
         PIPELINE_ARGS="--arg parentTargetMap tmp/parent-target-map.json"
       fi
 
-      nix-build -A ops.pipelines.depot -o pipeline --show-trace $$PIPELINE_ARGS
+      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
 
       # Steps need to be uploaded in reverse order because pipeline
       # upload prepends instead of appending.
@@ -85,7 +108,7 @@ steps:
 
       readonly FAILED_JOBS=$(curl 'https://graphql.buildkite.com/v1' \
         --silent \
-        -H "Authorization: Bearer $(cat /run/agenix/buildkite-graphql-token)" \
+        -H "Authorization: Bearer $(cat ${BUILDKITE_TOKEN_PATH})" \
         -d "{\"query\": \"query BuildStatusQuery { build(uuid: \\\"$BUILDKITE_BUILD_ID\\\") { jobs(passed: false) { count } } }\"}" | \
         jq -r '.data.build.jobs.count')
 
@@ -95,8 +118,8 @@ steps:
         exit 1
       fi
 
-  # After duck, on success, upload and run any post-build steps that
-  # were output by the dynamic pipeline.
+  # After duck, on success, upload and run any release steps that were
+  # output by the dynamic pipeline.
   - label: ":arrow_heading_down:"
     depends_on:
       - step: ":duck:"
@@ -106,6 +129,6 @@ steps:
 
       buildkite-agent artifact download "pipeline/*" .
 
-      find ./pipeline -name 'post-chunk-*.json' | tac | while read chunk; do
+      find ./pipeline -name 'release-chunk-*.json' | tac | while read chunk; do
         buildkite-agent pipeline upload $$chunk
       done
diff --git a/ops/posix_mq.rs/Cargo.lock b/ops/posix_mq.rs/Cargo.lock
index fdd0086c4d..dc344613d0 100644
--- a/ops/posix_mq.rs/Cargo.lock
+++ b/ops/posix_mq.rs/Cargo.lock
@@ -1,54 +1,63 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
 [[package]]
 name = "bitflags"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
 
 [[package]]
 name = "cc"
 version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
 
 [[package]]
 name = "cfg-if"
-version = "0.1.10"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "libc"
-version = "0.2.66"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
 
 [[package]]
-name = "nix"
-version = "0.16.1"
+name = "memoffset"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg",
 ]
 
 [[package]]
-name = "posix_mq"
-version = "0.9.0"
+name = "nix"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
 dependencies = [
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "libc",
+ "memoffset",
 ]
 
 [[package]]
-name = "void"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[metadata]
-"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
-"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
-"checksum nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb"
-"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+name = "posix_mq"
+version = "3771.0.0"
+dependencies = [
+ "libc",
+ "nix",
+]
diff --git a/ops/posix_mq.rs/Cargo.toml b/ops/posix_mq.rs/Cargo.toml
index d72e87a3dc..8390b80b86 100644
--- a/ops/posix_mq.rs/Cargo.toml
+++ b/ops/posix_mq.rs/Cargo.toml
@@ -1,11 +1,12 @@
 [package]
 name = "posix_mq"
-version = "0.9.0"
-authors = ["Vincent Ambo <mail@tazj.in>"]
+version = "3771.0.0"
+authors = ["Vincent Ambo <tazjin@tvl.su>"]
 description = "(Higher-level) Rust bindings to POSIX message queues"
 license = "MIT"
-repository = "https://git.tazj.in/tree/ops/posix_mq.rs"
+homepage = "https://cs.tvl.fyi/depot/-/tree/ops/posix_mq.rs"
+repository = "https://code.tvl.fyi/depot.git:/ops/posix_mq.rs.git"
 
 [dependencies]
-nix = "0.16"
+nix = "0.23"
 libc = "0.2"
diff --git a/ops/posix_mq.rs/README.md b/ops/posix_mq.rs/README.md
index 9370c6c087..800d2221e4 100644
--- a/ops/posix_mq.rs/README.md
+++ b/ops/posix_mq.rs/README.md
@@ -1,7 +1,6 @@
 posix_mq
 ========
 
-[![Build Status](https://travis-ci.org/aprilabank/posix_mq.rs.svg?branch=master)](https://travis-ci.org/aprilabank/posix_mq.rs)
 [![crates.io](https://img.shields.io/crates/v/posix_mq.svg)](https://crates.io/crates/posix_mq)
 
 This is a simple, relatively high-level library for the POSIX [message queue API][]. It wraps the lower-level API in a
@@ -29,5 +28,17 @@ queue.send(&message).expect("message sending failed");
 let result = queue.receive().expect("message receiving failed");
 ```
 
+## Development
+
+Development happens in the [TVL
+monorepo](https://cs.tvl.fyi/depot/-/tree/ops/posix_mq.rs).
+
+Starting from version `3771.0.0`, the version numbers correspond to
+_revisions_ of the TVL repository, available as git refs (e.g.
+`refs/r/3771`).
+
+See the TVL documentation for more information about how to contribute
+to the codebase.
+
 [message queue API]: https://linux.die.net/man/7/mq_overview
 [sister library]: https://github.com/aprilabank/posix_mq.kt
diff --git a/ops/posix_mq.rs/src/error.rs b/ops/posix_mq.rs/src/error.rs
index 1ef585c01e..bacd2aeb39 100644
--- a/ops/posix_mq.rs/src/error.rs
+++ b/ops/posix_mq.rs/src/error.rs
@@ -1,8 +1,5 @@
 use nix;
-use std::error;
-use std::fmt;
-use std::io;
-use std::num;
+use std::{error, fmt, io, num};
 
 /// This module implements a simple error type to match the errors that can be thrown from the C
 /// functions as well as some extra errors resulting from internal validations.
@@ -17,7 +14,7 @@ use std::num;
 /// * ENAMETOOLONG: This crate performs name validation
 ///
 /// If an unexpected error is encountered it will be wrapped appropriately and should be reported
-/// as a bug on https://github.com/aprilabank/posix_mq.rs
+/// as a bug on https://b.tvl.fyi
 
 #[derive(Debug)]
 pub enum Error {
@@ -47,13 +44,13 @@ pub enum Error {
 
     // Some other unexpected / unknown error occured. This is probably an error from
     // the nix crate. Bug reports also welcome for this!
-    UnknownInternalError(Option<nix::Error>),
+    UnknownInternalError(),
 }
 
-impl error::Error for Error {
-    fn description(&self) -> &str {
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use Error::*;
-        match *self {
+        f.write_str(match *self {
             // This error contains more sensible description strings already
             InvalidQueueName(e) => e,
             ValueReadingError(_) => "error reading system configuration for message queues",
@@ -67,31 +64,44 @@ impl error::Error for Error {
             QueueNotFound() => "the specified queue could not be found",
             InsufficientMemory() => "insufficient memory to call queue method",
             InsufficientSpace() => "insufficient space to call queue method",
-            ProcessFileDescriptorLimitReached() =>
-                "maximum number of process file descriptors reached",
-            SystemFileDescriptorLimitReached() =>
-                "maximum number of system file descriptors reached",
+            ProcessFileDescriptorLimitReached() => {
+                "maximum number of process file descriptors reached"
+            }
+            SystemFileDescriptorLimitReached() => {
+                "maximum number of system file descriptors reached"
+            }
             UnknownForeignError(_) => "unknown foreign error occured: please report a bug!",
-            UnknownInternalError(_) => "unknown internal error occured: please report a bug!",
-        }
+            UnknownInternalError() => "unknown internal error occured: please report a bug!",
+        })
     }
 }
 
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        // Explicitly import this to gain access to Error::description()
-        use std::error::Error;
-        f.write_str(self.description())
+impl error::Error for Error {
+    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+        match self {
+            Error::ValueReadingError(e) => Some(e),
+            Error::UnknownForeignError(e) => Some(e),
+            _ => None,
+        }
     }
 }
 
 /// This from implementation is used to translate errors from the lower-level
 /// C-calls into sensible Rust errors.
-impl From<nix::Error> for Error {
-    fn from(e: nix::Error) -> Self {
-        match e {
-            nix::Error::Sys(e) => match_errno(e),
-            _ => Error::UnknownInternalError(Some(e)),
+impl From<nix::errno::Errno> for Error {
+    fn from(err: nix::Error) -> Self {
+        use nix::errno::Errno::*;
+        match err {
+            EACCES => Error::PermissionDenied(),
+            EBADF => Error::InvalidQueueDescriptor(),
+            EINTR => Error::QueueCallInterrupted(),
+            EEXIST => Error::QueueAlreadyExists(),
+            EMFILE => Error::ProcessFileDescriptorLimitReached(),
+            ENFILE => Error::SystemFileDescriptorLimitReached(),
+            ENOENT => Error::QueueNotFound(),
+            ENOMEM => Error::InsufficientMemory(),
+            ENOSPC => Error::InsufficientSpace(),
+            _ => Error::UnknownForeignError(err),
         }
     }
 }
@@ -107,24 +117,6 @@ impl From<io::Error> for Error {
 // here because the system is probably seriously broken if those files don't contain numbers.
 impl From<num::ParseIntError> for Error {
     fn from(_: num::ParseIntError) -> Self {
-        Error::UnknownInternalError(None)
-    }
-}
-
-
-fn match_errno(err: nix::errno::Errno) -> Error {
-    use nix::errno::Errno::*;
-
-    match err {
-        EACCES => Error::PermissionDenied(),
-        EBADF  => Error::InvalidQueueDescriptor(),
-        EINTR  => Error::QueueCallInterrupted(),
-        EEXIST => Error::QueueAlreadyExists(),
-        EMFILE => Error::ProcessFileDescriptorLimitReached(),
-        ENFILE => Error::SystemFileDescriptorLimitReached(),
-        ENOENT => Error::QueueNotFound(),
-        ENOMEM => Error::InsufficientMemory(),
-        ENOSPC => Error::InsufficientSpace(),
-        _      => Error::UnknownForeignError(err),
+        Error::UnknownInternalError()
     }
 }
diff --git a/ops/posix_mq.rs/src/lib.rs b/ops/posix_mq.rs/src/lib.rs
index 057601eccf..ed35fb03be 100644
--- a/ops/posix_mq.rs/src/lib.rs
+++ b/ops/posix_mq.rs/src/lib.rs
@@ -1,5 +1,5 @@
-extern crate nix;
 extern crate libc;
+extern crate nix;
 
 use error::Error;
 use libc::mqd_t;
@@ -8,8 +8,8 @@ use nix::sys::stat;
 use std::ffi::CString;
 use std::fs::File;
 use std::io::Read;
-use std::string::ToString;
 use std::ops::Drop;
+use std::string::ToString;
 
 pub mod error;
 
@@ -33,16 +33,20 @@ impl Name {
         // have tried just using '/' as a queue name.
         if string.len() == 1 {
             return Err(Error::InvalidQueueName(
-                "Queue name must be a slash followed by one or more characters"
+                "Queue name must be a slash followed by one or more characters",
             ));
         }
 
         if string.len() > 255 {
-            return Err(Error::InvalidQueueName("Queue name must not exceed 255 characters"));
+            return Err(Error::InvalidQueueName(
+                "Queue name must not exceed 255 characters",
+            ));
         }
 
         if string.matches('/').count() > 1 {
-            return Err(Error::InvalidQueueName("Queue name can not contain more than one slash"));
+            return Err(Error::InvalidQueueName(
+                "Queue name can not contain more than one slash",
+            ));
         }
 
         // TODO: What error is being thrown away here? Is it possible?
@@ -97,16 +101,9 @@ impl Queue {
             flags
         };
 
-        let attr = mqueue::MqAttr::new(
-            0, max_pending, max_size, 0
-        );
+        let attr = mqueue::MqAttr::new(0, max_pending, max_size, 0);
 
-        let queue_descriptor = mqueue::mq_open(
-            &name.0,
-            oflags,
-            default_mode(),
-            Some(&attr),
-        )?;
+        let queue_descriptor = mqueue::mq_open(&name.0, oflags, default_mode(), Some(&attr))?;
 
         Ok(Queue {
             name,
@@ -121,12 +118,7 @@ impl Queue {
         // No extra flags need to be constructed as the default is to open and fail if the
         // queue does not exist yet - which is what we want here.
         let oflags = mqueue::MQ_OFlag::O_RDWR;
-        let queue_descriptor = mqueue::mq_open(
-            &name.0,
-            oflags,
-            default_mode(),
-            None,
-        )?;
+        let queue_descriptor = mqueue::mq_open(&name.0, oflags, default_mode(), None)?;
 
         let attr = mq_getattr(queue_descriptor)?;
 
@@ -151,16 +143,9 @@ impl Queue {
 
         let default_pending = read_i64_from_file(MSG_DEFAULT)?;
         let default_size = read_i64_from_file(MSGSIZE_DEFAULT)?;
-        let attr = mqueue::MqAttr::new(
-            0, default_pending, default_size, 0
-        );
+        let attr = mqueue::MqAttr::new(0, default_pending, default_size, 0);
 
-        let queue_descriptor = mqueue::mq_open(
-            &name.0,
-            oflags,
-            default_mode(),
-            Some(&attr),
-        )?;
+        let queue_descriptor = mqueue::mq_open(&name.0, oflags, default_mode(), Some(&attr))?;
 
         let actual_attr = mq_getattr(queue_descriptor)?;
 
@@ -187,11 +172,8 @@ impl Queue {
             return Err(Error::MessageSizeExceeded());
         }
 
-        mqueue::mq_send(
-            self.queue_descriptor,
-            msg.data.as_ref(),
-            msg.priority,
-        ).map_err(|e| e.into())
+        mqueue::mq_send(self.queue_descriptor, msg.data.as_ref(), msg.priority)
+            .map_err(|e| e.into())
     }
 
     /// Receive a message from the message queue.
@@ -200,11 +182,7 @@ impl Queue {
         let mut data: Vec<u8> = vec![0; self.max_size as usize];
         let mut priority: u32 = 0;
 
-        let msg_size = mqueue::mq_receive(
-            self.queue_descriptor,
-            data.as_mut(),
-            &mut priority,
-        )?;
+        let msg_size = mqueue::mq_receive(self.queue_descriptor, data.as_mut(), &mut priority)?;
 
         data.truncate(msg_size);
         Ok(Message { data, priority })
@@ -261,9 +239,9 @@ fn read_i64_from_file(name: &str) -> Result<i64, Error> {
 /// To work around it, this method calls the C-function directly.
 fn mq_getattr(mqd: mqd_t) -> Result<libc::mq_attr, Error> {
     use std::mem;
-    let mut attr = unsafe { mem::uninitialized::<libc::mq_attr>() };
-    let res = unsafe { libc::mq_getattr(mqd, &mut attr) };
+    let mut attr = mem::MaybeUninit::<libc::mq_attr>::uninit();
+    let res = unsafe { libc::mq_getattr(mqd, attr.as_mut_ptr()) };
     nix::errno::Errno::result(res)
-        .map(|_| attr)
+        .map(|_| unsafe { attr.assume_init() })
         .map_err(|e| e.into())
 }
diff --git a/ops/posix_mq.rs/src/tests.rs b/ops/posix_mq.rs/src/tests.rs
index 7a08876aea..1f4ea9a58d 100644
--- a/ops/posix_mq.rs/src/tests.rs
+++ b/ops/posix_mq.rs/src/tests.rs
@@ -4,8 +4,7 @@ use super::*;
 fn test_open_delete() {
     // Simple test with default queue settings
     let name = Name::new("/test-queue").unwrap();
-    let queue = Queue::open_or_create(name)
-        .expect("Opening queue failed");
+    let queue = Queue::open_or_create(name).expect("Opening queue failed");
 
     let message = Message {
         data: "test-message".as_bytes().to_vec(),
diff --git a/ops/secrets/besadii.age b/ops/secrets/besadii.age
index b78f02da8f..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 35e592ee51..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 e1c30b785b..6ebf3efca7 100644
--- a/ops/secrets/buildkite-graphql-token.age
+++ b/ops/secrets/buildkite-graphql-token.age
@@ -1,15 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw eGKM1q69QdToZ9wbtsdAgAfGHOsVrc/IJ4IFbHfoeAA
-eogaENxdhqW/2H+FM7SPWgN1UcXPzUTx3tYiVU9K8Rk
--> ssh-ed25519 CpJBgQ v00XK32Div5ddrWPdzjv5ZFPECtW14rPv3G6iFvXUFI
-OQAJaolWVUiVXTK14b9Q5ZTYR4YQL2e6Ye5TY4Xxq0Y
--> ssh-ed25519 aXKGcg ieOvBBSHPSP7k05I5unpRn6+S4K9NfRqwUb5s0XM0js
-z8Q+psAM7Zj02M7m3KNNjSTLmiLH9S+nOzQE5xz1nr0
--> ssh-ed25519 OkGqLg OKzXlZJyHE73V36WVZ2KhvFhif3HZtZDjjBBv5g3hyA
-ilL9pohUoCXfNi1jLekPx35Iu3dGOBAe1H2JFXrKHTU
--> VQDa2-grease 'HsH ^-&
-YuO3YgYZ3Q1CjlIayGFg1Y9zplKgzqR0mZeZlyaOJDMHDrWSOaWRPXjFVU/s2EvP
-MECrypRbNRaHEdPSY7udi1q5cVBPNj3Dci5uiq9t
---- HKTtOZJq9MSAhr3x1eUhk6yFJU3y7TCPilXPhMNfbwA
-1“0‘ๆ?๑ืaณ€ฌ“ฅเ๓9๎ฝก4๙bWyำŒ†้
-ญjbจ็nใ๐บ6	Ppีšป–lว'YธFy๗๗๑
\ 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
new file mode 100644
index 0000000000..c9aa988277
--- /dev/null
+++ 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 ab51ccc68e..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 c44c77f583..2cec1f7f36 100644
--- a/ops/secrets/clbot.age
+++ b/ops/secrets/clbot.age
@@ -1,14 +1,15 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw sjFTLxJ9JArZ/GU/R/hqRVgX73x3sDO4uNdVrRrZpXE
-cbMS1tn4+diLX4Hf1Pe0XBYvJH5G3ueZIIA+3KImq3Y
--> ssh-ed25519 CpJBgQ 3yeOIq2DxFqr8NW4VpdaUVoEmwvQayWThPzoMo9UCmY
-xLyNilVdqXZ6WjAbT9NDFIssFc4564C/13z4w8WGnpU
--> ssh-ed25519 aXKGcg peKlfil+osni6uHra2unBeQM5MBeK9TVmBg3BpozVy0
-KsKJ5yQQFWGbuiANV8uOck3sSIW82v/JKqLEuLJRsAo
--> ssh-ed25519 OkGqLg Jo5YHWYNkou8JIBKrSrRJBG1VMdStmDqe/S62hdo+Ac
-U5zaBxJ6TKsuaB3vKS7+03vBJLe+nAWMZ6fSlwF+VQs
--> 8SA_}x-grease
-J/zFiD0MDxVK5FDCv4fmA6sawl8gQZcPg0h1NunSjVnBUPNXx9FZylONpu9M56y8
-Z2JJ
---- bR5Pl8ZiMNPIgx/n6ozwOkikLE9E6GWEK2SVIMUlbvI
-gุyฤ†หx_ˆๅ๘ใแ้กn๘2๊	uT๔ฯZาฤRำ๔G๒7,iฃตS%ZSแKอQdๆ.`ฏ,y(๐Yn‰9cฌ„ภ…	
\ 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
new file mode 100644
index 0000000000..5e8ce94d5d
--- /dev/null
+++ 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 68dd1e7e2e..0000000000
--- a/ops/secrets/gerrit-queue.age
+++ /dev/null
Binary files differdiff --git a/ops/secrets/gerrit-secrets.age b/ops/secrets/gerrit-secrets.age
index 02a3c66b53..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 ad503dc32a..eef349d64c 100644
--- a/ops/secrets/grafana.age
+++ b/ops/secrets/grafana.age
@@ -1,13 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw CrJGrkztUpn+XkED1hn4Clr/oBNrer9J+/fdqDhgx18
-VWENh02k4HTkhDS2F219vrCUVuxXFOCPsCW+8eeZHs4
--> ssh-ed25519 CpJBgQ 8Lm14o93CEh/aerPtMiStKYtqF/HdgJD05uRRegLgUs
-b0H5XBOe4nepmGzl646Ar0XAazzHAJeTLCCGUVaZyW0
--> ssh-ed25519 aXKGcg SKWLHNM0WeFJoGlOPbI6v7CebdSK3qAmQ6kMW5YbIz4
-kQD7Oh9mQeCXyXzOc1kVI8ShE0J89TzuZBOboaQn7sE
--> ssh-ed25519 OkGqLg ablfqKN1GYY3GWGCHGtciRFJwO4e0kbcS75Kaj+elUA
-PQPeRVzV/Yi0lxI7U+lNbCpeatymazj7GjQLhmL4YI8
--> gse~-grease
-I9X7cHnmfbsnu/4AeVVtTRlbguJDylrAlCOqTOt11Gtg/Ft2fnZZTOmsKo8
---- 3xk3ls7SR7s394FtfqLwxgUDjTPMjnhLz79ClvIm4pE
-ผyF๔zผธฤH๑ุฉูมทใ*Mพศ\ซ26I`koฮƒธ&baาWŸvM™ฃ.ดฎีjใฃ‘มFศ;ZํลNึ*๋Pฒ
•_JŠ๓c…_‰(Sชjๆdน๏8Fแ
\ 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 5a45efa7cc..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
new file mode 100644
index 0000000000..c58773f36b
--- /dev/null
+++ b/ops/secrets/journaldriver.age
Binary files differdiff --git a/ops/secrets/keycloak-db.age b/ops/secrets/keycloak-db.age
index 5942bf24c2..54194df183 100644
--- a/ops/secrets/keycloak-db.age
+++ b/ops/secrets/keycloak-db.age
Binary files differdiff --git a/ops/secrets/mkSecrets.nix b/ops/secrets/mkSecrets.nix
index 4e40112b96..c99130835f 100644
--- a/ops/secrets/mkSecrets.nix
+++ b/ops/secrets/mkSecrets.nix
@@ -22,6 +22,6 @@ in
 
 defun [ path (attrs agenixSecret) (attrs any) ]
   (path: secrets:
-    depot.nix.readTree.drvTargets
-      # Import each secret into the Nix store
-      (builtins.mapAttrs (name: _: "${path}/${name}") secrets))
+  depot.nix.readTree.drvTargets
+    # Import each secret into the Nix store
+    (builtins.mapAttrs (name: _: "${path}/${name}") secrets))
diff --git a/ops/secrets/nix-cache-priv.age b/ops/secrets/nix-cache-priv.age
index 4a16897eb2..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 692d869015..ae06f49d69 100644
--- a/ops/secrets/nix-cache-pub.age
+++ b/ops/secrets/nix-cache-pub.age
@@ -1,13 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw 2wWiYCk+TcJdGdiT+YWVvv1FZ28EJYykwseyiZ9pkzs
-AMvMQQsWe3nar2TQM+wcyD2PEKlE9PeSx8G2ufJzEzI
--> ssh-ed25519 CpJBgQ SpGruCznXleG0wmFMUTGJf7VNGKLEYqeQb/mv+axKxM
-SL4MTYEiOFgp6+90Fp3QFnSzFUfMWxNF2OHdH3Q+uy0
--> ssh-ed25519 aXKGcg wWO1kn2tUlBZoMFsO1JrVhyqJCfv1BNhoVfKBwfidmA
-A3PAoWzbJWSlIKxGYsUEvuwRbDvRTjZYUdeSi+LQa1M
--> ssh-ed25519 OkGqLg 2usxSwcnF2tZbJt6R7M+psTSW2M5HcZgr51t47D01GI
-HVGRSasPX9/I9E9oZhhMd6hVK/ga3n/UYzRAe2CjRqI
--> /oh-grease v* Qu8SiS 2
-5dc
---- 59MLx4Yl2G9G8QjEp+gOrKBPjCqm/ntgg8guQICu/x0
-ู`เ	8DJฦ]๎sำP๋ฑRwa!โ7k47<i:'?)ฟึะีฉ๔๎‡ๅูฟSธ}๒Rมop)่_wIKpไ:SŒ5๒0kฝ๙	žฮj˜ํ
\ 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 baddeef1e3..0000000000
--- a/ops/secrets/oauth2_proxy.age
+++ /dev/null
@@ -1,14 +0,0 @@
-age-encryption.org/v1
--> ssh-ed25519 dcsaLw 3vCzURGgzn7i3pZp39oSfYy1F331qBDewFgjocK4/nE
-doccb4CZhyrA7jvbuG3i8nowApVGKWfIejJjLeXnb9Q
--> ssh-ed25519 CpJBgQ 4KovCGvA0cBvEkhfyantUCny49hTu4L038xj/ZG2lCg
-o9iaan7jKGYukS4IiTVLV5YqjiycaWyPXyo3x6k8Jhk
--> ssh-ed25519 aXKGcg stGJqj37f0E6S0qJW/r/cYXIoT+l4ERG0c/CMckpS28
-aNP2LcrFe9wLB1dnbJjoUTa8ckpMbR3cJtltDn/8st8
--> ssh-ed25519 OkGqLg UK89eEeI/SOWUaR4jg4rDuKFOkzsf6PgNkcphUCoyj8
-o3WFOhB0B2T1F8mxb5qw25S4r9bYyc4tqwLb/iK0TAY
--> ZcBt-grease P*F$|]1G *a9 ^dTv-Whe K`GVU
-mwq98CjcnoinoAsGUM2PolGrXBZhs9jbUQB8qEAZ7Qtzd6z6BjGoPGr4bjokZQ08
-RwOx9jBmAAFaW9Ak5JX9RBvxu/IIz6xVmQ8a8ev95tA
---- ShfGC5iYYwDC5fXRkZV9Oh1aHJONbdR1EaAp+lrKWUE
-ร๙’t~EชVจถ/ึ–๕E}‚_๛Hš็Rษ7†PsSw>ยฝ์ญZoœD`n)™ทK,T}
ˆ'F[ SณKyฮ7m"ะEฟG3ๆ"ฮ๕ตวljๅ€•๒ู^b[ึมMไlf›pžX๗Cว๐”ฒื]ๅ!N]m๛๖๙ใ
gจทพ0ึ๖€๚!ฐg˜๐ฏ1ญ‡ะโY ฺX
\ No newline at end of file
diff --git a/ops/secrets/owothia.age b/ops/secrets/owothia.age
index 845252dd1d..177ee61383 100644
--- a/ops/secrets/owothia.age
+++ b/ops/secrets/owothia.age
@@ -1,15 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw ZV01yZa6uSpirIxPgW8fLJ3lI/RRb0tRObGey3zlgGE
-cu64HZYAxEL0qbUKcQEGzzQpwkAvXwp6NYYGaoHNwPw
--> ssh-ed25519 CpJBgQ +NoCEPUKCscQxZLdjFI5YwWNiQuj8klra4AceYAOR1A
-xhNGia9flgRDn2QNsklyotwU7nJ9elXV8jMkT8XfUEA
--> ssh-ed25519 aXKGcg MsimFAWS4vN6exoeKA2PVin+82QXzt32oS9iei6f4l4
-i+ph/HZ6a5f9QWorgwt0RFvmV4E4HpGSmkZAqdXhZ68
--> ssh-ed25519 OkGqLg zLXi3YNberKHC7b/La1FdrLgLowjB4wovnXo/ayqeQs
-dYIN5zvmbMsN5yjhVrccjwYqXJHV9zcEJCjTnMIs55g
--> FlqGql'-grease
-g+GgOSpwwnqLywaY4h9wMA2h7buTMM8vYEufiyTOOOSD7ljq1cgBePAoCFluW8UW
-8SDabs5WTRYgqqDnzVkx9V3JeIWJrfiKQj9coLZ1Crx5+YRD9r766eGEvHOC5eat
-432j
---- V/bZkitOabEh8PO3J8dmv/IgycQOF5CmMvGTsHTdmlo
-ฯ๕๕7(‘ฑtฒNูึำยF๚ทgžภสฟZ‡&ooา,ฦฆฒQ1์(^ไน์…K}W‚ู14๏ซ)๕‚D@ืธŸŽ”๐YภฺYi
\ No newline at end of file
+-> ssh-ed25519 dcsaLw 8XtdgZ++/ZqmK4j8CO8oiuskTxjvKhWDK7fet5hbqiM
+Fs4O1vFtQL1JamnuCMPLzfzRPb90nxfXB6OXkyCMoHo
+-> ssh-ed25519 zcCuhA 6PNsPMdRXM77ci+mBQNRxr1oMGDNdlQilpUB0Q5es28
+APw2L/0htM9U0fJ1IUthdkoem/UTM/6NNQrgn4Vmpcs
+-> ssh-ed25519 CpJBgQ ed00il0q23M+3KH6hf5fFPaXGUKcz03Bn01jSoKiB1U
+jEN0Dk2edJBQreAlNE11sx0cI5u1mfFDT11Ev0KJ+gs
+-> ssh-ed25519 aXKGcg NocBhG6QGlWDZhjsA6Sxvjv9Gs+3Pq5gcOqnVdiefBg
+HYnqBv0pdPz8bqgZ98VDfYFeKcFNeuJrlOsyWt551Sg
+-> ssh-ed25519 OkGqLg e0081m/IkQafXh1gAWUZ2glYG7bklCG/LaUy63rK6gc
+G2RNMxCxRnqocYhiq142T8EPZQD8cRHHs7AHKFrMLaU
+-> +J}@hPk-grease
+406BMfqUt/KjayTopj4dNa4owPZphR6AsBXPurJwU/zV9ipirfW3oEeaprdh4uLg
+RHO0bSZQV1uu1YmbXkuwMaVj1cVn2vsDPEv3xG2SRzMoEpAAKaFCBba8
+--- 5ncLI9pS25vz5CebIZjPPDQ5cHISlyRFF55rGgFQnnM
+แ$"‹PKช&Cท\ๅ(ีGภ[›ฯ(
šฌทD๐ึlวบน8ก4็๎ฎP”ฦ)ฉต‰ฌน™บB‚ขฎิผฃฎ๑c\ฅU:๘สฌ(
\ No newline at end of file
diff --git a/ops/secrets/panettone.age b/ops/secrets/panettone.age
index a8a176fb13..0be42dc0a7 100644
--- a/ops/secrets/panettone.age
+++ b/ops/secrets/panettone.age
@@ -1,16 +1,15 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw lFE6Oxzl0jaGpmfxEzmvywEyxsmPNfhv+NNR95XGiDI
-NJhZ6KFNLcScSR5iNB5IAL4UqWzort+jWypbKQPsxu0
--> ssh-ed25519 CpJBgQ 7sMqCFUdss274yNWtYbXe+l7oevKaR99d6E7c4LWtjg
-rqwEyv2dT07qd87suVZxk+8+bmA2W6MFkoG8NktRRbY
--> ssh-ed25519 aXKGcg 9/0QlqFKxPVwjwagBTWHdhJXWWYXn0v649ZhmzpUxWc
-pMs+PoMRi3FghN2odcBQ9tpE+0Mb/jaErnOnuuoq4sw
--> ssh-ed25519 OkGqLg Is/FQ/8s+oq+qThcwOdnAgCrZX/kNBLc0Cwpvi2NMwk
-Zf31SwMF/fyBd1d899GPv8Z8A8GSBy5xuG4d8zL9Zz0
--> wyU-grease Dzk;3o # ,q\WtGwI
-PoJGe6Xlhl47AhFLxM4HLaEYAqcx9lzodHasyZ1AH0BtdSFYT92cYw/1rSNWheTk
-YedxiXNrosw
---- tJE6XbPtWlMYKHItyPlThcnLnmp/9AS1muhfgDosTCk
-$;˜ !†ุ๓?;
-žbฒ็HŸ}๙๏ิะรfxฐQลาอคdaแท๊ˆ‡K7ฟค1O†Uำณ<นกฬ่‹	ซ็y8ีmf–บV~ษJาชฐZฝ๐0ณฌ|‡tN๘เ[รO๗;๐2;/™ณ3)vไGP%ชคํ‰J0ึY%ุจขd’[1ฆk/+ณ
-
\ 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 53f0d39318..5cbf2bf612 100644
--- a/ops/secrets/secrets.nix
+++ b/ops/secrets/secrets.nix
@@ -1,10 +1,17 @@
 let
+  flokli = [
+    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTVTXOutUZZjXLB0lUSgeKcSY/8mxKkC0ingGK1whD2 flokli"
+  ];
+
   tazjin = [
     # tverskoy
     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM1fGWz/gsq+ZeZXjvUrV+pBlanw1c3zJ9kLTax9FWQy"
+
+    # zamalek
+    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDBRXeb8EuecLHP0bW4zuebXp4KRnXgJTZfeVWXQ1n1R"
   ];
 
-  grfn = [
+  aspen = [
     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMcBGBoWd5pPIIQQP52rcFOQN3wAY0J/+K2fuU6SffjA "
   ];
 
@@ -12,26 +19,36 @@ let
     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJk+KvgvI2oJTppMASNUfMcMkA2G5ZNt+HnWDzaXKLlo"
   ];
 
+  sanduny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOag0XhylaTVhmT6HB8EN2Fv5Ymrc4ZfypOXONUkykTX";
   whitby = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNh/w4BSKov0jdz3gKBc98tpoLta5bb87fQXWBhAl2I";
 
-  default.publicKeys = tazjin ++ grfn ++ sterni ++ [ whitby ];
-in {
-  "besadii.age" = default;
-  "buildkite-agent-token.age" = default;
-  "buildkite-graphql-token.age" = default;
-  "clbot-ssh.age" = default;
-  "clbot.age" = default;
-  "gerrit-queue.age" = default;
-  "gerrit-secrets.age" = default;
-  "grafana.age" = default;
-  "irccat.age" = default;
-  "keycloak-db.age" = default;
-  "nix-cache-priv.age" = default;
-  "nix-cache-pub.age" = default;
-  "oauth2_proxy.age" = default;
-  "owothia.age" = default;
-  "panettone.age" = default;
-  "smtprelay.age" = default;
-  "tf-glesys.age" = default;
-  "tf-keycloak.age" = default;
+  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;
+  "buildkite-agent-token.age" = whitbyDefault;
+  "buildkite-graphql-token.age" = whitbyDefault;
+  "buildkite-ssh-private-key.age" = whitbyDefault;
+  "clbot-ssh.age" = whitbyDefault;
+  "clbot.age" = whitbyDefault;
+  "depot-inbox-imap.age" = sandunyDefault;
+  "depot-replica-key.age" = whitbyDefault;
+  "gerrit-autosubmit.age" = whitbyDefault;
+  "gerrit-secrets.age" = whitbyDefault;
+  "grafana.age" = whitbyDefault;
+  "irccat.age" = whitbyDefault;
+  "journaldriver.age" = allDefault;
+  "keycloak-db.age" = whitbyDefault;
+  "nix-cache-priv.age" = whitbyDefault;
+  "nix-cache-pub.age" = whitbyDefault;
+  "owothia.age" = whitbyDefault;
+  "panettone.age" = whitbyDefault;
+  "smtprelay.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 166d2638e1..62fbaffadf 100644
--- a/ops/secrets/smtprelay.age
+++ b/ops/secrets/smtprelay.age
@@ -1,14 +1,16 @@
 age-encryption.org/v1
--> ssh-ed25519 dcsaLw xcNp0GhoE++itBIAUi+0OIKlLENHGqklq02/YGQbH0A
-34OgtbXFlhvjYJQI8zysSKdZiK7FBKn+lunvR1TWYrE
--> ssh-ed25519 CpJBgQ RSWDjIWDt3nbVmvOusrkmy8K+A15Fph/ApbbBw5L7VA
-mP+nnsLaVkeMAAMJ8nsBq4CAw66lVF87bmvGMmsT55A
--> ssh-ed25519 aXKGcg YBiyBkcEWP+5m8fTHPWlGKTfyN92gfhJQkmAxJ3Zei0
-dnnJmSII9wmPJ1jL8s8COPjxoIip4HwWPpmK5jNNlcE
--> ssh-ed25519 OkGqLg 1A5xPUHzoN+lXYlwKlbV42JCI1l361IyyllZ2HmxGCc
-Mi8igtdp0yFEM6lfiT/PqtA6+KWwqS5EWkmtKS+JBWk
--> ]3/,-grease Fj#1m Vq3REqK
-+sNTJq8Vdns
---- y1d0IBqYwo/ABm9XOEQG26UA7NtTg+8mg/QLtPyMLwc
-OูtตใXƒOW +]+ฬ7|ˆD€|n#ฟ‘pPฅSทa“”#•ๆ}v2†‘bMปฮศ๖œvจ?Vุ๛ผ ๙9ฒBหฑฅ}ฃvจM๛Œ๏
-วUv—Mq‘ค@ภI
5mาNx
ฬว
\ 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/tf-buildkite.age b/ops/secrets/tf-buildkite.age
new file mode 100644
index 0000000000..0cf6066fa6
--- /dev/null
+++ b/ops/secrets/tf-buildkite.age
Binary files differdiff --git a/ops/secrets/tf-glesys.age b/ops/secrets/tf-glesys.age
index 53aa5e1acb..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 ddc477b21a..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
new file mode 100644
index 0000000000..e897fedc03
--- /dev/null
+++ b/ops/secrets/tvl-alerts-bot-telegram-token.age
@@ -0,0 +1,15 @@
+age-encryption.org/v1
+-> 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 4f88e75b65..c54a681dce 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,11 @@
     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 = "cschilling";
     email = "christian.schilling.de@gmail.com";
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$9VN3IS6ViW5FFbVKWOZI6Q$gZxuYAYk0Opq4E5i8cbcNjfznCQNc+RiP7Xv1CUnrQU";
@@ -52,9 +62,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,11 +72,31 @@
     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";
   }
   {
+    username = "j4m3s";
+    email = "james.landrein@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$dMYmo+Uym9irtzAGXB2eNw$69OFcuqCqoLPBXKmmtYaQCquXximpyxsb2Kf8U7GdxM";
+  }
+  {
+    username = "jfroche";
+    email = "jfroche@pyxel.be";
+    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";
@@ -77,6 +107,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";
@@ -104,6 +139,11 @@
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$o2OcfhfKOry+UrcmODyQCw$qloaQgoIRDESwaA3yqPxxy8sgLk3mrjYFBbF41elVrM";
   }
   {
+    username = "talyz";
+    email = "kim.lindberger@gmail.com";
+    password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$KYgHYsxX/DZDhnxdkzn1/w$L2Yyc2lYAREZP0FD3iX57MB6gzoOCcVmCGDxIsUGAgk";
+  }
+  {
     username = "tazjin";
     email = "tazjin@tvl.su";
     password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$wOPEl9D3kSke//oLtbvqrg$j0npwwXgaXQ/emefKUwL59tH8hdmtzbgH2rQzWSmE2Y";
@@ -155,8 +195,38 @@
     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";
+  }
 ]
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");