VonixGuardian Vonix Mods v2.3.2 Search GitHub
VonixGuardian/Reference/Configuration

Configuration#

VonixGuardian reads a single JSON file, generated on first boot at config/vonixguardian/config.json. Edit it, then run /vg reload to apply everything that can be applied in-process.

<server root>/
├── config/vonixguardian/
│   ├── config.json          ← this file
│   └── worlds/              ← optional per-world overrides
├── blacklist.txt            ← optional log-suppression rules
└── vonixguardian.db         ← SQLite backend (default)

The file is lenient on read and strict on write. Whole-line // comments and trailing # … comments are stripped before parsing, and trailing commas are tolerated. Anything the mod itself writes is pretty-printed, comment-free, and saved atomically through a .tmp file plus rename.

Validation is all-or-nothing

On load, every field is validated. If any one value is out of range the whole config is rejected and the server keeps the previous values:

GuardianConfig validation failed (2 problems): - lookup.defaultPageSize: must be in [1,50] (got 0) - theme: must be one of [aqua, blue, gold, green, purple, red, white] (got teal)

Default file#

This is what a fresh install writes, with every value at its shipped default.

{
  "database": {
    "type": "sqlite",
    "file": "vonixguardian.db",
    "jdbcUrl": null,
    "user": null,
    "password": null,
    "migrationTarget": null,
    "hikari": {
      "maxPoolSize": 10,
      "connectionTimeoutMs": 30000,
      "maxLifetimeMs": 1800000,
      "leakDetectionMs": 0
    }
  },
  "queue": {
    "maxSize": 50000,
    "flushIntervalMs": 5000,
    "batchSize": 1000
  },
  "logFile": {
    "enabled": true,
    "directory": "logs/vonixguardian",
    "gzipRotated": true,
    "retentionDays": 30,
    "forceSyncOnFlush": false
  },
  "actions": {
    "logBlocks": true,
    "logContainers": true,
    "logItems": true,
    "logEntities": true,
    "logExplosions": true,
    "logChat": true,
    "logCommands": true,
    "logSessions": true,
    "logSigns": true,
    "logInteractions": true,
    "logWorldEvents": true,
    "worldBlacklist": [],
    "blockBlacklist": ["minecraft:air"],
    "sourceBlacklist": [],
    "itemBlacklist": [],
    "entityBlacklist": [],
    "entityBlockChangeCoalesceWindowMs": 500,
    "entityBlockChangeMaxTracked": 8192,
    "entityChangeAllowlist": [],
    "entityChangeLogAllEntities": false,
    "logNaturalBreaks": true,
    "logTreeGrowth": true,
    "logMushroomGrowth": true,
    "logVineGrowth": true,
    "logSculkSpread": true,
    "logPortals": true,
    "logWaterFlow": false,
    "logLavaFlow": false,
    "logFireExtinguish": true,
    "logCampfireStart": true,
    "logHopperMetaFilter": false,
    "logDuplicateSuppression": true,
    "logCancelledChat": false,
    "mixinHotEvents": true
  },
  "permissions": {
    "useLuckPerms": true,
    "defaultOpLevel": 3,
    "perNodeOpLevels": {}
  },
  "lookup": {
    "defaultPageSize": 7,
    "maxRadius": 10000,
    "maxResultRows": 100000,
    "maxConcurrent": 4
  },
  "privacy": {
    "hashIps": false,
    "salt": "vonix-guardian-default-salt-CHANGE-ME"
  },
  "purge": {
    "minAgeSecondsConsole": 86400,
    "minAgeSecondsInGame": 2592000,
    "autoPurgeSeconds": 0,
    "autoPurgeTime": "03:30"
  },
  "storage": {
    "persistNbt": true
  },
  "rollback": {
    "explosionSupplementalReach": 16,
    "mutationBatchSize": 1000,
    "requireConfirm": false
  },
  "theme": "aqua",
  "language": "en_us"
}

Missing keys are filled from these defaults on boot, so an older config file keeps working after an upgrade.

database#

Selects the storage backend.

Key Type Default Valid
type string sqlite sqlite, mysql, postgresql Backend selector.
file string vonixguardian.db non-blank when type is sqlite SQLite file, relative to the server root.
jdbcUrl string null non-blank for MySQL/PostgreSQL Full JDBC URL.
user string null — Database user.
password string null — Database password.
migrationTarget object null see below Destination descriptor for /vg migrate-db.
hikari object see below see below Connection-pool tuning.

