feat: initialize managed portal
This commit is contained in:
15
managed/store_dwell_alert/scripts/bootstrap_108.sh
Normal file
15
managed/store_dwell_alert/scripts/bootstrap_108.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${PROJECT_DIR:-$HOME/store_dwell_alert}"
|
||||
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ffmpeg python3-venv
|
||||
|
||||
"$PYTHON_BIN" -m venv "$PROJECT_DIR/.venv"
|
||||
"$PROJECT_DIR/.venv/bin/pip" install --upgrade pip
|
||||
"$PROJECT_DIR/.venv/bin/pip" install --no-cache-dir --index-url https://download.pytorch.org/whl/cu124 torch torchvision
|
||||
"$PROJECT_DIR/.venv/bin/pip" install -r "$PROJECT_DIR/requirements.txt"
|
||||
|
||||
echo "Bootstrap complete for $PROJECT_DIR"
|
||||
43
managed/store_dwell_alert/scripts/docker-entrypoint.sh
Executable file
43
managed/store_dwell_alert/scripts/docker-entrypoint.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR="/app"
|
||||
CONFIG_TEMPLATE="${PROJECT_DIR}/config/config.example.yaml"
|
||||
CONFIG_PATH="${CONFIG_PATH:-${PROJECT_DIR}/config/local.yaml}"
|
||||
LOG_DIR="${PROJECT_DIR}/logs"
|
||||
CAMERA_ID="${CAMERA_ID:-store_cam_01}"
|
||||
RTSP_URL="${RTSP_URL:-}"
|
||||
EVENT_SINK_PATH="${EVENT_SINK_PATH:-logs/events.jsonl}"
|
||||
API_HOST="${API_HOST:-0.0.0.0}"
|
||||
API_PORT="${API_PORT:-18081}"
|
||||
|
||||
mkdir -p "${LOG_DIR}" "$(dirname "${CONFIG_PATH}")"
|
||||
|
||||
if [ ! -f "${CONFIG_PATH}" ]; then
|
||||
cp "${CONFIG_TEMPLATE}" "${CONFIG_PATH}"
|
||||
fi
|
||||
|
||||
python - "$CONFIG_PATH" "$CAMERA_ID" "$RTSP_URL" "$EVENT_SINK_PATH" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
config_path = Path(sys.argv[1])
|
||||
camera_id = sys.argv[2]
|
||||
rtsp_url = sys.argv[3]
|
||||
event_sink_path = sys.argv[4]
|
||||
|
||||
raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||
raw["camera_id"] = camera_id
|
||||
stream = raw.setdefault("stream", {})
|
||||
if rtsp_url:
|
||||
stream["rtsp_url"] = rtsp_url
|
||||
event_sink = raw.setdefault("event_sink", {})
|
||||
event_sink["path"] = event_sink_path
|
||||
config_path.write_text(
|
||||
yaml.safe_dump(raw, allow_unicode=True, sort_keys=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
PY
|
||||
|
||||
exec python -m app.manage_api --config "${CONFIG_PATH}" --host "${API_HOST}" --port "${API_PORT}"
|
||||
107
managed/store_dwell_alert/scripts/install.sh
Executable file
107
managed/store_dwell_alert/scripts/install.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PYTHON_BIN="${PYTHON_BIN:-python3.12}"
|
||||
VENV_DIR="${PROJECT_DIR}/.venv"
|
||||
WHEELHOUSE_DIR="${PROJECT_DIR}/wheelhouse"
|
||||
LOCK_FILE="${PROJECT_DIR}/requirements.lock.txt"
|
||||
WEIGHTS_FILE="${PROJECT_DIR}/weights/yolo11n.pt"
|
||||
RUN_SCRIPT="${PROJECT_DIR}/scripts/run.sh"
|
||||
INSTALL_SERVICE_SCRIPT="${PROJECT_DIR}/scripts/install_service.sh"
|
||||
PROJECT_USER="${SUDO_USER:-$(id -un)}"
|
||||
|
||||
run_privileged() {
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
"$@"
|
||||
return
|
||||
fi
|
||||
sudo "$@"
|
||||
}
|
||||
|
||||
run_project_user() {
|
||||
if [[ "$(id -u)" -eq 0 && -n "${SUDO_USER:-}" ]]; then
|
||||
sudo -u "${PROJECT_USER}" -H "$@"
|
||||
return
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
ensure_system_package() {
|
||||
local command_name="$1"
|
||||
local package_name="$2"
|
||||
if command -v "${command_name}" >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Installing missing package: ${package_name}"
|
||||
run_privileged apt-get -o Acquire::ForceIPv4=true update
|
||||
run_privileged apt-get -o Acquire::ForceIPv4=true install -y "${package_name}"
|
||||
}
|
||||
|
||||
require_file() {
|
||||
local target="$1"
|
||||
if [[ ! -e "${target}" ]]; then
|
||||
echo "Missing required file: ${target}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
|
||||
echo "Python interpreter not found: ${PYTHON_BIN}" >&2
|
||||
echo "Set PYTHON_BIN=/path/to/python3.12 if needed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_system_package ffmpeg ffmpeg
|
||||
|
||||
if [[ ! -d "/usr/lib/python3.12/venv" && ! -d "/usr/lib/python3.12/ensurepip" ]]; then
|
||||
echo "Installing missing package: python3.12-venv"
|
||||
run_privileged apt-get -o Acquire::ForceIPv4=true update
|
||||
run_privileged apt-get -o Acquire::ForceIPv4=true install -y python3.12-venv
|
||||
fi
|
||||
|
||||
if ! command -v nvidia-smi >/dev/null 2>&1; then
|
||||
echo "nvidia-smi is required but not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_file "${LOCK_FILE}"
|
||||
require_file "${WEIGHTS_FILE}"
|
||||
require_file "${RUN_SCRIPT}"
|
||||
require_file "${INSTALL_SERVICE_SCRIPT}"
|
||||
|
||||
if [[ ! -d "${WHEELHOUSE_DIR}" ]]; then
|
||||
echo "Missing wheelhouse directory: ${WHEELHOUSE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_project_user "${PYTHON_BIN}" -m venv "${VENV_DIR}"
|
||||
run_project_user "${VENV_DIR}/bin/python" -m ensurepip --upgrade
|
||||
while IFS= read -r requirement; do
|
||||
if [[ -z "${requirement}" ]]; then
|
||||
continue
|
||||
fi
|
||||
run_project_user "${VENV_DIR}/bin/python" -m pip install \
|
||||
--no-index \
|
||||
--no-deps \
|
||||
--find-links "${WHEELHOUSE_DIR}" \
|
||||
"${requirement}"
|
||||
done < "${LOCK_FILE}"
|
||||
|
||||
mkdir -p \
|
||||
"${PROJECT_DIR}/config" \
|
||||
"${PROJECT_DIR}/data/runtime" \
|
||||
"${PROJECT_DIR}/data/staff_gallery" \
|
||||
"${PROJECT_DIR}/logs"
|
||||
|
||||
run_project_user bash "${RUN_SCRIPT}" --prepare-only
|
||||
bash "${INSTALL_SERVICE_SCRIPT}"
|
||||
run_privileged systemctl enable --now store-dwell-alert.service
|
||||
|
||||
cat <<EOF
|
||||
Offline install complete.
|
||||
Service started and enabled on boot: store-dwell-alert.service
|
||||
Runtime log: ${PROJECT_DIR}/logs/runtime.log
|
||||
Event sink: ${PROJECT_DIR}/logs/events.jsonl
|
||||
EOF
|
||||
33
managed/store_dwell_alert/scripts/install_service.sh
Executable file
33
managed/store_dwell_alert/scripts/install_service.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TEMPLATE_PATH="${PROJECT_DIR}/deploy/store-dwell-alert.service.tpl"
|
||||
CONFIG_PATH="${CONFIG_PATH:-${PROJECT_DIR}/config/local.yaml}"
|
||||
SERVICE_NAME="${SERVICE_NAME:-store-dwell-alert.service}"
|
||||
OUTPUT_PATH="${PROJECT_DIR}/deploy/${SERVICE_NAME}"
|
||||
RUN_USER="${RUN_USER:-${SUDO_USER:-$(id -un)}}"
|
||||
RUN_GROUP="${RUN_GROUP:-$(id -gn "${RUN_USER}")}"
|
||||
|
||||
if [[ ! -f "${TEMPLATE_PATH}" ]]; then
|
||||
echo "Missing service template: ${TEMPLATE_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${CONFIG_PATH}" ]]; then
|
||||
echo "Missing config file: ${CONFIG_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed \
|
||||
-e "s|__PROJECT_DIR__|${PROJECT_DIR}|g" \
|
||||
-e "s|__CONFIG_PATH__|${CONFIG_PATH}|g" \
|
||||
-e "s|__RUN_USER__|${RUN_USER}|g" \
|
||||
-e "s|__RUN_GROUP__|${RUN_GROUP}|g" \
|
||||
"${TEMPLATE_PATH}" > "${OUTPUT_PATH}"
|
||||
|
||||
sudo cp "${OUTPUT_PATH}" "/etc/systemd/system/${SERVICE_NAME}"
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
echo "Service installed to /etc/systemd/system/${SERVICE_NAME}"
|
||||
echo "Enable and start it with: sudo systemctl enable --now ${SERVICE_NAME}"
|
||||
64
managed/store_dwell_alert/scripts/package_bundle.sh
Executable file
64
managed/store_dwell_alert/scripts/package_bundle.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BUNDLE_NAME="${BUNDLE_NAME:-store_dwell_alert_bundle}"
|
||||
BUILD_DATE="${BUILD_DATE:-$(date +%F)}"
|
||||
DIST_DIR="${PROJECT_DIR}/dist"
|
||||
STAGE_DIR="${DIST_DIR}/${BUNDLE_NAME}"
|
||||
ARCHIVE_PATH="${DIST_DIR}/${BUNDLE_NAME}_${BUILD_DATE}.tar.gz"
|
||||
WHEELHOUSE_SOURCE="${WHEELHOUSE_SOURCE:-${PROJECT_DIR}/wheelhouse}"
|
||||
WEIGHTS_SOURCE="${WEIGHTS_SOURCE:-${PROJECT_DIR}/weights/yolo11n.pt}"
|
||||
|
||||
require_path() {
|
||||
local target="$1"
|
||||
if [[ ! -e "${target}" ]]; then
|
||||
echo "Missing required path: ${target}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_path "${PROJECT_DIR}/app"
|
||||
require_path "${PROJECT_DIR}/config/config.example.yaml"
|
||||
require_path "${PROJECT_DIR}/deploy/store-dwell-alert.service.tpl"
|
||||
require_path "${PROJECT_DIR}/requirements.txt"
|
||||
require_path "${PROJECT_DIR}/requirements.lock.txt"
|
||||
require_path "${PROJECT_DIR}/README.md"
|
||||
require_path "${PROJECT_DIR}/README_zh.md"
|
||||
require_path "${PROJECT_DIR}/scripts/install.sh"
|
||||
require_path "${PROJECT_DIR}/scripts/run.sh"
|
||||
require_path "${PROJECT_DIR}/scripts/install_service.sh"
|
||||
require_path "${WHEELHOUSE_SOURCE}"
|
||||
require_path "${WEIGHTS_SOURCE}"
|
||||
|
||||
rm -rf "${STAGE_DIR}"
|
||||
mkdir -p \
|
||||
"${STAGE_DIR}/config" \
|
||||
"${STAGE_DIR}/data/runtime" \
|
||||
"${STAGE_DIR}/data/staff_gallery" \
|
||||
"${STAGE_DIR}/deploy" \
|
||||
"${STAGE_DIR}/logs" \
|
||||
"${STAGE_DIR}/scripts" \
|
||||
"${STAGE_DIR}/weights"
|
||||
|
||||
cp -R "${PROJECT_DIR}/app" "${STAGE_DIR}/app"
|
||||
cp "${PROJECT_DIR}/README.md" "${STAGE_DIR}/README.md"
|
||||
cp "${PROJECT_DIR}/README_zh.md" "${STAGE_DIR}/README_zh.md"
|
||||
cp "${PROJECT_DIR}/requirements.txt" "${STAGE_DIR}/requirements.txt"
|
||||
cp "${PROJECT_DIR}/requirements.lock.txt" "${STAGE_DIR}/requirements.lock.txt"
|
||||
cp "${PROJECT_DIR}/config/config.example.yaml" "${STAGE_DIR}/config/config.example.yaml"
|
||||
cp "${PROJECT_DIR}/deploy/store-dwell-alert.service.tpl" "${STAGE_DIR}/deploy/store-dwell-alert.service.tpl"
|
||||
cp "${PROJECT_DIR}/scripts/install.sh" "${STAGE_DIR}/scripts/install.sh"
|
||||
cp "${PROJECT_DIR}/scripts/run.sh" "${STAGE_DIR}/scripts/run.sh"
|
||||
cp "${PROJECT_DIR}/scripts/install_service.sh" "${STAGE_DIR}/scripts/install_service.sh"
|
||||
cp "${WEIGHTS_SOURCE}" "${STAGE_DIR}/weights/yolo11n.pt"
|
||||
cp -R "${WHEELHOUSE_SOURCE}" "${STAGE_DIR}/wheelhouse"
|
||||
|
||||
chmod +x \
|
||||
"${STAGE_DIR}/scripts/install.sh" \
|
||||
"${STAGE_DIR}/scripts/run.sh" \
|
||||
"${STAGE_DIR}/scripts/install_service.sh"
|
||||
|
||||
rm -f "${ARCHIVE_PATH}"
|
||||
tar -czf "${ARCHIVE_PATH}" -C "${DIST_DIR}" "${BUNDLE_NAME}"
|
||||
echo "Bundle created: ${ARCHIVE_PATH}"
|
||||
44
managed/store_dwell_alert/scripts/run.sh
Executable file
44
managed/store_dwell_alert/scripts/run.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VENV_PYTHON="${PROJECT_DIR}/.venv/bin/python"
|
||||
CONFIG_TEMPLATE="${PROJECT_DIR}/config/config.example.yaml"
|
||||
CONFIG_PATH="${PROJECT_DIR}/config/local.yaml"
|
||||
LOG_DIR="${PROJECT_DIR}/logs"
|
||||
CAMERA_ID="${CAMERA_ID:-store_cam_01}"
|
||||
RTSP_URL="${RTSP_URL:-rtsp://user:password@camera-ip:554/h264/ch1/main/av_stream}"
|
||||
EVENT_SINK_PATH="${EVENT_SINK_PATH:-logs/events.jsonl}"
|
||||
PREPARE_ONLY=0
|
||||
|
||||
if [[ "${1:-}" == "--prepare-only" ]]; then
|
||||
PREPARE_ONLY=1
|
||||
shift
|
||||
fi
|
||||
|
||||
if [[ ! -x "${VENV_PYTHON}" ]]; then
|
||||
echo "Virtual environment is missing. Run scripts/install.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${RTSP_URL}" == "rtsp://user:password@camera-ip:554/h264/ch1/main/av_stream" ]]; then
|
||||
echo "Please edit scripts/run.sh and set RTSP_URL before starting." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${LOG_DIR}"
|
||||
|
||||
cp "${CONFIG_TEMPLATE}" "${CONFIG_PATH}"
|
||||
sed -i.bak \
|
||||
-e "s|^camera_id: .*|camera_id: ${CAMERA_ID}|" \
|
||||
-e "s|^ rtsp_url: .*| rtsp_url: ${RTSP_URL}|" \
|
||||
-e "s|^ path: .*| path: ${EVENT_SINK_PATH}|" \
|
||||
"${CONFIG_PATH}"
|
||||
rm -f "${CONFIG_PATH}.bak"
|
||||
|
||||
if [[ "${PREPARE_ONLY}" -eq 1 ]]; then
|
||||
echo "Prepared config at ${CONFIG_PATH}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec "${VENV_PYTHON}" -m app.main --config "${CONFIG_PATH}" "$@"
|
||||
6
managed/store_dwell_alert/scripts/run_local.sh
Normal file
6
managed/store_dwell_alert/scripts/run_local.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
"$PROJECT_DIR/.venv/bin/python" -m app.main "$@"
|
||||
Reference in New Issue
Block a user