package agent import ( "fmt" "os" "os/exec" "github.com/creack/pty" ) // PTY is a pseudo-terminal running an interactive login shell. type PTY struct { cmd *exec.Cmd f *os.File } // StartPTY launches a login shell attached to a new PTY. With user empty (or // equal to the agent's own user) it is the agent's shell — root by default, // since the agent runs as a root systemd service. Any other user is entered via // `su -l`, which only a root agent can do without a password. func StartPTY(user string) (*PTY, error) { var cmd *exec.Cmd if user == "" || user == currentUser() { cmd = exec.Command(defaultShell(), "-l") } else { if !validUserName(user) { return nil, fmt.Errorf("недопустимое имя пользователя %q", user) } if os.Geteuid() != 0 { return nil, fmt.Errorf("агент запущен от %s и не может открыть сессию от %s", currentUser(), user) } if !userExists(user) { return nil, fmt.Errorf("пользователь %s не найден", user) } su, err := exec.LookPath("su") if err != nil { return nil, fmt.Errorf("su не найден: %w", err) } cmd = exec.Command(su, "-l", user) } cmd.Env = append(os.Environ(), "TERM=xterm-256color") f, err := pty.Start(cmd) if err != nil { return nil, err } return &PTY{cmd: cmd, f: f}, nil } // Read reads terminal output. func (p *PTY) Read(b []byte) (int, error) { return p.f.Read(b) } // Write writes keystrokes to the terminal. func (p *PTY) Write(b []byte) (int, error) { return p.f.Write(b) } // Resize changes the terminal window size. func (p *PTY) Resize(cols, rows uint16) { if cols == 0 || rows == 0 { return } _ = pty.Setsize(p.f, &pty.Winsize{Rows: rows, Cols: cols}) } // Close terminates the shell and releases the PTY. func (p *PTY) Close() error { err := p.f.Close() if p.cmd.Process != nil { _ = p.cmd.Process.Kill() } _ = p.cmd.Wait() return err }