| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- #!/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 <<EOF
- Usage: $0 [OPTIONS] [COMMAND|FILTERS...]
- COMMANDS:
- push Commit, scan for secrets, and push to git origin
- deps, check Check for required dependencies
- help Show this help message
- SCRIPT EXECUTION:
- [FILTERS...] Run scripts matching filters from runs/ directory
- OPTIONS:
- --dry Show what would run without executing
- --verbose Show detailed output during execution
- EXAMPLES:
- $0 deps # Check dependencies
- $0 push # Secure git push with secret scanning
- $0 docker # Run scripts containing 'docker'
- $0 --dry base # Show what base scripts would run
- $0 --verbose all # Run all scripts with detailed output
- SECURITY:
- The push command automatically scans for secrets using GitLeaks
- before pushing to prevent accidental credential exposure.
- EOF
- }
- get_description() {
- local script="$1"
- grep '^# NAME:' "$script" 2>/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
|