echo6-docs/runbooks/binary-wrapper-interception.md
Matt Johnson e9231ac24a Migration: consolidate Echo6 docs to cortex with full infrastructure cleanup sync
- 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>
2026-04-13 06:02:16 +00:00

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:

  1. Accept all original arguments ($@)
  2. Perform pre-flight logic (inspection, routing, logging)
  3. exec the real binary with (possibly modified) arguments
  4. 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.


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

  1. exec is mandatory. Without exec, the wrapper runs the binary as a child process, which breaks signal handling (SIGTERM won't reach the real binary) and doubles PID usage. exec replaces the wrapper process entirely.

  2. Use "$@" not $@ or $*. Quoted "$@" preserves argument boundaries. Unquoted $@ splits arguments with spaces. $* merges all arguments into one string.

  3. Appended flags override earlier ones. Many CLI tools (argparse, getopt) use last-value-wins for duplicate flags. The wrapper can append --flag value after "$@" to force overrides without removing the caller's original flags.

  4. 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).

  5. Log to /tmp, not to the service's log directory. The wrapper log is a debug artifact, not part of the service's data. /tmp is 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