Compare commits
9 Commits
macos-clie
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc77933c9e | ||
|
|
848d1643a5 | ||
|
|
8afd942aad | ||
|
|
7aee798a01 | ||
|
|
3a31173046 | ||
|
|
b0fc1fa92c | ||
|
|
5cd53f277b | ||
|
|
80bfc69c0b | ||
|
|
dc4fbd7407 |
50
.github/workflows/docker-publish.yml
vendored
Normal file
50
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Publish Server Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/furumi-server
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,format=short
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -35,7 +35,7 @@ A Rust library containing all OS-agnostic logic:
|
||||
#### B. OS-Specific Mount Layer
|
||||
Thin executable wrappers that consume the Shared Client Core and handle OS integration:
|
||||
- **Linux:** Uses the `fuser` crate (binds to `libfuse`) to mount and handle events from `/dev/fuse`.
|
||||
- **macOS:** Can utilize `macFUSE` via the `fuser` crate initially. Future iterations may explore native Apple `FileProvider` frameworks using FFI to bypass third-party kext requirements.
|
||||
- **macOS:** Acts as a lightweight local NFSv3/v4 server (`nfssrv` or similar crate). The system natively mounts `localhost:/` via the built-in NFS client, avoiding any need for third-party kernel extensions (like `macFUSE`) or complex FileProvider bindings.
|
||||
- **Windows (Future):** Will wrap libraries like `WinFSP` or `dokany` to integrate with the Windows internal VFS.
|
||||
|
||||
## API Design (Read-Only Foundation)
|
||||
|
||||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
||||
# Stage 1: Build the rust binary
|
||||
FROM rust:1.88.0-bookworm AS builder
|
||||
|
||||
# Install supplementary dependencies that might be needed
|
||||
RUN apt-get update && apt-get install -y \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
protobuf-compiler \
|
||||
cmake \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Option: Copy in root workspace files and source crates
|
||||
COPY . .
|
||||
|
||||
# Build only the server for release
|
||||
RUN cargo build --release --bin furumi-server
|
||||
|
||||
# Stage 2: Create the minimal runtime image
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Install system dependencies needed at runtime (like OpenSSL if dynamically linked)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
libssl-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a non-root user
|
||||
RUN useradd -ms /bin/bash appuser
|
||||
WORKDIR /home/appuser
|
||||
|
||||
# Copy the binary from the builder stage
|
||||
COPY --from=builder /usr/src/app/target/release/furumi-server /usr/local/bin/furumi-server
|
||||
|
||||
USER appuser
|
||||
|
||||
# Expose ports: 50051 (gRPC) and 9090 (Metrics)
|
||||
EXPOSE 50051
|
||||
EXPOSE 9090
|
||||
|
||||
# Command to run the server
|
||||
ENTRYPOINT ["furumi-server"]
|
||||
12
README.md
12
README.md
@@ -7,12 +7,13 @@ Designed for streaming media (video, music) over the network.
|
||||
## Architecture
|
||||
|
||||
```
|
||||
furumi-server (gRPC + TLS) ←→ furumi-client-core ←→ furumi-mount-linux (FUSE)
|
||||
furumi-server (gRPC + TLS) ←→ furumi-client-core ←→ furumi-mount-{linux,macos} (FUSE / NFS)
|
||||
```
|
||||
|
||||
- **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-mount-linux** — mounts the remote directory locally via FUSE (read-only)
|
||||
- **furumi-mount-macos** — mounts the remote directory locally via a local NFS server (read-only)
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -26,12 +27,18 @@ cargo build --release --workspace
|
||||
--token mysecrettoken \
|
||||
--tls-cert-out /tmp/furumi-ca.pem
|
||||
|
||||
# Client — automatically uses TLS, trusts server certificate
|
||||
# Client (Linux) — automatically uses TLS, trusts server certificate
|
||||
./target/release/furumi-mount-linux \
|
||||
--server server-ip:50051 \
|
||||
--token mysecrettoken \
|
||||
--mount /mnt/remote
|
||||
|
||||
# Client (macOS)
|
||||
./target/release/furumi-mount-macos \
|
||||
--server server-ip:50051 \
|
||||
--token mysecrettoken \
|
||||
--mount /Volumes/remote
|
||||
|
||||
# Use it
|
||||
ls /mnt/remote
|
||||
mpv /mnt/remote/video.mkv
|
||||
@@ -80,6 +87,7 @@ Available at `http://<metrics-bind>/metrics`:
|
||||
## Requirements
|
||||
|
||||
- Linux with `libfuse3-dev` and `pkg-config` (for client)
|
||||
- macOS (uses built-in NFS client)
|
||||
- Rust 2024 edition
|
||||
|
||||
## License
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "furumi-server"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -100,6 +100,24 @@ impl RequestTimer {
|
||||
}
|
||||
}
|
||||
|
||||
/// An RAII guard that increments the ACTIVE_STREAMS gauge when created
|
||||
/// and decrements it when dropped. This ensures streams are correctly counted
|
||||
/// even if they terminate abruptly.
|
||||
pub struct ActiveStreamGuard;
|
||||
|
||||
impl ActiveStreamGuard {
|
||||
pub fn new() -> Self {
|
||||
ACTIVE_STREAMS.inc();
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ActiveStreamGuard {
|
||||
fn drop(&mut self) {
|
||||
ACTIVE_STREAMS.dec();
|
||||
}
|
||||
}
|
||||
|
||||
/// Render all registered metrics in Prometheus text format.
|
||||
pub fn render_metrics() -> String {
|
||||
let encoder = TextEncoder::new();
|
||||
|
||||
@@ -60,15 +60,14 @@ impl<V: VirtualFileSystem> RemoteFileSystem for RemoteFileSystemImpl<V> {
|
||||
match self.vfs.read_dir(&safe_path).await {
|
||||
Ok(mut rx) => {
|
||||
timer.finish_ok();
|
||||
metrics::ACTIVE_STREAMS.inc();
|
||||
let stream = async_stream::try_stream! {
|
||||
let _guard = metrics::ActiveStreamGuard::new();
|
||||
while let Some(result) = rx.recv().await {
|
||||
match result {
|
||||
Ok(entry) => yield entry,
|
||||
Err(e) => Err(Status::internal(e.to_string()))?,
|
||||
}
|
||||
}
|
||||
metrics::ACTIVE_STREAMS.dec();
|
||||
};
|
||||
Ok(Response::new(Box::pin(stream) as Self::ReadDirStream))
|
||||
}
|
||||
@@ -103,8 +102,8 @@ impl<V: VirtualFileSystem> RemoteFileSystem for RemoteFileSystemImpl<V> {
|
||||
match self.vfs.read_file(sanitized_req).await {
|
||||
Ok(mut rx) => {
|
||||
timer.finish_ok();
|
||||
metrics::ACTIVE_STREAMS.inc();
|
||||
let stream = async_stream::try_stream! {
|
||||
let _guard = metrics::ActiveStreamGuard::new();
|
||||
while let Some(result) = rx.recv().await {
|
||||
match result {
|
||||
Ok(chunk) => {
|
||||
@@ -117,7 +116,6 @@ impl<V: VirtualFileSystem> RemoteFileSystem for RemoteFileSystemImpl<V> {
|
||||
}
|
||||
}
|
||||
}
|
||||
metrics::ACTIVE_STREAMS.dec();
|
||||
};
|
||||
Ok(Response::new(Box::pin(stream) as Self::ReadFileStream))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user