233 lines
5.7 KiB
Go
233 lines
5.7 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
"git.dws.rip/DWS/onyx/internal/core"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// NewDaemonCmd creates the daemon command with start, stop, and status subcommands
|
|
func NewDaemonCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "daemon",
|
|
Short: "Manage the Onyx daemon for transparent versioning",
|
|
Long: `The daemon command controls the Onyx background daemon that monitors
|
|
your repository for changes and automatically creates snapshots.`,
|
|
}
|
|
|
|
cmd.AddCommand(newDaemonStartCmd())
|
|
cmd.AddCommand(newDaemonStopCmd())
|
|
cmd.AddCommand(newDaemonStatusCmd())
|
|
|
|
return cmd
|
|
}
|
|
|
|
// newDaemonStartCmd creates the daemon start subcommand
|
|
func newDaemonStartCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "start",
|
|
Short: "Start the Onyx daemon",
|
|
Long: `Starts the Onyx daemon in the background to monitor the repository.`,
|
|
RunE: runDaemonStart,
|
|
}
|
|
}
|
|
|
|
// newDaemonStopCmd creates the daemon stop subcommand
|
|
func newDaemonStopCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "stop",
|
|
Short: "Stop the Onyx daemon",
|
|
Long: `Gracefully stops the running Onyx daemon.`,
|
|
RunE: runDaemonStop,
|
|
}
|
|
}
|
|
|
|
// newDaemonStatusCmd creates the daemon status subcommand
|
|
func newDaemonStatusCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "status",
|
|
Short: "Check the Onyx daemon status",
|
|
Long: `Checks if the Onyx daemon is running and displays its status.`,
|
|
RunE: runDaemonStatus,
|
|
}
|
|
}
|
|
|
|
// runDaemonStart starts the daemon in the background
|
|
func runDaemonStart(cmd *cobra.Command, args []string) error {
|
|
// Get current directory
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current directory: %w", err)
|
|
}
|
|
|
|
// Check if this is an Onyx repository
|
|
if !core.IsOnyxRepo(cwd) {
|
|
return fmt.Errorf("not an Onyx repository")
|
|
}
|
|
|
|
// Open repository to get .onx path
|
|
repo, err := core.Open(cwd)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open repository: %w", err)
|
|
}
|
|
defer repo.Close()
|
|
|
|
pidFile := filepath.Join(repo.GetOnyxPath(), "daemon.pid")
|
|
|
|
// Check if daemon is already running
|
|
if isDaemonRunning(pidFile) {
|
|
return fmt.Errorf("daemon is already running")
|
|
}
|
|
|
|
// Find the onxd binary
|
|
onxdPath, err := exec.LookPath("onxd")
|
|
if err != nil {
|
|
// Try to find it in the same directory as onx
|
|
onxPath, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to locate onxd binary: %w", err)
|
|
}
|
|
onxdPath = filepath.Join(filepath.Dir(onxPath), "onxd")
|
|
if _, err := os.Stat(onxdPath); err != nil {
|
|
return fmt.Errorf("onxd binary not found. Please ensure it's installed")
|
|
}
|
|
}
|
|
|
|
// Start the daemon in the background
|
|
daemonCmd := exec.Command(onxdPath, "--repo", cwd)
|
|
daemonCmd.Stdout = nil
|
|
daemonCmd.Stderr = nil
|
|
daemonCmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Setsid: true, // Create new session
|
|
}
|
|
|
|
if err := daemonCmd.Start(); err != nil {
|
|
return fmt.Errorf("failed to start daemon: %w", err)
|
|
}
|
|
|
|
// Detach the process
|
|
if err := daemonCmd.Process.Release(); err != nil {
|
|
return fmt.Errorf("failed to release daemon process: %w", err)
|
|
}
|
|
|
|
fmt.Println("Onyx daemon started successfully")
|
|
return nil
|
|
}
|
|
|
|
// runDaemonStop stops the running daemon
|
|
func runDaemonStop(cmd *cobra.Command, args []string) error {
|
|
// Get current directory
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current directory: %w", err)
|
|
}
|
|
|
|
// Check if this is an Onyx repository
|
|
if !core.IsOnyxRepo(cwd) {
|
|
return fmt.Errorf("not an Onyx repository")
|
|
}
|
|
|
|
// Open repository to get .onx path
|
|
repo, err := core.Open(cwd)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open repository: %w", err)
|
|
}
|
|
defer repo.Close()
|
|
|
|
pidFile := filepath.Join(repo.GetOnyxPath(), "daemon.pid")
|
|
|
|
// Read PID from file
|
|
pid, err := readPIDFile(pidFile)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return fmt.Errorf("daemon is not running (no PID file found)")
|
|
}
|
|
return fmt.Errorf("failed to read PID file: %w", err)
|
|
}
|
|
|
|
// Check if process exists
|
|
process, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find daemon process: %w", err)
|
|
}
|
|
|
|
// Send SIGTERM to gracefully stop the daemon
|
|
if err := process.Signal(syscall.SIGTERM); err != nil {
|
|
return fmt.Errorf("failed to stop daemon: %w", err)
|
|
}
|
|
|
|
fmt.Println("Onyx daemon stopped successfully")
|
|
return nil
|
|
}
|
|
|
|
// runDaemonStatus checks the daemon status
|
|
func runDaemonStatus(cmd *cobra.Command, args []string) error {
|
|
// Get current directory
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current directory: %w", err)
|
|
}
|
|
|
|
// Check if this is an Onyx repository
|
|
if !core.IsOnyxRepo(cwd) {
|
|
return fmt.Errorf("not an Onyx repository")
|
|
}
|
|
|
|
// Open repository to get .onx path
|
|
repo, err := core.Open(cwd)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open repository: %w", err)
|
|
}
|
|
defer repo.Close()
|
|
|
|
pidFile := filepath.Join(repo.GetOnyxPath(), "daemon.pid")
|
|
|
|
if isDaemonRunning(pidFile) {
|
|
pid, _ := readPIDFile(pidFile)
|
|
fmt.Printf("Onyx daemon is running (PID: %d)\n", pid)
|
|
} else {
|
|
fmt.Println("Onyx daemon is not running")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isDaemonRunning checks if the daemon is running based on the PID file
|
|
func isDaemonRunning(pidFile string) bool {
|
|
pid, err := readPIDFile(pidFile)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Check if process exists
|
|
process, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Send signal 0 to check if process is alive
|
|
err = process.Signal(syscall.Signal(0))
|
|
return err == nil
|
|
}
|
|
|
|
// readPIDFile reads the PID from a file
|
|
func readPIDFile(path string) (int, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
pid, err := strconv.Atoi(string(data[:len(data)-1])) // Remove trailing newline
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid PID file: %w", err)
|
|
}
|
|
|
|
return pid, nil
|
|
}
|