Four controls and the unit's own answer beside them. Two catches, both on: the flow is seeded stopped and the node's commands setting is off. The initial values are read off the unit when the script runs, so starting the flow asks for what it was already doing rather than commanding it to something else.
183 lines
9.9 KiB
Makefile
183 lines
9.9 KiB
Makefile
# ─── Fluksio app Makefile ───
|
|
# Convenience targets for development, testing, linting, and deployment.
|
|
# The workspace root delegates to these (see ../Makefile).
|
|
|
|
.PHONY: dev-utils dev dev-local up down update install dev-backend dev-frontend \
|
|
generate-client seed-example seed-demo seed-house seed-aircon seed-hosted-demo test test-backend test-frontend soak bench-startup lint lint-backend \
|
|
lint-frontend umami clean help
|
|
|
|
COMPOSE_ROOT := $(CURDIR)
|
|
# Explicit project name keeps this stack isolated from the sibling website
|
|
# stack (otherwise both default to "docker", the directory their compose
|
|
# files live in).
|
|
COMPOSE_PROJECT := fluksio-app
|
|
# Compose interpolation needs the project-local .env before reading compose.yml.
|
|
COMPOSE := docker compose -p $(COMPOSE_PROJECT) --env-file $(COMPOSE_ROOT)/.env
|
|
COMPOSE_PROD := $(COMPOSE) -f docker/compose.yml
|
|
# Production also runs autoheal, which restarts the backend when its deep
|
|
# health check fails. It is profile-gated because it mounts the Docker socket.
|
|
COMPOSE_PROD_RUN := $(COMPOSE_PROD) --profile autoheal
|
|
COMPOSE_DEV := $(COMPOSE_PROD) -f docker/compose.dev.yml
|
|
# Integrated local stack: dev stack wired onto the shared `proxy` network.
|
|
COMPOSE_LOCAL := $(COMPOSE_DEV) -f docker/compose.local.yml
|
|
|
|
help: ## Show available targets
|
|
@awk 'BEGIN{FS=":.*?## "} /^[a-zA-Z_-]+:.*?##/ {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
|
|
|
# ── Development (Docker) ──────────────────────────────────────────
|
|
|
|
dev-utils: ## Start only the utility containers (db, adminer, proxy, mailcatcher, prestart)
|
|
$(COMPOSE_DEV) up --build db adminer proxy mailcatcher prestart
|
|
|
|
dev: ## Start the full dev stack (includes a local Traefik proxy)
|
|
$(COMPOSE_DEV) up --build
|
|
|
|
dev-local: ## Start the integrated local stack (called by the root `make dev`)
|
|
DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} \
|
|
$(COMPOSE_LOCAL) up --build -d proxy db adminer prestart backend frontend mailcatcher
|
|
|
|
up: ## Start the production stack
|
|
$(COMPOSE_PROD_RUN) up --build -d
|
|
|
|
umami: ## Provision + start the optional Umami site-analytics service (idempotent)
|
|
# Guard: the dashboard is internet-facing on analytics.$(DOMAIN); refuse to
|
|
# start when its session-signing secret is unset or still the placeholder.
|
|
@secret=$$(grep -E '^UMAMI_APP_SECRET=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
|
|
if [ -z "$$secret" ] || [ "$$secret" = "changethis" ]; then \
|
|
echo "ERROR: UMAMI_APP_SECRET is empty or 'changethis' in .env — run the workspace root's scripts/setup.sh (or set it: openssl rand -hex 32) before 'make umami'." >&2; \
|
|
exit 1; \
|
|
fi
|
|
# --no-recreate: reuse an already-running db instead of recreating it under
|
|
# the prod compose set, which would blip every service that depends on it.
|
|
$(COMPOSE_PROD) up -d --wait --no-recreate db
|
|
# Least-privilege role owning only the umami database, so the third-party
|
|
# image never holds the shared superuser credentials.
|
|
@pguser=$$(grep -E '^POSTGRES_USER=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); pguser=$${pguser:-postgres}; \
|
|
role=$$(grep -E '^UMAMI_DB_USER=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); role=$${role:-umami}; \
|
|
pw=$$(grep -E '^UMAMI_DB_PASSWORD=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
|
|
db=$$(grep -E '^UMAMI_DB=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); db=$${db:-umami}; \
|
|
if [ -z "$$pw" ] || [ "$$pw" = "changethis" ]; then \
|
|
echo "ERROR: UMAMI_DB_PASSWORD is empty or 'changethis' in .env — run the workspace root's scripts/setup.sh before 'make umami'." >&2; \
|
|
exit 1; \
|
|
fi; \
|
|
$(COMPOSE_PROD) exec -T db psql -v ON_ERROR_STOP=1 -U "$$pguser" -d postgres \
|
|
-v role="$$role" -v pw="$$pw" -v db="$$db" < docker/provision-umami-db.sql
|
|
# --no-deps: only (re)create umami, never restart the shared db underneath it.
|
|
$(COMPOSE_PROD) --profile analytics up -d --no-deps umami
|
|
|
|
update: ## Pull, rebuild using the layer cache, and recreate changed containers
|
|
git pull
|
|
$(COMPOSE_PROD_RUN) build
|
|
$(COMPOSE_PROD_RUN) up -d --remove-orphans
|
|
docker image prune -f
|
|
|
|
down: ## Stop all running containers
|
|
-$(COMPOSE_LOCAL) down
|
|
-$(COMPOSE_PROD_RUN) down
|
|
|
|
# ── Development (local, no Docker) ───────────────────────────────
|
|
# Run `make dev-backend` and `make dev-frontend` in two separate terminals.
|
|
|
|
install: ## Install all dependencies (backend + frontend)
|
|
cd backend && uv sync
|
|
cd frontend && bun install
|
|
|
|
dev-backend: ## Start the FastAPI backend with hot-reload (local)
|
|
cd backend && uv run fastapi dev app/main.py
|
|
|
|
dev-frontend: ## Start the Vite dev server (local)
|
|
cd frontend && bun dev
|
|
|
|
generate-client: ## Regenerate the frontend SDK from the backend's OpenAPI schema
|
|
bash scripts/generate-client.sh
|
|
|
|
seed-example: ## Seed the querying-chart example (needs a running stack + InfluxDB)
|
|
cd backend && uv run python ../scripts/seed_example_chart.py
|
|
|
|
seed-demo: ## Seed the training-run example: a batch flow and its dashboard
|
|
cd backend && uv run python ../scripts/seed_demo_training.py
|
|
|
|
seed-house: ## Seed the house write-path rig (needs the real broker reachable)
|
|
cd backend && uv run python ../scripts/seed_house_control.py
|
|
|
|
seed-aircon: ## Seed the aircon write-path rig (needs the unit reachable)
|
|
cd backend && uv run python ../scripts/seed_aircon_control.py
|
|
|
|
# Operators of the hosted demo only — NOT part of any deployment, and nothing a
|
|
# self-hosted instance needs. It wipes and recreates its three flows and its
|
|
# dashboard, so re-running it is how the public demo is reset.
|
|
# `uv run` does not read .env, so the credentials are lifted out of it here;
|
|
# anything already exported wins, which is how a remote instance is targeted:
|
|
# API_URL=https://api.example.com make seed-hosted-demo
|
|
seed-hosted-demo: ## Seed the hosted demo panel (operators only; API_URL selects the instance)
|
|
@user=$$(grep -E '^FIRST_SUPERUSER=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
|
|
pass=$$(grep -E '^FIRST_SUPERUSER_PASSWORD=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
|
|
cd backend && \
|
|
FIRST_SUPERUSER="$${FIRST_SUPERUSER:-$$user}" \
|
|
FIRST_SUPERUSER_PASSWORD="$${FIRST_SUPERUSER_PASSWORD:-$$pass}" \
|
|
uv run python ../scripts/seed_demo.py
|
|
|
|
# ── Testing ───────────────────────────────────────────────────────
|
|
|
|
test: test-backend test-frontend ## Run all tests (backend + frontend)
|
|
|
|
# The integrated stack publishes no host port for Postgres (docker/compose.local.yml
|
|
# drops them all), so the suite reaches the container on the compose network
|
|
# instead. Empty when the stack is down — then .env's localhost:5432 stands.
|
|
DB_HOST = $(shell docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' fluksio-db 2>/dev/null | awk '{print $$1}')
|
|
|
|
test-backend: ## Run backend tests (pytest + coverage)
|
|
cd backend && $(if $(DB_HOST),POSTGRES_SERVER=$(DB_HOST)) uv run bash scripts/tests-start.sh
|
|
|
|
# The hostname the stack is served under. A checkout configured for a
|
|
# deployment carries the deployment's domain, and *.fluksio.com resolves to the
|
|
# live instance from here — which is why the run below maps both names onto the
|
|
# local Traefik by address and never lets DNS decide.
|
|
DOMAIN = $(shell sed -n 's/^DOMAIN=//p' $(COMPOSE_ROOT)/.env | head -1)
|
|
PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1)
|
|
|
|
test-frontend: ## Run frontend tests (Playwright e2e) against the local stack
|
|
@ip=$$(docker network inspect proxy \
|
|
--format '{{range .Containers}}{{if eq .Name "fluksio-app-proxy-1"}}{{.IPv4Address}}{{end}}{{end}}' \
|
|
2>/dev/null | cut -d/ -f1); \
|
|
[ -n "$$ip" ] || { echo " ✗ proxy network or Traefik container not found — is the stack up?"; exit 1; }; \
|
|
docker run --rm --network proxy --ipc=host \
|
|
--user $$(id -u):$$(id -g) -e HOME=/tmp \
|
|
--add-host app.$(DOMAIN):$$ip --add-host api.$(DOMAIN):$$ip \
|
|
-v $(COMPOSE_ROOT):/app -w /app/frontend \
|
|
-e PLAYWRIGHT_BASE_URL=http://app.$(DOMAIN) \
|
|
-e PLAYWRIGHT_API_URL=http://api.$(DOMAIN) \
|
|
-e HOST_RESOLVER_RULES="MAP app.$(DOMAIN) $$ip, MAP api.$(DOMAIN) $$ip" \
|
|
-e CI=$${CI:-1} \
|
|
-e FIRST_SUPERUSER="$$(sed -n 's/^FIRST_SUPERUSER=//p' $(COMPOSE_ROOT)/.env | head -1)" \
|
|
-e FIRST_SUPERUSER_PASSWORD="$$(sed -n 's/^FIRST_SUPERUSER_PASSWORD=//p' $(COMPOSE_ROOT)/.env | head -1)" \
|
|
mcr.microsoft.com/playwright:v$(PW_VERSION)-noble \
|
|
npx playwright test $(PLAYWRIGHT_ARGS)
|
|
|
|
# Load and chaos against a *running* stack, never part of `make test`: it
|
|
# restarts this stack's containers. `SOAK_ARGS="--dry-run"` only looks.
|
|
soak: ## Run the soak/chaos harness (SOAK_ARGS="--minutes 30 --scenario redis")
|
|
cd backend && uv run python scripts/soak.py $(SOAK_ARGS)
|
|
|
|
bench-startup: ## Time submitting a run (BENCH_ARGS="--kedro ../some/kedro/project")
|
|
cd backend && uv run python scripts/bench_startup.py $(BENCH_ARGS)
|
|
|
|
# ── Linting ───────────────────────────────────────────────────────
|
|
|
|
lint: lint-backend lint-frontend ## Run all linters
|
|
|
|
lint-backend: ## Lint backend with ruff + mypy
|
|
cd backend && uv run ruff check .
|
|
cd backend && uv run ruff format --check .
|
|
cd backend && uv run mypy app
|
|
|
|
lint-frontend: ## Lint frontend with biome
|
|
cd frontend && bun run lint
|
|
|
|
# ── Cleanup ───────────────────────────────────────────────────────
|
|
|
|
clean: ## Remove build artifacts and caches
|
|
rm -rf frontend/dist frontend/blob-report frontend/test-results
|
|
rm -rf backend/.pytest_cache backend/htmlcov
|
|
find backend -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|