main.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "net"
  8. "os"
  9. "os/signal"
  10. "strings"
  11. "syscall"
  12. "time"
  13. "golang.org/x/crypto/ssh/terminal"
  14. )
  15. func main() {
  16. addr := flag.String("addr", "127.0.0.1:8282", "DebTik CLI server address")
  17. username := flag.String("u", "", "Username for automatic authentication")
  18. password := flag.String("p", "", "Password for automatic authentication")
  19. cmdFlag := flag.String("c", "", "Execute a single command non-interactively and exit")
  20. flag.Parse()
  21. conn, err := net.DialTimeout("tcp", *addr, 3*time.Second)
  22. if err != nil {
  23. fmt.Fprintf(os.Stderr, "\033[31mError: Could not connect to DebTik CLI service on %s\033[0m\n", *addr)
  24. fmt.Fprintf(os.Stderr, "Ensure proxmox-router service is active and listening on port 8282.\n")
  25. os.Exit(1)
  26. }
  27. defer conn.Close()
  28. // If one-shot command execution is requested:
  29. if *cmdFlag != "" {
  30. runOneShot(conn, *username, *password, *cmdFlag)
  31. return
  32. }
  33. // Interactive Mode
  34. runInteractive(conn, *username, *password)
  35. }
  36. func runOneShot(conn net.Conn, username, password, command string) {
  37. reader := bufio.NewReader(conn)
  38. // If credentials were provided, handle initial prompt
  39. if username != "" && password != "" {
  40. _ = autoLogin(conn, reader, username, password)
  41. }
  42. // Send command followed by quit
  43. _, _ = fmt.Fprintf(conn, "%s\nquit\n", command)
  44. // Read until EOF
  45. _, _ = io.Copy(os.Stdout, reader)
  46. }
  47. func runInteractive(conn net.Conn, username, password string) {
  48. fd := int(os.Stdin.Fd())
  49. isTerm := terminal.IsTerminal(fd)
  50. if isTerm {
  51. oldState, err := terminal.MakeRaw(fd)
  52. if err == nil {
  53. defer func() {
  54. _ = terminal.Restore(fd, oldState)
  55. }()
  56. }
  57. }
  58. // Setup clean signal handling to restore terminal on SIGINT/SIGTERM
  59. sigChan := make(chan os.Signal, 1)
  60. signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
  61. go func() {
  62. <-sigChan
  63. if isTerm {
  64. _ = terminal.Restore(fd, nil)
  65. }
  66. os.Exit(0)
  67. }()
  68. errChan := make(chan error, 2)
  69. // Copy from Remote Server -> Local Terminal
  70. go func() {
  71. _, err := io.Copy(os.Stdout, conn)
  72. errChan <- err
  73. }()
  74. // Copy from Local Terminal -> Remote Server
  75. go func() {
  76. _, err := io.Copy(conn, os.Stdin)
  77. errChan <- err
  78. }()
  79. // Wait for connection to close
  80. <-errChan
  81. }
  82. func autoLogin(conn net.Conn, reader *bufio.Reader, username, password string) error {
  83. // Read until "login: "
  84. for {
  85. line, err := reader.ReadString(':')
  86. if err != nil {
  87. return err
  88. }
  89. if strings.Contains(strings.ToLower(line), "login") {
  90. _, _ = fmt.Fprintf(conn, "%s\n", username)
  91. break
  92. }
  93. }
  94. // Read until "Password: "
  95. for {
  96. line, err := reader.ReadString(':')
  97. if err != nil {
  98. return err
  99. }
  100. if strings.Contains(strings.ToLower(line), "password") {
  101. _, _ = fmt.Fprintf(conn, "%s\n", password)
  102. break
  103. }
  104. }
  105. return nil
  106. }