#!/usr/bin/env bash set -euo pipefail # ═══════════════════════════════════════════════════════════ # 🔧 SETUP # ═══════════════════════════════════════════════════════════ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="$SCRIPT_DIR/.env" RUNS_DIR="$SCRIPT_DIR/runs" LOGS_DIR="$SCRIPT_DIR/logs" # Colors RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' YELLOW='\033[0;33m' NC='\033[0m' # ═══════════════════════════════════════════════════════════ # 🛠️ HELPERS # ═══════════════════════════════════════════════════════════ log() { echo -e "${GREEN}[RUN]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } # ═══════════════════════════════════════════════════════════ # 🔍 DEPENDENCY VALIDATION # ═══════════════════════════════════════════════════════════ check_dependencies() { local missing=() # Required tools local required_tools=("git" "find" "grep") # Optional but recommended tools local optional_tools=("gitleaks") log "Checking dependencies..." # Check required tools for tool in "${required_tools[@]}"; do if ! command -v "$tool" &>/dev/null; then missing+=("$tool") fi done # Check optional tools for tool in "${optional_tools[@]}"; do if ! command -v "$tool" &>/dev/null; then warn "Optional tool missing: $tool (recommended for security scanning)" else log "✓ Found: $tool" fi done # Report missing required tools if [[ ${#missing[@]} -gt 0 ]]; then error "Missing required tools: ${missing[*]}" error "Please install missing dependencies" exit 1 fi log "✓ All required dependencies found" } # ═══════════════════════════════════════════════════════════ # 🔒 SECURITY SCANNING # ═══════════════════════════════════════════════════════════ run_security_scan() { log "Running security scan..." if command -v gitleaks &>/dev/null; then log "Using GitLeaks for secret detection..." if gitleaks detect --verbose --exit-code 1; then log "✅ No secrets detected" return 0 else error "❌ Secrets detected! Review before pushing." return 1 fi else warn "GitLeaks not installed - skipping security scan" warn "Install with: paru -S gitleaks" echo read -p "Continue without security scan? (y/N): " -r answer if [[ ! "$answer" =~ ^[Yy]$ ]]; then error "Push cancelled for security" exit 1 fi return 0 fi } # ═══════════════════════════════════════════════════════════ # 📤 PUSH COMMAND # ═══════════════════════════════════════════════════════════ handle_push() { log "Preparing to push repository..." # Check if we're in a git repository if ! git rev-parse --git-dir &>/dev/null; then error "Not in a git repository" exit 1 fi # Check for uncommitted changes if ! git diff-index --quiet HEAD --; then warn "You have uncommitted changes" echo git status --short echo # Generate default commit message local default_msg="dev: automated commit - $(date '+%Y-%m-%d %H:%M:%S')" read -p "Commit all changes? (Y/n): " -r answer if [[ ! "$answer" =~ ^[Nn]$ ]]; then echo echo "Default: $default_msg" read -p "Custom commit message (or press Enter for default): " -r commit_msg # Use default if empty if [[ -z "$commit_msg" ]]; then commit_msg="$default_msg" fi git add . git commit -m "$commit_msg" log "✓ Changes committed: $commit_msg" fi fi # Run security scan if ! run_security_scan; then exit 1 fi # Get current branch local current_branch current_branch=$(git branch --show-current) # Push to origin log "Pushing branch '$current_branch' to origin..." if git push origin "$current_branch"; then log "✅ Successfully pushed to origin/$current_branch" else error "❌ Push failed" exit 1 fi } show_help() { cat </dev/null | cut -d: -f2- | sed 's/^ *//' || echo "No description" } matches_filters() { local script_name="$1" shift local filters=("$@") [[ ${#filters[@]} -eq 0 ]] && return 0 for filter in "${filters[@]}"; do if [[ "$script_name" == *"$filter"* ]] || [[ "${script_name,,}" == *"${filter,,}"* ]]; then return 0 fi done return 1 } # ═══════════════════════════════════════════════════════════ # 🚦 INITIALIZATION # ═══════════════════════════════════════════════════════════ # Create runs directory if missing if [[ ! -d "$RUNS_DIR" ]]; then log "Creating runs directory at: $RUNS_DIR" mkdir -p "$RUNS_DIR" # Create example script cat >"$RUNS_DIR/example.sh" <<'EOF' #!/usr/bin/env bash # NAME: Example script echo "Hello from runs/example.sh!" echo "Edit this script or add your own to $RUNS_DIR" EOF chmod +x "$RUNS_DIR/example.sh" log "✅ Created runs/ directory with example script" exit 0 fi # Create logs directory mkdir -p "$LOGS_DIR" # Load environment [[ -f "$ENV_FILE" ]] && source "$ENV_FILE" # ═══════════════════════════════════════════════════════════ # 📝 PARSE ARGUMENTS # ═══════════════════════════════════════════════════════════ DRY_RUN=0 VERBOSE=0 FILTERS=() COMMAND="" while [[ $# -gt 0 ]]; do case "$1" in --dry) DRY_RUN=1 ;; --verbose) VERBOSE=1 ;; --help | help) show_help exit 0 ;; push) COMMAND="push" ;; deps | check) COMMAND="deps" ;; -*) error "Unknown option: $1" show_help exit 1 ;; *) FILTERS+=("$1") ;; esac shift done # ═══════════════════════════════════════════════════════════ # 🚦 HANDLE COMMANDS # ═══════════════════════════════════════════════════════════ # Handle special commands first case "$COMMAND" in "push") handle_push exit 0 ;; "deps") check_dependencies exit 0 ;; "") # No command, continue with script execution ;; *) error "Unknown command: $COMMAND" show_help exit 1 ;; esac # ═══════════════════════════════════════════════════════════ # 🔍 FIND SCRIPTS TO RUN # ═══════════════════════════════════════════════════════════ mapfile -t ALL_SCRIPTS < <(find "$RUNS_DIR" -type f -executable | sort) if [[ ${#ALL_SCRIPTS[@]} -eq 0 ]]; then warn "No executable scripts found in $RUNS_DIR" exit 0 fi SCRIPTS_TO_RUN=() for script in "${ALL_SCRIPTS[@]}"; do script_name="$(basename "$script")" if matches_filters "$script_name" "${FILTERS[@]}"; then SCRIPTS_TO_RUN+=("$script") fi done if [[ ${#SCRIPTS_TO_RUN[@]} -eq 0 ]]; then warn "No scripts match the given filters: ${FILTERS[*]}" exit 0 fi # ═══════════════════════════════════════════════════════════ # ⚠️ CONFIRMATION FOR RUNNING ALL SCRIPTS # ═══════════════════════════════════════════════════════════ if [[ ${#FILTERS[@]} -eq 0 && $DRY_RUN -eq 0 ]]; then echo -e "${RED}⚠️ About to run ALL scripts:${NC}" for script in "${SCRIPTS_TO_RUN[@]}"; do name="$(basename "$script")" desc="$(get_description "$script")" echo " • $name - $desc" done echo read -p "Continue? (y/N): " -r answer if [[ ! "$answer" =~ ^[Yy]$ ]]; then echo "Cancelled" exit 0 fi fi # ═══════════════════════════════════════════════════════════ # 🚀 EXECUTE SCRIPTS # ═══════════════════════════════════════════════════════════ EXECUTED=() FAILED=() for script in "${SCRIPTS_TO_RUN[@]}"; do name="$(basename "$script")" desc="$(get_description "$script")" log_file="$LOGS_DIR/${name%.*}.log" if [[ $DRY_RUN -eq 1 ]]; then echo -e "${BLUE}[DRY]${NC} Would run: $name - $desc" continue fi echo -e "\n${BLUE}▶${NC} Running: $name" [[ "$desc" != "No description" ]] && echo " $desc" if [[ $VERBOSE -eq 1 ]]; then if "$script" 2>&1 | tee "$log_file"; then log "✅ $name completed" EXECUTED+=("$name") else error "❌ $name failed (see $log_file)" FAILED+=("$name") fi else if "$script" &>"$log_file"; then log "✅ $name completed" EXECUTED+=("$name") else error "❌ $name failed (see $log_file)" FAILED+=("$name") fi fi done # ═══════════════════════════════════════════════════════════ # 📊 SUMMARY # ═══════════════════════════════════════════════════════════ if [[ $DRY_RUN -eq 0 ]]; then echo -e "\n${BLUE}═══ SUMMARY ═══${NC}" echo "✅ Completed: ${#EXECUTED[@]}" [[ ${#FAILED[@]} -gt 0 ]] && echo "❌ Failed: ${#FAILED[@]}" if [[ ${#FAILED[@]} -gt 0 ]]; then echo -e "\n${RED}Failed scripts:${NC}" for failed in "${FAILED[@]}"; do echo " • $failed" done exit 1 fi fi exit 0