支持 curl 管道打开部署菜单。
服务器可直接拉取 deploy/manage.sh,克隆仓库后选择部署、卸载、更新或退出。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 </dev/tty; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
printf '%s' "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_ready() {
|
||||||
|
[[ -f "${1}/app/main.py" && -f "${1}/requirements.txt" && -f "${1}/deploy/manage.sh" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
echo "请使用 root 执行。可以先 sudo -i,再运行安装命令。" >&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" "$@" </dev/tty
|
||||||
|
}
|
||||||
|
|
||||||
|
install_packages() {
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y python3 python3-venv python3-pip git ca-certificates curl rsync
|
||||||
|
python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (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}" <<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 "${INSTALL_ROOT}/.venv/bin/python" ]]; then
|
||||||
|
python3 -m venv "${INSTALL_ROOT}/.venv"
|
||||||
|
fi
|
||||||
|
"${INSTALL_ROOT}/.venv/bin/pip" install --upgrade pip
|
||||||
|
"${INSTALL_ROOT}/.venv/bin/pip" install -r "${INSTALL_ROOT}/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=${INSTALL_ROOT}
|
||||||
|
EnvironmentFile=${ENV_FILE}
|
||||||
|
Environment=PYTHONUNBUFFERED=1
|
||||||
|
Environment=PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ExecStart=${INSTALL_ROOT}/.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() {
|
||||||
|
mkdir -p "${DATA_DIR}"
|
||||||
|
chown -R "${APP_USER}:${APP_USER}" "${INSTALL_ROOT}" "${DATA_DIR}"
|
||||||
|
chmod 750 "${INSTALL_ROOT}" "${DATA_DIR}"
|
||||||
|
if [[ -f "${ENV_FILE}" ]]; then
|
||||||
|
chmod 640 "${ENV_FILE}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "$@"
|
||||||
+2
-263
@@ -1,264 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# HyperNode-AI 交互式部署脚本,适用于 Ubuntu 22.04。
|
# 兼容旧入口,转到 deploy/manage.sh。
|
||||||
# 不安装 Nginx。反代由你自己配置,服务监听 0.0.0.0:33333。
|
exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/manage.sh" "$@"
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ python -m app
|
|||||||
| `app/jobs/daily_yield.py` | 凌晨清算入口 |
|
| `app/jobs/daily_yield.py` | 凌晨清算入口 |
|
||||||
| `app/scheduler.py` | 进程内定时任务 |
|
| `app/scheduler.py` | 进程内定时任务 |
|
||||||
| `app/settle.py` | 可单独执行的清算命令 |
|
| `app/settle.py` | 可单独执行的清算命令 |
|
||||||
| `deploy/ubuntu22.sh` | Ubuntu 22.04 交互式部署 |
|
| `deploy/manage.sh` | Ubuntu 22.04 交互式部署,支持 `curl \| bash` |
|
||||||
|
|
||||||
## 数据表
|
## 数据表
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,27 @@
|
|||||||
|
|
||||||
适用于 Ubuntu 22.04。脚本只安装应用本身,监听 `0.0.0.0:33333`。Nginx、Caddy 或其他反代请自行配置,上游地址使用 `127.0.0.1:33333`。
|
适用于 Ubuntu 22.04。脚本只安装应用本身,监听 `0.0.0.0:33333`。Nginx、Caddy 或其他反代请自行配置,上游地址使用 `127.0.0.1:33333`。
|
||||||
|
|
||||||
## 1. 把代码放到服务器
|
## 1. 一条命令打开菜单
|
||||||
|
|
||||||
仓库地址:[https://git.bz121.com/dekun/HyperNode-AI.git](https://git.bz121.com/dekun/HyperNode-AI.git)
|
在 Ubuntu 22.04 服务器上执行。已经是 root 时:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.bz121.com/dekun/HyperNode-AI.git
|
curl -fsSL https://git.bz121.com/dekun/HyperNode-AI/raw/branch/main/deploy/manage.sh | bash
|
||||||
cd HyperNode-AI
|
|
||||||
sudo bash deploy/ubuntu22.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
私有仓库克隆时需要你的 Gitea 账号。也可以用 scp 把整个项目目录上传到服务器,再进入该目录执行同一条命令。
|
当前用户不是 root 时:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.bz121.com/dekun/HyperNode-AI/raw/branch/main/deploy/manage.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会把仓库克隆到 `/opt/hypernode`,然后打开菜单。已经安装过时,也可以直接运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash /opt/hypernode/deploy/manage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
仓库需要能被服务器匿名读取。若上面的地址返回 401,到 Gitea 把仓库设为公开,或给部署机配置该仓库的读取权限。
|
||||||
|
|
||||||
## 2. 菜单
|
## 2. 菜单
|
||||||
|
|
||||||
@@ -22,8 +32,8 @@ sudo bash deploy/ubuntu22.sh
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 1 一键部署 | 安装 Python 依赖、创建系统用户、写入 systemd,并启动服务 |
|
| 1 一键部署 | 安装 Python 依赖、创建系统用户、写入 systemd,并启动服务 |
|
||||||
| 2 一键卸载 | 停止服务并删除程序。数据库要再输入一次 `yes` 才会删除 |
|
| 2 一键卸载 | 停止服务并删除程序。数据库要再输入一次 `yes` 才会删除 |
|
||||||
| 3 更新 | 用当前目录的代码覆盖 `/opt/hypernode`,重装依赖并重启。`.env` 和数据库保留 |
|
| 3 更新 | 从仓库拉取最新代码,重装依赖并重启。`.env` 和数据库保留 |
|
||||||
| 4 退出 | 离开脚本 |
|
| 0 退出 | 离开脚本 |
|
||||||
|
|
||||||
部署和更新结束时会请求 `http://127.0.0.1:33333/health`,失败会打印最近 50 行服务日志。
|
部署和更新结束时会请求 `http://127.0.0.1:33333/health`,失败会打印最近 50 行服务日志。
|
||||||
|
|
||||||
@@ -76,17 +86,8 @@ location / {
|
|||||||
|
|
||||||
当前接口没有登录鉴权。反代若只在本机使用,云安全组不要对公网放行 33333。需要从外网直接访问时,再放行该端口,并在反代或安全组上限制来源。
|
当前接口没有登录鉴权。反代若只在本机使用,云安全组不要对公网放行 33333。需要从外网直接访问时,再放行该端口,并在反代或安全组上限制来源。
|
||||||
|
|
||||||
## 5. 更新代码
|
## 5. 更新与卸载
|
||||||
|
|
||||||
在服务器上进入新的项目目录后重新打开菜单,选择 `3`:
|
再次执行同一条 `curl` 命令,或运行 `bash /opt/hypernode/deploy/manage.sh`。
|
||||||
|
|
||||||
```bash
|
选择 `3` 会执行 `git pull` 后重启服务。选择 `2` 会删除程序;是否删除 `/var/lib/hypernode` 会再问一次,直接回车会留下数据库。
|
||||||
cd /path/to/HyperNode-AI
|
|
||||||
sudo bash deploy/ubuntu22.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
选择更新后,脚本把该目录同步到 `/opt/hypernode`,然后重启服务。
|
|
||||||
|
|
||||||
## 6. 卸载
|
|
||||||
|
|
||||||
菜单选择 `2`。先输入 `yes` 确认删除程序。是否删除 `/var/lib/hypernode` 会再问一次;直接回车会留下数据库。
|
|
||||||
|
|||||||
Reference in New Issue
Block a user