The TUI is now a TypeScript/opentui app in tui/ rather than Bubble Tea.
opentui is a Zig core with TypeScript bindings and no Go bindings, so this half
of the tool can't live in the Go binary; it compiles with Bun into a sibling
executable (rtsp-streamer-tui) that `rtsp-streamer tui` execs.
Everything that isn't presentation stays in Go, reached over three JSON
commands. The configurator holds no credentials and never writes the config
itself:
config export the config, plus limits like max_tiles
config apply (stdin) merge cameras/layouts/active_layout, validate, save
atomically, reload the daemon
discover --json Protect discovery, writing nothing
Two properties of that split are deliberate:
- The controller password never crosses the bridge. It's json:"-" on the way
out, and apply only merges the three keys the TUI edits, so it can't be
clobbered on the way back in either.
- apply re-reads the file before merging, so an editor left open for an hour can
no longer overwrite a `views import`, a `layout set`, or a hand edit made in
the meantime.
Discovery is previewable as a result: `discover --json` writes nothing, the
merge happens in the TUI, and nothing reaches disk until you save. Only
--enable-rtsp has a side effect, and it's on the controller.
Config structs gain json tags mirroring their yaml ones so the config
round-trips through the bridge under the same key names it has on disk, and
maxGridDim moves to config.MaxGridDim so the CLI and both configurators enforce
one ceiling. The write path is byte-for-byte identical to `layout set`, checked
against a copy of a live config.
Visible change: the grid editor draws real bordered boxes, so a spanning tile is
one box instead of an origin cell plus "·" continuation marks, and the
header-offset arithmetic in mouse.go is gone — the framework hit-tests list
rows. Keybindings, the lipgloss palette and the screen flow are carried over
unchanged; S now saves from anywhere.
The Bubble Tea version stays as `tui --legacy`. It's compiled into the Go binary
and needs no Bun, and on a headless Pi the TUI is the only config UI there is,
so a fallback is worth its weight. The cost of the new one is size: ~120 MB
against ~13 MB, since Bun embeds its runtime and opentui's native library.
Tests: 67 bun tests drive the real (in-memory) opentui renderer, including mouse
click and drag, plus tsc --noEmit. `make test-tui` runs both, and
scripts/preview.ts dumps every screen as text without needing a terminal.
Three bugs found during the port are documented in tui/README.md, since none are
apparent from the code: overlapping cell borders render as ┌ where a lattice
needs ┬; a drag dies after the first resize if the tree is rebuilt, because the
renderer captures the press-target renderable; and a rebuilt box has no computed
layout until the next frame, so its screenX reads 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
118 lines
4.5 KiB
Makefile
118 lines
4.5 KiB
Makefile
BINARY := rtsp-streamer
|
|
PKG := ./cmd/rtsp-streamer
|
|
BINDIR := bin
|
|
|
|
# The interactive configurator is a separate executable: it is built with
|
|
# opentui, which is a TypeScript library, so Bun compiles it rather than Go.
|
|
# See tui/README.md.
|
|
TUIBINARY := rtsp-streamer-tui
|
|
TUIDIR := tui
|
|
BUN ?= $(shell command -v bun 2>/dev/null || echo $$HOME/.bun/bin/bun)
|
|
|
|
# Build metadata baked into the binary (see `rtsp-streamer version`).
|
|
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
|
|
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none)
|
|
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
LDFLAGS := -s -w \
|
|
-X main.version=$(VERSION) \
|
|
-X main.commit=$(COMMIT) \
|
|
-X main.date=$(DATE)
|
|
|
|
# Install location. Override with `make install PREFIX=/opt`.
|
|
PREFIX ?= /usr/local
|
|
DESTBIN := $(PREFIX)/bin/$(BINARY)
|
|
DESTTUI := $(PREFIX)/bin/$(TUIBINARY)
|
|
|
|
# Use sudo automatically when not already root, so `make install` /
|
|
# `make deploy` work whether or not you prefix them with sudo.
|
|
SUDO := $(shell [ "$$(id -u)" -eq 0 ] || echo sudo)
|
|
|
|
.PHONY: all build pi pi32 test test-tui vet clean run-tui tui tui-pi tui-deps install deploy uninstall
|
|
|
|
# Go only, so a plain `make` still works on a machine without Bun. The
|
|
# configurator is an explicit `make tui`.
|
|
all: build
|
|
|
|
## build: compile for the host platform (native arch)
|
|
build:
|
|
go build -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(BINARY) $(PKG)
|
|
|
|
## pi: cross-compile for Raspberry Pi 4 running a 64-bit OS (arm64)
|
|
pi:
|
|
GOOS=linux GOARCH=arm64 go build -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(BINARY)-arm64 $(PKG)
|
|
|
|
## pi32: cross-compile for a 32-bit Pi OS (armv7)
|
|
pi32:
|
|
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(BINARY)-armv7 $(PKG)
|
|
|
|
## tui-deps: install the configurator's node_modules (needs Bun)
|
|
tui-deps:
|
|
@command -v $(BUN) >/dev/null 2>&1 || { \
|
|
echo "bun not found. Install it with: curl -fsSL https://bun.sh/install | bash"; \
|
|
exit 1; }
|
|
cd $(TUIDIR) && $(BUN) install
|
|
|
|
## tui: compile the opentui configurator to a standalone binary. Bun embeds its
|
|
## own runtime and opentui's native library, so the result needs nothing
|
|
## installed on the target — but it is ~120 MB, unlike the ~8 MB Go binary.
|
|
tui: tui-deps
|
|
cd $(TUIDIR) && $(BUN) build --compile src/index.ts --outfile ../$(BINDIR)/$(TUIBINARY)
|
|
|
|
## tui-pi: cross-compile the configurator for a 64-bit Pi (arm64), the analogue
|
|
## of `make pi`. Pulls in the arm64 native package first, since Bun embeds
|
|
## opentui's native library rather than resolving it at run time. Defining
|
|
## OPENTUI_LIBC lets Bun drop the musl branch and embed only glibc.
|
|
tui-pi:
|
|
cd $(TUIDIR) && $(BUN) install --os=linux --cpu=arm64
|
|
cd $(TUIDIR) && $(BUN) build --compile --target=bun-linux-arm64 \
|
|
--define process.env.OPENTUI_LIBC='"glibc"' \
|
|
src/index.ts --outfile ../$(BINDIR)/$(TUIBINARY)-arm64
|
|
|
|
## install: build for this machine and copy the binaries to $(PREFIX)/bin
|
|
## (auto-sudo). This is the location the running daemon uses. The configurator
|
|
## is installed alongside it, which is where `rtsp-streamer tui` looks first.
|
|
install: build
|
|
$(SUDO) install -m 0755 $(BINDIR)/$(BINARY) $(DESTBIN)
|
|
@echo "installed -> $(DESTBIN)"
|
|
@if [ -x "$(BINDIR)/$(TUIBINARY)" ]; then \
|
|
$(SUDO) install -m 0755 $(BINDIR)/$(TUIBINARY) $(DESTTUI); \
|
|
echo "installed -> $(DESTTUI)"; \
|
|
else \
|
|
echo "note: $(TUIBINARY) not built (run 'make tui'); 'rtsp-streamer tui' will"; \
|
|
echo " not start until it is. 'rtsp-streamer tui --legacy' works regardless."; \
|
|
fi
|
|
|
|
## deploy: install, then restart the running wall in place (no reboot). Run as
|
|
## the kiosk user so it reaches the daemon's control socket. Falls back to a
|
|
## note if the daemon isn't up yet (it will start on next boot).
|
|
deploy: install
|
|
@$(DESTBIN) restart || echo "daemon not running; it will start on next boot"
|
|
@echo "run 'rtsp-streamer status' to check"
|
|
|
|
## uninstall: remove the installed binaries
|
|
uninstall:
|
|
$(SUDO) rm -f $(DESTBIN) $(DESTTUI)
|
|
@echo "removed $(DESTBIN) and $(DESTTUI)"
|
|
|
|
## test: run Go unit tests
|
|
test:
|
|
go test ./...
|
|
|
|
## test-tui: run the configurator's tests (needs Bun)
|
|
test-tui: tui-deps
|
|
cd $(TUIDIR) && $(BUN) test && $(BUN) x tsc --noEmit
|
|
|
|
## vet: run go vet
|
|
vet:
|
|
go vet ./...
|
|
|
|
## run-tui: launch the configurator from source against a scratch config, with
|
|
## no compile step — the fast loop while working on tui/.
|
|
run-tui: build
|
|
cd $(TUIDIR) && RTSP_STREAMER_BIN=$(CURDIR)/$(BINDIR)/$(BINARY) \
|
|
$(BUN) run src/index.ts --config /tmp/rtsp-streamer.yaml
|
|
|
|
## clean: remove build artifacts
|
|
clean:
|
|
rm -rf $(BINDIR)
|