mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-12 02:23:45 +00:00
docs: clarify branch resolution and mount ordering
This commit is contained in:
committed by
trapexit
parent
515b0549ef
commit
cfb925e5ae
@@ -38,6 +38,32 @@ These files and xattrs are used to help `mergerfs` know whether or not
|
||||
a directory happens to be a branch or is the underlying directory
|
||||
mount point.
|
||||
|
||||
## Paths which appear after startup
|
||||
|
||||
The [`branches` resolution process](branches.md#resolution) runs before
|
||||
this option starts waiting. A missing literal path is retained as a
|
||||
branch, so a configuration such as `/mnt/hdd/disk0/foobar` can work
|
||||
when `/mnt/hdd/disk0` is mounted asynchronously and `foobar` only
|
||||
becomes visible afterward, provided the path then meets one of the
|
||||
"mounted" conditions above.
|
||||
|
||||
For each branch which is not ready, mergerfs first runs `mount` once
|
||||
with the full branch path as the target and then polls that same path
|
||||
until the timeout expires. It does not create the path, try to mount
|
||||
its parent, or rerun `mount`. In the example above, the initial
|
||||
`mount /mnt/hdd/disk0/foobar` may fail while the path is absent. If
|
||||
another service mounts `/mnt/hdd/disk0` and
|
||||
`/mnt/hdd/disk0/foobar` appears and meets one of the "mounted"
|
||||
conditions before the deadline, mergerfs detects it during polling
|
||||
and continues.
|
||||
|
||||
Globs are different. If `/mnt/hdd/disk0/*` has no matches during
|
||||
branch resolution, the literal path `/mnt/hdd/disk0/*` is retained.
|
||||
The mount wait does not expand it again after `/mnt/hdd/disk0`
|
||||
becomes available. Ensure the parent filesystem is mounted before
|
||||
mergerfs, configure a literal branch path, or update `branches`
|
||||
through the runtime API once the paths exist.
|
||||
|
||||
|
||||
**NOTE:** If on a `systemd` based system and using `fstab` it is a
|
||||
good idea to set the mount option
|
||||
|
||||
@@ -14,7 +14,7 @@ Branches currently have two options which can be set. A
|
||||
included in a policy calculation and a individual
|
||||
[minfreespace](#minfreespace) value. The values are set by
|
||||
prepending an `=` at the end of a branch designation and using commas
|
||||
as delimiters. Example: `/mnt/drive=RW,1234`
|
||||
as delimiters. Example: `/mnt/hdd/disk0=RW,1234`
|
||||
|
||||
|
||||
### branch mode
|
||||
@@ -34,6 +34,33 @@ Same purpose and syntax as the [global option](minfreespace.md) but
|
||||
specific to the branch. Defaults to the global value.
|
||||
|
||||
|
||||
### resolution
|
||||
|
||||
Each colon-delimited branch designation is resolved independently when
|
||||
the `branches` option is parsed, both at startup and when it is updated
|
||||
through the runtime API:
|
||||
|
||||
1. The path portion is expanded as a glob.
|
||||
2. If the glob matches one or more directories, each match becomes a
|
||||
branch.
|
||||
3. If the glob has no matches, mergerfs logs a notice and keeps the
|
||||
configured path unchanged as a single, literal branch.
|
||||
4. mergerfs resolves each resulting path with `realpath`. If that
|
||||
succeeds, the canonical path is stored. If it fails, typically
|
||||
because the path does not exist yet, the unchanged path is stored.
|
||||
5. A path which exists but is not a directory is logged and skipped. A
|
||||
path which does not exist is logged but retained.
|
||||
|
||||
Consequently, a literal branch such as `/mnt/hdd/disk0/foobar` may be
|
||||
configured before it exists and can become usable after
|
||||
`/mnt/hdd/disk0` is mounted. Branch resolution is not repeated in the
|
||||
background, however. If `/mnt/hdd/disk0/*` has no matches when it is
|
||||
resolved, mergerfs stores the literal path `/mnt/hdd/disk0/*`;
|
||||
directories which match it later are not discovered automatically.
|
||||
Update `branches` through the runtime API or restart mergerfs to
|
||||
resolve the glob again.
|
||||
|
||||
|
||||
### globbing
|
||||
|
||||
To make it easier to include multiple branches mergerfs supports
|
||||
@@ -42,18 +69,18 @@ MUST be escaped when using via the shell else the shell itself will
|
||||
apply the glob itself.**
|
||||
|
||||
```
|
||||
# mergerfs /mnt/hdd\*:/mnt/ssd /media
|
||||
# mergerfs /mnt/hdd/\*:/mnt/ssd/\* /media
|
||||
```
|
||||
|
||||
The above line will use all directories in /mnt prefixed with **hdd**
|
||||
as well as **ssd**.
|
||||
The above line will use all directories within `/mnt/hdd` and
|
||||
`/mnt/ssd`.
|
||||
|
||||
To have the pool mounted at boot or otherwise accessible from related
|
||||
tools use `/etc/fstab`.
|
||||
|
||||
```
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||
/mnt/hdd*:/mnt/ssd /media mergerfs minfreespace=16G 0 0
|
||||
/mnt/hdd/*:/mnt/ssd/* /media mergerfs minfreespace=16G 0 0
|
||||
```
|
||||
|
||||
**NOTE:** The globbing is done at mount or when updated using the
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
If using path preservation and a `rename` fails with `EXDEV`:
|
||||
|
||||
1. Move file from `/branch/a/b/c` to `/branch/.mergerfs_rename_exdev/a/b/c`.
|
||||
1. Move file from `/mnt/hdd/disk0/a/b/c` to
|
||||
`/mnt/hdd/disk0/.mergerfs_rename_exdev/a/b/c`.
|
||||
2. symlink the rename's `newpath` to the moved file.
|
||||
|
||||
The `target` value is determined by the value of `rename-exdev`.
|
||||
|
||||
@@ -81,7 +81,7 @@ where it was created.
|
||||
$ sudo touch /mnt/mergerfs/new-file
|
||||
$ sudo getfattr -n user.mergerfs.allpaths /mnt/mergerfs/new-file
|
||||
# file: mnt/mergerfs/new-file
|
||||
user.mergerfs.allpaths="/mnt/hdd/drive1/new-file"
|
||||
user.mergerfs.allpaths="/mnt/hdd/disk0/new-file"
|
||||
```
|
||||
|
||||
If the location of the file is where it should be according to the
|
||||
|
||||
@@ -105,7 +105,7 @@ The way in which mergerfs behaves is controlled by the
|
||||
|
||||
```
|
||||
A + B = C
|
||||
/disk1 /disk2 /merged
|
||||
/mnt/hdd/disk0 /mnt/hdd/disk1 /media
|
||||
| | |
|
||||
+-- /dir1 +-- /dir1 +-- /dir1
|
||||
| | | | | |
|
||||
|
||||
@@ -92,15 +92,15 @@ If you always want the directory information from the one with the
|
||||
most recent mtime then use the `newest` policy for `getattr`.
|
||||
|
||||
|
||||
### 'mv /mnt/pool/foo /mnt/disk1/foo' removes 'foo'
|
||||
### 'mv /mnt/pool/foo /mnt/hdd/disk0/foo' removes 'foo'
|
||||
|
||||
This is not a bug.
|
||||
|
||||
Run in verbose mode to better understand what's happening:
|
||||
|
||||
```
|
||||
$ mv -v /mnt/pool/foo /mnt/disk1/foo
|
||||
copied '/mnt/pool/foo' -> '/mnt/disk1/foo'
|
||||
$ mv -v /mnt/pool/foo /mnt/hdd/disk0/foo
|
||||
copied '/mnt/pool/foo' -> '/mnt/hdd/disk0/foo'
|
||||
removed '/mnt/pool/foo'
|
||||
$ ls /mnt/pool/foo
|
||||
ls: cannot access '/mnt/pool/foo': No such file or directory
|
||||
|
||||
@@ -23,9 +23,9 @@ and offers better performance. As of 2020, the author of mhddfs has
|
||||
Below is an example of mhddfs and mergerfs setup to work similarly.
|
||||
|
||||
```
|
||||
mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool
|
||||
mhddfs -o mlimit=4G,allow_other /mnt/hdd/disk0,/mnt/hdd/disk1 /mnt/pool
|
||||
|
||||
mergerfs -o minfreespace=4G,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool
|
||||
mergerfs -o minfreespace=4G,category.create=ff /mnt/hdd/disk0:/mnt/hdd/disk1 /mnt/pool
|
||||
```
|
||||
|
||||
|
||||
|
||||
+62
-14
@@ -66,24 +66,69 @@ branches](config/branches.md#branch-setup).
|
||||
|
||||
```
|
||||
# Two positional options: `:` colon separated branches and mountpoint
|
||||
mergerfs -o opt[,opt...] /branch0:/branch1 /mountpoint
|
||||
mergerfs -o opt[,opt...] /mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media
|
||||
# or list of positional options where the last is the mountpoint
|
||||
mergerfs -o opt[,opt...] /branch0 /branch1 /mountpoint
|
||||
mergerfs -o opt[,opt...] /mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2 /media
|
||||
# or set mountpoint via -o mountpoint
|
||||
mergerfs -o mountpoint=/mountpoint /branch0 /branch1
|
||||
mergerfs -o mountpoint=/media /mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
|
||||
```
|
||||
|
||||
```
|
||||
mergerfs -o cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false /mnt/hdd0:/mnt/hdd1 /media
|
||||
mergerfs -o cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false /mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media
|
||||
```
|
||||
|
||||
|
||||
### /etc/fstab
|
||||
|
||||
```
|
||||
/mnt/hdd0:/mnt/hdd1 /media mergerfs cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false 0 0
|
||||
/mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media mergerfs cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false 0 0
|
||||
```
|
||||
|
||||
### systemd mount dependencies
|
||||
|
||||
On a `systemd` based system, no custom service or setup script is
|
||||
needed when the branch filesystems and mergerfs are all configured in
|
||||
`/etc/fstab`. Add
|
||||
[`x-systemd.requires-mounts-for=`](https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html#x-systemd.wants-mounts-for=)
|
||||
once for each branch mount target in the mergerfs entry:
|
||||
|
||||
```fstab
|
||||
LABEL=disk0 /mnt/hdd/disk0 ext4 defaults 0 2
|
||||
LABEL=disk1 /mnt/hdd/disk1 ext4 defaults 0 2
|
||||
LABEL=disk2 /mnt/hdd/disk2 ext4 defaults 0 2
|
||||
/mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media mergerfs cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false,x-systemd.requires-mounts-for=/mnt/hdd/disk0,x-systemd.requires-mounts-for=/mnt/hdd/disk1,x-systemd.requires-mounts-for=/mnt/hdd/disk2 0 0
|
||||
```
|
||||
|
||||
Replace the example labels and filesystem types with those used by the
|
||||
system. `systemd` creates `Requires=` and `After=` dependencies from
|
||||
the mergerfs mount to `mnt-hdd-disk0.mount`,
|
||||
`mnt-hdd-disk1.mount`, and `mnt-hdd-disk2.mount`. It starts those
|
||||
mounts first and does not start the mergerfs mount if a required mount
|
||||
fails. This checks the mount units, not merely whether the
|
||||
`/mnt/hdd/diskN` directories exist.
|
||||
|
||||
After editing `/etc/fstab`, reload the generated units:
|
||||
|
||||
```shell
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
The dependencies apply the next time `/media` is mounted and on
|
||||
subsequent boots.
|
||||
|
||||
When using a service such as the
|
||||
[`mergerfs-media.service`](#systemd-simple) example below instead of an
|
||||
`/etc/fstab` mergerfs entry, add the equivalent directive to its
|
||||
`[Unit]` section:
|
||||
|
||||
```systemd
|
||||
RequiresMountsFor=/mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
|
||||
```
|
||||
|
||||
`RequiresMountsFor=` adds both the requirement and ordering
|
||||
dependencies, so separate `Requires=` and `After=` entries for these
|
||||
mounts are unnecessary.
|
||||
|
||||
### /etc/fstab w/ config file
|
||||
|
||||
For more complex setups it can be useful to separate out the config.
|
||||
@@ -114,10 +159,9 @@ resolve the symlinks and use the real path.
|
||||
`ls -lh /etc/mergerfs/branches/media/*`
|
||||
|
||||
```text
|
||||
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd00 -> /mnt/hdd/hdd00
|
||||
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd01 -> /mnt/hdd/hdd01
|
||||
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd02 -> /mnt/hdd/hdd02
|
||||
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd03 -> /mnt/hdd/hdd03
|
||||
lrwxrwxrwx 1 root root 14 Aug 4 2023 disk0 -> /mnt/hdd/disk0
|
||||
lrwxrwxrwx 1 root root 14 Aug 4 2023 disk1 -> /mnt/hdd/disk1
|
||||
lrwxrwxrwx 1 root root 14 Aug 4 2023 disk2 -> /mnt/hdd/disk2
|
||||
```
|
||||
|
||||
### systemd (simple)
|
||||
@@ -127,6 +171,7 @@ lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd03 -> /mnt/hdd/hdd03
|
||||
```systemd title="mergerfs-media.service" linenums="1"
|
||||
[Unit]
|
||||
Description=mergerfs /media service
|
||||
RequiresMountsFor=/mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
|
||||
After=local-fs.target network.target
|
||||
|
||||
[Service]
|
||||
@@ -138,8 +183,9 @@ ExecStart=/usr/bin/mergerfs \
|
||||
-o category.create=pfrd \
|
||||
-o func.getattr=newest \
|
||||
-o dropcacheonclose=false \
|
||||
/mnt/hdd0 \
|
||||
/mnt/hdd1 \
|
||||
/mnt/hdd/disk0 \
|
||||
/mnt/hdd/disk1 \
|
||||
/mnt/hdd/disk2 \
|
||||
/media
|
||||
ExecStop=/usr/bin/umount /media
|
||||
# Or if you need fusermount
|
||||
@@ -193,8 +239,9 @@ WantedBy=default.target
|
||||
```systemd title="mergerfs-media.service" linenums="1"
|
||||
[Unit]
|
||||
Description=mergerfs /media service
|
||||
RequiresMountsFor=/mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
|
||||
Requires=setup-for-mergerfs.service
|
||||
After=local-fs.target network.target prepare-for-mergerfs.service
|
||||
After=local-fs.target network.target setup-for-mergerfs.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -205,8 +252,9 @@ ExecStart=/usr/bin/mergerfs \
|
||||
-o category.create=pfrd \
|
||||
-o func.getattr=newest \
|
||||
-o dropcacheonclose=false \
|
||||
/mnt/hdd0 \
|
||||
/mnt/hdd1 \
|
||||
/mnt/hdd/disk0 \
|
||||
/mnt/hdd/disk1 \
|
||||
/mnt/hdd/disk2 \
|
||||
/media
|
||||
ExecStop=/usr/bin/umount /media
|
||||
# Or if you need fusermount
|
||||
|
||||
@@ -110,7 +110,7 @@ situation and continue to be able to access the xattr interface.
|
||||
|
||||
```
|
||||
[trapexit:/mnt/mergerfs] $ getfattr -d .mergerfs
|
||||
user.mergerfs.branches="/mnt/a=RW:/mnt/b=RW"
|
||||
user.mergerfs.branches="/mnt/hdd/disk0=RW:/mnt/hdd/disk1=RW"
|
||||
user.mergerfs.minfreespace="4294967295"
|
||||
user.mergerfs.moveonenospc="false"
|
||||
...
|
||||
@@ -122,9 +122,9 @@ user.mergerfs.category.search="mfs"
|
||||
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.create .mergerfs
|
||||
user.mergerfs.category.search="prfd"
|
||||
|
||||
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.branches -v '+</mnt/c=RO' .mergerfs
|
||||
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.branches -v '+</mnt/hdd/disk2=RO' .mergerfs
|
||||
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.branches .mergerfs
|
||||
user.mergerfs.branches="/mnt/c=RO:/mnt/a=RW:/mnt/b=RW"
|
||||
user.mergerfs.branches="/mnt/hdd/disk2=RO:/mnt/hdd/disk0=RW:/mnt/hdd/disk1=RW"
|
||||
```
|
||||
|
||||
### Commands
|
||||
@@ -172,7 +172,7 @@ to set them will result in an error.
|
||||
|
||||
```
|
||||
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.fullpath test
|
||||
user.mergerfs.fullpath="/mnt/a/test"
|
||||
user.mergerfs.fullpath="/mnt/hdd/disk0/test"
|
||||
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.allpaths .
|
||||
user.mergerfs.allpaths="/mnt/a\000/mnt/b\000/mnt/c"
|
||||
user.mergerfs.allpaths="/mnt/hdd/disk0\000/mnt/hdd/disk1\000/mnt/hdd/disk2"
|
||||
```
|
||||
|
||||
@@ -105,7 +105,8 @@ to `.bashrc`.
|
||||
|
||||
### Docker and Podman usage
|
||||
|
||||
Assume `/mnt/fs0` and `/mnt/fs1` are pooled with mergerfs at `/media`.
|
||||
Assume `/mnt/hdd/disk0` and `/mnt/hdd/disk1` are pooled with mergerfs
|
||||
at `/media`.
|
||||
|
||||
All mergerfs branch paths _must_ be bind mounted into the container at
|
||||
the same path as found on the host so the preload library can see
|
||||
@@ -134,8 +135,8 @@ docker run \
|
||||
-e LD_PRELOAD=/usr/lib/mergerfs/preload.so \
|
||||
-v /usr/lib/mergerfs/preload.so:/usr/lib/mergerfs/preload.so:ro \
|
||||
-v /media:/media \
|
||||
-v /mnt/fs0:/mnt/fs0 \
|
||||
-v /mnt/fs1:/mnt/fs1 \
|
||||
-v /mnt/hdd/disk0:/mnt/hdd/disk0 \
|
||||
-v /mnt/hdd/disk1:/mnt/hdd/disk1 \
|
||||
ubuntu:latest \
|
||||
bash
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user