- 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>
279 lines
8.3 KiB
Markdown
279 lines
8.3 KiB
Markdown
# 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'
|
|
<paste wrapper script here>
|
|
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 <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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```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 <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*
|