Files

128 lines
5.0 KiB
Bash
Raw Permalink Normal View History

2026-08-22 22:28:41 +08:00
#!/bin/bash
# ============================================================
# MyBlog 服务器端更新脚本(由本地 deploy/update.ps1 上传并执行)
# 职责:备份 -> 解压新代码 -> 数据库迁移 -> 重启服务 -> 验证
# 用法:bash /tmp/update_server.sh [域名]
# 适用:Ubuntu + systemdmyblog 服务);需要 root 或已配置免密 sudo
# 安全说明:
# - 更新前自动备份到 /root/blog-backups(含数据库与上传文件),可随时回滚
# - 只增量覆盖代码文件,不删除服务器上的任何文件(不会加 --delete)
# - 数据库迁移是幂等的:重复执行只会补齐缺失的列,不会损坏数据
# ============================================================
set -eu
# bash 支持 pipefaildash/sh 下自动跳过),保证失败即退出
if [ -n "${BASH_VERSION:-}" ]; then set -o pipefail; fi
BLOG_DIR="/var/www/blog"
PKG="/tmp/myblog.tar.gz"
BACKUP_DIR="/root/blog-backups"
DOMAIN="${1:-}"
# 非 root 用户运行时自动加 sudo(前提:已配置免密 sudo)
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
SUDO="sudo"
fi
echo "== [1/5] 更新前备份 =="
$SUDO mkdir -p "$BACKUP_DIR"
TS=$(date +%Y%m%d_%H%M%S)
$SUDO tar -czf "$BACKUP_DIR/blog_backup_$TS.tar.gz" --exclude=venv --exclude='*/__pycache__' -C "$BLOG_DIR" .
echo "备份完成:$BACKUP_DIR/blog_backup_$TS.tar.gz"
echo "== [2/5] 解压新代码 =="
if [ ! -f "$PKG" ]; then
echo "错误:找不到更新包 $PKG,请先通过本地脚本上传" >&2
exit 1
fi
$SUDO tar -xzf "$PKG" -C "$BLOG_DIR"
$SUDO chown -R blog:blog "$BLOG_DIR"
# 前端缓存指纹:每次部署自动生成新版本号,浏览器强制拉取最新 JS/CSS,无需手动 Ctrl+F5
VERSION=$(date +%Y%m%d%H%M%S)
$SUDO sed -i "s/__VERSION__/${VERSION}/g" "$BLOG_DIR/frontend/index.html"
echo "前端版本号:${VERSION}"
# 把 Windows 上传的脚本统一转成 LF 行尾,防止 bash 解析 CR 报错
find "$BLOG_DIR/deploy" -maxdepth 2 -name '*.sh' -type f -exec sed -i 's/\r$//' {} +
echo "解压完成,文件属主已修正为 blog:blog"
echo "== [3/5] 数据库迁移(幂等) =="
python3 - "$BLOG_DIR" <<'PY'
import sqlite3, sys
from pathlib import Path
base = Path(sys.argv[1])
db = base / "backend" / "blog.db"
# 读取 .env 的 DATABASE_URL(与后端 database.py 的解析方式一致,留空用默认)
try:
for line in (base / ".env").read_text(encoding="utf-8").splitlines():
line = line.strip()
if line.startswith("DATABASE_URL="):
val = line.split("=", 1)[1].strip().strip('"').strip("'")
if val.startswith("sqlite:///"):
p = val[len("sqlite:///"):]
db = Path(p) if p.startswith("/") else base / p
break
except OSError:
pass
print("目标数据库:", db)
con = sqlite3.connect(db)
cur = con.cursor()
def ensure_table(table, ddl):
rows = cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,)).fetchall()
if not rows:
cur.execute(ddl)
print(f"建表:{table}")
else:
print(f"已存在:{table}")
def ensure_column(table, col, ddl):
cols = [r[1] for r in cur.execute(f"PRAGMA table_info({table})").fetchall()]
if col not in cols:
cur.execute(f"ALTER TABLE {table} ADD COLUMN {ddl}")
print(f"迁移:{table}.{col} 已新增")
else:
print(f"已存在:{table}.{col}")
ensure_column("articles", "category", "category VARCHAR(30) NOT NULL DEFAULT 'life'")
ensure_column("users", "token_version", "token_version INTEGER NOT NULL DEFAULT 0")
ensure_column("users", "avatar_updated_time", "avatar_updated_time DATETIME")
ensure_column("friends", "created_time", "created_time DATETIME")
ensure_table("projects", """
CREATE TABLE IF NOT EXISTS projects (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
tech VARCHAR(500),
project_type VARCHAR(20) NOT NULL DEFAULT 'static',
demo_url VARCHAR(500),
download_url VARCHAR(500),
github_url VARCHAR(500),
created_time DATETIME NOT NULL,
updated_time DATETIME NOT NULL
)""")
ensure_column("projects", "visibility", "visibility VARCHAR(20) NOT NULL DEFAULT 'public'")
con.commit()
con.close()
print("数据库迁移完成")
PY
echo "== [4/5] 重启后端服务 =="
$SUDO systemctl restart myblog
sleep 3
if ! $SUDO systemctl is-active --quiet myblog; then
echo "错误:myblog 服务未正常运行,请查看日志:journalctl -u myblog -n 50" >&2
exit 1
fi
echo "myblog 服务运行正常"
echo "== [5/5] 验证 =="
echo "--- 后端接口(应返回 success:true ---"
curl -s http://127.0.0.1:8080/api/article/list | head -c 200
echo
echo "--- 新前端是否生效(发布管理弹窗标记 pub-confirm 数量) ---"
grep -c "pub-confirm" "$BLOG_DIR/frontend/manage.js" || true
if [ -n "$DOMAIN" ]; then
echo "--- 线上 HTTPS$DOMAIN ---"
curl -sk -o /dev/null -w "%{http_code}\n" "https://$DOMAIN/" || echo "HTTPS 检查失败(可能是证书或网络问题)"
fi
echo "更新完成。如需回滚:备份在 $BACKUP_DIR/"