package main import ( "context" "errors" "fmt" "os" "os/exec" "path/filepath" ) // tuiBinaryName is the compiled opentui configurator (see tui/ and `make tui`). const tuiBinaryName = "rtsp-streamer-tui" // findTUIBinary locates the opentui configurator. It is a sibling binary rather // than something embedded in this one, because opentui is a TypeScript library // compiled by Bun. // // Search order, most specific first: // // 1. $RTSP_STREAMER_TUI — an explicit path, for development // 2. next to this executable — how `make install` lays it out // 3. $PATH func findTUIBinary() (string, error) { if p := os.Getenv("RTSP_STREAMER_TUI"); p != "" { if isExecutableFile(p) { return p, nil } return "", fmt.Errorf("RTSP_STREAMER_TUI=%s is not an executable file", p) } if exe, err := os.Executable(); err == nil { if resolved, err := filepath.EvalSymlinks(exe); err == nil { exe = resolved } if cand := filepath.Join(filepath.Dir(exe), tuiBinaryName); isExecutableFile(cand) { return cand, nil } } if p, err := exec.LookPath(tuiBinaryName); err == nil { return p, nil } return "", fmt.Errorf( "%s not found — build it with `make tui` (needs Bun) and install it with\n"+ "`make install`, or point $RTSP_STREAMER_TUI at it.\n"+ "To use the previous Bubble Tea configurator instead, run: rtsp-streamer tui --legacy", tuiBinaryName) } func isExecutableFile(path string) bool { st, err := os.Stat(path) return err == nil && !st.IsDir() && st.Mode()&0o111 != 0 } // execTUI runs the configurator with this process's terminal, so it can take // raw mode and the alternate screen directly. Its exit status becomes ours. func execTUI(ctx context.Context, bin, cfg string) error { c := exec.CommandContext(ctx, bin, "--config", cfg) c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr // The TUI shells back to this binary for the bridge commands; tell it which // one, so a locally-built binary is not shadowed by an installed one. env := append(os.Environ(), "RTSP_STREAMER_CONFIG="+cfg) if self, err := os.Executable(); err == nil { env = append(env, "RTSP_STREAMER_BIN="+self) } c.Env = env err := c.Run() var exitErr *exec.ExitError if errors.As(err, &exitErr) { // The TUI already reported whatever went wrong on its own terminal. os.Exit(exitErr.ExitCode()) } return err }