deploy(M00-E): 完善菜单式部署骨架
This commit is contained in:
+14
-1
@@ -1,4 +1,17 @@
|
||||
# Ubuntu 菜单脚本目录
|
||||
|
||||
后续 M00-D/M00-E 将在此目录补充 `/opt/apps` 初始化、Gitea 拉取、业务部署、EMQX、Nginx、证书、备份、恢复、回滚和诊断脚本。
|
||||
本目录为根目录 `setup.sh` 提供模块化函数。
|
||||
|
||||
| 文件 | 用途 |
|
||||
|---|---|
|
||||
| `lib.sh` | 固定路径、仓库、域名、状态输出和架构检查。 |
|
||||
| `preflight.sh` | 启动快检:架构、基础命令、Docker 禁用提醒、Redis 预留状态。 |
|
||||
| `init-layout.sh` | 创建 `/opt/apps` 目录布局并写入 `run/layout.json`。 |
|
||||
| `repo-status.sh` | 检查固定仓库、分支、DIRTY/AHEAD/BEHIND/DIVERGED 状态。 |
|
||||
| `deploy-business.sh` | 克隆/更新仓库并生成 dry-run release manifest。 |
|
||||
| `backup.sh` | 生成 manifest-only 备份记录。 |
|
||||
| `restore.sh` | 输出人工恢复要求,不自动改动生产数据。 |
|
||||
| `rollback.sh` | 列出 release 回滚点。 |
|
||||
| `diagnose.sh` | 汇总快检、仓库、磁盘、服务和公开端点。 |
|
||||
|
||||
M00 阶段脚本必须保持可重复执行和非破坏性。真实数据库、证书、EMQX ACL、Nginx 写入和 PM2 切换将在后续模块具备配置后继续补全。
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
qipai_backup() {
|
||||
qipai_require_root_for_write
|
||||
local backup_dir manifest
|
||||
backup_dir="${APP_ROOT}/backups/manual/$(date +%Y%m%d%H%M%S)"
|
||||
mkdir -p "$backup_dir"
|
||||
manifest="${backup_dir}/backup.json"
|
||||
cat >"$manifest" <<EOF
|
||||
{
|
||||
"generatedAt": "$(qipai_timestamp)",
|
||||
"type": "M00_MANIFEST_ONLY",
|
||||
"mysql": "SKIPPED_NO_CONFIG",
|
||||
"emqx": "SKIPPED_NO_CONFIG",
|
||||
"uploads": "SKIPPED_NO_UPLOAD_DIR",
|
||||
"note": "Real backup commands will be enabled after production configuration is present."
|
||||
}
|
||||
EOF
|
||||
qipai_pass "backup manifest written: $manifest"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_backup
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
# shellcheck source=repo-status.sh
|
||||
. "${SCRIPT_DIR}/repo-status.sh"
|
||||
|
||||
qipai_deploy_business() {
|
||||
qipai_require_root_for_write
|
||||
local repo_dir release_id release_dir manifest
|
||||
repo_dir="$(qipai_repo_dir)"
|
||||
|
||||
if [ ! -d "${repo_dir}/.git" ]; then
|
||||
qipai_info "cloning repository to ${repo_dir}"
|
||||
git clone --branch "$QIPAI_BRANCH" "$QIPAI_REPO_URL" "$repo_dir"
|
||||
fi
|
||||
|
||||
qipai_repo_status
|
||||
git -C "$repo_dir" pull --ff-only origin "$QIPAI_BRANCH"
|
||||
|
||||
release_id="$(date +%Y%m%d%H%M%S)-$(git -C "$repo_dir" rev-parse --short HEAD)"
|
||||
release_dir="$(qipai_release_dir)/${release_id}"
|
||||
mkdir -p "$release_dir"
|
||||
|
||||
manifest="${release_dir}/release.json"
|
||||
cat >"$manifest" <<EOF
|
||||
{
|
||||
"releaseId": "${release_id}",
|
||||
"generatedAt": "$(qipai_timestamp)",
|
||||
"commit": "$(git -C "$repo_dir" rev-parse HEAD)",
|
||||
"branch": "${QIPAI_BRANCH}",
|
||||
"backendBuild": "SKIPPED_NO_PROJECT",
|
||||
"adminBuild": "SKIPPED_NO_PROJECT",
|
||||
"miniappMirror": "RECORDED_SOURCE_COMMIT_ONLY",
|
||||
"databaseMigration": "SKIPPED_NO_MIGRATIONS",
|
||||
"deployed": false
|
||||
}
|
||||
EOF
|
||||
ln -sfn "$release_dir" "${APP_ROOT}/current-release"
|
||||
cp "$manifest" "$(qipai_current_release_file)"
|
||||
qipai_pass "release manifest written: $manifest"
|
||||
qipai_warn "business build is a safe M00 dry run until backend/admin projects exist"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_deploy_business
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
# shellcheck source=preflight.sh
|
||||
. "${SCRIPT_DIR}/preflight.sh"
|
||||
# shellcheck source=repo-status.sh
|
||||
. "${SCRIPT_DIR}/repo-status.sh"
|
||||
|
||||
qipai_diagnose() {
|
||||
qipai_preflight || true
|
||||
qipai_repo_status || true
|
||||
|
||||
qipai_info "disk usage:"
|
||||
df -h "$APP_ROOT" 2>/dev/null || df -h /
|
||||
|
||||
qipai_info "service status summary:"
|
||||
for service in nginx mysql emqx gitea; do
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl is-active --quiet "$service" 2>/dev/null && qipai_pass "${service}: active" || qipai_warn "${service}: inactive or missing"
|
||||
fi
|
||||
done
|
||||
|
||||
qipai_info "public endpoints:"
|
||||
qipai_info "app health: ${QIPAI_API_ORIGIN}/app-api/health"
|
||||
qipai_info "admin health: ${QIPAI_API_ORIGIN}/admin-api/health"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_diagnose
|
||||
fi
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
qipai_init_layout() {
|
||||
qipai_require_root_for_write
|
||||
|
||||
local dirs=(
|
||||
"${APP_ROOT}"
|
||||
"${APP_ROOT}/qipai-repo"
|
||||
"${APP_ROOT}/qipai-backend"
|
||||
"${APP_ROOT}/qipai-admin"
|
||||
"${APP_ROOT}/qipai-miniapp"
|
||||
"${APP_ROOT}/releases"
|
||||
"${APP_ROOT}/backups/mysql"
|
||||
"${APP_ROOT}/backups/emqx"
|
||||
"${APP_ROOT}/backups/files"
|
||||
"${APP_ROOT}/logs"
|
||||
"${APP_ROOT}/nginx"
|
||||
"${APP_ROOT}/redis-reserved"
|
||||
"${APP_ROOT}/run"
|
||||
)
|
||||
|
||||
for dir in "${dirs[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
qipai_pass "directory exists: $dir"
|
||||
done
|
||||
|
||||
if id qipai >/dev/null 2>&1; then
|
||||
qipai_pass "user qipai exists"
|
||||
else
|
||||
qipai_warn "user qipai does not exist; create manually before production deployment"
|
||||
fi
|
||||
|
||||
if id git >/dev/null 2>&1; then
|
||||
qipai_pass "user git exists"
|
||||
else
|
||||
qipai_warn "user git does not exist; required for native Gitea service"
|
||||
fi
|
||||
|
||||
cat >"${APP_ROOT}/run/layout.json" <<EOF
|
||||
{
|
||||
"generatedAt": "$(qipai_timestamp)",
|
||||
"appRoot": "${APP_ROOT}",
|
||||
"repo": "$(qipai_repo_dir)",
|
||||
"backend": "${APP_ROOT}/qipai-backend",
|
||||
"admin": "${APP_ROOT}/qipai-admin",
|
||||
"miniapp": "${APP_ROOT}/qipai-miniapp",
|
||||
"releases": "$(qipai_release_dir)",
|
||||
"redis": "RESERVED/DISABLED"
|
||||
}
|
||||
EOF
|
||||
qipai_pass "layout manifest written: ${APP_ROOT}/run/layout.json"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_init_layout
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
QIPAI_DEPLOY_VERSION="${QIPAI_DEPLOY_VERSION:-0.1.0-m00-deploy-baseline}"
|
||||
APP_ROOT="${APP_ROOT:-/opt/apps}"
|
||||
QIPAI_REPO_URL="${QIPAI_REPO_URL:-ssh://git@127.0.0.1:2222/panda/qipai.git}"
|
||||
QIPAI_PUBLIC_REPO_URL="${QIPAI_PUBLIC_REPO_URL:-ssh://git@git.txyundm.cn:2222/panda/qipai.git}"
|
||||
QIPAI_BRANCH="${QIPAI_BRANCH:-main}"
|
||||
QIPAI_DOMAIN="${QIPAI_DOMAIN:-api.txyundm.cn}"
|
||||
QIPAI_API_ORIGIN="https://${QIPAI_DOMAIN}"
|
||||
|
||||
qipai_timestamp() {
|
||||
date "+%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
|
||||
qipai_status() {
|
||||
local level="$1"
|
||||
shift
|
||||
printf '%s: %s\n' "$level" "$*"
|
||||
}
|
||||
|
||||
qipai_info() {
|
||||
qipai_status "INFO" "$@"
|
||||
}
|
||||
|
||||
qipai_pass() {
|
||||
qipai_status "PASS" "$@"
|
||||
}
|
||||
|
||||
qipai_warn() {
|
||||
qipai_status "WARN" "$@"
|
||||
}
|
||||
|
||||
qipai_fail() {
|
||||
qipai_status "FAIL" "$@"
|
||||
}
|
||||
|
||||
qipai_require_root_for_write() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
qipai_fail "This option writes under ${APP_ROOT}; run with sudo on Ubuntu."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
qipai_check_arch() {
|
||||
local kernel_arch dpkg_arch bits
|
||||
kernel_arch="$(uname -m 2>/dev/null || echo unknown)"
|
||||
dpkg_arch="$(dpkg --print-architecture 2>/dev/null || echo unknown)"
|
||||
bits="$(getconf LONG_BIT 2>/dev/null || echo unknown)"
|
||||
|
||||
[ "$kernel_arch" = "x86_64" ] && qipai_pass "kernel architecture: ${kernel_arch}" || {
|
||||
qipai_fail "kernel architecture must be x86_64, actual: ${kernel_arch}"
|
||||
return 1
|
||||
}
|
||||
[ "$dpkg_arch" = "amd64" ] && qipai_pass "dpkg architecture: ${dpkg_arch}" || {
|
||||
qipai_fail "dpkg architecture must be amd64, actual: ${dpkg_arch}"
|
||||
return 1
|
||||
}
|
||||
[ "$bits" = "64" ] && qipai_pass "userspace bits: ${bits}" || {
|
||||
qipai_fail "userspace must be 64-bit, actual: ${bits}"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
qipai_command_status() {
|
||||
local command_name="$1"
|
||||
if command -v "$command_name" >/dev/null 2>&1; then
|
||||
qipai_pass "${command_name}: $(command -v "$command_name")"
|
||||
else
|
||||
qipai_warn "${command_name}: not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
qipai_repo_dir() {
|
||||
printf '%s/qipai-repo\n' "$APP_ROOT"
|
||||
}
|
||||
|
||||
qipai_release_dir() {
|
||||
printf '%s/releases\n' "$APP_ROOT"
|
||||
}
|
||||
|
||||
qipai_current_release_file() {
|
||||
printf '%s/current-release.json\n' "$APP_ROOT"
|
||||
}
|
||||
|
||||
qipai_print_context() {
|
||||
qipai_info "deploy version: ${QIPAI_DEPLOY_VERSION}"
|
||||
qipai_info "app root: ${APP_ROOT}"
|
||||
qipai_info "repo: ${QIPAI_REPO_URL}"
|
||||
qipai_info "branch: ${QIPAI_BRANCH}"
|
||||
qipai_info "api origin: ${QIPAI_API_ORIGIN}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
qipai_preflight() {
|
||||
qipai_print_context
|
||||
qipai_check_arch
|
||||
qipai_command_status git
|
||||
qipai_command_status bash
|
||||
qipai_command_status node
|
||||
qipai_command_status npm
|
||||
qipai_command_status mysql
|
||||
qipai_command_status nginx
|
||||
qipai_command_status pm2
|
||||
qipai_command_status openssl
|
||||
qipai_command_status curl
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
qipai_warn "docker detected but this project does not use Docker"
|
||||
else
|
||||
qipai_pass "docker: not installed or not in PATH"
|
||||
fi
|
||||
|
||||
if command -v redis-server >/dev/null 2>&1; then
|
||||
qipai_warn "redis-server detected; Redis is reserved/disabled for MVP"
|
||||
else
|
||||
qipai_pass "redis: RESERVED/DISABLED"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_preflight
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
qipai_repo_status() {
|
||||
local repo_dir
|
||||
repo_dir="$(qipai_repo_dir)"
|
||||
|
||||
if [ ! -d "${repo_dir}/.git" ]; then
|
||||
qipai_warn "repo not cloned: ${repo_dir}"
|
||||
qipai_info "expected repo: ${QIPAI_REPO_URL}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
git -C "$repo_dir" remote -v
|
||||
local branch status
|
||||
branch="$(git -C "$repo_dir" branch --show-current)"
|
||||
status="$(git -C "$repo_dir" status --short --branch --untracked-files=all)"
|
||||
qipai_info "branch: ${branch}"
|
||||
printf '%s\n' "$status"
|
||||
|
||||
if [ "$branch" != "$QIPAI_BRANCH" ]; then
|
||||
qipai_fail "branch mismatch: expected ${QIPAI_BRANCH}, actual ${branch}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if git -C "$repo_dir" diff --quiet && git -C "$repo_dir" diff --cached --quiet; then
|
||||
qipai_pass "repo is not dirty"
|
||||
else
|
||||
qipai_fail "repo is DIRTY; deployment must stop"
|
||||
return 1
|
||||
fi
|
||||
|
||||
git -C "$repo_dir" fetch origin "$QIPAI_BRANCH"
|
||||
local counts
|
||||
counts="$(git -C "$repo_dir" rev-list --left-right --count "${QIPAI_BRANCH}...origin/${QIPAI_BRANCH}")"
|
||||
qipai_info "ahead/behind: ${counts}"
|
||||
case "$counts" in
|
||||
"0 0") qipai_pass "repo matches origin/${QIPAI_BRANCH}" ;;
|
||||
0$'\t'*) qipai_warn "repo is BEHIND origin/${QIPAI_BRANCH}" ;;
|
||||
*$'\t'0) qipai_fail "repo is AHEAD origin/${QIPAI_BRANCH}; deployment must stop"; return 1 ;;
|
||||
*) qipai_fail "repo is DIVERGED; deployment must stop"; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_repo_status
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
qipai_restore() {
|
||||
qipai_warn "restore is not automatic in M00 baseline"
|
||||
qipai_info "Required manual inputs: backup path, MySQL dump, uploads archive, EMQX export"
|
||||
qipai_info "No production data was changed."
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_restore
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
qipai_rollback() {
|
||||
local releases
|
||||
releases="$(qipai_release_dir)"
|
||||
if [ ! -d "$releases" ]; then
|
||||
qipai_warn "no releases directory: $releases"
|
||||
return 0
|
||||
fi
|
||||
qipai_info "available releases:"
|
||||
find "$releases" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort -r | head -20
|
||||
qipai_warn "M00 baseline lists rollback points only; automatic switch requires a selected release id"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--run" ]; then
|
||||
qipai_rollback
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user