# 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. ```bash ssh $TARGET_HOST "ls -la $BINARY_PATH && file $BINARY_PATH" ``` If it's already a symlink, follow it to the real target: ```bash 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. ```bash ssh $TARGET_HOST "sudo mv $BINARY_PATH ${BINARY_PATH}${REAL_SUFFIX}" ``` If the original was a symlink (e.g., pip-installed Python tool): ```bash # 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 ```bash 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: ```bash 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: ```bash #!/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: ```bash ssh $TARGET_HOST "sudo tee $WRAPPER_DIR/$WRAPPER_NAME > /dev/null << 'WRAPPER' WRAPPER sudo chmod +x $WRAPPER_DIR/$WRAPPER_NAME" ``` ### Gate ```bash 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. ```bash ssh $TARGET_HOST "sudo ln -sf $WRAPPER_DIR/$WRAPPER_NAME $BINARY_PATH" ``` ### Gate ```bash ssh $TARGET_HOST "ls -la $BINARY_PATH" ``` Must show: `$BINARY_PATH -> $WRAPPER_DIR/$WRAPPER_NAME` Verify the full chain: ```bash 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. ```bash # 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: ```bash ssh $TARGET_HOST "$BINARY_PATH " 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. ```bash ssh $TARGET_HOST "sudo systemctl restart " 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: ```bash 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 $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*