Compare commits
11 Commits
v0.2.0
...
f2d42751fd
| Author | SHA1 | Date | |
|---|---|---|---|
| f2d42751fd | |||
| bc34b6bc41 | |||
| 3ee7235b51 | |||
| 1f85d9c435 | |||
| 773e9c1ee7 | |||
| 0242376a65 | |||
|
|
64f292c7b1 | ||
| 73b6d7483e | |||
|
|
64b59ba72d | ||
|
|
c54af23845 | ||
|
|
dd3f3721b2 |
63
.github/workflows/deb-publish.yml
vendored
Normal file
63
.github/workflows/deb-publish.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Build and Publish Deb Package
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build-deb:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable universe and install dependencies
|
||||
run: |
|
||||
sudo add-apt-repository universe -y
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fuse3 libfuse3-dev pkg-config protobuf-compiler cmake
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb --locked
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build deb package
|
||||
run: |
|
||||
cargo deb -p furumi-mount-linux \
|
||||
--deb-version ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Locate deb file
|
||||
id: deb
|
||||
run: |
|
||||
DEB=$(ls target/debian/furumi-mount-linux_*.deb | head -1)
|
||||
echo "path=$DEB" >> "$GITHUB_OUTPUT"
|
||||
echo "name=$(basename $DEB)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Publish to Gitea APT registry
|
||||
run: |
|
||||
TARGET_URL="${{ secrets.PKG_REGISTRY_URL }}/api/packages/${{ secrets.PKG_OWNER }}/debian/pool/noble/main/upload"
|
||||
echo "Uploading to: $TARGET_URL"
|
||||
curl --fail-with-body \
|
||||
--user "${{ secrets.PKG_USER }}:${{ secrets.PKG_TOKEN }}" \
|
||||
--upload-file "${{ steps.deb.outputs.path }}" \
|
||||
"$TARGET_URL"
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -585,7 +585,7 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "furumi-client-core"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -607,7 +607,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "furumi-common"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"protobuf-src",
|
||||
@@ -617,7 +617,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "furumi-mount-linux"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -634,7 +634,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "furumi-mount-macos"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -652,7 +652,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "furumi-server"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
|
||||
14
README.md
14
README.md
@@ -27,13 +27,7 @@ cargo build --release --workspace
|
||||
--token mysecrettoken \
|
||||
--tls-cert-out /tmp/furumi-ca.pem
|
||||
|
||||
# Client (Linux) — automatically uses TLS, trusts server certificate
|
||||
./target/release/furumi-mount-linux \
|
||||
--server server-ip:50051 \
|
||||
--token mysecrettoken \
|
||||
--mount /mnt/remote
|
||||
|
||||
# Client (macOS)
|
||||
# Client
|
||||
./target/release/furumi-mount-macos \
|
||||
--server server-ip:50051 \
|
||||
--token mysecrettoken \
|
||||
@@ -44,6 +38,12 @@ ls /mnt/remote
|
||||
mpv /mnt/remote/video.mkv
|
||||
```
|
||||
|
||||
### Linux FUSE3
|
||||
Linux client uses FUSE. Install with:
|
||||
```
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install libfuse3-dev
|
||||
```
|
||||
## Encryption
|
||||
|
||||
TLS is enabled by default. The server auto-generates a self-signed certificate on each start — no manual cert management required. The client automatically trusts the server's certificate for encryption.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "furumi-client-core"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -155,7 +155,8 @@ impl FurumiClient {
|
||||
.timeout(Duration::from_secs(30))
|
||||
.concurrency_limit(256)
|
||||
.tcp_keepalive(Some(Duration::from_secs(60)))
|
||||
.http2_keep_alive_interval(Duration::from_secs(60));
|
||||
.http2_keep_alive_interval(Duration::from_secs(60))
|
||||
.keep_alive_while_idle(true);
|
||||
|
||||
let channel = if is_https {
|
||||
info!("TLS enabled (encryption only, certificate verification disabled)");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "furumi-common"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "furumi-mount-linux"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -15,3 +15,16 @@ tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
tokio-stream = "0.1.18"
|
||||
ctrlc = "3.5.2"
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "Furumi"
|
||||
copyright = "Furumi contributors"
|
||||
extended-description = "Furumi-ng: mount remote filesystem via encrypted gRPC + FUSE"
|
||||
depends = "fuse3"
|
||||
section = "utils"
|
||||
priority = "optional"
|
||||
maintainer-scripts = "debian/"
|
||||
assets = [
|
||||
{ source = "target/release/furumi-mount-linux", dest = "usr/bin/furumi-mount-linux", mode = "755" },
|
||||
{ source = "debian/furumi-mount.service", dest = "usr/lib/systemd/user/furumi-mount.service", mode = "644" },
|
||||
]
|
||||
|
||||
15
furumi-mount-linux/debian/furumi-mount.service
Normal file
15
furumi-mount-linux/debian/furumi-mount.service
Normal file
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Furumi remote filesystem mount
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=%h/.config/furumi/config
|
||||
ExecStart=/usr/bin/furumi-mount-linux
|
||||
ExecStopPost=fusermount3 -uz ${FURUMI_MOUNT}
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
36
furumi-mount-linux/debian/postinst
Normal file
36
furumi-mount-linux/debian/postinst
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "$1" = "configure" ]; then
|
||||
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
|
||||
REAL_USER="$SUDO_USER"
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
|
||||
CONFIG_DIR="$REAL_HOME/.config/furumi"
|
||||
CONFIG_FILE="$CONFIG_DIR/config"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
cat > "$CONFIG_FILE" << 'EOF'
|
||||
# Furumi mount configuration
|
||||
# Edit the values below, then enable and start the service:
|
||||
#
|
||||
# systemctl --user enable --now furumi-mount.service
|
||||
#
|
||||
# To apply changes after editing this file:
|
||||
#
|
||||
# systemctl --user restart furumi-mount.service
|
||||
|
||||
FURUMI_SERVER=your-server:50051
|
||||
FURUMI_TOKEN=your-token-here
|
||||
FURUMI_MOUNT=/path/to/mountpoint
|
||||
EOF
|
||||
chown -R "$REAL_USER:$REAL_USER" "$CONFIG_DIR"
|
||||
echo ""
|
||||
echo "furumi-mount: config created at $CONFIG_FILE"
|
||||
echo "furumi-mount: edit the file, then run:"
|
||||
echo " systemctl --user enable --now furumi-mount.service"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -57,7 +57,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
let client = rt.block_on(async {
|
||||
FurumiClient::connect(&full_addr, &args.token).await
|
||||
let c = FurumiClient::connect(&full_addr, &args.token).await?;
|
||||
|
||||
// Ping the server to verify connection and authentication token
|
||||
if let Err(e) = c.get_attr("/").await {
|
||||
return Err(format!("Failed to authenticate or connect to server: {}", e).into());
|
||||
}
|
||||
|
||||
Ok::<_, Box<dyn std::error::Error>>(c)
|
||||
})?;
|
||||
|
||||
let fuse_fs = fs::FurumiFuse::new(client, rt.handle().clone());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "furumi-mount-macos"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -116,7 +116,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
// Unmount
|
||||
let _ = Command::new("umount").arg(mount_point_umount.to_string_lossy().as_ref()).status();
|
||||
let _ = Command::new("diskutil")
|
||||
.arg("unmount")
|
||||
.arg("force")
|
||||
.arg(mount_point_umount.to_string_lossy().as_ref())
|
||||
.status();
|
||||
|
||||
handle.abort();
|
||||
println!("Unmounted successfully.");
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "furumi-server"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
56
windows-implementation-plan.md
Normal file
56
windows-implementation-plan.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Implementation Plan for `furumi-mount-windows` Client
|
||||
|
||||
## Architectural Decision
|
||||
|
||||
- **VFS Driver:** `WinFSP` (Windows File System Proxy).
|
||||
- **Justification:** Excellent performance, perfect compatibility with the FUSE model, widely used in similar projects (e.g., rclone, sshfs-win).
|
||||
- **Installation:** A unified installer (bundle) will be created (for example, using Inno Setup or WiX Toolkit), which will:
|
||||
- Check if WinFSP is already installed.
|
||||
- Automatically install the official `winfsp.msi` silently (using `/qn` flags) if the driver is missing.
|
||||
- Install the `furumi-mount-windows.exe` client itself.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Application Scaffold
|
||||
- Create a new binary crate `furumi-mount-windows` within the workspace.
|
||||
- Add dependencies: `winfsp` (or `wfd`), `tokio`, `clap`, `tracing`, and an internal dependency on `furumi-client-core`.
|
||||
|
||||
### 2. Entry Point (CLI)
|
||||
- In `main.rs`, configure parsing for command-line arguments and environment variables (`--server`, `--token`, `--mount`), similar to `furumi-mount-macos`.
|
||||
- Initialize the gRPC connection to the server via `furumi-client-core`.
|
||||
- Configure directory mounting:
|
||||
- As a network drive (e.g., `Z:`).
|
||||
- Or as a transparent folder within an existing NTFS filesystem (depending on driver support/flags).
|
||||
|
||||
### 3. VFS Implementation
|
||||
- Create an `fs.rs` module.
|
||||
- Implement the trait or callback structure required by WinFSP (e.g., the `WinFspFileSystem` structure).
|
||||
- Action mapping:
|
||||
- `GetFileInfo` / `GetSecurityByName` → gRPC `GetAttr` call.
|
||||
- `ReadDirectory` → Streaming gRPC `ReadDir` call.
|
||||
- `ReadFile` → `ReadFile` gRPC call (with support for stream chunking).
|
||||
- **Crucial Part:** Translating Unix file attributes (from gRPC) into Windows File Attributes to ensure the system permits high-performance continuous stream reading (especially for media).
|
||||
|
||||
### 4. Installer Creation
|
||||
- Write a configuration script for a Windows installer builder (e.g., `windows/setup.iss` for Inno Setup).
|
||||
- Neatly bundle both `winfsp-x.y.z.msi` and `furumi-mount-windows.exe` together.
|
||||
- Add Custom Actions / Logic to:
|
||||
- Check the Windows Registry for an existing WinFSP installation.
|
||||
- Trigger the `winfsp.msi` installation conditionally.
|
||||
|
||||
### 5. CI/CD Integration
|
||||
- Update the GitHub Actions workflow (`docker-publish.yml` or create a dedicated release workflow).
|
||||
- Add the target toolchain: `x86_64-pc-windows-msvc`.
|
||||
- Add a step to compile: `cargo build --release --bin furumi-mount-windows`.
|
||||
- Add a step to build the installer (e.g., `iscc setup.iss` or via `cargo-wix`).
|
||||
- Output the final `setup.exe` as a GitHub Release artifact alongside other binaries.
|
||||
|
||||
### 6. Testing Strategy
|
||||
- Write unit tests in Rust covering attribute translation and path mapping (mapping slashes `/` to backslashes `\`).
|
||||
- Manual System Testing:
|
||||
- Start `furumi-server` locally.
|
||||
- Run the installer on a clean Windows machine (VM without pre-installed WinFSP).
|
||||
- Verify that the drive mounts correctly and seamlessly.
|
||||
- Launch media playback (e.g., via VLC/mpv) to ensure streaming stability over the VFS connection.
|
||||
Reference in New Issue
Block a user