Homepage: https://github.com/cashell/changetrack-ng
A modernized rewrite of the changetrack system file change tracker.
changetrack-ng watches a list of files and directories for changes, records
each change in a VCS repository (RCS, Git, Fossil, SVN, or Mercurial), and
optionally sends notifications via email, HTTP webhook, OpenTelemetry (OTLP),
or an append-only JSON log.
It combines the per-file flexibility of the original changetrack with the modern VCS and package-manager integration of etckeeper.
Why not just use etckeeper?
etckeeper is purpose-built for /etc; changetrack-ng tracks arbitrary paths,
supports per-file email recipients, and adds OTLP/observability integration.
Why not just use the original changetrack?
The original (last maintained 2009) is broken on Perl 5.26+ and relies on
the removed find2perl. changetrack-ng is a clean rewrite with no
compatibility debt.
- Five VCS backends: Choose RCS for per-file versioning or Git, Fossil, SVN, and Mercurial for global tree mirrors.
- Four Notification Channels: Deliver unified diffs via Email, JSON to HTTP Webhook endpoints, structured events via OTLP/OpenTelemetry, or to a local File log.
- Sensitive File Protection: Automatic mode-based detection. Files with no group/world read bit (
0600/0640) have their contents fully captured in the local secure VCS mirror, but their diff contents are unconditionally stripped from all outbound notifications (Email, Webhook, OTLP). - Package Manager Integration: Automatic pre- and post-install hooks for
apt,dnf, andpacmanensure system changes by package updates are logically bundled and committed together. - Zero External Dependencies: Built safely on Perl core modules natively (requiring only
HTTP::Tiny,JSON::PP, andIO::Compress::Gzipacross Perl ≥ 5.20+). - Flexible Execution: Deploy via
cron.hourly,systemdtimers, explicit CLI invocations, or event-driven pipelines. Note there is no daemon to manage—all execution is polling-based or hook-driven. - Configurable Tracking: Track arbitrary files and directories, with per-file/per-directory overrides for notification recipients, sensitive-file flags, and ignore patterns.
- System and User Modes: Run as root to track system paths, or as an unprivileged user to track home directories. Each mode has its own config and VCS repo.
changetrack-ng executes in a sequence that ensures consistency and safe delivery:
- Config & Run-Lock:
changetrack-ng runacquires a lock (~/.cache/...or/var/run/...) to prevent race conditions during updates. - Scan Phase: It recursively walks target directories filtering out globally/locally ignored files (like
*.swpor/etc/mtab). - Diff Check: Uses
lstatcombinations alongside sidecar metadata diffs to catch permission adjustments and skips expensive content diffs unless modifications exist. - VCS Commit: All accumulated changes are written atomically or batch-staged to the chosen backend.
- Notification Dispatch: Change reports and unified diffs are passed down the pipeline to every enabled notification channel (observing sensitive file diff scrubbing restrictions natively).
# Install
sudo make install
# Install systemd timer (recommended) or cron
sudo make install-systemd # then: sudo systemctl enable --now changetrack-ng.timer
# OR
sudo make install-cron
# Install package manager hooks (auto-detects apt/dnf/pacman)
sudo make install-hooks
# Edit config
sudo $EDITOR /etc/changetrack-ng/changetrack-ng.conf
# First run
sudo changetrack-ng runUnprivileged user-home install (no sudo):
make install-user
$HOME/.local/bin/changetrack-ng runFor this mode, edit $HOME/.config/changetrack-ng/changetrack-ng.conf and set
output_dir under your home directory.
Minimal config — track /etc with Git and no notifications:
[global]
vcs = git
output_dir = /var/lib/changetrack-ng
[track]
DIRECTORY /etc- Perl 5.20 or later
- At least one VCS tool:
rcs,git,fossil,svn, orhg IO::Socket::SSLfor HTTPS endpoints (not core, but widely packaged)
Config uses a single INI format with [global], [track], and
[notifier:<type>:<name>] sections. The installed default at
/etc/changetrack-ng/changetrack-ng.conf is fully commented.
Comments start with # or ; — either as a full line or inline after a
value (when preceded by whitespace, e.g. timeout = 10 # seconds).
Security notes:
- Treat the config as sensitive data and set mode
0640or stricter. - Prefer environment-backed secrets for OTLP headers (
${VAR}expansion in config values is supported, andOTEL_EXPORTER_OTLP_HEADERSis preferred for secret tokens). - The webhook and OTLP notifiers verify TLS certificates by default. Set
insecure = 1on a notifier only if you intentionally use a self-signed endpoint.
Reliability notes:
command_timeout(global, default120seconds,0= unlimited) bounds every external command with a watchdog that kills the child, so a hung or runaway tool cannot stall a scheduled (cron/systemd) run.- The webhook notifier honors
max_diff_bytes(default0= unlimited) to cap the diff size in the POST payload, mirroring theotlpandfilenotifiers.
[global]
vcs = git
output_dir = /var/lib/changetrack-ng
[notifier:email:default]
enabled = true
to = default-admin@example.com
from = changetrack-ng@${HOSTNAME}
[notifier:webhook:default]
enabled = true
url = https://events.example.com/webhook
full_diff = 1
[track]
# Send diffs to default-admin
DIRECTORY /etc/systemd
# Override default email for networking configs
DIRECTORY /etc/NetworkManager : network-team@example.com
# Auto-suppresses diff content over 0600 sensitive file
/etc/shadow
# Explicit sensitive tracking over 0644 file
/etc/ssl/certs/custom.crt sensitive=yesAutomatic drop-ins are fixed sibling directories next to the main config file:
| Directory | Allowed sections |
|---|---|
conf.d/ |
[global] only |
track.d/ |
[track] only |
notifier.d/ |
[notifier:*] only |
All *.conf files in each directory are processed in sorted order and merged
into the main config. Custom automatic drop-in directories are not configured
separately; use explicit @include directives for custom files or directories.
@include /etc/changetrack-ng/extra.conf
@include-track /etc/changetrack-ng/track.d/
@include-notifier /etc/changetrack-ng/notifier.d/The include path may be a file or a directory. Directory includes process
sorted *.conf files, which makes them the supported mechanism for custom
drop-in directories. @include-track and @include-notifier enforce section
restrictions in the included path. Recursion is prevented by a visited-paths
guard.
For internals and module boundaries, see ARCHITECTURE.md. For operational troubleshooting, see INSTALL.md.
These flags apply to all subcommands:
| Flag | Short | Description |
|---|---|---|
--system |
Load /etc/changetrack-ng/changetrack-ng.conf (default when no flag is given). |
|
--user |
Load $HOME/.config/changetrack-ng/changetrack-ng.conf. --system and --user are mutually exclusive. |
|
--config <path> |
-c |
Load the specified config file directly. Overrides --system, --user, and $CHANGETRACK_CONFIG. |
--verbose |
-v |
Show high-level run structure: config path, VCS backend, lock status, file counts, commit and notify outcomes |
--debug |
-d |
Show all external commands run, effective config values (secrets redacted), per-file actions taken. Implies --verbose. |
--trace |
-t |
Show per-file disposition for every tracked path (O(N): fast-path skips, type classifications, ignore matches). Implies --debug. |
--quiet |
-q |
Suppress all INFO output. Intended for unattended/cron/hook invocations. |
--dry-run |
-n |
Detect changes without writing to VCS or dispatching notifications. |
All diagnostic output (--verbose, --debug, --trace) goes to STDERR. STDOUT remains clean and contains only subcommand data (diffs, status listings, log output), making it safe to pipe.
The config file is selected in this priority order (highest wins):
-c <path>/--config <path>— explicit path on the command line$CHANGETRACK_CONFIG— environment variable (useful in scripts and hook overrides)--system/--user— explicit scope flag- Default: system config (
/etc/changetrack-ng/changetrack-ng.conf)
The defaults file (/etc/default/changetrack-ng on Debian-like systems,
/etc/sysconfig/changetrack-ng on RHEL-like systems) is sourced by cron, systemd,
and package manager hooks before invoking the CLI. It lets administrators set
CHANGETRACK_CONFIG or CHANGETRACK_EXTRA_ARGS system-wide without editing hook or
unit files. The template is installed by make install-hooks.
| Command | Description |
|---|---|
run |
Scan, commit, notify (default) |
run --dry-run or -n |
Detect and report changes statically without mutating VCS or sending notifications |
init |
Initialize VCS repo explicitly |
status |
List changed files |
diff [path] |
Show pending unified diff |
log [path] |
Show VCS history |
revert <path> <rev> |
Restore file from a revision |
update-ignore |
Refresh VCS ignore file |
changetrack-ng supports four parallel notification channels to report changes.
Each channel is configured with a [notifier:<type>:<name>] section. Multiple
instances of the same type are supported (e.g., two webhook endpoints with
different URLs). A channel is active when its section contains enabled = true.
Disabled instances are ignored at parse time.
Sends traditional email diffs to administrators. Recipients can be defined globally or overriding on a per-file/per-directory basis directly in the [track] list. This allows routing changes of specific subsystems (like /etc/nginx) to specific teams.
Sends a JSON POST payload to one or more endpoints. Useful for Slack/Teams integrations, custom automation, or SIEM ingestion. Payloads can optionally include full unified diffs or just file-level metadata.
Exports one OTLP log record per changed file, fitting natively into modern observability stacks (Loki, Datadog, Splunk, etc.). Heartbeat records (sent even on no-change runs) let monitoring systems distinguish true silence from agent failure without vendor-specific logic.
[notifier:otlp:default]
enabled = true
endpoint = https://otelcol.example.com:4318/v1/logs
compression = gzip
heartbeat = 1Note on OTLP Security: The resolution precedence enforces
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT > OTEL_EXPORTER_OTLP_ENDPOINT >
endpoint config key. Always prefer injecting OTEL_EXPORTER_OTLP_HEADERS
via the environment rather than hardcoding bearer tokens in the config file.
Appends one OTel-shaped JSON record per event (one per changed file, one per heartbeat, one per warning) to a local NDJSON log file, ensuring a machine-readable audit trail exists locally alongside the VCS repo.
The following keys are supported by both the otlp and file notifiers:
| Key | Default | Description |
|---|---|---|
emit_recipients |
0 |
When 1, each change record includes a config_change.notified attribute listing the configured recipients for that file. Off by default to keep recipient lists out of the telemetry stream. |
max_diff_bytes |
0 (unlimited) |
Caps the size of the config_change.diff attribute in the emitted record. When a diff exceeds the cap, the diff is truncated and config_change.diff_truncated = true is added. The full diff is always retained in the VCS. Sensitive-file suppressed-diff placeholders are never truncated. |
severity_change |
INFO |
Severity name for file-change, file-create, and file-remove records. Accepts DEBUG, INFO, WARN, or ERROR (case-insensitive). Invalid or blank values fall back to INFO. |
severity_heartbeat |
DEBUG |
Severity name for heartbeat ("nothing changed") records. Same accepted values as severity_change. |
severity_warning |
WARN |
Severity name for scan-warning records. Same accepted values as severity_change. |
Example using non-default severities and diff truncation:
[notifier:otlp:default]
enabled = true
endpoint = https://otelcol.example.com:4318/v1/logs
heartbeat = 1
severity_change = WARN
severity_warning = ERROR
max_diff_bytes = 65536
[notifier:file:audit]
enabled = true
path = /var/log/changetrack-ng.log
emit_recipients = 1
max_diff_bytes = 32768| Variable | Default | Notes |
|---|---|---|
PREFIX |
/usr/local |
Set to /usr for system-wide install |
BINDIR |
$(PREFIX)/bin |
Binary location |
LIBDIR |
$(PREFIX)/lib/changetrack-ng |
Perl modules |
SYSCONFDIR |
$(PREFIX)/etc (or /etc for /usr) |
Config dir |
DATADIR |
/var/lib/changetrack-ng |
VCS output dir |
MANDIR |
$(PREFIX)/share/man |
Man pages |
DEFAULTSDIR |
/etc/default (Debian) or /etc/sysconfig (RHEL, auto-detected) |
Defaults file directory |
Override any variable on the make command line:
sudo make install PREFIX=/usrFirst-class user targets:
make install-usermake install-user-systemdmake install-user-cron
make test # Run full test suite (requires prove)
prove -l t/01-config.t # Run a single test file
make manifest-lint # Validate MANIFEST.toml (checks all listed files exist)Tests live in t/ and cover all implemented modules. All tests use only
core Perl modules; no network access required.
MANIFEST.toml is the authoritative inventory of every file in the repository.
Each entry records the file's description, install path, permissions, which
distribution tarballs include it, and tags for navigation.
tools/manifest-query is a Perl script that reads MANIFEST.toml via
TOML::Tiny and answers structured queries:
tools/manifest-query --dist full # files in the full source tarball
tools/manifest-query --dist deploy # files in the deploy tarball
tools/manifest-query --category library # all library modules
tools/manifest-query --tag vcs # all files tagged "vcs"
tools/manifest-query --validate # verify all listed files exist on disk
tools/manifest-query --deps-deb # Depends:/Recommends: lines for debian/control
tools/manifest-query --build-deps-deb # Build-Depends: lines for debian/controlTOML::Tiny (libtoml-tiny-perl / perl-TOML-Tiny) is required to run
tools/manifest-query but is not a runtime dependency of changetrack-ng
itself.
GNU General Public License v3 or later. See LICENSE.