SQLite needs only file. The JDBC URL is derived as jdbc:sqlite:<file>; providing an explicit jdbcUrl takes precedence. The path is resolved against the server's working directory.

MySQL / MariaDB and PostgreSQL need jdbcUrl, user, and password:

"database": {
  "type": "mysql",
  "jdbcUrl": "jdbc:mysql://127.0.0.1:3306/vonixguardian",
  "user": "guardian",
  "password": "CHANGE-ME",
  "hikari": { "maxPoolSize": 20 }
}

Validate type before restarting: a typo is rejected at load, and a bad jdbcUrl surfaces as an UNHEALTHY storage state in /vg status.

Do not commit real credentials

config.json holds the database password in plain text when a remote backend is used. Restrict file permissions on the server and keep the file out of version control. VonixGuardian never logs the password value.

database.hikari#

Pool tuning. Defaults match HikariCP's own behaviour except for the pool size.

Key Type Default Valid Meaning
maxPoolSize int 10 1–256 Maximum pooled connections.
connectionTimeoutMs long 30000 ≥ 250 How long a borrower waits before failing.
maxLifetimeMs long 1800000 0 or ≥ 30000 Connection recycle age. 0 disables.
leakDetectionMs long 0 0 or ≥ 2000 Leak-warning threshold. 0 disables.

Raise maxPoolSize for a busy server on MySQL or PostgreSQL. Leave it at 10 for SQLite — SQLite serialises writes regardless of pool size, so a larger pool adds contention without throughput.

database.migrationTarget#

Used only by /vg migrate-db. It describes the destination backend and is never hot-swapped — the running server stays on its active backend until an operator runs the migration.

"migrationTarget": {
  "type": "postgresql",
  "file": null,
  "jdbcUrl": "jdbc:postgresql://127.0.0.1:5432/vonixguardian",
  "user": "guardian",
  "password": "CHANGE-ME"
}
Key Required for
type Always. Must match the command's target argument.
file type: sqlite.
jdbcUrl type: mysql / postgresql.
user type: mysql / postgresql.
password type: mysql / postgresql. Must be non-null (may be empty).

queue#

The async writer queue. Events are admitted on the producing thread and written in batches by a background writer.

Key Type Default Valid Meaning
maxSize int 50000 > 0 Admission cap before events are dropped with a WARN.
flushIntervalMs long 5000 > 0 Maximum age of a queued event before it is forced out.
batchSize int 1000 > 0, ≤ maxSize Rows per INSERT batch.

When the queue is full, new events are dropped and counted. /vg status reports dropped, and the durable quarantine outbox retains what it can for replay.

Sizing. A batch of 1,000 rows is a good default for SQLite and fine for MySQL. Raising batchSize improves throughput on a remote database but widens the window of data at risk in a crash. Raising maxSize absorbs longer writer stalls at the cost of memory — 50,000 events is roughly a few megabytes.

queue changes require a restart

maxSize, flushIntervalMs, and batchSize are restart-only. /vg reload detects the change and reports queue under Requires restart, but the running server keeps its original values.

logFile#

An independent JSON-Lines audit trail written alongside the database. Useful as a tamper-evident side channel and for ingesting into an external log pipeline.

Key Type Default Valid Meaning
enabled bool true — Write the rolling file. Hot-swappable.
directory string logs/vonixguardian non-blank when enabled Directory for daily files.
gzipRotated bool true — Gzip a rotated day's file.
retentionDays int 30 ≥ 0 (0 = keep forever) How long rotated files are kept.
forceSyncOnFlush bool false — FileChannel.force(false) on every flush.

Files are named audit-YYYY-MM-DD.log.jsonl. Rotation is lazy — it happens on the next write after the UTC date changes, not at midnight.

forceSyncOnFlush trades durability for latency. With it false, a JVM crash can lose up to queue.flushIntervalMs of the most recent log lines. Set it true if the log file is your audit system of record and the host's storage is local. Leave it false on network or slow storage, where a per-batch fsync stalls the writer.

directory, gzipRotated, and retentionDays are restart-only. enabled and forceSyncOnFlush are hot-swappable.

actions#

What gets recorded. Two layers: coarse category toggles, then per-event toggles and blacklists.

Category toggles#

Key Default Covers
logBlocks true Block place and break.
logContainers true Container deposit and withdrawal.
logItems true Item drop, pickup, craft.
logEntities true Entity kill and spawn.
logExplosions true Explosions.
logChat true Chat messages.
logCommands true Commands.
logSessions true Join, leave, username change.
logSigns true Sign edits.
logInteractions true Block interactions (buttons, levers, doors).
logWorldEvents true Non-player world events.

