| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- package main
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strings"
- )
- func getScriptMetadata(scriptPath string) (string, bool) {
- file, err := os.Open(scriptPath)
- if err != nil {
- return "No description", false
- }
- defer file.Close()
- var desc string
- var requiresSudo bool
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if strings.HasPrefix(line, "# NAME:") {
- desc = strings.TrimSpace(strings.TrimPrefix(line, "# NAME:"))
- }
- if strings.HasPrefix(line, "# REQUIRES: sudo") ||
- strings.HasPrefix(line, "# REQUIRES:sudo") ||
- strings.HasPrefix(line, "# ELEVATED: true") ||
- strings.HasPrefix(line, "# ELEVATED:true") ||
- strings.HasPrefix(line, "# SUDO: true") ||
- strings.HasPrefix(line, "# SUDO:true") {
- requiresSudo = true
- }
- }
- if desc == "" {
- desc = "No description"
- }
- return desc, requiresSudo
- }
- func findScripts(filters []string) ([]Script, error) {
- var scripts []Script
- err := filepath.Walk(config.RunsDir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 {
- // Get relative path from runs directory
- relPath, err := filepath.Rel(config.RunsDir, path)
- if err != nil {
- return err
- }
- // Skip hidden files and directories
- if strings.HasPrefix(filepath.Base(path), ".") {
- return nil
- }
- scriptName := filepath.Base(path)
- if len(filters) == 0 || matchesFilters(relPath, scriptName, filters) {
- desc, requiresSudo := getScriptMetadata(path)
- scripts = append(scripts, Script{
- Path: path,
- Name: scriptName,
- RelPath: relPath,
- Desc: desc,
- RequiresSudo: requiresSudo,
- })
- }
- }
- return nil
- })
- sort.Slice(scripts, func(i, j int) bool {
- return scripts[i].RelPath < scripts[j].RelPath
- })
- return scripts, err
- }
- func matchesFilters(relPath, scriptName string, filters []string) bool {
- for _, filter := range filters {
- // Normalize paths for comparison (remove .sh extension from filter if present)
- normalizedFilter := strings.TrimSuffix(filter, ".sh")
- normalizedRelPath := strings.TrimSuffix(relPath, ".sh")
- normalizedScriptName := strings.TrimSuffix(scriptName, ".sh")
- // Check exact matches
- if normalizedRelPath == normalizedFilter || normalizedScriptName == normalizedFilter {
- return true
- }
- // Check if filter matches the relative path or script name (case insensitive)
- filterLower := strings.ToLower(normalizedFilter)
- relPathLower := strings.ToLower(normalizedRelPath)
- scriptNameLower := strings.ToLower(normalizedScriptName)
- if strings.Contains(relPathLower, filterLower) || strings.Contains(scriptNameLower, filterLower) {
- return true
- }
- // Check directory match (e.g., "tools" matches "tools/install.sh")
- if strings.HasPrefix(relPathLower, filterLower+"/") {
- return true
- }
- }
- return false
- }
- func executeScript(script Script, args []string, verbose bool) error {
- logFile := filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")
- var cmd *exec.Cmd
- if script.RequiresSudo {
- sudoLog("Script requires elevated privileges: %s", script.RelPath)
- if !commandExists("sudo") {
- errorLog("sudo command not found - cannot run elevated script")
- return fmt.Errorf("sudo not available")
- }
- fullArgs := append([]string{script.Path}, args...)
- cmd = exec.Command("sudo", fullArgs...)
- sudoLog("Running with sudo: %s", strings.Join(append([]string{script.Path}, args...), " "))
- } else {
- cmd = exec.Command(script.Path, args...)
- }
- if config.Interactive {
- cmd.Stdin = os.Stdin
- }
- logFileHandle, err := os.Create(logFile)
- if err != nil {
- return err
- }
- defer logFileHandle.Close()
- showOutput := verbose || config.Interactive
- if showOutput {
- cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle)
- cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle)
- } else {
- cmd.Stdout = logFileHandle
- cmd.Stderr = logFileHandle
- }
- return cmd.Run()
- }
- func createNewScript(scriptName string) error {
- scriptPath := filepath.Join(config.RunsDir, scriptName)
- dirPath := filepath.Dir(scriptPath)
- if err := os.MkdirAll(dirPath, 0o755); err != nil {
- return err
- }
- if !strings.HasSuffix(scriptName, ".sh") {
- scriptName += ".sh"
- }
- if _, err := os.Stat(scriptPath); err == nil {
- return fmt.Errorf("script %s already exists", scriptName)
- }
- template := fmt.Sprintf(`#!/usr/bin/env bash
- # NAME: %s script
- # REQUIRES: sudo
- set -euo pipefail
- echo "Running %s script..."
- echo "✅ %s completed successfully"
- `, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
- err := os.WriteFile(scriptPath, []byte(template), 0o755)
- if err != nil {
- return err
- }
- log("✅ Created script: %s", scriptPath)
- return nil
- }
- func ensureRunsDir() error {
- 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 {
- return err
- }
- 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 {
- return err
- }
- sudoExampleScript := filepath.Join(config.RunsDir, "system-info.sh")
- sudoContent := `#!/usr/bin/env bash
- # NAME: System information collector
- # REQUIRES: sudo
- set -euo pipefail
- echo "Collecting system information (requires elevated privileges)..."
- echo "=== Disk Usage ==="
- df -h
- echo "=== Memory Info ==="
- free -h
- echo "=== System Logs (last 10 lines) ==="
- tail -n 10 /var/log/syslog 2>/dev/null || tail -n 10 /var/log/messages 2>/dev/null || echo "No accessible system logs"
- echo "✅ System info collection completed"
- `
- if err := os.WriteFile(sudoExampleScript, []byte(sudoContent), 0o755); err != nil {
- return err
- }
- log("✅ Created runs/ directory with example scripts")
- return nil
- }
- return nil
- }
|