Support RSA keys and configure external ChuSQL submodules
This commit is contained in:
1 parent
a631d75cee
commit
d4caf05370
18 files changed
+648
-41
No files matched your search
@@ -0,0 +1,6 @@
|
||||
[submodule "external/ChuSQL"]
|
||||
path = external/ChuSQL
|
||||
url = ChuGit:/srv/git/ChuSQL.git
|
||||
[submodule "external/ChuSQL-RustDriver"]
|
||||
path = external/ChuSQL-RustDriver
|
||||
url = ChuGit:/srv/git/ChuSQL-RustDriver.git
|
||||
Generated
+369
@@ -108,6 +108,12 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@@ -215,6 +221,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"ssh-key",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tower",
|
||||
@@ -231,6 +238,22 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@@ -255,6 +278,18 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -265,6 +300,16 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -272,10 +317,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.16.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
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",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sec1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.3"
|
||||
@@ -307,6 +386,16 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.14"
|
||||
@@ -375,6 +464,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -408,12 +498,32 @@ dependencies = [
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.5.0"
|
||||
@@ -553,6 +663,15 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@@ -575,6 +694,9 @@ name = "lazy_static"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
@@ -582,6 +704,12 @@ version = "0.2.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
@@ -680,6 +808,41 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -687,6 +850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -695,6 +859,44 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p521"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -729,6 +931,15 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -741,6 +952,27 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
@@ -763,6 +995,15 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
@@ -874,6 +1115,37 @@ version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.5"
|
||||
@@ -905,6 +1177,20 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array",
|
||||
"pkcs8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
@@ -984,6 +1270,17 @@ dependencies = [
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -1009,6 +1306,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
@@ -1037,6 +1344,62 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh-cipher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"ssh-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh-encoding"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"pem-rfc7468",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh-key"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3"
|
||||
dependencies = [
|
||||
"p256",
|
||||
"p384",
|
||||
"p521",
|
||||
"rsa",
|
||||
"sec1",
|
||||
"sha2",
|
||||
"signature",
|
||||
"ssh-cipher",
|
||||
"ssh-encoding",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -1414,6 +1777,12 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
[workspace]
|
||||
members = ["apps/api", "crates/chusql-driver"]
|
||||
members = ["apps/api"]
|
||||
exclude = ["external/ChuSQL", "external/ChuSQL-RustDriver"]
|
||||
resolver = "3"
|
||||
@@ -1,18 +1,38 @@
|
||||
# ChuBlog
|
||||
|
||||
Rust/Axum 博客后端。文章作者在本地编辑 Markdown,通过受限 SSH Git 推送发布。注册、登录、权限管理使用 HTTP API;文章仅通过 Git 修改。ChuSQL 使用项目内复制的 `crates/chusql-driver`,只存可重建索引。
|
||||
Rust/Axum 博客后端。文章作者在本地编辑 Markdown,通过受限 SSH Git 推送发布。注册、登录、权限管理使用 HTTP API;文章仅通过 Git 修改。ChuSQL 通过外部子模块 `external/ChuSQL-RustDriver` 接入,只存可重建索引。
|
||||
|
||||
本轮按最新需求只实现后端,没有前端或在线文章管理。`PLAN.md` 保留原始规划,当前范围与后续安排见 [TODO.md](TODO.md)。
|
||||
|
||||
## 项目与运行
|
||||
|
||||
```text
|
||||
workspace/
|
||||
├── ChuBlog/ # 本项目
|
||||
│ └── crates/chusql-driver/ # 项目内的驱动副本
|
||||
└── ChuSQL/ # 现有数据库及已编译 Linux 产物
|
||||
ChuBlog/
|
||||
├── apps/api/
|
||||
├── external/
|
||||
│ ├── ChuSQL/ # Git 子模块,主仓库只记录版本指针
|
||||
│ └── ChuSQL-RustDriver/ # Git 子模块
|
||||
├── config/git-local.config
|
||||
└── scripts/init-deps.py
|
||||
```
|
||||
|
||||
先初始化外部依赖:
|
||||
|
||||
```sh
|
||||
python3 scripts/init-deps.py
|
||||
```
|
||||
|
||||
数据库地址为 `ChuGit:/srv/git/ChuSQL.git`,驱动为 `ChuGit:/srv/git/ChuSQL-RustDriver.git`。客户端需要配置 `ChuGit` SSH 别名。服务器上存在这两个本地仓库时,初始化脚本会优先使用项目级 `insteadOf` 将链接映射到 `file:///srv/git/…`,不经过 SSH。`config/git-local.config` 同时包含 SSH/GitHub 链接到本地仓库的映射,只在本项目启用,不修改全局配置。
|
||||
|
||||
开发电脑可明确选择已有本地仓库(工作树或 bare 仓库),不会把依赖的未提交改动复制进来:
|
||||
|
||||
```sh
|
||||
python3 scripts/init-deps.py --database-repo ../ChuSQL --driver-repo ../ChuSQLDriver
|
||||
```
|
||||
|
||||
子模块固定到已提交版本;源仓库必须包含这些提交。依赖源码和构建产物不进入博客仓库,更新依赖时只提交 `.gitmodules`/配置及子模块指针。不要对主仓库使用 `git add --force` 重新收录依赖文件。
|
||||
|
||||
|
||||
开发检查:
|
||||
|
||||
```sh
|
||||
@@ -25,7 +45,7 @@ cargo clippy --all-targets -- -D warnings
|
||||
|
||||
```sh
|
||||
# ChuSQL 尚未编译时,先按其项目说明构建 Rust 存储库、server 与 bootstrap。
|
||||
python3 scripts/prepare-deploy.py
|
||||
python3 scripts/prepare-deploy.py --build-database
|
||||
cp .env.example .env
|
||||
chmod 600 .env
|
||||
# 从标准输入输入管理员口令,Ctrl-D 结束;不要把明文写进命令行历史。
|
||||
@@ -38,7 +58,7 @@ docker compose --env-file .env -f deploy/compose.yaml run --rm db-init
|
||||
|
||||
构建时若 Debian 官方下载源较慢,可在 `.env` 配置 `DEBIAN_MIRROR=mirrors.ustc.edu.cn`,仍使用 Debian 签名校验。
|
||||
|
||||
`prepare-deploy.py` 复制相邻 ChuSQL 的既有编译产物,不修改该项目;没有产物会明确失败。跨平台部署须先在目标 Debian/架构构建,不能复制 macOS、Windows 或其他架构二进制。镜像不含编译器、源码、`.env` 或密码文件。首次启动从环境中的哈希创建管理员,后续账号与权限以持久文件为准,修改初始哈希不会覆盖已有账户。
|
||||
`prepare-deploy.py` 默认从数据库子模块读取编译产物;`--build-database` 会先构建其 Rust 存储库、Haskell server 和 bootstrap。也可用 `--chusql-source /已有构建的/ChuSQL` 选择已有外部构建,没有产物会明确失败。编译产物只暂存在被忽略的 `deploy/bin/`。跨平台部署须先在目标 Debian/架构构建,不能复制 macOS、Windows 或其他架构二进制。镜像不含编译器、源码、`.env` 或密码文件。首次启动从环境中的哈希创建管理员,后续账号与权限以持久文件为准,修改初始哈希不会覆盖已有账户。
|
||||
|
||||
Caddy 根据 `SITE_ADDRESS` 提供 HTTPS 和 API 反向代理;`PUBLIC_ORIGIN` 必须与外部站点完全一致,不带结尾斜杠。HTTP 仅对 localhost 开发场景允许。只需公网 TCP 80/443 和 Git SSH 2222(可改);主机管理 SSH 另行管理,数据库和 API 不映射宿主端口。既有 UDP 7001 不受影响。参考 [Caddy 请求体限制](https://caddyserver.com/docs/caddyfile/directives/request_body) 与 [反向代理文档](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy)。
|
||||
|
||||
@@ -48,7 +68,7 @@ Caddy 根据 `SITE_ADDRESS` 提供 HTTPS 和 API 反向代理;`PUBLIC_ORIGIN`
|
||||
|
||||
1. 注册:`POST /api/auth/register`,正文 `{ "username": "alice", "password": "至少12字节的口令" }`。默认角色 `reader`,绝不接受客户端指定角色。`ALLOW_REGISTRATION=false` 可关闭注册。
|
||||
2. 登录:`POST /api/auth/login`,相同字段。浏览器保存 HttpOnly Cookie;返回 `csrf`。所有修改请求必须携带站点 `Origin` 和 `X-CSRF-Token`。
|
||||
3. 本地生成 `ssh-keygen -t ed25519`;登录后 `PUT /api/account/ssh-key` 提交 `{ "public_key": "ssh-ed25519 AAAA..." }`,只交公钥。
|
||||
3. 本地生成 `ssh-keygen -t ed25519` 或 `ssh-keygen -t rsa -b 3072`;登录后 `PUT /api/account/ssh-key` 提交 `{ "public_key": "ssh-ed25519 AAAA..." }`,只交公钥。RSA 使用标准 `ssh-rsa AAAA...` 公钥文本,支持 2048–8192 位;实际认证使用 RSA/SHA-256 或 RSA/SHA-512,不启用旧的 SHA-1 签名。见 [OpenSSH 说明](https://www.openssh.com/txt/release-8.8)。
|
||||
4. 管理员登录后调用 `PATCH /api/admin/users/alice`,正文 `{ "enabled": true, "role": "publisher", "prefixes": ["alice-"] }`。不同有效发布者的前缀不能重叠。
|
||||
5. 此时 SSH 才接受该用户的密钥;`reader`、停用用户、未绑密钥的用户均不能访问 Git。管理员也需显式分配前缀和公钥才能发布。
|
||||
|
||||
@@ -99,7 +119,7 @@ MVP 每次只接受 **一个直接继承远端 main 的提交**,首次仅允
|
||||
- Markdown 上限 256 KiB;图片单张 2 MiB、最多 12 张、尺寸 4096×4096;文章合计 6 MiB;快照原始文件合计 64 MiB、最多 1000 篇。
|
||||
- Git 输入 pack 上限 8 MiB;接收仓库达到 256 MiB 后拒绝新 SSH 会话,需要管理员离线维护。此阈值是应用检查,生产还应为 Docker 数据盘配置真实文件系统配额。
|
||||
- 使用成熟 Markdown 解析器,拒绝原始 HTML、危险链接协议、外链图片、控制符和双向隐藏字符;代码块是惰性文本,绝不执行 MDX、JS、仓库脚本、构建任务或上传内容。
|
||||
- 仅支持 Ed25519 公钥;拒绝把 `authorized_keys` 选项当作公钥上传。SSH 只接受精确的 `git-receive-pack 'content.git'` / `git-upload-pack 'content.git'`,没有 shell、SCP/SFTP、TTY、转发、隧道、用户环境变量。
|
||||
- 支持 Ed25519 和 2048–8192 位 RSA 公钥,拒绝弱密钥、负数/畸形 RSA 参数及额外二进制数据;拒绝把 `authorized_keys` 选项当作公钥上传。SSH 只接受精确的 `git-receive-pack 'content.git'` / `git-upload-pack 'content.git'`,没有 shell、SCP/SFTP、TTY、转发、隧道、用户环境变量。
|
||||
- 接收仓库由 UID 10002 管理;API 为独立 UID 10001,正式仓库/用户密码文件不挂载给 SSH 容器;SSH 只读授权文件和只读固定 hook。接收内容再次验证后才导入 API 自己的 Git 仓库。
|
||||
- HTTP 请求体 8 KiB;注册/登录合计全局 10 次/分钟,修改操作全局 60 次/分钟;密码哈希并发最多 2。1000 用户、1000 个有效会话上限,一小时固定会话期限。全局限制偏保守,后续可细化 IP/账户限制以改善公平性。
|
||||
- 统计不收集 IP/访客身份:公开文章数、标签数、Markdown 字节数、启动以来文章 API 请求数。请求数不是独立访客数,重启后清零。
|
||||
@@ -119,8 +139,14 @@ MVP 每次只接受 **一个直接继承远端 main 的提交**,首次仅允
|
||||
## 联调
|
||||
|
||||
```sh
|
||||
python3 scripts/prepare-deploy.py
|
||||
python3 scripts/prepare-deploy.py --build-database
|
||||
python3 scripts/smoke.py
|
||||
python3 scripts/smoke.py --key-type rsa
|
||||
```
|
||||
|
||||
联调使用独立随机命名的 Compose 项目、本机 18080/18443/12222 端口,测试结束清理自己创建的容器与卷。覆盖真实 SSH 身份与授权、非法推送、发布撤回、ChuSQL 索引、断库阅读、重启与撤权。该测试会拉取基础镜像,需要 Docker 可用。
|
||||
|
||||
|
||||
## 历史清理后的协作
|
||||
|
||||
驱动的旧源码副本会从主仓库历史中移除,后续只保留外部子模块配置。历史重写后,已有克隆建议重新克隆并初始化依赖;不要把旧 main 合并回来,否则可能重新引入已删除的依赖历史。自建服务器的不可达对象清理与其他人的旧克隆需要分别处理,GitHub 缓存和旧提交直链的回收由平台管理。
|
||||
@@ -7,13 +7,13 @@
|
||||
- [x] Rust/Axum API:公开文章列表、详情、图片、基础统计、健康接口。
|
||||
- [x] 用户注册、登录、退出、修改密码;Argon2id、服务端会话、Cookie、Origin/CSRF 校验。
|
||||
- [x] reader / publisher / admin 权限;注册不能自选权限,管理员单独审批发布权、停用用户,修改权限使已有会话失效。
|
||||
- [x] 用户上传/轮换自己的 Ed25519 公钥,公钥存在与具有发布权分别检查。
|
||||
- [x] 用户上传/轮换自己的 Ed25519/RSA 公钥,公钥存在与具有发布权分别检查。
|
||||
- [x] OpenSSH 强制命令:仅固定仓库的 Git push/clone;禁用 shell、SFTP/SCP、TTY、代理/端口转发、隧道与用户环境。
|
||||
- [x] 发布者 slug 前缀授权,不允许重叠授权或修改其他命名空间。
|
||||
- [x] pre-receive 校验 Git quarantine 内的候选提交:单次一个线性提交、仅 main、禁止删除分支、强推、标签及其他分支。
|
||||
- [x] Markdown Frontmatter、路径、文件模式、大小配额、图片类型/内容/尺寸与链接安全校验;拒绝原始 HTML、隐藏控制符、符号链接、可执行文件及额外文件。
|
||||
- [x] 独立 SSH 接收仓库与 API 正式仓库;从 Git 恢复公开快照,草稿不出现在公开 API/资源接口。
|
||||
- [x] 项目内 `crates/chusql-driver` 副本;ChuSQL 公开元数据/标签索引及失败自动重建,数据库不承担唯一内容或账户存储。
|
||||
- [x] 外部 `ChuSQL`/`ChuSQL-RustDriver` 子模块配置与本地路径优先映射;ChuSQL 公开元数据/标签索引及失败自动重建,数据库不承担唯一内容或账户存储。
|
||||
- [x] Docker Compose、Caddy 反向代理、独立持久卷、容器资源限制、最小进程身份与网络隔离。
|
||||
- [x] 后端/驱动自动测试、真实容器 SSH 联调脚本、运行/API/架构说明。
|
||||
|
||||
|
||||
+2
-1
@@ -9,13 +9,14 @@ argon2 = "0.5"
|
||||
axum = "0.8"
|
||||
base64 = "0.22"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
chusql-driver = { path = "../../crates/chusql-driver" }
|
||||
chusql-driver = { path = "../../external/ChuSQL-RustDriver" }
|
||||
fs2 = "0.4"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
|
||||
rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
ssh-key = { version = "0.6.7", default-features = false, features = ["std"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
pulldown-cmark = "0.13"
|
||||
tracing = "0.1"
|
||||
|
||||
+31
-19
@@ -10,10 +10,7 @@ use axum::{
|
||||
http::{HeaderMap, HeaderValue, StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use base64::{
|
||||
Engine,
|
||||
engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
|
||||
};
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
@@ -428,23 +425,38 @@ pub async fn logout(State(app): State<Arc<App>>, headers: HeaderMap) -> ApiResul
|
||||
}
|
||||
pub fn public_key(value: &str) -> Result<String> {
|
||||
ensure!(
|
||||
value.len() < 512 && !value.contains(['\n', '\r']),
|
||||
value.len() <= 4096 && !value.contains(['\n', '\r']),
|
||||
"公钥格式无效"
|
||||
);
|
||||
let parts: Vec<_> = value.split_whitespace().collect();
|
||||
ensure!(
|
||||
parts.len() >= 2 && parts[0] == "ssh-ed25519",
|
||||
"仅支持 Ed25519 公钥,禁止 authorized_keys 选项"
|
||||
);
|
||||
let bytes = STANDARD.decode(parts[1])?;
|
||||
ensure!(
|
||||
bytes.len() == 51
|
||||
&& bytes[..4] == [0, 0, 0, 11]
|
||||
&& &bytes[4..15] == b"ssh-ed25519"
|
||||
&& bytes[15..19] == [0, 0, 0, 32],
|
||||
"Ed25519 公钥内容无效"
|
||||
);
|
||||
Ok(format!("ssh-ed25519 {}", STANDARD.encode(bytes)))
|
||||
let key = ssh_key::PublicKey::from_openssh(value)
|
||||
.context("SSH 公钥格式无效,禁止 authorized_keys 选项")?;
|
||||
match key.key_data() {
|
||||
ssh_key::public::KeyData::Ed25519(_) => {}
|
||||
ssh_key::public::KeyData::Rsa(rsa) => {
|
||||
let modulus = rsa.n.as_positive_bytes().context("RSA 模数必须为正数")?;
|
||||
let first = modulus.first().context("RSA 模数为空")?;
|
||||
let bits = modulus.len() * 8 - first.leading_zeros() as usize;
|
||||
ensure!(
|
||||
(2048..=8192).contains(&bits) && modulus.last().is_some_and(|b| b & 1 == 1),
|
||||
"RSA 模数必须为 2048–8192 位正奇数"
|
||||
);
|
||||
let exponent = rsa.e.as_positive_bytes().context("RSA 指数必须为正数")?;
|
||||
ensure!(exponent.len() <= 4, "RSA 指数过大");
|
||||
let exponent = exponent.iter().fold(0_u32, |n, b| (n << 8) | u32::from(*b));
|
||||
ensure!(
|
||||
exponent >= 3 && exponent % 2 == 1,
|
||||
"RSA 指数必须为至少 3 的奇数"
|
||||
);
|
||||
}
|
||||
_ => anyhow::bail!("仅支持 Ed25519 或 RSA 公钥"),
|
||||
}
|
||||
// Drop comments and options: the stored value is exactly the parsed public key.
|
||||
Ok(format!(
|
||||
"{} {}",
|
||||
key.algorithm().as_str(),
|
||||
key.to_bytes()
|
||||
.map(|bytes| base64::engine::general_purpose::STANDARD.encode(bytes))?
|
||||
))
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
|
||||
@@ -253,6 +253,58 @@ fn ssh_keys_cannot_inject_options_or_commands() {
|
||||
assert!(accounts::public_key(&format!("{key}\nssh-rsa AAA")).is_err());
|
||||
assert!(accounts::public_key("ssh-ed25519 AAAA").is_err());
|
||||
}
|
||||
fn rsa_key(bits: usize, exponent: &[u8]) -> String {
|
||||
let mut modulus = vec![0xff; bits.div_ceil(8)];
|
||||
modulus[0] >>= (8 - bits % 8) % 8;
|
||||
if modulus[0] & 0x80 != 0 {
|
||||
modulus.insert(0, 0);
|
||||
}
|
||||
let mut bytes = Vec::new();
|
||||
for field in [b"ssh-rsa".as_slice(), exponent, modulus.as_slice()] {
|
||||
bytes.extend((field.len() as u32).to_be_bytes());
|
||||
bytes.extend(field);
|
||||
}
|
||||
format!(
|
||||
"ssh-rsa {}",
|
||||
base64::engine::general_purpose::STANDARD.encode(bytes)
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn rsa_keys_enforce_strength_encoding_and_safe_canonical_form() {
|
||||
for bits in [2048, 3072, 4096, 8192] {
|
||||
let key = rsa_key(bits, &[1, 0, 1]);
|
||||
assert_eq!(
|
||||
accounts::public_key(&format!("{key} user comment")).unwrap(),
|
||||
key
|
||||
);
|
||||
}
|
||||
for key in [
|
||||
rsa_key(1024, &[1, 0, 1]),
|
||||
rsa_key(2047, &[1, 0, 1]),
|
||||
rsa_key(8193, &[1, 0, 1]),
|
||||
rsa_key(2048, &[2]),
|
||||
rsa_key(2048, &[1]),
|
||||
rsa_key(2048, &[0x80]),
|
||||
rsa_key(2048, &[1, 0, 0, 0, 1]),
|
||||
] {
|
||||
assert!(accounts::public_key(&key).is_err());
|
||||
}
|
||||
let key = rsa_key(2048, &[1, 0, 1]);
|
||||
assert!(accounts::public_key(&key.replace("ssh-rsa", "ssh-ed25519")).is_err());
|
||||
assert!(accounts::public_key(&format!("command=\"sh\" {key}")).is_err());
|
||||
let encoded = key.split_whitespace().nth(1).unwrap();
|
||||
let mut bytes = base64::engine::general_purpose::STANDARD
|
||||
.decode(encoded)
|
||||
.unwrap();
|
||||
bytes.push(0);
|
||||
assert!(
|
||||
accounts::public_key(&format!(
|
||||
"ssh-rsa {}",
|
||||
base64::engine::general_purpose::STANDARD.encode(bytes)
|
||||
))
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
async fn app(dir: &std::path::Path) -> Arc<App> {
|
||||
let config = dir.join("publishers.json");
|
||||
let hash = accounts::hash_password("Administrator-test-123".into())
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Optional on the self-hosted server; loaded by scripts/init-deps.py when these repos exist.
|
||||
[url "file:///srv/git/ChuSQL.git"]
|
||||
insteadOf = ChuGit:/srv/git/ChuSQL.git
|
||||
insteadOf = ssh://git@chunana.cn/srv/git/ChuSQL.git
|
||||
insteadOf = git@chunana.cn:/srv/git/ChuSQL.git
|
||||
insteadOf = git@github.com:TheBadRoger/ChuSQL.git
|
||||
insteadOf = https://github.com/TheBadRoger/ChuSQL.git
|
||||
[url "file:///srv/git/ChuSQL-RustDriver.git"]
|
||||
insteadOf = ChuGit:/srv/git/ChuSQL-RustDriver.git
|
||||
insteadOf = ssh://git@chunana.cn/srv/git/ChuSQL-RustDriver.git
|
||||
insteadOf = git@chunana.cn:/srv/git/ChuSQL-RustDriver.git
|
||||
insteadOf = git@github.com:TheBadRoger/ChuSQL-RustDriver.git
|
||||
insteadOf = https://github.com/TheBadRoger/ChuSQL-RustDriver.git
|
||||
+2
-1
@@ -8,7 +8,8 @@ KbdInteractiveAuthentication no
|
||||
PermitEmptyPasswords no
|
||||
PubkeyAuthentication yes
|
||||
AuthenticationMethods publickey
|
||||
PubkeyAcceptedAlgorithms ssh-ed25519
|
||||
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
|
||||
RequiredRSASize 2048
|
||||
AllowUsers git
|
||||
AuthorizedKeysFile none
|
||||
AuthorizedKeysCommand /usr/local/bin/chublog-authorized-keys
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
| POST | `/api/auth/login` | 公共 + Origin;`{username,password}`,返回 `{username,role,csrf}` 及 Cookie |
|
||||
| POST | `/api/auth/logout` | 登录 + Origin + CSRF;使会话失效并清除 Cookie |
|
||||
| GET | `/api/account` | 登录;返回自己的 `{username,permissions,csrf}`,不含口令哈希 |
|
||||
| PUT | `/api/account/ssh-key` | 登录 + Origin + CSRF;`{public_key}`,替换自己的唯一 Ed25519 公钥 |
|
||||
| PUT | `/api/account/ssh-key` | 登录 + Origin + CSRF;`{public_key}`,替换自己的唯一 Ed25519 或 RSA 公钥(RSA 2048–8192 位) |
|
||||
| PUT | `/api/account/password` | 登录 + Origin + CSRF;`{old_password,new_password}`,成功后全部会话失效 |
|
||||
| GET | `/api/admin/users` | 管理员;返回用户启用状态、角色、前缀及公钥,不含哈希 |
|
||||
| PATCH | `/api/admin/users/:username` | 管理员 + Origin + CSRF;`{enabled,role,prefixes}` |
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# ADR-002:外部项目使用子模块配置
|
||||
|
||||
## 状态
|
||||
|
||||
已接受,2026-10-15。替代早期复制数据库驱动源码的做法。
|
||||
|
||||
## 决定
|
||||
|
||||
数据库服务与 Rust 驱动作为两个独立 Git 子模块维护。主仓库只提交远程地址、固定版本指针、初始化/构建配置,不提交依赖源码或编译产物。Cargo 引用初始化后的驱动路径,数据库从外部 checkout 构建并暂存到被忽略的 Docker 构建目录。
|
||||
|
||||
正式来源使用 `ChuGit:/srv/git/ChuSQL.git` 与 `ChuGit:/srv/git/ChuSQL-RustDriver.git`。在服务器可读本地 bare 仓库时,项目级 `insteadOf` 优先使用 `file:///srv/git/…`;开发者可显式指定已有本地来源。文件协议只在初始化这两个明确子模块的命令中启用,不设置全局许可,不递归获取未知子模块。
|
||||
|
||||
已有驱动副本从主仓库的历史树中删除。外部项目仍各自保留完整历史;主仓库不能替代依赖仓库分发其对象,源仓库必须先具有固定指针所引用的提交。
|
||||
+1
Submodule external/ChuSQL added at a45e33f94b.
+1
Submodule external/ChuSQL-RustDriver added at 105ad89ace.
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Initialize pinned external submodules, preferring local repositories over SSH."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PROJECTS = ['ChuSQL', 'ChuSQL-RustDriver']
|
||||
PREFIX = 'ChuGit:/srv/git/'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--database-repo', type=Path, help='local clone/bare repository of ChuSQL')
|
||||
parser.add_argument('--driver-repo', type=Path, help='local clone/bare repository of ChuSQL-RustDriver')
|
||||
args = parser.parse_args()
|
||||
git_args = []
|
||||
local_config = ROOT / 'config/git-local.config'
|
||||
if not args.database_repo and not args.driver_repo and all((Path('/srv/git') / f'{name}.git').is_dir() for name in PROJECTS):
|
||||
current = subprocess.run(['git', 'config', '--local', '--get-all', 'include.path'], cwd=ROOT, text=True, capture_output=True, check=False)
|
||||
if str(local_config) not in current.stdout.splitlines():
|
||||
subprocess.run(['git', 'config', '--local', '--add', 'include.path', str(local_config)], cwd=ROOT, check=True)
|
||||
git_args.extend(['-c', f'include.path={local_config}'])
|
||||
for name, override in zip(PROJECTS, [args.database_repo, args.driver_repo]):
|
||||
source = override or Path('/srv/git') / f'{name}.git'
|
||||
if override and not source.is_dir():
|
||||
parser.error(f'Local repository does not exist: {source}')
|
||||
if source.is_dir():
|
||||
source = source.resolve()
|
||||
subprocess.run(['git', '-C', str(source), 'rev-parse', '--git-dir'], check=True, capture_output=True)
|
||||
key = f'url.{source.as_uri()}.insteadOf'
|
||||
url = f'{PREFIX}{name}.git'
|
||||
subprocess.run(['git', 'config', '--local', '--replace-all', key, url], cwd=ROOT, check=True)
|
||||
git_args.extend(['-c', f'{key}={url}'])
|
||||
print(f'{name}: using local repository {source}', flush=True)
|
||||
# The file protocol is enabled only for this explicit, fixed set of dependencies.
|
||||
if git_args:
|
||||
git_args.extend(['-c', 'protocol.file.allow=always'])
|
||||
subprocess.run(['git', *git_args, 'submodule', 'update', '--init', '--', *[f'external/{name}' for name in PROJECTS]], cwd=ROOT, check=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the API and copy existing local ChuSQL release artifacts into the Docker context."""
|
||||
"""Build the API and stage external ChuSQL artifacts without committing dependencies."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
@@ -8,17 +8,27 @@ import subprocess
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--no-build', action='store_true', help='use an existing API release binary')
|
||||
parser.add_argument('--chusql-source', type=Path, default=root / 'external/ChuSQL', help='initialized/built external ChuSQL checkout')
|
||||
parser.add_argument('--build-database', action='store_true', help='build external database artifacts before staging')
|
||||
args = parser.parse_args()
|
||||
chusql = args.chusql_source.resolve()
|
||||
if not (root / 'external/ChuSQL-RustDriver/Cargo.toml').is_file():
|
||||
parser.error('Initialize external dependencies with scripts/init-deps.py first.')
|
||||
if not (chusql / 'chusql-core/storage/Cargo.toml').is_file():
|
||||
parser.error(f'ChuSQL source is missing: {chusql}; initialize dependencies first.')
|
||||
if not args.no_build:
|
||||
subprocess.run(['cargo', 'build', '--locked', '--release'], cwd=root, check=True)
|
||||
out = root / 'deploy' / 'bin'
|
||||
if args.build_database:
|
||||
subprocess.run(['cargo', 'build', '--locked', '--release'], cwd=chusql / 'chusql-core/storage', check=True)
|
||||
for package in ['chusql-server', 'chusql-bootstrap']:
|
||||
subprocess.run(['stack', 'build', '--fast'], cwd=chusql / package, check=True)
|
||||
out = root / 'deploy/bin'
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(root / 'target/release/chublog-api', out / 'chublog-api')
|
||||
chusql = root.parent / 'ChuSQL'
|
||||
for package, executable in [('chusql-server', 'chusql-server'), ('chusql-bootstrap', 'csql-bootstrap')]:
|
||||
matches = list((chusql / package / '.stack-work/install').glob(f'*/**/bin/{executable}'))
|
||||
if len(matches) != 1:
|
||||
raise SystemExit(f'Expected one built {executable}, found {len(matches)}; build {package} first using its stack.yaml.')
|
||||
raise SystemExit(f'Expected one built {executable}, found {len(matches)}; use --build-database or select a built --chusql-source.')
|
||||
shutil.copy2(matches[0], out / executable)
|
||||
shutil.copy2(chusql / 'chusql-core/storage/target/release/libchusql_core_storage.so', out)
|
||||
print('Prepared deploy/bin. Build and run Compose from this same Linux architecture (Debian 13).')
|
||||
print('Prepared ignored deploy/bin. Build and run Compose on the same Debian 13 architecture.')
|
||||
+5
-1
@@ -3,6 +3,7 @@
|
||||
Uses an isolated Compose project and removes only that project's containers/volumes.
|
||||
Run prepare-deploy.py first. Ports 18080, 18443, 12222 must be free.
|
||||
"""
|
||||
import argparse
|
||||
import http.cookiejar
|
||||
import json
|
||||
import os
|
||||
@@ -16,6 +17,9 @@ import urllib.request
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ORIGIN = 'http://localhost:18080'
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--key-type', choices=['ed25519', 'rsa'], default='ed25519')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def run(args, **kwargs):
|
||||
@@ -77,7 +81,7 @@ with tempfile.TemporaryDirectory(prefix='chublog-smoke-') as temporary:
|
||||
publisher = Client()
|
||||
publisher.call('POST', '/api/auth/register', {'username': 'alice', 'password': password})
|
||||
publisher.csrf = publisher.call('POST', '/api/auth/login', {'username': 'alice', 'password': password})['csrf']
|
||||
run(['ssh-keygen', '-q', '-t', 'ed25519', '-N', '', '-f', str(tmp / 'key')])
|
||||
run(['ssh-keygen', '-q', '-t', args.key_type, '-b', '3072', '-N', '', '-f', str(tmp / 'key')])
|
||||
publisher.call('PUT', '/api/account/ssh-key', {'public_key': (tmp / 'key.pub').read_text().strip()})
|
||||
publisher.call('GET', '/api/admin/users', expected=403)
|
||||
env = os.environ.copy()
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify pinned submodules initialize from local repositories without an SSH connection."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
SCRIPT = Path(__file__).with_name('init-deps.py')
|
||||
|
||||
|
||||
class InitializeDependencies(unittest.TestCase):
|
||||
def test_local_rewrite_fetches_only_pinned_dependency_commits(self):
|
||||
with tempfile.TemporaryDirectory(prefix='chublog deps ') as temporary:
|
||||
root = Path(temporary)
|
||||
project = root / 'blog'
|
||||
project.mkdir()
|
||||
(project / 'scripts').mkdir()
|
||||
shutil.copy2(SCRIPT, project / 'scripts/init-deps.py')
|
||||
env = {**os.environ, 'GIT_CONFIG_GLOBAL': '/dev/null', 'GIT_CONFIG_NOSYSTEM': '1'}
|
||||
|
||||
def git(cwd, *args):
|
||||
return subprocess.run(['git', *args], cwd=cwd, env=env, check=True, text=True, capture_output=True).stdout.strip()
|
||||
|
||||
git(project, 'init', '--initial-branch=main')
|
||||
modules = []
|
||||
sources = []
|
||||
for name in ['ChuSQL', 'ChuSQL-RustDriver']:
|
||||
source = root / name
|
||||
source.mkdir()
|
||||
git(source, 'init', '--initial-branch=main')
|
||||
(source / 'source.txt').write_text(f'{name} committed source')
|
||||
git(source, 'add', '.')
|
||||
git(source, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', 'commit', '-m', 'Dependency version')
|
||||
oid = git(source, 'rev-parse', 'HEAD')
|
||||
# Dirty source must never leak into the blog or fetched dependency.
|
||||
(source / 'source.txt').write_text('uncommitted changes')
|
||||
path = f'external/{name}'
|
||||
modules.append(f'[submodule "{path}"]\n path = {path}\n url = ChuGit:/srv/git/{name}.git\n')
|
||||
git(project, 'update-index', '--add', '--cacheinfo', f'160000,{oid},{path}')
|
||||
sources.append(str(source))
|
||||
(project / '.gitmodules').write_text(''.join(modules))
|
||||
git(project, 'add', '.gitmodules')
|
||||
subprocess.run(['python3', 'scripts/init-deps.py', '--database-repo', sources[0], '--driver-repo', sources[1]], cwd=project, env=env, check=True, capture_output=True)
|
||||
for name in ['ChuSQL', 'ChuSQL-RustDriver']:
|
||||
self.assertEqual((project / f'external/{name}/source.txt').read_text(), f'{name} committed source')
|
||||
self.assertTrue(all(line.startswith('160000') for line in git(project, 'ls-files', '--stage', 'external').splitlines()))
|
||||
# Running the initialization a second time must preserve the same pins.
|
||||
subprocess.run(['python3', 'scripts/init-deps.py', '--database-repo', sources[0], '--driver-repo', sources[1]], cwd=project, env=env, check=True, capture_output=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user