One word everywhere: ReplyPier in UI copy, package and image names, the replypier.site domains (app/api/docs/help), REPLYPIER_* settings and the --rp-* design tokens. - public widget API is window.ReplyPier with replypierId / replypierBaseUrl / replypierConfig / replypierToken, loader at webclient/replypier.min.js; both bundles rebuilt from source - NOTICE, llms.txt, CONTRIBUTING, HELP_CENTER_INFRA and the demo runtime config rewritten; the demo config ships no third-party keys - migration adds the ReplyPier managed model value to the AI model enum - local compose project, database, role, volume and Docker network carry the same name
160 lines
5.1 KiB
Bash
Executable File
160 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
umask 077
|
|
|
|
LOCAL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT="$(cd "${LOCAL}/../.." && pwd)"
|
|
STATE="${LOCAL}/.state"
|
|
PYTHON="${ROOT}/backend/.venv/bin/python"
|
|
export ENV_FILE="${ROOT}/backend/.env"
|
|
export NO_ENTERPRISE=1
|
|
|
|
compose() {
|
|
docker --context colima-bytedesk compose --env-file "${LOCAL}/.env" -f "${LOCAL}/compose.yaml" "$@"
|
|
}
|
|
|
|
start_process() {
|
|
local name="$1" directory="$2" pid
|
|
shift 2
|
|
if [[ -f "${STATE}/${name}.pid" ]] && kill -0 "$(cat "${STATE}/${name}.pid")" 2>/dev/null; then
|
|
printf '%s is already running.\n' "${name}"
|
|
return
|
|
fi
|
|
pid="$("${PYTHON}" - "${STATE}/${name}.log" "${directory}" "$@" <<'PY'
|
|
import subprocess
|
|
import sys
|
|
|
|
with open(sys.argv[1], "a") as log:
|
|
process = subprocess.Popen(sys.argv[3:], cwd=sys.argv[2], stdin=subprocess.DEVNULL,
|
|
stdout=log, stderr=log, start_new_session=True)
|
|
print(process.pid)
|
|
PY
|
|
)"
|
|
printf '%s\n' "${pid}" > "${STATE}/${name}.pid"
|
|
}
|
|
|
|
stop_process() {
|
|
local name="$1" pid command
|
|
if [[ ! -f "${STATE}/${name}.pid" ]]; then return; fi
|
|
pid="$(cat "${STATE}/${name}.pid")"
|
|
if kill -0 "${pid}" 2>/dev/null; then
|
|
command="$(ps -p "${pid}" -o command=)"
|
|
if [[ "${command}" != *"${ROOT}"* ]]; then
|
|
printf 'Refusing to stop an unrelated process at PID %s.\n' "${pid}" >&2
|
|
return 1
|
|
fi
|
|
kill "${pid}"
|
|
for attempt in {1..40}; do
|
|
if ! kill -0 "${pid}" 2>/dev/null; then break; fi
|
|
sleep 0.25
|
|
done
|
|
fi
|
|
rm -f "${STATE}/${name}.pid"
|
|
}
|
|
|
|
build_frontend() {
|
|
mkdir -p "${STATE}/assets"
|
|
cd "${ROOT}/frontend"
|
|
node_modules/.bin/vite build --outDir "${STATE}/dashboard" --emptyOutDir
|
|
node_modules/.bin/vite build --config vite.widget.config.ts --outDir "${STATE}/assets" --emptyOutDir
|
|
if [[ ! -f "${STATE}/assets/widget.css" ]]; then
|
|
cp "${STATE}"/assets/unused/*.css "${STATE}/assets/widget.css"
|
|
fi
|
|
node_modules/.bin/esbuild src/webclient/replypier.js --bundle --minify --format=iife \
|
|
--target=es2015 --loader:.svg=text --define:process.env.NODE_ENV='"production"' \
|
|
--define:__REPLYPIER_API_URL__='"http://127.0.0.1:8018/api/v1"' \
|
|
--outfile="${STATE}/dashboard/webclient/replypier.min.js"
|
|
cat > "${STATE}/dashboard/config.js" <<'JS'
|
|
self.APP_CONFIG = {
|
|
API_URL: "http://127.0.0.1:8018/api/v1",
|
|
WS_URL: "ws://127.0.0.1:8018",
|
|
WIDGET_URL: "http://127.0.0.1:8018",
|
|
NODE_ENV: "production"
|
|
};
|
|
JS
|
|
if [[ -f "${STATE}/test-environment.json" ]]; then
|
|
"${PYTHON}" - "${LOCAL}" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
local = Path(sys.argv[1])
|
|
state = local / ".state"
|
|
environment = json.loads((state / "test-environment.json").read_text())
|
|
template = (local / "demo.template.html").read_text()
|
|
(state / "dashboard/local-demo.html").write_text(template.replace("__WIDGET_ID__", environment["widget_id"]))
|
|
PY
|
|
fi
|
|
}
|
|
|
|
case "${1:-status}" in
|
|
init)
|
|
python3 "${LOCAL}/init_env.py"
|
|
;;
|
|
setup)
|
|
python3 "${LOCAL}/init_env.py"
|
|
if [[ ! -x "${PYTHON}" ]]; then
|
|
uv venv --python 3.12 "${ROOT}/backend/.venv"
|
|
fi
|
|
uv pip install --python "${PYTHON}" -r "${LOCAL}/requirements.lock.txt"
|
|
(cd "${ROOT}/frontend" && npm ci --no-audit --no-fund)
|
|
build_frontend
|
|
;;
|
|
build)
|
|
build_frontend
|
|
;;
|
|
up)
|
|
if [[ ! -f "${STATE}/dashboard/index.html" ]]; then
|
|
printf 'Run %s setup first.\n' "$0" >&2
|
|
exit 1
|
|
fi
|
|
if ! colima --profile bytedesk status >/dev/null 2>&1; then
|
|
colima --profile bytedesk start --activate=false --ssh-config=false
|
|
fi
|
|
compose up -d --wait --wait-timeout 180
|
|
compose exec -T db psql -U replypier -d replypier -c 'CREATE EXTENSION IF NOT EXISTS vector;'
|
|
(cd "${ROOT}/backend" && "${PYTHON}" -m alembic upgrade head)
|
|
start_process backend "${ROOT}/backend" "${PYTHON}" -m uvicorn app.main:app --host 127.0.0.1 --port 8018
|
|
start_process knowledge "${ROOT}/backend" "${PYTHON}" -m app.workers.run_knowledge_processor
|
|
start_process frontend "${ROOT}/frontend" "$(command -v node)" "${ROOT}/frontend/node_modules/vite/bin/vite.js" preview --outDir "${STATE}/dashboard" --host 127.0.0.1 --port 3018 --strictPort
|
|
for attempt in {1..90}; do
|
|
if curl --silent --fail http://127.0.0.1:8018/health >/dev/null && curl --silent --fail http://127.0.0.1:3018/ >/dev/null; then
|
|
printf '\nReplyPier: http://127.0.0.1:3018/\n'
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
printf 'Startup did not become healthy; inspect %s/*.log\n' "${STATE}" >&2
|
|
exit 1
|
|
;;
|
|
stop)
|
|
stop_process frontend
|
|
stop_process knowledge
|
|
stop_process backend
|
|
compose stop
|
|
;;
|
|
restart)
|
|
stop_process frontend
|
|
stop_process knowledge
|
|
stop_process backend
|
|
exec "$0" up
|
|
;;
|
|
status)
|
|
compose ps
|
|
for name in backend knowledge frontend; do
|
|
if [[ -f "${STATE}/${name}.pid" ]]; then
|
|
ps -p "$(cat "${STATE}/${name}.pid")" -o pid=,etime=,command= || true
|
|
fi
|
|
done
|
|
curl --silent --show-error --fail http://127.0.0.1:8018/health
|
|
printf '\n'
|
|
;;
|
|
logs)
|
|
tail -n 70 "${STATE}/${2:-backend}.log"
|
|
;;
|
|
*)
|
|
printf 'Usage: %s {init|setup|build|up|stop|restart|status|logs [backend|knowledge|frontend]}\n' "$0" >&2
|
|
exit 2
|
|
;;
|
|
esac
|