#!/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 <