Terminal: show the shell's error output, so a missing shell is explained
The shell's stderr was discarded, so a shell that could not start looked like an immediate disconnect. It now goes to the terminal like other output, followed by the exit code. An end-to-end test covers a machine with no shell. Builds against Bamboo v0.1.10, which says why when there is no shell to run. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
35d4000bc4
commit
7481b2d01c
4 files changed
+43
-3
No files matched your search
@@ -137,6 +137,33 @@ func TestWebTerminalWithoutAnyOpenPort(t *testing.T) {
|
|||||||
t.Errorf("the test opened a port after all: %+v", maps.Mappings)
|
t.Errorf("the test opened a port after all: %+v", maps.Mappings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a machine with no shell (the published container image has none) says why, instead of the
|
||||||
|
// terminal just disconnecting: the shell's error output reaches the browser, then the exit code
|
||||||
|
if sshd.Supported {
|
||||||
|
t.Setenv("SHELL", "/nonexistent/shell")
|
||||||
|
ws := dial()
|
||||||
|
defer ws.Close()
|
||||||
|
var out bytes.Buffer
|
||||||
|
var exit termMsg
|
||||||
|
for exit.Type != "exit" {
|
||||||
|
ws.SetReadDeadline(time.Now().Add(15 * time.Second))
|
||||||
|
var f []byte
|
||||||
|
if err := websocket.Message.Receive(ws, &f); err != nil {
|
||||||
|
t.Fatalf("waiting for the shell to end (got %q): %v", out.String(), err)
|
||||||
|
}
|
||||||
|
if len(f) > 0 && f[0] == '{' {
|
||||||
|
if m := parseTermMsg(f); m.Type == "exit" {
|
||||||
|
exit = m
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out.Write(f)
|
||||||
|
}
|
||||||
|
if exit.Code != 127 || !strings.Contains(out.String(), "no shell to run") {
|
||||||
|
t.Errorf("a missing shell: exit %d, output %q", exit.Code, out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// the SSH server still decides who gets in
|
// the SSH server still decides who gets in
|
||||||
other := newSSHSigner(t)
|
other := newSSHSigner(t)
|
||||||
if err := os.WriteFile(keys, ssh.MarshalAuthorizedKey(other.PublicKey()), 0o600); err != nil {
|
if err := os.WriteFile(keys, ssh.MarshalAuthorizedKey(other.PublicKey()), 0o600); err != nil {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module git.veillax.com/Veillax/hammerhead
|
|||||||
go 1.27.3
|
go 1.27.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.veillax.com/Veillax/bamboo v0.1.9
|
git.veillax.com/Veillax/bamboo v0.1.10
|
||||||
git.veillax.com/Veillax/blacktip v0.1.10
|
git.veillax.com/Veillax/blacktip v0.1.10
|
||||||
git.veillax.com/Veillax/reef v0.1.12
|
git.veillax.com/Veillax/reef v0.1.12
|
||||||
github.com/emersion/go-msgauth v0.7.0
|
github.com/emersion/go-msgauth v0.7.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.veillax.com/Veillax/bamboo v0.1.9 h1:D+DWIFRxy2MZ9Oa7fRrdeM7xNUBZPwjfO5CRMgc9+q4=
|
git.veillax.com/Veillax/bamboo v0.1.10 h1:R21+L54cS87VXmOS4eAsfRwp6vL+ORLqJUBaRHBIlJE=
|
||||||
git.veillax.com/Veillax/bamboo v0.1.9/go.mod h1:Z3O7xoa52mFaADs7RxaBQl0a0K+5HlHPB1eOYQjM15k=
|
git.veillax.com/Veillax/bamboo v0.1.10/go.mod h1:Z3O7xoa52mFaADs7RxaBQl0a0K+5HlHPB1eOYQjM15k=
|
||||||
git.veillax.com/Veillax/blacktip v0.1.10 h1:2e1wUbMCp4z/DvQjAJCRmLyqu4PX+qV3mo5WBliqGiE=
|
git.veillax.com/Veillax/blacktip v0.1.10 h1:2e1wUbMCp4z/DvQjAJCRmLyqu4PX+qV3mo5WBliqGiE=
|
||||||
git.veillax.com/Veillax/blacktip v0.1.10/go.mod h1:tarAhdKU9hx5pdufe9wG3dRWYkWgPYZvawKM5DWgt6o=
|
git.veillax.com/Veillax/blacktip v0.1.10/go.mod h1:tarAhdKU9hx5pdufe9wG3dRWYkWgPYZvawKM5DWgt6o=
|
||||||
git.veillax.com/Veillax/reef v0.1.12 h1:Yizgcbh/TUz/dkO/4F+MOZkXlN7FU3/et/5uq1dH72s=
|
git.veillax.com/Veillax/reef v0.1.12 h1:Yizgcbh/TUz/dkO/4F+MOZkXlN7FU3/et/5uq1dH72s=
|
||||||
|
|||||||
+13
@@ -360,6 +360,9 @@ func (s *Server) runTerminal(ws *websocket.Conn, c *orgCtx, siteID string, cols,
|
|||||||
problem(err.Error())
|
problem(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// What the shell writes to its error output (including why it could not start) goes to the
|
||||||
|
// terminal like everything else, instead of being thrown away.
|
||||||
|
sess.Stderr = termWriter(func(p []byte) error { return send(termFrame{data: append([]byte(nil), p...)}) })
|
||||||
if err := sess.Shell(); err != nil {
|
if err := sess.Shell(); err != nil {
|
||||||
problem(fmt.Sprintf("Couldn't start a shell: %v", err))
|
problem(fmt.Sprintf("Couldn't start a shell: %v", err))
|
||||||
return
|
return
|
||||||
@@ -566,3 +569,13 @@ func clipText(s string, n int) string {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// termWriter sends what is written to it to the browser as terminal output.
|
||||||
|
type termWriter func(p []byte) error
|
||||||
|
|
||||||
|
func (w termWriter) Write(p []byte) (int, error) {
|
||||||
|
if err := w(p); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
Reference in new issue
Block a user