فهرست منبع

dev: automated commit - 2025-05-31 17:04:45

Mariano Z. 8 ماه پیش
والد
کامیت
f037255a1a
7فایلهای تغییر یافته به همراه783 افزوده شده و 377 حذف شده
  1. 1 0
      .gitignore
  2. 48 0
      README.md
  3. 46 0
      dev
  4. 3 0
      go.mod
  5. 677 0
      main.go
  6. 0 377
      run
  7. 8 0
      runs/test

+ 1 - 0
.gitignore

@@ -1 +1,2 @@
 logs/*
+bin/

+ 48 - 0
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.

+ 46 - 0
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" "$@"

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module marianozunino/dev
+
+go 1.24.1

+ 677 - 0
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 <command> [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 <name>        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 <script-name>\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 <name>' 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 <name>' 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)
+		}
+	}
+}

+ 0 - 377
run

@@ -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 <<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

+ 8 - 0
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