Per-event toggles#

Finer control, matching CoreProtect's per-listener configurability.

Key Default Records
logNaturalBreaks true Crops trampled, tall grass broken by a mob.
logTreeGrowth true Sapling to tree.
logMushroomGrowth true Huge-mushroom growth.
logVineGrowth true Vine, kelp, cave-vine growth.
logSculkSpread true Sculk-catalyst spread.
logPortals true Nether and end portal frame formation.
logWaterFlow false Water-flow block changes. Expensive.
logLavaFlow false Lava-flow block changes. Expensive.
logFireExtinguish true Fire extinguished by rain, water, or a player.
logCampfireStart true Campfire lighting.
logHopperMetaFilter false Skip hopper repeats with identical NBT.
logDuplicateSuppression true Collapse identical consecutive rows.
logCancelledChat false Record chat even when another plugin cancels it.
mixinHotEvents true Master switch for hot-tick mixin events.

logWaterFlow and logLavaFlow ship off because fluid updates are per-tick block changes; on a large farm or an ocean build they can dominate the row count. Enable them when fluid griefing is a real problem on your server, and pair them with a purge horizon.

mixinHotEvents is a kill switch, not a feature toggle. When false, the engine short-circuits any event whose source tag begins with #fire, #natural, or #dispenser before it reaches the gate or the queue. That is the emergency lever when a mixin pipeline is drowning the writer during a lag spike. Leave it true.

Blacklists#

Values are lists of strings. A null list is tolerated and treated as empty.

Key Default Skips
worldBlacklist [] Whole worlds, e.g. minecraft:the_end.
blockBlacklist ["minecraft:air"] Block ids.
sourceBlacklist [] Source tags, e.g. #tnt.
itemBlacklist [] Item ids.
entityBlacklist [] Entity ids.
"blockBlacklist": ["minecraft:air", "minecraft:stone", "minecraft:dirt"],
"entityBlacklist": ["minecraft:bat", "minecraft:armor_stand"],
"worldBlacklist": ["minecraft:the_end"]

Blacklisting a common block such as stone dramatically reduces row volume on a mining-heavy server, at the cost of losing the ability to roll back that block.

These are config lists, not blacklist.txt

actions.*Blacklist are JSON arrays edited in config.json. The separate blacklist.txt file at the server root uses a different line-based grammar and supports per-user and per-command rules. Both are applied; see Blacklist file.

Modded entity griefing#

This is the section that matters on a modded pack.

Key Type Default Valid Meaning
entityChangeAllowlist list [] see below Extra entities to record.
entityChangeLogAllEntities bool false — Record every entity block change.
entityBlockChangeCoalesceWindowMs long 500 ≥ 0 (negative disables) Dedup window per (actor, coord).
entityBlockChangeMaxTracked int 8192 > 0 Coalescer LRU capacity.

entityChangeAllowlist entries accept three shapes:

Shape Example Matches
Exact key isleofberk:night_fury That entity.
Namespace wildcard isleofberk:* Every entity in the namespace.
Bare namespace isleofberk Same as isleofberk:*.

An empty allowlist means vanilla-only: the hardcoded CoreProtect-parity set of enderman, ender dragon, wither, ravager, silverfish, turtle, fox, zombie, and falling block.

Manage the allowlist at runtime with /vg entitylog add|remove|list rather than by hand — the command writes the file and reloads it under a lock.

entityChangeLogAllEntities is a diagnostic flag

Setting it true bypasses the allowlist and records every LivingDestroyBlockEvent. On Forge and NeoForge that event is a prospective permission query fired per mob tick, so a single flying dragon can fire it hundreds of thousands of times per second without destroying anything. On a modded pack this will flood the queue, inflate the database, and drop events. Use the allowlist instead.

The coalescer is what makes modded packs viable. It collapses repeated (actor, coordinate) hits inside the window into one audit row. Setting entityBlockChangeCoalesceWindowMs to 0 is rewritten to 500 on load — a deliberate guard against accidentally disabling it. To genuinely disable coalescing you must set a negative value.

permissions#

Key Type Default Valid Meaning
useLuckPerms bool true — Try the LuckPerms bridge first.
defaultOpLevel int 3 0–4 Coarse op fallback when LP is absent.
perNodeOpLevels object {} values 0–4 Per-node op-level override map.

