Makefile•11.7 kB
# Makefile for mcp-memory-libsql-go
SHELL := /bin/sh
# Variables
BINARY_NAME=mcp-memory-libsql-go
MAIN_PACKAGE=./cmd/${BINARY_NAME}
BINARY_LOCATION=$(shell pwd)/bin/$(BINARY_NAME)
INTEGRATION_TESTER=./cmd/integration-tester
INTEGRATION_TESTER_BINARY=$(shell pwd)/bin/integration-tester
VERSION ?= $(shell git describe --tags --always --dirty)
REVISION ?= $(shell git rev-parse HEAD)
BUILD_DATE = $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
LDFLAGS = -ldflags "-X github.com/ZanzyTHEbar/${BINARY_NAME}/internal/buildinfo.Version=$(VERSION) -X github.com/ZanzyTHEbar/${BINARY_NAME}/internal/buildinfo.Revision=$(REVISION) -X github.com/ZanzyTHEbar/${BINARY_NAME}/internal/buildinfo.BuildDate=$(BUILD_DATE)"
# Docker config
IMAGE ?= $(BINARY_NAME)
TAG ?= local
DOCKER_IMAGE := $(IMAGE):$(TAG)
ENV_FILE ?=
ENV_FILE_ARG := $(if $(ENV_FILE),--env-file $(ENV_FILE),)
PORT_SSE ?= 8080
PORT_METRICS ?= 9090
PROFILES ?= memory
PROFILE_FLAGS := $(foreach p,$(PROFILES),--profile $(p))
# Allow skipping host permission operations when creating data directories
# Useful when running make as non-root or when mounted files should keep
# host ownership. Set SKIP_CHOWN=1 to avoid chmod/chown operations.
HOST_UID ?= $(shell id -u)
HOST_GID ?= $(shell id -g)
# Default PROJECTS ownership to the runner's UID/GID unless overridden
PROJECTS_UID ?= $(HOST_UID)
PROJECTS_GID ?= $(HOST_GID)
# Default SKIP_CHOWN to 0 when running as root, otherwise 1; allow override
SKIP_CHOWN_DEFAULT := $(if $(filter 0,$(HOST_UID)),0,1)
SKIP_CHOWN ?= $(SKIP_CHOWN_DEFAULT)
# Default target
.PHONY: all
all: build
# Build the binary
.PHONY: build
build:
CGO_ENABLED=1 go build $(LDFLAGS) -o $(BINARY_LOCATION) $(MAIN_PACKAGE)
# Build the integration tester into bin/
.PHONY: build-integration
build-integration:
mkdir -p $(shell pwd)/bin
CGO_ENABLED=1 go build $(LDFLAGS) -o $(INTEGRATION_TESTER_BINARY) $(INTEGRATION_TESTER)
# Install dependencies
.PHONY: deps
deps:
go mod tidy
# Run tests
.PHONY: test
test:
go test ./...
# Run the server
.PHONY: run
run: build
$(BINARY_LOCATION)
# Build the docker image
.PHONY: docker docker-build
docker: docker-build
docker-build:
docker build \
--build-arg VERSION=$(VERSION) \
--build-arg REVISION=$(REVISION) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
-t $(DOCKER_IMAGE) -f docker/Dockerfile .
.PHONY: docker-rebuild
docker-rebuild:
docker build --no-cache \
--build-arg VERSION=$(VERSION) \
--build-arg REVISION=$(REVISION) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
-t $(DOCKER_IMAGE) .
# Ensure local data directory exists
.PHONY: data
data:
mkdir -p ./data ./data/projects ./ollama_models
# Only perform chmod when explicitly allowed and when running as root.
# If SKIP_CHOWN=1 or we're not root, skip to avoid "Operation not permitted".
if [ "$${SKIP_CHOWN}" = "1" ]; then \
echo "Makefile: SKIP_CHOWN=1 set, skipping chmod/chown on ./data and ./ollama_models"; \
elif [ "$(shell id -u)" != "0" ]; then \
echo "Makefile: not running as root, skipping chmod/chown on ./data and ./ollama_models"; \
else \
chmod -R 777 ./data ./ollama_models || true; \
fi
# Run the docker image (SSE default)
.PHONY: docker-run
docker-run: docker-run-sse
# Run the docker image with sse transport
.PHONY: docker-run-sse
docker-run-sse: data
docker run --rm $(ENV_FILE_ARG) \
-p $(PORT_SSE):$(PORT_SSE) -p $(PORT_METRICS):$(PORT_METRICS) \
-v $(shell pwd)/data:/data \
-e MODE=$(MODE) \
-e PORT=$(PORT_SSE) \
-e METRICS_PORT=$(PORT_METRICS) \
-e SKIP_CHOWN=$(SKIP_CHOWN) \
$(DOCKER_IMAGE) -transport sse -addr :$(PORT_SSE) -sse-endpoint /sse
# Run the docker image with stdio transport
.PHONY: docker-run-stdio
docker-run-stdio: data
docker run --rm $(ENV_FILE_ARG) \
-v $(shell pwd)/data:/data \
-e SKIP_CHOWN=$(SKIP_CHOWN) \
$(DOCKER_IMAGE) -transport stdio
# Run the docker image with multi-project mode (SSE)
.PHONY: docker-run-multi
docker-run-multi: data
docker run --rm $(ENV_FILE_ARG) \
-p $(PORT_SSE):$(PORT_SSE) -p $(PORT_METRICS):$(PORT_METRICS) \
-v $(shell pwd)/data:/data \
-e MODE=multi \
-e PORT=$(PORT_SSE) \
-e METRICS_PORT=$(PORT_METRICS) \
-e SKIP_CHOWN=$(SKIP_CHOWN) \
$(DOCKER_IMAGE) -transport sse -addr :$(PORT_SSE) -sse-endpoint /sse -projects-dir /data/projects
# End-to-end docker test workflow
## Silence command echoing for docker-test target while still printing our own echoes
.SILENT: docker-test
.PHONY: docker-test
docker-test: data
echo "Checking for existing image $(DOCKER_IMAGE)..."; \
exec ./scripts/ci-docker-test.sh $(ENV_FILE)
# Compose helpers
.PHONY: compose-up compose-down compose-logs compose-ps
compose-up:
docker compose $(PROFILE_FLAGS) up --build -d
compose-down:
docker compose down $(if $(WITH_VOLUMES),-v,)
compose-logs:
docker compose logs -f --tail=200 $(if $(SERVICE),$(SERVICE),)
compose-ps:
docker compose ps
# Ensure .env.ci exists with safe defaults for CI
.PHONY: ensure-env-ci
ensure-env-ci:
if [ -n "$(ENV_FILE)" ]; then env_target="$(ENV_FILE)"; else env_target=".env.ci"; fi; \
if [ -s "$$env_target" ]; then echo "Using existing $$env_target"; exit 0; fi; \
mode_value="$${MODE:-multi}"; \
if [ "$$mode_value" = "multi" ]; then \
printf '%s\n' "MODE=multi" "LIBSQL_URL=file:/data/libsql.db" "LIBSQL_AUTH_TOKEN=" "EMBEDDING_DIMS=4" "EMBEDDINGS_PROVIDER=" "EMBEDDINGS_ADAPT_MODE=" "DB_MAX_OPEN_CONNS=16" "DB_MAX_IDLE_CONNS=8" "DB_CONN_MAX_IDLE_SEC=30" "DB_CONN_MAX_LIFETIME_SEC=60" "HYBRID_SEARCH=true" "HYBRID_TEXT_WEIGHT=0.4" "HYBRID_VECTOR_WEIGHT=0.6" "HYBRID_RRF_K=60" "METRICS_PROMETHEUS=true" "METRICS_PORT=9090" "TRANSPORT=sse" "PROJECTS_DIR=/data/projects" "PORT=8090" "SSE_ENDPOINT=/sse" "MULTI_PROJECT_AUTH_REQUIRED=false" "MULTI_PROJECT_AUTO_INIT_TOKEN=true" "MULTI_PROJECT_DEFAULT_TOKEN=ci-token" "SKIP_CHOWN=0" "BUILD_DATE=${BUILD_DATE:-ci}" > "$$env_target"; \
else \
printf '%s\n' "MODE=single" "LIBSQL_URL=file:/data/libsql.db" "LIBSQL_AUTH_TOKEN=" "EMBEDDING_DIMS=4" "PROJECTS_DIR=/data/projects" "PORT=8090" "METRICS_PORT=9090" "SSE_ENDPOINT=/sse" "SKIP_CHOWN=0" "BUILD_DATE=${BUILD_DATE:-ci}" > "$$env_target"; \
fi; \
echo "Wrote $$env_target";
# Coolify production-style targets: separate build and run commands
.PHONY: coolify-prod-build coolify-prod-run coolify-prod-down coolify-prod-logs coolify-prod-ps
# Build the docker image and prepare data (no run)
coolify-prod-build: docker-build data
@echo "Coolify: building image for production-style (multi-project, SSE, auth off, ollama)"
# Run using existing image (separate from build)
# Rely on environment variables provided by Coolify's UI or the shell. Do NOT inline-set ENVs here.
# Use COOLIFY_PROFILES (defaults to 'memory ollama') so both services are started by default
coolify-prod-run:
@echo "Coolify: starting production-style (multi-project, SSE, auth off, ollama) (envs must be provided by environment/Coolify UI)"
docker compose up -d
coolify-prod-down:
@echo "Coolify: stopping production-style services"
docker compose down $(if $(WITH_VOLUMES),-v,)
coolify-prod-logs:
docker compose logs -f --tail=200 memory
coolify-prod-ps:
docker compose ps
# Legacy docker-compose aliases (optional)
.PHONY: docker-compose
docker-compose: compose-up
# Production run profile (Ollama, SSE, multi-project, hybrid, pooling, metrics)
.PHONY: env-prod prod prod-down prod-logs prod-ps
# Generate a production env file used by compose
env-prod:
@echo "Writing .env.prod..."
@{ \
echo "EMBEDDINGS_PROVIDER=ollama"; \
echo "OLLAMA_HOST=http://ollama:11434"; \
echo "OLLAMA_EMBEDDINGS_MODEL=nomic-embed-text"; \
echo "EMBEDDING_DIMS=768"; \
echo "EMBEDDINGS_ADAPT_MODE=pad_or_truncate"; \
echo; \
echo "HYBRID_SEARCH=true"; \
echo "HYBRID_TEXT_WEIGHT=0.4"; \
echo "HYBRID_VECTOR_WEIGHT=0.6"; \
echo "HYBRID_RRF_K=60"; \
echo; \
echo "DB_MAX_OPEN_CONNS=16"; \
echo "DB_MAX_IDLE_CONNS=8"; \
echo "DB_CONN_MAX_IDLE_SEC=60"; \
echo "DB_CONN_MAX_LIFETIME_SEC=300"; \
echo; \
echo "METRICS_PROMETHEUS=true"; \
echo "METRICS_PORT=9090"; \
echo; \
echo "TRANSPORT=sse"; \
echo "PORT=8090"; \
echo "SSE_ENDPOINT=/sse"; \
echo; \
echo "# Multi-project auth toggles"; \
echo "MULTI_PROJECT_AUTH_REQUIRED=false"; \
echo "MULTI_PROJECT_AUTO_INIT_TOKEN=true"; \
echo "MULTI_PROJECT_DEFAULT_TOKEN=dev-token"; \
echo; \
echo "# Optional remote DB settings (leave blank for local files)"; \
echo "LIBSQL_URL="; \
echo "LIBSQL_AUTH_TOKEN="; \
} > .env.prod
ollama-local: docker-build data env-ollama
# Ollama local: multi-project SSE, auth off, embeddings=ollama (local env)
docker compose -f docker/docker-compose.ollama.yml --env-file .env.ollama up --build -d
ollama-local-down: env-ollama
docker compose -f docker/docker-compose.ollama.yml --env-file .env.ollama down $(if $(WITH_VOLUMES),-v,)
.PHONY: env-ollama
env-ollama:
@echo "Writing .env.ollama..."
@{ \
echo "MODE=multi"; \
echo "LIBSQL_URL="; \
echo "LIBSQL_AUTH_TOKEN="; \
echo "EMBEDDINGS_PROVIDER=ollama"; \
echo "OLLAMA_HOST=http://ollama:11434"; \
echo "OLLAMA_EMBEDDINGS_MODEL=nomic-embed-text"; \
echo "EMBEDDING_DIMS=768"; \
echo "EMBEDDINGS_ADAPT_MODE=pad_or_truncate"; \
echo "SKIP_CHOWN=0"; \
} > .env.ollama
prod: docker-build data env-prod
# Default prod: multi-project SSE, auth off, embeddings=ollama
docker compose --env-file .env.prod up --build -d
prod-down: env-prod
docker compose --env-file .env.prod down $(if $(WITH_VOLUMES),-v,)
prod-logs: env-prod
docker compose --env-file .env.prod logs -f --tail=200 memory-multi
prod-ps: env-prod
docker compose --env-file .env.prod ps
# VoyageAI profile env file
.PHONY: env-voyage voyage-up voyage-down
env-voyage:
@echo "Writing .env.voyage..."
@{ \
echo "EMBEDDINGS_PROVIDER=voyageai"; \
echo "VOYAGEAI_EMBEDDINGS_MODEL=voyage-3-lite"; \
echo "EMBEDDING_DIMS=1024"; \
echo "EMBEDDINGS_ADAPT_MODE=pad_or_truncate"; \
echo "TRANSPORT=sse"; \
echo "PORT=8080"; \
echo "SSE_ENDPOINT=/sse"; \
echo "METRICS_PROMETHEUS=true"; \
echo "METRICS_PORT=9090"; \
echo "HYBRID_SEARCH=true"; \
} > .env.voyage
voyage-up: docker-build data env-voyage
docker compose --env-file .env.voyage --profile voyageai up --build -d
voyage-down: env-voyage
docker compose --env-file .env.voyage --profile voyageai down $(if $(WITH_VOLUMES),-v,)
# Clean build artifacts
.PHONY: clean
clean:
rm -f $(BINARY_LOCATION)
# Install the binary globally
.PHONY: install
install:
@echo "Installing $(BINARY_NAME) globally..."
@chmod +x install.sh
./install.sh $(BINARY_LOCATION)
# Help
.PHONY: help
help:
@echo "Available targets:"
@echo " all - Build the project (default)"
@echo " build - Build the binary"
@echo " deps - Install dependencies"
@echo " test - Run tests"
@echo " run - Build and run the server"
@echo " clean - Clean build artifacts"
@echo " docker - Build the docker image (alias of docker-build)"
@echo " docker-build - Build the docker image"
@echo " docker-rebuild - Build the docker image with --no-cache"
@echo " docker-run - Run container (SSE, mounts ./data)"
@echo " docker-run-stdio - Run container (stdio)"
@echo " docker-run-multi - Run container (SSE, multi-project mode)"
@echo " docker-test - Run end-to-end docker test workflow"
@echo " compose-up - docker compose up (use PROFILES=single|multi|ollama|localai)"
@echo " compose-down - docker compose down (WITH_VOLUMES=1 to remove volumes)"
@echo " compose-logs - docker compose logs (SERVICE=memory)"
@echo " compose-ps - docker compose ps"
@echo " install - Install the binary globally"
@echo " help - Show this help message"