初始化

This commit is contained in:
gozeros committed 2026-09-07 18:24:55 +08:00
1 parent 34474bd5fc
commit b638b541d0
3008 files changed
+837699

No files matched your search

+55
View File
@@ -0,0 +1,55 @@
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RootDir = Resolve-Path (Join-Path $ScriptDir "..")
$BackendDir = Join-Path $RootDir "offline-backend-20260512\backend"
$FrontendDir = Join-Path $RootDir "frontend-web"
$RuntimeDir = Join-Path $RootDir ".runtime"
$LogDir = Join-Path $RuntimeDir "logs"
$PidDir = Join-Path $RuntimeDir "pids"
$BackendHost = if ($env:BACKEND_HOST) { $env:BACKEND_HOST } else { "0.0.0.0" }
$BackendPort = if ($env:BACKEND_PORT) { $env:BACKEND_PORT } else { "8001" }
$FrontendHost = if ($env:FRONTEND_HOST) { $env:FRONTEND_HOST } else { "0.0.0.0" }
$FrontendPort = if ($env:FRONTEND_PORT) { $env:FRONTEND_PORT } else { "5174" }
$HostIp = if ($env:HOST_IP) { $env:HOST_IP } else { "127.0.0.1" }
$BackendPublicBaseUrl = if ($env:BACKEND_PUBLIC_BASE_URL) { $env:BACKEND_PUBLIC_BASE_URL } else { "http://${HostIp}:${BackendPort}" }
$FrontendPublicBaseUrl = if ($env:FRONTEND_PUBLIC_BASE_URL) { $env:FRONTEND_PUBLIC_BASE_URL } else { "http://${HostIp}:${FrontendPort}" }
$GatewayCorsOrigins = if ($env:GATEWAY_CORS_ORIGINS) {
$env:GATEWAY_CORS_ORIGINS
} else {
"http://localhost:${FrontendPort},http://127.0.0.1:${FrontendPort},http://${HostIp}:${FrontendPort},http://localhost:8080,http://127.0.0.1:8080"
}
$BackendPython = Join-Path $BackendDir ".venv\Scripts\python.exe"
$BackendVendorDir = if ($env:BACKEND_VENDOR_DIR) { $env:BACKEND_VENDOR_DIR } else { Join-Path $BackendDir ".deer-flow\python-vendor" }
New-Item -ItemType Directory -Force -Path $LogDir, $PidDir | Out-Null
function Test-PidRunning {
param([string]$PidFile)
if (-not (Test-Path -LiteralPath $PidFile)) { return $false }
$rawPid = (Get-Content -LiteralPath $PidFile -ErrorAction SilentlyContinue | Select-Object -First 1)
$processId = 0
if (-not [int]::TryParse([string]$rawPid, [ref]$processId)) { return $false }
return [bool](Get-Process -Id $processId -ErrorAction SilentlyContinue)
}
function Stop-ProcessTree {
param([int]$ProcessId)
$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=$ProcessId" -ErrorAction SilentlyContinue
foreach ($child in $children) {
Stop-ProcessTree -ProcessId ([int]$child.ProcessId)
}
$proc = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
if ($proc) {
Stop-Process -Id $ProcessId -Force -ErrorAction SilentlyContinue
}
}
function ConvertTo-PSLiteral {
param([string]$Value)
return "'" + ($Value -replace "'", "''") + "'"
}
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKEND_DIR="${ROOT_DIR}/offline-backend-20260512/backend"
FRONTEND_DIR="${ROOT_DIR}/frontend-web"
RUNTIME_DIR="${ROOT_DIR}/.runtime"
LOG_DIR="${RUNTIME_DIR}/logs"
PID_DIR="${RUNTIME_DIR}/pids"
BACKEND_HOST="${BACKEND_HOST:-0.0.0.0}"
BACKEND_PORT="${BACKEND_PORT:-8001}"
FRONTEND_HOST="${FRONTEND_HOST:-0.0.0.0}"
FRONTEND_PORT="${FRONTEND_PORT:-5174}"
detect_host_ip() {
if command -v hostname >/dev/null 2>&1; then
# `hostname -I` is unsupported on Windows Git Bash; `|| true` keeps the
# pipefail-safe fallback (HOST_IP then defaults to 127.0.0.1 below).
hostname -I 2>/dev/null | awk '{print $1}' || true
fi
}
HOST_IP="${HOST_IP:-$(detect_host_ip)}"
HOST_IP="${HOST_IP:-127.0.0.1}"
BACKEND_PUBLIC_BASE_URL="${BACKEND_PUBLIC_BASE_URL:-http://${HOST_IP}:${BACKEND_PORT}}"
FRONTEND_PUBLIC_BASE_URL="${FRONTEND_PUBLIC_BASE_URL:-http://${HOST_IP}:${FRONTEND_PORT}}"
# The standalone legacy TaskCOP UI runs on Vite :8080 and calls the Gateway
# directly through its configured b1ConsumerUrl. Keep its local origins in the
# backend CORS allow-list instead of relying on a frontend reverse proxy.
GATEWAY_CORS_ORIGINS="${GATEWAY_CORS_ORIGINS:-http://localhost:${FRONTEND_PORT},http://127.0.0.1:${FRONTEND_PORT},http://${HOST_IP}:${FRONTEND_PORT},http://localhost:8080,http://127.0.0.1:8080}"
mkdir -p "${LOG_DIR}" "${PID_DIR}"
is_running() {
local pid_file="$1"
[[ -f "${pid_file}" ]] || return 1
local pid
pid="$(cat "${pid_file}" 2>/dev/null || true)"
[[ -n "${pid}" ]] || return 1
kill -0 "${pid}" >/dev/null 2>&1
}
stop_by_pid_file() {
local name="$1"
local pid_file="$2"
if ! [[ -f "${pid_file}" ]]; then
echo "${name} is not running (no pid file)."
return 0
fi
local pid
pid="$(cat "${pid_file}" 2>/dev/null || true)"
if [[ -z "${pid}" ]] || ! kill -0 "${pid}" >/dev/null 2>&1; then
echo "${name} is not running (stale pid file removed)."
rm -f "${pid_file}"
return 0
fi
echo "Stopping ${name} (pid ${pid})..."
# Services are started with setsid, so the pid is also the process group id.
# Stop the whole group to avoid leaving Vite/esbuild/uvicorn child processes.
kill "-${pid}" >/dev/null 2>&1 || true
kill "${pid}" >/dev/null 2>&1 || true
for _ in {1..30}; do
if ! kill -0 "${pid}" >/dev/null 2>&1; then
rm -f "${pid_file}"
echo "${name} stopped."
return 0
fi
sleep 0.2
done
echo "${name} did not stop gracefully; killing..."
kill -9 "-${pid}" >/dev/null 2>&1 || true
kill -9 "${pid}" >/dev/null 2>&1 || true
rm -f "${pid_file}"
echo "${name} stopped."
}
stop_by_pattern() {
local name="$1"
local pattern="$2"
if ! command -v pgrep >/dev/null 2>&1; then
return 0
fi
local pids
pids="$(pgrep -f "${pattern}" || true)"
if [[ -z "${pids}" ]]; then
return 0
fi
echo "Stopping ${name} processes matching: ${pattern}"
while IFS= read -r pid; do
[[ -n "${pid}" ]] || continue
[[ "${pid}" != "$$" ]] || continue
kill "${pid}" >/dev/null 2>&1 || true
done <<<"${pids}"
}
+78
View File
@@ -0,0 +1,78 @@
param(
[ValidateSet("status", "start", "open")]
[string]$Action = "status"
)
$InstallRoot = "C:\Program Files (x86)\Intax\mbwsmdmclient"
$RelatedServices = @(
"mbwsmdmclient",
"itxcltserver",
"zfmgclient",
"zfnetchkclient",
"ZFICONTROL",
"ZFFTSSERVER"
)
function Show-MbwsStatus {
Write-Host "MBWS related directory:" -ForegroundColor Cyan
Write-Host " $InstallRoot"
Write-Host ""
Write-Host "MBWS related services:" -ForegroundColor Cyan
foreach ($name in $RelatedServices) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if ($null -eq $svc) {
Write-Host (" {0,-16} not found" -f $name) -ForegroundColor DarkGray
continue
}
Write-Host (" {0,-16} {1}" -f $svc.Name, $svc.Status)
}
Write-Host ""
Write-Host "MBWS related processes:" -ForegroundColor Cyan
Get-Process -ErrorAction SilentlyContinue |
Where-Object {
$_.Path -like "C:\Program Files (x86)\Intax\*" -or
$_.ProcessName -match "itx|mbws|zf"
} |
Select-Object ProcessName, Id, Path |
Format-Table -AutoSize
}
function Start-MbwsServices {
foreach ($name in $RelatedServices) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if ($null -eq $svc) {
Write-Host "Service not found: $name" -ForegroundColor DarkGray
continue
}
if ($svc.Status -eq "Running") {
Write-Host "Already running: $name" -ForegroundColor Green
continue
}
Write-Host "Starting: $name" -ForegroundColor Yellow
Start-Service -Name $name -ErrorAction Continue
}
Write-Host ""
Show-MbwsStatus
}
switch ($Action) {
"status" {
Show-MbwsStatus
}
"start" {
Start-MbwsServices
}
"open" {
if (Test-Path -LiteralPath $InstallRoot) {
explorer $InstallRoot
} else {
Write-Error "Directory does not exist: $InstallRoot"
}
}
}
+32
View File
@@ -0,0 +1,32 @@
# Package frontend node_modules for offline development.
# Uses tar -h to dereference pnpm junctions so the archive is self-contained.
param(
[string]$OutputDir = (Join-Path (Split-Path $PSScriptRoot -Parent) "dist")
)
$ErrorActionPreference = "Stop"
$Root = Split-Path $PSScriptRoot -Parent
$Frontend = Join-Path $Root "frontend-web"
$Archive = Join-Path $OutputDir "frontend-offline-deps.tar.gz"
if (-not (Test-Path (Join-Path $Frontend "node_modules\.pnpm"))) {
throw "frontend-web/node_modules is missing or incomplete. Run 'pnpm install' in frontend-web first."
}
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
if (Test-Path $Archive) { Remove-Item $Archive -Force }
Write-Host "Packaging frontend dependencies..."
Write-Host " Source: $Frontend"
Write-Host " Output: $Archive"
$start = Get-Date
tar -czhf $Archive -C $Frontend node_modules package.json pnpm-lock.yaml
$elapsed = ((Get-Date) - $start).TotalSeconds
$sizeMb = [math]::Round((Get-Item $Archive).Length / 1MB, 2)
Write-Host ""
Write-Host "Done in ${elapsed}s — ${sizeMb} MB"
Write-Host ""
Write-Host "Copy to offline machine, then run:"
Write-Host " scripts\restore-frontend-offline-deps.ps1 -Archive <path-to-archive>"
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FRONTEND="${ROOT}/frontend-web"
OUTPUT_DIR="${1:-${ROOT}/dist}"
ARCHIVE="${OUTPUT_DIR}/frontend-offline-deps.tar.gz"
if [[ ! -d "${FRONTEND}/node_modules/.pnpm" ]]; then
echo "frontend-web/node_modules is missing or incomplete. Run 'pnpm install' in frontend-web first." >&2
exit 1
fi
mkdir -p "${OUTPUT_DIR}"
rm -f "${ARCHIVE}"
echo "Packaging frontend dependencies..."
echo " Source: ${FRONTEND}"
echo " Output: ${ARCHIVE}"
tar -czhf "${ARCHIVE}" -C "${FRONTEND}" node_modules package.json pnpm-lock.yaml
SIZE_MB=$(du -m "${ARCHIVE}" | awk '{print $1}')
echo ""
echo "Done — ${SIZE_MB} MB"
echo ""
echo "Copy to offline machine, then run:"
echo " scripts/restore-frontend-offline-deps.sh <path-to-archive>"
+4
View File
@@ -0,0 +1,4 @@
. "$PSScriptRoot\env.ps1"
& "$PSScriptRoot\stop-all.ps1"
& "$PSScriptRoot\start-all.ps1"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"${SCRIPT_DIR}/stop-all.sh"
"${SCRIPT_DIR}/start-all.sh"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"${SCRIPT_DIR}/stop-backend.sh"
"${SCRIPT_DIR}/start-backend.sh"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"${SCRIPT_DIR}/stop-frontend.sh"
"${SCRIPT_DIR}/start-frontend.sh"
+42
View File
@@ -0,0 +1,42 @@
# Restore frontend node_modules from an offline dependency archive.
param(
[Parameter(Mandatory = $true)]
[string]$Archive
)
$ErrorActionPreference = "Stop"
$Root = Split-Path $PSScriptRoot -Parent
$Frontend = Join-Path $Root "frontend-web"
if (-not (Test-Path $Archive)) {
throw "Archive not found: $Archive"
}
Write-Host "Restoring frontend dependencies to $Frontend ..."
# Back up existing node_modules if present
$backup = Join-Path $Frontend "node_modules.bak"
if (Test-Path (Join-Path $Frontend "node_modules")) {
if (Test-Path $backup) { Remove-Item $backup -Recurse -Force }
Rename-Item (Join-Path $Frontend "node_modules") "node_modules.bak"
Write-Host " Existing node_modules backed up to node_modules.bak"
}
tar -xzf $Archive -C $Frontend
# Quick sanity check
$required = @(
"node_modules\.pnpm",
"node_modules\react\package.json",
"node_modules\vite\package.json"
)
foreach ($rel in $required) {
if (-not (Test-Path (Join-Path $Frontend $rel))) {
throw "Restore incomplete — missing $rel"
}
}
Write-Host ""
Write-Host "Restore complete. You can now run:"
Write-Host " cd frontend-web"
Write-Host " pnpm dev"
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <path-to-frontend-offline-deps.tar.gz>" >&2
exit 1
fi
ARCHIVE="$1"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FRONTEND="${ROOT}/frontend-web"
if [[ ! -f "${ARCHIVE}" ]]; then
echo "Archive not found: ${ARCHIVE}" >&2
exit 1
fi
echo "Restoring frontend dependencies to ${FRONTEND} ..."
if [[ -d "${FRONTEND}/node_modules" ]]; then
rm -rf "${FRONTEND}/node_modules.bak"
mv "${FRONTEND}/node_modules" "${FRONTEND}/node_modules.bak"
echo " Existing node_modules backed up to node_modules.bak"
fi
tar -xzf "${ARCHIVE}" -C "${FRONTEND}"
for rel in node_modules/.pnpm node_modules/react/package.json node_modules/vite/package.json; do
if [[ ! -e "${FRONTEND}/${rel}" ]]; then
echo "Restore incomplete — missing ${rel}" >&2
exit 1
fi
done
echo ""
echo "Restore complete. You can now run:"
echo " cd frontend-web"
echo " pnpm dev"
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Installs Docker Engine in an Ubuntu WSL distribution. Run as root.
set -euo pipefail
if [[ "${EUID}" -ne 0 ]]; then
echo "Run this script as root." >&2
exit 1
fi
. /etc/os-release
if [[ "${ID}" != "ubuntu" ]]; then
echo "This installer supports Ubuntu only (detected: ${ID})." >&2
exit 1
fi
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
cat > /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: ${VERSION_CODENAME}
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
systemctl enable --now docker
docker version --format 'Docker Engine {{.Server.Version}} is ready.'
docker compose version
+21
View File
@@ -0,0 +1,21 @@
. "$PSScriptRoot\env.ps1"
& "$PSScriptRoot\start-all.ps1"
$deadline = (Get-Date).AddSeconds(60)
$url = "$FrontendPublicBaseUrl/"
Write-Host "Waiting for frontend: $url"
while ((Get-Date) -lt $deadline) {
try {
$response = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 2
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
Write-Host "Frontend is ready: $url"
exit 0
}
} catch {
Start-Sleep -Milliseconds 500
}
}
throw "Timed out waiting for frontend: $url"
+4
View File
@@ -0,0 +1,4 @@
. "$PSScriptRoot\env.ps1"
& "$PSScriptRoot\start-backend.ps1"
& "$PSScriptRoot\start-frontend.ps1"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"${SCRIPT_DIR}/start-backend.sh"
"${SCRIPT_DIR}/start-frontend.sh"
+42
View File
@@ -0,0 +1,42 @@
. "$PSScriptRoot\env.ps1"
$PidFile = Join-Path $PidDir "backend.pid"
$LogFile = Join-Path $LogDir "backend.log"
if (Test-PidRunning $PidFile) {
$existingPid = Get-Content -LiteralPath $PidFile | Select-Object -First 1
Write-Host "Backend is already running (pid $existingPid)."
Write-Host "URL: $BackendPublicBaseUrl"
exit 0
}
if (-not (Test-Path -LiteralPath $BackendPython)) {
throw "Backend Python not found: $BackendPython. Run uv sync in offline-backend-20260512/backend first."
}
Remove-Item -LiteralPath $PidFile -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $BackendVendorDir | Out-Null
Write-Host "Starting backend..."
Write-Host "Log: $LogFile"
$RunnerFile = Join-Path $RuntimeDir "run-backend.ps1"
$runner = @"
`$ErrorActionPreference = "Continue"
Set-Location -LiteralPath $(ConvertTo-PSLiteral $BackendDir)
`$env:GATEWAY_CORS_ORIGINS = $(ConvertTo-PSLiteral $GatewayCorsOrigins)
`$env:PYTHONPATH = $(ConvertTo-PSLiteral ".;$BackendVendorDir")
if (-not `$env:WEKNORA_ADMIN_PASSWORD) { `$env:WEKNORA_ADMIN_PASSWORD = "1qaz@WSX" }
& $(ConvertTo-PSLiteral $BackendPython) -m uvicorn app.gateway.app:app --host $(ConvertTo-PSLiteral $BackendHost) --port $(ConvertTo-PSLiteral $BackendPort) --reload --reload-dir 'app' --reload-dir 'packages' --reload-exclude $(ConvertTo-PSLiteral (Join-Path $BackendDir '.deer-flow')) --reload-exclude $(ConvertTo-PSLiteral $RuntimeDir) *> $(ConvertTo-PSLiteral $LogFile)
"@
Set-Content -LiteralPath $RunnerFile -Value $runner -Encoding UTF8
$proc = Start-Process -FilePath "powershell.exe" -ArgumentList @(
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", $RunnerFile
) -WindowStyle Hidden -PassThru
Set-Content -LiteralPath $PidFile -Value $proc.Id
Write-Host "Backend started (pid $($proc.Id))."
Write-Host "URL: $BackendPublicBaseUrl"
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/env.sh"
PID_FILE="${PID_DIR}/backend.pid"
LOG_FILE="${LOG_DIR}/backend.log"
BACKEND_VENDOR_DIR="${BACKEND_VENDOR_DIR:-${BACKEND_DIR}/.deer-flow/python-vendor}"
BACKEND_WHEELHOUSE="${BACKEND_WHEELHOUSE:-${BACKEND_DIR}/wheelhouse}"
BACKEND_INSTALL_DEPS="${BACKEND_INSTALL_DEPS:-1}"
BACKEND_PIP_PACKAGES="${BACKEND_PIP_PACKAGES:-croniter==6.2.2 langgraph-checkpoint-postgres==3.0.5 psycopg==3.3.3 psycopg-binary==3.3.3 psycopg-pool==3.3.0 orjson==3.11.9 tzdata==2026.2}"
if is_running "${PID_FILE}"; then
echo "Backend is already running (pid $(cat "${PID_FILE}"))."
echo "URL: http://${HOST_IP}:${BACKEND_PORT}"
exit 0
fi
rm -f "${PID_FILE}"
echo "Starting backend..."
echo "Log: ${LOG_FILE}"
mkdir -p "${BACKEND_VENDOR_DIR}"
if [[ "${BACKEND_INSTALL_DEPS}" != "0" ]]; then
if ! (
cd "${BACKEND_DIR}" && \
PYTHONPATH=".:${BACKEND_VENDOR_DIR}${PYTHONPATH:+:${PYTHONPATH}}" \
uv run --no-sync python -c "import croniter; import psycopg; import psycopg_pool; import langgraph.checkpoint.postgres; import langgraph.store.postgres"
) >/dev/null 2>&1; then
if [[ ! -d "${BACKEND_WHEELHOUSE}" ]]; then
echo "ERROR: wheelhouse not found: ${BACKEND_WHEELHOUSE}" >&2
exit 1
fi
echo "Installing backend runtime deps into ${BACKEND_VENDOR_DIR}"
(
cd "${BACKEND_DIR}" && \
uv pip install \
--target "${BACKEND_VENDOR_DIR}" \
--no-deps \
--no-index \
--find-links "${BACKEND_WHEELHOUSE}" \
${BACKEND_PIP_PACKAGES}
)
fi
fi
# nohup instead of setsid: setsid does not exist on Windows Git Bash.
nohup bash -c '
cd "$0" || exit 1
exec env \
GATEWAY_CORS_ORIGINS="$1" \
PYTHONPATH=".:$4${PYTHONPATH:+:${PYTHONPATH}}" \
uv run uvicorn app.gateway.app:app \
--host "$2" \
--port "$3" \
--reload \
--reload-dir "app" \
--reload-dir "packages" \
--reload-exclude "$0/.deer-flow" \
--reload-exclude "$0/.runtime"
' "${BACKEND_DIR}" "${GATEWAY_CORS_ORIGINS}" "${BACKEND_HOST}" "${BACKEND_PORT}" "${BACKEND_VENDOR_DIR}" \
>"${LOG_FILE}" 2>&1 &
echo "$!" >"${PID_FILE}"
echo "Backend started (pid $(cat "${PID_FILE}"))."
echo "URL: http://${HOST_IP}:${BACKEND_PORT}"
+41
View File
@@ -0,0 +1,41 @@
. "$PSScriptRoot\env.ps1"
$PidFile = Join-Path $PidDir "frontend.pid"
$LogFile = Join-Path $LogDir "frontend.log"
if (Test-PidRunning $PidFile) {
$existingPid = Get-Content -LiteralPath $PidFile | Select-Object -First 1
Write-Host "Frontend is already running (pid $existingPid)."
Write-Host "URL: $FrontendPublicBaseUrl"
exit 0
}
$NpmCmd = (Get-Command npm.cmd -ErrorAction SilentlyContinue).Source
if (-not $NpmCmd) {
throw "npm.cmd was not found in PATH. Install Node.js or add npm.cmd to PATH."
}
Remove-Item -LiteralPath $PidFile -Force -ErrorAction SilentlyContinue
Write-Host "Starting frontend..."
Write-Host "Log: $LogFile"
$RunnerFile = Join-Path $RuntimeDir "run-frontend.ps1"
$runner = @"
`$ErrorActionPreference = "Continue"
Set-Location -LiteralPath $(ConvertTo-PSLiteral $FrontendDir)
`$env:VITE_BACKEND_BASE_URL = $(ConvertTo-PSLiteral $BackendPublicBaseUrl)
`$env:VITE_LANGGRAPH_BASE_URL = $(ConvertTo-PSLiteral "$BackendPublicBaseUrl/api")
& $(ConvertTo-PSLiteral $NpmCmd) run dev -- --host $(ConvertTo-PSLiteral $FrontendHost) --port $(ConvertTo-PSLiteral $FrontendPort) *> $(ConvertTo-PSLiteral $LogFile)
"@
Set-Content -LiteralPath $RunnerFile -Value $runner -Encoding UTF8
$proc = Start-Process -FilePath "powershell.exe" -ArgumentList @(
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", $RunnerFile
) -WindowStyle Hidden -PassThru
Set-Content -LiteralPath $PidFile -Value $proc.Id
Write-Host "Frontend started (pid $($proc.Id))."
Write-Host "URL: $FrontendPublicBaseUrl"
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/env.sh"
PID_FILE="${PID_DIR}/frontend.pid"
LOG_FILE="${LOG_DIR}/frontend.log"
if is_running "${PID_FILE}"; then
echo "Frontend is already running (pid $(cat "${PID_FILE}"))."
echo "URL: ${FRONTEND_PUBLIC_BASE_URL}"
exit 0
fi
rm -f "${PID_FILE}"
echo "Starting frontend..."
echo "Log: ${LOG_FILE}"
# nohup instead of setsid: setsid does not exist on Windows Git Bash.
nohup bash -c '
cd "$0" || exit 1
exec env \
VITE_BACKEND_BASE_URL="$1" \
VITE_LANGGRAPH_BASE_URL="$1/api" \
pnpm exec vite \
--host "$2" \
--port "$3"
' "${FRONTEND_DIR}" "${BACKEND_PUBLIC_BASE_URL}" "${FRONTEND_HOST}" "${FRONTEND_PORT}" \
>"${LOG_FILE}" 2>&1 &
echo "$!" >"${PID_FILE}"
echo "Frontend started (pid $(cat "${PID_FILE}"))."
echo "URL: ${FRONTEND_PUBLIC_BASE_URL}"
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
IMAGE_TAG="${IMAGE_TAG:-deerflow-backend-offline:20260505}"
CONTAINER_NAME="${CONTAINER_NAME:-deerflow-backend}"
HOST_PORT="${HOST_PORT:-2026}"
IMAGE_TAR="${IMAGE_TAR:-${SCRIPT_DIR}/deerflow-backend-offline-image-20260505.tar}"
# Default timezone for the container. The offline image is built from a base
# layer that ships with UTC, which made scheduled tasks fire at the wrong wall
# clock and made log timestamps confusing for ops. ``Asia/Shanghai`` keeps the
# scheduler aligned with the operator's expectation; override with TZ=... when
# deploying to a different region.
CONTAINER_TZ="${CONTAINER_TZ:-${TZ:-Asia/Shanghai}}"
die() {
echo "ERROR: $*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "command not found: $1"
}
require_file() {
[ -f "$1" ] || die "required file not found: $1"
}
require_dir() {
[ -d "$1" ] || die "required directory not found: $1"
}
require_env_key() {
local key="$1"
grep -Eq "^[[:space:]]*${key}=" "${SCRIPT_DIR}/.env" || die "required .env key not found: ${key}"
}
require_cmd docker
if [ ! -f "${SCRIPT_DIR}/.env" ]; then
if [ -f "${SCRIPT_DIR}/.env.example" ]; then
cp "${SCRIPT_DIR}/.env.example" "${SCRIPT_DIR}/.env"
echo "Generated .env from .env.example. Please edit .env and run this script again."
exit 1
fi
die "missing .env"
fi
if [ ! -f "${SCRIPT_DIR}/config.yaml" ]; then
if [ -f "${SCRIPT_DIR}/config.yaml.example" ]; then
cp "${SCRIPT_DIR}/config.yaml.example" "${SCRIPT_DIR}/config.yaml"
echo "Generated config.yaml from config.yaml.example. Please edit model/API settings and run this script again."
exit 1
fi
die "missing config.yaml"
fi
require_env_key "AUTH_JWT_SECRET"
require_env_key "SCHEDULED_TASKS_ENABLED"
require_env_key "ELEMENT_ENABLED"
require_file "${SCRIPT_DIR}/config.yaml"
require_file "${SCRIPT_DIR}/extensions_config.json"
require_file "${SCRIPT_DIR}/backend/pyproject.toml"
require_file "${SCRIPT_DIR}/backend/app/gateway/app.py"
require_file "${SCRIPT_DIR}/backend/packages/harness/deerflow/runtime/scheduler/element.py"
require_dir "${SCRIPT_DIR}/skills"
require_dir "${SCRIPT_DIR}/wheelhouse"
# Knowledge base directory, mounted into the container at /mnt/knowledge_base.
require_dir "${SCRIPT_DIR}/backend/knowledge/knowledge_base"
mkdir -p "${SCRIPT_DIR}/data"
if ! docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then
require_file "${IMAGE_TAR}"
echo "Loading Docker image from ${IMAGE_TAR}"
docker load -i "${IMAGE_TAR}"
fi
if docker ps -a --format '{{.Names}}' | awk -v name="${CONTAINER_NAME}" '$0 == name { found=1 } END { exit(found ? 0 : 1) }'; then
echo "Removing existing container ${CONTAINER_NAME}"
docker rm -f "${CONTAINER_NAME}" >/dev/null
fi
echo "Starting backend container ${CONTAINER_NAME}"
docker run -d \
--name "${CONTAINER_NAME}" \
--restart unless-stopped \
-p "${HOST_PORT}:8001" \
--env-file "${SCRIPT_DIR}/.env" \
-e TZ="${CONTAINER_TZ}" \
-e DEER_FLOW_PROJECT_ROOT=/app \
-e DEER_FLOW_HOME=/app/backend/.deer-flow \
-e DEER_FLOW_CONFIG_PATH=/app/config.yaml \
-e DEER_FLOW_EXTENSIONS_CONFIG_PATH=/app/extensions_config.json \
-e DEER_FLOW_SKILLS_PATH=/app/skills \
-e UV_LINK_MODE=copy \
-v "${SCRIPT_DIR}/backend/app:/app/backend/app" \
-v "${SCRIPT_DIR}/backend/packages:/app/backend/packages" \
-v "${SCRIPT_DIR}/backend/scripts:/app/backend/scripts" \
-v "${SCRIPT_DIR}/backend/pyproject.toml:/app/backend/pyproject.toml:ro" \
-v "${SCRIPT_DIR}/backend/uv.lock:/app/backend/uv.lock:ro" \
-v "${SCRIPT_DIR}/backend/langgraph.json:/app/backend/langgraph.json:ro" \
-v "${SCRIPT_DIR}/config.yaml:/app/config.yaml:ro" \
-v "${SCRIPT_DIR}/skill_evolution.json:/app/skill_evolution.json:rw" \
-v "${SCRIPT_DIR}/extensions_config.json:/app/extensions_config.json:rw" \
-v "${SCRIPT_DIR}/skills:/app/skills:rw" \
-v "${SCRIPT_DIR}/wheelhouse:/app/wheelhouse:ro" \
-v "${SCRIPT_DIR}/backend/knowledge/knowledge_base:/mnt/knowledge_base:ro" \
-v "${SCRIPT_DIR}/data:/app/backend/.deer-flow" \
"${IMAGE_TAG}" \
sh -c 'cd backend
# Apply timezone inside the container. ``TZ`` alone is enough for Python /
# uvicorn log timestamps, but linking /etc/localtime keeps C-level tools
# (e.g. anything that reads /etc/localtime directly) on the same clock.
if [ -n "${TZ:-}" ] && [ -f "/usr/share/zoneinfo/${TZ}" ]; then
ln -sf "/usr/share/zoneinfo/${TZ}" /etc/localtime 2>/dev/null || true
echo "${TZ}" > /etc/timezone 2>/dev/null || true
fi
if [ "${SCHEDULER_INSTALL_DEPS:-1}" != "0" ]; then
uv run --no-sync python -c "import croniter" >/dev/null 2>&1 || {
PYTHON_BIN="$(uv run --no-sync python -c "import sys; print(sys.executable)")"
uv pip install --python "${PYTHON_BIN}" --no-index --find-links /app/wheelhouse ${SCHEDULER_PIP_PACKAGES:-croniter==6.2.2}
}
fi
if [ "${HINDSIGHT_INSTALL_DEPS:-1}" != "0" ]; then
uv run --no-sync python -c "import hindsight_client" >/dev/null 2>&1 || {
PYTHON_BIN="$(uv run --no-sync python -c "import sys; print(sys.executable)")"
uv pip install --python "${PYTHON_BIN}" --no-index --find-links /app/wheelhouse ${HINDSIGHT_INSTALL_DEPS:-hindsight_client==0.6.2}
}
fi
if [ "${PG_CHECKPOINTER_INSTALL_DEPS:-1}" != "0" ]; then
uv run --no-sync python -c "import psycopg; import psycopg_pool; import langgraph.checkpoint.postgres; import langgraph.store.postgres" >/dev/null 2>&1 || {
PYTHON_BIN="$(uv run --no-sync python -c "import sys; print(sys.executable)")"
uv pip install --python "${PYTHON_BIN}" --no-deps --no-index --find-links /app/wheelhouse ${PG_CHECKPOINTER_PIP_PACKAGES:-langgraph-checkpoint-postgres==3.0.5 psycopg==3.3.3 psycopg-binary==3.3.3 psycopg-pool==3.3.0 orjson==3.11.9 tzdata==2026.2}
}
fi
PYTHONPATH=. uv run --no-sync python -m uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 --workers ${GATEWAY_WORKERS:-8}'
echo
echo "Backend started."
echo " URL: http://<server-ip>:${HOST_PORT}"
echo " Logs: docker logs -f ${CONTAINER_NAME}"
echo " Stop: ./stop_backend.sh"
+18
View File
@@ -0,0 +1,18 @@
. "$PSScriptRoot\env.ps1"
$backendPidFile = Join-Path $PidDir "backend.pid"
$frontendPidFile = Join-Path $PidDir "frontend.pid"
if (Test-PidRunning $backendPidFile) {
$pidValue = Get-Content -LiteralPath $backendPidFile | Select-Object -First 1
Write-Host "Backend: running (pid $pidValue) $BackendPublicBaseUrl"
} else {
Write-Host "Backend: stopped"
}
if (Test-PidRunning $frontendPidFile) {
$pidValue = Get-Content -LiteralPath $frontendPidFile | Select-Object -First 1
Write-Host "Frontend: running (pid $pidValue) $FrontendPublicBaseUrl"
} else {
Write-Host "Frontend: stopped"
}
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/env.sh"
print_status() {
local name="$1"
local pid_file="$2"
local url="$3"
local pattern="$4"
if is_running "${pid_file}"; then
echo "${name}: running (pid $(cat "${pid_file}"))"
echo " ${url}"
elif command -v pgrep >/dev/null 2>&1 && pgrep -f "${pattern}" >/dev/null 2>&1; then
echo "${name}: running (unmanaged by scripts)"
echo " ${url}"
else
echo "${name}: stopped"
fi
}
print_status "Backend" "${PID_DIR}/backend.pid" "http://${HOST_IP}:${BACKEND_PORT}" "${BACKEND_DIR}.*uvicorn app.gateway.app:app.*--port ${BACKEND_PORT}"
print_status "Frontend" "${PID_DIR}/frontend.pid" "${FRONTEND_PUBLIC_BASE_URL}" "${FRONTEND_DIR}.*vite.*--port ${FRONTEND_PORT}"
echo "Logs: ${LOG_DIR}"
+4
View File
@@ -0,0 +1,4 @@
. "$PSScriptRoot\env.ps1"
& "$PSScriptRoot\stop-frontend.ps1"
& "$PSScriptRoot\stop-backend.ps1"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"${SCRIPT_DIR}/stop-frontend.sh"
"${SCRIPT_DIR}/stop-backend.sh"
+20
View File
@@ -0,0 +1,20 @@
. "$PSScriptRoot\env.ps1"
$PidFile = Join-Path $PidDir "backend.pid"
if (-not (Test-Path -LiteralPath $PidFile)) {
Write-Host "Backend is not running (no pid file)."
exit 0
}
$rawPid = Get-Content -LiteralPath $PidFile | Select-Object -First 1
$processId = 0
if (-not [int]::TryParse([string]$rawPid, [ref]$processId) -or -not (Get-Process -Id $processId -ErrorAction SilentlyContinue)) {
Remove-Item -LiteralPath $PidFile -Force -ErrorAction SilentlyContinue
Write-Host "Backend is not running (stale pid file removed)."
exit 0
}
Write-Host "Stopping backend (pid $processId)..."
Stop-ProcessTree -ProcessId $processId
Remove-Item -LiteralPath $PidFile -Force -ErrorAction SilentlyContinue
Write-Host "Backend stopped."
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/env.sh"
stop_by_pid_file "Backend" "${PID_DIR}/backend.pid"
stop_by_pattern "Backend" "${BACKEND_DIR}.*uvicorn app.gateway.app:app.*--port ${BACKEND_PORT}"
+20
View File
@@ -0,0 +1,20 @@
. "$PSScriptRoot\env.ps1"
$PidFile = Join-Path $PidDir "frontend.pid"
if (-not (Test-Path -LiteralPath $PidFile)) {
Write-Host "Frontend is not running (no pid file)."
exit 0
}
$rawPid = Get-Content -LiteralPath $PidFile | Select-Object -First 1
$processId = 0
if (-not [int]::TryParse([string]$rawPid, [ref]$processId) -or -not (Get-Process -Id $processId -ErrorAction SilentlyContinue)) {
Remove-Item -LiteralPath $PidFile -Force -ErrorAction SilentlyContinue
Write-Host "Frontend is not running (stale pid file removed)."
exit 0
}
Write-Host "Stopping frontend (pid $processId)..."
Stop-ProcessTree -ProcessId $processId
Remove-Item -LiteralPath $PidFile -Force -ErrorAction SilentlyContinue
Write-Host "Frontend stopped."
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/env.sh"
stop_by_pid_file "Frontend" "${PID_DIR}/frontend.pid"
stop_by_pattern "Frontend" "${FRONTEND_DIR}.*vite.*--port ${FRONTEND_PORT}"
+54
View File
@@ -0,0 +1,54 @@
param(
[int]$TimeoutSeconds = 90,
[switch]$OpenBrowser
)
. "$PSScriptRoot\env.ps1"
function Wait-HttpReady {
param(
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $true)]
[string]$Url,
[Parameter(Mandatory = $true)]
[datetime]$Deadline
)
Write-Host "Waiting for ${Name}: $Url"
while ((Get-Date) -lt $Deadline) {
try {
$response = Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 2
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
Write-Host "$Name is ready: $Url"
return
}
} catch {
Start-Sleep -Milliseconds 700
}
}
throw "Timed out waiting for ${Name}: $Url"
}
& "$PSScriptRoot\start-all.ps1"
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
Wait-HttpReady -Name "backend" -Url "$BackendPublicBaseUrl/api/health" -Deadline $deadline
Wait-HttpReady -Name "frontend" -Url "$FrontendPublicBaseUrl/" -Deadline $deadline
Write-Host ""
Write-Host "DeerFlow is ready."
Write-Host "Frontend: $FrontendPublicBaseUrl/"
Write-Host "Backend: $BackendPublicBaseUrl"
Write-Host ""
Write-Host "Logs:"
Write-Host " Backend: $(Join-Path $LogDir 'backend.log')"
Write-Host " Frontend: $(Join-Path $LogDir 'frontend.log')"
if ($OpenBrowser) {
Start-Process "$FrontendPublicBaseUrl/"
}
Binary file not shown.
Binary file not shown.