2026-09-15 05:20:44 +00:00
|
|
|
|
# Local process control for LazyBoy.
|
2026-09-16 07:06:53 +00:00
|
|
|
|
# make build cargo + production web UI (web/dist)
|
|
|
|
|
|
# make box Docker computer image (lazyboy-box:local)
|
|
|
|
|
|
# make start rebuild, then API :8787 + Vite :5173
|
|
|
|
|
|
# make restart stop, build, start
|
2026-09-15 03:20:42 +00:00
|
|
|
|
# make stop
|
|
|
|
|
|
|
|
|
|
|
|
.DEFAULT_GOAL := help
|
|
|
|
|
|
|
|
|
|
|
|
ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
|
|
|
|
|
|
RUNDIR := $(ROOT)/.run
|
2026-09-15 05:20:44 +00:00
|
|
|
|
BIN := $(ROOT)/target/debug/lazyboy
|
2026-09-15 03:20:42 +00:00
|
|
|
|
WEBDIR := $(ROOT)/web
|
|
|
|
|
|
|
2026-09-16 07:06:53 +00:00
|
|
|
|
HOST ?= 0.0.0.0
|
2026-09-15 03:20:42 +00:00
|
|
|
|
PORT_API ?= 8787
|
|
|
|
|
|
PORT_WEB ?= 5173
|
|
|
|
|
|
|
|
|
|
|
|
PID_SERVE := $(RUNDIR)/serve.pid
|
|
|
|
|
|
PID_WEB := $(RUNDIR)/web.pid
|
|
|
|
|
|
LOG_SERVE := $(RUNDIR)/serve.log
|
|
|
|
|
|
LOG_WEB := $(RUNDIR)/web.log
|
|
|
|
|
|
|
2026-09-16 07:06:53 +00:00
|
|
|
|
.PHONY: help build build-bin build-ui box start stop restart status logs prepare-ui start-serve start-web stop-serve stop-web
|
2026-09-15 03:20:42 +00:00
|
|
|
|
|
|
|
|
|
|
# Shared POSIX helpers. Sourced by each recipe.
|
|
|
|
|
|
define CTL
|
|
|
|
|
|
alive() {
|
|
|
|
|
|
pidfile="$$1"
|
|
|
|
|
|
[ -f "$$pidfile" ] || return 1
|
|
|
|
|
|
pid=$$(cat "$$pidfile")
|
|
|
|
|
|
[ -n "$$pid" ] && kill -0 "$$pid" 2>/dev/null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
kill_tree() {
|
|
|
|
|
|
pid="$$1"
|
|
|
|
|
|
[ -n "$$pid" ] || return 0
|
|
|
|
|
|
for child in $$(pgrep -P "$$pid" 2>/dev/null); do
|
|
|
|
|
|
kill_tree "$$child"
|
|
|
|
|
|
done
|
|
|
|
|
|
kill -TERM "$$pid" 2>/dev/null || true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
kill_tree_hard() {
|
|
|
|
|
|
pid="$$1"
|
|
|
|
|
|
[ -n "$$pid" ] || return 0
|
|
|
|
|
|
for child in $$(pgrep -P "$$pid" 2>/dev/null); do
|
|
|
|
|
|
kill_tree_hard "$$child"
|
|
|
|
|
|
done
|
|
|
|
|
|
kill -KILL "$$pid" 2>/dev/null || true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
free_port() {
|
|
|
|
|
|
port="$$1"
|
|
|
|
|
|
extra=$$(lsof -nP -tiTCP:"$$port" -sTCP:LISTEN 2>/dev/null || true)
|
|
|
|
|
|
[ -n "$$extra" ] || return 0
|
|
|
|
|
|
echo "freeing :$$port ($$extra)"
|
|
|
|
|
|
kill -TERM $$extra 2>/dev/null || true
|
|
|
|
|
|
sleep 0.2
|
|
|
|
|
|
extra=$$(lsof -nP -tiTCP:"$$port" -sTCP:LISTEN 2>/dev/null || true)
|
|
|
|
|
|
[ -n "$$extra" ] && kill -KILL $$extra 2>/dev/null || true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stop_one() {
|
|
|
|
|
|
name="$$1"
|
|
|
|
|
|
pidfile="$$2"
|
|
|
|
|
|
port="$$3"
|
|
|
|
|
|
if alive "$$pidfile"; then
|
|
|
|
|
|
pid=$$(cat "$$pidfile")
|
|
|
|
|
|
echo "stopping $$name pid=$$pid"
|
|
|
|
|
|
kill_tree "$$pid"
|
|
|
|
|
|
n=0
|
|
|
|
|
|
while [ $$n -lt 15 ] && kill -0 "$$pid" 2>/dev/null; do
|
|
|
|
|
|
sleep 0.2
|
|
|
|
|
|
n=$$((n + 1))
|
|
|
|
|
|
done
|
|
|
|
|
|
kill_tree_hard "$$pid"
|
|
|
|
|
|
elif [ -f "$$pidfile" ]; then
|
|
|
|
|
|
echo "$$name not running (stale pidfile)"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "$$name not running"
|
|
|
|
|
|
fi
|
|
|
|
|
|
rm -f "$$pidfile"
|
|
|
|
|
|
free_port "$$port"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wait_alive() {
|
|
|
|
|
|
pidfile="$$1"
|
|
|
|
|
|
name="$$2"
|
|
|
|
|
|
log="$$3"
|
|
|
|
|
|
i=0
|
|
|
|
|
|
while [ $$i -lt 15 ]; do
|
|
|
|
|
|
if alive "$$pidfile"; then
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
sleep 0.1
|
|
|
|
|
|
i=$$((i + 1))
|
|
|
|
|
|
done
|
|
|
|
|
|
echo "$$name exited immediately. Last log:"
|
|
|
|
|
|
tail -n 40 "$$log" 2>/dev/null || true
|
|
|
|
|
|
rm -f "$$pidfile"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wait_listen() {
|
|
|
|
|
|
pidfile="$$1"
|
|
|
|
|
|
port="$$2"
|
|
|
|
|
|
name="$$3"
|
|
|
|
|
|
log="$$4"
|
|
|
|
|
|
i=0
|
|
|
|
|
|
while [ $$i -lt 50 ]; do
|
|
|
|
|
|
if lsof -nP -iTCP:"$$port" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [ -f "$$pidfile" ]; then
|
|
|
|
|
|
pid=$$(cat "$$pidfile")
|
|
|
|
|
|
if [ -n "$$pid" ] && ! kill -0 "$$pid" 2>/dev/null; then
|
|
|
|
|
|
echo "$$name died before listening on :$$port. Last log:"
|
|
|
|
|
|
tail -n 40 "$$log" 2>/dev/null || true
|
|
|
|
|
|
rm -f "$$pidfile"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
sleep 0.2
|
|
|
|
|
|
i=$$((i + 1))
|
|
|
|
|
|
done
|
|
|
|
|
|
echo "$$name did not listen on :$$port. Last log:"
|
|
|
|
|
|
tail -n 40 "$$log" 2>/dev/null || true
|
|
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
status_one() {
|
|
|
|
|
|
name="$$1"
|
|
|
|
|
|
pidfile="$$2"
|
|
|
|
|
|
port="$$3"
|
|
|
|
|
|
if alive "$$pidfile"; then
|
|
|
|
|
|
pid=$$(cat "$$pidfile")
|
|
|
|
|
|
listen=no
|
|
|
|
|
|
lsof -nP -iTCP:"$$port" -sTCP:LISTEN >/dev/null 2>&1 && listen=yes
|
|
|
|
|
|
echo "$$name running pid=$$pid :$$port listen=$$listen"
|
|
|
|
|
|
elif [ -f "$$pidfile" ]; then
|
|
|
|
|
|
echo "$$name stopped (stale pidfile)"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "$$name stopped"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
load_env() {
|
|
|
|
|
|
if [ -f "$(ROOT)/.env" ]; then
|
|
|
|
|
|
set -a
|
|
|
|
|
|
. "$(ROOT)/.env"
|
|
|
|
|
|
set +a
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
endef
|
|
|
|
|
|
export CTL
|
|
|
|
|
|
|
|
|
|
|
|
help:
|
2026-09-16 07:06:53 +00:00
|
|
|
|
@echo "make build Compile API + production web UI (web/dist; phone uses :$(PORT_API))"
|
|
|
|
|
|
@echo "make box Build Docker computer image lazyboy-box:local (first time / after box/)"
|
|
|
|
|
|
@echo "make start Rebuild and start API (:$(PORT_API)) and frontend (:$(PORT_WEB))"
|
2026-09-15 03:20:42 +00:00
|
|
|
|
@echo "make stop Stop both"
|
2026-09-16 07:06:53 +00:00
|
|
|
|
@echo "make restart Stop, rebuild, start"
|
2026-09-15 03:20:42 +00:00
|
|
|
|
@echo "make status Show PIDs and ports"
|
|
|
|
|
|
@echo "make logs Tail both logs (Ctrl-C to leave)"
|
|
|
|
|
|
|
2026-09-16 07:06:53 +00:00
|
|
|
|
build: build-bin build-ui
|
|
|
|
|
|
@echo "built $(BIN) and $(WEBDIR)/dist"
|
|
|
|
|
|
|
|
|
|
|
|
build-bin:
|
|
|
|
|
|
@echo "building lazyboy…"
|
|
|
|
|
|
@cargo build -p lazyboy
|
|
|
|
|
|
|
|
|
|
|
|
build-ui:
|
|
|
|
|
|
@if [ ! -d "$(WEBDIR)/node_modules" ]; then \
|
|
|
|
|
|
echo "npm install…"; \
|
|
|
|
|
|
(cd "$(WEBDIR)" && npm install) || exit 1; \
|
|
|
|
|
|
fi
|
|
|
|
|
|
@echo "building web UI…"
|
|
|
|
|
|
@(cd "$(WEBDIR)" && npm run build) || exit 1
|
|
|
|
|
|
|
|
|
|
|
|
box:
|
|
|
|
|
|
@echo "building computer image lazyboy-box:local…"
|
|
|
|
|
|
@bash "$(ROOT)/box/build.sh"
|
|
|
|
|
|
|
|
|
|
|
|
start: build
|
|
|
|
|
|
@$(MAKE) start-serve
|
|
|
|
|
|
@$(MAKE) start-web
|
2026-09-15 03:20:42 +00:00
|
|
|
|
@echo
|
|
|
|
|
|
@echo "API http://127.0.0.1:$(PORT_API)"
|
|
|
|
|
|
@echo "UI http://127.0.0.1:$(PORT_WEB)"
|
2026-09-16 07:06:53 +00:00
|
|
|
|
@lan=$$(awk '/手機同一個 Wi-Fi:/ {sub("http://", "", $$NF); sub(/:[0-9]+$$/, "", $$NF); print $$NF; exit}' "$(LOG_SERVE)"); \
|
2026-09-16 06:40:00 +00:00
|
|
|
|
if [ -n "$$lan" ]; then \
|
|
|
|
|
|
echo "Phone http://$$lan:$(PORT_API)"; \
|
|
|
|
|
|
fi
|
2026-09-15 03:20:42 +00:00
|
|
|
|
|
|
|
|
|
|
restart: stop
|
|
|
|
|
|
@$(MAKE) start
|
|
|
|
|
|
|
|
|
|
|
|
stop: stop-web stop-serve
|
|
|
|
|
|
@echo "stopped"
|
|
|
|
|
|
|
|
|
|
|
|
status:
|
|
|
|
|
|
@eval "$$CTL"; \
|
|
|
|
|
|
status_one serve "$(PID_SERVE)" "$(PORT_API)"; \
|
|
|
|
|
|
status_one web "$(PID_WEB)" "$(PORT_WEB)"
|
|
|
|
|
|
|
|
|
|
|
|
logs:
|
|
|
|
|
|
@mkdir -p "$(RUNDIR)"
|
|
|
|
|
|
@touch "$(LOG_SERVE)" "$(LOG_WEB)"
|
|
|
|
|
|
@echo "=== serve $(LOG_SERVE) ==="
|
|
|
|
|
|
@echo "=== web $(LOG_WEB) ==="
|
|
|
|
|
|
@tail -f "$(LOG_SERVE)" "$(LOG_WEB)"
|
|
|
|
|
|
|
2026-09-16 07:06:53 +00:00
|
|
|
|
prepare-ui: build-ui
|
2026-09-16 06:40:00 +00:00
|
|
|
|
|
2026-09-15 03:20:42 +00:00
|
|
|
|
start-serve:
|
|
|
|
|
|
@mkdir -p "$(RUNDIR)"
|
|
|
|
|
|
@eval "$$CTL"; \
|
|
|
|
|
|
if alive "$(PID_SERVE)"; then \
|
2026-09-16 07:06:53 +00:00
|
|
|
|
echo "restarting serve with rebuilt binary"; \
|
2026-09-16 06:40:00 +00:00
|
|
|
|
stop_one serve "$(PID_SERVE)" "$(PORT_API)"; \
|
2026-09-15 03:20:42 +00:00
|
|
|
|
fi; \
|
|
|
|
|
|
load_env; \
|
2026-09-16 07:06:53 +00:00
|
|
|
|
LAZYBOY_WEB_HOST="$(HOST)" LAZYBOY_WEB_PORT="$(PORT_API)" nohup "$(BIN)" serve > "$(LOG_SERVE)" 2>&1 < /dev/null & echo $$! > "$(PID_SERVE)"; \
|
2026-09-15 03:20:42 +00:00
|
|
|
|
wait_alive "$(PID_SERVE)" serve "$(LOG_SERVE)" || exit 1; \
|
|
|
|
|
|
wait_listen "$(PID_SERVE)" "$(PORT_API)" serve "$(LOG_SERVE)" || exit 1; \
|
|
|
|
|
|
echo "started serve pid=$$(cat "$(PID_SERVE)") :$(PORT_API) log=$(LOG_SERVE)"
|
|
|
|
|
|
|
|
|
|
|
|
start-web:
|
|
|
|
|
|
@mkdir -p "$(RUNDIR)"
|
|
|
|
|
|
@eval "$$CTL"; \
|
|
|
|
|
|
if alive "$(PID_WEB)"; then \
|
2026-09-16 07:06:53 +00:00
|
|
|
|
stop_one web "$(PID_WEB)" "$(PORT_WEB)"; \
|
2026-09-15 03:20:42 +00:00
|
|
|
|
fi; \
|
|
|
|
|
|
if [ ! -d "$(WEBDIR)/node_modules" ]; then \
|
|
|
|
|
|
echo "npm install…"; \
|
|
|
|
|
|
(cd "$(WEBDIR)" && npm install) || exit 1; \
|
|
|
|
|
|
fi; \
|
2026-09-16 07:06:53 +00:00
|
|
|
|
LAZYBOY_WEB_PORT="$(PORT_API)" nohup npm --prefix "$(WEBDIR)" run dev -- --host "$(HOST)" --port "$(PORT_WEB)" > "$(LOG_WEB)" 2>&1 < /dev/null & echo $$! > "$(PID_WEB)"; \
|
2026-09-15 03:20:42 +00:00
|
|
|
|
wait_alive "$(PID_WEB)" web "$(LOG_WEB)" || exit 1; \
|
|
|
|
|
|
wait_listen "$(PID_WEB)" "$(PORT_WEB)" web "$(LOG_WEB)" || exit 1; \
|
|
|
|
|
|
echo "started web pid=$$(cat "$(PID_WEB)") :$(PORT_WEB) log=$(LOG_WEB)"
|
|
|
|
|
|
|
|
|
|
|
|
stop-serve:
|
|
|
|
|
|
@eval "$$CTL"; stop_one serve "$(PID_SERVE)" "$(PORT_API)"
|
|
|
|
|
|
|
|
|
|
|
|
stop-web:
|
|
|
|
|
|
@eval "$$CTL"; stop_one web "$(PID_WEB)" "$(PORT_WEB)"
|