From 2a847263f249908e723061de11d75f7624efb1e6 Mon Sep 17 00:00:00 2001 From: dekun Date: Sat, 10 Oct 2026 21:58:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=20curl=20=E7=AE=A1=E9=81=93?= =?UTF-8?q?=E6=89=93=E5=BC=80=E9=83=A8=E7=BD=B2=E8=8F=9C=E5=8D=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 服务器可直接拉取 deploy/manage.sh,克隆仓库后选择部署、卸载、更新或退出。 Co-authored-by: Cursor --- deploy/manage.sh | 341 +++++++++++++++++++++++++++++++++++++++++++++ deploy/ubuntu22.sh | 265 +---------------------------------- 说明.md | 2 +- 部署.md | 41 +++--- 4 files changed, 365 insertions(+), 284 deletions(-) create mode 100644 deploy/manage.sh diff --git a/deploy/manage.sh b/deploy/manage.sh new file mode 100644 index 0000000..7410663 --- /dev/null +++ b/deploy/manage.sh @@ -0,0 +1,341 @@ +#!/usr/bin/env bash +# HyperNode-AI 部署管理器 — 一键部署 / 卸载 / 更新 +# +# 新服务器: +# curl -fsSL https://git.bz121.com/dekun/HyperNode-AI/raw/branch/main/deploy/manage.sh | bash +# +# 已安装: +# bash /opt/hypernode/deploy/manage.sh +# +set -e +if [[ -n "${BASH_VERSION:-}" ]]; then + set -o pipefail +fi + +INSTALL_ROOT="${INSTALL_ROOT:-/opt/hypernode}" +DATA_DIR="${DATA_DIR:-/var/lib/hypernode}" +GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/HyperNode-AI.git}" +GIT_BRANCH="${GIT_BRANCH:-main}" +APP_USER="hypernode" +SERVICE_NAME="hypernode" +BIND_HOST="0.0.0.0" +BIND_PORT="33333" +ENV_FILE="${INSTALL_ROOT}/.env" + +_script_src="${BASH_SOURCE[0]:-}" +if [[ -n "${_script_src}" && -f "${_script_src}" ]]; then + REPO_ROOT="$(cd "$(dirname "${_script_src}")/.." && pwd)" +else + REPO_ROOT="" +fi +unset _script_src + +set -u + +log() { + printf '\n==> %s\n' "$*" +} + +ask() { + local value="" + if [[ ! -r /dev/tty ]]; then + echo "请在终端中运行。通过 curl 管道执行时,也需要当前会话有终端。" >&2 + exit 1 + fi + if ! read -r -p "$1" value &2 + echo "或:curl -fsSL https://git.bz121.com/dekun/HyperNode-AI/raw/branch/main/deploy/manage.sh | sudo bash" >&2 + exit 1 + fi +} + +ensure_git() { + if command -v git >/dev/null 2>&1; then + return 0 + fi + if ! command -v apt-get >/dev/null 2>&1; then + echo "未找到 git,且当前系统没有 apt-get。" >&2 + exit 1 + fi + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y git ca-certificates curl +} + +bootstrap_repo() { + if repo_ready "${INSTALL_ROOT}"; then + REPO_ROOT="${INSTALL_ROOT}" + return 0 + fi + if [[ -n "${REPO_ROOT}" ]] && repo_ready "${REPO_ROOT}"; then + return 0 + fi + + echo "HyperNode-AI 部署管理器 — 首次自举" + echo "将克隆到: ${INSTALL_ROOT}" + require_root + ensure_git + if [[ -e "${INSTALL_ROOT}" ]]; then + echo "错误: ${INSTALL_ROOT} 已存在,但不是有效的 HyperNode-AI 目录。" >&2 + echo "请先处理该目录,或设置 INSTALL_ROOT 指向其他路径。" >&2 + exit 1 + fi + git clone --branch "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}" + exec bash "${INSTALL_ROOT}/deploy/manage.sh" "$@" = (3, 10) else 1)' +} + +ensure_user() { + if ! id -u "${APP_USER}" >/dev/null 2>&1; then + useradd --system --home-dir "${INSTALL_ROOT}" --shell /usr/sbin/nologin "${APP_USER}" + fi +} + +ensure_install_tree() { + if repo_ready "${INSTALL_ROOT}"; then + return 0 + fi + if [[ -n "${REPO_ROOT}" && "${REPO_ROOT}" != "${INSTALL_ROOT}" ]] && repo_ready "${REPO_ROOT}"; then + mkdir -p "${INSTALL_ROOT}" + rsync -a --delete \ + --exclude '.venv/' \ + --exclude '.env' \ + --exclude '__pycache__/' \ + --exclude '*.db' \ + "${REPO_ROOT}/" "${INSTALL_ROOT}/" + return 0 + fi + ensure_git + git clone --branch "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}" +} + +write_env() { + mkdir -p "${DATA_DIR}" + if [[ -f "${ENV_FILE}" ]]; then + log "保留已有配置 ${ENV_FILE}" + return 0 + fi + log "写入 ${ENV_FILE}" + cat >"${ENV_FILE}" <"/etc/systemd/system/${SERVICE_NAME}.service" </dev/null; then + ready=1 + break + fi + sleep 1 + done + if [[ "${ready}" -ne 1 ]]; then + echo "服务未在 10 秒内就绪。最近日志:" >&2 + journalctl -u "${SERVICE_NAME}.service" -n 50 --no-pager >&2 || true + return 1 + fi +} + +cmd_deploy() { + log "一键部署" + install_packages + ensure_user + ensure_install_tree + write_env + install_python_deps + fix_permissions + write_service + systemctl enable "${SERVICE_NAME}.service" + systemctl restart "${SERVICE_NAME}.service" + wait_until_ready + log "部署完成" + echo "监听地址:${BIND_HOST}:${BIND_PORT}" + echo "健康检查:curl http://127.0.0.1:${BIND_PORT}/health" + echo "接口文档:http://<服务器 IP>:${BIND_PORT}/docs" + echo "程序目录:${INSTALL_ROOT}" + echo "数据库:${DATA_DIR}/hypernode.db" + echo "日志:journalctl -u ${SERVICE_NAME}.service -f" + echo "反代请自行配置,上游地址使用 127.0.0.1:${BIND_PORT}" +} + +cmd_update() { + if [[ ! -f "/etc/systemd/system/${SERVICE_NAME}.service" ]]; then + echo "尚未部署。请先选择「一键部署」。" >&2 + return 1 + fi + log "更新程序" + install_packages + if [[ -d "${INSTALL_ROOT}/.git" ]]; then + git -C "${INSTALL_ROOT}" pull --ff-only origin "${GIT_BRANCH}" + else + ensure_install_tree + fi + install_python_deps + fix_permissions + write_service + systemctl restart "${SERVICE_NAME}.service" + wait_until_ready + log "更新完成,配置和数据库已保留" +} + +cmd_uninstall() { + local answer + echo "即将停止服务并删除 ${INSTALL_ROOT} 与 systemd 单元。" + answer="$(ask "确认卸载请输入 yes:")" + if [[ "${answer}" != "yes" ]]; then + echo "已取消卸载。" + return 0 + fi + + log "卸载服务" + systemctl stop "${SERVICE_NAME}.service" >/dev/null 2>&1 || true + systemctl disable "${SERVICE_NAME}.service" >/dev/null 2>&1 || true + rm -f "/etc/systemd/system/${SERVICE_NAME}.service" + systemctl daemon-reload + rm -rf "${INSTALL_ROOT}" + + answer="$(ask "同时删除数据库 ${DATA_DIR} ?输入 yes 删除,其他键保留:")" + if [[ "${answer}" == "yes" ]]; then + rm -rf "${DATA_DIR}" + echo "数据库已删除。" + else + echo "数据库已保留在 ${DATA_DIR}" + fi + + if id -u "${APP_USER}" >/dev/null 2>&1; then + userdel "${APP_USER}" || echo "系统用户 ${APP_USER} 未删除,可稍后执行 userdel ${APP_USER}" + fi + log "卸载完成" +} + +detect_ip() { + hostname -I 2>/dev/null | awk '{print $1}' +} + +show_banner() { + local ip path + ip="$(detect_ip)" + path="${INSTALL_ROOT}" + [[ -n "${ip}" ]] || ip="?" + echo "" + echo "========================================" + echo " HyperNode-AI 部署管理器" + echo " 仓库: git.bz121.com/dekun/HyperNode-AI" + echo " 路径: ${path}" + echo " 端口: ${BIND_PORT}" + echo " IP: ${ip}" + echo "========================================" +} + +show_menu() { + echo " 1) 一键部署" + echo " 2) 一键卸载" + echo " 3) 更新" + echo " 0) 退出" + echo "" +} + +main_menu() { + local choice + bootstrap_repo "$@" + require_root + if [[ -r /etc/os-release ]]; then + # shellcheck disable=SC1091 + source /etc/os-release + if [[ "${ID:-}" != "ubuntu" ]]; then + echo "当前系统是 ${ID:-unknown},这个脚本用于 Ubuntu 22.04。" >&2 + exit 1 + fi + if [[ "${VERSION_ID:-}" != "22.04" ]]; then + echo "警告:当前是 Ubuntu ${VERSION_ID:-unknown},脚本按 22.04 编写,将继续。" >&2 + fi + fi + + while true; do + show_banner + show_menu + choice="$(ask "请选择 [0-3]: ")" + case "${choice}" in + 1) cmd_deploy || echo "部署失败。" ;; + 2) cmd_uninstall || echo "卸载失败。" ;; + 3) cmd_update || echo "更新失败。" ;; + 0) + echo "已退出。" + exit 0 + ;; + *) echo "无效选项:${choice}" ;; + esac + ask "按 Enter 返回菜单..." >/dev/null + done +} + +main_menu "$@" diff --git a/deploy/ubuntu22.sh b/deploy/ubuntu22.sh index c9e27d8..a285acf 100644 --- a/deploy/ubuntu22.sh +++ b/deploy/ubuntu22.sh @@ -1,264 +1,3 @@ #!/usr/bin/env bash -# HyperNode-AI 交互式部署脚本,适用于 Ubuntu 22.04。 -# 不安装 Nginx。反代由你自己配置,服务监听 0.0.0.0:33333。 -# -# sudo bash deploy/ubuntu22.sh - -set -euo pipefail - -APP_USER="hypernode" -APP_DIR="/opt/hypernode" -DATA_DIR="/var/lib/hypernode" -ENV_FILE="${APP_DIR}/.env" -SERVICE_NAME="hypernode" -BIND_HOST="0.0.0.0" -BIND_PORT="33333" -SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -if [[ "${EUID}" -ne 0 ]]; then - echo "请用 root 运行:sudo bash deploy/ubuntu22.sh" >&2 - exit 1 -fi - -if [[ ! -t 0 ]]; then - echo "请在交互式终端中运行本脚本。" >&2 - exit 1 -fi - -if [[ ! -r /etc/os-release ]]; then - echo "无法识别操作系统,这个脚本用于 Ubuntu 22.04。" >&2 - exit 1 -fi -# shellcheck disable=SC1091 -source /etc/os-release -if [[ "${ID:-}" != "ubuntu" ]]; then - echo "当前系统是 ${ID:-unknown},这个脚本用于 Ubuntu 22.04。" >&2 - exit 1 -fi -if [[ "${VERSION_ID:-}" != "22.04" ]]; then - echo "警告:当前是 Ubuntu ${VERSION_ID:-unknown},脚本按 22.04 编写,将继续。" >&2 -fi - -if [[ ! -f "${SOURCE_DIR}/requirements.txt" || ! -f "${SOURCE_DIR}/app/main.py" ]]; then - echo "未在 ${SOURCE_DIR} 找到项目文件。" >&2 - exit 1 -fi - -log() { - printf '\n==> %s\n' "$*" -} - -pause() { - read -r -p "按回车返回菜单..." _ || exit 0 -} - -sync_code() { - mkdir -p "${APP_DIR}" "${DATA_DIR}" - if [[ "$(realpath "${SOURCE_DIR}")" != "$(realpath "${APP_DIR}")" ]]; then - rsync -a --delete \ - --exclude '.venv/' \ - --exclude '.env' \ - --exclude '.git/' \ - --exclude '__pycache__/' \ - --exclude '*.db' \ - --exclude '.pytest_cache/' \ - "${SOURCE_DIR}/" "${APP_DIR}/" - fi -} - -write_env() { - if [[ -f "${ENV_FILE}" ]]; then - log "保留已有配置 ${ENV_FILE}" - return - fi - log "写入 ${ENV_FILE}" - cat >"${ENV_FILE}" <"/etc/systemd/system/${SERVICE_NAME}.service" </dev/null; then - ready=1 - break - fi - sleep 1 - done - if [[ "${ready}" -ne 1 ]]; then - echo "服务未在 10 秒内就绪。最近日志:" >&2 - journalctl -u "${SERVICE_NAME}.service" -n 50 --no-pager >&2 || true - return 1 - fi -} - -ensure_user() { - if ! id -u "${APP_USER}" >/dev/null 2>&1; then - useradd --system --home-dir "${APP_DIR}" --shell /usr/sbin/nologin "${APP_USER}" - fi -} - -install_packages() { - export DEBIAN_FRONTEND=noninteractive - apt-get update - apt-get install -y python3 python3-venv python3-pip rsync ca-certificates curl - python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)' -} - -cmd_deploy() { - log "一键部署" - install_packages - ensure_user - sync_code - write_env - fix_permissions - install_python_deps - fix_permissions - write_service - systemctl enable "${SERVICE_NAME}.service" - systemctl restart "${SERVICE_NAME}.service" - wait_until_ready - log "部署完成" - echo "监听地址:${BIND_HOST}:${BIND_PORT}" - echo "健康检查:curl http://127.0.0.1:${BIND_PORT}/health" - echo "接口文档:http://<服务器 IP>:${BIND_PORT}/docs" - echo "程序目录:${APP_DIR}" - echo "数据库:${DATA_DIR}/hypernode.db" - echo "日志:journalctl -u ${SERVICE_NAME}.service -f" - echo "反代请自行配置,上游地址使用 127.0.0.1:${BIND_PORT}" -} - -cmd_update() { - if [[ ! -f "/etc/systemd/system/${SERVICE_NAME}.service" ]]; then - echo "尚未部署。请先选择「一键部署」。" >&2 - return 1 - fi - log "更新程序" - install_packages - sync_code - install_python_deps - fix_permissions - write_service - systemctl restart "${SERVICE_NAME}.service" - wait_until_ready - log "更新完成,配置和数据库已保留" -} - -cmd_uninstall() { - local answer - echo "即将停止服务并删除 ${APP_DIR} 与 systemd 单元。" - read -r -p "确认卸载请输入 yes:" answer || exit 0 - if [[ "${answer}" != "yes" ]]; then - echo "已取消卸载。" - return 0 - fi - - log "卸载服务" - systemctl stop "${SERVICE_NAME}.service" || true - systemctl disable "${SERVICE_NAME}.service" || true - rm -f "/etc/systemd/system/${SERVICE_NAME}.service" - systemctl daemon-reload - rm -rf "${APP_DIR}" - - read -r -p "同时删除数据库 ${DATA_DIR} ?输入 yes 删除,其他键保留:" answer || exit 0 - if [[ "${answer}" == "yes" ]]; then - rm -rf "${DATA_DIR}" - echo "数据库已删除。" - else - echo "数据库已保留在 ${DATA_DIR}" - fi - - if id -u "${APP_USER}" >/dev/null 2>&1; then - userdel "${APP_USER}" || echo "系统用户 ${APP_USER} 未删除,可稍后执行 userdel ${APP_USER}" - fi - log "卸载完成" -} - -show_menu() { - cat <