feat: interactive terminal — WS-bridged docker exec PTY (#46)

GET /services/:id/terminal upgrades to a WebSocket attached to a
`docker exec` PTY in the service's running replica. JSON frames both
ways: stdin/resize out, stdout/exit/error in. Shell probe tries each
candidate (bash→sh) since `command -v` needs a shell to run; distroless
images get a clean "no shell" error instead of an OCI failure dump.

- docker.Client gains ExecAttach + ExecResize wrappers
- Frontend: xterm.js replaces the one-off console section — live PTY,
  fit-addon resizes forwarded over the socket
- CLI: `containr shell <id>` bridges the local TTY (raw mode, SIGWINCH
  resize forwarding); `containr exec` keeps the one-off command path
- Access mirrors service logs: project manage scope required

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomáš DvořákandDevin authored and GitHub committed 2026-10-09 11:13:34 +02:00
1 parent 5eac4a382b
commit 612520bbff
13 files changed
+570 -75

No files matched your search

+15
View File
@@ -516,6 +516,21 @@ func (c *Client) ExecRun(ctx context.Context, containerID string, cmd []string)
return buf.String(), inspect.ExitCode, nil
}
// ExecAttach attaches to a running exec instance, returning the hijacked
// bidirectional stream. With Tty enabled the stream carries raw pty data
// (no stdcopy multiplexing).
func (c *Client) ExecAttach(ctx context.Context, execID string) (types.HijackedResponse, error) {
return c.cli.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{})
}
// ExecResize resizes the pty of a running exec instance.
func (c *Client) ExecResize(ctx context.Context, execID string, cols, rows uint) error {
return c.cli.ContainerExecResize(ctx, execID, container.ResizeOptions{
Height: rows,
Width: cols,
})
}
// GetImageInfo returns information about a Docker image
func (c *Client) GetImageInfo(ctx context.Context, imageName string) (*ImageInfo, error) {
images, err := c.cli.ImageList(ctx, image.ListOptions{})