Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,36 @@ jobs:
- name: Integration tests (repo mount)
run: unset HF_TOKEN && cargo test --release --test repo_ops -- --test-threads=1 --nocapture

smoke-test-windows:
name: Smoke Tests (Windows NFS)
runs-on: windows-2022
needs: lint-test
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
HF_ENDPOINT: https://huggingface.co
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@1.95.0
with:
targets: x86_64-pc-windows-msvc

- uses: Swatinem/rust-cache@v2
with:
key: smoke-windows

- name: Enable Client for NFS feature
shell: powershell
run: Install-WindowsFeature -Name NFS-Client

- name: Build hf-mount-nfs
shell: powershell
run: cargo build --release --no-default-features --features nfs --bin hf-mount-nfs

- name: Integration tests (NFS, bucket lifecycle)
shell: powershell
run: cargo test --release --no-default-features --features nfs --test nfs_ops -- --test-threads=1 --nocapture

fsx:
name: fsx (data integrity)
runs-on:
Expand Down
45 changes: 43 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,46 @@ jobs:
name: macos-${{ matrix.arch }}
path: dist/

build-windows:
name: Build Windows (${{ matrix.arch }})
needs: [bump]
if: |
always() &&
(startsWith(github.ref, 'refs/tags/') || needs.bump.result == 'success')
runs-on: windows-2022
strategy:
matrix:
include:
- arch: x86_64
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.bump.outputs.new_tag || github.ref }}

- uses: dtolnay/rust-toolchain@1.95.0
with:
targets: ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
with:
key: release-${{ matrix.target }}

- name: Build
# No vendored-openssl: native-tls uses Schannel on Windows, no OpenSSL dependency.
run: cargo build --release --target ${{ matrix.target }} --no-default-features --features nfs --bin hf-mount-nfs

- name: Package
shell: bash
run: |
mkdir -p dist
cp target/${{ matrix.target }}/release/hf-mount-nfs.exe dist/hf-mount-nfs-${{ matrix.arch }}-windows.exe

- uses: actions/upload-artifact@v4
with:
name: windows-${{ matrix.arch }}
path: dist/

# Compute the docker tag base + whether to push :latest. A bumped
# release wins, then a manually-pushed tag, else fall back to sha-<sha>.
# `latest` only on stable releases (tag matches `v\d` and has no `-`).
Expand Down Expand Up @@ -263,18 +303,19 @@ jobs:

release:
name: Create Release
needs: [bump, build-linux, build-macos]
needs: [bump, build-linux, build-macos, build-windows]
if: |
always() &&
needs.build-linux.result == 'success' &&
needs.build-macos.result == 'success' &&
needs.build-windows.result == 'success' &&
(startsWith(github.ref, 'refs/tags/') || needs.bump.result == 'success')
runs-on: ubuntu-22.04
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
pattern: '{linux-*,macos-*}'
pattern: '{linux-*,macos-*,windows-*}'
merge-multiple: true

- name: List artifacts
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/windows-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Windows Build

on:
pull_request:
paths:
- "src/**"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/windows-build.yml"
workflow_dispatch:

env:
CARGO_TERM_COLOR: always

jobs:
build:
name: Build hf-mount-nfs.exe
runs-on: windows-2022
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@1.95.0
with:
targets: x86_64-pc-windows-msvc

- uses: Swatinem/rust-cache@v2
with:
key: windows-build

- name: Build hf-mount-nfs
# No vendored-openssl: native-tls uses Schannel on Windows, no OpenSSL dependency.
run: cargo build --release --target x86_64-pc-windows-msvc --no-default-features --features nfs --bin hf-mount-nfs

- name: Stage artifacts
shell: bash
run: |
mkdir -p dist
cp target/x86_64-pc-windows-msvc/release/hf-mount-nfs.exe dist/

