kitgit

tirbofish/kitgit · diff

09c7d9c · Thribhu K

fix: updated the ports so port 2222 is not required to be defined for the remote. feat: on `ssh git@{git_server}`, you are given the poem "Fox". thought that would be nice fix: upgraded openssh to support post-quantum (git kept on bothering).

Verified

diff --git a/Cargo.lock b/Cargo.lock
index a46632f..bf2c3c7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10,30 +10,31 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
 
 [[package]]
 name = "aead"
-version = "0.5.2"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99"
 dependencies = [
- "crypto-common",
- "generic-array",
+ "crypto-common 0.2.2",
+ "inout",
 ]
 
 [[package]]
 name = "aes"
-version = "0.8.4"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58"
 dependencies = [
- "cfg-if",
  "cipher",
- "cpufeatures 0.2.17",
+ "cpubits",
+ "cpufeatures 0.3.0",
+ "zeroize",
 ]
 
 [[package]]
 name = "aes-gcm"
-version = "0.10.3"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028"
 dependencies = [
  "aead",
  "aes",
@@ -41,6 +42,7 @@ dependencies = [
  "ctr",
  "ghash",
  "subtle",
+ "zeroize",
 ]
 
 [[package]]
@@ -125,13 +127,13 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
 
 [[package]]
 name = "argon2"
-version = "0.5.3"
+version = "0.6.0-rc.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
+checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5"
 dependencies = [
  "base64ct",
  "blake2",
- "cpufeatures 0.2.17",
+ "cpufeatures 0.3.0",
  "password-hash",
 ]
 
@@ -341,6 +343,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
 
 [[package]]
+name = "base16ct"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
+
+[[package]]
 name = "base64"
 version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -369,13 +377,13 @@ dependencies = [
 
 [[package]]
 name = "bcrypt-pbkdf"
-version = "0.10.0"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2"
+checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0"
 dependencies = [
  "blowfish",
  "pbkdf2",
- "sha2",
+ "sha2 0.11.0",
 ]
 
 [[package]]
@@ -413,11 +421,11 @@ dependencies = [
 
 [[package]]
 name = "blake2"
-version = "0.10.6"
+version = "0.11.0-rc.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690"
 dependencies = [
- "digest",
+ "digest 0.11.3",
 ]
 
 [[package]]
@@ -426,23 +434,33 @@ version = "0.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.9",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+ "zeroize",
 ]
 
 [[package]]
 name = "block-padding"
-version = "0.3.3"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
 dependencies = [
- "generic-array",
+ "hybrid-array",
 ]
 
 [[package]]
 name = "blowfish"
-version = "0.9.1"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
+checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298"
 dependencies = [
  "byteorder",
  "cipher",
@@ -477,9 +495,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
 
 [[package]]
 name = "cbc"
-version = "0.1.2"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
 dependencies = [
  "cipher",
 ]
@@ -510,24 +528,15 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
 
 [[package]]
 name = "chacha20"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
-dependencies = [
- "cfg-if",
- "cipher",
- "cpufeatures 0.2.17",
-]
-
-[[package]]
-name = "chacha20"
 version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
 dependencies = [
  "cfg-if",
+ "cipher",
  "cpufeatures 0.3.0",
  "rand_core 0.10.1",
+ "zeroize",
 ]
 
 [[package]]
@@ -546,12 +555,14 @@ dependencies = [
 
 [[package]]
 name = "cipher"
-version = "0.4.4"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
 dependencies = [
- "crypto-common",
+ "block-buffer 0.12.1",
+ "crypto-common 0.2.2",
  "inout",
+ "zeroize",
 ]
 
 [[package]]
@@ -595,6 +606,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
 
 [[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[package]]
 name = "colorchoice"
 version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -624,6 +641,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
 
 [[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
 name = "cookie"
 version = "0.18.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -659,6 +682,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
+name = "cpubits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
+[[package]]
 name = "cpufeatures"
 version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -721,33 +750,80 @@ version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.9",
  "rand_core 0.6.4",
  "subtle",
  "zeroize",
 ]
 
 [[package]]
+name = "crypto-bigint"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
+dependencies = [
+ "cpubits",
+ "ctutils",
+ "getrandom 0.4.3",
+ "hybrid-array",
+ "num-traits",
+ "rand_core 0.10.1",
+ "serdect",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "crypto-common"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
- "generic-array",
- "rand_core 0.6.4",
+ "generic-array 0.14.9",
  "typenum",
 ]
 
 [[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "getrandom 0.4.3",
+ "hybrid-array",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "crypto-primes"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f"
+dependencies = [
+ "crypto-bigint 0.7.5",
+ "rand_core 0.10.1",
+]
+
+[[package]]
 name = "ctr"
-version = "0.9.2"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21"
 dependencies = [
  "cipher",
 ]
 
 [[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+ "subtle",
+]
+
+[[package]]
 name = "curve25519-dalek"
 version = "4.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -756,8 +832,25 @@ dependencies = [
  "cfg-if",
  "cpufeatures 0.2.17",
  "curve25519-dalek-derive",
- "digest",
- "fiat-crypto",
+ "digest 0.10.7",
+ "fiat-crypto 0.2.9",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "curve25519-dalek-derive",
+ "digest 0.11.3",
+ "fiat-crypto 0.3.0",
+ "rand_core 0.10.1",
  "rustc_version",
  "subtle",
  "zeroize",
@@ -845,8 +938,19 @@ version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
 dependencies = [
- "const-oid",
- "pem-rfc7468",
+ "const-oid 0.9.6",
+ "pem-rfc7468 0.7.0",
+ "zeroize",
+]
+
+[[package]]
+name = "der"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
+dependencies = [
+ "const-oid 0.10.2",
+ "pem-rfc7468 1.0.0",
  "zeroize",
 ]
 
@@ -860,18 +964,39 @@ dependencies = [
 ]
 
 [[package]]
+name = "des"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
 name = "digest"
 version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
- "block-buffer",
- "const-oid",
- "crypto-common",
+ "block-buffer 0.10.4",
+ "const-oid 0.9.6",
+ "crypto-common 0.1.6",
  "subtle",
 ]
 
 [[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "const-oid 0.10.2",
+ "crypto-common 0.2.2",
+ "ctutils",
+]
+
+[[package]]
 name = "displaydoc"
 version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -909,12 +1034,27 @@ version = "0.16.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
 dependencies = [
- "der",
- "digest",
- "elliptic-curve",
- "rfc6979",
- "signature",
- "spki",
+ "der 0.7.10",
+ "digest 0.10.7",
+ "elliptic-curve 0.13.8",
+ "rfc6979 0.4.0",
+ "signature 2.2.0",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "ecdsa"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0"
+dependencies = [
+ "der 0.8.1",
+ "digest 0.11.3",
+ "elliptic-curve 0.14.1",
+ "rfc6979 0.6.0",
+ "signature 3.0.0",
+ "spki 0.8.0",
+ "zeroize",
 ]
 
 [[package]]
@@ -923,8 +1063,18 @@ version = "2.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
 dependencies = [
- "pkcs8",
- "signature",
+ "pkcs8 0.10.2",
+ "signature 2.2.0",
+]
+
+[[package]]
+name = "ed25519"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
+dependencies = [
+ "pkcs8 0.11.0",
+ "signature 3.0.0",
 ]
 
 [[package]]
@@ -933,11 +1083,26 @@ version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
 dependencies = [
- "curve25519-dalek",
- "ed25519",
- "rand_core 0.6.4",
+ "curve25519-dalek 4.1.3",
+ "ed25519 2.2.3",
  "serde",
- "sha2",
+ "sha2 0.10.9",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
+dependencies = [
+ "curve25519-dalek 5.0.0",
+ "ed25519 3.0.0",
+ "rand_core 0.10.1",
+ "serde",
+ "sha2 0.11.0",
+ "signature 3.0.0",
  "subtle",
  "zeroize",
 ]
@@ -957,17 +1122,39 @@ version = "0.13.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
 dependencies = [
- "base16ct",
- "crypto-bigint",
- "digest",
- "ff",
- "generic-array",
- "group",
- "hkdf",
- "pem-rfc7468",
- "pkcs8",
+ "base16ct 0.2.0",
+ "crypto-bigint 0.5.5",
+ "digest 0.10.7",
+ "ff 0.13.1",
+ "generic-array 0.14.9",
+ "group 0.13.0",
+ "hkdf 0.12.4",
+ "pem-rfc7468 0.7.0",
+ "pkcs8 0.10.2",
  "rand_core 0.6.4",
- "sec1",
+ "sec1 0.7.3",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "elliptic-curve"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65"
+dependencies = [
+ "base16ct 1.0.0",
+ "crypto-bigint 0.7.5",
+ "crypto-common 0.2.2",
+ "digest 0.11.3",
+ "ff 0.14.0",
+ "group 0.14.0",
+ "hkdf 0.13.0",
+ "hybrid-array",
+ "pem-rfc7468 1.0.0",
+ "pkcs8 0.11.0",
+ "rand_core 0.10.1",
+ "sec1 0.8.1",
  "subtle",
  "zeroize",
 ]
@@ -1052,12 +1239,28 @@ dependencies = [
 ]
 
 [[package]]
+name = "ff"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
+dependencies = [
+ "rand_core 0.10.1",
+ "subtle",
+]
+
+[[package]]
 name = "fiat-crypto"
 version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
 
 [[package]]
+name = "fiat-crypto"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
+
+[[package]]
 name = "find-msvc-tools"
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1216,6 +1419,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "generic-array"
+version = "1.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab4e5aa225bc56696909483320f0ff9b600f1a971b52e07a17d70f3d9b43254b"
+dependencies = [
+ "generic-array 0.14.9",
+ "rustversion",
+ "typenum",
+]
+
+[[package]]
 name = "getrandom"
 version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1244,11 +1458,10 @@ dependencies = [
 
 [[package]]
 name = "ghash"
-version = "0.5.1"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5"
 dependencies = [
- "opaque-debug",
  "polyval",
 ]
 
@@ -1273,12 +1486,23 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
 dependencies = [
- "ff",
+ "ff 0.13.1",
  "rand_core 0.6.4",
  "subtle",
 ]
 
 [[package]]
+name = "group"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
+dependencies = [
+ "ff 0.14.0",
+ "rand_core 0.10.1",
+ "subtle",
+]
+
+[[package]]
 name = "hashbrown"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1328,7 +1552,7 @@ dependencies = [
  "http",
  "httpdate",
  "mime",
- "sha1",
+ "sha1 0.10.7",
 ]
 
 [[package]]
@@ -1354,9 +1578,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
 name = "hex-literal"
-version = "0.4.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
 
 [[package]]
 name = "hkdf"
@@ -1364,7 +1588,16 @@ version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
 dependencies = [
- "hmac",
+ "hmac 0.12.1",
+]
+
+[[package]]
+name = "hkdf"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
+dependencies = [
+ "hmac 0.13.0",
 ]
 
 [[package]]
@@ -1373,7 +1606,16 @@ version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
- "digest",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "hmac"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
+dependencies = [
+ "digest 0.11.3",
 ]
 
 [[package]]
@@ -1437,6 +1679,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
+name = "hybrid-array"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
+dependencies = [
+ "ctutils",
+ "subtle",
+ "typenum",
+ "zeroize",
+]
+
+[[package]]
 name = "hyper"
 version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1508,7 +1762,7 @@ dependencies = [
  "js-sys",
  "log",
  "wasm-bindgen",
- "windows-core 0.62.2",
+ "windows-core",
 ]
 
 [[package]]
@@ -1654,40 +1908,24 @@ dependencies = [
 
 [[package]]
 name = "inout"
-version = "0.1.4"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
 dependencies = [
  "block-padding",
- "generic-array",
+ "hybrid-array",
 ]
 
 [[package]]
-name = "internal-russh-forked-ssh-key"
-version = "0.6.9+upstream-0.6.7"
+name = "internal-russh-num-bigint"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb5af01d366561582e9ea5f841837cc1d8e37e7142a32f33a43801e81863cba5"
+checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8"
 dependencies = [
- "argon2",
- "bcrypt-pbkdf",
- "ecdsa",
- "ed25519-dalek",
- "hex",
- "hmac",
- "num-bigint-dig",
- "p256",
- "p384",
- "p521",
- "rand_core 0.6.4",
- "rsa",
- "sec1",
- "sha1",
- "sha2",
- "signature",
- "ssh-cipher",
- "ssh-encoding",
- "subtle",
- "zeroize",
+ "num-integer",
+ "num-traits",
+ "rand 0.10.2",
+ "rand_core 0.10.1",
 ]
 
 [[package]]
@@ -1739,6 +1977,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "keccak"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+]
+
+[[package]]
+name = "kem"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd"
+dependencies = [
+ "crypto-common 0.2.2",
+ "rand_core 0.10.1",
+]
+
+[[package]]
 name = "kitgit"
 version = "0.1.0"
 dependencies = [
@@ -1757,19 +2015,19 @@ dependencies = [
  "futures",
  "git2",
  "hex",
- "hmac",
+ "hmac 0.12.1",
  "http-body-util",
  "mime_guess",
  "openidconnect",
  "pulldown-cmark",
  "qrcode",
- "rand 0.8.7",
+ "rand 0.10.2",
  "reqwest",
  "russh",
  "serde",
  "serde_json",
- "sha1",
- "sha2",
+ "sha1 0.10.7",
+ "sha2 0.10.9",
  "sqlx",
  "syntect",
  "thiserror 2.0.19",
@@ -1924,14 +2182,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
 dependencies = [
  "cfg-if",
- "digest",
+ "digest 0.10.7",
 ]
 
 [[package]]
 name = "md5"
-version = "0.7.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c"
 
 [[package]]
 name = "memchr"
@@ -1977,6 +2235,31 @@ dependencies = [
 ]
 
 [[package]]
+name = "ml-kem"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66"
+dependencies = [
+ "hybrid-array",
+ "kem",
+ "module-lattice",
+ "pkcs8 0.11.0",
+ "rand_core 0.10.1",
+ "sha3 0.11.0",
+]
+
+[[package]]
+name = "module-lattice"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
+dependencies = [
+ "ctutils",
+ "hybrid-array",
+ "num-traits",
+]
+
+[[package]]
 name = "multer"
 version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1994,6 +2277,18 @@ dependencies = [
 ]
 
 [[package]]
+name = "nix"
+version = "0.31.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
 name = "nu-ansi-term"
 version = "0.50.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2010,7 +2305,6 @@ checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
 dependencies = [
  "num-integer",
  "num-traits",
- "rand 0.8.7",
 ]
 
 [[package]]
@@ -2079,7 +2373,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_path_to_error",
- "sha2",
+ "sha2 0.10.9",
  "thiserror 1.0.69",
  "url",
 ]
@@ -2097,12 +2391,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
 
 [[package]]
-name = "opaque-debug"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
-
-[[package]]
 name = "openidconnect"
 version = "4.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2111,23 +2399,23 @@ dependencies = [
  "base64 0.21.7",
  "chrono",
  "dyn-clone",
- "ed25519-dalek",
- "hmac",
+ "ed25519-dalek 2.2.0",
+ "hmac 0.12.1",
  "http",
  "itertools",
  "log",
  "oauth2",
- "p256",
- "p384",
+ "p256 0.13.2",
+ "p384 0.13.1",
  "rand 0.8.7",
- "rsa",
+ "rsa 0.9.10",
  "serde",
  "serde-value",
  "serde_json",
  "serde_path_to_error",
  "serde_plain",
  "serde_with",
- "sha2",
+ "sha2 0.10.9",
  "subtle",
  "thiserror 1.0.69",
  "url",
@@ -2166,10 +2454,23 @@ version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
 dependencies = [
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "sha2",
+ "ecdsa 0.16.9",
+ "elliptic-curve 0.13.8",
+ "primeorder 0.13.6",
+ "sha2 0.10.9",
+]
+
+[[package]]
+name = "p256"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a"
+dependencies = [
+ "ecdsa 0.17.0",
+ "elliptic-curve 0.14.1",
+ "primefield",
+ "primeorder 0.14.0",
+ "sha2 0.11.0",
 ]
 
 [[package]]
@@ -2178,40 +2479,58 @@ version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
 dependencies = [
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "sha2",
+ "ecdsa 0.16.9",
+ "elliptic-curve 0.13.8",
+ "primeorder 0.13.6",
+ "sha2 0.10.9",
+]
+
+[[package]]
+name = "p384"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f"
+dependencies = [
+ "ecdsa 0.17.0",
+ "elliptic-curve 0.14.1",
+ "fiat-crypto 0.3.0",
+ "primefield",
+ "primeorder 0.14.0",
+ "sha2 0.11.0",
 ]
 
 [[package]]
 name = "p521"
-version = "0.13.3"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
+checksum = "4ad64cc32c2dc466317c12ee5853e61f159f9eab1fe7efade0395dc2e7b43449"
 dependencies = [
- "base16ct",
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "rand_core 0.6.4",
- "sha2",
+ "base16ct 1.0.0",
+ "ecdsa 0.17.0",
+ "elliptic-curve 0.14.1",
+ "primefield",
+ "primeorder 0.14.0",
+ "sha2 0.11.0",
 ]
 
 [[package]]
 name = "pageant"
-version = "0.0.2"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c6f0e349ea8dea1b50aa17c082777d30df133d89898c7568a615354772d3731"
+checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5"
 dependencies = [
+ "base16ct 1.0.0",
+ "byteorder",
  "bytes",
  "delegate",
  "futures",
- "log",
- "rand 0.8.7",
- "thiserror 1.0.69",
+ "log",
+ "rand 0.10.2",
+ "sha2 0.11.0",
+ "thiserror 2.0.19",
  "tokio",
  "windows",
+ "windows-strings",
 ]
 
 [[package]]
@@ -2245,23 +2564,21 @@ dependencies = [
 
 [[package]]
 name = "password-hash"
-version = "0.5.0"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
+checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b"
 dependencies = [
- "base64ct",
- "rand_core 0.6.4",
- "subtle",
+ "phc",
 ]
 
 [[package]]
 name = "pbkdf2"
-version = "0.12.2"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
 dependencies = [
- "digest",
- "hmac",
+ "digest 0.11.3",
+ "hmac 0.13.0",
 ]
 
 [[package]]
@@ -2274,12 +2591,31 @@ dependencies = [
 ]
 
 [[package]]
+name = "pem-rfc7468"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
 name = "percent-encoding"
 version = "2.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
 
 [[package]]
+name = "phc"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892"
+dependencies = [
+ "base64ct",
+ "ctutils",
+]
+
+[[package]]
 name = "pin-project-lite"
 version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2291,24 +2627,36 @@ version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
 dependencies = [
- "der",
- "pkcs8",
- "spki",
+ "der 0.7.10",
+ "pkcs8 0.10.2",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "pkcs1"
+version = "0.8.0-rc.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e"
+dependencies = [
+ "der 0.8.1",
+ "spki 0.8.0",
 ]
 
 [[package]]
 name = "pkcs5"
-version = "0.7.1"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
+checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca"
 dependencies = [
  "aes",
+ "aes-gcm",
  "cbc",
- "der",
+ "der 0.8.1",
  "pbkdf2",
+ "rand_core 0.10.1",
  "scrypt",
- "sha2",
- "spki",
+ "sha2 0.11.0",
+ "spki 0.8.0",
 ]
 
 [[package]]
@@ -2317,10 +2665,20 @@ version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
 dependencies = [
- "der",
+ "der 0.7.10",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
+dependencies = [
+ "der 0.8.1",
  "pkcs5",
- "rand_core 0.6.4",
- "spki",
+ "rand_core 0.10.1",
+ "spki 0.8.0",
 ]
 
 [[package]]
@@ -2337,24 +2695,23 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
 
 [[package]]
 name = "poly1305"
-version = "0.8.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c"
 dependencies = [
- "cpufeatures 0.2.17",
- "opaque-debug",
+ "cpufeatures 0.3.0",
  "universal-hash",
+ "zeroize",
 ]
 
 [[package]]
 name = "polyval"
-version = "0.6.2"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd"
 dependencies = [
- "cfg-if",
- "cpufeatures 0.2.17",
- "opaque-debug",
+ "cpubits",
+ "cpufeatures 0.3.0",
  "universal-hash",
 ]
 
@@ -2383,12 +2740,39 @@ dependencies = [
 ]
 
 [[package]]
+name = "primefield"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4"
+dependencies = [
+ "crypto-bigint 0.7.5",
+ "crypto-common 0.2.2",
+ "ff 0.14.0",
+ "rand_core 0.10.1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "primeorder"
 version = "0.13.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
 dependencies = [
- "elliptic-curve",
+ "elliptic-curve 0.13.8",
+]
+
+[[package]]
+name = "primeorder"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06"
+dependencies = [
+ "elliptic-curve 0.14.1",
+ "once_cell",
+ "primefield",
+ "serdect",
+ "wnaf",
 ]
 
 [[package]]
@@ -2528,7 +2912,7 @@ version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
 dependencies = [
- "chacha20 0.10.1",
+ "chacha20",
  "getrandom 0.4.3",
  "rand_core 0.10.1",
 ]
@@ -2668,11 +3052,21 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
 dependencies = [
- "hmac",
+ "hmac 0.12.1",
  "subtle",
 ]
 
 [[package]]
+name = "rfc6979"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b"
+dependencies = [
+ "crypto-bigint 0.7.5",
+ "hmac 0.13.0",
+]
+
+[[package]]
 name = "ring"
 version = "0.17.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2692,103 +3086,128 @@ version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
 dependencies = [
- "const-oid",
- "digest",
+ "const-oid 0.9.6",
+ "digest 0.10.7",
  "num-bigint-dig",
  "num-integer",
  "num-traits",
- "pkcs1",
- "pkcs8",
+ "pkcs1 0.7.5",
+ "pkcs8 0.10.2",
  "rand_core 0.6.4",
- "sha2",
- "signature",
- "spki",
+ "signature 2.2.0",
+ "spki 0.7.3",
  "subtle",
  "zeroize",
 ]
 
 [[package]]
+name = "rsa"
+version = "0.10.0-rc.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf"
+dependencies = [
+ "const-oid 0.10.2",
+ "crypto-bigint 0.7.5",
+ "crypto-primes",
+ "digest 0.11.3",
+ "pkcs1 0.8.0-rc.4",
+ "pkcs8 0.11.0",
+ "rand_core 0.10.1",
+ "sha2 0.11.0",
+ "signature 3.0.0",
+ "spki 0.8.0",
+ "zeroize",
+]
+
+[[package]]
 name = "russh"
-version = "0.50.4"
+version = "0.62.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d8075561703e70dab7b095b2c13597cde37f5be94af0849fa4e51c315020d0"
+checksum = "b8b67b5a0d8068c89dcbe9d95df986af7a851d1f3c604525274c37468e60464f"
 dependencies = [
  "aes",
- "aes-gcm",
  "bitflags",
  "block-padding",
  "byteorder",
  "bytes",
  "cbc",
- "chacha20 0.9.1",
+ "cipher",
+ "crypto-bigint 0.7.5",
  "ctr",
- "curve25519-dalek",
+ "curve25519-dalek 5.0.0",
  "data-encoding",
  "delegate",
- "der",
- "digest",
- "ecdsa",
- "ed25519-dalek",
- "elliptic-curve",
+ "der 0.8.1",
+ "digest 0.11.3",
+ "ecdsa 0.17.0",
+ "ed25519-dalek 3.0.0",
+ "elliptic-curve 0.14.1",
  "enum_dispatch",
  "flate2",
  "futures",
- "generic-array",
- "getrandom 0.2.17",
+ "generic-array 1.4.4",
+ "getrandom 0.4.3",
+ "ghash",
  "hex-literal",
- "hmac",
- "home",
+ "hmac 0.13.0",
  "inout",
- "internal-russh-forked-ssh-key",
+ "internal-russh-num-bigint",
+ "keccak",
  "log",
  "md5",
+ "ml-kem",
+ "module-lattice",
  "num-bigint",
- "once_cell",
- "p256",
- "p384",
+ "p256 0.14.0",
+ "p384 0.14.0",
  "p521",
  "pageant",
  "pbkdf2",
- "pkcs1",
+ "pkcs1 0.8.0-rc.4",
  "pkcs5",
- "pkcs8",
- "poly1305",
- "rand 0.8.7",
- "rand_core 0.6.4",
- "rsa",
+ "pkcs8 0.11.0",
+ "polyval",
+ "rand 0.10.2",
+ "rand_core 0.10.1",
+ "ring",
+ "rsa 0.10.0-rc.18",
  "russh-cryptovec",
  "russh-util",
- "sec1",
- "sha1",
- "sha2",
- "signature",
- "spki",
+ "salsa20",
+ "scrypt",
+ "sec1 0.8.1",
+ "sha1 0.11.0",
+ "sha2 0.11.0",
+ "sha3 0.12.0",
+ "signature 3.0.0",
+ "spki 0.8.0",
  "ssh-encoding",
+ "ssh-key",
  "subtle",
- "thiserror 1.0.69",
+ "thiserror 2.0.19",
  "tokio",
- "tokio-stream",
  "typenum",
+ "universal-hash",
  "zeroize",
 ]
 
 [[package]]
 name = "russh-cryptovec"
-version = "0.50.2"
+version = "0.62.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fcb7c127135848b47715b5bcb13d8a27ccd86ce1de1c15eab5982df91fe279a"
+checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438"
 dependencies = [
- "libc",
  "log",
+ "nix",
  "ssh-encoding",
- "winapi",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
 name = "russh-util"
-version = "0.50.0"
+version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c698d702527b51a82e64de98d506e9c1e83a063035d41df4f2354499ec090b79"
+checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6"
 dependencies = [
  "chrono",
  "tokio",
@@ -2860,10 +3279,11 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
 
 [[package]]
 name = "salsa20"
-version = "0.10.2"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c"
 dependencies = [
+ "cfg-if",
  "cipher",
 ]
 
@@ -2908,13 +3328,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "scrypt"
-version = "0.11.0"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0"
 dependencies = [
+ "cfg-if",
  "pbkdf2",
  "salsa20",
- "sha2",
+ "sha2 0.11.0",
 ]
 
 [[package]]
@@ -2923,10 +3344,24 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
 dependencies = [
- "base16ct",
- "der",
- "generic-array",
- "pkcs8",
+ "base16ct 0.2.0",
+ "der 0.7.10",
+ "generic-array 0.14.9",
+ "pkcs8 0.10.2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "sec1"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
+dependencies = [
+ "base16ct 1.0.0",
+ "ctutils",
+ "der 0.8.1",
+ "hybrid-array",
  "subtle",
  "zeroize",
 ]
@@ -3064,6 +3499,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "serdect"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
+dependencies = [
+ "base16ct 1.0.0",
+ "serde",
+]
+
+[[package]]
 name = "sha1"
 version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3071,7 +3516,18 @@ checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
 dependencies = [
  "cfg-if",
  "cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha1"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
 ]
 
 [[package]]
@@ -3082,7 +3538,39 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
 dependencies = [
  "cfg-if",
  "cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "sha3"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
+dependencies = [
+ "digest 0.11.3",
+ "keccak",
+]
+
+[[package]]
+name = "sha3"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759"
+dependencies = [
+ "digest 0.11.3",
+ "keccak",
+ "sponge-cursor",
 ]
 
 [[package]]
@@ -3116,11 +3604,21 @@ version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
 dependencies = [
- "digest",
+ "digest 0.10.7",
  "rand_core 0.6.4",
 ]
 
 [[package]]
+name = "signature"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
+dependencies = [
+ "digest 0.11.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
 name = "simd-adler32"
 version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3167,10 +3665,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
 dependencies = [
  "base64ct",
- "der",
+ "der 0.7.10",
+]
+
+[[package]]
+name = "spki"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
+dependencies = [
+ "base64ct",
+ "der 0.8.1",
 ]
 
 [[package]]
+name = "sponge-cursor"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
+
+[[package]]
 name = "sqlx"
 version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3210,7 +3724,7 @@ dependencies = [
  "rustls",
  "serde",
  "serde_json",
- "sha2",
+ "sha2 0.10.9",
  "smallvec",
  "thiserror 2.0.19",
  "tokio",
@@ -3249,7 +3763,7 @@ dependencies = [
  "quote",
  "serde",
  "serde_json",
- "sha2",
+ "sha2 0.10.9",
  "sqlx-core",
  "sqlx-mysql",
  "sqlx-postgres",
@@ -3272,17 +3786,17 @@ dependencies = [
  "bytes",
  "chrono",
  "crc",
- "digest",
+ "digest 0.10.7",
  "dotenvy",
  "either",
  "futures-channel",
  "futures-core",
  "futures-io",
  "futures-util",
- "generic-array",
+ "generic-array 0.14.9",
  "hex",
- "hkdf",
- "hmac",
+ "hkdf 0.12.4",
+ "hmac 0.12.1",
  "itoa",
  "log",
  "md-5",
@@ -3290,10 +3804,10 @@ dependencies = [
  "once_cell",
  "percent-encoding",
  "rand 0.8.7",
- "rsa",
+ "rsa 0.9.10",
  "serde",
- "sha1",
- "sha2",
+ "sha1 0.10.7",
+ "sha2 0.10.9",
  "smallvec",
  "sqlx-core",
  "stringprep",
@@ -3321,8 +3835,8 @@ dependencies = [
  "futures-core",
  "futures-util",
  "hex",
- "hkdf",
- "hmac",
+ "hkdf 0.12.4",
+ "hmac 0.12.1",
  "home",
  "itoa",
  "log",
@@ -3332,7 +3846,7 @@ dependencies = [
  "rand 0.8.7",
  "serde",
  "serde_json",
- "sha2",
+ "sha2 0.10.9",
  "smallvec",
  "sqlx-core",
  "stringprep",
@@ -3370,31 +3884,61 @@ dependencies = [
 
 [[package]]
 name = "ssh-cipher"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"
+checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597"
 dependencies = [
+ "aead",
  "aes",
  "aes-gcm",
- "cbc",
- "chacha20 0.9.1",
+ "chacha20",
  "cipher",
- "ctr",
+ "ctutils",
+ "des",
  "poly1305",
  "ssh-encoding",
- "subtle",
+ "zeroize",
 ]
 
 [[package]]
 name = "ssh-encoding"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"
+checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e"
 dependencies = [
  "base64ct",
  "bytes",
- "pem-rfc7468",
- "sha2",
+ "crypto-bigint 0.7.5",
+ "ctutils",
+ "digest 0.11.3",
+ "pem-rfc7468 1.0.0",
+ "zeroize",
+]
+
+[[package]]
+name = "ssh-key"
+version = "0.7.0-rc.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3"
+dependencies = [
+ "argon2",
+ "bcrypt-pbkdf",
+ "ctutils",
+ "ed25519-dalek 3.0.0",
+ "hex",
+ "hmac 0.13.0",
+ "p256 0.14.0",
+ "p384 0.14.0",
+ "p521",
+ "rand_core 0.10.1",
+ "rsa 0.10.0-rc.18",
+ "sec1 0.8.1",
+ "sha1 0.11.0",
+ "sha2 0.11.0",
+ "signature 3.0.0",
+ "ssh-cipher",
+ "ssh-encoding",
+ "zeroize",
 ]
 
 [[package]]
@@ -3637,7 +4181,6 @@ dependencies = [
  "futures-core",
  "pin-project-lite",
  "tokio",
- "tokio-util",
 ]
 
 [[package]]
@@ -3860,12 +4403,12 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
 
 [[package]]
 name = "universal-hash"
-version = "0.5.1"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
 dependencies = [
- "crypto-common",
- "subtle",
+ "crypto-common 0.2.2",
+ "ctutils",
 ]
 
 [[package]]
@@ -4070,22 +4613,6 @@ dependencies = [
 ]
 
 [[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.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4095,32 +4622,24 @@ dependencies = [
 ]
 
 [[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"
-version = "0.58.0"
+version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
 dependencies = [
- "windows-core 0.58.0",
- "windows-targets 0.52.6",
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-numerics",
 ]
 
 [[package]]
-name = "windows-core"
-version = "0.58.0"
+name = "windows-collections"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
 dependencies = [
- "windows-implement 0.58.0",
- "windows-interface 0.58.0",
- "windows-result 0.2.0",
- "windows-strings 0.1.0",
- "windows-targets 0.52.6",
+ "windows-core",
 ]
 
 [[package]]
@@ -4129,22 +4648,22 @@ version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
 dependencies = [
- "windows-implement 0.60.2",
- "windows-interface 0.59.3",
+ "windows-implement",
+ "windows-interface",
  "windows-link",
- "windows-result 0.4.1",
- "windows-strings 0.5.1",
+ "windows-result",
+ "windows-strings",
 ]
 
 [[package]]
-name = "windows-implement"
-version = "0.58.0"
+name = "windows-future"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.119",
+ "windows-core",
+ "windows-link",
+ "windows-threading",
 ]
 
 [[package]]
@@ -4160,17 +4679,6 @@ dependencies = [
 
 [[package]]
 name = "windows-interface"
-version = "0.58.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.119",
-]
-
-[[package]]
-name = "windows-interface"
 version = "0.59.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
@@ -4187,12 +4695,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
 
 [[package]]
-name = "windows-result"
-version = "0.2.0"
+name = "windows-numerics"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
 dependencies = [
- "windows-targets 0.52.6",
+ "windows-core",
+ "windows-link",
 ]
 
 [[package]]
@@ -4206,16 +4715,6 @@ dependencies = [
 
 [[package]]
 name = "windows-strings"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
-dependencies = [
- "windows-result 0.2.0",
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-strings"
 version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
@@ -4282,6 +4781,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4381,6 +4889,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "wnaf"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1"
+dependencies = [
+ "ff 0.14.0",
+ "group 0.14.0",
+ "hybrid-array",
+]
+
+[[package]]
 name = "writeable"
 version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 8c37b19..e80f789 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,9 +27,9 @@ http-body-util = "0.1"
 mime_guess = "2"
 openidconnect = "4"
 pulldown-cmark = { version = "0.13", default-features = false, features = ["html", "simd"] }
-rand = "0.8"
+rand = "0.10"
 reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "cookies", "gzip"] }
-russh = "0.50"
+russh = { version = "0.62", default-features = false, features = ["flate2", "rsa", "ring"] }
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
 sha2 = "0.10"
diff --git a/brand/index.html b/brand/index.html
deleted file mode 100644
index 83ba72f..0000000
--- a/brand/index.html
+++ /dev/null
@@ -1,349 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
-    <title>kitgit — brand</title>
-    <meta
-      name="description"
-      content="kitgit — self-hosted git. ink on paper."
-    />
-    <link rel="preconnect" href="https://fonts.googleapis.com" />
-    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
-    <!-- Web fallback when a Nerd Font is not installed locally -->
-    <link
-      href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap"
-      rel="stylesheet"
-    />
-    <link rel="stylesheet" href="./theme.css" />
-    <style>
-      .hero {
-        padding: var(--kg-space-6) 0 var(--kg-space-5);
-        min-height: calc(100vh - 4.5rem);
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-      }
-
-      .hero p {
-        max-width: 28rem;
-        margin: var(--kg-space-4) 0 var(--kg-space-5);
-        color: var(--kg-muted);
-        font-size: 1rem;
-      }
-
-      .section {
-        padding: var(--kg-space-5) 0;
-      }
-
-      .section + .section {
-        border-top: 1px solid var(--kg-line);
-      }
-
-      .stack {
-        display: flex;
-        flex-direction: column;
-        gap: var(--kg-space-3);
-      }
-
-      .type-row {
-        display: grid;
-        gap: var(--kg-space-2);
-      }
-
-      .type-row small {
-        color: var(--kg-faint);
-        font-size: 0.6875rem;
-        letter-spacing: 0.1em;
-        text-transform: uppercase;
-      }
-
-      .toggle {
-        font: inherit;
-        font-size: 0.75rem;
-        letter-spacing: 0.08em;
-        text-transform: uppercase;
-        color: var(--kg-muted);
-        background: none;
-        border: 0;
-        cursor: pointer;
-        padding: 0;
-      }
-
-      .toggle:hover {
-        color: var(--kg-fg);
-      }
-
-      .sample-list {
-        list-style: none;
-        margin: 0;
-        padding: 0;
-        border: 1px solid var(--kg-line);
-      }
-
-      .sample-list li {
-        display: flex;
-        justify-content: space-between;
-        gap: var(--kg-space-3);
-        padding: var(--kg-space-2) var(--kg-space-3);
-        border-bottom: 1px solid var(--kg-line);
-        font-size: 0.875rem;
-      }
-
-      .sample-list li:last-child {
-        border-bottom: 0;
-      }
-
-      .sample-list a {
-        color: var(--kg-fg);
-        text-decoration: none;
-      }
-
-      .sample-list a:hover {
-        text-decoration: underline;
-        text-underline-offset: 0.2em;
-      }
-
-      .voice {
-        display: grid;
-        gap: var(--kg-space-4);
-      }
-
-      @media (min-width: 640px) {
-        .voice {
-          grid-template-columns: 1fr 1fr;
-        }
-      }
-
-      .voice dt {
-        font-size: 0.75rem;
-        letter-spacing: 0.12em;
-        text-transform: uppercase;
-        color: var(--kg-muted);
-        margin-bottom: var(--kg-space-1);
-      }
-
-      .voice dd {
-        margin: 0;
-      }
-    </style>
-  </head>
-  <body>
-    <div class="kg-shell">
-      <header class="kg-bar">
-        <a class="kg-mark" href="#">
-          <span class="kg-mark__glyph" aria-hidden="true">狐</span>
-          <span class="kg-mark__name">kitgit</span>
-        </a>
-        <nav aria-label="Brand sections">
-          <a href="#mark">mark</a>
-          <a href="#type">type</a>
-          <a href="#ink">ink</a>
-          <a href="#voice">voice</a>
-        </nav>
-        <button
-          type="button"
-          class="toggle"
-          id="theme-toggle"
-          aria-pressed="false"
-        >
-          invert
-        </button>
-      </header>
-
-      <main>
-        <!-- first viewport: brand only -->
-        <section class="hero" aria-label="Brand">
-          <a class="kg-mark kg-mark--hero kg-caret" href="#">
-            <span class="kg-mark__glyph" aria-hidden="true">狐</span>
-            <span class="kg-mark__name">kitgit</span>
-          </a>
-          <p>
-            Self-hosted git. Templates, not a product page. Ink on paper.
-          </p>
-          <div class="kg-actions">
-            <a class="kg-btn" href="#mark">read the system</a>
-            <a class="kg-btn kg-btn--ghost" href="#sample">see a template</a>
-          </div>
-        </section>
-
-        <section class="section" id="mark">
-          <span class="kg-kicker">01 — mark</span>
-          <h2 class="kg-display">Wordmark, not a logo lockup.</h2>
-          <p class="kg-muted" style="margin: 1rem 0 1.5rem; max-width: 36rem">
-            Always lowercase. Always monospace. The mark is a solid ink square —
-            no SVG, no lockup. UI chrome elsewhere uses Nerd Font glyphs
-            (<code>nf-md-git</code>, branch, commit, etc.).
-          </p>
-          <div class="stack">
-            <a class="kg-mark kg-mark--hero" href="#">
-              <span class="kg-mark__glyph" aria-hidden="true">狐</span>
-              <span class="kg-mark__name">kitgit</span>
-            </a>
-            <a class="kg-mark" href="#">
-              <span class="kg-mark__glyph" aria-hidden="true">狐</span>
-              <span class="kg-mark__name">kitgit</span>
-            </a>
-            <span class="kg-mark kg-faint">
-              <span class="kg-mark__name">kitgit</span>
-            </span>
-          </div>
-        </section>
-
-        <section class="section" id="type">
-          <span class="kg-kicker">02 — type</span>
-          <h2 class="kg-display">One face. Nerd Font first.</h2>
-          <p class="kg-muted" style="margin: 1rem 0 1.5rem; max-width: 36rem">
-            Primary stack:
-            <strong style="color: var(--kg-fg); font-weight: 500"
-              >JetBrainsMono Nerd Font</strong
-            >
-            → JetBrains Mono → FiraCode Nerd Font → system mono. No second
-            family. Hierarchy is weight and size only.
-          </p>
-          <div class="stack">
-            <div class="type-row">
-              <small>display / 700</small>
-              <p class="kg-display">clone. push. host.</p>
-            </div>
-            <div class="type-row">
-              <small>title / 700</small>
-              <p class="kg-title">thribhu / kitgit</p>
-            </div>
-            <div class="type-row">
-              <small>body / 400</small>
-              <p>
-                A bare forge for people who want git over http and ssh — and
-                almost nothing else.
-              </p>
-            </div>
-            <div class="type-row">
-              <small>muted / 400</small>
-              <p class="kg-muted">updated 2h ago · 3 commits · main</p>
-            </div>
-          </div>
-        </section>
-
-        <section class="section" id="ink">
-          <span class="kg-kicker">03 — ink</span>
-          <h2 class="kg-display">Black. White. Stop.</h2>
-          <p class="kg-muted" style="margin: 1rem 0 1.5rem; max-width: 36rem">
-            No accent hue. Interaction is invert, underline, or opacity. Borders
-            are either faint paper lines or full ink.
-          </p>
-          <div class="kg-swatches" aria-label="Color tokens">
-            <div class="kg-swatch" style="background: #ffffff; color: #0a0a0a">
-              <span>bg #fff</span>
-            </div>
-            <div class="kg-swatch" style="background: #f7f7f7; color: #0a0a0a">
-              <span>raised #f7</span>
-            </div>
-            <div class="kg-swatch" style="background: #0a0a0a; color: #ffffff">
-              <span>fg #0a</span>
-            </div>
-            <div class="kg-swatch" style="background: #6b6b6b; color: #ffffff">
-              <span>muted #6b</span>
-            </div>
-            <div class="kg-swatch" style="background: #e5e5e5; color: #0a0a0a">
-              <span>line #e5</span>
-            </div>
-            <div class="kg-swatch" style="background: #0a0a0a; color: #ffffff">
-              <span>invert</span>
-            </div>
-          </div>
-          <pre
-            class="kg-code"
-            style="margin-top: 1.5rem"
-            aria-label="Install example"
-          ><span class="cm"># serve a bare repo</span>
-$ kitgit init ~/repos/app.git
-$ kitgit serve --listen 0.0.0.0:8080
-<span class="ok">listening on :8080</span></pre>
-        </section>
-
-        <section class="section" id="voice">
-          <span class="kg-kicker">04 — voice</span>
-          <h2 class="kg-display">Short. Literal. Quiet.</h2>
-          <dl class="voice" style="margin-top: 1.5rem">
-            <div>
-              <dt>Name</dt>
-              <dd><strong>kitgit</strong> — kit + git. A kit you host.</dd>
-            </div>
-            <div>
-              <dt>Tagline</dt>
-              <dd>Self-hosted git. Templates, not a product page.</dd>
-            </div>
-            <div>
-              <dt>Say</dt>
-              <dd>
-                “repos”, “bare”, “clone url”, “owner”, “push”. Prefer verbs over
-                marketing nouns.
-              </dd>
-            </div>
-            <div>
-              <dt>Don’t say</dt>
-              <dd>
-                “forge platform”, “developer experience”, “seamless”, “powerful”,
-                “next-gen”.
-              </dd>
-            </div>
-            <div>
-              <dt>UI copy</dt>
-              <dd>
-                Sentence case. No exclamation marks. Empty states are one line:
-                <em class="kg-muted">no repos yet.</em>
-              </dd>
-            </div>
-            <div>
-              <dt>Iconography</dt>
-              <dd>
-                Nerd Font codepoints only. No SVG icon packs. Prefer text labels
-                when a glyph would be decorative.
-              </dd>
-            </div>
-          </dl>
-        </section>
-
-        <section class="section" id="sample">
-          <span class="kg-kicker">05 — template sample</span>
-          <h2 class="kg-display">How a page should feel.</h2>
-          <p class="kg-muted" style="margin: 1rem 0 1.5rem; max-width: 36rem">
-            One job. One title. Lists over cards. Metadata stays muted.
-          </p>
-          <p class="kg-title" style="margin-bottom: 0.75rem">repos</p>
-          <ul class="sample-list">
-            <li>
-              <a href="#">thribhu/kitgit</a>
-              <span class="kg-muted">main · 12c</span>
-            </li>
-            <li>
-              <a href="#">thribhu/notes</a>
-              <span class="kg-muted">trunk · 4c</span>
-            </li>
-            <li>
-              <a href="#">ops/backup</a>
-              <span class="kg-muted">master · 1c</span>
-            </li>
-          </ul>
-          <p class="kg-faint" style="margin-top: 1rem; font-size: 0.75rem">
-            clone · http://git.example/thribhu/kitgit.git
-          </p>
-        </section>
-      </main>
-
-      <footer class="kg-footer">
-        kitgit · ink on paper · brand v0.1
-      </footer>
-    </div>
-
-    <script>
-      const root = document.documentElement;
-      const btn = document.getElementById("theme-toggle");
-      btn.addEventListener("click", () => {
-        const on = root.classList.toggle("kg-dark");
-        btn.setAttribute("aria-pressed", String(on));
-        btn.textContent = on ? "paper" : "invert";
-      });
-    </script>
-  </body>
-</html>
diff --git a/brand/theme.css b/brand/theme.css
deleted file mode 100644
index 025ad40..0000000
--- a/brand/theme.css
+++ /dev/null
@@ -1,371 +0,0 @@
-/* kitgit — brand theme
-   ink on paper. no chrome. */
-
-:root {
-  /* surfaces */
-  --kg-bg: #ffffff;
-  --kg-bg-raised: #f7f7f7;
-  --kg-fg: #0a0a0a;
-  --kg-muted: #6b6b6b;
-  --kg-faint: #a3a3a3;
-  --kg-line: #e5e5e5;
-  --kg-line-strong: #0a0a0a;
-
-  /* interactive */
-  --kg-link: #0a0a0a;
-  --kg-link-hover: #0a0a0a;
-  --kg-focus: #0a0a0a;
-  --kg-danger: #0a0a0a;
-
-  /* invert block (nav, code, CTA) */
-  --kg-invert-bg: #0a0a0a;
-  --kg-invert-fg: #ffffff;
-  --kg-invert-muted: #a3a3a3;
-
-  /* type */
-  --kg-font: "JetBrainsMono Nerd Font", "JetBrainsMono NF", "JetBrains Mono",
-    "FiraCode Nerd Font", "Fira Code", "SauceCodePro Nerd Font",
-    ui-monospace, "Cascadia Code", "SF Mono", Menlo, Consolas, monospace;
-  --kg-font-size: 14px;
-  --kg-line-height: 1.55;
-  --kg-tracking-tight: -0.02em;
-  --kg-tracking-brand: 0.08em;
-
-  /* rhythm */
-  --kg-space-1: 0.25rem;
-  --kg-space-2: 0.5rem;
-  --kg-space-3: 1rem;
-  --kg-space-4: 1.5rem;
-  --kg-space-5: 2.5rem;
-  --kg-space-6: 4rem;
-  --kg-max: 75rem;
-  --kg-radius: 0;
-
-  color-scheme: light;
-}
-
-/* optional dark invert — add class="kg-dark" on <html> */
-html.kg-dark {
-  --kg-bg: #0a0a0a;
-  --kg-bg-raised: #141414;
-  --kg-fg: #f5f5f5;
-  --kg-muted: #8a8a8a;
-  --kg-faint: #5c5c5c;
-  --kg-line: #262626;
-  --kg-line-strong: #f5f5f5;
-  --kg-link: #f5f5f5;
-  --kg-invert-bg: #f5f5f5;
-  --kg-invert-fg: #0a0a0a;
-  --kg-invert-muted: #525252;
-  color-scheme: dark;
-}
-
-*,
-*::before,
-*::after {
-  box-sizing: border-box;
-}
-
-html {
-  font-size: var(--kg-font-size);
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-rendering: optimizeLegibility;
-}
-
-body {
-  margin: 0;
-  min-height: 100vh;
-  font-family: var(--kg-font);
-  font-size: 1rem;
-  line-height: var(--kg-line-height);
-  color: var(--kg-fg);
-  background: var(--kg-bg);
-}
-
-/* —— wordmark —— */
-.kg-mark {
-  display: inline-flex;
-  align-items: baseline;
-  gap: 0.55em;
-  font-family: var(--kg-font);
-  font-weight: 700;
-  font-size: 1.125rem;
-  letter-spacing: var(--kg-tracking-brand);
-  text-transform: lowercase;
-  text-decoration: none;
-  color: var(--kg-fg);
-  line-height: 1;
-}
-
-.kg-mark__glyph {
-  display: inline-block;
-  flex-shrink: 0;
-  align-self: center;
-  font-weight: 700;
-  letter-spacing: 0;
-  font-size: 1.15em;
-  line-height: 1;
-  speak: never;
-}
-
-.kg-mark__name {
-  letter-spacing: var(--kg-tracking-brand);
-}
-
-.kg-mark--hero {
-  font-size: clamp(2.5rem, 8vw, 4.5rem);
-  letter-spacing: 0.06em;
-}
-
-.kg-mark--hero .kg-mark__glyph {
-  font-size: 1.05em;
-}
-
-/* —— type scale —— */
-.kg-display {
-  font-family: var(--kg-font);
-  font-weight: 700;
-  font-size: clamp(1.75rem, 4vw, 2.25rem);
-  line-height: 1.2;
-  letter-spacing: var(--kg-tracking-tight);
-  margin: 0;
-}
-
-.kg-title {
-  font-weight: 700;
-  font-size: 1.125rem;
-  letter-spacing: var(--kg-tracking-tight);
-  margin: 0;
-}
-
-.kg-body {
-  margin: 0;
-  color: var(--kg-fg);
-}
-
-.kg-muted {
-  color: var(--kg-muted);
-}
-
-.kg-faint {
-  color: var(--kg-faint);
-}
-
-.kg-mono {
-  font-family: var(--kg-font);
-}
-
-.kg-kicker {
-  display: block;
-  font-size: 0.75rem;
-  letter-spacing: 0.14em;
-  text-transform: uppercase;
-  color: var(--kg-muted);
-  margin: 0 0 var(--kg-space-3);
-}
-
-/* —— links & controls —— */
-a.kg-link {
-  color: var(--kg-link);
-  text-decoration: underline;
-  text-underline-offset: 0.2em;
-  text-decoration-thickness: 1px;
-}
-
-a.kg-link:hover {
-  text-decoration-thickness: 2px;
-}
-
-.kg-btn {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  gap: 0.5em;
-  padding: 0.65em 1.1em;
-  font-family: var(--kg-font);
-  font-size: 0.875rem;
-  font-weight: 500;
-  letter-spacing: 0.02em;
-  line-height: 1;
-  border: 1px solid var(--kg-line-strong);
-  border-radius: var(--kg-radius);
-  background: var(--kg-invert-bg);
-  color: var(--kg-invert-fg);
-  text-decoration: none;
-  cursor: pointer;
-  transition: opacity 120ms ease;
-}
-
-.kg-btn:hover {
-  opacity: 0.85;
-}
-
-.kg-btn:focus-visible {
-  outline: 2px solid var(--kg-focus);
-  outline-offset: 3px;
-}
-
-.kg-btn--ghost {
-  background: transparent;
-  color: var(--kg-fg);
-}
-
-.kg-btn--ghost:hover {
-  opacity: 1;
-  background: var(--kg-bg-raised);
-}
-
-.kg-actions {
-  display: flex;
-  flex-wrap: wrap;
-  gap: var(--kg-space-2);
-}
-
-/* —— structure —— */
-.kg-shell {
-  width: min(100% - 2rem, var(--kg-max));
-  margin-inline: auto;
-}
-
-.kg-rule {
-  border: 0;
-  border-top: 1px solid var(--kg-line);
-  margin: var(--kg-space-5) 0;
-}
-
-.kg-rule--strong {
-  border-top-color: var(--kg-line-strong);
-}
-
-.kg-bar {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: var(--kg-space-3);
-  padding: var(--kg-space-3) 0;
-  border-bottom: 1px solid var(--kg-line);
-}
-
-.kg-bar nav {
-  display: flex;
-  gap: var(--kg-space-3);
-}
-
-.kg-bar nav a {
-  color: var(--kg-muted);
-  text-decoration: none;
-  font-size: 0.8125rem;
-  letter-spacing: 0.04em;
-}
-
-.kg-bar nav a:hover,
-.kg-bar nav a[aria-current="page"] {
-  color: var(--kg-fg);
-}
-
-.kg-footer {
-  padding: var(--kg-space-4) 0 var(--kg-space-6);
-  color: var(--kg-faint);
-  font-size: 0.75rem;
-  letter-spacing: 0.06em;
-}
-
-/* code / terminal strip */
-.kg-code {
-  display: block;
-  padding: var(--kg-space-3) var(--kg-space-4);
-  background: var(--kg-invert-bg);
-  color: var(--kg-invert-fg);
-  font-family: var(--kg-font);
-  font-size: 0.8125rem;
-  line-height: 1.6;
-  overflow-x: auto;
-  border-radius: var(--kg-radius);
-}
-
-.kg-code .cm {
-  color: var(--kg-invert-muted);
-}
-
-.kg-code .ok {
-  color: var(--kg-invert-fg);
-}
-
-/* token swatches (brand preview only) */
-.kg-swatches {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
-  gap: var(--kg-space-2);
-}
-
-.kg-swatch {
-  aspect-ratio: 1;
-  border: 1px solid var(--kg-line);
-  display: flex;
-  flex-direction: column;
-  justify-content: flex-end;
-  padding: var(--kg-space-2);
-  font-size: 0.6875rem;
-  letter-spacing: 0.04em;
-}
-
-.kg-swatch span {
-  background: color-mix(in srgb, var(--kg-bg) 85%, transparent);
-  padding: 0.1em 0.25em;
-}
-
-/* motion — presence, not noise */
-@keyframes kg-ink {
-  from {
-    opacity: 0;
-    transform: translateY(0.4rem);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
-}
-
-@keyframes kg-caret {
-  0%,
-  49% {
-    opacity: 1;
-  }
-  50%,
-  100% {
-    opacity: 0;
-  }
-}
-
-.kg-in {
-  animation: kg-ink 480ms ease both;
-}
-
-.kg-in-2 {
-  animation: kg-ink 480ms ease 80ms both;
-}
-
-.kg-in-3 {
-  animation: kg-ink 480ms ease 160ms both;
-}
-
-.kg-caret::after {
-  content: "█";
-  margin-left: 0.15em;
-  animation: kg-caret 1.1s step-end infinite;
-  font-weight: 400;
-}
-
-@media (prefers-reduced-motion: reduce) {
-  .kg-in,
-  .kg-in-2,
-  .kg-in-3 {
-    animation: none;
-  }
-
-  .kg-caret::after {
-    animation: none;
-    opacity: 1;
-  }
-}
diff --git a/deploy/.env.prod.example b/deploy/.env.prod.example
index 6213ae9..ea5b48e 100644
--- a/deploy/.env.prod.example
+++ b/deploy/.env.prod.example
@@ -5,6 +5,8 @@ AUTHENTIK_BOOTSTRAP_PASSWORD=
 AUTHENTIK_BOOTSTRAP_EMAIL=admin@tirbo.fish
 
 KITGIT_PUBLIC_URL=https://git.tirbo.fish
+# Optional; prod compose sets this to 22 so clone URLs omit a non-default port.
+# KITGIT_SSH_PUBLIC_PORT=22
 KITGIT_SESSION_SECRET=
 KITGIT_OIDC_ISSUER=https://auth.tirbo.fish/application/o/kitgit/
 KITGIT_OIDC_DISCOVERY_URL=http://authentik-server:9000/application/o/kitgit/
diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml
index 154b8d5..0d24f1b 100644
--- a/deploy/docker-compose.prod.yml
+++ b/deploy/docker-compose.prod.yml
@@ -100,6 +100,8 @@ services:
       KITGIT_DATA_DIR: /data
       KITGIT_HTTP_BIND: 0.0.0.0:8080
       KITGIT_SSH_BIND: 0.0.0.0:2222
+      # Advertise classic git@host:… URLs (host publishes 22 → container 2222).
+      KITGIT_SSH_PUBLIC_PORT: "22"
       KITGIT_PUBLIC_URL: ${KITGIT_PUBLIC_URL}
       KITGIT_SESSION_SECRET: ${KITGIT_SESSION_SECRET}
       KITGIT_OIDC_ISSUER: ${KITGIT_OIDC_ISSUER}
@@ -115,7 +117,8 @@ services:
     volumes:
       - kitgit-data:/data
     ports:
-      - "2222:2222"
+      # Host 22 = Git SSH (GitHub-style). Droplet admin sshd listens on 2222.
+      - "22:2222"
     depends_on:
       postgres:
         condition: service_healthy
diff --git a/docs/environment.md b/docs/environment.md
index 38f5c55..eb0d170 100644
--- a/docs/environment.md
+++ b/docs/environment.md
@@ -4,7 +4,8 @@
 |----------|---------|
 | `KITGIT_DATABASE_URL` | Postgres |
 | `KITGIT_DATA_DIR` | Repos, avatars, SSH host key, releases |
-| `KITGIT_HTTP_BIND` / `KITGIT_SSH_BIND` | Listen addresses |
+| `KITGIT_HTTP_BIND` / `KITGIT_SSH_BIND` | Listen addresses (container-internal) |
+| `KITGIT_SSH_PUBLIC_PORT` | Port in clone URLs / `ssh git@host` (default: bind port; use `22` when published as `22:2222`) |
 | `KITGIT_PUBLIC_URL` | Public base URL for clone links |
 | `KITGIT_SESSION_SECRET` | Session cookie secret |
 | `KITGIT_OIDC_ISSUER` | Public issuer (browser), e.g. `http://localhost:9000/application/o/kitgit/` |
diff --git a/docs/production.md b/docs/production.md
index 8399381..ea8b926 100644
--- a/docs/production.md
+++ b/docs/production.md
@@ -6,7 +6,8 @@ Minimal single-Droplet deploy under the **personal** project:
 |----------|-------|
 | Droplet | `kitgit` · `s-2vcpu-4gb` · `syd1` · Docker 1-Click |
 | Sites | https://git.tirbo.fish · https://auth.tirbo.fish |
-| Git SSH | `git.tirbo.fish:2222` |
+| Git SSH | `git@git.tirbo.fish` (host port 22 → kitgit) |
+| Admin SSH | droplet port **2222** (system `sshd`; not Git) |
 | Cost | ~$24/mo (droplet only; no managed DB/LB/registry) |
 
 On the droplet, the stack lives in `/opt/kitgit` and is started with:
@@ -17,3 +18,5 @@ docker compose up -d --build
 ```
 
 Prod files: `deploy/docker-compose.prod.yml`, `deploy/Caddyfile`, `deploy/.env.prod.example`, `deploy/authentik/blueprints-prod/`.
+
+**SSH layout:** Git uses host port **22** (`ssh git@git.tirbo.fish`). Droplet admin `sshd` is on **2222** (`ssh -p 2222 root@…`).
diff --git a/src/auth.rs b/src/auth.rs
index f34f61a..d60d2c7 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -105,9 +105,9 @@ pub fn hash_token(token: &str) -> String {
 }
 
 pub fn new_session_token() -> String {
-    use rand::RngCore;
+    use rand::RngExt;
     let mut buf = [0u8; 32];
-    rand::thread_rng().fill_bytes(&mut buf);
+    rand::rng().fill(&mut buf);
     hex::encode(buf)
 }
 
diff --git a/src/config.rs b/src/config.rs
index ce341e7..2da9705 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -23,6 +23,11 @@ pub struct Config {
     #[arg(long, env = "KITGIT_SSH_BIND", default_value = "0.0.0.0:2222")]
     pub ssh_bind: SocketAddr,
 
+    /// Port advertised in clone URLs / `ssh git@host`. Defaults to the
+    /// `KITGIT_SSH_BIND` port. Set to `22` in production when published as `22:2222`.
+    #[arg(long, env = "KITGIT_SSH_PUBLIC_PORT", default_value_t = 0)]
+    pub ssh_public_port: u16,
+
     #[arg(long, env = "KITGIT_PUBLIC_URL", default_value = "http://localhost:8080")]
     pub public_url: String,
 
@@ -163,4 +168,13 @@ impl Config {
     pub fn ssh_host_key_path(&self) -> PathBuf {
         self.data_dir.join("ssh_host_ed25519_key")
     }
+
+    /// External SSH port for clone URLs. Falls back to the bind port when unset (0).
+    pub fn ssh_advertise_port(&self) -> u16 {
+        if self.ssh_public_port == 0 {
+            self.ssh_bind.port()
+        } else {
+            self.ssh_public_port
+        }
+    }
 }
diff --git a/src/git/repo.rs b/src/git/repo.rs
index b0757f2..06e53dd 100644
--- a/src/git/repo.rs
+++ b/src/git/repo.rs
@@ -240,6 +240,23 @@ pub fn read_blob(repo: &G2Repo, reference: &str, path: &str) -> Result<(Vec<u8>,
     Ok((data, binary))
 }
 
+/// Whether `path` is a tree (directory) at `reference`. Missing paths are not trees.
+pub fn path_is_dir(repo: &G2Repo, reference: &str, path: &str) -> bool {
+    if path.is_empty() {
+        return true;
+    }
+    let Ok(commit) = repo.revparse_single(reference).and_then(|o| o.peel_to_commit()) else {
+        return false;
+    };
+    let Ok(tree) = commit.tree() else {
+        return false;
+    };
+    let Ok(entry) = tree.get_path(Path::new(path)) else {
+        return false;
+    };
+    entry.kind() == Some(ObjectType::Tree)
+}
+
 /// Find a README in `dir` only (GitHub-style). Empty `dir` = repo root.
 /// Does not fall back to parent/root READMEs when browsing a subdirectory.
 pub fn find_readme(
diff --git a/src/git/ssh.rs b/src/git/ssh.rs
index 6a622a9..570b77a 100644
--- a/src/git/ssh.rs
+++ b/src/git/ssh.rs
@@ -3,9 +3,9 @@ use crate::git::repo::bare_path;
 use crate::state::AppState;
 use anyhow::{Context, Result};
 use base64::Engine;
-use russh::keys::{decode_secret_key, encode_pkcs8_pem, PublicKey as RPublicKey, PublicKeyBase64};
-use russh::server::{Auth, Handler, Msg, Server, Session};
-use russh::{Channel, ChannelId, CryptoVec, MethodKind, MethodSet};
+use russh::keys::{decode_secret_key, encode_pkcs8_pem, PublicKeyBase64};
+use russh::server::{Auth, ChannelOpenHandle, Handler, Msg, Server, Session};
+use russh::{Channel, ChannelId, MethodKind, MethodSet};
 use sha2::{Digest, Sha256};
 use std::path::PathBuf;
 use std::process::Stdio;
@@ -20,6 +20,13 @@ fn publickey_methods() -> MethodSet {
     m
 }
 
+fn auth_reject() -> Auth {
+    Auth::Reject {
+        proceed_with_methods: Some(publickey_methods()),
+        partial_success: false,
+    }
+}
+
 pub async fn run_ssh(state: AppState) -> Result<()> {
     std::fs::create_dir_all(&state.config.data_dir)?;
     let key_path = state.config.ssh_host_key_path();
@@ -30,6 +37,8 @@ pub async fn run_ssh(state: AppState) -> Result<()> {
         auth_rejection_time: std::time::Duration::from_secs(1),
         keys: vec![key],
         methods: publickey_methods(),
+        // Default Preferred leads with mlkem768x25519-sha256 (PQ hybrid KEX).
+        preferred: russh::Preferred::DEFAULT,
         ..Default::default()
     };
     let bind = state.config.ssh_bind;
@@ -46,7 +55,7 @@ fn load_or_create_host_key(path: &PathBuf) -> Result<russh::keys::PrivateKey> {
         return decode_secret_key(&data, None).context("decode host key");
     }
     let key = russh::keys::PrivateKey::random(
-        &mut rand::thread_rng(),
+        &mut rand::rng(),
         russh::keys::Algorithm::Ed25519,
     )?;
     let mut pem = Vec::new();
@@ -64,7 +73,7 @@ pub fn fingerprint_ssh_pubkey(line: &str) -> Result<String> {
     Ok(ssh_fingerprint_sha256(&raw))
 }
 
-fn fingerprint_public_key(key: &RPublicKey) -> String {
+fn fingerprint_public_key(key: &russh::keys::PublicKey) -> String {
     // Must match the authorized_keys blob (type || key material), same as
     // OpenSSH `ssh-keygen -lf` SHA256 fingerprints (base64, no padding).
     let blob = key.public_key_bytes();
@@ -91,6 +100,7 @@ impl Server for SshServer {
         SshHandler {
             state: self.state.clone(),
             user_id: None,
+            username: None,
             stdin: None,
             receive_meta: None,
         }
@@ -100,27 +110,55 @@ impl Server for SshServer {
 struct SshHandler {
     state: AppState,
     user_id: Option<Uuid>,
+    username: Option<String>,
     stdin: Option<Arc<Mutex<tokio::process::ChildStdin>>>,
     receive_meta: Option<(String, String)>,
 }
 
+impl SshHandler {
+    /// Print `static/text.txt` (with `{user}` substituted) and close — no shell access.
+    async fn greet_and_quit(
+        &self,
+        channel: ChannelId,
+        session: &mut Session,
+    ) -> Result<(), anyhow::Error> {
+        let username = self.username.as_deref().unwrap_or("git");
+        let path = self.state.config.static_dir.join("text.txt");
+        let mut msg = tokio::fs::read_to_string(&path).await.unwrap_or_else(|_| {
+            format!(
+                "Hi {username}! You've successfully authenticated, but kitgit does not provide shell access.\n"
+            )
+        });
+        msg = msg.replace("{user}", username);
+        if !msg.ends_with('\n') {
+            msg.push('\n');
+        }
+        session.channel_success(channel)?;
+        session.data(channel, msg.into_bytes())?;
+        // Match GitHub: non-zero exit so clients don't treat this as a shell login.
+        session.exit_status_request(channel, 1)?;
+        session.eof(channel)?;
+        session.close(channel)?;
+        Ok(())
+    }
+}
+
 impl Handler for SshHandler {
     type Error = anyhow::Error;
 
     async fn auth_publickey(
         &mut self,
         _user: &str,
-        public_key: &RPublicKey,
+        public_key: &russh::keys::PublicKey,
     ) -> Result<Auth, Self::Error> {
         let fp = fingerprint_public_key(public_key);
         if let Some(user) = queries::user_by_ssh_fingerprint(&self.state.pool, &fp).await? {
             self.user_id = Some(user.id);
+            self.username = Some(user.username);
             return Ok(Auth::Accept);
         }
         tracing::debug!("ssh publickey rejected fp={fp}");
-        Ok(Auth::Reject {
-            proceed_with_methods: Some(publickey_methods()),
-        })
+        Ok(auth_reject())
     }
 
     async fn auth_password(
@@ -129,17 +167,45 @@ impl Handler for SshHandler {
         _password: &str,
     ) -> Result<Auth, Self::Error> {
         // Password auth is not supported — keys only.
-        Ok(Auth::Reject {
-            proceed_with_methods: Some(publickey_methods()),
-        })
+        Ok(auth_reject())
     }
 
     async fn channel_open_session(
         &mut self,
         _channel: Channel<Msg>,
+        reply: ChannelOpenHandle,
         _session: &mut Session,
-    ) -> Result<bool, Self::Error> {
-        Ok(true)
+    ) -> Result<(), Self::Error> {
+        reply.accept().await;
+        Ok(())
+    }
+
+    async fn pty_request(
+        &mut self,
+        channel: ChannelId,
+        _term: &str,
+        _col_width: u32,
+        _row_height: u32,
+        _pix_width: u32,
+        _pix_height: u32,
+        _modes: &[(russh::Pty, u32)],
+        session: &mut Session,
+    ) -> Result<(), Self::Error> {
+        // Accept PTY so interactive `ssh git@host` can proceed to shell_request.
+        session.channel_success(channel)?;
+        Ok(())
+    }
+
+    async fn shell_request(
+        &mut self,
+        channel: ChannelId,
+        session: &mut Session,
+    ) -> Result<(), Self::Error> {
+        if self.user_id.is_none() {
+            session.channel_failure(channel)?;
+            return Ok(());
+        }
+        self.greet_and_quit(channel, session).await
     }
 
     async fn exec_request(
@@ -157,16 +223,13 @@ impl Handler for SshHandler {
         let (service, repo_path) = match parse_git_command(&cmd) {
             Ok(v) => v,
             Err(_) => {
-                session.data(channel, CryptoVec::from_slice(b"unsupported command\n"))?;
-                session.exit_status_request(channel, 1)?;
-                session.eof(channel)?;
-                session.close(channel)?;
-                return Ok(());
+                // Non-git exec (e.g. `ssh host exit`) — same greeting as a bare SSH.
+                return self.greet_and_quit(channel, session).await;
             }
         };
         let (owner, name) = split_repo_path(&repo_path)?;
         let Some((repo, _)) = queries::get_repo(&self.state.pool, &owner, &name).await? else {
-            session.data(channel, CryptoVec::from_slice(b"repository not found\n"))?;
+            session.data(channel, b"repository not found\n".as_slice())?;
             session.exit_status_request(channel, 1)?;
             session.eof(channel)?;
             session.close(channel)?;
@@ -179,7 +242,7 @@ impl Handler for SshHandler {
             _ => false,
         };
         if !allowed {
-            session.data(channel, CryptoVec::from_slice(b"access denied\n"))?;
+            session.data(channel, b"access denied\n".as_slice())?;
             session.exit_status_request(channel, 1)?;
             session.eof(channel)?;
             session.close(channel)?;
@@ -208,7 +271,7 @@ impl Handler for SshHandler {
                 match stdout.read(&mut buf).await {
                     Ok(0) => break,
                     Ok(n) => {
-                        let _ = handle.data(channel, CryptoVec::from_slice(&buf[..n])).await;
+                        let _ = handle.data(channel, buf[..n].to_vec()).await;
                     }
                     Err(_) => break,
                 }
diff --git a/src/markdown.rs b/src/markdown.rs
index af41092..c570530 100644
--- a/src/markdown.rs
+++ b/src/markdown.rs
@@ -1,9 +1,38 @@
-use pulldown_cmark::{html, Options, Parser};
+use pulldown_cmark::{Event, Options, Parser, Tag, html};
 use std::borrow::Cow;
 
+/// Where a Markdown file lives inside a repository, used to rewrite relative
+/// links/images so they resolve under `/{owner}/{repo}/…` instead of replacing
+/// the repo name in the browser URL (e.g. `/owner/docs`).
+#[derive(Clone, Copy, Debug)]
+pub struct MarkdownRepoBase<'a> {
+    pub owner: &'a str,
+    pub repo: &'a str,
+    pub git_ref: &'a str,
+    /// Directory containing the Markdown file (`""` for repo root).
+    pub dir: &'a str,
+}
+
 /// Render Markdown to HTML. Protects TeX math from Markdown emphasis/escaping,
 /// then leaves `$…$` / `$$…$$` for client-side KaTeX (+ KaTeX fonts).
 pub fn render_markdown(src: &str) -> String {
+    render_markdown_inner(src, None)
+}
+
+/// Like [`render_markdown`], but rewrites relative links and images against a
+/// repository path. `is_dir` should return true when `path` is a tree entry.
+pub fn render_markdown_in_repo(
+    src: &str,
+    base: &MarkdownRepoBase<'_>,
+    is_dir: impl Fn(&str) -> bool,
+) -> String {
+    render_markdown_inner(src, Some((base, &is_dir)))
+}
+
+fn render_markdown_inner(
+    src: &str,
+    repo: Option<(&MarkdownRepoBase<'_>, &dyn Fn(&str) -> bool)>,
+) -> String {
     let src = src.strip_prefix('\u{feff}').unwrap_or(src);
     let src = preprocess_math_fences(src);
     let (protected, slots) = protect_math(&src);
@@ -14,10 +43,147 @@ pub fn render_markdown(src: &str) -> String {
     opts.insert(Options::ENABLE_FOOTNOTES);
     let parser = Parser::new_ext(&protected, opts);
     let mut out = String::new();
-    html::push_html(&mut out, parser);
+    if let Some((base, is_dir)) = repo {
+        let events = parser.map(|ev| rewrite_event(ev, base, is_dir));
+        html::push_html(&mut out, events);
+    } else {
+        html::push_html(&mut out, parser);
+    }
     restore_math(&out, &slots)
 }
 
+fn rewrite_event<'a>(
+    event: Event<'a>,
+    base: &MarkdownRepoBase<'_>,
+    is_dir: &dyn Fn(&str) -> bool,
+) -> Event<'a> {
+    match event {
+        Event::Start(Tag::Link {
+            link_type,
+            dest_url,
+            title,
+            id,
+        }) => Event::Start(Tag::Link {
+            link_type,
+            dest_url: rewrite_repo_url(&dest_url, base, is_dir, false).into(),
+            title,
+            id,
+        }),
+        Event::Start(Tag::Image {
+            link_type,
+            dest_url,
+            title,
+            id,
+        }) => Event::Start(Tag::Image {
+            link_type,
+            dest_url: rewrite_repo_url(&dest_url, base, is_dir, true).into(),
+            title,
+            id,
+        }),
+        other => other,
+    }
+}
+
+fn rewrite_repo_url(
+    url: &str,
+    base: &MarkdownRepoBase<'_>,
+    is_dir: &dyn Fn(&str) -> bool,
+    image: bool,
+) -> String {
+    if !is_relative_repo_path(url) {
+        return url.to_string();
+    }
+
+    let (path_part, suffix) = split_url_suffix(url);
+    let resolved = resolve_repo_path(base.dir, path_part);
+
+    if image {
+        if resolved.is_empty() {
+            return format!(
+                "/{}/{}/tree/{}{suffix}",
+                base.owner, base.repo, base.git_ref
+            );
+        }
+        return format!(
+            "/{}/{}/raw/{}/{}{suffix}",
+            base.owner, base.repo, base.git_ref, resolved
+        );
+    }
+
+    if resolved.is_empty() {
+        return format!(
+            "/{}/{}/tree/{}{suffix}",
+            base.owner, base.repo, base.git_ref
+        );
+    }
+
+    let kind = if path_part.ends_with('/') || is_dir(&resolved) {
+        "tree"
+    } else {
+        "blob"
+    };
+    format!(
+        "/{}/{}/{}/{}/{}{suffix}",
+        base.owner, base.repo, kind, base.git_ref, resolved
+    )
+}
+
+fn is_relative_repo_path(url: &str) -> bool {
+    if url.is_empty() || url.starts_with('#') {
+        return false;
+    }
+    if url.starts_with('/') || url.starts_with("//") {
+        return false;
+    }
+    if url.contains("://") {
+        return false;
+    }
+    let scheme = url.split_once(':').map(|(s, _)| s);
+    if let Some(s) = scheme {
+        // `mailto:`, `tel:`, etc. — but allow Windows-ish drive letters? skip.
+        if s.chars().all(|c| c.is_ascii_alphabetic()) && s.len() > 1 {
+            return false;
+        }
+    }
+    true
+}
+
+fn split_url_suffix(url: &str) -> (&str, &str) {
+    let bytes = url.as_bytes();
+    for (i, &b) in bytes.iter().enumerate() {
+        if b == b'?' || b == b'#' {
+            return (&url[..i], &url[i..]);
+        }
+    }
+    (url, "")
+}
+
+/// Join `rel` onto `dir` with `.` / `..` normalization (POSIX-style).
+fn resolve_repo_path(dir: &str, rel: &str) -> String {
+    let mut stack: Vec<&str> = dir
+        .split('/')
+        .filter(|s| !s.is_empty())
+        .collect();
+    for part in rel.split('/') {
+        match part {
+            "" | "." => {}
+            ".." => {
+                stack.pop();
+            }
+            p => stack.push(p),
+        }
+    }
+    stack.join("/")
+}
+
+/// Parent directory of a repo-relative file path (`""` for root files).
+pub fn parent_dir(path: &str) -> &str {
+    match path.rsplit_once('/') {
+        Some((dir, _)) => dir,
+        None => "",
+    }
+}
+
 fn preprocess_math_fences(src: &str) -> String {
     let mut out = String::with_capacity(src.len());
     let mut lines = src.lines().peekable();
@@ -206,4 +372,98 @@ mod tests {
         assert!(html.contains("$$"), "{html}");
         assert!(html.contains("\\frac{a}{b}"), "{html}");
     }
+
+    #[test]
+    fn relative_link_to_docs_dir_uses_tree() {
+        let base = MarkdownRepoBase {
+            owner: "tirbofish",
+            repo: "kitgit",
+            git_ref: "main",
+            dir: "",
+        };
+        let html = render_markdown_in_repo(
+            "see the [docs](docs) folder",
+            &base,
+            |p| p == "docs",
+        );
+        assert!(
+            html.contains(r#"href="/tirbofish/kitgit/tree/main/docs""#),
+            "{html}"
+        );
+        assert!(!html.contains(r#"href="docs""#), "{html}");
+    }
+
+    #[test]
+    fn relative_link_to_file_uses_blob() {
+        let base = MarkdownRepoBase {
+            owner: "tirbofish",
+            repo: "kitgit",
+            git_ref: "main",
+            dir: "",
+        };
+        let html = render_markdown_in_repo(
+            "[guide](docs/production.md)",
+            &base,
+            |_| false,
+        );
+        assert!(
+            html.contains(r#"href="/tirbofish/kitgit/blob/main/docs/production.md""#),
+            "{html}"
+        );
+    }
+
+    #[test]
+    fn relative_link_resolves_from_file_dir() {
+        let base = MarkdownRepoBase {
+            owner: "o",
+            repo: "r",
+            git_ref: "main",
+            dir: "docs",
+        };
+        let html = render_markdown_in_repo("[x](./brand.md)", &base, |_| false);
+        assert!(
+            html.contains(r#"href="/o/r/blob/main/docs/brand.md""#),
+            "{html}"
+        );
+    }
+
+    #[test]
+    fn relative_image_uses_raw() {
+        let base = MarkdownRepoBase {
+            owner: "o",
+            repo: "r",
+            git_ref: "main",
+            dir: "",
+        };
+        let html = render_markdown_in_repo("![logo](static/logo.png)", &base, |_| false);
+        assert!(
+            html.contains(r#"src="/o/r/raw/main/static/logo.png""#),
+            "{html}"
+        );
+    }
+
+    #[test]
+    fn absolute_and_fragment_links_untouched() {
+        let base = MarkdownRepoBase {
+            owner: "o",
+            repo: "r",
+            git_ref: "main",
+            dir: "",
+        };
+        let html = render_markdown_in_repo(
+            "[a](https://example.com) [b](#section) [c](/already/absolute)",
+            &base,
+            |_| false,
+        );
+        assert!(html.contains("href=\"https://example.com\""), "{html}");
+        assert!(html.contains("href=\"#section\""), "{html}");
+        assert!(html.contains("href=\"/already/absolute\""), "{html}");
+    }
+
+    #[test]
+    fn parent_dir_helpers() {
+        assert_eq!(parent_dir("README.md"), "");
+        assert_eq!(parent_dir("docs/production.md"), "docs");
+        assert_eq!(parent_dir("a/b/c.md"), "a/b");
+    }
 }
diff --git a/src/mfa.rs b/src/mfa.rs
index b857b2b..8721157 100644
--- a/src/mfa.rs
+++ b/src/mfa.rs
@@ -6,7 +6,7 @@ use data_encoding::BASE32_NOPAD;
 use hmac::{Hmac, Mac};
 use qrcode::render::svg;
 use qrcode::QrCode;
-use rand::RngCore;
+use rand::RngExt;
 use sha1::Sha1;
 use sha2::{Digest, Sha256};
 use std::time::{SystemTime, UNIX_EPOCH};
@@ -20,7 +20,7 @@ const RECOVERY_CODE_COUNT: usize = 8;
 
 pub fn generate_totp_secret() -> String {
     let mut buf = [0u8; 20];
-    rand::thread_rng().fill_bytes(&mut buf);
+    rand::rng().fill(&mut buf);
     BASE32_NOPAD.encode(&buf)
 }
 
@@ -104,10 +104,10 @@ pub fn verify_totp(secret_base32: &str, code: &str) -> bool {
 
 pub fn generate_recovery_codes() -> Vec<String> {
     let mut out = Vec::with_capacity(RECOVERY_CODE_COUNT);
-    let mut rng = rand::thread_rng();
+    let mut rng = rand::rng();
     for _ in 0..RECOVERY_CODE_COUNT {
         let mut buf = [0u8; 5];
-        rng.fill_bytes(&mut buf);
+        rng.fill(&mut buf);
         let hex = hex::encode(buf);
         out.push(format!("{}-{}", &hex[..5], &hex[5..]));
     }
diff --git a/src/web/routes.rs b/src/web/routes.rs
index 1e06180..492b99a 100644
--- a/src/web/routes.rs
+++ b/src/web/routes.rs
@@ -7,7 +7,7 @@ use crate::db::queries;
 use crate::git;
 use crate::git::ssh::fingerprint_ssh_pubkey;
 use crate::highlight::highlight;
-use crate::markdown::render_markdown;
+use crate::markdown::{MarkdownRepoBase, parent_dir, render_markdown, render_markdown_in_repo};
 use crate::state::AppState;
 use crate::web::templates::*;
 use axum::extract::{Form, Multipart, Path, Query, State};
@@ -265,9 +265,23 @@ fn html_escape(s: &str) -> String {
         .collect()
 }
 
-fn readme_to_html(name: &str, src: &str) -> String {
+fn readme_to_html(
+    name: &str,
+    src: &str,
+    owner: &str,
+    repo: &str,
+    git_ref: &str,
+    dir: &str,
+    grepo: &git2::Repository,
+) -> String {
     if name.ends_with(".md") || name.ends_with(".MD") {
-        render_markdown(src)
+        let base = MarkdownRepoBase {
+            owner,
+            repo,
+            git_ref,
+            dir,
+        };
+        render_markdown_in_repo(src, &base, |path| git::path_is_dir(grepo, git_ref, path))
     } else {
         format!("<pre>{}</pre>", html_escape(src))
     }
@@ -600,10 +614,14 @@ fn clone_urls(state: &AppState, owner: &str, repo: &str) -> (String, String) {
         .ok()
         .and_then(|u| u.host_str().map(|h| h.to_string()))
         .unwrap_or_else(|| "localhost".into());
-    let port = state.config.ssh_bind.port();
-    // Always use ssh:// form. `git@host:2222/path` is parsed as host=host path=2222/path
-    // on port 22 — which hits the system sshd and asks for a password.
-    let ssh = format!("ssh://git@{host}:{port}/{owner}/{repo}.git");
+    let port = state.config.ssh_advertise_port();
+    // Port 22: GitHub-style `git@host:owner/repo.git` (default SSH port).
+    // Other ports: ssh:// form — `git@host:2222/path` is parsed as path `2222/path` on port 22.
+    let ssh = if port == 22 {
+        format!("git@{host}:{owner}/{repo}.git")
+    } else {
+        format!("ssh://git@{host}:{port}/{owner}/{repo}.git")
+    };
     (http, ssh)
 }
 
@@ -1507,7 +1525,15 @@ pub async fn repo_home(
                 })
                 .collect();
             if let Ok(Some((name, src))) = git::find_readme(g, &current_branch, "") {
-                readme_html = Some(readme_to_html(&name, &src));
+                readme_html = Some(readme_to_html(
+                    &name,
+                    &src,
+                    &owner,
+                    &repo,
+                    &current_branch,
+                    "",
+                    g,
+                ));
             }
             if languages.as_object().map(|o| o.is_empty()).unwrap_or(true) {
                 if let Ok(files) = git::walk_files(g, &current_branch) {
@@ -1590,7 +1616,15 @@ pub async fn repo_tree(
         })
         .collect();
     let readme_html = match git::find_readme(&grepo, &branch, &path) {
-        Ok(Some((name, src))) => Some(readme_to_html(&name, &src)),
+        Ok(Some((name, src))) => Some(readme_to_html(
+            &name,
+            &src,
+            &owner,
+            &repo,
+            &branch,
+            &path,
+            &grepo,
+        )),
         _ => None,
     };
     let branches = git::list_branches(&grepo).unwrap_or_default();
@@ -1634,7 +1668,16 @@ pub async fn repo_blob(
     } else {
         let text = String::from_utf8_lossy(&data);
         if path.ends_with(".md") || path.ends_with(".MD") {
-            (Some(render_markdown(&text)), true)
+            let base = MarkdownRepoBase {
+                owner: &owner,
+                repo: &repo,
+                git_ref: &branch,
+                dir: parent_dir(&path),
+            };
+            let html = render_markdown_in_repo(&text, &base, |p| {
+                git::path_is_dir(&grepo, &branch, p)
+            });
+            (Some(html), true)
         } else {
             (Some(highlight(&path, &text)), false)
         }
diff --git a/static/text.txt b/static/text.txt
new file mode 100644
index 0000000..aa65e62
--- /dev/null
+++ b/static/text.txt
@@ -0,0 +1,28 @@
+Hi {user}! You have been authenticated. Take a break, and read this poem...
+
+I heard a cough
+as if a thief was there
+outside my sleep
+a sharp intake of air
+
+a fox in her fox-fur
+stepping across
+the grass in her black gloves
+barked at my house
+
+just so abrupt and odd
+the way she went
+hungrily asking
+in the heart's thick accent
+
+in such serious sleepless
+trespass she came
+a woman with a man's voice
+but no name
+
+as if to say: it's midnight
+and my life
+is laid beneath my children
+like gold leaf
+
+Alice Oswald, "Fox" from Falling Awake
\ No newline at end of file