package main import ( "fmt" "log" "os" "os/signal" "path/filepath" "syscall" "time" "git.dws.rip/DWS/onyx/internal/core" "git.dws.rip/DWS/onyx/internal/daemon" "github.com/spf13/cobra" ) var ( version = "0.1.0" repoPath string interval time.Duration debounce time.Duration pidFile string ) func main() { rootCmd := &cobra.Command{ Use: "onxd", Short: "Onyx Daemon - Transparent versioning daemon", Long: `The Onyx daemon monitors your repository for changes and automatically creates snapshots of your work. This enables transparent versioning without manual commits.`, Version: version, RunE: runDaemon, } // Add flags rootCmd.PersistentFlags().StringVarP(&repoPath, "repo", "r", ".", "Path to the Onyx repository") rootCmd.PersistentFlags().DurationVarP(&interval, "interval", "i", 1*time.Second, "Ticker interval for periodic checks") rootCmd.PersistentFlags().DurationVarP(&debounce, "debounce", "d", 500*time.Millisecond, "Debounce duration for filesystem events") if err := rootCmd.Execute(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } func runDaemon(cmd *cobra.Command, args []string) error { // Resolve repository path absPath, err := filepath.Abs(repoPath) if err != nil { return fmt.Errorf("failed to resolve repository path: %w", err) } // Check if this is an Onyx repository if !core.IsOnyxRepo(absPath) { return fmt.Errorf("not an Onyx repository: %s", absPath) } // Open the repository repo, err := core.Open(absPath) if err != nil { return fmt.Errorf("failed to open repository: %w", err) } defer repo.Close() // Create daemon configuration config := &daemon.Config{ Debounce: debounce, TickerInterval: interval, RepoPath: absPath, } // Create the daemon d, err := daemon.New(repo, config) if err != nil { return fmt.Errorf("failed to create daemon: %w", err) } // Write PID file pidFile = filepath.Join(repo.GetOnyxPath(), "daemon.pid") if err := writePIDFile(pidFile); err != nil { return fmt.Errorf("failed to write PID file: %w", err) } defer os.Remove(pidFile) // Set up signal handlers sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // Start the daemon if err := d.Start(); err != nil { return fmt.Errorf("failed to start daemon: %w", err) } log.Printf("Onyx daemon started (PID: %d)", os.Getpid()) log.Printf("Watching repository: %s", absPath) log.Printf("Debounce: %v, Interval: %v", debounce, interval) // Wait for shutdown signal sig := <-sigChan log.Printf("Received signal: %v", sig) // Stop the daemon if err := d.Stop(); err != nil { return fmt.Errorf("failed to stop daemon: %w", err) } return nil } // writePIDFile writes the current process ID to a file func writePIDFile(path string) error { pid := os.Getpid() return os.WriteFile(path, []byte(fmt.Sprintf("%d\n", pid)), 0644) } // readPIDFile reads the process ID from a file func readPIDFile(path string) (int, error) { data, err := os.ReadFile(path) if err != nil { return 0, err } var pid int _, err = fmt.Sscanf(string(data), "%d", &pid) return pid, err }