Add first cut at golang monitor program
This commit is contained in:
1
monitor/.gitignore
vendored
Normal file
1
monitor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
monitor
|
||||||
84
monitor/devrunner.go
Normal file
84
monitor/devrunner.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// a vibe coded el cheapo: https://claude.ai/chat/328ca558-1019-49b9-9f08-e85cfcea2ceb
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runProcess(ctx context.Context, wg *sync.WaitGroup, name, command string) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Printf("[%s] Stopping\n", name)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
fmt.Printf("[%s] Starting: %s\n", name, command)
|
||||||
|
|
||||||
|
// Create command with context for cancellation
|
||||||
|
cmd := exec.CommandContext(ctx, "sh", "-c", command)
|
||||||
|
|
||||||
|
// Setup stdout pipe
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[%s] Error creating stdout pipe: %v\n", name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup stderr pipe
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[%s] Error creating stderr pipe: %v\n", name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the command
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[%s] Error starting command: %v\n", name, err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy output in separate goroutines
|
||||||
|
var ioWg sync.WaitGroup
|
||||||
|
ioWg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer ioWg.Done()
|
||||||
|
io.Copy(os.Stdout, stdout)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer ioWg.Done()
|
||||||
|
io.Copy(os.Stderr, stderr)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for command to finish
|
||||||
|
err = cmd.Wait()
|
||||||
|
ioWg.Wait() // Ensure all output is copied
|
||||||
|
|
||||||
|
// Check if we should restart
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Printf("[%s] Stopped\n", name)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[%s] Process exited with error: %v\n", name, err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[%s] Process exited normally\n", name)
|
||||||
|
}
|
||||||
|
fmt.Printf("[%s] Restarting in 1 second...\n", name)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
monitor/filechange.go
Normal file
6
monitor/filechange.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type FileChange struct {
|
||||||
|
Path string
|
||||||
|
Operation string
|
||||||
|
}
|
||||||
8
monitor/go.mod
Normal file
8
monitor/go.mod
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module philologue.net/diachron/monitor
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
|
)
|
||||||
4
monitor/go.sum
Normal file
4
monitor/go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
50
monitor/main.go
Normal file
50
monitor/main.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
// "sync"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// var program1 = os.Getenv("BUILD_COMMAND")
|
||||||
|
//var program2 = os.Getenv("RUN_COMMAND")
|
||||||
|
|
||||||
|
var watchedDir = os.Getenv("WATCHED_DIR")
|
||||||
|
|
||||||
|
// Create context for graceful shutdown
|
||||||
|
// ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
//defer cancel()
|
||||||
|
|
||||||
|
// Setup signal handling
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
fileChanges := make(chan FileChange, 10)
|
||||||
|
|
||||||
|
go watchFiles(watchedDir, fileChanges)
|
||||||
|
|
||||||
|
go printChanges(fileChanges)
|
||||||
|
|
||||||
|
// WaitGroup to track both processes
|
||||||
|
// var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Start both processes
|
||||||
|
//wg.Add(2)
|
||||||
|
// go runProcess(ctx, &wg, "builder", program1)
|
||||||
|
// go runProcess(ctx, &wg, "runner", program2)
|
||||||
|
|
||||||
|
// Wait for interrupt signal
|
||||||
|
<-sigCh
|
||||||
|
fmt.Println("\nReceived interrupt signal, shutting down...")
|
||||||
|
|
||||||
|
// Cancel context to signal goroutines to stop
|
||||||
|
/// cancel()
|
||||||
|
|
||||||
|
// Wait for both processes to finish
|
||||||
|
// wg.Wait()
|
||||||
|
fmt.Println("All processes terminated cleanly")
|
||||||
|
}
|
||||||
11
monitor/printchanges.go
Normal file
11
monitor/printchanges.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printChanges(changes <-chan FileChange) {
|
||||||
|
for change := range changes {
|
||||||
|
fmt.Printf("[%s] %s\n", change.Operation, change.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
monitor/watchfiles.go
Normal file
74
monitor/watchfiles.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func watchFiles(dir string, changes chan<- FileChange) {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
// Add all directories recursively
|
||||||
|
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
err = watcher.Add(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error watching %s: %v\n", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle different types of events
|
||||||
|
var operation string
|
||||||
|
switch {
|
||||||
|
case event.Op&fsnotify.Write == fsnotify.Write:
|
||||||
|
operation = "MODIFIED"
|
||||||
|
case event.Op&fsnotify.Create == fsnotify.Create:
|
||||||
|
operation = "CREATED"
|
||||||
|
// If a new directory is created, start watching it
|
||||||
|
if info, err := os.Stat(event.Name); err == nil && info.IsDir() {
|
||||||
|
watcher.Add(event.Name)
|
||||||
|
}
|
||||||
|
case event.Op&fsnotify.Remove == fsnotify.Remove:
|
||||||
|
operation = "REMOVED"
|
||||||
|
case event.Op&fsnotify.Rename == fsnotify.Rename:
|
||||||
|
operation = "RENAMED"
|
||||||
|
case event.Op&fsnotify.Chmod == fsnotify.Chmod:
|
||||||
|
operation = "CHMOD"
|
||||||
|
default:
|
||||||
|
operation = "UNKNOWN"
|
||||||
|
}
|
||||||
|
|
||||||
|
changes <- FileChange{
|
||||||
|
Path: event.Name,
|
||||||
|
Operation: operation,
|
||||||
|
}
|
||||||
|
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Watcher error: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user