From 83332f265d6c0761d92a5380f1435ef39486541a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Wed, 18 Mar 2026 12:04:09 +0800 Subject: [PATCH] ops: add oneclick deploy bundle builder --- scripts/build_oneclick_bundle.sh | 172 +++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100755 scripts/build_oneclick_bundle.sh diff --git a/scripts/build_oneclick_bundle.sh b/scripts/build_oneclick_bundle.sh new file mode 100755 index 0000000..e7fe610 --- /dev/null +++ b/scripts/build_oneclick_bundle.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 构建“一键部署包”(代码 + vendor + 加密数据库快照) +# 目标:下载压缩包 → 解压 → 运行 install.sh → 即可恢复到最新数据并可访问。 +# +# 依赖:git / tar / gzip +# 可选:composer(仅当需要现装 vendor) +# +# 用法: +# bash scripts/build_oneclick_bundle.sh +# 输出: +# /app/working/dist/saasshop_bundle_.tar.gz +# /app/working/dist/saasshop_bundle_latest.tar.gz + +REPO_DIR=$(cd "$(dirname "$0")/.." && pwd) +cd "$REPO_DIR" + +DIST_DIR="/app/working/dist" +mkdir -p "$DIST_DIR" + +DATA_REPO_SSH="" +if [[ -f /app/working.secret/saasshop_data_repo_ssh ]]; then + DATA_REPO_SSH=$(cat /app/working.secret/saasshop_data_repo_ssh) +fi + +if [[ "$DATA_REPO_SSH" == "" ]]; then + echo "缺少数据仓地址:/app/working.secret/saasshop_data_repo_ssh" + exit 31 +fi + +STAMP=$(date +%Y%m%d_%H%M%S) +BUNDLE_ROOT=$(mktemp -d) +trap 'rm -rf "$BUNDLE_ROOT" || true' EXIT + +APP_DIR="$BUNDLE_ROOT/saasshop" +mkdir -p "$APP_DIR" + +# 1) 打包代码(不带 .git) +echo "[bundle] copying app files ..." +# 使用 tar 管道复制(比 cp -a 更容易排除) +tar \ + --exclude=".git" \ + --exclude="node_modules" \ + --exclude="storage/logs" \ + --exclude="storage/framework/cache" \ + --exclude="storage/framework/sessions" \ + --exclude="storage/framework/views" \ + --exclude="storage/app/private" \ + --exclude=".env" \ + -cf - . | (cd "$APP_DIR" && tar -xf -) + +# 2) 确保 vendor 存在(无构建链/傻瓜部署,优先直接随包携带) +if [[ ! -d "$APP_DIR/vendor" ]]; then + echo "[bundle] vendor 不存在,尝试 composer install --no-dev ..." + if command -v composer >/dev/null 2>&1; then + (cd "$APP_DIR" && composer install --no-dev --prefer-dist --no-interaction) + else + echo "[bundle] composer 不存在且 vendor 缺失,无法构建傻瓜包" + exit 32 + fi +fi + +# 3) 拉取数据仓最新快照 +echo "[bundle] fetching latest encrypted snapshot from data repo ..." +DATA_TMP="$BUNDLE_ROOT/data_repo" +git clone "$DATA_REPO_SSH" "$DATA_TMP" >/dev/null + +if [[ ! -f "$DATA_TMP/snapshots/latest.sql.gz.enc" ]]; then + echo "数据仓缺少 snapshots/latest.sql.gz.enc" + exit 33 +fi + +mkdir -p "$APP_DIR/bundle/snapshots" +cp -f "$DATA_TMP/snapshots/latest.sql.gz.enc" "$APP_DIR/bundle/snapshots/latest.sql.gz.enc" +if [[ -f "$DATA_TMP/snapshots/manifest.json" ]]; then + cp -f "$DATA_TMP/snapshots/manifest.json" "$APP_DIR/bundle/snapshots/manifest.json" +fi + +# 4) 写入 install.sh(真正一键入口) +cat > "$APP_DIR/install.sh" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +# SaaSShop 一键部署脚本(随 bundle 一起发放) +# 用法: +# export SAASSHOP_DB_SNAPSHOT_KEY='解密密钥' +# bash install.sh +# +# 你也可以先准备好 .env(推荐),脚本会优先读取项目根目录 .env。 + +APP_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$APP_DIR" + +if [[ ! -f .env ]]; then + if [[ -f .env.example ]]; then + cp .env.example .env + echo "[install] 已生成 .env(来自 .env.example),请按需修改 DB_* / APP_URL 等配置。" + else + echo "[install] 缺少 .env 与 .env.example,无法继续" + exit 41 + fi +fi + +# 读取 .env(仅用于 DB 连接) +set -a +# shellcheck disable=SC1091 +source .env +set +a + +DB_HOST=${DB_HOST:-127.0.0.1} +DB_PORT=${DB_PORT:-3306} +DB_DATABASE=${DB_DATABASE:-appdb} +DB_USERNAME=${DB_USERNAME:-appuser} +DB_PASSWORD=${DB_PASSWORD:-} + +if [[ "${SAASSHOP_DB_SNAPSHOT_KEY:-}" == "" ]]; then + echo "[install] 缺少 SAASSHOP_DB_SNAPSHOT_KEY:无法解密并恢复完整数据。" + echo "[install] 请先:export SAASSHOP_DB_SNAPSHOT_KEY='...密钥...'" + exit 42 +fi + +# 权限准备 +mkdir -p storage bootstrap/cache +chmod -R ug+rwX storage bootstrap/cache || true + +# APP_KEY +if ! grep -q '^APP_KEY=' .env || [[ "${APP_KEY:-}" == "" ]]; then + echo "[install] 生成 APP_KEY ..." + php artisan key:generate --force +fi + +echo "[install] 导入最新数据库快照 ..." +ENC_FILE="bundle/snapshots/latest.sql.gz.enc" +if [[ ! -f "$ENC_FILE" ]]; then + echo "[install] 缺少快照文件:$ENC_FILE" + exit 43 +fi + +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR" || true' EXIT +SQL_GZ="$TMP_DIR/latest.sql.gz" + +openssl enc -d -aes-256-cbc -pbkdf2 \ + -pass env:SAASSHOP_DB_SNAPSHOT_KEY \ + -in "$ENC_FILE" -out "$SQL_GZ" + +gzip -dc "$SQL_GZ" | mysql \ + --host="$DB_HOST" --port="$DB_PORT" \ + --user="$DB_USERNAME" --password="$DB_PASSWORD" + +echo "[install] 清理缓存 ..." +php artisan optimize:clear + +echo "[done] 部署完成。接下来:配置 nginx 指向 public/,即可访问。" +EOF +chmod +x "$APP_DIR/install.sh" + +# 5) 打包 +OUT_FILE="$DIST_DIR/saasshop_bundle_${STAMP}.tar.gz" +LATEST_FILE="$DIST_DIR/saasshop_bundle_latest.tar.gz" + +echo "[bundle] creating tar.gz ..." +( + cd "$BUNDLE_ROOT" + tar -czf "$OUT_FILE" saasshop +) + +cp -f "$OUT_FILE" "$LATEST_FILE" + +echo "[bundle] output: $OUT_FILE" +echo "[bundle] latest: $LATEST_FILE"