package main import ( "bufio" "flag" "fmt" "io" "os" "os/exec" "strings" "syscall" ) /* NOTES: * apt detected it wasn't connected to a TTY, so it said on stderr, WARNING: apt does not have a stable CLI interface. Use with caution in scripts. * All packages are up to date. <- This isn't seen when using `apt-get update`, only apt. * We don't see the warning about "apt does not have a stable CLI interface." when using apt-get. * I can't seem to reproduce the output error (where apt is reading the database and showing the percentage increasing). See the output directory for am example program that has that type of output. (Which works fine. Why doesn't apt or apt-get work correctly?!) */ type CheckResult struct { Lines int // Number of lines read. UpToDate bool // All packages are up to date. AutoRemove bool // Use 'apt autoremove' to remove it. } // Check the output for CheckResult conditions. func check_lines(r io.Reader, output chan CheckResult) { scanner := bufio.NewScanner(r) var err error var result CheckResult var line string for scanner.Scan() { line = scanner.Text() if line == "All packages are up to date." { result.UpToDate = true } if line == "Use 'apt autoremove' to remove it." { result.AutoRemove = true } result.Lines += 1 } err = scanner.Err() if err != nil { panic(err) } output <- result } // Run the given command, showing output, checking output. func run_check_command(command []string, debug_output bool) (CheckResult, error) { var result CheckResult cmd := exec.Command(command[0], command[1:]...) r, w, err := os.Pipe() if err != nil { fmt.Printf("Failed to create pipe: %s", err) return result, err } // Start output parser. output := make(chan CheckResult) go check_lines(r, output) // Connections // Connect output with stdout and parser. if debug_output { filename := fmt.Sprintf("debug-%s.txt", command[1]) fp, err := os.Create(filename) if err != nil { fmt.Printf("Failed to create file [%s]: %s", filename, err) return result, err } defer fp.Close() cmd.Stdout = io.MultiWriter(os.Stdout, w, fp) fmt.Printf("See: %s for output.\n", filename) } else { cmd.Stdout = io.MultiWriter(os.Stdout, w) } cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin // cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive") // Run the process as root (We should be SETUID). cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 0, Gid: 0} fmt.Println(strings.Repeat("=", 64)) fmt.Println("Running", command) err = cmd.Run() fmt.Println(strings.Repeat("=", 50)) if err != nil { fmt.Println("Did you forget to:") fmt.Println(" sudo chown root:root aptgrade") fmt.Println(" sudo chmod a+s aptgrade") fmt.Println("Command failed:", err) } // If I want check_lines to end, it needs the reader closed. // So, close the write end of the pipe. w.Close() // fmt.Println("Reading results from go routine:") value := <-output if !cmd.ProcessState.Success() { // Process exited with non-success code. fmt.Printf("Process %d exited with status code %d\n", cmd.ProcessState.Pid(), cmd.ProcessState.ExitCode()) } // fmt.Printf("Result: %+v\n", value) return value, err } // const APT = "/usr/bin/apt" const APT = "/usr/bin/apt-get" func main() { // const APT = APT_CMD var show bool flag.BoolVar(&show, "show", false, "Show run results.") /* // Testing var APT string flag.StringVar(&APT, "apt", APT_CMD, "Apt command to use.") */ var output bool flag.BoolVar(&output, "output", false, "Create debug output files.") flag.Parse() if os.Geteuid() != 0 { fmt.Println("Use `sudo install -g 0 -o 0 -m 4755 aptgrade ~/bin/aptgrade`") fmt.Println("This program requires SETUID.") os.Exit(2) } // Check for updates. result, err := run_check_command([]string{APT, "update"}, output) if err != nil { fmt.Printf("Error: %s\n", err) os.Exit(2) } if show { fmt.Printf("Result: {%+v}\n", result) } // Is everything up to date? if !result.UpToDate { // Perform upgrade. result, err = run_check_command([]string{APT, "upgrade", "-y"}, output) if err != nil { fmt.Printf("Error: %s\n", err) os.Exit(2) } if show { fmt.Printf("Result: {%+v}\n", result) } // Is apt suggesting autoremove? if result.AutoRemove { // Perform autoremove. result, err = run_check_command([]string{APT, "autoremove", "-y"}, output) if err != nil { fmt.Printf("Error: %s\n", err) os.Exit(2) } if show { fmt.Printf("Result: {%+v}\n", result) } } } fmt.Println("Done") }