377 lines
13 KiB
Bash
Executable file
377 lines
13 KiB
Bash
Executable file
#!/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
|