471 lines
15 KiB
Bash
Executable File
471 lines
15 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Ubuntu 一键部署/更新脚本。
|
|
# - 首次执行会交互式生成 .env.deploy,本机保存,不提交 git。
|
|
# - 后续执行会拉取代码、更新依赖、构建前端、安装/重启 systemd 服务。
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
ENV_FILE="${ROOT_DIR}/.env.deploy"
|
|
PYTHON_VERSION="${PYTHON_VERSION:-3.10.14}"
|
|
DEPLOY_INSTANCE="${CLAW_DEPLOY_INSTANCE:-zk-data-agent}"
|
|
BACKEND_SERVICE="${CLAW_BACKEND_SERVICE:-${DEPLOY_INSTANCE}-backend}"
|
|
FRONTEND_SERVICE="${CLAW_FRONTEND_SERVICE:-${DEPLOY_INSTANCE}-frontend}"
|
|
USER_SYSTEMD_DIR="${HOME}/.config/systemd/user"
|
|
SYSTEM_SYSTEMD_DIR="/etc/systemd/system"
|
|
BRANCH=""
|
|
FORCE=0
|
|
SKIP_GIT=0
|
|
BOOTSTRAP_SYSTEM=0
|
|
SERVICE_SCOPE="${CLAW_SERVICE_SCOPE:-}"
|
|
ENABLE_LINUX_ACCOUNTS="${CLAW_ENABLE_LINUX_ACCOUNTS:-}"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: bash scripts/deploy-ubuntu.sh [branch] [--force] [--skip-git] [--bootstrap-system] [--system-service] [--user-service] [--enable-linux-accounts]
|
|
|
|
Options:
|
|
branch 要部署的 git 分支;不传则使用当前分支
|
|
--force 使用 git reset --hard origin/<branch> 覆盖本地改动
|
|
--skip-git 跳过 git 拉取,只部署当前工作区
|
|
--bootstrap-system
|
|
强制使用 sudo 安装 Ubuntu 系统依赖;首次安装会默认执行
|
|
--system-service
|
|
安装 root/system 级 systemd 服务;以 root 执行时默认启用
|
|
--user-service
|
|
安装当前用户级 systemd 服务
|
|
--enable-linux-accounts
|
|
启用平台账号同名 Linux 用户工作区;要求 system 服务和 root 权限
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--force)
|
|
FORCE=1
|
|
shift
|
|
;;
|
|
--skip-git)
|
|
SKIP_GIT=1
|
|
shift
|
|
;;
|
|
--bootstrap-system)
|
|
BOOTSTRAP_SYSTEM=1
|
|
shift
|
|
;;
|
|
--system-service)
|
|
SERVICE_SCOPE="system"
|
|
shift
|
|
;;
|
|
--user-service)
|
|
SERVICE_SCOPE="user"
|
|
shift
|
|
;;
|
|
--enable-linux-accounts)
|
|
ENABLE_LINUX_ACCOUNTS="1"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
if [[ -n "${BRANCH}" ]]; then
|
|
echo "Unexpected argument: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
BRANCH="$1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
REQUESTED_SERVICE_SCOPE="${SERVICE_SCOPE}"
|
|
REQUESTED_ENABLE_LINUX_ACCOUNTS="${ENABLE_LINUX_ACCOUNTS}"
|
|
|
|
log() {
|
|
printf "\n\033[1;34m==>\033[0m %s\n" "$1"
|
|
}
|
|
|
|
warn() {
|
|
printf "\033[1;33mWARN\033[0m %s\n" "$1" >&2
|
|
}
|
|
|
|
fail() {
|
|
printf "\033[1;31mERROR\033[0m %s\n" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
configure_instance_names() {
|
|
if [[ -z "${SERVICE_SCOPE}" && -n "${CLAW_SERVICE_SCOPE:-}" ]]; then
|
|
SERVICE_SCOPE="${CLAW_SERVICE_SCOPE}"
|
|
fi
|
|
DEPLOY_INSTANCE="${CLAW_DEPLOY_INSTANCE:-${DEPLOY_INSTANCE:-zk-data-agent}}"
|
|
BACKEND_SERVICE="${CLAW_BACKEND_SERVICE:-${DEPLOY_INSTANCE}-backend}"
|
|
FRONTEND_SERVICE="${CLAW_FRONTEND_SERVICE:-${DEPLOY_INSTANCE}-frontend}"
|
|
if [[ -z "${SERVICE_SCOPE}" ]]; then
|
|
if [[ "${EUID}" == "0" ]]; then
|
|
SERVICE_SCOPE="system"
|
|
else
|
|
SERVICE_SCOPE="user"
|
|
fi
|
|
fi
|
|
if [[ -z "${ENABLE_LINUX_ACCOUNTS}" ]]; then
|
|
ENABLE_LINUX_ACCOUNTS="${CLAW_ENABLE_LINUX_ACCOUNTS:-0}"
|
|
fi
|
|
}
|
|
|
|
require_command() {
|
|
local name="$1"
|
|
local hint="$2"
|
|
if ! command -v "${name}" >/dev/null 2>&1; then
|
|
fail "缺少命令:${name}。${hint}"
|
|
fi
|
|
}
|
|
|
|
resolve_npm_bin() {
|
|
if [[ -n "${CLAW_NODE_BIN_DIR:-}" ]]; then
|
|
export PATH="${CLAW_NODE_BIN_DIR}:${PATH}"
|
|
fi
|
|
local npm_bin="${CLAW_NPM_BIN:-$(command -v npm || true)}"
|
|
if [[ -z "${npm_bin}" || ! -x "${npm_bin}" ]]; then
|
|
fail "缺少命令:npm。请先安装 Node.js 和 npm,建议 Node.js 20 或 22。"
|
|
fi
|
|
local node_bin_dir
|
|
node_bin_dir="$(dirname "${npm_bin}")"
|
|
export PATH="${node_bin_dir}:${PATH}"
|
|
if ! "${npm_bin}" --version >/dev/null 2>&1; then
|
|
fail "npm 不可用:${npm_bin}。请确认同目录下存在 node,或重新安装 Node.js/npm。"
|
|
fi
|
|
printf "%s\n" "${npm_bin}"
|
|
}
|
|
|
|
bootstrap_system_packages() {
|
|
if [[ "${BOOTSTRAP_SYSTEM}" != "1" && -f "${ENV_FILE}" ]]; then
|
|
return
|
|
fi
|
|
require_command sudo "首次安装系统依赖需要 sudo。"
|
|
if ! command -v apt-get >/dev/null 2>&1; then
|
|
warn "未发现 apt-get,跳过系统依赖安装。请手工安装 git、curl、pyenv、Node.js、npm 和 Python 编译依赖。"
|
|
return
|
|
fi
|
|
log "首次初始化 Ubuntu 系统依赖"
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
git curl ca-certificates build-essential \
|
|
libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
|
|
libffi-dev liblzma-dev libncursesw5-dev xz-utils tk-dev \
|
|
systemd
|
|
if [[ "${ENABLE_LINUX_ACCOUNTS}" == "1" ]]; then
|
|
sudo apt-get install -y passwd python3 python3-venv
|
|
fi
|
|
}
|
|
|
|
configure_nginx_upload_limit() {
|
|
if [[ "${BOOTSTRAP_SYSTEM}" != "1" && -f "${ENV_FILE}" ]]; then
|
|
return
|
|
fi
|
|
local script="${ROOT_DIR}/scripts/configure-nginx-upload-limit.sh"
|
|
if [[ -x "${script}" ]]; then
|
|
log "配置 nginx 上传体积限制"
|
|
"${script}" || warn "nginx 上传体积限制配置失败,可稍后手动执行:bash ${script}"
|
|
fi
|
|
}
|
|
|
|
prompt_value() {
|
|
local var_name="$1"
|
|
local prompt="$2"
|
|
local default_value="${3:-}"
|
|
local secret="${4:-0}"
|
|
local value
|
|
|
|
if [[ "${secret}" == "1" ]]; then
|
|
read -r -s -p "${prompt}" value
|
|
printf "\n"
|
|
else
|
|
if [[ -n "${default_value}" ]]; then
|
|
read -r -p "${prompt} [${default_value}]: " value
|
|
else
|
|
read -r -p "${prompt}: " value
|
|
fi
|
|
fi
|
|
value="${value:-${default_value}}"
|
|
printf -v "${var_name}" "%s" "${value}"
|
|
}
|
|
|
|
write_env_file() {
|
|
local api_key="$1"
|
|
local base_url="$2"
|
|
local model="$3"
|
|
local backend_host="$4"
|
|
local backend_port="$5"
|
|
local frontend_host="$6"
|
|
local frontend_port="$7"
|
|
local claw_api_url="http://${backend_host}:${backend_port}"
|
|
|
|
umask 077
|
|
cat >"${ENV_FILE}" <<EOF
|
|
# ZK Data Agent 本机部署配置。
|
|
# 该文件包含敏感信息,只保存在部署机器本地,不要提交到 git。
|
|
|
|
export OPENAI_API_KEY=$(printf "%q" "${api_key}")
|
|
export OPENAI_BASE_URL=$(printf "%q" "${base_url}")
|
|
export OPENAI_MODEL=$(printf "%q" "${model}")
|
|
export OPENAI_TIMEOUT_SECONDS="${OPENAI_TIMEOUT_SECONDS:-3600}"
|
|
export CLAW_MODEL_IDLE_TIMEOUT_SECONDS="${CLAW_MODEL_IDLE_TIMEOUT_SECONDS:-60}"
|
|
|
|
export CLAW_DEPLOY_INSTANCE=$(printf "%q" "${DEPLOY_INSTANCE}")
|
|
export CLAW_BACKEND_HOST=$(printf "%q" "${backend_host}")
|
|
export CLAW_BACKEND_PORT=$(printf "%q" "${backend_port}")
|
|
export CLAW_FRONTEND_HOST=$(printf "%q" "${frontend_host}")
|
|
export CLAW_FRONTEND_PORT=$(printf "%q" "${frontend_port}")
|
|
export CLAW_API_URL=$(printf "%q" "${claw_api_url}")
|
|
export CLAW_SERVICE_SCOPE=$(printf "%q" "${SERVICE_SCOPE}")
|
|
export CLAW_ENABLE_LINUX_ACCOUNTS=$(printf "%q" "${ENABLE_LINUX_ACCOUNTS:-0}")
|
|
EOF
|
|
chmod 600 "${ENV_FILE}"
|
|
}
|
|
|
|
upsert_env_export() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local tmp_file
|
|
tmp_file="$(mktemp)"
|
|
grep -v -E "^export ${key}=" "${ENV_FILE}" >"${tmp_file}" || true
|
|
printf 'export %s=%s\n' "${key}" "$(printf "%q" "${value}")" >>"${tmp_file}"
|
|
install -m 0600 "${tmp_file}" "${ENV_FILE}"
|
|
rm -f "${tmp_file}"
|
|
}
|
|
|
|
ensure_env_file() {
|
|
if [[ -f "${ENV_FILE}" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "${ENV_FILE}"
|
|
if [[ -n "${REQUESTED_SERVICE_SCOPE}" ]]; then
|
|
SERVICE_SCOPE="${REQUESTED_SERVICE_SCOPE}"
|
|
export CLAW_SERVICE_SCOPE="${REQUESTED_SERVICE_SCOPE}"
|
|
fi
|
|
if [[ -n "${REQUESTED_ENABLE_LINUX_ACCOUNTS}" ]]; then
|
|
ENABLE_LINUX_ACCOUNTS="${REQUESTED_ENABLE_LINUX_ACCOUNTS}"
|
|
export CLAW_ENABLE_LINUX_ACCOUNTS="${REQUESTED_ENABLE_LINUX_ACCOUNTS}"
|
|
fi
|
|
configure_instance_names
|
|
if [[ -n "${REQUESTED_SERVICE_SCOPE}" ]]; then
|
|
upsert_env_export CLAW_SERVICE_SCOPE "${SERVICE_SCOPE}"
|
|
fi
|
|
if [[ -n "${REQUESTED_ENABLE_LINUX_ACCOUNTS}" ]]; then
|
|
upsert_env_export CLAW_ENABLE_LINUX_ACCOUNTS "${ENABLE_LINUX_ACCOUNTS}"
|
|
fi
|
|
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
|
fail "${ENV_FILE} 缺少 OPENAI_API_KEY,请编辑该文件补齐。"
|
|
fi
|
|
log "使用本机部署配置:${ENV_FILE}"
|
|
echo "CLAW_DEPLOY_INSTANCE=${DEPLOY_INSTANCE}"
|
|
echo "OPENAI_BASE_URL=${OPENAI_BASE_URL:-}"
|
|
echo "OPENAI_MODEL=${OPENAI_MODEL:-}"
|
|
echo "OPENAI_TIMEOUT_SECONDS=${OPENAI_TIMEOUT_SECONDS:-3600}"
|
|
echo "CLAW_MODEL_IDLE_TIMEOUT_SECONDS=${CLAW_MODEL_IDLE_TIMEOUT_SECONDS:-60}"
|
|
echo "CLAW_SERVICE_SCOPE=${SERVICE_SCOPE}"
|
|
echo "CLAW_ENABLE_LINUX_ACCOUNTS=${CLAW_ENABLE_LINUX_ACCOUNTS:-0}"
|
|
echo "OPENAI_API_KEY=已配置"
|
|
return
|
|
fi
|
|
|
|
if [[ ! -t 0 ]]; then
|
|
fail "首次部署需要交互式输入 OPENAI_API_KEY。请登录机器后执行:bash scripts/deploy-ubuntu.sh"
|
|
fi
|
|
|
|
log "首次部署:生成本机配置 ${ENV_FILE}"
|
|
echo "配置会写入 ${ENV_FILE},该文件已被 .gitignore 忽略。"
|
|
|
|
local api_key base_url model backend_host backend_port frontend_host frontend_port
|
|
prompt_value api_key "请输入 OPENAI_API_KEY: " "" 1
|
|
[[ -n "${api_key}" ]] || fail "OPENAI_API_KEY 不能为空。"
|
|
prompt_value base_url "请输入 OPENAI_BASE_URL" "http://model.mify.ai.srv/v1"
|
|
prompt_value model "请输入 OPENAI_MODEL" "tongyi/deepseek-v4-pro"
|
|
configure_instance_names
|
|
if [[ "${ENABLE_LINUX_ACCOUNTS}" == "1" && "${SERVICE_SCOPE}" != "system" ]]; then
|
|
fail "--enable-linux-accounts 需要 --system-service,并建议使用 sudo/root 执行部署。"
|
|
fi
|
|
prompt_value backend_host "请输入后端监听地址" "127.0.0.1"
|
|
prompt_value backend_port "请输入后端端口" "8765"
|
|
prompt_value frontend_host "请输入前端监听地址" "0.0.0.0"
|
|
prompt_value frontend_port "请输入前端端口" "3000"
|
|
write_env_file "${api_key}" "${base_url}" "${model}" "${backend_host}" "${backend_port}" "${frontend_host}" "${frontend_port}"
|
|
echo "已写入 ${ENV_FILE},后续修改配置请编辑这个文件。"
|
|
}
|
|
|
|
update_git() {
|
|
if [[ "${SKIP_GIT}" == "1" ]]; then
|
|
log "跳过 git 拉取,使用当前工作区"
|
|
return
|
|
fi
|
|
require_command git "请先安装 git。"
|
|
cd "${ROOT_DIR}"
|
|
[[ -d .git ]] || fail "${ROOT_DIR} 不是 git 仓库。"
|
|
|
|
if [[ -z "${BRANCH}" ]]; then
|
|
BRANCH="$(git branch --show-current)"
|
|
fi
|
|
[[ -n "${BRANCH}" ]] || fail "无法判断当前分支,请显式传入分支名。"
|
|
|
|
log "更新代码:origin/${BRANCH}"
|
|
git fetch origin
|
|
git checkout "${BRANCH}"
|
|
if [[ "${FORCE}" == "1" ]]; then
|
|
git reset --hard "origin/${BRANCH}"
|
|
else
|
|
if [[ -n "$(git status --porcelain)" ]]; then
|
|
fail "工作区有未提交改动。请先处理,或使用 --force 覆盖。"
|
|
fi
|
|
git pull --ff-only origin "${BRANCH}"
|
|
fi
|
|
}
|
|
|
|
ensure_python() {
|
|
require_command pyenv "请先安装 pyenv,并安装 Python ${PYTHON_VERSION}。"
|
|
log "准备 Python 环境:${PYTHON_VERSION}"
|
|
cd "${ROOT_DIR}"
|
|
if ! pyenv versions --bare | grep -Fx "${PYTHON_VERSION}" >/dev/null 2>&1; then
|
|
echo "pyenv 未发现 Python ${PYTHON_VERSION},开始安装。"
|
|
pyenv install "${PYTHON_VERSION}"
|
|
fi
|
|
PYENV_VERSION="${PYTHON_VERSION}" pyenv exec python -m venv "${ROOT_DIR}/.venv"
|
|
"${ROOT_DIR}/.venv/bin/python" -m pip install --upgrade pip setuptools wheel
|
|
"${ROOT_DIR}/.venv/bin/python" -m pip install -e .
|
|
}
|
|
|
|
build_frontend() {
|
|
local npm_bin
|
|
npm_bin="$(resolve_npm_bin)"
|
|
export PATH="$(dirname "${npm_bin}"):${PATH}"
|
|
log "安装并构建前端"
|
|
cd "${ROOT_DIR}/frontend/app"
|
|
if [[ -f package-lock.json ]]; then
|
|
"${npm_bin}" ci
|
|
else
|
|
"${npm_bin}" install
|
|
fi
|
|
"${npm_bin}" run build
|
|
}
|
|
|
|
remember_node_runtime() {
|
|
local npm_bin npx_bin node_bin node_bin_dir tmp_file
|
|
npm_bin="$(resolve_npm_bin)"
|
|
node_bin_dir="$(dirname "${npm_bin}")"
|
|
export PATH="${node_bin_dir}:${PATH}"
|
|
node_bin="$(PATH="${node_bin_dir}:${PATH}" command -v node || true)"
|
|
npx_bin="$(PATH="${node_bin_dir}:${PATH}" command -v npx || true)"
|
|
tmp_file="$(mktemp)"
|
|
grep -v -E '^(# systemd 用户服务不会读取交互式 shell 配置|export CLAW_(NPM_BIN|NPX_BIN|NODE_BIN|NODE_BIN_DIR)=)' "${ENV_FILE}" >"${tmp_file}" || true
|
|
cat >>"${tmp_file}" <<EOF
|
|
|
|
# systemd 用户服务不会读取交互式 shell 配置,这里记录部署时可用的 Node.js/npm/npx 路径。
|
|
export CLAW_NPM_BIN=$(printf "%q" "${npm_bin}")
|
|
export CLAW_NPX_BIN=$(printf "%q" "${npx_bin}")
|
|
export CLAW_NODE_BIN=$(printf "%q" "${node_bin}")
|
|
export CLAW_NODE_BIN_DIR=$(printf "%q" "${node_bin_dir}")
|
|
EOF
|
|
install -m 0600 "${tmp_file}" "${ENV_FILE}"
|
|
rm -f "${tmp_file}"
|
|
log "记录 Node.js 运行路径"
|
|
echo "npm=${npm_bin}"
|
|
echo "npx=${npx_bin:-未检测到}"
|
|
echo "node=${node_bin:-未检测到}"
|
|
}
|
|
|
|
install_systemd_service() {
|
|
local service_name="$1"
|
|
local template_path="$2"
|
|
local target_dir="${USER_SYSTEMD_DIR}"
|
|
local wanted_by="default.target"
|
|
if [[ "${SERVICE_SCOPE}" == "system" ]]; then
|
|
target_dir="${SYSTEM_SYSTEMD_DIR}"
|
|
wanted_by="multi-user.target"
|
|
fi
|
|
local target_path="${target_dir}/${service_name}.service"
|
|
local tmp_path
|
|
tmp_path="$(mktemp)"
|
|
mkdir -p "${target_dir}"
|
|
sed \
|
|
-e "s#__PROJECT_ROOT__#${ROOT_DIR}#g" \
|
|
-e "s#__BACKEND_SERVICE__#${BACKEND_SERVICE}#g" \
|
|
-e "s#__FRONTEND_SERVICE__#${FRONTEND_SERVICE}#g" \
|
|
-e "s#WantedBy=default.target#WantedBy=${wanted_by}#g" \
|
|
"${template_path}" >"${tmp_path}"
|
|
install -m 0644 "${tmp_path}" "${target_path}"
|
|
rm -f "${tmp_path}"
|
|
}
|
|
|
|
install_services() {
|
|
require_command systemctl "当前系统不支持 systemd,无法安装服务。"
|
|
if [[ "${ENABLE_LINUX_ACCOUNTS}" == "1" && "${SERVICE_SCOPE}" != "system" ]]; then
|
|
fail "CLAW_ENABLE_LINUX_ACCOUNTS=1 需要 system 服务。请用 sudo bash scripts/deploy-ubuntu.sh --system-service --enable-linux-accounts。"
|
|
fi
|
|
log "安装/更新 ${SERVICE_SCOPE} systemd 服务"
|
|
install_systemd_service "${BACKEND_SERVICE}" "${ROOT_DIR}/deploy/systemd/zk-data-agent-backend.service.template"
|
|
install_systemd_service "${FRONTEND_SERVICE}" "${ROOT_DIR}/deploy/systemd/zk-data-agent-frontend.service.template"
|
|
if [[ "${SERVICE_SCOPE}" == "system" ]]; then
|
|
systemctl daemon-reload
|
|
systemctl enable "${BACKEND_SERVICE}" "${FRONTEND_SERVICE}"
|
|
else
|
|
systemctl --user daemon-reload
|
|
systemctl --user enable "${BACKEND_SERVICE}" "${FRONTEND_SERVICE}"
|
|
fi
|
|
}
|
|
|
|
restart_services() {
|
|
log "重启服务"
|
|
if [[ "${SERVICE_SCOPE}" == "system" ]]; then
|
|
systemctl restart "${BACKEND_SERVICE}"
|
|
systemctl restart "${FRONTEND_SERVICE}"
|
|
else
|
|
systemctl --user restart "${BACKEND_SERVICE}"
|
|
systemctl --user restart "${FRONTEND_SERVICE}"
|
|
fi
|
|
}
|
|
|
|
health_check() {
|
|
# shellcheck disable=SC1090
|
|
source "${ENV_FILE}"
|
|
local backend_url="http://${CLAW_BACKEND_HOST:-127.0.0.1}:${CLAW_BACKEND_PORT:-8765}/api/state"
|
|
local frontend_url="http://127.0.0.1:${CLAW_FRONTEND_PORT:-3000}/"
|
|
log "健康检查"
|
|
for _ in {1..40}; do
|
|
if curl -fsS "${backend_url}" >/dev/null 2>&1 && curl -fsS "${frontend_url}" >/dev/null 2>&1; then
|
|
echo "backend: ${backend_url}"
|
|
echo "frontend: http://<机器IP>:${CLAW_FRONTEND_PORT:-3000}/"
|
|
echo "服务已启动。"
|
|
return
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
warn "健康检查失败,输出最近日志。"
|
|
if [[ "${SERVICE_SCOPE}" == "system" ]]; then
|
|
journalctl -u "${BACKEND_SERVICE}" -n 80 --no-pager || true
|
|
journalctl -u "${FRONTEND_SERVICE}" -n 80 --no-pager || true
|
|
else
|
|
journalctl --user -u "${BACKEND_SERVICE}" -n 80 --no-pager || true
|
|
journalctl --user -u "${FRONTEND_SERVICE}" -n 80 --no-pager || true
|
|
fi
|
|
exit 1
|
|
}
|
|
|
|
main() {
|
|
log "ZK Data Agent Ubuntu 部署"
|
|
bootstrap_system_packages
|
|
configure_nginx_upload_limit
|
|
update_git
|
|
ensure_env_file
|
|
configure_instance_names
|
|
ensure_python
|
|
build_frontend
|
|
remember_node_runtime
|
|
install_services
|
|
restart_services
|
|
health_check
|
|
}
|
|
|
|
main "$@"
|