MOBUX_PORT ?= 5151
MOBUX_DEV_PORT ?= 5152
MOBUX_SMOKE_PORT ?= 8281
MOBUX_SMOKE_DATA ?= /tmp/mobux-smoke
MOBUX_SMOKE_TMUX ?= mobux-test
export MOBUX_SMOKE_TMUX
MOBUX_PROXY_FIXTURE_PORT ?= $(shell expr $(MOBUX_SMOKE_PORT) + 10)
MOBUX_SMOKE_MCP_PORT ?= $(shell expr $(MOBUX_SMOKE_PORT) + 13)
MOBUX_ACCESS_SMOKE_PORT ?= $(shell expr $(MOBUX_SMOKE_PORT) + 12)
MOBUX_ACCESS_JWKS_PORT ?= $(shell expr $(MOBUX_SMOKE_PORT) + 11)
MOBUX_ACCESS_SMOKE_AUD ?= mobux-smoke-aud
MOBUX_ACCESS_SMOKE_EMAIL ?= smoke@example.com
MOBUX_USER ?= $(USER)
MOBUX_PIN ?= 30879
CARGO := $(HOME)/.cargo/bin/cargo
MOBUX_PACKAGE_ID ?= io.github.mvhenten.mobux
MOBUX_APP_NAME ?= Mobux
TWA_INSTALL_DIR ?= web/static/install
TWA_WELLKNOWN_DIR ?= web/static/.well-known
MOBUX_DEV_DOMAIN ?= sandbox:5152
PID := $(shell lsof -ti :$(MOBUX_PORT) 2>/dev/null)
SMOKE_PID := $(shell lsof -ti :$(MOBUX_SMOKE_PORT) 2>/dev/null)
.PHONY: build run dev dev-watch _dev-bounce clean start stop restart status logs test web setup setup-twa twa twa-dev \
transcribe setup-transcribe tts-voice \
smoke-start smoke-stop smoke-logs smoke-status \
test-smoke test-critical-path test-update-runner test-install test-spa test-reader test-reader-grouping test-read-mode-scrollback test-stt-ux test-stt-per-kind test-e2e \
podman-build podman-run podman-stop podman-test
PODMAN_IMAGE ?= localhost/mobux:dev
PODMAN_PORT ?= 8381
PODMAN_NAME ?= mobux-podman
WHISPER_DIR ?= $(HOME)/.local/whisper.cpp
WHISPER_MODEL_NAME ?= base.en
setup:
./bin/setup
setup-twa:
./bin/setup-twa
transcribe:
@if [ -z "$(FILE)" ]; then echo "usage: make transcribe FILE=<audio-or-video-file>" >&2; exit 2; fi
@WHISPER_DIR="$(WHISPER_DIR)" ./bin/transcribe "$(FILE)"
setup-transcribe:
@command -v ffmpeg >/dev/null 2>&1 || { echo "ffmpeg is required (apt install ffmpeg)"; exit 1; }
@if [ ! -d "$(WHISPER_DIR)" ]; then \
git clone --depth 1 https://github.com/ggerganov/whisper.cpp "$(WHISPER_DIR)"; \
fi
cmake -B "$(WHISPER_DIR)/build" -S "$(WHISPER_DIR)" -DCMAKE_BUILD_TYPE=Release
cmake --build "$(WHISPER_DIR)/build" -j --config Release
bash "$(WHISPER_DIR)/models/download-ggml-model.sh" "$(WHISPER_MODEL_NAME)" "$(WHISPER_DIR)/models"
@echo "whisper.cpp ready at $(WHISPER_DIR) (model: $(WHISPER_MODEL_NAME))"
web:
node web/build.js
tts-voice:
@node scripts/tts-voice.mjs ensure .tmp/tts-voice
clean:
$(CARGO) clean -p mobux
build: web
$(CARGO) build
run: build
env MOBUX_AUTH_USER=$(MOBUX_USER) MOBUX_PIN=$(MOBUX_PIN) MOBUX_PORT=$(MOBUX_PORT) \
MOBUX_TLS=1 $(CARGO) run
dev: build
env MOBUX_AUTH_USER=$(MOBUX_USER) MOBUX_PIN=$(MOBUX_PIN) MOBUX_DEV=1 MOBUX_PORT=$(MOBUX_DEV_PORT) \
MOBUX_TLS=1 $(CARGO) run
dev-watch: build
cargo watch -w src -w Cargo.toml -x build -s '$(MAKE) _dev-bounce'
_dev-bounce:
-@kill $$(lsof -ti :$(MOBUX_DEV_PORT)) 2>/dev/null || true
@sleep 1
@nohup env MOBUX_AUTH_USER=$(MOBUX_USER) MOBUX_PIN=$(MOBUX_PIN) MOBUX_DEV=1 MOBUX_PORT=$(MOBUX_DEV_PORT) \
MOBUX_TLS=1 ./target/debug/mobux > /tmp/mobux-dev.log 2>&1 &
@sleep 2 && lsof -i :$(MOBUX_DEV_PORT) >/dev/null 2>&1 \
&& echo "dev :$(MOBUX_DEV_PORT) rebuilt + restarted" || echo "FAILED to restart :$(MOBUX_DEV_PORT)"
spa-dev:
@cd web/spa && env MOBUX_BACKEND=https://127.0.0.1:$(MOBUX_DEV_PORT) \
MOBUX_DEV_AUTH='$(MOBUX_USER):$(MOBUX_PIN)' \
npm run dev -- --host 0.0.0.0
DEV_LOG_DIR ?= $(HOME)/development/.tmp/mobux-dev
dev-up: build
@mkdir -p $(DEV_LOG_DIR)
@if lsof -i :$(MOBUX_DEV_PORT) >/dev/null 2>&1; then echo "backend :$(MOBUX_DEV_PORT) already up"; else \
setsid nohup env MOBUX_AUTH_USER=$(MOBUX_USER) MOBUX_PIN=$(MOBUX_PIN) MOBUX_DEV=1 \
MOBUX_PORT=$(MOBUX_DEV_PORT) MOBUX_TLS=1 ./target/debug/mobux \
> $(DEV_LOG_DIR)/backend.log 2>&1 < /dev/null & \
fi
@if lsof -i :5173 >/dev/null 2>&1; then echo "spa :5173 already up"; else \
cd web/spa && setsid nohup env MOBUX_BACKEND=https://127.0.0.1:$(MOBUX_DEV_PORT) \
MOBUX_DEV_AUTH='$(MOBUX_USER):$(MOBUX_PIN)' \
npm run dev -- --host 0.0.0.0 \
> $(DEV_LOG_DIR)/spa.log 2>&1 < /dev/null & \
fi
@sleep 6
@$(MAKE) --no-print-directory dev-status
dev-down:
-@kill $$(lsof -ti :$(MOBUX_DEV_PORT)) 2>/dev/null || true
-@kill $$(lsof -ti :5173) 2>/dev/null || true
@echo "dev servers stopped"
dev-status:
@lsof -i :$(MOBUX_DEV_PORT) >/dev/null 2>&1 && echo "backend :$(MOBUX_DEV_PORT) up" || echo "backend :$(MOBUX_DEV_PORT) DOWN"
@lsof -i :5173 >/dev/null 2>&1 && echo "spa :5173 up -> https://$(shell hostname):5173/static/spa/" || echo "spa :5173 DOWN"
dev-logs:
@tail -f $(DEV_LOG_DIR)/backend.log $(DEV_LOG_DIR)/spa.log
start: build
@if [ -n "$(PID)" ]; then echo "already running (pid $(PID))"; exit 1; fi
nohup env MOBUX_AUTH_USER=$(MOBUX_USER) MOBUX_PIN=$(MOBUX_PIN) MOBUX_PORT=$(MOBUX_PORT) \
MOBUX_TLS=1 ./target/debug/mobux > /tmp/mobux.log 2>&1 &
@sleep 2 && lsof -i :$(MOBUX_PORT) >/dev/null 2>&1 && echo "mobux running on port $(MOBUX_PORT)" || echo "FAILED to start"
stop:
@if [ -z "$(PID)" ]; then echo "not running"; exit 0; fi
kill $(PID) && echo "stopped (pid $(PID))"
restart: stop
@sleep 2
@$(MAKE) start
status:
@if [ -n "$(PID)" ]; then echo "running (pid $(PID)) on port $(MOBUX_PORT)"; else echo "not running"; fi
logs:
@tail -f /tmp/mobux.log
smoke-start: build
@if [ -n "$(SMOKE_PID)" ]; then echo "smoke already running (pid $(SMOKE_PID)) on $(MOBUX_SMOKE_PORT)"; exit 1; fi
@if [ "$(MOBUX_SMOKE_PORT)" = "$(MOBUX_PORT)" ]; then echo "MOBUX_SMOKE_PORT must differ from MOBUX_PORT"; exit 1; fi
@mkdir -p $(MOBUX_SMOKE_DATA)/home
@ @ @env -u TMUX -u TMUX_PANE HOME=$(MOBUX_SMOKE_DATA)/home HISTFILE=/dev/null \
tmux -L $(MOBUX_SMOKE_TMUX) start-server \; set-option -s exit-empty off
@nohup env MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) MOBUX_TLS=0 \
HOME=$(MOBUX_SMOKE_DATA)/home HISTFILE=/dev/null \
MOBUX_TMUX_SOCKET=$(MOBUX_SMOKE_TMUX) \
MOBUX_UPDATE_TEST_INDEX='{"name":"mobux","vers":"999.0.0","yanked":false}' \
MOBUX_UPDATE_CHECK_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT)/api/update/test-index \
MOBUX_UPDATE_DISABLE_RUN=1 \
MOBUX_FILES=site=$(CURDIR)/test/assets/files-site \
MOBUX_PROXY=up=$(MOBUX_PROXY_FIXTURE_PORT) \
MOBUX_MCP_PORT=$(MOBUX_SMOKE_MCP_PORT) \
MOBUX_ACCESS_PORT=$(MOBUX_ACCESS_SMOKE_PORT) \
MOBUX_ACCESS_TEAM_DOMAIN=http://127.0.0.1:$(MOBUX_ACCESS_JWKS_PORT) \
MOBUX_ACCESS_AUD=$(MOBUX_ACCESS_SMOKE_AUD) \
MOBUX_ACCESS_ALLOWED_EMAILS=$(MOBUX_ACCESS_SMOKE_EMAIL) \
MOBUX_PORT=$(MOBUX_SMOKE_PORT) MOBUX_AUTH_USER=smoke MOBUX_PIN=00000 \
./target/debug/mobux > $(MOBUX_SMOKE_DATA)/mobux.log 2>&1 < /dev/null &
@sleep 2 && lsof -i :$(MOBUX_SMOKE_PORT) >/dev/null 2>&1 \
&& echo "smoke mobux running on port $(MOBUX_SMOKE_PORT) (data $(MOBUX_SMOKE_DATA))" \
|| { echo "smoke FAILED to start"; tail $(MOBUX_SMOKE_DATA)/mobux.log; exit 1; }
smoke-stop:
@if [ -n "$(SMOKE_PID)" ]; then kill $(SMOKE_PID) && echo "smoke stopped (pid $(SMOKE_PID))"; else echo "smoke not running"; fi
@env -u TMUX -u TMUX_PANE tmux -L $(MOBUX_SMOKE_TMUX) kill-server 2>/dev/null || true
smoke-logs:
@tail -f $(MOBUX_SMOKE_DATA)/mobux.log
smoke-status:
@if [ -n "$(SMOKE_PID)" ]; then echo "smoke running (pid $(SMOKE_PID)) on port $(MOBUX_SMOKE_PORT)"; else echo "smoke not running"; fi
test:
MOBUX_USER=$(MOBUX_USER) MOBUX_PASS=$(MOBUX_PIN) npx playwright test
.PHONY: test-smoke
test-smoke:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/smoke.spec.cjs
.PHONY: test-node
test-node:
node --test --test-timeout=30000 test/terminal-text.test.mjs
.PHONY: test-critical-path
test-critical-path: test-node
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
MOBUX_PROXY_FIXTURE_PORT=$(MOBUX_PROXY_FIXTURE_PORT) \
MOBUX_ACCESS_PORT=$(MOBUX_ACCESS_SMOKE_PORT) \
MOBUX_ACCESS_JWKS_PORT=$(MOBUX_ACCESS_JWKS_PORT) \
MOBUX_ACCESS_AUD=$(MOBUX_ACCESS_SMOKE_AUD) \
MOBUX_ACCESS_EMAIL=$(MOBUX_ACCESS_SMOKE_EMAIL) \
npx playwright test test/critical-path.spec.cjs test/native-select.spec.cjs test/files.spec.cjs test/proxy.spec.cjs test/access.spec.cjs && \
$(MCP_TEST)
MCP_TEST = MOBUX_MCP_URL=http://127.0.0.1:$(MOBUX_SMOKE_MCP_PORT)/mcp \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
node --test --test-timeout=30000 test/mcp.test.mjs
.PHONY: test-mcp
test-mcp:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; $(MCP_TEST)
.PHONY: test-native-select
test-native-select:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/native-select.spec.cjs
.PHONY: test-files
test-files:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/files.spec.cjs
.PHONY: test-proxy
test-proxy:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
MOBUX_PROXY_FIXTURE_PORT=$(MOBUX_PROXY_FIXTURE_PORT) \
npx playwright test test/proxy.spec.cjs
.PHONY: test-access
test-access:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
MOBUX_PROXY_FIXTURE_PORT=$(MOBUX_PROXY_FIXTURE_PORT) \
MOBUX_ACCESS_PORT=$(MOBUX_ACCESS_SMOKE_PORT) \
MOBUX_ACCESS_JWKS_PORT=$(MOBUX_ACCESS_JWKS_PORT) \
MOBUX_ACCESS_AUD=$(MOBUX_ACCESS_SMOKE_AUD) \
MOBUX_ACCESS_EMAIL=$(MOBUX_ACCESS_SMOKE_EMAIL) \
npx playwright test test/access.spec.cjs
.PHONY: test-update-runner
test-update-runner:
@command -v shellcheck >/dev/null 2>&1 && shellcheck src/update_runner.sh test/update-runner.test.sh || echo "shellcheck not installed; skipping lint"
@bash test/update-runner.test.sh
.PHONY: test-install
test-install:
@command -v shellcheck >/dev/null 2>&1 && shellcheck install.sh test/install.test.sh || echo "shellcheck not installed; skipping lint"
@bash test/install.test.sh
.PHONY: test-stt-ux
test-stt-ux:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_STT_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_STT_USER=smoke MOBUX_STT_PASS=00000 \
npx playwright test test/stt-ux.spec.cjs
.PHONY: test-stt-per-kind
test-stt-per-kind:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_STT_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_STT_USER=smoke MOBUX_STT_PASS=00000 \
npx playwright test test/stt-per-kind.spec.cjs
.PHONY: test-spa
test-spa:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/spa.spec.cjs test/settings-ui.spec.cjs test/spa-base.spec.cjs test/pages-listing.spec.cjs test/reader-font.spec.cjs test/reader-command-grouping.spec.cjs test/session-history.spec.cjs test/read-mode-render.spec.cjs test/read-mode.spec.cjs test/read-mode-scrollback.spec.cjs test/background-resume.spec.cjs
.PHONY: test-spa-base
test-spa-base:
npx playwright test test/spa-base.spec.cjs
.PHONY: test-reader
test-reader:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/reader-font.spec.cjs
.PHONY: test-reader-grouping
test-reader-grouping:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/reader-command-grouping.spec.cjs
.PHONY: test-read-mode-render
test-read-mode-render:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/read-mode-render.spec.cjs
.PHONY: test-read-mode-scrollback
test-read-mode-scrollback:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/read-mode-scrollback.spec.cjs test/background-resume.spec.cjs
.PHONY: test-read-mode
test-read-mode:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/read-mode.spec.cjs
.PHONY: test-session-history
test-session-history:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/session-history.spec.cjs
.PHONY: test-conformance
test-conformance:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test test/conformance.spec.cjs
.PHONY: test-fleet
test-fleet: build
npx playwright test test/fleet
.PHONY: test-e2e
test-e2e:
@$(MAKE) smoke-start
@trap '$(MAKE) smoke-stop' EXIT; \
MOBUX_URL=http://127.0.0.1:$(MOBUX_SMOKE_PORT) \
MOBUX_DATA_DIR=$(MOBUX_SMOKE_DATA) \
MOBUX_USER=smoke MOBUX_PASS=00000 \
npx playwright test
podman-build:
podman build -t $(PODMAN_IMAGE) -f Containerfile .
podman-run: podman-build
-@podman rm -f $(PODMAN_NAME) >/dev/null 2>&1
@podman run -d --name $(PODMAN_NAME) -p $(PODMAN_PORT):8080 \
-e MOBUX_AUTH_USER=test -e MOBUX_PIN=00000 \
$(PODMAN_IMAGE) >/dev/null
@echo "mobux running in container on http://localhost:$(PODMAN_PORT) (test/00000)"
podman-stop:
-@podman rm -f $(PODMAN_NAME) >/dev/null 2>&1 && echo "stopped $(PODMAN_NAME)" || echo "not running"
podman-test: podman-build
-@podman rm -f $(PODMAN_NAME) >/dev/null 2>&1
@podman run -d --name $(PODMAN_NAME) -p $(PODMAN_PORT):8080 \
-e MOBUX_AUTH_USER=test -e MOBUX_PIN=00000 \
$(PODMAN_IMAGE) >/dev/null
@trap 'podman rm -f $(PODMAN_NAME) >/dev/null 2>&1' EXIT; \
for i in $$(seq 1 30); do \
if curl -fsS -u test:00000 -o /dev/null http://localhost:$(PODMAN_PORT)/ 2>/dev/null; then break; fi; \
sleep 0.5; \
done; \
MOBUX_URL=http://localhost:$(PODMAN_PORT) \
MOBUX_USER=test MOBUX_PASS=00000 \
MOBUX_TEST_TMUX="podman exec $(PODMAN_NAME) tmux" \
npx playwright test
twa:
@env MOBUX_PACKAGE_ID=$(MOBUX_PACKAGE_ID) \
MOBUX_APP_NAME=$(MOBUX_APP_NAME) \
TWA_WORK_DIR=twa \
TWA_INSTALL_DIR=$(TWA_INSTALL_DIR) \
TWA_WELLKNOWN_DIR=$(TWA_WELLKNOWN_DIR) \
./bin/twa-build
twa-dev:
@$(MAKE) twa \
MOBUX_DOMAIN=$(MOBUX_DEV_DOMAIN) \
MOBUX_PACKAGE_ID=io.github.mvhenten.mobux.dev \
MOBUX_APP_NAME="Mobux Dev" \
TWA_INSTALL_DIR=twa/dist-dev/install \
TWA_WELLKNOWN_DIR=twa/dist-dev/.well-known