]> git.hungrycats.org Git - bees/commitdiff
docs: add a configuration setup guide and make the filter prerequisites visible
authorZygo Blaxell <bees@furryterror.org>
Sun, 6 Sep 2026 01:49:42 +0000 (21:49 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sun, 6 Sep 2026 01:49:42 +0000 (21:49 -0400)
A first-time user's experience with the scratch build found several
things the reference did not say, or said only where nobody would look:

 - A configuration file should hold only the keys being changed.  Copying
   --show-builtin-config output into it produces duplicate-key errors as
   soon as a section is added, and [config] is optional rather than a
   required header.

 - A [filter.NAME] section does nothing until a scan.extent.*.filter
   chain names it (without the "filter." prefix), and no chain is
   evaluated unless method = 1, which --scan-mode 4 does not set.

 - Backslashes in name-pattern are consumed by value escape processing,
   so a regexp \. has to be written \\. -- the regexp example in the
   format reference itself had this wrong.

 - hash.NAME.priority is an ordering for future multi-domain lookup and
   csum-tree support, not a weight or a hardware capability probe.

 - --show-config takes the filesystem root, and why.

Add a "Writing a configuration file" section with a minimal file, the
multi-line --option form, and worked examples for excluding by
extension, excluding a subvolume, and switching hash function, and add
callouts at the filter and method keys.  Point at the filter_* event
counters and [log.filter] for confirming a rule matches.

Assisted-by: Claude-Code:claude-fable-5-1
docs/config-file.md
docs/config-format.md
docs/options.md
src/bees-config-v2.cc

index 45c6ebc364cbe75a0bb38153d31be4374c0fee34..b84f6f09914132ee1c3c00b8f9eeb4a1f1716f44 100644 (file)
@@ -4,6 +4,138 @@ This page describes the bees configuration options.  For the location
 and syntax of configuration, see [bees configuration format
 reference](config-format.md).
 
+## Writing a configuration file
+
+A configuration file only needs to contain the keys you want to change.
+Every key you leave out takes its built-in default, so the typical file is
+short.  Start from an empty file, not from a copy of the built-in
+configuration:  `--show-builtin-config` output is a reference to read, and
+pasting it into a file gives you hundreds of keys to keep in sync with
+future defaults for no benefit.  It also makes a common mistake easy:  a
+key that appears twice in one file is a **duplicate key** error, and a copied
+file with your own `[filter.videos]` added at the end has every `[filter.*]`
+key twice.
+
+A minimal global configuration file (`/etc/bees/bees.conf`) looks like this:
+
+```ini
+[config]
+    version = CURRENT
+
+[state.hash]
+    size = 1G
+```
+
+The `[config]` section is optional.  Leave it out to float to each new
+release's defaults, or set `version` to pin them:  `CURRENT` for this
+build's schema, `STABLE` for the last released one, or a release alias such
+as `v0.11` to freeze the defaults that release shipped.  An empty `[config]`
+section is legal and does nothing.  See [`version`](#options) below.
+
+Sections are named by dotted key prefix, so these two spellings are the same
+key and you can use whichever reads better:
+
+```ini
+[state.hash]
+    size = 1G
+
+state.hash.size = 1G
+```
+
+The same syntax works on the command line.  `--option` (`-o`) takes any
+text the configuration file would accept, including section headers and
+multiple lines, so a whole rule can go in one shell-quoted argument:
+
+```sh
+bees -o '[filter.videos]
+    name-pattern = [.](mp4|mkv|webm)$
+    match-action = REJECT' /mnt/fs
+```
+
+Keys given by `--option` and `--config` files are all treated as one file,
+so the same duplicate-key rule applies across them.
+
+Check what bees will actually use with `--show-config ROOT_PATH`, where
+`ROOT_PATH` is the filesystem you will run bees on:  it merges the built-in
+defaults, the global and local files, and the command line, and prints the
+result.  It needs the filesystem because some values depend on it:  the
+`${ROOT}`, `${UUID}`, `${LABEL}` and `${FS_BYTES}` interpolations, the
+choice of local configuration file, and limits checked against the
+filesystem block size.
+
+### Example: exclude files by extension
+
+Define a rule, then reference it from the extent scanner's filter chain.
+**A rule that is not referenced by a chain is never evaluated**; defining
+`[filter.videos]` on its own does nothing.
+
+```ini
+[filter.videos]
+    name-pattern = [.](mp4|mkv|webm)$
+    match-action = REJECT
+
+[scan.extent.*]
+    method = 1
+    filter = common.* videos ACCEPT
+```
+
+The chain lists rule names without the `filter.` prefix:  `videos` here is
+`[filter.videos]`, and `common.*` is every `[filter.common.NAME]` rule.
+The `ACCEPT` at the end accepts whatever no rule rejected.  The pattern is
+a regular expression by default (`pattern-type = regexp`); `[.]` is used
+instead of `\.` because configuration values process backslash escapes
+before the pattern sees them, so `\.` would need to be written `\\.`.
+See [Value escape sequences](config-format.md#value-escape-sequences).
+
+> **Filters need `method = 1`.**  Filter chains are only evaluated by the
+> `scan_next` extent scanner.  With the default `method = 0` the chain is
+> parsed and validated but never run, so every extent is accepted and
+> nothing in the `filter_*` [event counters](event-counters.md#filter)
+> moves.  The legacy `--scan-mode 4` option selects extent scanning but does
+> not set `method`, so set it in the configuration file.  The default is
+> expected to change to `1` in a future release.
+
+### Example: exclude a subvolume
+
+`name-scope = subvol` matches the path of the subvolume containing each
+reference, from the filesystem root:
+
+```ini
+[filter.no-backups]
+    name-scope = subvol
+    name-pattern = ^/backups(/|$)
+    match-action = REJECT
+
+[scan.extent.*]
+    method = 1
+    filter = common.* no-backups ACCEPT
+```
+
+### Example: use a different hash function
+
+Add a hash domain with a higher `priority` than the built-in `[hash.crc64]`
+(priority 100).  The name after `hash.` is a label of your choosing:
+
+```ini
+[hash.fast]
+    function = xxhash3
+    priority = 200
+```
+
+Changing the function makes an existing `beeshash.dat` useless until it is
+repopulated; see [hash domain notes](#hash-domain-notes).
+
+### Checking that a filter does what you meant
+
+The `filter_*` [event counters](event-counters.md#filter) in the stats
+output count every verdict by role and by the rule that decided it:
+`filter_dst_reject_videos` going up means the `videos` rule above is
+rejecting extents.  If only `filter_*_accept_ACCEPT` moves, no rule is
+matching — check the pattern, the chain spec, and `method`.  For
+per-extent detail, set `[log.filter] level = 7` (one line per
+evaluation) or `8` (condition-by-condition trace); see
+[log categories](#categories).
+
 ## [config] section
 
 The `[config]` section defines where bees looks for configuration files and how defaults are versioned.  Configuration is layered in order of increasing precedence:
@@ -958,11 +1090,28 @@ exactly like `[scan.extent.*]` and `[filter.*]`.
   implementation and rejected with a clear message.
 
 * **`priority`**
-  Lookup priority.  The highest-priority insert-enabled domain becomes the
-  global hash function; ties are broken by config-file order.  `0` disables
-  lookup for the domain.
+  Lookup priority, a non-negative integer.  Domains are consulted in
+  descending priority order.  The highest-priority insert-enabled domain
+  becomes the global hash function; ties are broken by config-file order.
+  `0` disables lookup for the domain.
   Default: `100`.
 
+  The number is an ordering, not a weight or a capability check:  bees does
+  not probe the host for hardware support, and `200` is not "twice as
+  preferred" as `100`.  Any value above the built-in `[hash.crc64]` domain's
+  `100` selects your domain instead.
+
+  On this release only the top-priority domain is used, so `priority`
+  amounts to a selector.  It is defined as an ordering because of where the
+  design is going:  a lookup will try the highest-priority domain first and,
+  on a miss, fall back to the next.  That allows two things a single
+  function cannot.  One is reading hashes from the btrfs csum tree, which
+  needs a different function for compressed data than for uncompressed
+  data.  The other is migrating from one function to another without
+  discarding the hash table:  a new domain at higher priority takes over
+  inserts while the old one, at lower priority, keeps serving lookups for
+  entries it wrote until they are evicted.
+
 * **`step`**
   Distance in bytes between the start of one hash window and the next.
   Must equal the btrfs checksum block size, `4096`.
@@ -1200,14 +1349,23 @@ The special section `[scan.extent.*]` sets default values inherited by all named
   Maximum physical extent size for this tier.  Extents larger than this are skipped by the scanner.  Accepts [size values](config-format.md#size-values); `max` means no upper bound.  Default: `max`.
 
 * **`filter`**
-  Space-separated filter chain spec applied to each extent in this tier before deduplication.  The chain is evaluated left to right; tokens are filter section names or the terminal keywords `ACCEPT` and `REJECT`.  The default wildcard value `common.* ACCEPT` runs the common rules and accepts anything that passes through them.  See [Filter chain ordering](#filter-chain-ordering).
+  Space-separated filter chain spec applied to each extent in this tier before deduplication.  The chain is evaluated left to right; tokens are filter section names or the terminal keywords `ACCEPT` and `REJECT`.  Section names are given without the `filter.` prefix, so `common.*` names every `[filter.common.NAME]` section and `videos` names `[filter.videos]`.  The default wildcard value `common.* ACCEPT` runs the common rules and accepts anything that passes through them.  See [Filter chain ordering](#filter-chain-ordering).
   Default (from wildcard): `common.* ACCEPT`.
 
+  > **Only evaluated when `method = 1`.**  With `method = 0` the chain is
+  > parsed and validated but never run.  See `method` below.
+
 * **`method`**
-  Temporary selector for extent-scan implementation.  Default: `0`.
+  Selector for the extent-scan implementation.  Default: `0`.
   Inherited from `[scan.extent.*]` if not set per tier.
-  `0` selects the production `scan_one_extent` path.
-  `1` selects the temporary `scan_next_extent` hook for integration tests.
+  `0` selects the `scan_one_extent` path, which does not evaluate filter
+  chains.
+  `1` selects the `scan_next_extent` path, which is the only one that
+  evaluates the tier's `filter` chain.  **Set `method = 1` whenever you
+  configure filters**; without it, filter rules are silently inert.  The
+  legacy `--scan-mode 4` option does not set `method`.  The default is
+  expected to change to `1` in a future release, at which point this key
+  becomes a compatibility fallback.
 
 * **`first-transid`**
   Where this scanner starts **the first time the tier is created** — that
@@ -1294,6 +1452,17 @@ The `[filter.*]` sections define filter rules that control which extents and ref
 
 Each named filter rule (`[filter.NAME]`) corresponds to one evaluation step in a filter chain.  Rules are combined into chains by the scanner configuration.  Within a chain, rules are evaluated in order; each rule has a **match action** (applied when all conditions match) and a **no-match action** (applied otherwise).
 
+> **Two things a rule needs before it does anything.**  First, a chain has
+> to reference it:  a `[filter.NAME]` section that no
+> [`scan.extent.*.filter`](#wildcard-defaults-scanextent) chain names is
+> parsed and validated but never evaluated.  Second, the extent scanner has
+> to be running with `method = 1`; the default `method = 0` does not
+> evaluate filter chains at all.  See
+> [Writing a configuration file](#writing-a-configuration-file) for a
+> complete example, and the `filter_*`
+> [event counters](event-counters.md#filter) to confirm a rule is deciding
+> verdicts.
+
 ### Wildcard defaults: [filter.*]
 
 The special section `[filter.*]` sets default values inherited by all named filter sections.  A key defined in `[filter.*]` applies to every `[filter.NAME]` section that does not define its own value for that key.
index 6fdb14e2eba2acaca4dd12cb2dc1dd97422d25b9..6c2649fa9854cded8cafe9b32b65bcc1ff5e3ef2 100644 (file)
@@ -294,7 +294,11 @@ The `filter.*.name-pattern` option uses one of two pattern syntaxes selected by
 * The selected path always begins with `/`, regardless of `name-scope`.
 * The match is not implicitly anchored; it may match anywhere within the selected path.
 * Use `^` and `$` to anchor the match to the beginning or end of the selected path.
-* Example: `\.c$` matches names ending in `.c`.  `.*\.c$` also works, but the leading `.*` is unnecessary.
+* Example: `[.]c$` matches names ending in `.c`.  `.*[.]c$` also works, but the leading `.*` is unnecessary.
+* Backslashes are consumed by [value escape processing](#value-escape-sequences)
+  before the pattern is compiled, so a regexp backslash must be doubled:
+  write `\\.c$` to get the regexp `\.c$`.  A bracket class such as `[.]`
+  avoids the problem.
 
 The `LABEL` string may be empty.  Consider this case when interpolating
 `${LABEL}` into strings.
index 133b7c714285647d9b7485c33fc673c8d0421dfe..56b8f24bd6604c763ae4b7bead2f2fa811816b4b 100644 (file)
@@ -134,9 +134,17 @@ space used--until the read-only snapshots are deleted.
 * `--show-config ROOT_PATH`
 
   Load configuration for `ROOT_PATH`, write the merged configuration to
-  standard output, and exit without running deduplication.  `ROOT_PATH` must
-  be the root of a btrfs filesystem tree (subvol id 5), the same as a normal
-  bees run.
+  standard output, and exit without running deduplication.  `ROOT_PATH` is
+  the filesystem bees would run on, not a configuration file:  it must be an
+  existing directory that is the root of a mounted btrfs filesystem (subvol
+  id 5), and it must be readable and searchable by the user running the
+  command, the same as a normal bees run.
+
+  The filesystem is needed because the merged configuration depends on it:
+  the `${ROOT}`, `${UUID}`, `${LABEL}` and `${FS_BYTES}` interpolations
+  and the local configuration filename are derived from it, and some limits
+  are validated against its block size.  If `ROOT_PATH` does not qualify,
+  bees reports which requirement failed and exits without output.
 
   The output is a self-documenting INI snapshot: each key is written with
   the highest-priority non-blank documentation comment found anywhere in
index 7c8521c91f6491b8e8466adbe1a10d211d4e32e8..dd90c317e9b49556d4bf3fe9475139d4b6fa3ac6 100644 (file)
@@ -303,7 +303,9 @@ static const char bees_config_v2[] = R"--v2-config--(
         #   current = current filesystem transid; skip all existing data
         first-transid = min
 
-        # Filtering rules to apply in each tier.
+        # Filter chain to apply in each tier: filter section names
+        # without the "filter." prefix, ending in ACCEPT or REJECT.
+        # Only evaluated when method = 1.
         filter = common.* ACCEPT
 
 # The [filter.*] section sets default parameters inherited by all named filter