Implemented AutoTLS via RustTLS
This commit is contained in:
261
Cargo.lock
generated
261
Cargo.lock
generated
@@ -67,6 +67,45 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1-rs"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs-derive",
|
||||||
|
"asn1-rs-impl",
|
||||||
|
"displaydoc",
|
||||||
|
"nom",
|
||||||
|
"num-traits",
|
||||||
|
"rusticata-macros",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1-rs-derive"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1-rs-impl"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-lock"
|
name = "async-lock"
|
||||||
version = "3.4.2"
|
version = "3.4.2"
|
||||||
@@ -321,6 +360,22 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.15"
|
version = "0.5.15"
|
||||||
@@ -356,6 +411,26 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "der-parser"
|
||||||
|
version = "10.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs",
|
||||||
|
"displaydoc",
|
||||||
|
"nom",
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"rusticata-macros",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@@ -377,6 +452,17 @@ dependencies = [
|
|||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "displaydoc"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dunce"
|
name = "dunce"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -478,13 +564,19 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"furumi-common",
|
"furumi-common",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
"moka",
|
"moka",
|
||||||
"prost",
|
"prost",
|
||||||
|
"rustls",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
|
"tower 0.5.3",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -533,6 +625,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"prost",
|
"prost",
|
||||||
|
"rcgen",
|
||||||
"rustls",
|
"rustls",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
@@ -993,6 +1086,12 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -1054,6 +1153,16 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -1112,6 +1221,15 @@ version = "4.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oid-registry"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -1124,6 +1242,12 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "page_size"
|
name = "page_size"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -1438,6 +1562,20 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rcgen"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e"
|
||||||
|
dependencies = [
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"time",
|
||||||
|
"x509-parser",
|
||||||
|
"yasna",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -1490,6 +1628,15 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusticata-macros"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.44"
|
version = "0.38.44"
|
||||||
@@ -1525,12 +1672,34 @@ dependencies = [
|
|||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923"
|
||||||
|
dependencies = [
|
||||||
|
"openssl-probe",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
@@ -1564,12 +1733,44 @@ version = "1.0.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
@@ -1749,6 +1950,17 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synstructure"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tagptr"
|
name = "tagptr"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1876,6 +2088,16 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||||
|
dependencies = [
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
@@ -1921,8 +2143,11 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"prost",
|
"prost",
|
||||||
|
"rustls-native-certs",
|
||||||
|
"rustls-pemfile",
|
||||||
"socket2 0.5.10",
|
"socket2 0.5.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
@@ -2213,6 +2438,15 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -2411,6 +2645,33 @@ dependencies = [
|
|||||||
"wasmparser",
|
"wasmparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x509-parser"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs",
|
||||||
|
"data-encoding",
|
||||||
|
"der-parser",
|
||||||
|
"lazy_static",
|
||||||
|
"nom",
|
||||||
|
"oid-registry",
|
||||||
|
"ring",
|
||||||
|
"rusticata-macros",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yasna"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||||
|
dependencies = [
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.42"
|
version = "0.8.42"
|
||||||
|
|||||||
54
README.md
54
README.md
@@ -1,16 +1,16 @@
|
|||||||
# Furumi-ng
|
# Furumi-ng
|
||||||
|
|
||||||
Remote filesystem over gRPC. Mount a directory from a remote server as a local folder via FUSE.
|
Remote filesystem over encrypted gRPC. Mount a directory from a remote server as a local folder via FUSE.
|
||||||
|
|
||||||
Designed for streaming media (video, music) over the network.
|
Designed for streaming media (video, music) over the network.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
furumi-server (gRPC) ←→ furumi-client-core (library) ←→ furumi-mount-linux (FUSE)
|
furumi-server (gRPC + TLS) ←→ furumi-client-core ←→ furumi-mount-linux (FUSE)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **furumi-server** — exposes a directory over gRPC with optional Bearer token auth and Prometheus metrics
|
- **furumi-server** — exposes a directory over gRPC with auto-TLS, Bearer token auth, and Prometheus metrics
|
||||||
- **furumi-client-core** — cross-platform gRPC client library with attribute caching
|
- **furumi-client-core** — cross-platform gRPC client library with attribute caching
|
||||||
- **furumi-mount-linux** — mounts the remote directory locally via FUSE (read-only)
|
- **furumi-mount-linux** — mounts the remote directory locally via FUSE (read-only)
|
||||||
|
|
||||||
@@ -20,17 +20,17 @@ furumi-server (gRPC) ←→ furumi-client-core (library) ←→ furumi-mount
|
|||||||
# Build
|
# Build
|
||||||
cargo build --release --workspace
|
cargo build --release --workspace
|
||||||
|
|
||||||
# Server
|
# Server — auto-generates TLS certificate, saves it for client
|
||||||
./target/release/furumi-server \
|
./target/release/furumi-server \
|
||||||
--root /path/to/media \
|
--root /path/to/media \
|
||||||
--bind 0.0.0.0:50051 \
|
|
||||||
--token mysecrettoken
|
|
||||||
|
|
||||||
# Client (on another machine)
|
|
||||||
mkdir -p /mnt/remote
|
|
||||||
./target/release/furumi-mount-linux \
|
|
||||||
--server http://server-ip:50051 \
|
|
||||||
--token mysecrettoken \
|
--token mysecrettoken \
|
||||||
|
--tls-cert-out /tmp/furumi-ca.pem
|
||||||
|
|
||||||
|
# Client — loads the server's certificate for encrypted connection
|
||||||
|
./target/release/furumi-mount-linux \
|
||||||
|
--server https://server-ip:50051 \
|
||||||
|
--token mysecrettoken \
|
||||||
|
--tls-ca /tmp/furumi-ca.pem \
|
||||||
--mount /mnt/remote
|
--mount /mnt/remote
|
||||||
|
|
||||||
# Use it
|
# Use it
|
||||||
@@ -38,6 +38,16 @@ ls /mnt/remote
|
|||||||
mpv /mnt/remote/video.mkv
|
mpv /mnt/remote/video.mkv
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
|
||||||
|
TLS is enabled by default. The server auto-generates a self-signed certificate on each start — no manual cert management required. The certificate is used **only for encryption**, not for server identity verification.
|
||||||
|
|
||||||
|
To pass the certificate to the client:
|
||||||
|
1. Server: `--tls-cert-out /path/to/cert.pem` saves the generated cert
|
||||||
|
2. Client: `--tls-ca /path/to/cert.pem` loads it for the TLS handshake
|
||||||
|
|
||||||
|
To disable TLS (not recommended): `--no-tls` on the server, and use `http://` on the client.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
All options can be set via CLI flags or environment variables.
|
All options can be set via CLI flags or environment variables.
|
||||||
@@ -46,22 +56,36 @@ All options can be set via CLI flags or environment variables.
|
|||||||
|
|
||||||
| Flag | Env | Default | Description |
|
| Flag | Env | Default | Description |
|
||||||
|------|-----|---------|-------------|
|
|------|-----|---------|-------------|
|
||||||
| `--bind` | `FURUMI_BIND` | `[::1]:50051` | gRPC listen address |
|
| `--bind` | `FURUMI_BIND` | `0.0.0.0:50051` | gRPC listen address |
|
||||||
| `--root` | `FURUMI_ROOT` | `.` | Directory to expose |
|
| `--root` | `FURUMI_ROOT` | `.` | Directory to expose |
|
||||||
| `--token` | `FURUMI_TOKEN` | *(empty, auth off)* | Bearer token |
|
| `--token` | `FURUMI_TOKEN` | *(empty, auth off)* | Bearer token |
|
||||||
| `--metrics-bind` | `FURUMI_METRICS_BIND` | `0.0.0.0:9090` | Prometheus metrics endpoint |
|
| `--metrics-bind` | `FURUMI_METRICS_BIND` | `0.0.0.0:9090` | Prometheus endpoint |
|
||||||
|
| `--tls-cert-out` | `FURUMI_TLS_CERT_OUT` | — | Save auto-generated cert PEM |
|
||||||
|
| `--no-tls` | — | `false` | Disable TLS |
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
|
||||||
| Flag | Env | Default | Description |
|
| Flag | Env | Default | Description |
|
||||||
|------|-----|---------|-------------|
|
|------|-----|---------|-------------|
|
||||||
| `--server` | `FURUMI_SERVER` | `http://[::1]:50051` | Server address |
|
| `--server` | `FURUMI_SERVER` | `https://0.0.0.0:50051` | Server address |
|
||||||
| `--token` | `FURUMI_TOKEN` | *(empty)* | Bearer token |
|
| `--token` | `FURUMI_TOKEN` | *(empty)* | Bearer token |
|
||||||
| `--mount` | `FURUMI_MOUNT` | — | Mount point directory |
|
| `--mount` | `FURUMI_MOUNT` | — | Mount point directory |
|
||||||
|
| `--tls-ca` | `FURUMI_TLS_CA` | — | Server CA cert PEM file |
|
||||||
|
|
||||||
|
## Prometheus Metrics
|
||||||
|
|
||||||
|
Available at `http://<metrics-bind>/metrics`:
|
||||||
|
|
||||||
|
- `furumi_grpc_requests_total` — request count by method and status
|
||||||
|
- `furumi_grpc_request_duration_seconds` — request latency histogram
|
||||||
|
- `furumi_bytes_read_total` — total bytes streamed
|
||||||
|
- `furumi_active_streams` — current streaming connections
|
||||||
|
- `furumi_file_open_errors_total` — file access errors
|
||||||
|
- `furumi_auth_failures_total` — authentication failures
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Linux with `libfuse3-dev` (for client)
|
- Linux with `libfuse3-dev` and `pkg-config` (for client)
|
||||||
- Rust 2024 edition
|
- Rust 2024 edition
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -9,8 +9,14 @@ anyhow = "1.0.102"
|
|||||||
prost = "0.13.5"
|
prost = "0.13.5"
|
||||||
tokio = { version = "1.50.0", features = ["full"] }
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
tokio-stream = "0.1.18"
|
tokio-stream = "0.1.18"
|
||||||
tonic = "0.12.3"
|
tonic = { version = "0.12.3", features = ["tls", "tls-native-roots"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
moka = { version = "0.12.10", features = ["sync", "future"] }
|
moka = { version = "0.12.10", features = ["sync", "future"] }
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
|
rustls = { version = "0.23.37", features = ["ring"] }
|
||||||
|
tokio-rustls = "0.26.4"
|
||||||
|
webpki-roots = "1.0.6"
|
||||||
|
hyper-util = { version = "0.1.20", features = ["tokio"] }
|
||||||
|
hyper = { version = "1.8.1", features = ["client"] }
|
||||||
|
tower = { version = "0.5.3", features = ["util"] }
|
||||||
|
|||||||
@@ -38,15 +38,40 @@ pub struct FurumiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FurumiClient {
|
impl FurumiClient {
|
||||||
/// Connects to the Furumi-ng server with an optional bearer token.
|
/// Connects to the Furumi-ng server.
|
||||||
pub async fn connect(addr: &str, token: &str) -> Result<Self> {
|
///
|
||||||
let endpoint = Endpoint::from_shared(addr.to_string())
|
/// - `addr`: Server URL. Use `https://` for TLS, `http://` for plaintext.
|
||||||
|
/// - `token`: Bearer token for auth (empty = no auth).
|
||||||
|
/// - `tls_ca_pem`: Optional CA certificate PEM bytes for verifying the server.
|
||||||
|
/// If using TLS without a CA cert, pass `None` (uses system roots).
|
||||||
|
pub async fn connect(addr: &str, token: &str, tls_ca_pem: Option<Vec<u8>>) -> Result<Self> {
|
||||||
|
// Install ring as the default crypto provider for rustls
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
|
let mut endpoint = Endpoint::from_shared(addr.to_string())
|
||||||
.map_err(|e| ClientError::Internal(format!("Invalid URI: {}", e)))?
|
.map_err(|e| ClientError::Internal(format!("Invalid URI: {}", e)))?
|
||||||
.timeout(Duration::from_secs(30))
|
.timeout(Duration::from_secs(30))
|
||||||
.concurrency_limit(256)
|
.concurrency_limit(256)
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60)))
|
.tcp_keepalive(Some(Duration::from_secs(60)))
|
||||||
.http2_keep_alive_interval(Duration::from_secs(60));
|
.http2_keep_alive_interval(Duration::from_secs(60));
|
||||||
|
|
||||||
|
// Configure TLS if using https://
|
||||||
|
if addr.starts_with("https://") {
|
||||||
|
let mut tls_config = tonic::transport::ClientTlsConfig::new()
|
||||||
|
.domain_name("furumi-ng");
|
||||||
|
|
||||||
|
if let Some(ca_pem) = tls_ca_pem {
|
||||||
|
info!("TLS enabled with server CA certificate");
|
||||||
|
tls_config = tls_config
|
||||||
|
.ca_certificate(tonic::transport::Certificate::from_pem(ca_pem));
|
||||||
|
} else {
|
||||||
|
info!("TLS enabled with system root certificates");
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint = endpoint.tls_config(tls_config)
|
||||||
|
.map_err(|e| ClientError::Internal(format!("TLS config error: {}", e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
info!("Connecting to {}", addr);
|
info!("Connecting to {}", addr);
|
||||||
let channel = endpoint.connect().await?;
|
let channel = endpoint.connect().await?;
|
||||||
|
|
||||||
@@ -58,7 +83,7 @@ impl FurumiClient {
|
|||||||
|
|
||||||
let attr_cache = Cache::builder()
|
let attr_cache = Cache::builder()
|
||||||
.max_capacity(100_000)
|
.max_capacity(100_000)
|
||||||
.time_to_live(Duration::from_secs(5)) // short TTL to catch up changes quickly
|
.time_to_live(Duration::from_secs(5))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Ok(Self { client, attr_cache })
|
Ok(Self { client, attr_cache })
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ use std::sync::Arc;
|
|||||||
use furumi_client_core::FurumiClient;
|
use furumi_client_core::FurumiClient;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about = "Furumi-ng: mount remote filesystem via encrypted gRPC + FUSE")]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Server address to connect to
|
/// Server address to connect to (use https:// for encrypted connection)
|
||||||
#[arg(short, long, env = "FURUMI_SERVER", default_value = "http://[::1]:50051")]
|
#[arg(short, long, env = "FURUMI_SERVER", default_value = "https://0.0.0.0:50051")]
|
||||||
server: String,
|
server: String,
|
||||||
|
|
||||||
/// Authentication Bearer token (leave empty if auth is disabled on server)
|
/// Authentication Bearer token (leave empty if auth is disabled on server)
|
||||||
@@ -21,6 +21,10 @@ struct Args {
|
|||||||
/// Mount point directory
|
/// Mount point directory
|
||||||
#[arg(short, long, env = "FURUMI_MOUNT")]
|
#[arg(short, long, env = "FURUMI_MOUNT")]
|
||||||
mount: PathBuf,
|
mount: PathBuf,
|
||||||
|
|
||||||
|
/// Path to server's TLS CA certificate PEM file (required for https:// connections)
|
||||||
|
#[arg(long, env = "FURUMI_TLS_CA")]
|
||||||
|
tls_ca: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@@ -32,6 +36,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
eprintln!("Error: Mount point {:?} does not exist or is not a directory", args.mount);
|
eprintln!("Error: Mount point {:?} does not exist or is not a directory", args.mount);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load TLS CA certificate if provided
|
||||||
|
let tls_ca_pem = if let Some(ref ca_path) = args.tls_ca {
|
||||||
|
let pem = std::fs::read(ca_path).unwrap_or_else(|e| {
|
||||||
|
eprintln!("Error: Failed to read TLS CA cert {:?}: {}", ca_path, e);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
Some(pem)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Create a robust tokio runtime for the background gRPC work
|
// Create a robust tokio runtime for the background gRPC work
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
@@ -39,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let client = rt.block_on(async {
|
let client = rt.block_on(async {
|
||||||
FurumiClient::connect(&args.server, &args.token).await
|
FurumiClient::connect(&args.server, &args.token, tls_ca_pem).await
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let fuse_fs = fs::FurumiFuse::new(client, rt.handle().clone());
|
let fuse_fs = fs::FurumiFuse::new(client, rt.handle().clone());
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ futures-util = "0.3.32"
|
|||||||
jsonwebtoken = "10.3.0"
|
jsonwebtoken = "10.3.0"
|
||||||
libc = "0.2.183"
|
libc = "0.2.183"
|
||||||
prost = "0.13.5"
|
prost = "0.13.5"
|
||||||
rustls = "0.23.37"
|
rustls = { version = "0.23.37", features = ["ring"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
tokio = { version = "1.50.0", features = ["full"] }
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
tokio-stream = "0.1.18"
|
tokio-stream = "0.1.18"
|
||||||
@@ -26,6 +26,7 @@ async-trait = "0.1.89"
|
|||||||
prometheus = { version = "0.14.0", features = ["process"] }
|
prometheus = { version = "0.14.0", features = ["process"] }
|
||||||
axum = { version = "0.7", features = ["tokio"] }
|
axum = { version = "0.7", features = ["tokio"] }
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
|
rcgen = { version = "0.14.7", features = ["pem"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.26.0"
|
tempfile = "3.26.0"
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use axum::{Router, routing::get};
|
use axum::{Router, routing::get};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use tonic::transport::Server;
|
use tonic::transport::{Identity, Server, ServerTlsConfig};
|
||||||
use vfs::local::LocalVfs;
|
use vfs::local::LocalVfs;
|
||||||
use furumi_common::proto::remote_file_system_server::RemoteFileSystemServer;
|
use furumi_common::proto::remote_file_system_server::RemoteFileSystemServer;
|
||||||
use server::RemoteFileSystemImpl;
|
use server::RemoteFileSystemImpl;
|
||||||
use security::AuthInterceptor;
|
use security::AuthInterceptor;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about = "Furumi-ng: remote filesystem server over encrypted gRPC")]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// IP address and port to bind the gRPC server to
|
/// IP address and port to bind the gRPC server to
|
||||||
#[arg(short, long, env = "FURUMI_BIND", default_value = "[::1]:50051")]
|
#[arg(short, long, env = "FURUMI_BIND", default_value = "0.0.0.0:50051")]
|
||||||
bind: String,
|
bind: String,
|
||||||
|
|
||||||
/// Document root directory to expose via VFS
|
/// Document root directory to expose via VFS
|
||||||
@@ -32,6 +32,14 @@ struct Args {
|
|||||||
/// IP address and port for the Prometheus metrics HTTP endpoint
|
/// IP address and port for the Prometheus metrics HTTP endpoint
|
||||||
#[arg(long, env = "FURUMI_METRICS_BIND", default_value = "0.0.0.0:9090")]
|
#[arg(long, env = "FURUMI_METRICS_BIND", default_value = "0.0.0.0:9090")]
|
||||||
metrics_bind: String,
|
metrics_bind: String,
|
||||||
|
|
||||||
|
/// Disable TLS encryption (not recommended, use only for debugging)
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
no_tls: bool,
|
||||||
|
|
||||||
|
/// Save the auto-generated TLS certificate to this file (for client --tls-ca)
|
||||||
|
#[arg(long, env = "FURUMI_TLS_CERT_OUT")]
|
||||||
|
tls_cert_out: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn metrics_handler() -> String {
|
async fn metrics_handler() -> String {
|
||||||
@@ -40,12 +48,17 @@ async fn metrics_handler() -> String {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Install ring as the default crypto provider for rustls (must be before any TLS usage)
|
||||||
|
rustls::crypto::ring::default_provider()
|
||||||
|
.install_default()
|
||||||
|
.expect("Failed to install rustls crypto provider");
|
||||||
|
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let addr: SocketAddr = args.bind.parse().unwrap_or_else(|e| {
|
let addr: SocketAddr = args.bind.parse().unwrap_or_else(|e| {
|
||||||
eprintln!("Error: Invalid bind address '{}': {}", args.bind, e);
|
eprintln!("Error: Invalid bind address '{}': {}", args.bind, e);
|
||||||
eprintln!(" Expected format: IP:PORT (e.g. 0.0.0.0:50051 or [::1]:50051)");
|
eprintln!(" Expected format: IP:PORT (e.g. 0.0.0.0:50051)");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,14 +82,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let auth = AuthInterceptor::new(args.token.clone());
|
let auth = AuthInterceptor::new(args.token.clone());
|
||||||
let svc = RemoteFileSystemServer::with_interceptor(remote_fs, auth.clone());
|
let svc = RemoteFileSystemServer::with_interceptor(remote_fs, auth.clone());
|
||||||
|
|
||||||
|
// Print startup info
|
||||||
println!("Furumi-ng Server listening on {}", addr);
|
println!("Furumi-ng Server listening on {}", addr);
|
||||||
|
if args.no_tls {
|
||||||
|
println!("WARNING: TLS is DISABLED — traffic is unencrypted");
|
||||||
|
} else {
|
||||||
|
println!("TLS: enabled (auto-generated self-signed certificate)");
|
||||||
|
}
|
||||||
if args.token.is_empty() {
|
if args.token.is_empty() {
|
||||||
println!("WARNING: Authentication is DISABLED");
|
println!("WARNING: Authentication is DISABLED");
|
||||||
} else {
|
} else {
|
||||||
println!("Authentication is enabled (Bearer token required)");
|
println!("Authentication: enabled (Bearer token)");
|
||||||
}
|
}
|
||||||
println!("Document Root: {:?}", root_path);
|
println!("Document Root: {:?}", root_path);
|
||||||
println!("Metrics endpoint: http://{}/metrics", metrics_addr);
|
println!("Metrics: http://{}/metrics", metrics_addr);
|
||||||
|
|
||||||
// Spawn the Prometheus metrics HTTP server on a separate port
|
// Spawn the Prometheus metrics HTTP server on a separate port
|
||||||
let metrics_app = Router::new().route("/metrics", get(metrics_handler));
|
let metrics_app = Router::new().route("/metrics", get(metrics_handler));
|
||||||
@@ -85,14 +104,39 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
axum::serve(metrics_listener, metrics_app).await.unwrap();
|
axum::serve(metrics_listener, metrics_app).await.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
Server::builder()
|
let mut builder = Server::builder()
|
||||||
// Enable TCP Keep-Alive and HTTP2 Ping to keep connections alive for long media streams
|
|
||||||
.tcp_keepalive(Some(std::time::Duration::from_secs(60)))
|
.tcp_keepalive(Some(std::time::Duration::from_secs(60)))
|
||||||
.http2_keepalive_interval(Some(std::time::Duration::from_secs(60)))
|
.http2_keepalive_interval(Some(std::time::Duration::from_secs(60)));
|
||||||
|
|
||||||
|
if !args.no_tls {
|
||||||
|
let cert_pair = rcgen::generate_simple_self_signed(vec![
|
||||||
|
"localhost".to_string(),
|
||||||
|
"furumi-ng".to_string(),
|
||||||
|
])
|
||||||
|
.expect("Failed to generate self-signed certificate");
|
||||||
|
|
||||||
|
let cert_pem = cert_pair.cert.pem();
|
||||||
|
let key_pem = cert_pair.signing_key.serialize_pem();
|
||||||
|
|
||||||
|
// Optionally save the certificate PEM for the client
|
||||||
|
if let Some(ref cert_path) = args.tls_cert_out {
|
||||||
|
std::fs::write(cert_path, &cert_pem)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
eprintln!("Error: Failed to write TLS cert to {:?}: {}", cert_path, e);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
println!("TLS certificate saved to {:?} (use with client --tls-ca)", cert_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let identity = Identity::from_pem(cert_pem, key_pem);
|
||||||
|
let tls_config = ServerTlsConfig::new().identity(identity);
|
||||||
|
builder = builder.tls_config(tls_config)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
.add_service(svc)
|
.add_service(svc)
|
||||||
.serve(addr)
|
.serve(addr)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user