- Documents recent infrastructure cleanup (8 CTs destroyed, 35 DNS records removed, Headscale cleanup) - Adds 24 new runbooks covering Authentik, PeerTube, Meshtastic, RECON, Proxmox, Mailcow, Internet Archive, GPU routing - Adds project documentation for headscale, vaultwarden, peertube, matrix, mmud, advbbs, arr stack - Updates services.md, environment.md, caddy.md, authentik.md to match live infrastructure - Removes 4 deprecated runbook duplicates (canonical versions live in projects/) - Adds .gitignore for binary archives and editor temp files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.3 KiB
Binary Wrapper Interception
Transparently intercept a CLI binary with a wrapper script that adds pre-flight logic (routing, validation, logging) before exec-ing the real binary. The caller — whether a service, cron job, or another script — never knows the difference.
Use this when you need to modify the behavior of a tool that's called by a system you don't control (e.g., a runner, scheduler, or third-party service), without changing the caller's config or code.
Prerequisites
- The binary to intercept is installed and working
- You have root/sudo access on the target machine
- The caller invokes the binary by absolute path or via PATH lookup
Inputs
Prompt the user for all of these before executing:
TARGET_HOST= # SSH alias or IP (e.g., cortex). Use "localhost" if local.
BINARY_NAME= # Name of the binary to intercept (e.g., "whisper-ctranslate2")
BINARY_PATH= # Full path to the binary (e.g., "/usr/local/bin/whisper-ctranslate2")
WRAPPER_NAME= # Name for the wrapper script (e.g., "whisper-smart")
WRAPPER_DIR= # Directory for the wrapper (e.g., "/usr/local/bin")
REAL_SUFFIX= # Suffix for the renamed real binary (default: "-real")
Step 1: Locate the Real Binary
Find the actual binary or symlink that will be intercepted.
ssh $TARGET_HOST "ls -la $BINARY_PATH && file $BINARY_PATH"
If it's already a symlink, follow it to the real target:
ssh $TARGET_HOST "readlink -f $BINARY_PATH"
Gate
Must return a valid file. Record the real binary location — you'll need it for the rename.
Step 2: Rename the Real Binary
Move the original binary out of the way so the wrapper can take its place.
ssh $TARGET_HOST "sudo mv $BINARY_PATH ${BINARY_PATH}${REAL_SUFFIX}"
If the original was a symlink (e.g., pip-installed Python tool):
# Preserve the symlink target
REAL_TARGET=$(ssh $TARGET_HOST "readlink -f $BINARY_PATH")
ssh $TARGET_HOST "sudo rm $BINARY_PATH && sudo ln -s $REAL_TARGET ${BINARY_PATH}${REAL_SUFFIX}"
Gate
ssh $TARGET_HOST "ls -la ${BINARY_PATH}${REAL_SUFFIX}"
ssh $TARGET_HOST "${BINARY_PATH}${REAL_SUFFIX} --version 2>/dev/null || ${BINARY_PATH}${REAL_SUFFIX} --help 2>/dev/null | head -1"
The renamed binary must exist and be executable. If not, undo immediately:
ssh $TARGET_HOST "sudo mv ${BINARY_PATH}${REAL_SUFFIX} $BINARY_PATH"
Step 3: Write the Wrapper Script
Create the wrapper at $WRAPPER_DIR/$WRAPPER_NAME. The wrapper must:
- Accept all original arguments (
$@) - Perform pre-flight logic (inspection, routing, logging)
execthe real binary with (possibly modified) arguments- Never silently swallow errors — if pre-flight fails, exit with a meaningful code
Template:
#!/bin/bash
# Wrapper for $BINARY_NAME — transparently intercepts calls
# Real binary at: ${BINARY_PATH}${REAL_SUFFIX}
LOGFILE="/tmp/${WRAPPER_NAME}.log"
# ──── Pre-flight logic ────
# Add your inspection, routing, or validation here.
# Example: inspect input files, check resource availability, choose parameters.
# Parse arguments to find relevant inputs (file paths, flags, etc.)
# This section is use-case specific.
# ──── Logging ────
echo "[WRAPPER] $(date) args: $@" >> "$LOGFILE"
# ──── Execute real binary ────
# Use exec to replace this process — caller sees the real binary's exit code,
# stdout, stderr, and signal handling as if wrapper didn't exist.
exec ${BINARY_PATH}${REAL_SUFFIX} "$@"
Deploy the wrapper:
ssh $TARGET_HOST "sudo tee $WRAPPER_DIR/$WRAPPER_NAME > /dev/null << 'WRAPPER'
<paste wrapper script here>
WRAPPER
sudo chmod +x $WRAPPER_DIR/$WRAPPER_NAME"
Gate
ssh $TARGET_HOST "ls -la $WRAPPER_DIR/$WRAPPER_NAME && head -1 $WRAPPER_DIR/$WRAPPER_NAME"
Must show executable permissions and #!/bin/bash shebang.
Step 4: Install the Symlink
Replace the original binary path with a symlink to the wrapper.
ssh $TARGET_HOST "sudo ln -sf $WRAPPER_DIR/$WRAPPER_NAME $BINARY_PATH"
Gate
ssh $TARGET_HOST "ls -la $BINARY_PATH"
Must show: $BINARY_PATH -> $WRAPPER_DIR/$WRAPPER_NAME
Verify the full chain:
ssh $TARGET_HOST "ls -la $BINARY_PATH && ls -la ${BINARY_PATH}${REAL_SUFFIX}"
Should show:
BINARY_PATH -> WRAPPER_DIR/WRAPPER_NAME (wrapper)
BINARY_PATH-real -> /path/to/actual/binary (real binary)
Step 5: Test the Interception
Run the binary as the caller would. The wrapper should intercept transparently.
# Direct invocation
ssh $TARGET_HOST "$BINARY_PATH --version"
# Check wrapper log
ssh $TARGET_HOST "tail -5 /tmp/${WRAPPER_NAME}.log"
The --version output should come from the real binary. The log should show the wrapper fired.
Test with actual workload arguments:
ssh $TARGET_HOST "$BINARY_PATH <typical args here>"
ssh $TARGET_HOST "tail -1 /tmp/${WRAPPER_NAME}.log"
Gate
Both must succeed. If the binary fails or produces different output than before, the wrapper has a bug — check argument passing (quoting, $@ vs $*).
Step 6: Verify Service Integration
If the binary is called by a service (systemd, cron, etc.), restart that service and confirm it picks up the wrapper.
ssh $TARGET_HOST "sudo systemctl restart <service-name>"
ssh $TARGET_HOST "sleep 5 && tail -5 /tmp/${WRAPPER_NAME}.log"
The log should show entries from the service's invocations, not just your manual tests.
Rollback
To remove the wrapper and restore the original binary:
ssh $TARGET_HOST "sudo rm $BINARY_PATH && sudo mv ${BINARY_PATH}${REAL_SUFFIX} $BINARY_PATH"
# Or if the original was a symlink:
ssh $TARGET_HOST "sudo rm $BINARY_PATH && sudo ln -s <original-target> $BINARY_PATH"
No service restart needed — next invocation hits the real binary directly.
Key Principles
-
execis mandatory. Withoutexec, the wrapper runs the binary as a child process, which breaks signal handling (SIGTERM won't reach the real binary) and doubles PID usage.execreplaces the wrapper process entirely. -
Use
"$@"not$@or$*. Quoted"$@"preserves argument boundaries. Unquoted$@splits arguments with spaces.$*merges all arguments into one string. -
Appended flags override earlier ones. Many CLI tools (argparse, getopt) use last-value-wins for duplicate flags. The wrapper can append
--flag valueafter"$@"to force overrides without removing the caller's original flags. -
Exit codes matter. If pre-flight fails, exit with a non-zero code that the caller understands. Some callers retry on specific exit codes (e.g., PeerTube runner retries on exit 1).
-
Log to /tmp, not to the service's log directory. The wrapper log is a debug artifact, not part of the service's data.
/tmpis cleaned on reboot, which is fine for wrapper logs.
Troubleshooting
Wrapper not being called
Check the symlink chain: ls -la $BINARY_PATH. If the service uses a hardcoded absolute path that bypasses PATH, the symlink might be in the wrong location.
Arguments with spaces break
Use "$@" (quoted) in the exec line, not $@ (unquoted).
Service fails after wrapper install
Check the wrapper's shebang (#!/bin/bash), permissions (chmod +x), and that exec is present. Without exec, the wrapper may exit before the binary finishes.
Wrapper log is empty
The service might be calling a different path than expected. Check: which $BINARY_NAME and compare with what the service config specifies.
Usage Examples
Whisper transcription routing (PeerTube runner on cortex)
The PeerTube remote runner calls whisper-ctranslate2 for auto-captioning. The smart wrapper intercepts this to route short videos to GPU and long videos to CPU.
BINARY_NAME=whisper-ctranslate2
BINARY_PATH=/usr/local/bin/whisper-ctranslate2
WRAPPER_NAME=whisper-smart
REAL_SUFFIX=-real
Symlink chain:
/usr/local/bin/whisper-ctranslate2 → /usr/local/bin/whisper-smart
/usr/local/bin/whisper-ctranslate2-real → /home/zvx/.local/bin/whisper-ctranslate2
Wrapper logic:
- ffprobe audio duration from first non-flag argument
- < 1hr → exec with --device cuda --compute_type float16
- >= 1hr → exec with --device cpu --compute_type int8
- Appends --model medium after $@ (last-value-wins override)
Last updated: 2026-02-17