useLuckPerms: true is safe when LuckPerms is not installed — detection is by reflection and a missing LP simply falls through to op level.

perNodeOpLevels lets you tune the fallback per node, which is otherwise impossible without LuckPerms:

"perNodeOpLevels": {
  "vonixguardian.lookup": 2,
  "vonixguardian.rollback": 3,
  "vonixguardian.purge": 4
}

Unknown keys are logged as a WARN and ignored; values outside 0–4 are a hard validation error. permissions is restart-only.

See Permissions for the complete node list and resolution order.

lookup#

Query UX and backpressure.

Key Type Default Valid Meaning
defaultPageSize int 7 1–50 Rows per page when unspecified.
maxRadius int 10000 ≥ 1 Largest accepted numeric r:.
maxResultRows int 100000 100–10000000 Absolute row cap per query.
maxConcurrent int 4 1–64 Concurrent query permits.

defaultPageSize is also clamped to 50 at the command layer, so a config value of 50 is the practical maximum. maxResultRows is the hard ceiling the DAO refuses to exceed; a query that would materialise more rows fails rather than exhausting heap.

maxConcurrent sizes a semaphore around the read path. It is restart-only. Raise it on a server with many staff running lookups; lower it if queries compete with ticks.

privacy#

Key Type Default Valid Meaning
hashIps bool false — Hash IP and hostname values before storing.
salt string vonix-guardian-default-salt-CHANGE-ME ≥ 16 chars when hashing Salt prefix.

Default behaviour stores the raw connecting address on session rows. If your privacy policy does not allow that, set hashIps: true and change the salt.

Hashing fails closed on the placeholder salt

hashIps: true with the shipped placeholder salt is a hard validation error — the server refuses the config rather than silently producing trivially reversible hashes:

privacy.salt: must be changed from the default placeholder when privacy.hashIps is true; set a random >=16 char salt or set hashIps=false

Generate a salt once, store it with your other secrets, and never change it afterwards — a changed salt makes every stored hash unlinkable to new rows.

privacy.salt is restart-only. privacy.hashIps is hot-swappable.

purge#

Retention floors and the optional background purge daemon.

Key Type Default Valid Meaning
minAgeSecondsConsole long 86400 ≥ 60 Console purge floor (24 h).
minAgeSecondsInGame long 2592000 ≥ 3600 In-game purge floor (30 d).
autoPurgeSeconds long 0 0 or ≥ 2592000 Retention horizon. 0 = daemon off.
autoPurgeTime string 03:30 HH:mm, 24 h Daily run time, server local.

autoPurgeSeconds ships as 0, which means unbounded retention: the daemon does not run and rows accumulate until an operator purges manually or sets a horizon. 7776000 (90 days) is the suggested horizon for a busy server and is not applied for you.

"purge": {
  "autoPurgeSeconds": 7776000,
  "autoPurgeTime": "04:00"
}

With a non-zero value the daemon runs daily at autoPurgeTime, deleting rows older than the horizon. /vg status reports either the enabled state and rows purged this restart, or 0 = unbounded with the suggested value.

The floor is not the horizon

minAgeSecondsInGame limits how recent a purge may target. autoPurgeSeconds is how much history to keep. A server with a 30-day in-game floor and a 90-day horizon keeps 90 days; the floor only prevents /vg purge t:2d.

purge is fully hot-swappable, including the daemon schedule.

storage#

Key Type Default Valid Meaning
persistNbt bool true — Capture block entity, item, and entity NBT.

With persistNbt: true, producers capture full block-state properties and raw NBT bytes so a rollback restores chest contents, sign text, and entity data faithfully. That is the safe default and the reason a rolled-back chest comes back full.

With persistNbt: false, those columns stay empty. Inventory rollback and restore then refuse rows that lack a full payload rather than restoring a lossy approximation. Set it false only if storage cost outweighs fidelity for your server, and understand that container rollback becomes unreliable.

storage.persistNbt is hot-swappable.

rollback#

Key Type Default Valid Meaning
explosionSupplementalReach int 16 0–1024 Padding for the supplemental explosion scan.
mutationBatchSize int 1000 ≥ 1 World mutations applied per tick slice.
requireConfirm bool false — Stop after preview and wait for /vg apply.

explosionSupplementalReach solves a real problem: explosion rows record the blast centre, so a blast whose centre sits outside your radius but whose damage reaches inside it would be missed. The supplemental scan keeps the spatial predicate but pads it outward by this many blocks. 16 mirrors vanilla TNT reach; raise it for modded explosives with a larger radius. Widening it only relaxes a pre-filter — row admission still verifies the affected list, so it never over-admits.

