2026-05-15 16:00:25 +08:00
SHELL := /bin/bash
DEV_RUST_LOG := info,executor::candidate_loop= debug,stream::execution= debug
i f e q ( $( origin RUST_LOG ) , c o m m a n d l i n e )
DEV_RUST_LOG := $( RUST_LOG)
e n d i f
export DEV_RUST_LOG
2026-05-15 18:10:49 +08:00
.PHONY : dev dev -backend dev -frontend migration backfill
2026-05-15 16:00:25 +08:00
d e f i n e D E V _ B A C K E N D _ S C R I P T
s e t - e u o p i p e f a i l
i f [ ! - f . e n v ] ; t h e n
echo "=> 未找到 .env, 请先执行: cp .env.example .env"
exit 1
f i
s e t - a
s o u r c e . e n v
s e t + a
d o t e n v _ h a s _ k e y ( ) {
local key = " $$ 1 "
grep -Eq " ^[[:space:]]* $$ {key}= " .env
}
l o w e r c a s e ( ) {
printf '%s' " $$ 1 " | tr '[:upper:]' '[:lower:]'
}
d e v _ u s e s _ s q l i t e _ d a t a b a s e ( ) {
local driver
local url
driver = " $$ (lowercase " $$ { AETHER_DATABASE_DRIVER:-} ")"
url = " $$ {AETHER_DATABASE_URL:- $$ {DATABASE_URL:-}} "
[ [ " $$ {driver} " = = "sqlite" || " $$ {url} " = = sqlite:* ] ]
}
d e v _ u s e s _ p o s t g r e s _ d a t a b a s e ( ) {
local driver
local url
driver = " $$ (lowercase " $$ { AETHER_DATABASE_DRIVER:-} ")"
url = " $$ {AETHER_DATABASE_URL:- $$ {DATABASE_URL:-}} "
if [ [ -z " $$ {driver} " && -z " $$ {url} " ] ] ; then
return 0
fi
[ [ " $$ {driver} " = = "postgres" || " $$ {driver} " = = "postgresql" || " $$ {url} " = = postgres:* || " $$ {url} " = = postgresql:* ] ]
}
d e v _ u s e s _ r e d i s _ r u n t i m e ( ) {
local backend
backend = " $$ (lowercase " $$ { AETHER_RUNTIME_BACKEND:-} ")"
if [ [ " $$ {backend} " = = "memory" ] ] ; then
return 1
fi
if [ [ " $$ {backend} " = = "redis" ] ] ; then
return 0
fi
if dev_uses_sqlite_database; then
return 1
fi
return 0
}
p r i n t _ d e v _ i n f r a _ h i n t ( ) {
echo "=> 本地开发依赖未就绪。"
echo "=> 可手动启动 Postgres / Redis:"
echo "=> docker compose up -d postgres redis"
}
c h e c k _ p o s t g r e s _ r e a d y ( ) {
local host = " $$ 1 "
local port = " $$ 2 "
if command -v pg_isready >/dev/null 2>& 1; then
pg_isready -h " $$ {host} " -p " $$ {port} " >/dev/null 2>& 1
return $$ ?
fi
if command -v nc >/dev/null 2>& 1; then
nc -z " $$ {host} " " $$ {port} " >/dev/null 2>& 1
return $$ ?
fi
return 0
}
c h e c k _ r e d i s _ r e a d y ( ) {
local host = " $$ 1 "
local port = " $$ 2 "
local password = " $$ 3 "
if command -v redis-cli >/dev/null 2>& 1; then
REDISCLI_AUTH = " $$ {password} " redis-cli -h " $$ {host} " -p " $$ {port} " ping >/dev/null 2>& 1
return $$ ?
fi
if command -v nc >/dev/null 2>& 1; then
nc -z " $$ {host} " " $$ {port} " >/dev/null 2>& 1
return $$ ?
fi
return 0
}
i s _ l o c a l _ h o s t ( ) {
case " $$ 1 " in
localhost| 127.0.0.1| ::1)
return 0
; ;
esac
return 1
}
e n s u r e _ d e v _ i n f r a ( ) {
local postgres_host = " $$ {DB_HOST:-localhost} "
local postgres_port = " $$ {DB_PORT:-5432} "
local redis_host = " $$ {REDIS_HOST:-localhost} "
local redis_port = " $$ {REDIS_PORT:-6379} "
local redis_password = " $$ {REDIS_PASSWORD:-} "
local need_postgres = false
local need_redis = false
local services = ( )
if dev_uses_postgres_database; then
if ! check_postgres_ready " $$ {postgres_host} " " $$ {postgres_port} " ; then
if is_local_host " $$ {postgres_host} " ; then
need_postgres = true
services += ( postgres)
else
echo " => PostgreSQL 不可用: $$ {postgres_host}: $$ {postgres_port} "
print_dev_infra_hint
return 1
fi
fi
fi
if dev_uses_redis_runtime; then
if ! check_redis_ready " $$ {redis_host} " " $$ {redis_port} " " $$ {redis_password} " ; then
if is_local_host " $$ {redis_host} " ; then
need_redis = true
services += ( redis)
else
echo " => Redis 不可用: $$ {redis_host}: $$ {redis_port} "
print_dev_infra_hint
return 1
fi
fi
fi
if [ " $$ {#services[@]} " -eq 0 ] ; then
return 0
fi
if ! command -v docker >/dev/null 2>& 1; then
echo "=> 未找到 docker, 无法自动启动本地开发依赖。"
print_dev_infra_hint
return 1
fi
echo " => 本地开发依赖未就绪,正在启动: docker compose up -d $$ {services[*]} "
if ! docker compose up -d " $$ {services[@]} " ; then
echo "=> docker compose 启动本地开发依赖失败。"
print_dev_infra_hint
return 1
fi
for _ in { 1..100} ; do
local ready = true
if [ " $$ {need_postgres} " = "true" ] && ! check_postgres_ready " $$ {postgres_host} " " $$ {postgres_port} " ; then
ready = false
fi
if [ " $$ {need_redis} " = "true" ] && ! check_redis_ready " $$ {redis_host} " " $$ {redis_port} " " $$ {redis_password} " ; then
ready = false
fi
if [ " $$ {ready} " = "true" ] ; then
return 0
fi
sleep 0.2
done
if [ " $$ {need_postgres} " = "true" ] && ! check_postgres_ready " $$ {postgres_host} " " $$ {postgres_port} " ; then
echo " => PostgreSQL 不可用: $$ {postgres_host}: $$ {postgres_port} "
fi
if [ " $$ {need_redis} " = "true" ] && ! check_redis_ready " $$ {redis_host} " " $$ {redis_port} " " $$ {redis_password} " ; then
echo " => Redis 不可用: $$ {redis_host}: $$ {redis_port} "
fi
print_dev_infra_hint
return 1
}
p r i n t _ s t a r t u p _ f a i l u r e _ h i n t ( ) {
local log_file = " $$ 1 "
if [ -n " $$ {log_file} " ] && [ -f " $$ {log_file} " ] ; then
if grep -Eq "database schema is behind" " $$ {log_file} " ; then
2026-05-15 18:10:49 +08:00
echo "=> 检测到数据库 schema 落后,请执行: make migration"
2026-05-15 16:00:25 +08:00
return
fi
if grep -Eq "database backfills are behind" " $$ {log_file} " ; then
2026-05-15 18:10:49 +08:00
echo "=> 检测到待执行 backfills, 请执行: make backfill"
2026-05-15 16:00:25 +08:00
return
fi
fi
echo "=> 未识别到明确的修复动作,请根据上面的日志继续排查。"
}
w a i t _ f o r _ s t a r t u p ( ) {
local pid = " $$ 1 "
local timeout_seconds = " $$ 2 "
local service_name = " $$ 3 "
shift 3
STARTUP_WAIT_EARLY_EXIT = false
local attempts = $$ ( ( timeout_seconds * 10) )
if [ " $$ {attempts} " -lt 1 ] ; then
attempts = 1
fi
for ( ( i = 0; i < attempts; i++) ) ; do
if " $$ @ " >/dev/null 2>& 1; then
return 0
fi
if ! kill -0 " $$ {pid} " >/dev/null 2>& 1; then
STARTUP_WAIT_EARLY_EXIT = true
echo " => $$ {service_name} 启动进程已提前退出,请检查上面的日志。 "
print_startup_failure_hint " $$ {GATEWAY_LOG_FILE} "
return 1
fi
sleep 0.1
done
if " $$ @ " >/dev/null 2>& 1; then
return 0
fi
if ! kill -0 " $$ {pid} " >/dev/null 2>& 1; then
STARTUP_WAIT_EARLY_EXIT = true
echo " => $$ {service_name} 启动进程已提前退出,请检查上面的日志。 "
print_startup_failure_hint " $$ {GATEWAY_LOG_FILE} "
return 1
fi
echo " => $$ {service_name} 在 $$ {timeout_seconds}s 内未通过启动检查。 "
echo "=> 如果这是冷编译或存在并发 cargo 构建,可调大启动超时后重试。"
return 1
}
c r e a t e _ g a t e w a y _ l o g _ f i l e ( ) {
local tmp_root = " $$ {TMPDIR:-/tmp} "
tmp_root = " $$ {tmp_root%/} "
GATEWAY_LOG_DIR = " $$ (mktemp -d " $$ { tmp_root} /aether-dev-startup.XXXXXX")"
GATEWAY_LOG_FILE = " $$ {GATEWAY_LOG_DIR}/gateway.log "
: > " $$ {GATEWAY_LOG_FILE} "
}
c l e a n u p ( ) {
local status = " $$ {1:-0} "
trap - INT TERM EXIT
if [ -n " $$ {GATEWAY_PID:-} " ] ; then
echo ""
echo "=> 停止 aether-gateway..."
kill " $$ {GATEWAY_PID} " >/dev/null 2>& 1 || true
wait " $$ {GATEWAY_PID} " >/dev/null 2>& 1 || true
fi
if [ -n " $$ {GATEWAY_LOG_FILE:-} " ] && [ -f " $$ {GATEWAY_LOG_FILE} " ] ; then
rm -f " $$ {GATEWAY_LOG_FILE} "
fi
if [ -n " $$ {GATEWAY_LOG_DIR:-} " ] && [ -d " $$ {GATEWAY_LOG_DIR} " ] ; then
rmdir " $$ {GATEWAY_LOG_DIR} " >/dev/null 2>& 1 || true
fi
exit " $$ {status} "
}
t r a p 'cleanup 130' I N T
t r a p 'cleanup 143' T E R M
t r a p 'cleanup $$?' E X I T
export APP_PORT = " $$ {APP_PORT:-8084} "
export RUST_LOG = " $$ {DEV_RUST_LOG} "
RUST_SERVICE_STARTUP_TIMEOUT_SECONDS = " $$ {RUST_SERVICE_STARTUP_TIMEOUT_SECONDS:-180} "
GATEWAY_STARTUP_TIMEOUT_SECONDS = " $$ {GATEWAY_STARTUP_TIMEOUT_SECONDS:- $$ {RUST_SERVICE_STARTUP_TIMEOUT_SECONDS}} "
export AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE = " $$ {AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE:-rust-authoritative} "
i f d e v _ u s e s _ p o s t g r e s _ d a t a b a s e ; t h e n
export DATABASE_URL = " postgresql:// $$ {DB_USER:-postgres}: $$ {DB_PASSWORD:-}@ $$ {DB_HOST:-localhost}: $$ {DB_PORT:-5432}/ $$ {DB_NAME:-aether} "
if ! dotenv_has_key "AETHER_GATEWAY_DATA_POSTGRES_URL" ; then
export AETHER_GATEWAY_DATA_POSTGRES_URL = " $$ {DATABASE_URL} "
fi
f i
i f d e v _ u s e s _ r e d i s _ r u n t i m e ; t h e n
export REDIS_URL = " redis://: $$ {REDIS_PASSWORD:-}@ $$ {REDIS_HOST:-localhost}: $$ {REDIS_PORT:-6379}/0 "
if ! dotenv_has_key "AETHER_GATEWAY_DATA_REDIS_URL" ; then
export AETHER_GATEWAY_DATA_REDIS_URL = " $$ {REDIS_URL} "
fi
e l s e
unset REDIS_URL
unset AETHER_GATEWAY_DATA_REDIS_URL
f i
i f ! d o t e n v _ h a s _ k e y "AETHER_GATEWAY_DATA_ENCRYPTION_KEY" ; t h e n
export AETHER_GATEWAY_DATA_ENCRYPTION_KEY = " $$ {ENCRYPTION_KEY:-} "
f i
export DB_POOL_SIZE = " $$ {DB_POOL_SIZE:-5} "
export DB_MAX_OVERFLOW = " $$ {DB_MAX_OVERFLOW:-5} "
export HTTP_MAX_CONNECTIONS = " $$ {HTTP_MAX_CONNECTIONS:-20} "
export HTTP_KEEPALIVE_CONNECTIONS = " $$ {HTTP_KEEPALIVE_CONNECTIONS:-5} "
i f ! c o m m a n d - v c a r g o > / d e v / n u l l 2 > & 1 ; t h e n
echo "=> 未找到 cargo, 无法启动 aether-gateway。请先安装 Rust toolchain。"
exit 1
f i
i f ! c o m m a n d - v c u r l > / d e v / n u l l 2 > & 1 ; t h e n
echo "=> 未找到 curl, 无法检查 aether-gateway 健康状态。请先安装 curl。"
exit 1
f i
i f ! e n s u r e _ d e v _ i n f r a ; t h e n
exit 1
f i
GATEWAY_PID = ""
GATEWAY_LOG_DIR = ""
GATEWAY_LOG_FILE = ""
STARTUP_WAIT_EARLY_EXIT = false
c r e a t e _ g a t e w a y _ l o g _ f i l e
echo "=> 启动 aether-gateway (Rust frontdoor : 0.0.0.0:$${APP_PORT })..."
echo "=> 日志过滤 : $${RUST_LOG }"
echo "=> 执行命令 : cargo run -p aether -gateway -- --app -port $${APP_PORT }"
c a r g o r u n - p a e t h e r - g a t e w a y - - - - a p p - p o r t "$${APP_PORT}" > > (
tee -a " $$ {GATEWAY_LOG_FILE} "
) 2 > & 1 &
GATEWAY_PID = $$ !
if ! wait_for_startup "$${GATEWAY_PID}" "$${GATEWAY_STARTUP_TIMEOUT_SECONDS}" "aether-gateway" curl -sf "http : //127.0.0.1:$${APP_PORT }/_gateway /health "; then
if [ " $$ {STARTUP_WAIT_EARLY_EXIT} " = "true" ] ; then
GATEWAY_PID = ""
fi
exit 1
f i
i f w a i t "$${GATEWAY_PID}" ; t h e n
gateway_exit_code = 0
e l s e
gateway_exit_code = $$ ?
f i
GATEWAY_PID = ""
i f [ "$${gateway_exit_code}" - n e 1 3 0 ] & & [ "$${gateway_exit_code}" - n e 1 4 3 ] ; t h e n
echo "=> aether-gateway 运行失败并已退出,请检查上面的日志。"
print_startup_failure_hint " $$ {GATEWAY_LOG_FILE} "
f i
e x i t "$${gateway_exit_code}"
e n d e f
export DEV_BACKEND_SCRIPT
d e f i n e D E V _ S C R I P T
s e t - e u o p i p e f a i l
backend_pid = ""
frontend_pid = ""
c l e a n u p ( ) {
local status = " $$ {1:-0} "
trap - INT TERM EXIT
if [ -n " $$ {backend_pid} " ] || [ -n " $$ {frontend_pid} " ] ; then
echo ""
echo "=> 停止本地开发服务..."
if [ -n " $$ {backend_pid} " ] ; then
kill " $$ {backend_pid} " >/dev/null 2>& 1 || true
wait " $$ {backend_pid} " >/dev/null 2>& 1 || true
fi
if [ -n " $$ {frontend_pid} " ] ; then
kill " $$ {frontend_pid} " >/dev/null 2>& 1 || true
wait " $$ {frontend_pid} " >/dev/null 2>& 1 || true
fi
fi
exit " $$ {status} "
}
w a i t _ f o r _ b a c k e n d _ r e a d y ( ) {
while :; do
if curl -sf " http://127.0.0.1: $$ {APP_PORT}/_gateway/health " >/dev/null 2>& 1; then
return 0
fi
if ! kill -0 " $$ {backend_pid} " >/dev/null 2>& 1; then
if wait " $$ {backend_pid} " ; then
status = 0
else
status = $$ ?
fi
if [ " $$ {status} " -ne 0 ] ; then
echo " => 后端进程已退出 (status $$ {status}) "
else
echo "=> 后端进程已退出"
fi
backend_pid = ""
cleanup " $$ {status} "
fi
sleep 0.2
done
}
t r a p 'cleanup 130' I N T
t r a p 'cleanup 143' T E R M
t r a p 'cleanup $$?' E X I T
i f [ - f . e n v ] ; t h e n
set -a
source .env
set +a
f i
export APP_PORT = " $$ {APP_PORT:-8084} "
echo "=> 启动后端 : RUST_LOG =$${DEV_RUST_LOG } cargo run -p aether -gateway -- --app -port $${APP_PORT :-8084}"
/ b i n / b a s h - e u o p i p e f a i l - c "$$DEV_BACKEND_SCRIPT" &
backend_pid = $$ !
echo "=> 等待后端健康检查 : http ://127.0.0.1:$${APP_PORT }/_gateway /health "
w a i t _ f o r _ b a c k e n d _ r e a d y
echo "=> 启动前端 : cd frontend && npm run dev "
( c d f r o n t e n d & & e x e c n p m r u n d e v ) &
frontend_pid = $$ !
while : ; do
if ! kill -0 " $$ {backend_pid} " >/dev/null 2>& 1; then
if wait " $$ {backend_pid} " ; then
status = 0
else
status = $$ ?
fi
if [ " $$ {status} " -ne 0 ] ; then
echo " => 后端进程已退出 (status $$ {status}) "
else
echo "=> 后端进程已退出"
fi
backend_pid = ""
cleanup " $$ {status} "
fi
if ! kill -0 " $$ {frontend_pid} " >/dev/null 2>& 1; then
if wait " $$ {frontend_pid} " ; then
status = 0
else
status = $$ ?
fi
if [ " $$ {status} " -ne 0 ] ; then
echo " => 前端进程已退出 (status $$ {status}) "
else
echo "=> 前端进程已退出"
fi
frontend_pid = ""
cleanup " $$ {status} "
fi
sleep 1
d o n e
e n d e f
export DEV_SCRIPT
2026-05-15 18:10:49 +08:00
d e f i n e D B _ T A S K _ S C R I P T
s e t - e u o p i p e f a i l
if [ -z "$${DB_TASK_FLAG : -}" ] | | [ -z "$${DB_TASK_LABEL :-}" ]; then
echo "=> 内部错误: DB_TASK_FLAG / DB_TASK_LABEL 未设置"
exit 1
f i
i f [ ! - f . e n v ] ; t h e n
echo "=> 未找到 .env, 请先执行: cp .env.example .env"
exit 1
f i
s e t - a
s o u r c e . e n v
s e t + a
d o t e n v _ h a s _ k e y ( ) {
local key = " $$ 1 "
grep -Eq " ^[[:space:]]* $$ {key}= " .env
}
l o w e r c a s e ( ) {
printf '%s' " $$ 1 " | tr '[:upper:]' '[:lower:]'
}
u s e s _ p o s t g r e s _ d a t a b a s e ( ) {
local driver
local url
driver = " $$ (lowercase " $$ { AETHER_DATABASE_DRIVER:-} ")"
url = " $$ {AETHER_DATABASE_URL:- $$ {DATABASE_URL:-}} "
if [ [ -z " $$ {driver} " && -z " $$ {url} " ] ] ; then
return 0
fi
[ [ " $$ {driver} " = = "postgres" || " $$ {driver} " = = "postgresql" || " $$ {url} " = = postgres:* || " $$ {url} " = = postgresql:* ] ]
}
i f u s e s _ p o s t g r e s _ d a t a b a s e ; t h e n
export DATABASE_URL = " postgresql:// $$ {DB_USER:-postgres}: $$ {DB_PASSWORD:-}@ $$ {DB_HOST:-localhost}: $$ {DB_PORT:-5432}/ $$ {DB_NAME:-aether} "
if ! dotenv_has_key "AETHER_GATEWAY_DATA_POSTGRES_URL" ; then
export AETHER_GATEWAY_DATA_POSTGRES_URL = " $$ {DATABASE_URL} "
fi
f i
i f ! d o t e n v _ h a s _ k e y "AETHER_GATEWAY_DATA_ENCRYPTION_KEY" ; t h e n
export AETHER_GATEWAY_DATA_ENCRYPTION_KEY = " $$ {ENCRYPTION_KEY:-} "
f i
i f ! c o m m a n d - v c a r g o > / d e v / n u l l 2 > & 1 ; t h e n
echo " => 未找到 cargo, 无法执行 $$ {DB_TASK_LABEL}。请先安装 Rust toolchain。 "
exit 1
f i
echo "=> 执行 $${DB_TASK_LABEL} : cargo run -p aether -gateway -- $${DB_TASK_FLAG }"
e x e c c a r g o r u n - p a e t h e r - g a t e w a y - - "$${DB_TASK_FLAG}"
e n d e f
export DB_TASK_SCRIPT
2026-05-15 16:00:25 +08:00
dev :
@$( SHELL) -euo pipefail -c " $$ DEV_SCRIPT "
dev-backend :
@$( SHELL) -euo pipefail -c " $$ DEV_BACKEND_SCRIPT "
dev-frontend :
@cd frontend && npm run dev
2026-05-15 18:10:49 +08:00
migration :
@DB_TASK_FLAG= --migrate DB_TASK_LABEL = "数据库迁移" $( SHELL) -euo pipefail -c " $$ DB_TASK_SCRIPT "
backfill :
@DB_TASK_FLAG= --apply-backfills DB_TASK_LABEL = "数据库 backfill" $( SHELL) -euo pipefail -c " $$ DB_TASK_SCRIPT "