Add vsock-backed SSH session reminders

Remind users when a VM is still running after 	hanger vm ssh exits instead of silently dropping them back to the host shell.\n\nAttach a Firecracker vsock device to each VM, persist the host vsock path/CID,\nadd a new guest-side banger-vsock-pingd responder to the runtime bundle and both\nimage-build paths, and expose a vm.ping RPC that the CLI and TUI call after SSH\nreturns. Doctor and start/build preflight now validate the helper plus\n/dev/vhost-vsock so the feature fails early and clearly.\n\nValidated with go mod tidy, bash -n customize.sh, git diff --check, make build,\nand GOCACHE=/tmp/banger-gocache go test ./... outside the sandbox because the\ndaemon tests need real Unix/UDP sockets. Rebuild the image/rootfs used for new\nVMs so the guest ping service is present.
This commit is contained in:
Thales Maciel 2026-03-18 20:14:51 -03:00
parent 4930d82cb9
commit 08ef706e3f
No known key found for this signature in database
GPG key ID: 33112E6833C34679
31 changed files with 912 additions and 75 deletions

View file

@ -4,7 +4,9 @@ import (
"bytes"
"context"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
@ -209,6 +211,56 @@ func TestVMSetParamsFromFlagsRejectsNonPositiveCPUAndMemory(t *testing.T) {
}
}
func TestRunSSHSessionPrintsReminderWhenPingAlive(t *testing.T) {
origSSHExec := sshExecFunc
origPing := vmPingFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmPingFunc = origPing
})
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return nil
}
vmPingFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMPingResult, error) {
return api.VMPingResult{Name: "devbox", Alive: true}, nil
}
var stderr bytes.Buffer
if err := runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"}); err != nil {
t.Fatalf("runSSHSession: %v", err)
}
if !strings.Contains(stderr.String(), "devbox is still running") {
t.Fatalf("stderr = %q, want reminder", stderr.String())
}
}
func TestRunSSHSessionPreservesSSHExitStatusOnPingWarning(t *testing.T) {
origSSHExec := sshExecFunc
origPing := vmPingFunc
t.Cleanup(func() {
sshExecFunc = origSSHExec
vmPingFunc = origPing
})
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
return &exec.ExitError{}
}
vmPingFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMPingResult, error) {
return api.VMPingResult{}, errors.New("dial failed")
}
var stderr bytes.Buffer
err := runSSHSession(context.Background(), "/tmp/bangerd.sock", "devbox", strings.NewReader(""), &bytes.Buffer{}, &stderr, []string{"root@127.0.0.1"})
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) {
t.Fatalf("runSSHSession error = %v, want exit error", err)
}
if !strings.Contains(stderr.String(), "failed to check whether devbox is still running") {
t.Fatalf("stderr = %q, want warning", stderr.String())
}
}
func TestResolveVMTargetsDeduplicatesAndReportsErrors(t *testing.T) {
vms := []model.VMRecord{
testCLIResolvedVM("alpha-id", "alpha"),