# Matrix Runtime — Makefile # # Common targets: # make help list every target # make build build a local binary into ./bin # make test full verification: fmt-check, vet, race tests + coverage # make install build + install the binary (auto-uses sudo when needed) # make uninstall remove an installed binary (and systemd unit if present) # # Install without root (user-local, no sudo): # make install PREFIX=$HOME/.local # Production install with systemd + data dir + service user (needs root): # sudo make install INSTALL_SYSTEMD=1 SHELL := /usr/bin/env bash # ---- Module / binary ------------------------------------------------------- MODULE := github.com/agent-matrix/matrix-runtime BINARY := matrix-runtime PKG := ./cmd/matrix-runtime BIN_DIR := bin # ---- Install locations (GNU-style; honour DESTDIR/PREFIX) ------------------ DESTDIR ?= PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin # Production install knobs INSTALL_SYSTEMD ?= 0 SERVICE_USER ?= matrix DATA_DIR ?= /var/lib/matrix-runtime SYSTEMD_DIR ?= /etc/systemd/system ENV_FILE ?= /etc/matrix-runtime/matrix-runtime.env # ---- Version stamping ------------------------------------------------------ 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 $(MODULE)/internal/config.Version=$(VERSION) \ -X $(MODULE)/internal/config.Commit=$(COMMIT) \ -X $(MODULE)/internal/config.Date=$(DATE) # Static, reproducible production build. GOFLAGS_PROD := -trimpath CGO_ENABLED ?= 0 GO_FILES := $(shell find . -name '*.go' -not -path './legacy/*') .DEFAULT_GOAL := build # Run a shell script robustly even if a Windows/WSL checkout gave it CRLF line # endings: strip CR first, then run with bash (no exec-bit dependency). RUNSH = sh_strip_run() { sed -i 's/\r$$//' "$$1" 2>/dev/null || true; bash "$$1" "$${@:2}"; }; sh_strip_run .PHONY: all build prod-build run test fmt fmt-check vet lint tidy coverage \ docker compose-up compose-down smoke install uninstall \ install-systemd release clean help web web-auto normalize \ venv py-install py-test py-lint setup ## all: build the frontend bundle and the backend binary all: web prod-build ## normalize: strip CRLF -> LF from scripts, Makefile and Go (Windows/WSL fix) normalize: @for f in $$(find . -path ./legacy -prune -o \( -name '*.sh' -o -name '*.go' \) -print) Makefile; do \ sed -i 's/\r$$//' "$$f" 2>/dev/null || true; \ done @echo "normalize: line endings set to LF" ## web: build the enterprise console bundle (web/src -> web/static/app.js) web: @$(RUNSH) scripts/build-web.sh # web-auto: rebuild the console bundle when a JS toolchain is available, else # fall back to the committed web/static/app.js (already embedded in the binary). # Best-effort: never fails the build (works offline / air-gapped / CRLF checkout). web-auto: @if command -v npx >/dev/null 2>&1; then \ $(RUNSH) scripts/build-web.sh || echo "web build failed — using committed web/static/app.js"; \ else \ echo "==> npx not found — using committed frontend (web/static/app.js, embedded)"; \ fi ## build: compile a local binary into ./bin (backend API + embedded console) build: @mkdir -p $(BIN_DIR) go build -trimpath -ldflags "$(LDFLAGS)" -o $(BIN_DIR)/$(BINARY) $(PKG) @echo "built $(BIN_DIR)/$(BINARY) ($(VERSION))" ## prod-build: optimized, static, version-stamped binary prod-build: @mkdir -p $(BIN_DIR) CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS_PROD) -ldflags "$(LDFLAGS)" -o $(BIN_DIR)/$(BINARY) $(PKG) @echo "built $(BIN_DIR)/$(BINARY) ($(VERSION), static)" ## run: start the whole MatrixCloud (API + console + SQLite) in local-dev mode run: web-auto @echo "==> MatrixCloud starting — the console URL is printed below" @echo " (if port 8080 is busy it falls back to the next free port; override with MATRIX_RUNTIME_PORT)" go run -ldflags "$(LDFLAGS)" $(PKG) --mode local-dev ## test: full verification gate (fmt, vet, race tests + coverage) test: fmt-check vet go test -race -covermode=atomic -coverprofile=coverage.out ./... @go tool cover -func=coverage.out | tail -1 ## fmt: normalise line endings (CRLF->LF) and gofmt all non-legacy Go files fmt: @for f in $(GO_FILES); do sed -i 's/\r$$//' "$$f" 2>/dev/null || true; done @gofmt -w $(GO_FILES) @echo "gofmt: formatted $(words $(GO_FILES)) files (LF normalised)" ## fmt-check: fail if any Go file has CRLF endings or is not gofmt-clean fmt-check: @crlf="$$(grep -lUP '\r$$' $(GO_FILES) 2>/dev/null || true)"; \ if [ -n "$$crlf" ]; then \ echo "These files have CRLF (Windows) line endings — gofmt requires LF:"; \ echo "$$crlf" | sed 's/^/ /'; \ echo ""; \ echo "Fix: make fmt"; \ echo "This repo ships .gitattributes to keep LF; if git re-introduces CRLF run:"; \ echo " git add --renormalize . && git checkout ."; \ exit 1; \ fi; \ unformatted="$$(gofmt -l $(GO_FILES))"; \ if [ -n "$$unformatted" ]; then \ echo "These files are not gofmt-clean:"; echo "$$unformatted" | sed 's/^/ /'; \ echo "Run 'make fmt'."; exit 1; \ fi; \ echo "gofmt: clean" ## vet: run go vet vet: go vet ./... ## lint: run golangci-lint if installed (optional) lint: @if command -v golangci-lint >/dev/null 2>&1; then \ golangci-lint run ./...; \ else \ echo "golangci-lint not installed; skipping (using 'go vet' via 'make vet')"; \ fi ## tidy: ensure go.mod is tidy tidy: go mod tidy ## coverage: open a coverage report coverage: test go tool cover -html=coverage.out -o coverage.html @echo "wrote coverage.html" # ---- Python client (clients/python) — uv-managed .venv --------------------- PY_DIR := clients/python ## venv: create clients/python/.venv with uv (fast) and install the client venv: @if command -v uv >/dev/null 2>&1; then \ cd $(PY_DIR) && uv sync --extra dev && \ echo "venv ready: $(PY_DIR)/.venv (run: source $(PY_DIR)/.venv/bin/activate)"; \ else \ echo "uv not found — install it for fast setup: https://docs.astral.sh/uv/"; \ echo "falling back to python venv + pip…"; \ cd $(PY_DIR) && python3 -m venv .venv && ./.venv/bin/pip install -e '.[dev]'; \ fi ## py-install: install the matrixcloud client + CLI into the .venv py-install: venv ## py-test: run the Python client test suite py-test: @cd $(PY_DIR) && if command -v uv >/dev/null 2>&1; then uv run pytest; else ./.venv/bin/pytest; fi ## py-lint: lint the Python client (ruff) py-lint: @cd $(PY_DIR) && if command -v uv >/dev/null 2>&1; then uv run ruff check .; else ./.venv/bin/ruff check .; fi ## setup: build the runtime AND set up the Python client venv (full dev setup) setup: build venv @echo "setup complete — run 'make run' to start MatrixCloud, or 'cd $(PY_DIR) && uv run mxc status'" ## docker: build the container image docker: docker build -t $(BINARY):$(VERSION) -t $(BINARY):local . ## compose-up / compose-down: docker compose helpers compose-up: docker compose -f deploy/docker-compose/docker-compose.yml up --build compose-down: docker compose -f deploy/docker-compose/docker-compose.yml down ## smoke: run the smoke test against a running instance smoke: @$(RUNSH) scripts/smoke-test.sh ## install: install the runtime (backend API + embedded MatrixCloud console). ## Auto-elevates with sudo when the target dir needs root. Use ## PREFIX=$HOME/.local for a user install with no root. Set ## INSTALL_SYSTEMD=1 (as root) to also install a systemd service. install: web-auto prod-build @bindir="$(DESTDIR)$(BINDIR)"; sudo=""; \ if [ "$$(id -u)" = "0" ] || [ -w "$$(dirname "$$bindir")" ] || [ -w "$$bindir" ]; then \ : ; \ elif command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then \ sudo="sudo"; echo "==> $$bindir needs root — using passwordless sudo"; \ else \ bindir="$$HOME/.local/bin"; \ echo "==> $(BINDIR) needs root and passwordless sudo is unavailable —"; \ echo " installing to $$bindir instead (override with PREFIX=... or run: sudo make install)"; \ fi; \ echo "==> installing $(BINARY) (backend + embedded console) to $$bindir"; \ $$sudo install -d "$$bindir" && \ $$sudo install -m 0755 "$(BIN_DIR)/$(BINARY)" "$$bindir/$(BINARY)" && \ echo "installed: $$bindir/$(BINARY)"; \ case ":$$PATH:" in *":$$bindir:"*) ;; *) echo "note: add $$bindir to your PATH — e.g. echo 'export PATH=\"$$bindir:\$$PATH\"' >> ~/.bashrc";; esac; \ echo "run it: $(BINARY) --mode local-dev # then open http://localhost:8080" @if [ "$(INSTALL_SYSTEMD)" = "1" ]; then $(MAKE) install-systemd; else \ echo "(systemd service available via: sudo make install INSTALL_SYSTEMD=1)"; fi ## install-systemd: provision service user, data dir, env file and unit install-systemd: @echo "==> provisioning systemd service" @id -u "$(SERVICE_USER)" >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin "$(SERVICE_USER)" install -d -o "$(SERVICE_USER)" -g "$(SERVICE_USER)" "$(DESTDIR)$(DATA_DIR)" install -d "$(DESTDIR)$(dir $(ENV_FILE))" @if [ ! -f "$(DESTDIR)$(ENV_FILE)" ]; then \ install -m 0640 deploy/systemd/matrix-runtime.env.example "$(DESTDIR)$(ENV_FILE)"; \ echo "installed env file: $(DESTDIR)$(ENV_FILE) (edit before starting)"; \ else echo "env file exists, leaving as-is: $(DESTDIR)$(ENV_FILE)"; fi install -d "$(DESTDIR)$(SYSTEMD_DIR)" sed -e "s|@BINDIR@|$(BINDIR)|g" \ -e "s|@ENV_FILE@|$(ENV_FILE)|g" \ -e "s|@DATA_DIR@|$(DATA_DIR)|g" \ -e "s|@SERVICE_USER@|$(SERVICE_USER)|g" \ deploy/systemd/matrix-runtime.service.in > "$(DESTDIR)$(SYSTEMD_DIR)/matrix-runtime.service" @echo "installed unit: $(DESTDIR)$(SYSTEMD_DIR)/matrix-runtime.service" @echo "next: sudo systemctl daemon-reload && sudo systemctl enable --now matrix-runtime" ## uninstall: remove the installed binary (and systemd unit if present) uninstall: @bindir="$(DESTDIR)$(BINDIR)"; sudo=""; \ if [ "$$(id -u)" != "0" ] && [ -e "$$bindir/$(BINARY)" ] && [ ! -w "$$bindir" ]; then \ command -v sudo >/dev/null 2>&1 && sudo="sudo"; fi; \ $$sudo rm -f "$$bindir/$(BINARY)"; \ if [ -f "$(DESTDIR)$(SYSTEMD_DIR)/matrix-runtime.service" ]; then \ $$sudo rm -f "$(DESTDIR)$(SYSTEMD_DIR)/matrix-runtime.service"; \ echo "removed systemd unit (run: sudo systemctl daemon-reload)"; \ fi; \ echo "uninstalled $(BINARY)" ## release: cross-compile static binaries into ./dist release: @mkdir -p dist @for osarch in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do \ os=$${osarch%/*}; arch=$${osarch#*/}; \ out="dist/$(BINARY)-$$os-$$arch"; \ echo "building $$out"; \ CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch \ go build $(GOFLAGS_PROD) -ldflags "$(LDFLAGS)" -o "$$out" $(PKG) || exit 1; \ done @echo "release artifacts in ./dist" ## clean: remove build artifacts clean: rm -rf $(BIN_DIR) dist coverage.out coverage.html ## help: list targets help: @printf "\n \033[1;32mMatrix Runtime\033[0m — the execution plane for Matrix Cloud\n" @printf " backend API + embedded MatrixCloud console + SQLite, in one binary\n\n" @printf " \033[1mTargets\033[0m\n" @grep -E '^## [a-z][a-z-]*:' $(MAKEFILE_LIST) \ | sed -E 's/^## ([a-z-]+): /\1|/' \ | awk -F'|' '{printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}' @printf "\n \033[1mQuick start\033[0m\n" @printf " make run start everything → http://localhost:8080\n" @printf " make build build bin/matrix-runtime\n" @printf " make test fmt-check + vet + race tests + coverage\n" @printf " sudo make install install to /usr/local/bin\n" @printf " make install PREFIX=\$$HOME/.local user install, no root\n\n"