diff --git a/client/cmd/node/autoUpdate.go b/client/cmd/node/autoUpdate.go index 8952702..48b1e10 100644 --- a/client/cmd/node/autoUpdate.go +++ b/client/cmd/node/autoUpdate.go @@ -13,18 +13,31 @@ import ( // autoUpdateCmd represents the command to setup automatic updates var autoUpdateCmd = &cobra.Command{ - Use: "auto-update", + Use: "auto-update [enable|disable]", Short: "Setup automatic update checks", - Long: `Setup a cron job to automatically check for Quilibrium node updates every 10 minutes. + Long: `Setup or remove a cron job to automatically check for Quilibrium node updates every 10 minutes. -This command will create a cron entry that runs 'qclient node update' every 10 minutes +This command will create or remove a cron entry that runs 'qclient node update' every 10 minutes to check for and apply any available updates. Example: # Setup automatic update checks - qclient node auto-update`, + qclient node auto-update enable + + # Remove automatic update checks + qclient node auto-update disable`, Run: func(cmd *cobra.Command, args []string) { - setupCronJob() + if len(args) != 1 || (args[0] != "enable" && args[0] != "disable") { + fmt.Fprintf(os.Stderr, "Error: must specify either 'enable' or 'disable'\n") + cmd.Help() + return + } + + if args[0] == "enable" { + setupCronJob() + } else { + removeCronJob() + } }, } @@ -57,6 +70,16 @@ func setupCronJob() { } } +func removeCronJob() { + // OS-specific removal + if runtime.GOOS == "darwin" || runtime.GOOS == "linux" { + removeUnixCron() + } else { + fmt.Fprintf(os.Stderr, "Error: auto-update is only supported on macOS and Linux\n") + return + } +} + func isCrontabInstalled() bool { // Check if crontab is installed _, err := exec.LookPath("crontab") @@ -66,12 +89,23 @@ func isCrontabInstalled() bool { func installCrontab() { fmt.Fprintf(os.Stdout, "Installing cron package...\n") // Install crontab - installCmd := exec.Command("sudo", "apt-get", "update", "&&", "sudo", "apt-get", "install", "-y", "cron") + updateCmd := exec.Command("sudo", "apt-get", "update") + updateCmd.Stdout = nil + updateCmd.Stderr = nil + if err := updateCmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error updating package lists: %v\n", err) + return + } + + installCmd := exec.Command("sudo", "apt-get", "install", "-y", "cron") + installCmd.Stdout = nil + installCmd.Stderr = nil if err := installCmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error installing cron package: %v\n", err) return } + // verify crontab is installed if isCrontabInstalled() { fmt.Fprintf(os.Stdout, "Cron package installed\n") } else { @@ -103,17 +137,39 @@ func setupUnixCron(qclientPath string) { } // Check if our update command is already in crontab - if strings.Contains(currentCrontab, "qclient node update") { + if strings.Contains(currentCrontab, "### qclient managed cron tasks") && + strings.Contains(currentCrontab, "#### node auto-update") { fmt.Fprintf(os.Stdout, "Automatic update check is already configured in crontab\n") return } - // Add new cron entry - newCrontab := currentCrontab - if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") { - newCrontab += "\n" + // Add new cron entry with indicators + var newCrontab string + + // If qclient section exists but node auto-update doesn't, we need to add it + if strings.Contains(currentCrontab, "### qclient managed cron tasks") && + strings.Contains(currentCrontab, "### end qclient managed cron tasks") { + // Insert node auto-update section before the end marker + parts := strings.Split(currentCrontab, "### end qclient managed cron tasks") + if len(parts) >= 2 { + newCrontab = parts[0] + + "#### node auto-update\n" + + cronExpression + "\n" + + "#### end node auto-update (DO NOT DELETE)\n\n" + + "### end qclient managed cron tasks" + parts[1] + } + } else { + // Add the entire section with markers + newCrontab = currentCrontab + if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") { + newCrontab += "\n" + } + newCrontab += "\n### qclient managed cron tasks\n" + + "#### node auto-update\n" + + cronExpression + "\n" + + "#### end node auto-update (DO NOT DELETE)\n\n" + + "### end qclient managed cron tasks (DO NOT DELETE)\n" } - newCrontab += cronExpression + "\n" // Write to temporary file tempFile, err := os.CreateTemp("", "qclient-crontab") @@ -139,3 +195,95 @@ func setupUnixCron(qclientPath string) { fmt.Fprintf(os.Stdout, "Successfully configured cron job to check for updates every 10 minutes\n") fmt.Fprintf(os.Stdout, "Added: %s\n", cronExpression) } + +func removeUnixCron() { + if !isCrontabInstalled() { + fmt.Fprintf(os.Stderr, "Error: crontab command not found\n") + return + } + + fmt.Fprintf(os.Stdout, "Removing auto-update cron job...\n") + + // Check existing crontab + checkCmd := exec.Command("crontab", "-l") + checkOutput, err := checkCmd.CombinedOutput() + + if err != nil { + fmt.Fprintf(os.Stderr, "Error checking existing crontab: %v\n", err) + return + } + + currentCrontab := string(checkOutput) + + // No crontab or doesn't contain our section + if currentCrontab == "" || + !strings.Contains(currentCrontab, "### qclient managed cron tasks") || + !strings.Contains(currentCrontab, "#### node auto-update") { + fmt.Fprintf(os.Stdout, "No auto-update job found in crontab\n") + return + } + + var newCrontab string + + // If only node auto-update section exists, remove the whole qclient section + if !strings.Contains(currentCrontab, "#### end node auto-update") || + !strings.Contains(strings.Split(currentCrontab, "### end qclient managed cron tasks")[0], "####") || + strings.Count(strings.Split(currentCrontab, "### end qclient managed cron tasks")[0], "####") <= 2 { + // Remove entire qclient section + parts := strings.Split(currentCrontab, "### qclient managed cron tasks") + if len(parts) >= 2 { + endParts := strings.Split(parts[1], "### end qclient managed cron tasks") + if len(endParts) >= 2 { + newCrontab = parts[0] + endParts[1] + } else { + newCrontab = parts[0] + } + } else { + newCrontab = currentCrontab + } + } else { + // Remove just the auto-update section + startMarker := "#### node auto-update" + endMarker := "#### end node auto-update (DO NOT DELETE)" + + startIdx := strings.Index(currentCrontab, startMarker) + endIdx := strings.Index(currentCrontab, endMarker) + + if startIdx >= 0 && endIdx >= 0 { + endIdx += len(endMarker) + // Remove the section including markers + newCrontab = currentCrontab[:startIdx] + currentCrontab[endIdx:] + } else { + newCrontab = currentCrontab + } + } + + // Clean up any leftover double newlines + newCrontab = strings.ReplaceAll(newCrontab, "\n\n\n", "\n\n") + if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") { + newCrontab += "\n" + } + + // Write to temporary file + tempFile, err := os.CreateTemp("", "qclient-crontab") + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err) + return + } + defer os.Remove(tempFile.Name()) + + if _, err := tempFile.WriteString(newCrontab); err != nil { + fmt.Fprintf(os.Stderr, "Error writing to temporary file: %v\n", err) + return + } + tempFile.Close() + + // Install new crontab + installCmd := exec.Command("crontab", tempFile.Name()) + if err := installCmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error updating crontab: %v\n", err) + return + } + + fmt.Fprintf(os.Stdout, "Successfully removed auto-update cron job\n") +} diff --git a/client/cmd/node/node.go b/client/cmd/node/node.go index d06a35b..6eebae3 100644 --- a/client/cmd/node/node.go +++ b/client/cmd/node/node.go @@ -47,6 +47,10 @@ var NodeCmd = &cobra.Command{ var err error userLookup, err = user.Lookup(nodeUser) if err != nil { + if !utils.IsSudo() { + fmt.Printf("need to be sudo to install quilibrium user, retry with sudo: %s\n", err) + os.Exit(1) + } userLookup, err = InstallQuilibriumUser() if err != nil { fmt.Printf("error installing quilibrium user: %s\n", err)