diff --git a/.editorconfig b/.editorconfig index db088c5..ffb3453 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,6 +17,9 @@ indent_size = 4 indent_size = 4 trim_trailing_whitespace = false +[*.yaml] +trim_trailing_whitespace = false + eclint_indent_style = unset [Dockerfile] diff --git a/.github/workflows/golang-build-debug.yml b/.github/workflows/golang-build-debug.yml index 43253a5..a3ecc94 100644 --- a/.github/workflows/golang-build-debug.yml +++ b/.github/workflows/golang-build-debug.yml @@ -12,13 +12,20 @@ jobs: name: build-debug runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v5 + - name: Set up Go + uses: actions/setup-go@v5 with: go-version: 1.25.1 cache: false + - name: Checkout code uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean + - name: Vendor the application for debug run: make vendor + - name: Build the application for debug run: make debug diff --git a/.github/workflows/golang-build.yml b/.github/workflows/golang-build.yml index 4f50719..aca90bc 100644 --- a/.github/workflows/golang-build.yml +++ b/.github/workflows/golang-build.yml @@ -12,15 +12,23 @@ jobs: name: build runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v5 + - name: Set up Go + uses: actions/setup-go@v5 with: go-version: 1.25.1 cache: false + - name: Checkout code uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean + - name: Vendor the application for production run: make vendor + - name: Build the application for production run: make zipfuse + - name: Build the application helper for production run: make mount.zipfuse diff --git a/.github/workflows/golang-coverage.yml b/.github/workflows/golang-coverage.yml index ff21142..90d4b97 100644 --- a/.github/workflows/golang-coverage.yml +++ b/.github/workflows/golang-coverage.yml @@ -12,14 +12,21 @@ jobs: name: coverage runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v5 + - name: Set up Go + uses: actions/setup-go@v5 with: go-version: 1.25.1 cache: false + - name: Checkout code uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean + - name: Run all Go tests run: make test-coverage + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: diff --git a/.github/workflows/golang-tests.yml b/.github/workflows/golang-tests.yml index 198cf84..88c2606 100644 --- a/.github/workflows/golang-tests.yml +++ b/.github/workflows/golang-tests.yml @@ -12,11 +12,17 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v5 + - name: Set up Go + uses: actions/setup-go@v5 with: go-version: 1.25.1 cache: false + - name: Checkout code uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean + - name: Run all Go tests run: make test diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index c90a5d0..7a965cf 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -12,12 +12,19 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v5 + - name: Set up Go + uses: actions/setup-go@v5 with: go-version: 1.25.1 cache: false - - uses: actions/checkout@v4 - - name: golangci-lint run + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean + + - name: Run all Go linters uses: golangci/golangci-lint-action@v8 with: skip-cache: true diff --git a/.github/workflows/zipfuse-cli.yml b/.github/workflows/zipfuse-cli.yml index 3fc9194..4e51a6d 100644 --- a/.github/workflows/zipfuse-cli.yml +++ b/.github/workflows/zipfuse-cli.yml @@ -13,20 +13,24 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.25.1' - - name: Install FUSE run: | sudo apt-get update sudo apt-get install -y fuse3 sudo sed -i 's/#user_allow_other/user_allow_other/' /etc/fuse.conf + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.25.1 + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean + - name: Vendor dependencies run: make vendor diff --git a/.github/workflows/zipfuse-docs.yml b/.github/workflows/zipfuse-docs.yml new file mode 100644 index 0000000..ca14190 --- /dev/null +++ b/.github/workflows/zipfuse-docs.yml @@ -0,0 +1,28 @@ +name: zipfuse-docs +on: + push: + branches: + - master + - main + pull_request: +permissions: + contents: read +jobs: + zipfuse: + name: docs + runs-on: ubuntu-latest + + steps: + - name: Install Asciidoc + run: | + sudo apt-get update + sudo apt-get install -y asciidoc w3m + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean + + - name: Build the documentation + run: make docs diff --git a/.github/workflows/zipfuse-fstab.yml b/.github/workflows/zipfuse-fstab.yml index 174f78c..1cc8c34 100644 --- a/.github/workflows/zipfuse-fstab.yml +++ b/.github/workflows/zipfuse-fstab.yml @@ -13,18 +13,22 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Install FUSE + run: | + sudo apt-get update + sudo apt-get install -y fuse3 - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.25.1' + go-version: 1.25.1 + cache: false - - name: Install FUSE - run: | - sudo apt-get update - sudo apt-get install -y fuse3 + - name: Checkout code + uses: actions/checkout@v4 + + - name: Clean the environment + run: make clean - name: Vendor dependencies run: make vendor @@ -173,7 +177,7 @@ jobs: - name: Unmount filesystem if: always() run: | - if ! sudo fusermount3 -u mountpoint; then + if ! sudo umount mountpoint; then echo "ERROR: Filesystem unmount has failed" exit 1 fi diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 28628bd..3f02a57 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,9 +4,11 @@ project_name: zipfuse before: hooks: + - make clean - make vendor - make lint - make test + - make docs builds: - id: zipfuse @@ -59,6 +61,16 @@ archives: files: - LICENSE - INSTALL + - docs/zipfuse.pdf + - docs/mount.zipfuse.pdf + - src: docs/zipfuse.text + dst: docs/zipfuse.txt + - src: docs/mount.zipfuse.text + dst: docs/mount.zipfuse.txt + - src: docs/zipfuse.1 + dst: man/zipfuse.1 + - src: docs/mount.zipfuse.8 + dst: man/mount.zipfuse.8 signs: - artifacts: checksum diff --git a/INSTALL b/INSTALL index 0e7d20d..9247fcb 100644 --- a/INSTALL +++ b/INSTALL @@ -3,82 +3,41 @@ Installing the filesystem 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 that it exists in your $PATH. +of zipfuse is the "fusermount3" binary, so ensure that 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 +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). +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 +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. -Mounting the filesystem -======================== - -Mounting with command-line or systemd service (recommended): -------------------------------------------------------------- - -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 [flags] - - is the root of the underlying filesystem to expose. - is the mountpoint where the FUSE filesystem will appear. - -For mounting using a systemd service unit: +You can install the documentation manpages by simply copying the bundled manpage +files from the "man" directory to the location observed by your man(1) program. +If unsure about the target paths, executing of "man --path" should reveal them. - [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.target +Using the filesystem +===================== -It 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. +For information on how to use the filesystem, please refer to the bundled +documentation found in the "docs" folder, the bundled manpages found in the +"man" folder, or the project website: https://github.com/desertwitch/zipfuse -The above are the recommended and modern approaches for almost all use cases. - -Mounting with mount(8) and /etc/fstab: ---------------------------------------- - -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: - - # - /home/alice/zips /home/alice/zipfuse zipfuse setuid=alice,allow_other,webserver=:8000 0 0 +Updating the filesystem +======================== -As you can see, program options (read more in README) need format conversion: +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. - --allow-other --webserver :8000 => allow_other,webserver=:8000 +Uninstalling the filesystem +============================ -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). +To uninstall the filesystem, ensure that all instances are unmounted and +then remove any installed files from the locations you have installed to. diff --git a/Makefile b/Makefile index bba7345..5fe4066 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,24 @@ # Makefile +VERSION := $(shell \ + tag=$$(git describe --tags --exact-match 2>/dev/null); \ + if [ -n "$$tag" ]; then echo $$tag | sed 's/^v//'; \ + else git rev-parse --short=7 HEAD; fi) + +A2X = a2x +A2X_FLAGS = -a version=$(VERSION) + +DOCS_DIR = docs + ZIPFUSE = zipfuse ZIPFUSE_DIR = ./cmd/zipfuse +ZIPFUSE_ADOC = $(DOCS_DIR)/zipfuse.adoc HELPER = mount.zipfuse HELPER_DIR = ./cmd/mount.zipfuse +HELPER_ADOC = $(DOCS_DIR)/mount.zipfuse.adoc -VERSION := $(shell \ - tag=$$(git describe --tags --exact-match 2>/dev/null); \ - if [ -n "$$tag" ]; then echo $$tag | sed 's/^v//'; \ - else git rev-parse --short=7 HEAD; fi) - -.PHONY: all $(ZIPFUSE) $(HELPER) check clean debug help info lint test test-coverage vendor +.PHONY: all $(ZIPFUSE) $(HELPER) check clean debug docs docs-clean docs-man docs-pdf docs-text help info lint test test-coverage vendor all: vendor $(ZIPFUSE) $(HELPER) ## Runs the entire build chain for the application @@ -26,6 +33,7 @@ check: ## Runs all static analysis and tests on the application code @$(MAKE) test clean: ## Returns the application build stage to its original state (deleting files) + @$(MAKE) docs-clean @rm -vf $(ZIPFUSE) $(HELPER) || true debug: ## Builds the application in debug mode (with symbols, race checks, ...) @@ -33,6 +41,26 @@ debug: ## Builds the application in debug mode (with symbols, race checks, ...) CGO_ENABLED=1 GOFLAGS="-mod=vendor" go build -ldflags="-X main.Version=$(VERSION)-DBG" -trimpath -race -o $(HELPER) $(HELPER_DIR) @$(MAKE) info +docs: ## Builds all documentation (manpages, PDF, plain text) + @$(MAKE) docs-man + @$(MAKE) docs-pdf + @$(MAKE) docs-text + +docs-clean: ## Removes generated documentation files + @rm -vf $(DOCS_DIR)/*.pdf $(DOCS_DIR)/*.text $(DOCS_DIR)/*.1 $(DOCS_DIR)/*.8 $(DOCS_DIR)/*.xml || true + +docs-man: ## Builds manpage documentation + $(A2X) $(A2X_FLAGS) -f manpage $(ZIPFUSE_ADOC) + $(A2X) $(A2X_FLAGS) -f manpage $(HELPER_ADOC) + +docs-pdf: ## Builds PDF documentation + $(A2X) $(A2X_FLAGS) -f pdf $(ZIPFUSE_ADOC) + $(A2X) $(A2X_FLAGS) -f pdf $(HELPER_ADOC) + +docs-text: ## Builds plain text documentation + $(A2X) $(A2X_FLAGS) -f text $(ZIPFUSE_ADOC) + $(A2X) $(A2X_FLAGS) -f text $(HELPER_ADOC) + help: ## Shows all build related commands of the Makefile @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index c6f6940..cbc92b8 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,9 @@ Running `make all` produces two binaries: - `zipfuse` - binary of the FUSE filesystem - `mount.zipfuse` - binary of the FUSE mount helper -The latter is needed only for mounting with `mount(8)` or `/etc/fstab`. +The latter is needed only for mounting with `mount(8)` or `/etc/fstab`. + +The documentation can be built using `make docs` (requires `asciidoc` & `w3m`). ## Installing the filesystem @@ -76,6 +78,11 @@ 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. + ## Mounting the filesystem ### Mounting with command-line or `systemd` service (recommended): @@ -152,6 +159,21 @@ xtim=SECS (numeric and in seconds; overrides filesystem mount timeout) 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). +## Unmounting the filesystem + +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). + +## Updating the filesystem version + +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. + ## Program options and configurables ``` @@ -205,13 +227,25 @@ The following signals are observed and handled by the filesystem: - `SIGUSR1` forces a garbage collection (within Go) - `SIGUSR2` dumps a diagnostic stacktrace to standard error (`stderr`) -## Security, Contributions, and License +## Performance considerations 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. + +## Security, contributions, and license + 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. diff --git a/docs/mount.zipfuse.adoc b/docs/mount.zipfuse.adoc new file mode 100644 index 0000000..4f3b630 --- /dev/null +++ b/docs/mount.zipfuse.adoc @@ -0,0 +1,227 @@ +MOUNT.ZIPFUSE(8) +================ + +NAME +---- + +mount.zipfuse - filesystem mount helper + +SYNOPSIS +-------- + +*mount.zipfuse* -h + +*mount.zipfuse* [-o key[=value],key[=value],...] + +DESCRIPTION +----------- + +`mount.zipfuse` is a FUSE filesystem mount helper for the `zipfuse(1)` +filesystem. It allows for mounting of the filesystem using `mount(8)` +and entries in the `/etc/fstab` file. It internally forks and executes +the `zipfuse(1)` filesystem binary with the appropriate set of arguments. + +DEPENDENCIES +------------ + +* `fusermount3(1)` (as usually part of a `fuse3` package) + +USAGE +----- + +`mount.zipfuse` *should not be called directly by the user.* + +It is invoked through `mount(8)` directly or parsing `/etc/fstab`. + +For general filesystem usage, refer instead to the `zipfuse(1)` manpage. + +OPTIONS +------- + +*allow_other='bool'*:: +Allow other system users to access the mounted filesystem. ++ +Default: true if root; false if not + +*fd_cache_bypass='bool'*:: +Disable file descriptor caching; open/close a new file descriptor on every +single request. ++ +Default: false + +*fd_cache_size='int'*:: +Maximum open file descriptors to retain in cache (for more performant +re-accessing). ++ +Default: 70% of `fd_limit` + +*fd_cache_ttl='duration'*:: +Time-to-live before evicting cached file descriptors (that are not in use). ++ +Default: 60s + +*fd_limit='int'*:: +Maximum total open file descriptors at any given time (must be > +`fd_cache_size`). ++ +Default: 50% of operating system's soft limit + +*flatten_zips='bool'*:: +Flatten ZIP-contained subdirectories into one directory per ZIP archive. ++ +Default: false + +*force_unicode='bool'*:: +Unicode (or fallback to synthetic generated) paths for ZIPs; disabling +garbles non-compliant ZIPs when trying to be interpreted as unicode. ++ +Default: true + +*must_crc32='bool'*:: +Force integrity verification for non-compressed ZIP archives (slower). ++ +Default: false + +*ring_buffer_size='int'*:: +Lines of the in-memory event ring-buffer (as served in the diagnostics +dashboard). ++ +Default: 500 + +*stream_pool_size='size'*:: +Buffer size for the streamed read buffer pool (multiplies with concurrency). ++ +Default: 128KiB + +*stream_threshold='size'*:: +Files larger than this are streamed in chunks, instead of fully loaded into +RAM. ++ +Default: 1MiB + +*strict_cache='bool'*:: +Do not treat ZIP files/contents as immutable (non-changing) for caching +decisions. ++ +Default: false + +*verbose='bool'*:: +Print all FUSE communication and diagnostics to standard error. ++ +Default: false + +*webserver='addr'*:: +Address for the diagnostics dashboard (e.g. `:8000`). If unset, the +webserver is disabled. ++ +Default: (empty) + +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`. + +OVERRIDE OPTIONS +---------------- + +*setuid='username|uid'*:: +Run the filesystem under another user account. ++ +Default: (none; runs as invoking user) + +*xbin='path'*:: +Override another path for the `zipfuse(1)` binary. ++ +Default: zipfuse + +*xlog='path'*:: +Override another path for the filesystem log file. ++ +Default: /var/log/zipfuse.log + +*xtim='seconds'*:: +Override another timeout value for the mount timeout. ++ +Default: 20 + +EXAMPLES +-------- + +Mount a directory of ZIP archives with default settings: + + sudo mount -t zipfuse ~/zips ~/zipfuse + +Mount with default settings and diagnostics dashboard on port 8000: + + sudo mount -t zipfuse ~/zips ~/zipfuse -o webserver=:8000 + +Mount allowing other users to access, with flattened directory structure: + + sudo mount -t zipfuse ~/zips ~/zipfuse -o allow_other,flatten_zips + +Mount using an entry in `/etc/fstab` and while under another user account: +---- +# +/home/alice/zips /home/alice/zipfuse zipfuse setuid=alice,allow_other 0 0 +---- + +UNMOUNTING +---------- + +The filesystem will observe `SIGTERM` and `SIGINT` to initiate a graceful +unmount of the filesystem, if it is not busy. You can send `SIGTERM` to the +filesystem's PID using `kill(1)`. Alternatively, of course, `fusermount3(1)` or +`umount(8)` can be used on the mountpoint, which also allows forcing an unmount +on a stuck as busy filesystem (if so required). + +EXIT STATUS +----------- + +The mount helper follows standard conventions with return codes: + +* `0` - Success +* `1` - General Failure + +It internally communicates with the `zipfuse(1)` binary to deduce if +the mount has been successful or has failed and derive from the result +its own return code, as well as propagating any error messages onward. + +SECURITY +-------- + +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. + +AUTHOR AND LICENSE +------------------ + +Copyright (C) 2025 - desertwitch (dezertwitsh@gmail.com) + +The ZipFUSE project is licensed under the MIT license. + +Please refer to the `LICENSE` document for more information. + +VERSION +------- + +This document was last changed on: *{docdate} {doctime}* + +This document was built for program version (or commit): *{version}* + +SEE ALSO +-------- + +Refer to the following manpages for further information: + +* `zipfuse(1)` +* `systemd(1)` +* `fusermount3(1)` +* `mount(8)` +* `umount(8)` +* `fstab(5)` + +Visit the ZipFUSE project website for news and documentation: + +* https://github.com/desertwitch/zipfuse[https://github.com/desertwitch/zipfuse] diff --git a/docs/zipfuse.adoc b/docs/zipfuse.adoc new file mode 100644 index 0000000..3feb1c0 --- /dev/null +++ b/docs/zipfuse.adoc @@ -0,0 +1,295 @@ +ZIPFUSE(1) +========== + +NAME +---- + +zipfuse - a filesystem for reading ZIP archives + +SYNOPSIS +-------- + +*zipfuse* -h + +*zipfuse* [flags] + +DESCRIPTION +----------- + +`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. + +DEPENDENCIES +------------ + +* `fusermount3(1)` (as usually part of a `fuse3` package) + +USAGE +----- + +The filesystem needs to be mounted either by direct invocation of the binary or +using the FUSE mount helper `mount.zipfuse(8)`, which is almost always supplied +alongside the program. This manpage covers only direct invocation of the binary, +for information on how to mount using `mount(8)` or `/etc/fstab`, refer instead +to the `mount.zipfuse(8)` manpage. + +Direct invocation of the binary will start the filesystem and mirror underlying +filesystem at `` onto `` respecting given option `[flags]`. +Any regular folders and ZIP archives present in `` will be presented as +appropriate directory structure inside `` and allow for browsing of +ZIP archives as if they were regular filesystem structures, their extraction +being handled on-the-fly and in-memory by the backing `zipfuse` filesystem. + +The filesystem generally runs in foreground mode and can be put into background +either by running inside a `screen(1)`, `tmux(1)` session or also more simply by +running with `nohup(1)` and `&`, piping output to e.g. an appropriate logfile. + +The foreground mode complements nicely with wrapping the binary into a full +fledged `systemd(1)` service unit; refer to the respective section further +below for more information on how to realize such a setup on your system. + +OPTIONS +------- + +-a, *--allow-other 'bool'*:: +Allow other system users to access the mounted filesystem. ++ +Default: true if root; false if not + +-d, *--dry-run 'bool'*:: +Do not mount; instead print all would-be inodes and paths to standard output. ++ +Default: false + +*--fd-cache-bypass 'bool'*:: +Disable file descriptor caching; open/close a new file descriptor on every +single request. ++ +Default: false + +*--fd-cache-size 'int'*:: +Maximum open file descriptors to retain in cache (for more performant +re-accessing). ++ +Default: 70% of `fd-limit` + +*--fd-cache-ttl 'duration'*:: +Time-to-live before evicting cached file descriptors (that are not in use). ++ +Default: 60s + +*--fd-limit 'int'*:: +Maximum total open file descriptors at any given time (must be > +`fd-cache-size`). ++ +Default: 50% of operating system's soft limit + +-f, *--flatten-zips 'bool'*:: +Flatten ZIP-contained subdirectories into one directory per ZIP archive. ++ +Default: false + +*--force-unicode 'bool'*:: +Unicode (or fallback to synthetic generated) paths for ZIPs; disabling +garbles non-compliant ZIPs when trying to be interpreted as unicode. ++ +Default: true + +*--must-crc32 'bool'*:: +Force integrity verification for non-compressed ZIP archives (slower). ++ +Default: false + +*--ring-buffer-size 'int'*:: +Lines of the in-memory event ring-buffer (as served in the diagnostics +dashboard). ++ +Default: 500 + +*--stream-pool-size 'size'*:: +Buffer size for the streamed read buffer pool (multiplies with concurrency). ++ +Default: 128KiB + +-s, *--stream-threshold 'size'*:: +Files larger than this are streamed in chunks, instead of fully loaded into +RAM. ++ +Default: 1MiB + +*--strict-cache 'bool'*:: +Do not treat ZIP files/contents as immutable (non-changing) for caching +decisions. ++ +Default: false + +-v, *--verbose 'bool'*:: +Print all FUSE communication and diagnostics to standard error. ++ +Default: false + +*--version*:: +Print the program version to standard output. ++ +Default: false + +-w, *--webserver 'addr'*:: +Address for the diagnostics dashboard (e.g. `:8000`). If unset, the +webserver is disabled. ++ +Default: (empty) + +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`. + +EXAMPLES +-------- + +Mount a directory of ZIP archives with default settings: + + zipfuse ~/zips ~/zipfuse + +Mount with default settings and diagnostics dashboard on port 8000: + + zipfuse ~/zips ~/zipfuse --webserver :8000 + +Mount allowing other users to access, with flattened directory structure: + + zipfuse ~/zips ~/zipfuse --allow-other --flatten-zips + +Run in background with `nohup(1)`: + + nohup zipfuse ~/zips ~/zipfuse -w :8000 > ~/zipfuse.log 2>&1 & + +UNMOUNTING +---------- + +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. If backgrounded, you can +send `SIGTERM` to the filesystem's PID using `kill(1)`. Alternatively, of +course, `fusermount3(1)` or `umount(8)` can be used on the mountpoint, which +also allows forcing an unmount on a stuck as busy filesystem (if so required). + +EXIT STATUS +----------- + +The filesystem follows standard conventions with return codes: + +* `0` - Success +* `1` - General Failure + +SIGNALS AND WEBSERVER ROUTES +---------------------------- + +The following signals are observed and handled by the filesystem: + +* `SIGTERM` or `SIGINT` (CTRL+C) gracefully unmounts the filesystem +* `SIGUSR1` forces a garbage collection (within Go) +* `SIGUSR2` dumps a diagnostic stacktrace to standard error (`stderr`) + +When enabled, the diagnostics server exposes the following routes: + +* `/` for filesystem dashboard and event ring-buffer +* `/gc` for forcing of a garbage collection (within Go) +* `/reset` for resetting the filesystem metrics at runtime +* `/set/must-crc32/` for adapting forced integrity checking +* `/set/fd-cache-bypass/` for bypassing the file descriptor cache +* `/set/stream-threshold/` for adapting of the streaming threshold + +INTEGRATION +----------- + +The `systemd(1)` framework provides an ideal basis for realizing a long-running +`zipfuse` filesystem instance as a service. If such a service unit is set up, +the filesystem can be started, monitored and stopped all through `systemd(1)` +service management with little maintenance or user interaction being required. + +Such a service unit could look as follows, although needing customization: +---- +[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.target +---- + +For users not wishing to use `systemd(1)`, or on otherwise incompatible systems, +it is recommended to refer to the `mount.zipfuse(8)` manpage on how to mount the +filesystem using `mount(8)` or `/etc/fstab` instead. Of course, the filesystem +binary itself can also be orchestrated using common shell scripting combined +with e.g. `nohup(1)` and `&`, as well as piping any output to logfile instead. + +PERFORMANCE +----------- + +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. + +SECURITY +-------- + +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. + +AUTHOR AND LICENSE +------------------ + +Copyright (C) 2025 - desertwitch (dezertwitsh@gmail.com) + +The ZipFUSE project is licensed under the MIT license. + +Please refer to the `LICENSE` document for more information. + +VERSION +------- + +This document was last changed on: *{docdate} {doctime}* + +This document was built for program version (or commit): *{version}* + +SEE ALSO +-------- + +Refer to the following manpages for further information: + +* `mount.zipfuse(8)` +* `systemd(1)` +* `fusermount3(1)` +* `mount(8)` +* `umount(8)` +* `fstab(5)` + +Visit the ZipFUSE project website for news and documentation: + +* https://github.com/desertwitch/zipfuse[https://github.com/desertwitch/zipfuse]