- uses: actions/upload-artifact@v4
with:
name: hf-mount-windows-x86_64
path: dist/
if-no-files-found: error
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ bytes = "1"
chrono = "0.4"
clap = { version = "4", features = ["derive", "env"] }
ctrlc = { version = "3", features = ["termination"] }
fuser = { version = "0.17", optional = true }
futures = "0.3"
libc = "0.2"
nfsserve = { version = "0.11", optional = true }
reqwest = { version = "0.12", features = ["json", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal", "process"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
ulid = "1"
Expand All @@ -40,6 +39,12 @@ fuse = ["dep:fuser"]
nfs = ["dep:nfsserve"]
vendored-openssl = ["openssl-sys/vendored"]

# fuser is Unix-only (Linux/macOS FUSE bindings). Gating it as a Unix-target
# dep means the `fuse` feature is effectively unavailable on Windows; the NFS
# backend (`hf-mount-nfs`) is the supported path on Windows.
[target.'cfg(unix)'.dependencies]
fuser = { version = "0.17", optional = true }

[dependencies.openssl-sys]
version = "0.9"
optional = true
Expand Down Expand Up @@ -68,5 +73,8 @@ tempfile = "3"

# Pin fuser to the HF fork until cberner/fuser merges Session::from_fds
# (sidecar with externally pre-cloned fds — see #94).
# Pin nfsserve to the HF fork until the portmap_listener PR is merged & released
# (Windows NFS clients need a portmapper on 127.0.0.1:111 — see nfsserve#44).
[patch.crates-io]
fuser = { git = "https://github.com/huggingface/fuser.git", branch = "sidecar-multi-fd" }
nfsserve = { git = "https://github.com/huggingface/nfsserve.git", branch = "feat/portmap-listener" }
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,38 @@ The NFS backend has no system dependencies. For FUSE:

**macOS**: install [macFUSE](https://osxfuse.github.io/) (`brew install macfuse`, requires reboot on first install)

**Windows**: NFS backend only (FUSE is Unix-only). Enable the "Client for NFS" feature:

```powershell
# Windows Server
Install-WindowsFeature -Name NFS-Client

# Windows 10 / 11
Enable-WindowsOptionalFeature -Online -FeatureName ServicesForNFS-ClientOnly,ClientForNFS-Infrastructure -All
```

`hf-mount-nfs.exe` must run as Administrator (it binds the privileged portmapper port 111). To make admin-mounted drives visible to non-admin Explorer / apps, set `EnableLinkedConnections` and reboot ([MS doc](https://learn.microsoft.com/en-US/troubleshoot/windows-client/networking/mapped-drives-not-available-from-elevated-command)):

```cmd
reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLinkedConnections /t REG_DWORD /d 1 /f
```

### Build from source

Requires Rust 1.89+.

```bash
# NFS only (no system deps, works everywhere)
# NFS only (no system deps, works everywhere including Windows)
cargo build --release --features nfs

# FUSE (requires macFUSE on macOS, fuse3 on Linux)
# FUSE (requires macFUSE on macOS, fuse3 on Linux; not available on Windows)
cargo build --release --features fuse

# All backends
cargo build --release --features fuse,nfs
```

Binaries: `target/release/hf-mount`, `target/release/hf-mount-nfs`, `target/release/hf-mount-fuse`
Binaries: `target/release/hf-mount`, `target/release/hf-mount-nfs`, `target/release/hf-mount-fuse`. On Windows only `hf-mount-nfs.exe` is produced.

## Best for / Not for

Expand Down
10 changes: 10 additions & 0 deletions src/bin/hf-mount-fuse.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
#[cfg(not(unix))]
fn main() {
eprintln!("hf-mount-fuse is Unix-only (FUSE). Use hf-mount-nfs.exe on Windows.");
std::process::exit(1);
}

#[cfg(unix)]
use tracing::info;

#[cfg(unix)]
use hf_mount::fuse::mount_fuse;
#[cfg(unix)]
use hf_mount::setup::setup;

#[cfg(unix)]
fn main() {
let s = setup(false);
let mut daemon_guard = hf_mount::daemon::DaemonGuard::from_env();
Expand Down
10 changes: 10 additions & 0 deletions src/bin/hf-mount.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg_attr(not(unix), allow(dead_code, unused_imports))]

use std::path::PathBuf;

use clap::Parser;
Expand Down Expand Up @@ -30,6 +32,13 @@ enum Command {
Status,
}

#[cfg(not(unix))]
fn main() {
eprintln!("hf-mount daemon controller is Unix-only. On Windows, run hf-mount-nfs.exe directly.");
std::process::exit(1);
}

#[cfg(unix)]
fn main() {
let cli = Cli::parse();

Expand Down Expand Up @@ -110,6 +119,7 @@ fn main() {

/// Replace the current process with the backend binary via execvp.
/// Passes the ready-notification fd via the HF_MOUNT_DAEMON_FD env var.
#[cfg(unix)]
fn exec_backend(backend: &std::path::Path, args: &[String], guard: &hf_mount::daemon::DaemonGuard) -> std::io::Error {
use std::os::unix::process::CommandExt;

Expand Down
15 changes: 14 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
pub mod cached_xet_client;
#[cfg(unix)]
pub mod daemon;
#[cfg(not(unix))]
pub mod daemon {
//! Windows stub: only the surface used by hf-mount-nfs. The full daemon
//! controller (hf-mount) is Unix-only and not built on Windows.
pub struct DaemonGuard;
impl DaemonGuard {
pub fn from_env() -> Option<Self> {
None
}
pub fn notify_ready(&mut self) {}
}
}
pub mod error;
pub mod file_cache;
#[cfg(feature = "fuse")]
#[cfg(all(unix, feature = "fuse"))]
pub mod fuse;
pub mod hub_api;
#[cfg(feature = "nfs")]
Expand Down
Loading
Loading