mutationBatchSize is the per-tick slice of world edits during a rollback. Lower it if a large rollback causes visible tick spikes; raise it for faster completion on a capable server.

requireConfirm off (the default) means /vg rollback and /vg restore apply immediately at op level 3. Set it true on a production server where an accidental global rollback is the bigger risk:

/vg rollback u:Griefer t:24h r:#global     ← now stops after previewing
/vg apply                                   ← explicit confirmation

Existing configs keep applying immediately until an operator sets the flag. There is no automatic replay of an open batch on boot.

theme and language#

Key Default Valid
theme aqua aqua, blue, gold, green, purple, red, white
language en_us see below

Themes colour command output. language selects a message bundle from the 15 shipped bundles: en_us, en, de, es, fr, ja, ko, pl, ru, tr, tt, uk, vi, zh_cn, zh_tw. Both are hot-swappable.

Reload vs restart#

/vg reload re-reads the file and applies what is safe. Anything it cannot apply is reported by name.

Subsection /vg reload
actions.* (all toggles, blacklists, coalescer, allowlist) Hot
logFile.enabled, logFile.forceSyncOnFlush Hot
lookup.defaultPageSize, lookup.maxRadius, lookup.maxResultRows Hot
privacy.hashIps Hot
purge.* (including the daemon schedule) Hot
storage.persistNbt Hot
rollback.* (reach, batch size, requireConfirm) Hot
theme, language Hot
blacklist.txt, config/vonixguardian/worlds/ Hot
database.* Restart
queue.* Restart
logFile.directory, gzipRotated, retentionDays Restart
permissions.* Restart
lookup.maxConcurrent Restart
privacy.salt Restart

A restart-only change is detected and named but not applied — the live server keeps the old value, and the reload output says so:

[VonixGuardian] Hot-swapped: 1 actions
[VonixGuardian] Requires restart: 1 database
[VonixGuardian] Errors: 0 (none)

Per-world overrides#

Any subset of the actions block can be overridden per world, mirroring CoreProtect's world_nether.yml pattern.

Create config/vonixguardian/worlds/<world_key>.json, where the world key is the registry id with : replaced by __:

config/vonixguardian/worlds/
├── minecraft__overworld.json
├── minecraft__the_nether.json
└── minecraft__the_end.json

The file is a partial actions object. Fields you omit inherit from the root actions block.

{
  "logWaterFlow": false,
  "logLavaFlow": false,
  "logBlocks": true,
  "entityChangeAllowlist": ["twilightforest:*"]
}

That example keeps full block logging in the Nether but enables the modded entity allowlist only there.

To disable all logging in the End, set every toggle to false in minecraft__the_end.json, or simply add minecraft:the_end to the root worldBlacklist.

Overrides reload with /vg reload, are reported in /vg status under Per-world overrides, and are cached by file modification time so an unchanged file is not re-parsed.

Blacklist file#

blacklist.txt at the server root (not in the config directory) suppresses specific logging with a line-based grammar. One rule per line; blank lines and lines starting with # are ignored.

Rule Example Suppresses
user:<name> user:Notch That player's actions.
user_uuid:<uuid> user_uuid:069a79f4-44e9-4726-a5be-fca90e38aaf5 Same, by UUID.
command:<name> command:help That command (lower-cased prefix).
block:<id> block:minecraft:stone That block id.
entity:<id> entity:minecraft:creeper That entity id.
<id>@<user> minecraft:shears@#dispenser That id, only for that actor.
# Suppress a staff account entirely
user:BuildBot

# Don't record the help command
command:help

# Ignore stone and dirt — too much volume
block:minecraft:stone
block:minecraft:dirt

# Never log shears dispensed by a dispenser
minecraft:shears@#dispenser

Unknown rule kinds are logged as a WARN and skipped; a malformed file never aborts boot. /vg status reports the loaded rule count, and /vg reload re-parses it and reports the count under blacklist.txt.

Applying changes without editing the file#

For the hot-swappable subset you can skip the editor entirely:

/vg config get lookup.maxRadius
/vg config set lookup.maxRadius 20000
/vg config set actions.entityBlacklist minecraft:zombie,create:contraption
/vg config set actions.itemBlacklist none

See Commands → config for the full list of settable keys. Everything else is file-only by design.