.PHONY: help makehelp dev server daemon cli multica build test migrate-up migrate-down sqlc seed clean setup start stop check worktree-env setup-main start-main stop-main check-main setup-worktree start-worktree stop-worktree check-worktree db-up db-down db-reset selfhost selfhost-build selfhost-stop MAIN_ENV_FILE ?= .env WORKTREE_ENV_FILE ?= .env.worktree ENV_FILE ?= $(if $(wildcard $(MAIN_ENV_FILE)),$(MAIN_ENV_FILE),$(if $(wildcard $(WORKTREE_ENV_FILE)),$(WORKTREE_ENV_FILE),$(MAIN_ENV_FILE))) ifneq ($(wildcard $(ENV_FILE)),) include $(ENV_FILE) endif POSTGRES_DB ?= multica POSTGRES_USER ?= multica POSTGRES_PASSWORD ?= multica POSTGRES_PORT ?= 5432 PORT := $(or $(BACKEND_PORT),$(API_PORT),$(SERVER_PORT),$(PORT),8080) FRONTEND_PORT ?= 3000 FRONTEND_ORIGIN ?= http://localhost:$(FRONTEND_PORT) MULTICA_APP_URL ?= $(FRONTEND_ORIGIN) DATABASE_URL ?= postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@localhost:$(POSTGRES_PORT)/$(POSTGRES_DB)?sslmode=disable NEXT_PUBLIC_API_URL ?= http://localhost:$(PORT) NEXT_PUBLIC_WS_URL ?= ws://localhost:$(PORT)/ws GOOGLE_REDIRECT_URI ?= $(FRONTEND_ORIGIN)/auth/callback MULTICA_SERVER_URL ?= ws://localhost:$(PORT)/ws LOCAL_UPLOAD_BASE_URL ?= http://localhost:$(PORT) export MULTICA_ARGS ?= $(ARGS) COMPOSE := docker compose define REQUIRE_ENV @if [ ! -f "$(ENV_FILE)" ]; then \ echo "Missing env file: $(ENV_FILE)"; \ echo "Create .env from .env.example, or run 'make worktree-env' and use .env.worktree."; \ exit 1; \ fi endef # Default target changed from selfhost to help: bare `make` now prints this help # instead of launching a full Docker Compose build, which is safer for onboarding. .DEFAULT_GOAL := help ##@ Help help: ## Show available make targets and common local workflows @awk 'BEGIN {FS = ":.*## "; printf "\nUsage:\n make \033[36m\033[0m\n\nQuick start:\n \033[36mmake dev\033[0m Bootstrap the current checkout and start everything\n \033[36mmake check\033[0m Run the full local verification pipeline\n\nCheckout modes:\n Main checkout uses \033[36m.env\033[0m\n Worktrees use \033[36m.env.worktree\033[0m (generate with \033[36mmake worktree-env\033[0m)\n\n"} \ /^##@/ {printf "\n\033[1m%s\033[0m\n", substr($$0, 5); next} \ /^[a-zA-Z0-9_.-]+:.*## / {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) makehelp: help ## Alias for `make help` # ---------- Self-hosting (Docker Compose) ---------- ##@ Self-hosting selfhost: ## Create .env if needed, then pull and start the official self-hosted images @if [ ! -f .env ]; then \ echo "==> Creating .env from .env.example..."; \ cp .env.example .env; \ JWT=$$(openssl rand -hex 32); \ PGPASS=$$(openssl rand -hex 24); \ if [ "$$(uname)" = "Darwin" ]; then \ sed -i '' "s/^JWT_SECRET=.*/JWT_SECRET=$$JWT/" .env; \ sed -i '' "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$$PGPASS/" .env; \ sed -i '' -E "s#^(DATABASE_URL=postgres://[^:]+:)[^@]*(@.*)#\1$$PGPASS\2#" .env; \ else \ sed -i "s/^JWT_SECRET=.*/JWT_SECRET=$$JWT/" .env; \ sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$$PGPASS/" .env; \ sed -i -E "s#^(DATABASE_URL=postgres://[^:]+:)[^@]*(@.*)#\1$$PGPASS\2#" .env; \ fi; \ echo "==> Generated random JWT_SECRET and POSTGRES_PASSWORD"; \ fi @echo "==> Pulling official Multica images..." @if ! docker compose -f docker-compose.selfhost.yml pull; then \ echo ""; \ echo "Official images for tag '$${MULTICA_IMAGE_TAG:-latest}' are not published yet."; \ echo "If this is before the first GHCR release, build from the current checkout:"; \ echo " make selfhost-build"; \ exit 1; \ fi @echo "==> Starting Multica via Docker Compose..." docker compose -f docker-compose.selfhost.yml up -d @echo "==> Waiting for backend to be ready..." @for i in $$(seq 1 30); do \ if curl -sf http://localhost:$${PORT:-8080}/health > /dev/null 2>&1; then \ break; \ fi; \ sleep 2; \ done @if curl -sf http://localhost:$${PORT:-8080}/health > /dev/null 2>&1; then \ echo ""; \ echo "✓ Multica is running!"; \ echo " Frontend: http://localhost:$${FRONTEND_PORT:-3000}"; \ echo " Backend: http://localhost:$${PORT:-8080}"; \ echo ""; \ echo "Images: $${MULTICA_BACKEND_IMAGE:-ghcr.io/multica-ai/multica-backend}:$${MULTICA_IMAGE_TAG:-latest}"; \ echo " $${MULTICA_WEB_IMAGE:-ghcr.io/multica-ai/multica-web}:$${MULTICA_IMAGE_TAG:-latest}"; \ echo ""; \ echo "Log in: configure RESEND_API_KEY in .env for email codes,"; \ echo " or read the generated code from backend logs when Resend is unset."; \ echo ""; \ echo "Next — install the CLI and connect your machine:"; \ echo " brew install multica-ai/tap/multica"; \ echo " multica setup self-host"; \ else \ echo ""; \ echo "Services are still starting. Check logs:"; \ echo " docker compose -f docker-compose.selfhost.yml logs"; \ fi selfhost-build: ## Build backend/web from the current checkout and start the self-hosted stack @if [ ! -f .env ]; then \ echo "==> Creating .env from .env.example..."; \ cp .env.example .env; \ JWT=$$(openssl rand -hex 32); \ PGPASS=$$(openssl rand -hex 24); \ if [ "$$(uname)" = "Darwin" ]; then \ sed -i '' "s/^JWT_SECRET=.*/JWT_SECRET=$$JWT/" .env; \ sed -i '' "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$$PGPASS/" .env; \ sed -i '' -E "s#^(DATABASE_URL=postgres://[^:]+:)[^@]*(@.*)#\1$$PGPASS\2#" .env; \ else \ sed -i "s/^JWT_SECRET=.*/JWT_SECRET=$$JWT/" .env; \ sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$$PGPASS/" .env; \ sed -i -E "s#^(DATABASE_URL=postgres://[^:]+:)[^@]*(@.*)#\1$$PGPASS\2#" .env; \ fi; \ echo "==> Generated random JWT_SECRET and POSTGRES_PASSWORD"; \ fi @echo "==> Building Multica from the current checkout..." docker compose -f docker-compose.selfhost.yml -f docker-compose.selfhost.build.yml up -d --build @echo "==> Waiting for backend to be ready..." @for i in $$(seq 1 30); do \ if curl -sf http://localhost:$${PORT:-8080}/health > /dev/null 2>&1; then \ break; \ fi; \ sleep 2; \ done @if curl -sf http://localhost:$${PORT:-8080}/health > /dev/null 2>&1; then \ echo ""; \ echo "✓ Multica is running!"; \ echo " Frontend: http://localhost:$${FRONTEND_PORT:-3000}"; \ echo " Backend: http://localhost:$${PORT:-8080}"; \ echo ""; \ echo "Log in: configure RESEND_API_KEY in .env for email codes,"; \ echo " or read the generated code from backend logs when Resend is unset."; \ echo ""; \ echo "Built images locally via docker-compose.selfhost.build.yml."; \ echo "Local tags: multica-backend:dev and multica-web:dev."; \ echo ""; \ echo "Next — install the CLI and connect your machine:"; \ echo " brew install multica-ai/tap/multica"; \ echo " multica setup self-host"; \ else \ echo ""; \ echo "Services are still starting. Check logs:"; \ echo " docker compose -f docker-compose.selfhost.yml logs"; \ fi selfhost-stop: ## Stop the self-hosted Docker Compose stack @echo "==> Stopping Multica services..." docker compose -f docker-compose.selfhost.yml down @echo "✓ All services stopped." # ---------- One-click commands ---------- ##@ One-click setup: ## Prepare the current checkout from its env file: install deps, ensure DB, run migrations $(REQUIRE_ENV) @echo "==> Using env file: $(ENV_FILE)" @echo "==> Installing dependencies..." pnpm install @bash scripts/ensure-postgres.sh "$(ENV_FILE)" @echo "==> Running migrations..." cd server && go run ./cmd/migrate up @echo "" @echo "✓ Setup complete! Run 'make start' to launch the app." start: ## Start backend and frontend for the current checkout and run migrations first $(REQUIRE_ENV) @echo "Using env file: $(ENV_FILE)" @echo "Backend: http://localhost:$(PORT)" @echo "Frontend: http://localhost:$(FRONTEND_PORT)" @bash scripts/ensure-postgres.sh "$(ENV_FILE)" @echo "Running migrations..." cd server && go run ./cmd/migrate up @echo "Starting backend and frontend..." @trap 'kill 0' EXIT; \ (cd server && go run ./cmd/server) & \ pnpm dev:web & \ wait stop: ## Stop backend and frontend processes for the current checkout $(REQUIRE_ENV) @echo "Stopping services..." @-lsof -ti:$(PORT) | xargs kill -9 2>/dev/null @-lsof -ti:$(FRONTEND_PORT) | xargs kill -9 2>/dev/null @case "$(DATABASE_URL)" in \ ""|*@localhost:*|*@localhost/*|*@127.0.0.1:*|*@127.0.0.1/*|*@\[::1\]:*|*@\[::1\]/*) \ echo "✓ App processes stopped. Shared PostgreSQL is still running on localhost:$(POSTGRES_PORT)." ;; \ *) \ echo "✓ App processes stopped. Remote PostgreSQL was not affected." ;; \ esac check: ## Run typecheck, TS tests, Go tests, and Playwright E2E for the current checkout $(REQUIRE_ENV) @ENV_FILE="$(ENV_FILE)" bash scripts/check.sh db-up: ## Start the shared PostgreSQL container used by main and worktrees @$(COMPOSE) up -d postgres db-down: ## Stop the shared PostgreSQL container without removing its Docker volume @$(COMPOSE) down # Drop + recreate the current env's database, then run all migrations. # Use for a clean slate in local dev. Only affects the DB named in # ENV_FILE (POSTGRES_DB); the shared postgres container and other # worktree DBs are untouched. Refuses to run against a remote host. db-reset: ## Drop and recreate the current env's database, then re-run all migrations $(REQUIRE_ENV) @case "$(DATABASE_URL)" in \ ""|*@localhost:*|*@localhost/*|*@127.0.0.1:*|*@127.0.0.1/*|*@\[::1\]:*|*@\[::1\]/*) ;; \ *) echo "Refusing to reset: DATABASE_URL points at a remote host."; exit 1 ;; \ esac @bash scripts/ensure-postgres.sh "$(ENV_FILE)" @echo "==> Dropping and recreating database '$(POSTGRES_DB)'..." @$(COMPOSE) exec -T postgres psql -U $(POSTGRES_USER) -d postgres -v ON_ERROR_STOP=1 \ -c "DROP DATABASE IF EXISTS \"$(POSTGRES_DB)\" WITH (FORCE);" \ -c "CREATE DATABASE \"$(POSTGRES_DB)\";" @echo "==> Running migrations..." cd server && go run ./cmd/migrate up @echo "" @echo "✓ Database '$(POSTGRES_DB)' reset. Run 'make start' to launch the app." worktree-env: ## Generate .env.worktree with a unique DB name and app ports for this worktree @bash scripts/init-worktree-env.sh .env.worktree setup-main: ## Prepare the main checkout using .env @$(MAKE) setup ENV_FILE=$(MAIN_ENV_FILE) start-main: ## Start the main checkout using .env @$(MAKE) start ENV_FILE=$(MAIN_ENV_FILE) stop-main: ## Stop the main checkout processes defined by .env @$(MAKE) stop ENV_FILE=$(MAIN_ENV_FILE) check-main: ## Run the full verification pipeline for the main checkout @ENV_FILE=$(MAIN_ENV_FILE) bash scripts/check.sh setup-worktree: ## Ensure .env.worktree exists, then prepare this worktree @if [ ! -f "$(WORKTREE_ENV_FILE)" ]; then \ echo "==> Generating $(WORKTREE_ENV_FILE) with unique ports..."; \ bash scripts/init-worktree-env.sh $(WORKTREE_ENV_FILE); \ else \ echo "==> Using existing $(WORKTREE_ENV_FILE)"; \ fi @$(MAKE) setup ENV_FILE=$(WORKTREE_ENV_FILE) start-worktree: ## Start this worktree using .env.worktree @$(MAKE) start ENV_FILE=$(WORKTREE_ENV_FILE) stop-worktree: ## Stop this worktree's backend and frontend processes @$(MAKE) stop ENV_FILE=$(WORKTREE_ENV_FILE) check-worktree: ## Run the full verification pipeline for this worktree @ENV_FILE=$(WORKTREE_ENV_FILE) bash scripts/check.sh # ---------- Individual commands ---------- ##@ Individual commands dev: ## Bootstrap this checkout end-to-end: create env if needed, ensure DB, migrate, start services @bash scripts/dev.sh server: ## Run only the Go server for the current checkout $(REQUIRE_ENV) @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go run ./cmd/server daemon: ## Restart the local agent daemon using the CLI's stored auth/session @$(MAKE) multica MULTICA_ARGS="daemon restart --profile local" cli: ## Run the multica CLI with ARGS or MULTICA_ARGS from source @$(MAKE) multica MULTICA_ARGS="$(MULTICA_ARGS)" multica: ## Run the multica CLI entrypoint directly from the Go source tree cd server && go run ./cmd/multica $(MULTICA_ARGS) VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) DATE ?= $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') build: ## Build the server, CLI, and migrate binaries into server/bin cd server && go build -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT)" -o bin/server ./cmd/server cd server && go build -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE)" -o bin/multica ./cmd/multica cd server && go build -o bin/migrate ./cmd/migrate test: ## Run Go tests after ensuring the target DB exists and migrations are applied $(REQUIRE_ENV) @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go run ./cmd/migrate up cd server && go test ./... # Database ##@ Database migrate-up: ## Create the target DB if needed, then apply database migrations $(REQUIRE_ENV) @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go run ./cmd/migrate up migrate-down: ## Create the target DB if needed, then roll back database migrations $(REQUIRE_ENV) @bash scripts/ensure-postgres.sh "$(ENV_FILE)" cd server && go run ./cmd/migrate down sqlc: ## Regenerate sqlc code cd server && sqlc generate # Cleanup ##@ Cleanup clean: ## Remove generated server binaries and temp files rm -rf server/bin server/tmp