package cli import ( "context" "flag" "fmt" "os" "os/exec" "syscall" "github.com/davis7dotsh/tx9/internal/box" "github.com/davis7dotsh/tx9/internal/docker" ) // cmdEnter implements `tx9 enter ` (alias `ssh`): exec into the agent // container as `agent`, dropping into the box's persistent tmux `main` // session. Starts the box first if it isn't already running (no readiness // retry loop here — that's `create`/`upgrade`'s job; enter just needs the // containers up so `docker exec` has somewhere to attach). // // This replicates boxd's own `cmd_enter` exactly (recovered from git history // at 6f1ced3, docker/boxd): `docker exec -it -u agent ... bash -l`. The // login shell is what triggers guest/agent-bash-profile.sh, which runs // `hb up`/`hb wire-once` and then execs into the tmux `main` session — there // is no separate `hb enter` subcommand to invoke. func cmdEnter(args []string) error { fs := flag.NewFlagSet("enter", flag.ContinueOnError) if err := parseFlagsAnywhere(fs, args); err != nil { return err } name, err := requireBoxName(fs, "enter") if err != nil { return err } var agentID, tok string err = withBoxLock(name, func(ctx context.Context, cli *docker.Client) error { b, err := box.Get(ctx, cli, name) if err != nil { return fmt.Errorf("enter %s: %w", name, err) } if b.AgentState != "running" || b.ExecutorState != "running" { if err := box.Start(ctx, cli, b); err != nil { return fmt.Errorf("enter %s: start: %w", name, err) } } tok, err = box.Token(name) if err != nil { return fmt.Errorf("enter %s: %w", name, err) } agentID = b.AgentID return nil }) if err != nil { return err } dockerBin, err := exec.LookPath("docker") if err != nil { return fmt.Errorf("enter %s: docker binary not found on PATH: %w", name, err) } argv := []string{ dockerBin, "exec", "-it", "-u", "agent", "-e", "HOME=/data/home/agent", "-e", "USER=agent", "-e", "LOGNAME=agent", "-e", "SHELL=/bin/bash", "-e", "EXECUTOR_HOST=executor", "-e", "BOXD_EXECUTOR_TOKEN=" + tok, "-w", "/data/home/agent", agentID, "bash", "-l", } // syscall.Exec replaces this process outright so the TTY (job control, // signals, resize) is docker's problem from here on, exactly as boxd's // own `exec docker exec ...` did. if err := syscall.Exec(dockerBin, argv, os.Environ()); err != nil { return fmt.Errorf("enter %s: exec docker: %w", name, err) } return nil }