Note: This software is under active development.
CLI arguments and documentation may change until a stable (v1.0.0) release.
zipfuse is a read-only FUSE filesystem that mirrors another filesystem, but
exposing only its contained ZIP archives as files and folders. It handles
in-memory enumeration, chunked streaming and on-the-fly extraction - so that
consumers remain entirely unaware of an archive being involved. It includes a
HTTP webserver for a responsive diagnostics dashboard and runtime configurables.
The filesystem strives to remain simple and purpose-driven, while also utilizing caching both in userspace and on the kernel side for improved performance. In contrast to similar filesystems, it does not mount single ZIP archives, but handles any ZIP archives contained within a filesystem without re-mounting.
While initially developed entirely for a personal need and being used with photo albums, it is organically growing into a far more general-purpose direction, so that it can be useful for other applications also.
To build from source, a Makefile is included with the project's source code.
Running make all will compile the application and pull in any necessary
dependencies. make check runs the test suite and static analysis tools.
The Makefile assumes a Go installation (1.25.1+) as a prerequisite.
git clone https://github.com/desertwitch/zipfuse.git
cd zipfuse
make all
./zipfuse --helpRunning make all produces two binaries:
zipfuse- binary of the FUSE filesystemmount.zipfuse- binary of the FUSE mount helper
The latter is needed only for mounting with mount(8) or /etc/fstab.
The documentation can be built using make docs (requires asciidoc & w3m).
You will need to ensure that you have FUSE (libfuse, fuse3...) installed on
the system that you are planning to use zipfuse on. The only hard dependency
of zipfuse is the fusermount3 binary, so ensure it exists in your $PATH.
The recommended location to install FUSE filesystems to can differ between Linux
distributions. Most important is that you install the binaries to a location
that is covered in your $PATH environment variable. A common and relatively
portable solution would be installing the zipfuse binary into /bin and the
mount.zipfuse binary into /sbin on your system. You have to ensure that the
files have the appropriate permissions set for users intending to execute them,
specifically the executable bit needs to be set on both binaries (chmod +x).
As can be derived from the recommended paths above, the zipfuse binary itself
does not need elevated permissions. In contrast, the mount.zipfuse is usually
executed by the system as root (when processing /etc/fstab), but will (when
configured to do so) execute the filesystem binary as a given unprivileged user.
You can install the documentation manpages by simply copying the release-bundled
manpage files from the man directory to the location observed by your man
program. If building from source, they will be present in docs, respectively.
If unsure about the target paths, executing of man --path should reveal them.
The zipfuse filesystem binary runs as a foreground process and is ideal for
systemd wrapping, or use directly from command-line as either a foreground or
background (paired with nohup and/or &) process. For continous usage,
integration into the larger systemd framework is recommended and preferable.
For mounting using the command-line:
zipfuse <source> <mountpoint> [flags]
<source> is the root of the underlying filesystem to expose.
<mountpoint> is the mountpoint where the FUSE filesystem will appear.
For mounting using a systemd service unit:
[Unit]
Description=ZipFUSE
[Service]
Type=simple
ExecStart=/usr/local/bin/zipfuse /home/alice/zips /home/alice/zipfuse --webserver :8000
Restart=on-failure
RestartSec=5
TimeoutStartSec=30
TimeoutStopSec=30
KillSignal=SIGTERM
User=alice
Group=alice
[Install]
WantedBy=multi-user.targetIt is not recommended to use a .mount unit over a .service unit.
The reason is that a .mount unit would again rely on the FUSE mount helper.
For more complex orchestration with systemd, see also inside the examples folder.
The above are the recommended and modern approaches for almost all use cases.
For users not able to use systemd, a FUSE mount helper is provided, so the
filesystem can be used with mount(8) or also /etc/fstab entry. This usually
requires putting the mount.zipfuse binary into /sbin or another location
that the mount(8) program examines for the filesystem helper binaries.
For mounting using the mount(8) program:
sudo mount -t zipfuse /home/alice/zips /home/alice/zipfuse -o setuid=alice,allow_other,webserver=:8000
For mounting using an entry in the /etc/fstab file:
# <file system> <mount point> <type> <options> <dump> <pass>
/home/alice/zips /home/alice/zipfuse zipfuse setuid=alice,allow_other,webserver=:8000 0 0
Additional mount options to control mount helper behavior itself:
setuid=USER (as username or UID; overrides executing user)
xbin=/full/path/to/zipfuse/binary (overrides filesystem binary)
xlog=/full/path/to/writeable/logfile (overrides filesystem logfile)
xtim=SECS (numeric and in seconds; overrides filesystem mount timeout)
As you can see, program options (read more below) need format conversion:
--allow-other --webserver :8000 is turning into allow_other,webserver=:8000
Note that FUSE mount helper events are printed to standard error (stderr).
Any filesystem events are printed to /var/log/zipfuse.log (if it is writeable).
The filesystem will observe SIGTERM and SIGINT to initiate a graceful
unmount of the filesystem, if it is not busy. In foreground mode, this means you
can simply press CTRL+C to unmount the filesystem. In background mode, you can
send SIGTERM to the filesystem's PID using kill. Alternatively, of course,
fusermount3 -u or umount can be used directly on the mountpoint, which also
allows forcing an unmount on a stuck as busy filesystem (if so required).
You can update the filesystem by simply replacing any installed files in the locations you have installed them to with their new counterparts. This is best done when no instances of the filesystem are currently mounted.
zipfuse <source> <mountpoint> [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--allow-other <bool> |
-a | (true if root; false if not) | Allow other system users to access the mounted filesystem. |
--dry-run <bool> |
-d | false | Do not mount; instead print all would-be inodes and paths to standard output. |
--fd-cache-bypass <bool> |
(none) | false | Disable file descriptor caching; open/close a new file descriptor on every single request. |
--fd-cache-size <int> |
(none) | (70% of fd-limit) |
Maximum open file descriptors to retain in cache (for more performant re-accessing). |
--fd-cache-ttl <duration> |
(none) | 60s | Time-to-live before evicting cached file descriptors (that are not in use). |
--fd-limit <int> |
(none) | (50% of OS soft limit) | Maximum total open file descriptors at any given time (must be > fd-cache-size). |
--flatten-zips <bool> |
-f | false | Flatten ZIP-contained subdirectories into one directory per ZIP archive. |
--force-unicode <bool> |
(none) | true | Unicode (or fallback to synthetic generated) paths for ZIPs; disabling garbles non-compliant ZIPs when trying to be interpreted as unicode. |
--must-crc32 <bool> |
(none) | false | Force integrity verification for non-compressed ZIP archives (slower). |
--ring-buffer-size <int> |
(none) | 500 | Lines of the in-memory event ring-buffer (as served in the diagnostics dashboard). |
--stream-pool-size <size> |
(none) | 128KiB | Buffer size for the streamed read buffer pool (multiplies with concurrency). |
--stream-threshold <size> |
-s | 1MiB | Files larger than this are streamed in chunks, instead of fully loaded into RAM. |
--strict-cache <bool> |
(none) | false | Do not treat ZIP files/contents as immutable (non-changing) for caching decisions. |
--verbose <bool> |
-v | false | Print all FUSE communication and diagnostics to standard error. |
| --version | (none) | false | Print the program version to standard output. |
--webserver <addr> |
-w | (empty) | Address for the diagnostics dashboard (e.g. :8000). If unset, the webserver is disabled. |
Size parameters accept human-readable formats like 1024, 128KB, 128KiB, 10MB, or 10MiB.
Duration parameters accept Go duration formats like 30s, 5m, 1h, or combined values like 1h30m.
Mount /home/alice/zips onto /home/alice/zipfuse and serve dashboard on port 8080:
zipfuse /home/alice/zips /home/alice/zipfuse --webserver :8080
Dry-run to inspect would-be inodes and files without actual mounting:
zipfuse /home/alice/zips /home/alice/zipfuse --dry-run
When enabled, the diagnostics server exposes the following routes:
/for filesystem dashboard and event ring-buffer/gcfor forcing of a garbage collection (within Go)/resetfor resetting the filesystem metrics at runtime/set/must-crc32/<bool>for adapting forced integrity checking/set/fd-cache-bypass/<bool>for bypassing the file descriptor cache/set/stream-threshold/<string>for adapting of the streaming threshold
The following signals are observed and handled by the filesystem:
SIGTERMorSIGINT(CTRL+C) gracefully unmounts the filesystemSIGUSR1forces a garbage collection (within Go)SIGUSR2dumps a diagnostic stacktrace to standard error (stderr)
The filesystem is read-only, purpose-built and assumes more or less static content being served for a few consuming applications. While it may well be possible it works for larger-scale operations or in more complex environments, it was not built for such and should always be used with appropriate cautions.
It is important to note that uncompressed ZIP archives will offer raw I/O
performance, provided that --must-crc32 is not enabled. For users wishing to
utilize only the organizational benefit of ZIP archives, creating their ZIP
archives with no compression can yield significant performance benefits, at the
cost of more storage consumption.
Uncompressed archives also benefit from true seeking, while compressed archives implement only pseudo-seeking (discard to request offset), which adds further overhead adding to that of the decompressor.
The webserver is disabled by default. When enabled, it is unsecured and assumes an otherwise appropriately secured environment (a modern reverse proxy, firewall, ...) to prevent any unauthorized access to the runtime configurables.
Feel free to fork this project as needed, or open issues and pull requests if you notice issues or otherwise wish to add features - but please do approach them with perspective of it originally being a personal, small-scale project.
All code is licensed under the MIT license.

