Metamanager Server provides the OS-level daemons, systemd services, and Debian packaging for the Metamanager WordPress plugin.
The server installs compression and metadata embedding daemons that watch a filesystem job queue and process media files using ExifTool, jpegtran, optipng, cwebp, and ffmpeg — all outside of PHP so WordPress never blocks on the hot path.
Plugin docs → mm-plugin.richardkentgates.com · Server docs → metamanager.richardkentgates.com
Looking for the WordPress plugin? Features, metadata fields, Schema.org, sitemaps, WP-CLI, and REST API documentation live in the plugin repository.
# Import the GPG key
wget -q -O /tmp/metamanager.key https://apt.richardkentgates.com/key.gpg
sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/metamanager.gpg /tmp/metamanager.key
# Add the apt repository
echo "deb [signed-by=/usr/share/keyrings/metamanager.gpg] https://apt.richardkentgates.com stable main" | sudo tee /etc/apt/sources.list.d/metamanager.list
# Install
sudo apt update && sudo apt install metamanagerThis installs:
- Compression and metadata daemons
- Systemd services (auto-enabled)
- All OS dependencies (ExifTool, jpegtran, optipng, cwebp, ffmpeg, jq, inotify-tools)
After installing, install the WordPress plugin and activate it.
| OS | PHP | WordPress | Package manager | Result |
|---|---|---|---|---|
| Ubuntu 22.04 LTS | 8.1 – 8.3 | 6.0+ | apt | ✅ Full install |
| AlmaLinux 9.7 (RHEL 9-compat) | 8.3.30 | 6.9.4 | dnf + EPEL + CRB | ✅ Full install |
| GitHub Actions (ubuntu-latest) | 8.2 | Latest | apt | ✅ CI (every push) |
| Component | Minimum | Notes |
|---|---|---|
| OS | Linux | systemd required; tested on Ubuntu 22.04+, Debian 12+, AlmaLinux / Rocky / RHEL 9+. The install script supports apt, dnf, and yum. |
| bash | 5.0+ | Required by the daemon scripts. Ubuntu 18.04 (bash 4.4) is not supported. |
| ExifTool | any | libimage-exiftool-perl (apt) or perl-Image-ExifTool (dnf via EPEL) |
| jpegtran | any | libjpeg-turbo-progs (apt) or libjpeg-turbo-utils (dnf) |
| optipng | any | optipng — in EPEL 9 for RHEL-based systems |
| cwebp | any | webp (apt) or libwebp-tools (dnf via CRB) |
| ffmpeg | any | ffmpeg (apt) or via RPM Fusion (dnf) — for video remux |
| inotify-tools | any | inotify-tools — in EPEL 9 for RHEL-based systems |
| jq | any | JSON parsing in daemon scripts |
| systemd | v232+ | Minimum for ProtectSystem=strict and ReadWritePaths= used in service units |
WordPress (PHP) OS (Bash daemons)
──────────────── ─────────────────────────────────────
Upload / scan / edit media file
(image, video, audio, PDF)
│
├── On upload or scan: enqueue_import_job() writes an
│ 'import' job — daemon reads embedded tags (EXIF/IPTC/
│ XMP, ID3, QuickTime, Vorbis, GPS) and returns them
│ as JSON. WP-Cron applies to WP fields, never overwrites.
│
▼
Write job JSON to inotifywait detects new file
wp-content/metamanager-jobs/ │
compress/ or meta/ ◄───────┘
│
▼
Process file:
jpegtran / optipng / cwebp (image compression)
ffmpeg (video remux)
ExifTool (metadata — all types)
│
▼
Write result JSON to
completed/ or failed/
│
WP-Cron (every 60s) ◄─────────────┘
reads result files,
inserts into DB,
deletes result file
│
▼
History table updated
(Media → Metamanager)
Metamanager runs two systemd units: a compression daemon and a metadata daemon:
- Watches
wp-content/metamanager-jobs/compress/viainotifywait - Delegates to tool by media type:
- Images:
jpegtran(lossless JPEG),optipng(lossless PNG),cwebp -lossless(lossless WebP) - Video:
ffmpeg -c copy(lossless container remux — strips padding, no re-encoding)
- Images:
- Writes result JSON to
completed/orfailed/
- Watches
wp-content/metamanager-jobs/meta/(import jobs arrive asjob_type: "import"insidemeta/) - Import jobs: reads embedded tags (EXIF/IPTC/XMP, ID3, QuickTime, Vorbis) via ExifTool, returns JSON for WP-Cron to apply
- Embed jobs: writes current WordPress field values back into files via ExifTool
- Handles all media types including PDF (XMP fields)
| Pattern | Detail |
|---|---|
| Event-driven | inotifywait fires on file creation — no polling, no sleep loops |
| Atomic ownership | First daemon to rename a job file claims it (mv ... .processing) |
| PID files | Written to wp-content/metamanager-jobs/ — used by plugin for health checks |
| Error handling | Failed jobs written to failed/ with error message in result JSON |
| Tool delegation | Each tool handles one format family — no monolithic binary |
Both daemons run as the web-server user (patched at install time) (the WordPress user) with security hardening:
[Service]
Type=simple
User=www-data
Group=www-data
ExecStart=/usr/local/bin/metamanager-compress-daemon.sh
Restart=on-failure
RestartSec=5
# Security hardening
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/path/to/wordpress/wp-content/metamanager-jobs /path/to/wordpress/wp-content/uploads /var/log
NoNewPrivileges=true
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true# Status
systemctl status metamanager-compress-daemon
systemctl status metamanager-meta-daemon
# Logs
journalctl -u metamanager-compress-daemon -f
journalctl -u metamanager-meta-daemon -f
# Restart
sudo systemctl restart metamanager-compress-daemon
sudo systemctl restart metamanager-meta-daemonmetamanager-install.sh handles fresh installs and updates:
- Detects package manager (
apt,dnf,yum) - Installs all OS dependencies (ExifTool, jpegtran, optipng, cwebp, ffmpeg, jq, inotify-tools)
- Copies daemon scripts to
/usr/local/bin/ - Creates and enables systemd service units
- Sets correct ownership (
www-data:www-data) on the job queue directory
- Does not install the WordPress plugin (that's a separate step)
- Does not configure WordPress or database settings
- Does not require any WordPress path when run from the server repo directory
# Fresh install
sudo bash metamanager-install.sh --wp-path /srv/www/wordpress
# Update: daemon updates are automatic via the WordPress plugin's MM_Daemon_Updater (triggers apt-get when versions mismatch).
# To re-run the installer manually (e.g. after a WordPress path change):
sudo bash metamanager-install.sh --wp-path /srv/www/wordpress
# Uninstall
sudo apt-get remove metamanager # stops daemons, removes units/scripts
sudo apt-get purge metamanager # also removes logs, state, configPushes to dev trigger the CI workflow:
- ShellCheck — lints all bash scripts (daemon scripts, installer)
- Auto-increment version — bumps
debian/changelogandVERSIONfile
Triggered manually via GitHub Actions workflow_dispatch:
- Merges
devintotest - Builds
.debpackage - Deploys to apt repo (pre-release channel)
Triggered manually via GitHub Actions workflow_dispatch:
- Merges
testintomain(with-X theirsfor any doc conflicts) - Builds
.debpackage - Creates git tag (
v2.x.x) - Creates GitHub release with
.debattached - Deploys to apt repo (stable channel)
The apt repo at apt.richardkentgates.com serves two channels:
| Channel | Branch | Package version | Use case |
|---|---|---|---|
| Stable | main |
2.4.x |
Production |
| Test | dists/test |
2.4.x-1 (same format; pin with -t test) |
Pre-release testing |
The plugin reads the VERSION file for dashboard display only — it never triggers updates.
| File | Purpose | Format |
|---|---|---|
debian/changelog |
Debian package version | 2.4.17-1 (upstream-revision) |
VERSION |
Installed daemon version (read by plugin) | 2.4.17 (plain semver, no -1) |
daemon-compatibility.json |
Plugin-to-daemon version mapping | { "2.3.58": "2.4.8" } |
CI auto-bumps debian/changelog and VERSION on every push to dev. They must stay in sync — CI handles this automatically; never edit either file manually.
Via WordPress admin (recommended):
The WordPress plugin's MM_Daemon_Updater compares the installed daemon version against the required version declared by the plugin's daemon-compatibility.json and triggers apt-get install automatically when a mismatch is detected after a plugin update. No manual intervention is needed.
Via apt directly:
sudo apt update && sudo apt upgrade metamanager# 1. Clone
git clone https://github.com/richardkentgates/metamanager.git
# 2. Copy to WordPress plugins directory
cp -r metamanager /path/to/wordpress/wp-content/plugins/
# 3. Run installer (handles daemons + dependencies)
sudo bash /path/to/wordpress/wp-content/plugins/metamanager/metamanager-install.sh \
--wp-path /path/to/wordpress# Stop and remove daemons
sudo systemctl stop metamanager-compress-daemon metamanager-meta-daemon
sudo systemctl disable metamanager-compress-daemon metamanager-meta-daemon
sudo rm /etc/systemd/system/metamanager-*.service
sudo rm /usr/local/bin/metamanager-*-daemon.sh
sudo systemctl daemon-reload
# Remove the plugin if not already deleted via WP admin
rm -rf /path/to/wordpress/wp-content/plugins/metamanagerOr via the installer:
sudo apt-get purge metamanagerSee the plugin README for the WordPress admin uninstall flow.
metamanager/
├── daemons/
│ ├── metamanager-compress-daemon.sh # Compression daemon
│ └── metamanager-meta-daemon.sh # Metadata daemon
├── metamanager-install.sh # Installer script
├── VERSION # Installed daemon version
├── daemon-compatibility.json # Plugin ↔ daemon version map
├── debian/
│ ├── changelog # Debian package version
│ ├── control # Package metadata
│ ├── postinst # Post-install script
│ ├── postrm # Post-remove/purge cleanup
│ └── metamanager.install # Files to install
├── .github/
│ ├── workflows/
│ │ ├── ci.yml # Dev CI (ShellCheck + version bump)
│ │ ├── promote-to-test.yml # workflow_dispatch: dev→test, build, deploy
│ │ └── promote-to-main.yml # workflow_dispatch: test→main, tag, release, deploy
│ └── BRANCHING.md # Branch strategy
├── docs/ # Documentation
│ ├── 01-overview.md # Architecture overview
│ ├── 02-installation.md # Installation guide
│ ├── 03-daemon-reference.md # Daemon scripts reference
│ ├── 04-systemd.md # systemd services and hardening
│ ├── 05-job-queue.md # Job queue contract
│ └── 06-troubleshooting.md # Common issues and fixes
├── ARCHITECTURE.md # Server architecture reference
├── CONTRIBUTING.md # Development guide
├── CHANGELOG.md # Server release history
├── SECURITY.md # Security policy
└── AGENTS.md # AI agent workflow rules
Metamanager would not exist without the following open source tools and projects. Full credit, respect, and gratitude to their authors and maintainers.
Author: Phil Harvey License: Perl Artistic License / GPL v1+ Website: https://exiftool.org
The backbone of all metadata work. ExifTool reads and writes EXIF, IPTC, and XMP tags across virtually every image format.
License: BSD 3-Clause / IJG License / zlib Website: https://libjpeg-turbo.org
Lossless JPEG optimisation — reordering Huffman tables and enabling progressive scan without re-encoding a single pixel.
Author: Cosmin Truța License: zlib/libpng License Website: https://optipng.sourceforge.net
Lossless PNG compression by trying multiple DEFLATE parameters and filter combinations.
Author: Google License: BSD 3-Clause Website: https://developers.google.com/speed/webp
Lossless WebP compression via cwebp -lossless.
License: LGPL v2.1+ / GPL v2+ Website: https://ffmpeg.org
Lossless video container remux via ffmpeg -c copy — strips padding and redundant data without re-encoding.
License: GPL v2 Repository: https://github.com/inotify-tools/inotify-tools
Event-driven file watching — inotifywait fires immediately on job file creation.
License: MIT License Website: https://jqlang.github.io/jq/
JSON parsing inside the bash daemon scripts.
License: LGPL v2.1+ Website: https://systemd.io
Process lifecycle, automatic restart, boot-time start, and journal-based logging for both daemons.
Metamanager is free and open source. If it saves you time or adds value to your work, consider supporting its continued development:
GPLv3 or later. See LICENSE.
Copyright © Richard Kent Gates