Configuration
PolicyFS reads its config from /etc/pfs/pfs.yaml by default.
Override the path with the PFS_CONFIG_FILE environment variable or the --config flag.
The Debian package ships an example at /etc/pfs/pfs.yaml.example.
Full example
fuse:
allow_other: true
log:
level: info
format: json
file: /var/log/pfs/pfs.log
mounts:
media:
mountpoint: /mnt/pfs/media
statfs:
reporting: mount_pooled_targets
on_error: ignore_failed
storage_paths:
- id: ssd1
path: /mnt/ssd1/media
indexed: false
- id: hdd1
path: /mnt/hdd1/media
indexed: true
storage_groups:
ssds: [ssd1]
hdds: [hdd1]
routing_rules:
- match: '**'
read_targets: [ssds, hdds]
write_targets: [ssds]
write_policy: first_found
path_preserving: true
indexer:
ignore: ['**/.DS_Store', '**/Thumbs.db']
mover:
enabled: true
jobs:
- name: archive
trigger:
type: usage
threshold_start: 80
threshold_stop: 70
allowed_window:
start: '23:00'
end: '06:00'
finish_current: true
source:
groups: [ssds]
patterns: ['library/**']
ignore: ['**/.DS_Store']
destination:
groups: [hdds]
policy: most_free
path_preserving: true
conditions:
min_age: 7d
min_size: 100MB
delete_source: true
delete_empty_dir: true
verify: false
Top-level keys
fuse
| Field | Type | Default | Description |
|---|---|---|---|
allow_other |
bool | false |
Pass the allow_other FUSE mount option, letting non-root users access the mount. Requires user_allow_other in /etc/fuse.conf. |
log
| Field | Type | Default | Description |
|---|---|---|---|
level |
string | info |
Log level: debug, info, warn, error, or off. |
format |
string | json |
Log format: json or text. |
file |
string | (empty) | If set, structured logs are also written to this file. Override with --log-file or PFS_LOG_FILE. |
The Debian package example config sets log.file to /var/log/pfs/pfs.log.
mounts
A map of mount name to mount configuration. The mount name is a short identifier used in systemd unit names and CLI commands (e.g. media).
Mount configuration
Each mount under mounts.<name> has:
mountpoint
Absolute path where the FUSE filesystem is mounted (e.g. /mnt/pfs/media).
log
Optional mount-scoped logging overrides.
| Field | Type | Default | Description |
|---|---|---|---|
level |
string | (empty) | If set, overrides top-level log.level for this mount only (empty inherits). |
Changes to mounts.<name>.log.level can be applied to a running daemon with pfs reload <mount>.
statfs
Controls how PolicyFS answers statfs for this mount (used by df -h and some SMB clients).
For a deeper explanation of the trade-offs (why mount-wide reporting is the default, and why per-path reporting can differ by directory), see Statfs reporting.
| Field | Type | Default | Description |
|---|---|---|---|
reporting |
string | mount_pooled_targets |
Reporting mode: mount_pooled_targets, path_pooled_targets. |
on_error |
string | ignore_failed |
Error policy: ignore_failed, fail_eio, fallback_effective_target, fallback_loopback. |
Changes to mounts.<name>.statfs.* require restarting the daemon.
storage_paths
List of physical storage roots. Each entry:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id |
string | yes | - | Stable identifier used in logs, database, and config references. |
path |
string | yes | - | Absolute filesystem path. |
indexed |
bool | no | false |
When true, metadata is served from the SQLite index and mutations are deferred. |
min_free_gb |
float | no | 0 |
Minimum free space in GiB. Write targets below this threshold are skipped. |
storage_groups
Map of group name to list of storage path IDs. Groups are expanded wherever storage references appear (routing rules, mover source/destination).
storage_groups:
ssds: [ssd1, ssd2]
hdds: [hdd1, hdd2, hdd3]
routing_rules
List of routing rules evaluated top-to-bottom. First match wins.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
match |
string | yes | - | Glob pattern for virtual paths. ** matches any depth. |
targets |
list | no | - | Shorthand: sets both read_targets and write_targets. |
read_targets |
list | no | - | Storage IDs or group names for reads. |
write_targets |
list | no | - | Storage IDs or group names for writes. |
write_policy |
string | no | first_found |
Target selection: first_found, most_free, or least_free. |
path_preserving |
bool | no | false |
Prefer write targets where the parent directory already exists. |
Catch-all rule required
The last rule must be match: '**'. PolicyFS rejects configs without a catch-all rule, and only one catch-all is allowed.
Changes to mounts.<name>.routing_rules can be applied to a running daemon with pfs reload <mount>.
indexer
| Field | Type | Default | Description |
|---|---|---|---|
ignore |
list | [] |
Glob patterns for files/directories to skip during indexing. |
mover
Controls file movement jobs for this mount.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Master switch for all mover jobs. |
jobs |
list | [] |
List of mover job definitions. |
delete_source is true by default
The mover deletes the source file after a successful copy. Before running pfs move for the first time, use pfs move <mount> --dry-run to see what would be moved.
Add `--force` if you want to bypass job triggers/conditions.
Mover job
Each job under mover.jobs[]:
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | - | Unique job name (used in --job flag and logs). |
description |
string | - | Optional human-readable description. |
trigger |
object | - | When the job should run. |
source |
object | - | Where candidates come from. |
destination |
object | - | Where files are moved to. |
conditions |
object | - | Filters applied to candidates. |
delete_source |
bool | true |
Delete the source file after a successful move. Use --dry-run to preview before running for the first time. |
delete_empty_dir |
bool | true |
Remove empty parent directories after moving. |
verify |
bool | false |
Re-read the destination file after copying to verify integrity. |
trigger
| Field | Type | Default | Description |
|---|---|---|---|
type |
string | - | Trigger type: usage or manual. |
threshold_start |
int | 80 |
Start moving when any source storage usage exceeds this percent. |
threshold_stop |
int | 70 |
Stop moving when source storage usage drops below this percent. |
allowed_window |
object | - | Optional time window restriction (only valid for type: usage). |
trigger.allowed_window
| Field | Type | Default | Description |
|---|---|---|---|
start |
string | - | Window start time in HH:MM format (e.g. 23:00). |
end |
string | - | Window end time in HH:MM format (e.g. 06:00). Wraps past midnight. |
finish_current |
bool | true |
If true, a file being copied when the window closes is allowed to finish. |
source
| Field | Type | Description |
|---|---|---|
paths |
list | Storage path IDs to scan. |
groups |
list | Storage group names to scan (expanded to IDs). |
patterns |
list | Glob patterns to filter candidate files (e.g. library/**). |
ignore |
list | Glob patterns to exclude (e.g. **/.DS_Store). |
include_file |
string | Optional newline-delimited list file of paths/globs to include as candidates. If the file cannot be read, the job fails. |
ignore_file |
string | Optional newline-delimited list file of paths/globs to exclude (ignore wins). If the file cannot be read, the job fails. |
At least one of paths or groups must be provided.
include_file / ignore_file semantics: entries in these files are matched against the
virtual relative path (e.g. library/movies/A.mkv - forward slashes, no leading /), the same
as patterns and ignore. Lines starting with # and blank lines are ignored. Ignore always
wins: if a path matches ignore or ignore_file, it is skipped regardless of patterns or
include_file. At least one of patterns or include_file must be provided.
destination
| Field | Type | Default | Description |
|---|---|---|---|
paths |
list | - | Storage path IDs as destinations. |
groups |
list | - | Storage group names as destinations (expanded to IDs). |
policy |
string | most_free |
Target selection: most_free, least_free, or first_found. |
skip_if_exists_any |
bool | false |
If true, skip a candidate when the destination path already exists on any destination storage (avoids duplicates; may increase disk I/O). When a file is skipped, delete_source does not apply - the source file is kept. |
path_preserving |
bool | false |
Prefer destinations where the parent directory already exists. The mover applies min_free_gb to those destinations first, then falls back to all destinations if none remain eligible. |
When a move needs a missing destination parent, it creates each missing component separately. New directories copy ownership and permissions from the corresponding source directories, retain group write and traversal, and preserve setgid semantics. Existing destination directories are not changed, and symlink components are rejected.
The mover must have permission to create directories and apply source ownership. Packaged systemd units run as root by default.
conditions
| Field | Type | Description |
|---|---|---|
min_age |
string | Minimum file age (e.g. 7d, 24h, 30m). |
min_size |
string | Minimum file size (e.g. 100MB, 1GB). |
max_size |
string | Maximum file size. |
Environment variables
| Variable | Default | Description |
|---|---|---|
PFS_CONFIG_FILE |
/etc/pfs/pfs.yaml |
Override the config file path. |
PFS_LOG_FILE |
(unset) | If set, enables structured log duplication to this file. Overrides log.file. |
PFS_STATE_DIR |
/var/lib/pfs |
Base directory for persistent state (index DB, event logs). |
PFS_RUNTIME_DIR |
/run/pfs |
Base directory for runtime data (locks, sockets). |
PFS_LOG_DISK_ACCESS |
- | Set to 1 to enable disk access logging for indexed storage. |
TZ |
system | Timezone for allowed_window evaluation. |