From f037255a1ad957c2c1ab3983390ae4fe645968f4 Mon Sep 17 00:00:00 2001 From: "Mariano Z." Date: Sat, 31 May 2025 17:04:49 -0300 Subject: [PATCH] dev: automated commit - 2025-05-31 17:04:45 --- .gitignore | 1 + README.md | 48 ++++ dev | 46 ++++ go.mod | 3 + main.go | 677 +++++++++++++++++++++++++++++++++++++++++++++++++++++ run | 377 ----------------------------- runs/test | 8 + 7 files changed, 783 insertions(+), 377 deletions(-) create mode 100644 README.md create mode 100755 dev create mode 100644 go.mod create mode 100644 main.go delete mode 100755 run create mode 100755 runs/test diff --git a/.gitignore b/.gitignore index c75dc7c..c2a5267 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ logs/* +bin/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c888d8 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Dev Tool + +Simple development script runner that builds itself on first use. + +## Quick Start + +```bash +git clone https://git.mz.uy/marianozunino/dev +cd dev +./dev +``` + +## Usage + +```bash +./dev run # Run all scripts +./dev run docker # Run scripts matching "docker" +./dev ls # List available scripts +./dev new backup # Create new script template +./dev push # Git push with security scan +``` + +## How It Works + +The `./dev` script: + +1. Detects your Linux architecture (amd64/arm64) +2. Builds `bin/dev-linux-{arch}` if needed +3. Runs your command + +## Requirements + +- Linux (any distro) +- Go installed: `sudo pacman -S go` (or your distro's equivalent) + +## File Structure + +``` +project/ +├── dev* # Launcher script +├── main.go # Source code +├── Makefile # Build helper +├── bin/ # Built binaries +├── runs/ # Your scripts +└── logs/ # Execution logs +``` + +That's it! Clone and run `./dev` to get started. diff --git a/dev b/dev new file mode 100755 index 0000000..9a37344 --- /dev/null +++ b/dev @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Simple dev tool launcher - builds if needed + +set -euo pipefail + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +log() { echo -e "${GREEN}[DEV]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +# Simple platform detection +ARCH=$(uname -m) +case $ARCH in +x86_64) ARCH="amd64" ;; +arm64 | aarch64) ARCH="arm64" ;; +*) + error "Unsupported arch: $ARCH" + exit 1 + ;; +esac + +BINARY="bin/dev-linux-$ARCH" + +# Build if binary doesn't exist or source is newer +if [[ ! -f "$BINARY" ]] || [[ "main.go" -nt "$BINARY" ]]; then + if ! command -v go &>/dev/null; then + error "Go not installed. Install with: sudo pacman -S go" + exit 1 + fi + + log "Building dev tool..." + mkdir -p bin + + if go build -ldflags="-s -w" -o "$BINARY" main.go; then + log "✅ Built: $BINARY" + else + error "Build failed" + exit 1 + fi +fi + +# Run the binary +exec "$BINARY" "$@" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a4d0d1a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module marianozunino/dev + +go 1.24.1 diff --git a/main.go b/main.go new file mode 100644 index 0000000..cc963d4 --- /dev/null +++ b/main.go @@ -0,0 +1,677 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + "time" +) + +// Colors for terminal output +const ( + Red = "\033[0;31m" + Green = "\033[0;32m" + Blue = "\033[0;34m" + Yellow = "\033[0;33m" + NC = "\033[0m" // No Color +) + +// Global configuration +type Config struct { + ScriptDir string + EnvFile string + RunsDir string + LogsDir string + DryRun bool + Verbose bool + Filters []string + Command string +} + +// Script represents an executable script +type Script struct { + Path string + Name string + Description string +} + +// ExecutionResult tracks script execution outcomes +type ExecutionResult struct { + Executed []string + Failed []string +} + +// Logging functions +func log(msg string, args ...interface{}) { + fmt.Printf(Green+"[RUN]"+NC+" "+msg+"\n", args...) +} + +func warn(msg string, args ...interface{}) { + fmt.Printf(Yellow+"[WARN]"+NC+" "+msg+"\n", args...) +} + +func errorLog(msg string, args ...interface{}) { + fmt.Fprintf(os.Stderr, Red+"[ERROR]"+NC+" "+msg+"\n", args...) +} + +// Check if command exists in PATH +func commandExists(cmd string) bool { + _, err := exec.LookPath(cmd) + return err == nil +} + +// Check for required and optional dependencies +func checkDependencies() error { + requiredTools := []string{"git", "find", "grep"} + optionalTools := []string{"gitleaks"} + var missing []string + + log("Checking dependencies...") + + // Check required tools + for _, tool := range requiredTools { + if !commandExists(tool) { + missing = append(missing, tool) + } + } + + // Check optional tools + for _, tool := range optionalTools { + if !commandExists(tool) { + warn("Optional tool missing: %s (recommended for security scanning)", tool) + } else { + log("✓ Found: %s", tool) + } + } + + // Report missing required tools + if len(missing) > 0 { + errorLog("Missing required tools: %s", strings.Join(missing, ", ")) + errorLog("Please install missing dependencies") + return fmt.Errorf("missing dependencies") + } + + log("✓ All required dependencies found") + return nil +} + +// Run security scan using gitleaks +func runSecurityScan() error { + log("Running security scan...") + + if !commandExists("gitleaks") { + warn("GitLeaks not installed - skipping security scan") + warn("Install with: paru -S gitleaks") + fmt.Println() + + fmt.Print("Continue without security scan? (y/N): ") + reader := bufio.NewReader(os.Stdin) + answer, _ := reader.ReadString('\n') + answer = strings.TrimSpace(strings.ToLower(answer)) + + if answer != "y" && answer != "yes" { + errorLog("Push cancelled for security") + return fmt.Errorf("security scan cancelled") + } + return nil + } + + log("Using GitLeaks for secret detection...") + cmd := exec.Command("gitleaks", "detect", "--verbose", "--exit-code", "1") + + if err := cmd.Run(); err != nil { + errorLog("❌ Secrets detected! Review before pushing.") + return fmt.Errorf("secrets detected") + } + + log("✅ No secrets detected") + return nil +} + +// Check if we're in a git repository +func isGitRepo() bool { + cmd := exec.Command("git", "rev-parse", "--git-dir") + return cmd.Run() == nil +} + +// Check for uncommitted changes +func hasUncommittedChanges() bool { + cmd := exec.Command("git", "diff-index", "--quiet", "HEAD", "--") + return cmd.Run() != nil +} + +// Get current git branch +func getCurrentBranch() (string, error) { + cmd := exec.Command("git", "branch", "--show-current") + output, err := cmd.Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(output)), nil +} + +// Handle git push workflow +func handlePush() error { + log("Preparing to push repository...") + + // Check if we're in a git repository + if !isGitRepo() { + errorLog("Not in a git repository") + return fmt.Errorf("not in git repo") + } + + // Check for uncommitted changes + if hasUncommittedChanges() { + warn("You have uncommitted changes") + fmt.Println() + + // Show git status + cmd := exec.Command("git", "status", "--short") + cmd.Stdout = os.Stdout + cmd.Run() + fmt.Println() + + // Generate default commit message + defaultMsg := fmt.Sprintf("dev: automated commit - %s", time.Now().Format("2006-01-02 15:04:05")) + + fmt.Print("Commit all changes? (Y/n): ") + reader := bufio.NewReader(os.Stdin) + answer, _ := reader.ReadString('\n') + answer = strings.TrimSpace(strings.ToLower(answer)) + + if answer != "n" && answer != "no" { + fmt.Println() + fmt.Printf("Default: %s\n", defaultMsg) + fmt.Print("Custom commit message (or press Enter for default): ") + + commitMsg, _ := reader.ReadString('\n') + commitMsg = strings.TrimSpace(commitMsg) + + if commitMsg == "" { + commitMsg = defaultMsg + } + + // Add and commit changes + if err := exec.Command("git", "add", ".").Run(); err != nil { + return fmt.Errorf("failed to add changes: %v", err) + } + + if err := exec.Command("git", "commit", "-m", commitMsg).Run(); err != nil { + return fmt.Errorf("failed to commit changes: %v", err) + } + + log("✓ Changes committed: %s", commitMsg) + } + } + + // Run security scan + if err := runSecurityScan(); err != nil { + return err + } + + // Get current branch + branch, err := getCurrentBranch() + if err != nil { + return fmt.Errorf("failed to get current branch: %v", err) + } + + // Push to origin + log("Pushing branch '%s' to origin...", branch) + cmd := exec.Command("git", "push", "origin", branch) + + if err := cmd.Run(); err != nil { + errorLog("❌ Push failed") + return fmt.Errorf("push failed") + } + + log("✅ Successfully pushed to origin/%s", branch) + return nil +} + +// Show help message +func showHelp(programName string) { + fmt.Printf(`%s - Development script runner + +Usage: %s [options] [arguments] + +COMMANDS: + run [filters...] Run scripts matching filters (or all if no filters) + push,u,ush Commit, scan for secrets, and push to git origin + ls, list List all available scripts + new Create a new script template + deps, check Check for required dependencies + help Show this help message + +RUN OPTIONS: + --dry Show what would run without executing + --verbose Show detailed output during execution + +EXAMPLES: + %s # Show this help + %s run # Run all scripts (with confirmation) + %s run docker # Run scripts containing 'docker' + %s run --dry api # Show what API scripts would run + %s ls # List all scripts + %s new backup # Create a new script called 'backup.sh' + %s push # Secure git push with secret scanning + +SECURITY: + The push command automatically scans for secrets using GitLeaks + before pushing to prevent accidental credential exposure. + +`, programName, programName, programName, programName, programName, programName, programName, programName, programName) +} + +// Get script description from comment +func getDescription(scriptPath string) string { + file, err := os.Open(scriptPath) + if err != nil { + return "No description" + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "# NAME:") { + desc := strings.TrimSpace(strings.TrimPrefix(line, "# NAME:")) + if desc != "" { + return desc + } + } + } + return "No description" +} + +// Check if script name matches any of the filters +func matchesFilters(scriptName string, filters []string) bool { + if len(filters) == 0 { + return true + } + + scriptNameLower := strings.ToLower(scriptName) + for _, filter := range filters { + filterLower := strings.ToLower(filter) + if strings.Contains(scriptName, filter) || strings.Contains(scriptNameLower, filterLower) { + return true + } + } + return false +} + +// Find executable scripts in runs directory +func findScripts(runsDir string, filters []string) ([]Script, error) { + var scripts []Script + + err := filepath.Walk(runsDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Check if file is executable + if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 { + scriptName := filepath.Base(path) + if matchesFilters(scriptName, filters) { + scripts = append(scripts, Script{ + Path: path, + Name: scriptName, + Description: getDescription(path), + }) + } + } + return nil + }) + if err != nil { + return nil, err + } + + // Sort scripts by name + sort.Slice(scripts, func(i, j int) bool { + return scripts[i].Name < scripts[j].Name + }) + + return scripts, nil +} + +// List all available scripts +func listScripts(runsDir string) error { + scripts, err := findScripts(runsDir, nil) + if err != nil { + return err + } + + if len(scripts) == 0 { + warn("No executable scripts found in %s", runsDir) + return nil + } + + fmt.Printf(Blue + "Available scripts:" + NC + "\n") + for _, script := range scripts { + fmt.Printf(" %s%s%s - %s\n", Green, script.Name, NC, script.Description) + } + fmt.Printf("\nTotal: %d scripts\n", len(scripts)) + return nil +} + +// Create a new script template +func createNewScript(runsDir, scriptName string) error { + if scriptName == "" { + return fmt.Errorf("script name required") + } + + // Ensure runs directory exists + if err := os.MkdirAll(runsDir, 0o755); err != nil { + return err + } + + // Add .sh extension if not provided + if !strings.HasSuffix(scriptName, ".sh") { + scriptName += ".sh" + } + + scriptPath := filepath.Join(runsDir, scriptName) + + // Check if script already exists + if _, err := os.Stat(scriptPath); err == nil { + return fmt.Errorf("script %s already exists", scriptName) + } + + // Create script template + template := fmt.Sprintf(`#!/usr/bin/env bash +# NAME: %s script + +set -euo pipefail + +# Add your script logic here +echo "Running %s script..." + +# Example: +# - Add your commands below +# - Use proper error handling +# - Add logging as needed + +echo "✅ %s completed successfully" +`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh")) + + if err := os.WriteFile(scriptPath, []byte(template), 0o755); err != nil { + return err + } + + log("✅ Created new script: %s", scriptPath) + log("Edit the script to add your commands") + return nil +} + +// Execute a single script +func executeScript(script Script, logsDir string, verbose bool) error { + logFile := filepath.Join(logsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log") + + cmd := exec.Command(script.Path) + + if verbose { + // Create log file + logFileHandle, err := os.Create(logFile) + if err != nil { + return err + } + defer logFileHandle.Close() + + // Use MultiWriter to write to both stdout and log file + cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle) + cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle) + + return cmd.Run() + } else { + // Only write to log file + logFileHandle, err := os.Create(logFile) + if err != nil { + return err + } + defer logFileHandle.Close() + + cmd.Stdout = logFileHandle + cmd.Stderr = logFileHandle + + return cmd.Run() + } +} + +// Parse command line arguments +func parseArgs(args []string) (*Config, error) { + config := &Config{} + + // Get current working directory (equivalent to bash SCRIPT_DIR) + scriptDir, err := os.Getwd() + if err != nil { + return nil, err + } + config.ScriptDir = scriptDir + config.EnvFile = filepath.Join(config.ScriptDir, ".env") + config.RunsDir = filepath.Join(config.ScriptDir, "runs") + config.LogsDir = filepath.Join(config.ScriptDir, "logs") + + if len(args) == 0 { + return config, fmt.Errorf("help") + } + + i := 0 + // First argument should be the command + if i < len(args) { + arg := args[i] + switch arg { + case "--help", "help": + return config, fmt.Errorf("help") + case "run": + config.Command = "run" + i++ + case "push", "u", "ush": + config.Command = "push" + i++ + case "ls", "list": + config.Command = "list" + i++ + case "new": + config.Command = "new" + i++ + case "deps", "check": + config.Command = "deps" + i++ + default: + return nil, fmt.Errorf("unknown command: %s", arg) + } + } + + // Parse remaining arguments based on command + for i < len(args) { + arg := args[i] + switch arg { + case "--dry": + config.DryRun = true + case "--verbose": + config.Verbose = true + default: + if strings.HasPrefix(arg, "-") { + return nil, fmt.Errorf("unknown option: %s", arg) + } + config.Filters = append(config.Filters, arg) + } + i++ + } + + return config, nil +} + +// Confirm execution of all scripts +func confirmExecution(scripts []Script) bool { + fmt.Printf(Red + "⚠️ About to run ALL scripts:" + NC + "\n") + for _, script := range scripts { + fmt.Printf(" • %s - %s\n", script.Name, script.Description) + } + fmt.Println() + + fmt.Print("Continue? (y/N): ") + reader := bufio.NewReader(os.Stdin) + answer, _ := reader.ReadString('\n') + answer = strings.TrimSpace(strings.ToLower(answer)) + + return answer == "y" || answer == "yes" +} + +func main() { + const programName = "dev" + + // Parse arguments + config, err := parseArgs(os.Args[1:]) + if err != nil { + if err.Error() == "help" { + showHelp(programName) + os.Exit(0) + } + errorLog("Error: %v", err) + fmt.Println() + showHelp(programName) + os.Exit(1) + } + + // Handle special commands + switch config.Command { + case "push": + if err := handlePush(); err != nil { + os.Exit(1) + } + os.Exit(0) + case "deps": + if err := checkDependencies(); err != nil { + os.Exit(1) + } + os.Exit(0) + case "list": + if err := listScripts(config.RunsDir); err != nil { + errorLog("Error listing scripts: %v", err) + os.Exit(1) + } + os.Exit(0) + case "new": + if len(config.Filters) == 0 { + errorLog("Script name required for 'new' command") + fmt.Printf("Usage: %s new \n", programName) + os.Exit(1) + } + scriptName := config.Filters[0] + if err := createNewScript(config.RunsDir, scriptName); err != nil { + errorLog("Error creating script: %v", err) + os.Exit(1) + } + os.Exit(0) + case "run": + // Continue with script execution logic below + case "": + // No command provided, show help + showHelp(programName) + os.Exit(0) + default: + errorLog("Unknown command: %s", config.Command) + showHelp(programName) + os.Exit(1) + } + + // Initialize runs directory if it doesn't exist (only for run command) + if _, err := os.Stat(config.RunsDir); os.IsNotExist(err) { + log("Creating runs directory at: %s", config.RunsDir) + if err := os.MkdirAll(config.RunsDir, 0o755); err != nil { + errorLog("Failed to create runs directory: %v", err) + os.Exit(1) + } + + // Create example script + exampleScript := filepath.Join(config.RunsDir, "example.sh") + content := `#!/usr/bin/env bash +# NAME: Example script +echo "Hello from runs/example.sh!" +echo "Edit this script or add your own to runs/" +` + if err := os.WriteFile(exampleScript, []byte(content), 0o755); err != nil { + errorLog("Failed to create example script: %v", err) + os.Exit(1) + } + + log("✅ Created runs/ directory with example script") + log("Use '%s ls' to list scripts or '%s new ' to create a new one", programName, programName) + os.Exit(0) + } + + // Create logs directory + if err := os.MkdirAll(config.LogsDir, 0o755); err != nil { + errorLog("Failed to create logs directory: %v", err) + os.Exit(1) + } + + // Find scripts to run + scripts, err := findScripts(config.RunsDir, config.Filters) + if err != nil { + errorLog("Error finding scripts: %v", err) + os.Exit(1) + } + + if len(scripts) == 0 { + if len(config.Filters) > 0 { + warn("No scripts match the given filters: %s", strings.Join(config.Filters, ", ")) + fmt.Printf("Use '%s ls' to see all available scripts\n", programName) + } else { + warn("No executable scripts found in %s", config.RunsDir) + fmt.Printf("Use '%s new ' to create a new script\n", programName) + } + os.Exit(0) + } + + // Confirm execution if running all scripts without dry run + if len(config.Filters) == 0 && !config.DryRun { + if !confirmExecution(scripts) { + fmt.Println("Cancelled") + os.Exit(0) + } + } + + // Execute scripts + result := ExecutionResult{} + + for _, script := range scripts { + if config.DryRun { + fmt.Printf(Blue+"[DRY]"+NC+" Would run: %s - %s\n", script.Name, script.Description) + continue + } + + fmt.Printf("\n"+Blue+"▶"+NC+" Running: %s\n", script.Name) + if script.Description != "No description" { + fmt.Printf(" %s\n", script.Description) + } + + if err := executeScript(script, config.LogsDir, config.Verbose); err != nil { + errorLog("❌ %s failed (see %s)", script.Name, + filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")) + result.Failed = append(result.Failed, script.Name) + } else { + log("✅ %s completed", script.Name) + result.Executed = append(result.Executed, script.Name) + } + } + + // Print summary + if !config.DryRun { + fmt.Printf("\n" + Blue + "═══ SUMMARY ═══" + NC + "\n") + fmt.Printf("✅ Completed: %d\n", len(result.Executed)) + if len(result.Failed) > 0 { + fmt.Printf("❌ Failed: %d\n", len(result.Failed)) + fmt.Printf("\n" + Red + "Failed scripts:" + NC + "\n") + for _, failed := range result.Failed { + fmt.Printf(" • %s\n", failed) + } + os.Exit(1) + } + } +} diff --git a/run b/run deleted file mode 100755 index 5eac16d..0000000 --- a/run +++ /dev/null @@ -1,377 +0,0 @@ -#!/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 diff --git a/runs/test b/runs/test new file mode 100755 index 0000000..d50e865 --- /dev/null +++ b/runs/test @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# NAME: Enable and configure system services + +set -euo pipefail + +echo "Enabling and configuring system services..." + +exit 0