2762b0dfb8
- deploy_phishing.py: undefined extra_vars on orchestration playbook - freebird/main_menu.py: set_variable() called as function, is a method; fix to config.prompt_set_variable() - freebird/tool_manager.py: shell=True with f-string paths; replace with direct pip binary call - certipy-enum.py: password exposed in process list and logs; pass via env var - cleanup_engine.py: load_vars_file() called with path string, expects provider name - ioc-u.py: scapy import not guarded; tool crashed if scapy missing - um_ops.py + um-vault.py: SQL table names f-stringed directly; quote identifiers - ops-logger.sh: 63-line duplicate function block; removed second copy; quote config_script var - recon_tools.py: ir-sandbox + link-sandbox not in TOOLS dict; forensics menu option 5 hidden from user - um-crack.py: missing --scope flag inconsistent with all other Umbra tools - heartbeat_ingest.py: openssl subprocess not catching FileNotFoundError; wrap with clear error
1738 lines
56 KiB
Bash
1738 lines
56 KiB
Bash
#!/usr/bin/env bash
|
|
# OpsLogger - Complete logging solution for red team operations
|
|
# Version 2.5.3 - Merged config and prompt fixes
|
|
|
|
# Configuration variables
|
|
CONFIG_FILE="${HOME}/.ops-logger.conf"
|
|
DEFAULT_TARGET="target-$(hostname | tr '.' '-')"
|
|
DEFAULT_LOG_DIR="${HOME}/OperationLogs"
|
|
PROMPT_NEW_SHELLS=true
|
|
RECORD_INTERVAL=0.5
|
|
DEBUG=false
|
|
|
|
# File markers
|
|
LOG_MARKER="/tmp/ops-logger-active"
|
|
RECORDING_MARKER="/tmp/ops-logger-recording"
|
|
CONFIG_IN_PROGRESS="/tmp/ops-logger-configuring"
|
|
VERBOSE_CMD_MARKER="/tmp/ops-logger-cmd"
|
|
|
|
# Helper functions
|
|
log_debug() {
|
|
if [[ "$DEBUG" == "true" ]]; then
|
|
local debug_dir="${LOG_DIR:-${DEFAULT_LOG_DIR}}"
|
|
mkdir -p "$debug_dir" 2>/dev/null
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >> "${debug_dir}/ops-logger-debug.log"
|
|
fi
|
|
}
|
|
|
|
ensure_dir() {
|
|
mkdir -p "$1" 2>/dev/null || return 1
|
|
return 0
|
|
}
|
|
|
|
load_config() {
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
source "$CONFIG_FILE"
|
|
else
|
|
TARGET_NAME="$DEFAULT_TARGET"
|
|
LOG_DIR="$DEFAULT_LOG_DIR"
|
|
fi
|
|
|
|
TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}"
|
|
LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}"
|
|
}
|
|
|
|
save_config() {
|
|
ensure_dir "$(dirname "$CONFIG_FILE")"
|
|
cat > "$CONFIG_FILE" << EOF
|
|
# Ops Logger Configuration
|
|
TARGET_NAME="$TARGET_NAME"
|
|
LOG_DIR="$LOG_DIR"
|
|
PROMPT_NEW_SHELLS=$PROMPT_NEW_SHELLS
|
|
RECORD_INTERVAL=$RECORD_INTERVAL
|
|
DEBUG=$DEBUG
|
|
EOF
|
|
}
|
|
|
|
# Get public IP
|
|
get_public_ip() {
|
|
local ip
|
|
ip=$(curl -s --connect-timeout 3 ifconfig.me) || \
|
|
ip=$(curl -s --connect-timeout 3 ipinfo.io/ip) || \
|
|
ip=$(curl -s --connect-timeout 3 icanhazip.com) || \
|
|
ip="unknown"
|
|
echo "$ip"
|
|
}
|
|
|
|
# Detection functions
|
|
is_tmux() { [[ -n "$TMUX" ]]; }
|
|
|
|
# Get normalized pane ID (session-window-pane format)
|
|
get_pane_id() {
|
|
if is_tmux; then
|
|
# If TMUX_PANE is set (from hook), use that specific pane
|
|
if [[ -n "$TMUX_PANE" ]]; then
|
|
local session=$(tmux display -t "$TMUX_PANE" -p '#{session_name}')
|
|
local window=$(tmux display -t "$TMUX_PANE" -p '#{window_index}')
|
|
local pane=$(tmux display -t "$TMUX_PANE" -p '#{pane_index}')
|
|
echo "${session}-${window}-${pane}"
|
|
else
|
|
local session=$(tmux display -p '#{session_name}')
|
|
local window=$(tmux display -p '#{window_index}')
|
|
local pane=$(tmux display -p '#{pane_index}')
|
|
echo "${session}-${window}-${pane}"
|
|
fi
|
|
else
|
|
echo "$$"
|
|
fi
|
|
}
|
|
|
|
# Get tmux pane reference for tmux commands (% format for tmux commands)
|
|
get_tmux_pane_ref() {
|
|
if is_tmux; then
|
|
# If TMUX_PANE is set (from hook), use that specific pane
|
|
if [[ -n "$TMUX_PANE" ]]; then
|
|
echo "$TMUX_PANE"
|
|
else
|
|
tmux display -p '#{pane_id}'
|
|
fi
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# Get the actual tmux prefix key instead of hardcoding C-b
|
|
get_tmux_prefix() {
|
|
# First check if we're using oh-my-tmux which often uses C-a
|
|
if tmux show-options -g | grep -q "TMUX_CONF"; then
|
|
# Check if prefix has been overridden in .local config
|
|
local prefix=$(tmux show-options -g prefix 2>/dev/null | awk '{print $2}')
|
|
if [[ -n "$prefix" ]]; then
|
|
case "$prefix" in
|
|
"C-q") echo "C-q" ;;
|
|
"C-a") echo "C-a" ;;
|
|
"C-b") echo "C-b" ;;
|
|
*) echo "$prefix" ;;
|
|
esac
|
|
else
|
|
echo "C-a" # oh-my-tmux default
|
|
fi
|
|
else
|
|
# Try to get the actual prefix from tmux config
|
|
local prefix=$(tmux show-options -g prefix 2>/dev/null | awk '{print $2}')
|
|
if [[ -n "$prefix" ]]; then
|
|
echo "$prefix"
|
|
else
|
|
# Default to C-b if we can't determine
|
|
echo "C-b"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
is_logging_active() { [[ -f "${LOG_MARKER}-$1" ]]; }
|
|
is_recording_active() { [[ -f "${RECORDING_MARKER}-$1" ]]; }
|
|
|
|
# ================================================================
|
|
# DEPENDENCY CHECKING
|
|
# ================================================================
|
|
|
|
# Check if TPM is installed
|
|
check_tpm_installed() {
|
|
[[ -d "$HOME/.tmux/plugins/tpm" ]]
|
|
}
|
|
|
|
# Check if tmux-logging plugin is installed
|
|
check_tmux_logging_installed() {
|
|
[[ -d "$HOME/.tmux/plugins/tmux-logging" ]]
|
|
}
|
|
|
|
# Print installation instructions for TMux Plugin Manager (TPM)
|
|
print_tpm_install_instructions() {
|
|
echo "========================================================================="
|
|
echo "Tmux Plugin Manager (TPM) is not installed. To install:"
|
|
echo "========================================================================="
|
|
echo "1. Run these commands:"
|
|
echo " mkdir -p ~/.tmux/plugins"
|
|
echo " git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm"
|
|
echo ""
|
|
echo "2. Add these lines to your ~/.tmux.conf:"
|
|
echo " # List of plugins"
|
|
echo " set -g @plugin 'tmux-plugins/tpm'"
|
|
echo " set -g @plugin 'tmux-plugins/tmux-sensible'"
|
|
echo " set -g @plugin 'tmux-plugins/tmux-logging'"
|
|
echo ""
|
|
echo " # Initialize TMUX plugin manager (keep this line at the bottom)"
|
|
echo " run '~/.tmux/plugins/tpm/tpm'"
|
|
echo ""
|
|
echo "3. Reload your tmux configuration:"
|
|
echo " tmux source-file ~/.tmux.conf"
|
|
echo ""
|
|
echo "4. Install plugins by pressing:"
|
|
echo " prefix + I (capital I)"
|
|
echo "========================================================================="
|
|
}
|
|
|
|
# Print installation instructions for tmux-logging plugin
|
|
print_tmux_logging_install_instructions() {
|
|
echo "========================================================================="
|
|
echo "tmux-logging plugin is not installed. To install:"
|
|
echo "========================================================================="
|
|
echo "1. Ensure TPM is installed (see previous instructions if needed)"
|
|
echo ""
|
|
echo "2. Add this line to your ~/.tmux.conf (before the tpm init line):"
|
|
echo " set -g @plugin 'tmux-plugins/tmux-logging'"
|
|
echo ""
|
|
echo "3. Reload your tmux configuration:"
|
|
echo " tmux source-file ~/.tmux.conf"
|
|
echo ""
|
|
echo "4. Install the plugin by pressing:"
|
|
echo " prefix + I (capital I)"
|
|
echo "========================================================================="
|
|
}
|
|
|
|
# Check for required dependencies
|
|
check_dependencies() {
|
|
local id="$1"
|
|
local missing=false
|
|
|
|
if ! is_tmux; then
|
|
echo "WARNING: Not running in tmux. Only basic CSV logging will be available."
|
|
echo " For full functionality, start tmux first."
|
|
return 0
|
|
fi
|
|
|
|
# Check TPM and tmux-logging plugin
|
|
if ! check_tpm_installed; then
|
|
missing=true
|
|
print_tpm_install_instructions
|
|
elif ! check_tmux_logging_installed; then
|
|
missing=true
|
|
print_tmux_logging_install_instructions
|
|
fi
|
|
|
|
if $missing; then
|
|
# Don't use display-message, just echo
|
|
echo "Plugin dependencies missing, see instructions above"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ================================================================
|
|
# CSV LOGGING (our custom implementation)
|
|
# ================================================================
|
|
|
|
# Setup CSV logging (ORIGINAL FORMAT MAINTAINED)
|
|
setup_csv_log() {
|
|
local target="$1"
|
|
local log_dir="$2"
|
|
local csv_file="${log_dir}/${target}_commands.csv"
|
|
|
|
ensure_dir "$log_dir"
|
|
if [[ ! -f "$csv_file" ]]; then
|
|
echo '"StartTime","EndTime","SourceIP","User","Path","Command"' > "$csv_file"
|
|
fi
|
|
|
|
echo "$csv_file"
|
|
}
|
|
|
|
# Create command hook with better timing and error handling
|
|
create_command_hook() {
|
|
local hook_script="$1"
|
|
local csv_log="$2"
|
|
local pane_id="$3"
|
|
local verbose_log="$4"
|
|
|
|
cat > "$hook_script" << 'EOF'
|
|
#!/usr/bin/env bash
|
|
# Command hook - FIXED version with proper function timing
|
|
|
|
# IMMEDIATELY set all variables and disable errexit
|
|
set +e
|
|
|
|
# Set variables first before anything else
|
|
CSV_LOG="__CSV_LOG__"
|
|
VERBOSE_LOG="__VERBOSE_LOG__"
|
|
PANE_ID="__PANE_ID__"
|
|
PUBLIC_IP=$(timeout 5 curl -s ifconfig.me 2>/dev/null || echo "unknown")
|
|
RTL_INTERNAL_LOGGING=true
|
|
RTL_CMD_START_TIME=""
|
|
VERBOSE_CMD_MARKER="__VERBOSE_CMD_MARKER__"
|
|
|
|
# Function to check if command should be logged
|
|
should_log_command() {
|
|
local cmd="$1"
|
|
case "$cmd" in
|
|
*"RTL_"*|*"log_command"*|*"CSV_LOG"*|*"should_log_command"*) return 1 ;;
|
|
"history "*|*"PROMPT_COMMAND"*|*"source /tmp/ops"*) return 1 ;;
|
|
"") return 1 ;;
|
|
*) return 0 ;;
|
|
esac
|
|
}
|
|
|
|
# Function to write verbose command header
|
|
write_verbose_header() {
|
|
local cmd="$1"
|
|
local start_time="$2"
|
|
local user=$(whoami)
|
|
local path=$(pwd)
|
|
|
|
# Create a marker file with command info for the verbose processor
|
|
echo "$cmd|$start_time|$user|$path|$PUBLIC_IP|$PANE_ID" > "${VERBOSE_CMD_MARKER}-${PANE_ID}"
|
|
}
|
|
|
|
# Function to log command to CSV
|
|
RTL_log_command() {
|
|
local cmd="$1"
|
|
local start_time="$2"
|
|
local end_time="$3"
|
|
local user=$(whoami)
|
|
local path=$(pwd)
|
|
|
|
should_log_command "$cmd" || return 0
|
|
|
|
local old_flag="$RTL_INTERNAL_LOGGING"
|
|
RTL_INTERNAL_LOGGING=false
|
|
|
|
local escaped_cmd=${cmd//\"/\"\"}
|
|
|
|
printf '"%s","%s","%s","%s","%s","%s"\n' \
|
|
"$start_time" "$end_time" "$PUBLIC_IP" "$user" "$path" "$escaped_cmd" >> "$CSV_LOG" 2>/dev/null
|
|
|
|
RTL_INTERNAL_LOGGING="$old_flag"
|
|
}
|
|
|
|
# BASH-specific setup
|
|
if [[ -n "$BASH_VERSION" ]]; then
|
|
# Save original PROMPT_COMMAND
|
|
[[ -z "$RTL_ORIG_PROMPT_COMMAND" ]] && RTL_ORIG_PROMPT_COMMAND="$PROMPT_COMMAND"
|
|
|
|
RTL_LAST_COMMAND=""
|
|
RTL_LAST_HISTNUM=""
|
|
|
|
# FIXED: Define preexec function BEFORE setting trap
|
|
RTL_preexec() {
|
|
local cmd="$BASH_COMMAND"
|
|
if should_log_command "$cmd" 2>/dev/null; then
|
|
RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
write_verbose_header "$cmd" "$RTL_CMD_START_TIME"
|
|
fi
|
|
}
|
|
|
|
# FIXED: Define PROMPT_COMMAND function BEFORE using it
|
|
RTL_PROMPT_COMMAND() {
|
|
if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local current_histnum=$(history 1 2>/dev/null | awk '{print $1}')
|
|
local current_cmd=$(history 1 2>/dev/null | sed 's/^[ ]*[0-9]*[ ]*//')
|
|
|
|
if [[ "$current_cmd" != "$RTL_LAST_COMMAND" && "$current_histnum" != "$RTL_LAST_HISTNUM" ]]; then
|
|
if should_log_command "$current_cmd" 2>/dev/null; then
|
|
local end_time="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
local start_time="${RTL_CMD_START_TIME:-$end_time}"
|
|
|
|
RTL_log_command "$current_cmd" "$start_time" "$end_time"
|
|
|
|
RTL_LAST_COMMAND="$current_cmd"
|
|
RTL_LAST_HISTNUM="$current_histnum"
|
|
fi
|
|
fi
|
|
|
|
RTL_CMD_START_TIME=""
|
|
}
|
|
|
|
# NOW set the trap (function is already defined)
|
|
trap 'RTL_preexec 2>/dev/null || true' DEBUG 2>/dev/null
|
|
|
|
# Set PROMPT_COMMAND (function is already defined)
|
|
if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then
|
|
PROMPT_COMMAND="RTL_PROMPT_COMMAND; $RTL_ORIG_PROMPT_COMMAND"
|
|
else
|
|
PROMPT_COMMAND="RTL_PROMPT_COMMAND"
|
|
fi
|
|
|
|
# ZSH-specific setup
|
|
elif [[ -n "$ZSH_VERSION" ]]; then
|
|
# Define functions first
|
|
RTL_zsh_preexec() {
|
|
local cmd="$1"
|
|
if should_log_command "$cmd" 2>/dev/null; then
|
|
RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
write_verbose_header "$cmd" "$RTL_CMD_START_TIME"
|
|
fi
|
|
}
|
|
|
|
RTL_zsh_precmd() {
|
|
if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local cmd=$(fc -ln -1 2>/dev/null)
|
|
|
|
if should_log_command "$cmd" 2>/dev/null; then
|
|
local end_time="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
local start_time="${RTL_CMD_START_TIME:-$end_time}"
|
|
|
|
RTL_log_command "$cmd" "$start_time" "$end_time"
|
|
fi
|
|
|
|
RTL_CMD_START_TIME=""
|
|
}
|
|
|
|
# NOW set the hooks (functions are already defined)
|
|
if autoload -Uz add-zsh-hook 2>/dev/null; then
|
|
add-zsh-hook preexec RTL_zsh_preexec 2>/dev/null || true
|
|
add-zsh-hook precmd RTL_zsh_precmd 2>/dev/null || true
|
|
fi
|
|
fi
|
|
|
|
# Mark as active
|
|
echo "$$" > "__LOG_MARKER__-${PANE_ID}"
|
|
echo "Ops Logger command hook installed for pane $PANE_ID" >&2
|
|
EOF
|
|
|
|
# Replace placeholders with actual values
|
|
sed -i "s|__CSV_LOG__|$csv_log|g" "$hook_script"
|
|
sed -i "s|__VERBOSE_LOG__|$verbose_log|g" "$hook_script"
|
|
sed -i "s|__PANE_ID__|$pane_id|g" "$hook_script"
|
|
sed -i "s|__LOG_MARKER__|$LOG_MARKER|g" "$hook_script"
|
|
sed -i "s|__VERBOSE_CMD_MARKER__|$VERBOSE_CMD_MARKER|g" "$hook_script"
|
|
|
|
chmod +x "$hook_script"
|
|
}
|
|
|
|
# ================================================================
|
|
# UNIFIED VERBOSE LOGGING WITH COMMAND HEADERS
|
|
# ================================================================
|
|
|
|
# Create single master verbose log file for all panes
|
|
create_master_verbose_log() {
|
|
local target="$1"
|
|
local log_dir="$2"
|
|
# Remove date from filename for continuous logging
|
|
local verbose_dir="${log_dir}/verbose"
|
|
ensure_dir "$verbose_dir"
|
|
# Single log file for entire target
|
|
local log_file="${verbose_dir}/${target}_master.log"
|
|
# Create the file with header if it doesn't exist
|
|
if [[ ! -f "$log_file" ]]; then
|
|
cat > "$log_file" << EOF
|
|
================================================================================
|
|
RED TEAM TERMINAL LOGGER
|
|
Master Verbose Log
|
|
================================================================================
|
|
Target: $target
|
|
Date: $(date +%Y-%m-%d)
|
|
Host: $(hostname)
|
|
Public IP: $(get_public_ip)
|
|
Started: $(date '+%Y-%m-%d %H:%M:%S')
|
|
================================================================================
|
|
|
|
EOF
|
|
fi
|
|
|
|
echo "$log_file"
|
|
}
|
|
|
|
# Create debug filter that captures EVERYTHING (no filtering)
|
|
create_debug_filter_script() {
|
|
local filter_script="/tmp/ops-debug-filter-$$.sh"
|
|
|
|
cat > "$filter_script" << 'EODEBUG'
|
|
#!/usr/bin/env bash
|
|
# DEBUG: Raw pipe-pane output capture
|
|
|
|
LOGFILE="$1"
|
|
PANE_ID="$2"
|
|
DEBUG_RAW_FILE="${LOGFILE}.debug-raw"
|
|
|
|
mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null
|
|
|
|
echo "[$(date)] DEBUG FILTER STARTED for pane $PANE_ID" >> "$DEBUG_RAW_FILE"
|
|
echo "=================================================" >> "$DEBUG_RAW_FILE"
|
|
|
|
line_count=0
|
|
while IFS= read -r line; do
|
|
line_count=$((line_count + 1))
|
|
|
|
# Write EVERYTHING to debug file with line numbers
|
|
printf "[%04d][$(date +%H:%M:%S)] RAW: %s\n" "$line_count" "$line" >> "$DEBUG_RAW_FILE"
|
|
|
|
# Also write to main log (no filtering at all)
|
|
echo "$line" >> "$LOGFILE"
|
|
done
|
|
|
|
echo "[$(date)] DEBUG FILTER ENDED - Total lines: $line_count" >> "$DEBUG_RAW_FILE"
|
|
EODEBUG
|
|
|
|
chmod +x "$filter_script"
|
|
echo "$filter_script"
|
|
}
|
|
|
|
# Add debug start function
|
|
start_debug_verbose_logging() {
|
|
local id="$1"
|
|
local tmux_pane_ref="$2"
|
|
local target="$3"
|
|
local log_dir="$4"
|
|
|
|
local debug_log="${log_dir}/debug-${target}-${id}-$(date +%H%M%S).log"
|
|
local filter_script=$(create_debug_filter_script)
|
|
|
|
echo "Starting DEBUG verbose logging to: $debug_log"
|
|
|
|
# Use simple pipe-pane with debug filter
|
|
tmux pipe-pane -t "$tmux_pane_ref" "bash '$filter_script' '$debug_log' '$id'"
|
|
|
|
sleep 1
|
|
|
|
# Check if logging started
|
|
if tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$tmux_pane_ref.*1"; then
|
|
echo "DEBUG logging started successfully"
|
|
echo "Raw output file: ${debug_log}.debug-raw"
|
|
echo "Filtered output file: $debug_log"
|
|
|
|
echo "$filter_script" > "/tmp/ops-debug-filter-script-${id}"
|
|
return 0
|
|
else
|
|
echo "ERROR: DEBUG logging failed to start"
|
|
rm -f "$filter_script"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Create the MINIMAL FIX verbose filter script
|
|
# Create the FIXED verbose filter script
|
|
create_verbose_filter_script() {
|
|
local filter_script="/tmp/ops-verbose-filter-$$.sh"
|
|
|
|
cat > "$filter_script" << 'EOFILTER'
|
|
#!/usr/bin/env bash
|
|
# FIXED: Proper command boundary detection
|
|
|
|
LOGFILE="$1"
|
|
PANE_ID="$2"
|
|
TARGET="$3"
|
|
VERBOSE_CMD_MARKER="$4"
|
|
|
|
mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null
|
|
|
|
# Command tracking
|
|
CURRENT_COMMAND=""
|
|
IN_COMMAND=false
|
|
OUTPUT_LINE_COUNT=0
|
|
MAX_LINES_PER_COMMAND=22
|
|
LAST_MARKER_CHECK=""
|
|
|
|
# Function to properly close a command
|
|
close_current_command() {
|
|
if [[ "$IN_COMMAND" == "true" ]]; then
|
|
echo "==============================================================================" >> "$LOGFILE"
|
|
echo "" >> "$LOGFILE"
|
|
IN_COMMAND=false
|
|
OUTPUT_LINE_COUNT=0
|
|
CURRENT_COMMAND=""
|
|
fi
|
|
}
|
|
|
|
# Function to check for new command markers
|
|
check_command_marker() {
|
|
local marker_file="${VERBOSE_CMD_MARKER}-${PANE_ID}"
|
|
if [[ -f "$marker_file" ]]; then
|
|
# Read the marker content
|
|
local marker_content=$(cat "$marker_file" 2>/dev/null)
|
|
|
|
# Only process if this is a new marker (different from last check)
|
|
if [[ "$marker_content" != "$LAST_MARKER_CHECK" ]]; then
|
|
LAST_MARKER_CHECK="$marker_content"
|
|
|
|
# FIXED: Always close previous command first
|
|
close_current_command
|
|
|
|
# Read command info
|
|
IFS='|' read -r cmd start_time user path public_ip pane_marker <<< "$marker_content"
|
|
|
|
# Start new command if it's for this pane
|
|
if [[ "$pane_marker" == "$PANE_ID" && -n "$cmd" ]]; then
|
|
CURRENT_COMMAND="$cmd"
|
|
IN_COMMAND=true
|
|
OUTPUT_LINE_COUNT=0
|
|
|
|
# Write command header
|
|
cat >> "$LOGFILE" << EOCMD
|
|
==============================================================================
|
|
COMMAND EXECUTION - $start_time
|
|
==============================================================================
|
|
Command: $cmd
|
|
User: $user
|
|
Path: $path
|
|
Start: $start_time
|
|
Pane: $PANE_ID
|
|
Public IP: $public_ip
|
|
------------------------------------------------------------------------------
|
|
OUTPUT:
|
|
EOCMD
|
|
|
|
# Remove the marker file after processing
|
|
rm -f "$marker_file" 2>/dev/null
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Simple ANSI stripping
|
|
strip_ansi() {
|
|
local line="$1"
|
|
# Remove bracketed paste mode
|
|
line="${line//[?2004h/}"
|
|
line="${line//[?2004l/}"
|
|
# Remove ANSI escape sequences
|
|
line=$(echo "$line" | sed -E 's/\x1b\[[0-9;]*[mGKHF]//g; s/\x1b\[[?]?[0-9]*[hlc]//g')
|
|
echo "$line"
|
|
}
|
|
|
|
# Check if line looks like a shell prompt
|
|
is_prompt_line() {
|
|
local line="$1"
|
|
# More specific prompt detection patterns
|
|
if [[ "$line" =~ .*@.*:.*[\$#][[:space:]]*$ ]] || \
|
|
[[ "$line" =~ ^[[:space:]]*[\$#][[:space:]]*$ ]] || \
|
|
[[ "$line" =~ .*[\$#][[:space:]]+[a-zA-Z] ]]; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Main processing loop
|
|
while IFS= read -r line; do
|
|
# ALWAYS check for new commands first, before any processing
|
|
check_command_marker
|
|
|
|
# Clean the line
|
|
clean_line=$(strip_ansi "$line")
|
|
|
|
# Skip completely empty lines
|
|
[[ -z "$clean_line" ]] && continue
|
|
|
|
# If we're in a command, log the output
|
|
if [[ "$IN_COMMAND" == "true" ]]; then
|
|
OUTPUT_LINE_COUNT=$((OUTPUT_LINE_COUNT + 1))
|
|
|
|
# Check if this looks like a prompt (indicating command end)
|
|
if is_prompt_line "$clean_line" && [[ $OUTPUT_LINE_COUNT -gt 1 ]]; then
|
|
# Don't log the prompt line, just close the command
|
|
close_current_command
|
|
continue
|
|
fi
|
|
|
|
# Log the output line with timestamp
|
|
if [[ $OUTPUT_LINE_COUNT -le $MAX_LINES_PER_COMMAND ]]; then
|
|
echo "$(date '+%H:%M:%S') $clean_line" >> "$LOGFILE"
|
|
elif [[ $OUTPUT_LINE_COUNT -eq $(($MAX_LINES_PER_COMMAND + 1)) ]]; then
|
|
echo "... [OUTPUT TRUNCATED - showing first $MAX_LINES_PER_COMMAND lines only] ..." >> "$LOGFILE"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# End any active command on exit
|
|
close_current_command
|
|
|
|
EOFILTER
|
|
|
|
chmod +x "$filter_script"
|
|
echo "$filter_script"
|
|
}
|
|
|
|
# Enhanced command hook that integrates with verbose logging
|
|
create_enhanced_command_hook() {
|
|
local hook_script="$1"
|
|
local csv_log="$2"
|
|
local pane_id="$3"
|
|
local verbose_log="$4"
|
|
|
|
cat > "$hook_script" << 'EOF'
|
|
#!/usr/bin/env bash
|
|
# Enhanced command hook with verbose integration
|
|
|
|
set +e
|
|
|
|
# Configuration
|
|
CSV_LOG="__CSV_LOG__"
|
|
VERBOSE_LOG="__VERBOSE_LOG__"
|
|
PANE_ID="__PANE_ID__"
|
|
PUBLIC_IP=$(timeout 5 curl -s ifconfig.me 2>/dev/null || echo "unknown")
|
|
RTL_INTERNAL_LOGGING=true
|
|
RTL_CMD_START_TIME=""
|
|
VERBOSE_CMD_MARKER="__VERBOSE_CMD_MARKER__"
|
|
|
|
# Function to check if command should be logged
|
|
should_log_command() {
|
|
local cmd="$1"
|
|
case "$cmd" in
|
|
*"RTL_"*|*"log_command"*|*"CSV_LOG"*|*"should_log_command"*) return 1 ;;
|
|
"history "*|*"PROMPT_COMMAND"*|*"source /tmp/ops"*) return 1 ;;
|
|
"") return 1 ;;
|
|
*) return 0 ;;
|
|
esac
|
|
}
|
|
|
|
# Enhanced function to write verbose command header
|
|
write_verbose_header() {
|
|
local cmd="$1"
|
|
local start_time="$2"
|
|
local user=$(whoami)
|
|
local path=$(pwd)
|
|
|
|
# Create marker for verbose filter
|
|
echo "$cmd|$start_time|$user|$path|$PUBLIC_IP|$PANE_ID" > "${VERBOSE_CMD_MARKER}-${PANE_ID}"
|
|
}
|
|
|
|
# Function to log command to CSV (unchanged)
|
|
RTL_log_command() {
|
|
local cmd="$1"
|
|
local start_time="$2"
|
|
local end_time="$3"
|
|
local user=$(whoami)
|
|
local path=$(pwd)
|
|
|
|
should_log_command "$cmd" || return 0
|
|
|
|
local old_flag="$RTL_INTERNAL_LOGGING"
|
|
RTL_INTERNAL_LOGGING=false
|
|
|
|
local escaped_cmd=${cmd//\"/\"\"}
|
|
|
|
printf '"%s","%s","%s","%s","%s","%s"\n' \
|
|
"$start_time" "$end_time" "$PUBLIC_IP" "$user" "$path" "$escaped_cmd" >> "$CSV_LOG" 2>/dev/null
|
|
|
|
RTL_INTERNAL_LOGGING="$old_flag"
|
|
}
|
|
|
|
# BASH-specific setup
|
|
if [[ -n "$BASH_VERSION" ]]; then
|
|
[[ -z "$RTL_ORIG_PROMPT_COMMAND" ]] && RTL_ORIG_PROMPT_COMMAND="$PROMPT_COMMAND"
|
|
|
|
RTL_LAST_COMMAND=""
|
|
RTL_LAST_HISTNUM=""
|
|
|
|
RTL_preexec() {
|
|
local cmd="$BASH_COMMAND"
|
|
if should_log_command "$cmd" 2>/dev/null; then
|
|
RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
write_verbose_header "$cmd" "$RTL_CMD_START_TIME"
|
|
fi
|
|
}
|
|
|
|
RTL_PROMPT_COMMAND() {
|
|
if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local current_histnum=$(history 1 2>/dev/null | awk '{print $1}')
|
|
local current_cmd=$(history 1 2>/dev/null | sed 's/^[ ]*[0-9]*[ ]*//')
|
|
|
|
if [[ "$current_cmd" != "$RTL_LAST_COMMAND" && "$current_histnum" != "$RTL_LAST_HISTNUM" ]]; then
|
|
if should_log_command "$current_cmd" 2>/dev/null; then
|
|
local end_time="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
local start_time="${RTL_CMD_START_TIME:-$end_time}"
|
|
|
|
RTL_log_command "$current_cmd" "$start_time" "$end_time"
|
|
|
|
RTL_LAST_COMMAND="$current_cmd"
|
|
RTL_LAST_HISTNUM="$current_histnum"
|
|
fi
|
|
fi
|
|
|
|
RTL_CMD_START_TIME=""
|
|
}
|
|
|
|
trap 'RTL_preexec 2>/dev/null || true' DEBUG 2>/dev/null
|
|
|
|
if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then
|
|
PROMPT_COMMAND="RTL_PROMPT_COMMAND; $RTL_ORIG_PROMPT_COMMAND"
|
|
else
|
|
PROMPT_COMMAND="RTL_PROMPT_COMMAND"
|
|
fi
|
|
|
|
# ZSH-specific setup
|
|
elif [[ -n "$ZSH_VERSION" ]]; then
|
|
RTL_zsh_preexec() {
|
|
local cmd="$1"
|
|
if should_log_command "$cmd" 2>/dev/null; then
|
|
RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
write_verbose_header "$cmd" "$RTL_CMD_START_TIME"
|
|
fi
|
|
}
|
|
|
|
RTL_zsh_precmd() {
|
|
if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local cmd=$(fc -ln -1 2>/dev/null)
|
|
|
|
if should_log_command "$cmd" 2>/dev/null; then
|
|
local end_time="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
local start_time="${RTL_CMD_START_TIME:-$end_time}"
|
|
|
|
RTL_log_command "$cmd" "$start_time" "$end_time"
|
|
fi
|
|
|
|
RTL_CMD_START_TIME=""
|
|
}
|
|
|
|
if autoload -Uz add-zsh-hook 2>/dev/null; then
|
|
add-zsh-hook preexec RTL_zsh_preexec 2>/dev/null || true
|
|
add-zsh-hook precmd RTL_zsh_precmd 2>/dev/null || true
|
|
fi
|
|
fi
|
|
|
|
# Mark as active
|
|
echo "$$" > "__LOG_MARKER__-${PANE_ID}"
|
|
echo "Enhanced Ops Logger with verbose integration installed for pane $PANE_ID" >&2
|
|
EOF
|
|
|
|
# Replace placeholders
|
|
sed -i "s|__CSV_LOG__|$csv_log|g" "$hook_script"
|
|
sed -i "s|__VERBOSE_LOG__|$verbose_log|g" "$hook_script"
|
|
sed -i "s|__PANE_ID__|$pane_id|g" "$hook_script"
|
|
sed -i "s|__LOG_MARKER__|$LOG_MARKER|g" "$hook_script"
|
|
sed -i "s|__VERBOSE_CMD_MARKER__|$VERBOSE_CMD_MARKER|g" "$hook_script"
|
|
|
|
chmod +x "$hook_script"
|
|
}
|
|
|
|
# Start unified verbose logging
|
|
# Start unified verbose logging (add delay for stability)
|
|
start_unified_verbose_logging() {
|
|
local id="$1"
|
|
local tmux_pane_ref="$2"
|
|
local target="$3"
|
|
local log_dir="$4"
|
|
|
|
# Create master log file
|
|
local master_log=$(create_master_verbose_log "$target" "$log_dir")
|
|
|
|
# Create filter script
|
|
local filter_script=$(create_verbose_filter_script)
|
|
|
|
log_debug "Starting unified verbose logging to: $master_log"
|
|
|
|
# Use pipe-pane with our filter script
|
|
tmux pipe-pane -t "$tmux_pane_ref" "bash '$filter_script' '$master_log' '$id' '$target' '$VERBOSE_CMD_MARKER'"
|
|
|
|
# Give pipe-pane time to establish
|
|
sleep 0.5
|
|
|
|
# Check if logging started successfully
|
|
if tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$tmux_pane_ref.*1"; then
|
|
log_debug "Unified verbose logging started successfully"
|
|
|
|
# Store filter script path for cleanup
|
|
echo "$filter_script" > "/tmp/ops-filter-script-${id}"
|
|
|
|
return 0
|
|
else
|
|
log_debug "ERROR: Unified verbose logging failed to start"
|
|
rm -f "$filter_script"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Stop unified verbose logging
|
|
stop_unified_verbose_logging() {
|
|
local tmux_pane_ref="$1"
|
|
local id="$2"
|
|
|
|
# Stop pipe-pane
|
|
tmux pipe-pane -t "$tmux_pane_ref" 2>/dev/null || true
|
|
|
|
# Clean up filter script
|
|
if [[ -f "/tmp/ops-filter-script-${id}" ]]; then
|
|
local filter_script=$(cat "/tmp/ops-filter-script-${id}")
|
|
rm -f "$filter_script" 2>/dev/null
|
|
rm -f "/tmp/ops-filter-script-${id}"
|
|
fi
|
|
|
|
# Clean up markers
|
|
rm -f "${VERBOSE_CMD_MARKER}-${id}" 2>/dev/null
|
|
rm -f "/tmp/ops-cmd-active-${id}" 2>/dev/null
|
|
|
|
log_debug "Stopped unified verbose logging for pane: $tmux_pane_ref"
|
|
}
|
|
|
|
# ================================================================
|
|
# OHMYTMUX-COMPATIBLE WINDOW NAME MANAGEMENT
|
|
# ================================================================
|
|
|
|
# Improved window name handling that works with ohmytmux
|
|
get_current_window_name() {
|
|
local tmux_pane_ref="$1"
|
|
local name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}')
|
|
|
|
# Remove our indicators as well as any ohmytmux status indicators
|
|
name="${name#🔴 }"
|
|
name="${name#🎥 }"
|
|
name="${name#● }"
|
|
name="${name#⚠ }"
|
|
name="${name#▶ }"
|
|
|
|
echo "$name"
|
|
}
|
|
|
|
set_window_logging_indicator() {
|
|
local tmux_pane_ref="$1"
|
|
local current_name=$(get_current_window_name "$tmux_pane_ref")
|
|
|
|
# Preserve ohmytmux automatic formats but add our indicator
|
|
if [[ "$current_name" == *Z ]]; then
|
|
# Zoomed window format in ohmytmux
|
|
tmux rename-window -t "$tmux_pane_ref" "🔴 ${current_name%Z}Z"
|
|
else
|
|
tmux rename-window -t "$tmux_pane_ref" "🔴 $current_name"
|
|
fi
|
|
}
|
|
|
|
set_window_recording_indicator() {
|
|
local tmux_pane_ref="$1"
|
|
local current_name=$(get_current_window_name "$tmux_pane_ref")
|
|
|
|
# Remove logging indicator if present and add recording
|
|
current_name="${current_name#🔴 }"
|
|
|
|
# Preserve ohmytmux automatic formats
|
|
if [[ "$current_name" == *Z ]]; then
|
|
# Zoomed window format in ohmytmux
|
|
tmux rename-window -t "$tmux_pane_ref" "🎥 ${current_name%Z}Z"
|
|
else
|
|
tmux rename-window -t "$tmux_pane_ref" "🎥 $current_name"
|
|
fi
|
|
}
|
|
|
|
clear_window_indicators() {
|
|
local tmux_pane_ref="$1"
|
|
local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}')
|
|
local clean_name=$(get_current_window_name "$tmux_pane_ref")
|
|
|
|
# Preserve any ohmytmux indicators that might be present
|
|
if [[ "$current_name" == *Z ]]; then
|
|
# Zoomed window format in ohmytmux
|
|
tmux rename-window -t "$tmux_pane_ref" "${clean_name}Z"
|
|
else
|
|
tmux rename-window -t "$tmux_pane_ref" "$clean_name"
|
|
fi
|
|
}
|
|
|
|
# MAIN LOGGING FUNCTIONS
|
|
# ================================================================
|
|
|
|
# Enhanced logging installation with unified verbose logging
|
|
install_logging() {
|
|
local id="$1"
|
|
local csv_log="$2"
|
|
local target="$3"
|
|
local log_dir="$4"
|
|
|
|
log_debug "Installing enhanced logging for pane $id"
|
|
|
|
if is_tmux; then
|
|
local tmux_pane_ref=$(get_tmux_pane_ref)
|
|
|
|
# Get master verbose log path
|
|
local master_log=$(create_master_verbose_log "$target" "$log_dir")
|
|
|
|
# Start unified verbose logging
|
|
if start_unified_verbose_logging "$id" "$tmux_pane_ref" "$target" "$log_dir"; then
|
|
log_debug "Unified verbose logging started successfully"
|
|
else
|
|
log_debug "Verbose logging failed, continuing with CSV only"
|
|
fi
|
|
|
|
# Create enhanced command hook
|
|
local hook_script="/tmp/ops-hook-${id}.sh"
|
|
create_enhanced_command_hook "$hook_script" "$csv_log" "$id" "$master_log"
|
|
|
|
# Install the hook
|
|
tmux send-keys -t "$tmux_pane_ref" "source '$hook_script' 2>/dev/null && echo 'Enhanced logging hooks installed successfully' || echo 'Logging may have warnings but is active'" ENTER
|
|
|
|
sleep 2
|
|
|
|
# Set window indicator
|
|
set_window_logging_indicator "$tmux_pane_ref"
|
|
|
|
# Mark as active
|
|
touch "${LOG_MARKER}-${id}"
|
|
touch "${LOG_MARKER}-${id}.success"
|
|
|
|
else
|
|
# Direct shell logging (CSV only)
|
|
local hook_script="/tmp/ops-hook-${id}.sh"
|
|
create_enhanced_command_hook "$hook_script" "$csv_log" "$id" ""
|
|
source "$hook_script"
|
|
echo "Direct shell logging started (CSV only)"
|
|
|
|
touch "${LOG_MARKER}-${id}"
|
|
touch "${LOG_MARKER}-${id}.success"
|
|
fi
|
|
|
|
log_debug "Enhanced logging installation completed"
|
|
}
|
|
|
|
# Enhanced removal function
|
|
remove_logging() {
|
|
local id="$1"
|
|
|
|
log_debug "Removing enhanced logging for pane $id"
|
|
|
|
if is_tmux; then
|
|
local tmux_pane_ref=$(get_tmux_pane_ref)
|
|
|
|
# Stop unified verbose logging
|
|
stop_unified_verbose_logging "$tmux_pane_ref" "$id"
|
|
|
|
# Clean up command hooks (same as before)
|
|
local cleanup_script="/tmp/ops-cleanup-${id}.sh"
|
|
cat > "$cleanup_script" << 'EOF'
|
|
#!/usr/bin/env bash
|
|
set +e
|
|
|
|
if [[ -n "$BASH_VERSION" ]]; then
|
|
trap - DEBUG 2>/dev/null
|
|
if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then
|
|
PROMPT_COMMAND="$RTL_ORIG_PROMPT_COMMAND"
|
|
unset RTL_ORIG_PROMPT_COMMAND
|
|
else
|
|
unset PROMPT_COMMAND
|
|
fi
|
|
unset -f RTL_PROMPT_COMMAND RTL_log_command should_log_command RTL_preexec write_verbose_header 2>/dev/null
|
|
elif [[ -n "$ZSH_VERSION" ]]; then
|
|
add-zsh-hook -d preexec RTL_zsh_preexec 2>/dev/null
|
|
add-zsh-hook -d precmd RTL_zsh_precmd 2>/dev/null
|
|
unset -f RTL_zsh_preexec RTL_zsh_precmd RTL_log_command should_log_command write_verbose_header 2>/dev/null
|
|
fi
|
|
|
|
unset RTL_CMD_START_TIME RTL_LAST_COMMAND RTL_LAST_HISTNUM RTL_INTERNAL_LOGGING 2>/dev/null
|
|
unset CSV_LOG VERBOSE_LOG PANE_ID PUBLIC_IP VERBOSE_CMD_MARKER 2>/dev/null
|
|
|
|
echo "Enhanced Ops Logger hooks removed"
|
|
EOF
|
|
chmod +x "$cleanup_script"
|
|
|
|
tmux send-keys -t "$tmux_pane_ref" "source '$cleanup_script' 2>/dev/null; rm -f '$cleanup_script'" ENTER
|
|
|
|
# Clear window indicators
|
|
clear_window_indicators "$tmux_pane_ref"
|
|
|
|
# Clean up temp files
|
|
rm -f "/tmp/ops-hook-${id}.sh"
|
|
|
|
else
|
|
# Direct shell cleanup (same as before)
|
|
if [[ -n "$BASH_VERSION" ]]; then
|
|
trap - DEBUG 2>/dev/null
|
|
if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then
|
|
PROMPT_COMMAND="$RTL_ORIG_PROMPT_COMMAND"
|
|
unset RTL_ORIG_PROMPT_COMMAND
|
|
else
|
|
unset PROMPT_COMMAND
|
|
fi
|
|
unset -f RTL_PROMPT_COMMAND RTL_log_command should_log_command RTL_preexec write_verbose_header 2>/dev/null
|
|
elif [[ -n "$ZSH_VERSION" ]]; then
|
|
add-zsh-hook -d preexec RTL_zsh_preexec 2>/dev/null
|
|
add-zsh-hook -d precmd RTL_zsh_precmd 2>/dev/null
|
|
unset -f RTL_zsh_preexec RTL_zsh_precmd RTL_log_command should_log_command write_verbose_header 2>/dev/null
|
|
fi
|
|
|
|
echo "Direct shell logging stopped"
|
|
fi
|
|
|
|
# Remove markers
|
|
rm -f "${LOG_MARKER}-${id}" "${LOG_MARKER}-${id}.success"
|
|
rm -f "/tmp/ops-hook-${id}.sh"
|
|
|
|
log_debug "Enhanced logging removal completed"
|
|
}
|
|
|
|
# ================================================================
|
|
# RECORDING FUNCTIONS
|
|
# ================================================================
|
|
|
|
start_recording() {
|
|
local id="$1"
|
|
local target="$2"
|
|
local log_dir="$3"
|
|
local recordings_dir="${log_dir}/recordings"
|
|
local timestamp=$(date +%Y%m%d_%H%M%S)
|
|
|
|
ensure_dir "$recordings_dir"
|
|
|
|
# Check for asciinema
|
|
if ! command -v asciinema >/dev/null 2>&1; then
|
|
local error_msg="ERROR: asciinema not installed"
|
|
echo "$error_msg" >&2
|
|
echo "To install asciinema:"
|
|
echo " Ubuntu/Debian: sudo apt install asciinema"
|
|
echo " Fedora/RHEL: sudo dnf install asciinema"
|
|
echo " macOS: brew install asciinema"
|
|
echo " Pip: pip3 install asciinema"
|
|
return 1
|
|
fi
|
|
|
|
local cast_file="${recordings_dir}/${target}_${id}_${timestamp}.cast"
|
|
echo "asciinema:$cast_file" > "${RECORDING_MARKER}-${id}"
|
|
|
|
if is_tmux; then
|
|
local tmux_pane_ref=$(get_tmux_pane_ref)
|
|
|
|
# Set recording indicator (ohmytmux compatible)
|
|
set_window_recording_indicator "$tmux_pane_ref"
|
|
|
|
# Start recording cleanly
|
|
tmux send-keys -t "$tmux_pane_ref" "asciinema rec '$cast_file'" ENTER
|
|
|
|
else
|
|
echo "Starting asciinema recording. Use 'exit' or Ctrl+D to stop."
|
|
asciinema rec "$cast_file"
|
|
fi
|
|
}
|
|
|
|
stop_recording() {
|
|
local id="$1"
|
|
|
|
[[ ! -f "${RECORDING_MARKER}-${id}" ]] && {
|
|
echo "No active recording"
|
|
return 1
|
|
}
|
|
|
|
read -r rec_type rec_path < <(cat "${RECORDING_MARKER}-${id}" | tr ':' ' ')
|
|
|
|
if [[ "$rec_type" == "asciinema" ]]; then
|
|
if is_tmux; then
|
|
local tmux_pane_ref=$(get_tmux_pane_ref)
|
|
|
|
# Try sending Ctrl+D
|
|
tmux send-keys -t "$tmux_pane_ref" C-d
|
|
|
|
sleep 1
|
|
|
|
# If that didn't work, try killing the asciinema process
|
|
if ps aux | grep -v grep | grep -q "asciinema rec"; then
|
|
log_debug "Ctrl+D didn't stop recording, trying to kill asciinema process"
|
|
tmux send-keys -t "$tmux_pane_ref" "pkill -f 'asciinema rec'" ENTER
|
|
|
|
sleep 1
|
|
|
|
if ps aux | grep -v grep | grep -q "asciinema rec"; then
|
|
pkill -f "asciinema rec" || true
|
|
fi
|
|
fi
|
|
|
|
# Clear recording indicator but preserve logging indicator
|
|
local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}')
|
|
if [[ "$current_name" == "🎥"* ]]; then
|
|
if is_logging_active "$id"; then
|
|
local clean_name="${current_name#🎥 }"
|
|
tmux rename-window -t "$tmux_pane_ref" "🔴 $clean_name"
|
|
else
|
|
clear_window_indicators "$tmux_pane_ref"
|
|
fi
|
|
fi
|
|
|
|
else
|
|
echo "Recording stopped: $rec_path"
|
|
fi
|
|
fi
|
|
|
|
rm -f "${RECORDING_MARKER}-${id}"
|
|
}
|
|
|
|
# ================================================================
|
|
# CONFIGURATION FUNCTIONS
|
|
# ================================================================
|
|
|
|
create_config() {
|
|
local is_first_run="${1:-false}"
|
|
|
|
# If this is a first run from ensure_config in tmux, use the local version's approach
|
|
if [[ "$is_first_run" == "true" ]] && is_tmux; then
|
|
# Mark configuration as in progress
|
|
touch "$CONFIG_IN_PROGRESS"
|
|
|
|
# Create a temporary script that will handle the config creation
|
|
local config_script="/tmp/ops-config-$$.sh"
|
|
cat > "$config_script" << 'EOSCRIPT'
|
|
#!/usr/bin/env bash
|
|
CONFIG_FILE="${HOME}/.ops-logger.conf"
|
|
DEFAULT_TARGET="target-$(hostname | tr '.' '-')"
|
|
DEFAULT_LOG_DIR="${HOME}/OperationLogs"
|
|
|
|
echo "Red Team Terminal Logger - First Time Setup"
|
|
echo "==========================================="
|
|
echo ""
|
|
|
|
read -p "Enter target name [$DEFAULT_TARGET]: " TARGET_NAME
|
|
TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}"
|
|
|
|
read -p "Enter log directory [$DEFAULT_LOG_DIR]: " LOG_DIR
|
|
LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}"
|
|
|
|
read -p "Prompt for logging in new shells? [Y/n]: " PROMPT_NEW_SHELLS
|
|
[[ "${PROMPT_NEW_SHELLS,,}" == "n" ]] && PROMPT_NEW_SHELLS=false || PROMPT_NEW_SHELLS=true
|
|
|
|
read -p "Enable debug logging? [y/N]: " DEBUG
|
|
[[ "${DEBUG,,}" == "y" ]] && DEBUG=true || DEBUG=false
|
|
|
|
# Save config
|
|
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
cat > "$CONFIG_FILE" << EOF
|
|
# Ops Logger Configuration
|
|
TARGET_NAME="$TARGET_NAME"
|
|
LOG_DIR="$LOG_DIR"
|
|
PROMPT_NEW_SHELLS=$PROMPT_NEW_SHELLS
|
|
RECORD_INTERVAL=0.5
|
|
DEBUG=$DEBUG
|
|
EOF
|
|
|
|
# Create directories
|
|
mkdir -p "$LOG_DIR" "$LOG_DIR/verbose" "$LOG_DIR/recordings"
|
|
|
|
# Remove the in-progress marker
|
|
rm -f "/tmp/ops-logger-configuring"
|
|
|
|
echo ""
|
|
echo "Configuration saved!"
|
|
echo " Target name: $TARGET_NAME"
|
|
echo " Log directory: $LOG_DIR"
|
|
echo " Prompt new shells: $PROMPT_NEW_SHELLS"
|
|
echo " Debug mode: $DEBUG"
|
|
echo ""
|
|
echo "Now run: prefix+L to start logging (or ops-logger --start)"
|
|
echo "Press ENTER to close this window..."
|
|
read
|
|
EOSCRIPT
|
|
chmod +x "$config_script"
|
|
|
|
# Open new window for config
|
|
bash "$config_script"; rm -f "$config_script"
|
|
return 0
|
|
fi
|
|
|
|
# For manual --config or non-tmux environments (GitHub version approach)
|
|
echo "Red Team Terminal Logger - First Time Setup"
|
|
echo "==========================================="
|
|
echo ""
|
|
|
|
CONFIG_FILE="${HOME}/.ops-logger.conf"
|
|
DEFAULT_TARGET="target-$(hostname | tr '.' '-')"
|
|
DEFAULT_LOG_DIR="${HOME}/OperationLogs"
|
|
|
|
# Detect whether we're in a tty
|
|
if [[ ! -t 0 ]]; then
|
|
echo "Error: Cannot prompt user in non-interactive shell."
|
|
return 1
|
|
fi
|
|
|
|
read -p "Enter target name [$DEFAULT_TARGET]: " TARGET_NAME
|
|
TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}"
|
|
|
|
read -p "Enter log directory [$DEFAULT_LOG_DIR]: " LOG_DIR
|
|
LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}"
|
|
|
|
read -p "Prompt for logging in new shells? [Y/n]: " PROMPT_NEW_SHELLS
|
|
[[ "${PROMPT_NEW_SHELLS,,}" == "n" ]] && PROMPT_NEW_SHELLS=false || PROMPT_NEW_SHELLS=true
|
|
|
|
read -p "Enable debug logging? [y/N]: " DEBUG
|
|
[[ "${DEBUG,,}" == "y" ]] && DEBUG=true || DEBUG=false
|
|
|
|
# Save config
|
|
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
cat > "$CONFIG_FILE" <<EOF
|
|
# Ops Logger Configuration
|
|
TARGET_NAME="$TARGET_NAME"
|
|
LOG_DIR="$LOG_DIR"
|
|
PROMPT_NEW_SHELLS=$PROMPT_NEW_SHELLS
|
|
RECORD_INTERVAL=0.5
|
|
DEBUG=$DEBUG
|
|
EOF
|
|
|
|
mkdir -p "$LOG_DIR" "$LOG_DIR/verbose" "$LOG_DIR/recordings"
|
|
|
|
echo ""
|
|
echo "Configuration saved!"
|
|
echo " Target name: $TARGET_NAME"
|
|
echo " Log directory: $LOG_DIR"
|
|
echo " Prompt new shells: $PROMPT_NEW_SHELLS"
|
|
echo " Debug mode: $DEBUG"
|
|
echo ""
|
|
echo "Now run: prefix+L to start logging (or ops-logger --start)"
|
|
}
|
|
|
|
# Keep the ensure_config from local version
|
|
ensure_config() {
|
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
create_config "true"
|
|
# If configuration is in progress, abort the current operation
|
|
if [[ -f "$CONFIG_IN_PROGRESS" ]]; then
|
|
return 1
|
|
fi
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ================================================================
|
|
# MAIN USER FUNCTIONS
|
|
# ================================================================
|
|
|
|
start_logging() {
|
|
# Ensure we output to terminal if run from tmux keybinding
|
|
if is_tmux && [[ -t 1 ]]; then
|
|
local tty=$(tty 2>/dev/null || echo "/dev/tty")
|
|
exec 1>$tty 2>$tty
|
|
fi
|
|
|
|
# Auto-setup config with prompts on first logging start
|
|
ensure_config || return 1
|
|
load_config
|
|
|
|
local id=$(get_pane_id)
|
|
is_logging_active "$id" && {
|
|
echo "Already logging"
|
|
return 0
|
|
}
|
|
|
|
local csv_log=$(setup_csv_log "$TARGET_NAME" "$LOG_DIR")
|
|
|
|
install_logging "$id" "$csv_log" "$TARGET_NAME" "$LOG_DIR"
|
|
}
|
|
|
|
stop_logging() {
|
|
# Ensure we output to terminal if run from tmux keybinding
|
|
if is_tmux && [[ -t 1 ]]; then
|
|
local tty=$(tty 2>/dev/null || echo "/dev/tty")
|
|
exec 1>$tty 2>$tty
|
|
fi
|
|
|
|
local id=$(get_pane_id)
|
|
is_logging_active "$id" || {
|
|
echo "Not logging"
|
|
return 0
|
|
}
|
|
|
|
remove_logging "$id"
|
|
}
|
|
|
|
toggle_logging() {
|
|
# Ensure we output to terminal if run from tmux keybinding
|
|
if is_tmux && [[ -t 1 ]]; then
|
|
local tty=$(tty 2>/dev/null || echo "/dev/tty")
|
|
exec 1>$tty 2>$tty
|
|
fi
|
|
|
|
local id=$(get_pane_id)
|
|
is_logging_active "$id" && stop_logging || start_logging
|
|
}
|
|
|
|
toggle_recording() {
|
|
# Ensure we output to terminal if run from tmux keybinding
|
|
if is_tmux && [[ -t 1 ]]; then
|
|
local tty=$(tty 2>/dev/null || echo "/dev/tty")
|
|
exec 1>$tty 2>$tty
|
|
fi
|
|
|
|
local id=$(get_pane_id)
|
|
# Load existing config or use defaults
|
|
[[ -f "$CONFIG_FILE" ]] && load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; }
|
|
|
|
is_recording_active "$id" && stop_recording "$id" || start_recording "$id" "$TARGET_NAME" "$LOG_DIR"
|
|
}
|
|
|
|
prompt_for_logging() {
|
|
local id
|
|
id=$(get_pane_id)
|
|
|
|
# ✅ Debug logging to see what's happening
|
|
echo "[$(date)] prompt_for_logging called for pane: $id" >> /tmp/ops-logger-prompt-debug.log
|
|
echo "[$(date)] TMUX_PANE: ${TMUX_PANE:-'not set'}" >> /tmp/ops-logger-prompt-debug.log
|
|
|
|
# ✅ Avoid recursion with a pane-specific lock - BUT with timeout
|
|
local lock_file="/tmp/ops-logger-prompted-${id}"
|
|
if [[ -f "$lock_file" ]]; then
|
|
# Check if lock file is stale (older than 30 seconds)
|
|
if [[ $(find "$lock_file" -mmin +0.5 2>/dev/null) ]]; then
|
|
echo "[$(date)] Removing stale lock file" >> /tmp/ops-logger-prompt-debug.log
|
|
rm -f "$lock_file"
|
|
else
|
|
echo "[$(date)] Recent lock file exists, returning" >> /tmp/ops-logger-prompt-debug.log
|
|
return 0
|
|
fi
|
|
fi
|
|
touch "$lock_file"
|
|
|
|
# ✅ Cleanup function to ensure lock file is always removed
|
|
cleanup_lock() {
|
|
rm -f "$lock_file"
|
|
echo "[$(date)] Lock file cleaned up" >> /tmp/ops-logger-prompt-debug.log
|
|
}
|
|
trap cleanup_lock EXIT
|
|
|
|
is_logging_active "$id" && {
|
|
echo "[$(date)] Already logging, returning" >> /tmp/ops-logger-prompt-debug.log
|
|
cleanup_lock
|
|
return 0
|
|
}
|
|
|
|
load_config
|
|
echo "[$(date)] Config loaded - PROMPT_NEW_SHELLS: $PROMPT_NEW_SHELLS" >> /tmp/ops-logger-prompt-debug.log
|
|
|
|
[[ "$PROMPT_NEW_SHELLS" != "true" ]] && {
|
|
echo "[$(date)] Prompting disabled, returning" >> /tmp/ops-logger-prompt-debug.log
|
|
cleanup_lock
|
|
return 0
|
|
}
|
|
|
|
# ✅ FIXED: Use tmux display-popup for proper interaction in tmux
|
|
if is_tmux; then
|
|
echo "[$(date)] In tmux, creating popup" >> /tmp/ops-logger-prompt-debug.log
|
|
|
|
# Create a temporary script for the popup
|
|
local popup_script="/tmp/ops-prompt-${id}.sh"
|
|
local script_path=$(readlink -f "$0")
|
|
|
|
cat > "$popup_script" << 'EOPOPUP'
|
|
#!/bin/bash
|
|
echo "Start logging this shell?"
|
|
echo ""
|
|
echo " [Y]es - Start logging"
|
|
echo " [N]o - Skip logging"
|
|
echo ""
|
|
read -n 1 -p "Choice [Y/n]: " response
|
|
echo ""
|
|
|
|
if [[ -z "$response" || "${response,,}" == "y" ]]; then
|
|
echo "Starting logging..."
|
|
__SCRIPT_PATH__ --start
|
|
echo "Logging started!"
|
|
sleep 2
|
|
else
|
|
echo "Logging skipped."
|
|
sleep 1
|
|
fi
|
|
EOPOPUP
|
|
|
|
# Replace placeholder with actual script path
|
|
sed -i "s|__SCRIPT_PATH__|$script_path|g" "$popup_script"
|
|
chmod +x "$popup_script"
|
|
|
|
echo "[$(date)] Popup script created, calling display-popup" >> /tmp/ops-logger-prompt-debug.log
|
|
|
|
# Use tmux display-popup for proper interaction
|
|
tmux display-popup -E -w 50 -h 10 -T "Ops Logger" "bash '$popup_script'; rm -f '$popup_script'"
|
|
|
|
echo "[$(date)] display-popup completed" >> /tmp/ops-logger-prompt-debug.log
|
|
|
|
else
|
|
echo "[$(date)] Not in tmux, using direct prompt" >> /tmp/ops-logger-prompt-debug.log
|
|
|
|
# Direct shell - standard approach
|
|
echo "Start logging this shell?"
|
|
echo ""
|
|
echo " [Y]es - Start logging"
|
|
echo " [N]o - Skip logging"
|
|
echo ""
|
|
|
|
if read -t 10 -n 1 -p "Choice [Y/n]: " response; then
|
|
echo ""
|
|
if [[ -z "$response" || "${response,,}" == "y" ]]; then
|
|
start_logging
|
|
echo "Logging started!"
|
|
else
|
|
echo "Logging skipped."
|
|
fi
|
|
else
|
|
echo ""
|
|
echo "Timeout - logging skipped."
|
|
fi
|
|
fi
|
|
|
|
# Clean up will be handled by trap
|
|
echo "[$(date)] prompt_for_logging completed" >> /tmp/ops-logger-prompt-debug.log
|
|
}
|
|
|
|
# ================================================================
|
|
# TMUX INTEGRATION
|
|
# ================================================================
|
|
|
|
install_tmux_keys() {
|
|
load_config
|
|
|
|
if is_tmux; then
|
|
local script_path
|
|
script_path=$(readlink -f "$0")
|
|
|
|
# Bind keys
|
|
tmux bind-key L run-shell "bash -c '[ -f ~/.ops-logger.conf ] && tmux run-shell \"$script_path --toggle\" || tmux display-popup -E -w 80% -h 60% -T \"Ops Logger Config\" \"bash $script_path --toggle\"'"
|
|
tmux bind-key R run-shell "'$script_path' --toggle-recording"
|
|
|
|
# ✅ FIXED: Create a wrapper script that exports the pane context
|
|
if [[ "$PROMPT_NEW_SHELLS" == "true" ]]; then
|
|
local hook_wrapper="/tmp/ops-logger-pane-wrapper.sh"
|
|
cat > "$hook_wrapper" << EOWRAPPER
|
|
#!/bin/bash
|
|
# Wrapper that sets pane context and calls the prompt
|
|
|
|
# Sleep to let pane initialize
|
|
sleep 1
|
|
|
|
# Check config
|
|
if [ -f ~/.ops-logger.conf ]; then
|
|
source ~/.ops-logger.conf
|
|
if [ "\$PROMPT_NEW_SHELLS" = "true" ]; then
|
|
# Get pane info in tmux context
|
|
PANE_ID=\$(tmux display-message -p "#{session_name}-#{window_index}-#{pane_index}")
|
|
|
|
# Check if already logging
|
|
if [ ! -f "/tmp/ops-logger-active-\$PANE_ID" ]; then
|
|
# Export the tmux pane reference for the script to use
|
|
export TMUX_PANE=\$(tmux display-message -p "#{pane_id}")
|
|
|
|
# Call the script with the pane context
|
|
$script_path --prompt
|
|
fi
|
|
fi
|
|
fi
|
|
EOWRAPPER
|
|
chmod +x "$hook_wrapper"
|
|
|
|
tmux set-hook -g after-new-window "run-shell '$hook_wrapper'"
|
|
tmux set-hook -g after-split-window "run-shell '$hook_wrapper'"
|
|
echo "Hooks installed for new windows/panes"
|
|
fi
|
|
|
|
echo "Keys installed: $(get_tmux_prefix)+L (logging), $(get_tmux_prefix)+R (recording)"
|
|
log_debug "Tmux key bindings installed"
|
|
else
|
|
echo "Not in tmux, no keys installed"
|
|
fi
|
|
}
|
|
|
|
uninstall_tmux_keys() {
|
|
if is_tmux; then
|
|
tmux unbind-key L 2>/dev/null
|
|
tmux unbind-key R 2>/dev/null
|
|
tmux set-hook -gu after-new-window 2>/dev/null
|
|
tmux set-hook -gu after-split-window 2>/dev/null
|
|
echo "Tmux keys removed"
|
|
log_debug "Tmux key bindings removed"
|
|
|
|
# Remove our hook scripts
|
|
rm -f /tmp/ops-logger-hook-wrapper.sh 2>/dev/null
|
|
rm -f /tmp/ops-logger-tmux-hook.sh 2>/dev/null
|
|
rm -f /tmp/ops-logger-pane-wrapper.sh 2>/dev/null
|
|
fi
|
|
|
|
# Also remove config file as user expected
|
|
load_config
|
|
local log_dir_for_cleanup="$LOG_DIR"
|
|
|
|
# Remove config
|
|
[[ -f "$CONFIG_FILE" ]] && rm -f "$CONFIG_FILE"
|
|
|
|
# Remove debug log
|
|
[[ -f "${log_dir_for_cleanup}/ops-logger-debug.log" ]] && rm -f "${log_dir_for_cleanup}/ops-logger-debug.log"
|
|
|
|
echo "Configuration removed"
|
|
}
|
|
|
|
uninstall_all() {
|
|
local id=$(get_pane_id)
|
|
|
|
log_debug "Starting complete uninstall"
|
|
|
|
# Stop active sessions
|
|
is_logging_active "$id" && stop_logging
|
|
is_recording_active "$id" && stop_recording "$id"
|
|
|
|
# Remove tmux integration and config
|
|
is_tmux && uninstall_tmux_keys
|
|
|
|
# Clean up temp files
|
|
rm -f /tmp/ops-* 2>/dev/null
|
|
|
|
echo "OpsLogger uninstalled completely"
|
|
}
|
|
|
|
show_status() {
|
|
# Ensure we output to terminal if run from tmux keybinding
|
|
if is_tmux && [[ -t 1 ]]; then
|
|
local tty=$(tty 2>/dev/null || echo "/dev/tty")
|
|
exec 1>$tty 2>$tty
|
|
fi
|
|
|
|
load_config
|
|
local id=$(get_pane_id)
|
|
|
|
echo "Red Team Terminal Logger Status:"
|
|
echo "==============================="
|
|
echo "Target: $TARGET_NAME"
|
|
echo "Log directory: $LOG_DIR"
|
|
echo "Debug mode: $DEBUG"
|
|
echo "Logging: $(is_logging_active "$id" && echo "ACTIVE" || echo "INACTIVE")"
|
|
echo "Recording: $(is_recording_active "$id" && echo "ACTIVE" || echo "INACTIVE")"
|
|
echo "Public IP: $(get_public_ip)"
|
|
echo "Asciinema: $(command -v asciinema >/dev/null 2>&1 && echo "INSTALLED" || echo "NOT INSTALLED")"
|
|
|
|
if is_tmux; then
|
|
echo "Environment: tmux ($(tmux display -p '#{session_name}'))"
|
|
echo "Tmux prefix: $(get_tmux_prefix)"
|
|
echo "ohmytmux: $(tmux show-options -g | grep -q "TMUX_CONF" && echo "DETECTED" || echo "NOT DETECTED")"
|
|
echo "Normalized Pane ID: $id"
|
|
|
|
# Check plugin installation status
|
|
echo "TPM: $(check_tpm_installed && echo "INSTALLED" || echo "NOT INSTALLED")"
|
|
echo "tmux-logging plugin: $(check_tmux_logging_installed && echo "INSTALLED" || echo "NOT INSTALLED")"
|
|
|
|
# Check if there's active pipe-pane logging
|
|
echo "Active logging pipe: $(tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$(get_tmux_pane_ref).*1" && echo "YES" || echo "NO")"
|
|
else
|
|
echo "Environment: direct shell"
|
|
fi
|
|
|
|
# Show current log files
|
|
local csv_log="${LOG_DIR}/${TARGET_NAME}_commands_$(date +%Y-%m-%d).csv"
|
|
|
|
[[ -f "$csv_log" ]] && {
|
|
echo "Current CSV log: $csv_log"
|
|
echo "Commands logged: $(($(wc -l < "$csv_log") - 1))"
|
|
}
|
|
|
|
# Show verbose logs with headers
|
|
local verbose_dir="${LOG_DIR}/verbose"
|
|
if [[ -d "$verbose_dir" ]]; then
|
|
local verbose_files=$(find "$verbose_dir" -name "*${TARGET_NAME}*.log" -type f 2>/dev/null | wc -l)
|
|
echo "Verbose log files: $verbose_files in $verbose_dir"
|
|
if [[ "$verbose_files" -gt 0 ]]; then
|
|
local latest_verbose=$(find "$verbose_dir" -name "*${TARGET_NAME}*.log" -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2-)
|
|
[[ -n "$latest_verbose" ]] && echo "Latest verbose log: $latest_verbose ($(du -h "$latest_verbose" | cut -f1))"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
show_help() {
|
|
echo "Red Team Terminal Logger - Professional Solution v2.5.2"
|
|
echo "========================================================"
|
|
echo "USAGE: $0 [OPTIONS]"
|
|
echo ""
|
|
echo "LOGGING CONTROLS:"
|
|
echo " --start Start command/verbose logging"
|
|
echo " --stop Stop logging"
|
|
echo " --toggle Toggle logging on/off"
|
|
echo ""
|
|
echo "RECORDING CONTROLS:"
|
|
echo " --start-recording Start terminal recording (requires asciinema)"
|
|
echo " --stop-recording Stop recording"
|
|
echo " --toggle-recording Toggle recording on/off"
|
|
echo ""
|
|
echo "SETUP & CONFIGURATION:"
|
|
echo " --prompt Show logging prompt (for new shells)"
|
|
echo " --install Install tmux keybindings"
|
|
echo " --uninstall Remove tmux keybindings and config"
|
|
echo " --uninstall-all Complete removal (same as --uninstall)"
|
|
echo " --config Configure settings"
|
|
echo " --save-config Save config (internal use)"
|
|
echo ""
|
|
echo "INFO:"
|
|
echo " --status Show current status"
|
|
echo " --help Show this help"
|
|
echo ""
|
|
echo "DEBUG:"
|
|
echo " --debug-on Enable debug logging"
|
|
echo " --debug-off Disable debug logging"
|
|
echo ""
|
|
echo "TMUX INTEGRATION:"
|
|
echo " Keys: prefix+L (toggle logging), prefix+R (recording)"
|
|
echo " Compatible with ohmytmux themes and window naming"
|
|
echo " Auto-prompts for new windows/panes (configurable)"
|
|
echo ""
|
|
echo "VERSION 2.5.2 FIXES:"
|
|
echo " - FIXED: Verbose logs now include formatted command headers"
|
|
echo " - FIXED: Configuration uses interactive tmux windows"
|
|
echo " - FIXED: Command metadata properly captured and formatted"
|
|
echo " - IMPROVED: Better separation of capture and formatting"
|
|
}
|
|
|
|
# Main command handler with output handling
|
|
main() {
|
|
# Always ensure direct terminal output for tmux operations
|
|
if is_tmux && [[ -t 1 ]]; then
|
|
local tty=$(tty 2>/dev/null || echo "/dev/tty")
|
|
exec 1>$tty 2>$tty
|
|
fi
|
|
|
|
case "$1" in
|
|
--start) start_logging ;;
|
|
--stop) stop_logging ;;
|
|
--toggle) toggle_logging ;;
|
|
--start-recording) [[ -f "$CONFIG_FILE" ]] && load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; }; start_recording "$(get_pane_id)" "$TARGET_NAME" "$LOG_DIR" ;;
|
|
--stop-recording) stop_recording "$(get_pane_id)" ;;
|
|
--toggle-recording) toggle_recording ;;
|
|
--prompt) prompt_for_logging ;;
|
|
--install) install_tmux_keys ;;
|
|
--uninstall) uninstall_tmux_keys ;;
|
|
--uninstall-all) uninstall_all ;;
|
|
--config) create_config ;;
|
|
--save-config) save_config_from_tmux ;;
|
|
--status) show_status ;;
|
|
--debug-pipe)
|
|
load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; }
|
|
local id=$(get_pane_id)
|
|
local tmux_pane_ref=$(get_tmux_pane_ref)
|
|
start_debug_verbose_logging "$id" "$tmux_pane_ref" "$TARGET_NAME" "$LOG_DIR"
|
|
;;
|
|
--debug-on) [[ -f "$CONFIG_FILE" ]] && { load_config; DEBUG=true; save_config; } || echo "Run --config first to set up configuration"; echo "Debug logging enabled" ;;
|
|
--debug-off) [[ -f "$CONFIG_FILE" ]] && { load_config; DEBUG=false; save_config; } || echo "Run --config first to set up configuration"; echo "Debug logging disabled" ;;
|
|
--help|'') show_help ;;
|
|
*) echo "Unknown option: $1. Use --help" ;;
|
|
esac
|
|
}
|
|
|
|
# ShellOpsLog compatibility layer
|
|
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
|
|
export REDTEAM_LOGGER_PATH="${BASH_SOURCE[0]}"
|
|
|
|
start_operation_log() {
|
|
local auto_start=0
|
|
local log_dir="$HOME/OperationLogs"
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
-AutoStart|-autostart) auto_start=1; shift ;;
|
|
*) log_dir="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE"
|
|
|
|
LOG_DIR="$log_dir"
|
|
TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}"
|
|
save_config
|
|
|
|
if [[ "$auto_start" -eq 1 ]]; then
|
|
"$REDTEAM_LOGGER_PATH" --start
|
|
else
|
|
"$REDTEAM_LOGGER_PATH" --prompt
|
|
fi
|
|
}
|
|
|
|
stop_operation_log() {
|
|
"$REDTEAM_LOGGER_PATH" --stop
|
|
}
|
|
|
|
log_debug "ShellOpsLog compatibility layer loaded"
|
|
else
|
|
# Script is being executed directly
|
|
main "$@"
|
|
fi
|