Files
HyperNode-AI/deploy/ubuntu22.sh
T
dekun 2bbdb00a4d 初始化 HyperNode-AI 控制面。
包含节点账本、每日清算,以及 Ubuntu 交互式部署脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 21:53:20 +08:00

265 lines
6.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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}" <<EOF
DATABASE_URL=sqlite:////${DATA_DIR}/hypernode.db
YIELD_TIMEZONE=Asia/Shanghai
YIELD_HOUR=0
YIELD_MINUTE=5
DISABLE_SCHEDULER=false
EOF
}
install_python_deps() {
if [[ ! -x "${APP_DIR}/.venv/bin/python" ]]; then
python3 -m venv "${APP_DIR}/.venv"
fi
"${APP_DIR}/.venv/bin/pip" install --upgrade pip
"${APP_DIR}/.venv/bin/pip" install -r "${APP_DIR}/requirements.txt"
}
write_service() {
cat >"/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
[Unit]
Description=HyperNode-AI control plane
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${APP_USER}
Group=${APP_USER}
WorkingDirectory=${APP_DIR}
EnvironmentFile=${ENV_FILE}
Environment=PYTHONUNBUFFERED=1
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=${APP_DIR}/.venv/bin/uvicorn app.main:app --host ${BIND_HOST} --port ${BIND_PORT} --workers 1
Restart=always
RestartSec=3
TimeoutStopSec=20
KillSignal=SIGINT
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
}
fix_permissions() {
chown -R "${APP_USER}:${APP_USER}" "${APP_DIR}" "${DATA_DIR}"
chmod 750 "${APP_DIR}" "${DATA_DIR}"
chmod 640 "${ENV_FILE}"
}
wait_until_ready() {
local ready=0
local _i
for _i in 1 2 3 4 5 6 7 8 9 10; do
if curl -fsS "http://127.0.0.1:${BIND_PORT}/health" >/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 <<EOF
========================================
HyperNode-AI 部署菜单 端口 ${BIND_PORT}
========================================
1) 一键部署
2) 一键卸载
3) 更新
4) 退出
========================================
EOF
}
while true; do
show_menu
read -r -p "请选择 [1-4]: " choice || exit 0
case "${choice}" in
1)
cmd_deploy || echo "部署失败。"
pause
;;
2)
cmd_uninstall || echo "卸载失败。"
pause
;;
3)
cmd_update || echo "更新失败。"
pause
;;
4)
echo "已退出。"
exit 0
;;
*)
echo "无效选项:${choice}"
;;
esac
done