I'm building a custom CLI daemon in Go for Ubuntu. When I run the CLI locally (without SSH), everything works correctly — for example:
admin@admin> whoami
responds as expected.
However, after binding the CLI loop to an SSH session and running it as a server, I can successfully SSH into it and see the banner and prompt, but no keyboard input is accepted. The session displays output, but I cannot type anything — not even a single character.
So the issue is: 🟢 Local CLI → input works normally 🟢 SSH connection → connects and shows prompt 🔴 SSH session → no input is received inside my Go program
Below is my code, for the daemon:
package main
import (
"bufio"
"database/sql"
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/gliderlabs/ssh"
_ "github.com/mattn/go-sqlite3"
"golang.org/x/crypto/bcrypt"
)
const dbPath = "/opt/ngfw/auth/db/admins.db"
// ---------------- AUTH --------------------
func authUser(username, password string) bool {
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
log.Println("DB error:", err)
return false
}
defer db.Close()
var hash string
err = db.QueryRow("SELECT password FROM users WHERE username=?", username).Scan(&hash)
if err != nil {
return false
}
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}
// ---------------- SYSTEM INFO --------------------
func showSystem(s ssh.Session) {
hostname, _ := os.Hostname()
uptimeBytes, _ := os.ReadFile("/proc/uptime")
uptimeParts := strings.Fields(string(uptimeBytes))
uptimeSec, _ := strconv.ParseFloat(uptimeParts[0], 64)
uptime := time.Duration(uptimeSec) * time.Second
kernelBytes, _ := exec.Command("uname", "-r").Output()
kernel := strings.TrimSpace(string(kernelBytes))
fmt.Fprintf(s, "\nSystem Information\n")
fmt.Fprintf(s, "-------------------\n")
fmt.Fprintf(s, "Hostname: %s\n", hostname)
fmt.Fprintf(s, "Version: 0.1-dev\n")
fmt.Fprintf(s, "Kernel: %s\n", kernel)
fmt.Fprintf(s, "Uptime: %s\n\n", uptime.Truncate(time.Second))
}
// ---------------- CLI LOOP --------------------
func cliSession(s ssh.Session) {
hostname, _ := os.Hostname()
user := s.User()
pty, _, ok := s.Pty()
fmt.Fprintf(s, "\n[DEBUG] PTY OK=%v TERM=%s\n", ok, pty.Term)
reader := bufio.NewReader(s)
fmt.Fprintf(s, "DEBUG READ TEST...\n")
b, err := reader.Peek(1)
fmt.Fprintf(s, "PEEK=%v ERR=%v\n", b, err)
fmt.Fprintf(s, "\n🔥 Welcome to CLI 🔥\nLogged in as: %s\n\n", user)
for {
fmt.Fprintf(s, "%s@%s> ", user, hostname)
line, err := reader.ReadString('\n')
if err != nil {
fmt.Fprintln(s, "\n🛑 Lost input stream — disconnecting")
return
}
cmd := strings.TrimSpace(line)
switch cmd {
case "whoami":
fmt.Fprintln(s, user)
case "show ?":
fmt.Fprintln(s, "\nAvailable SHOW commands:")
fmt.Fprintln(s, " system")
fmt.Fprintln(s, " interfaces (coming soon)")
fmt.Fprintln(s, " users (coming soon)")
case "show system":
showSystem(s)
case "exit":
fmt.Fprintln(s, "🫡 Logging out…")
return
default:
if cmd != "" {
fmt.Fprintf(s, "%s: command not found\n", cmd)
}
}
}
}
// ---------------- SSH SERVER --------------------
func main() {
fmt.Println("🔥 SSH CLI STARTED on port 2222")
ssh.Handle(cliSession)
log.Fatal(ssh.ListenAndServe(
":2222",
nil,
ssh.PasswordAuth(func(ctx ssh.Context, pass string) bool {
return authUser(ctx.User(), pass)
}),
))
}
Help is appreciated
[DEBUG] PTY OK=true TERM=…?