From f73e401748f43a4819e27932879bc45fb23a0230 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 16 Mar 2026 18:52:51 +0800 Subject: [PATCH] Add automated install and stable updates --- install.sh | 132 ++++++++++++++++++++++++++++++ readme.md | 105 ++++++++++++++++++++++++ systemd/backup-obs-update.service | 10 +++ systemd/backup-obs-update.timer | 11 +++ update.sh | 56 +++++++++++++ 5 files changed, 314 insertions(+) create mode 100755 install.sh create mode 100644 systemd/backup-obs-update.service create mode 100644 systemd/backup-obs-update.timer create mode 100755 update.sh diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9f8f2e5 --- /dev/null +++ b/install.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +SCRIPT_PATH="${BASH_SOURCE[0]:-$0}" +SCRIPT_DIR="$(cd "$(dirname -- "$SCRIPT_PATH")" && pwd -P)" +INSTALL_MODE="system" +INSTALL_USER="root" +SYSTEMD_DIR="/etc/systemd/system" +DRY_RUN=false + +usage() { + cat <<'USAGE' +用法: + ./install.sh [--user=NAME] [--mode=system|user] [--dry-run] + +说明: + - system 模式会安装到 /etc/systemd/system + - user 模式会安装到 ~/.config/systemd/user + - 不会覆盖已有 env.conf 和 exclude.user.list + - dry-run 只打印将执行的动作,不实际写入 systemd +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --user=*) INSTALL_USER="${1#*=}"; shift;; + --mode=*) INSTALL_MODE="${1#*=}"; shift;; + --dry-run) DRY_RUN=true; shift;; + -h|--help) usage; exit 0;; + *) echo "未知参数:$1" >&2; usage; exit 2;; + esac +done + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "缺少命令:$1" >&2 + exit 2 + } +} + +need_cmd systemctl +need_cmd install +need_cmd sed + +if [[ "$INSTALL_MODE" != "system" && "$INSTALL_MODE" != "user" ]]; then + echo "无效 --mode:$INSTALL_MODE" >&2 + exit 2 +fi + +if [[ "$INSTALL_MODE" == "system" && "$(id -u)" -ne 0 ]]; then + echo "system 模式需要 root 权限" >&2 + exit 2 +fi + +if [[ "$INSTALL_MODE" == "user" ]]; then + HOME_DIR="$(getent passwd "$INSTALL_USER" | cut -d: -f6)" + if [[ -z "$HOME_DIR" ]]; then + echo "无法找到用户:$INSTALL_USER" >&2 + exit 2 + fi + SYSTEMD_DIR="${HOME_DIR}/.config/systemd/user" +fi + +mkdir -p "${SCRIPT_DIR}/systemd" + +if [[ ! -f "${SCRIPT_DIR}/env.conf" ]]; then + if $DRY_RUN; then + echo "[dry-run] 将创建 ${SCRIPT_DIR}/env.conf" + else + cp "${SCRIPT_DIR}/env.conf.example" "${SCRIPT_DIR}/env.conf" + echo "已创建 ${SCRIPT_DIR}/env.conf" + fi +fi + +if [[ ! -f "${SCRIPT_DIR}/exclude.user.list" ]]; then + if $DRY_RUN; then + echo "[dry-run] 将创建 ${SCRIPT_DIR}/exclude.user.list" + else + cp "${SCRIPT_DIR}/exclude.user.list.example" "${SCRIPT_DIR}/exclude.user.list" + echo "已创建 ${SCRIPT_DIR}/exclude.user.list" + fi +fi + +chmod 755 \ + "${SCRIPT_DIR}/backup.sh" \ + "${SCRIPT_DIR}/clean_backups.sh" \ + "${SCRIPT_DIR}/lib/telegram.sh" \ + "${SCRIPT_DIR}/install.sh" \ + "${SCRIPT_DIR}/update.sh" + +if $DRY_RUN; then + echo "[dry-run] 目标 systemd 目录:$SYSTEMD_DIR" +else + mkdir -p "$SYSTEMD_DIR" +fi + +render_unit() { + local src="$1" + local dst="$2" + if $DRY_RUN; then + echo "[dry-run] 将渲染 $(basename "$src") -> $dst" + return 0 + fi + sed \ + -e "s|__WORKDIR__|${SCRIPT_DIR}|g" \ + -e "s|__RUN_USER__|${INSTALL_USER}|g" \ + "$src" > "$dst" + + if [[ "$INSTALL_MODE" == "user" ]]; then + sed -i '/^User=__RUN_USER__$/d;/^User=/d' "$dst" + fi +} + +render_unit "${SCRIPT_DIR}/systemd/backup-obs-update.service" "${SYSTEMD_DIR}/backup-obs-update.service" +render_unit "${SCRIPT_DIR}/systemd/backup-obs-update.timer" "${SYSTEMD_DIR}/backup-obs-update.timer" + +if $DRY_RUN; then + echo "[dry-run] 将执行 systemctl daemon-reload" + echo "[dry-run] 将启用 backup-obs-update.timer" +elif [[ "$INSTALL_MODE" == "system" ]]; then + systemctl daemon-reload + systemctl enable --now backup-obs-update.timer + echo "已启用 system timer:backup-obs-update.timer" + echo "查看状态:systemctl status backup-obs-update.timer" + echo "查看日志:journalctl -u backup-obs-update.service -n 100" +else + systemctl --user daemon-reload + systemctl --user enable --now backup-obs-update.timer + echo "已启用 user timer:backup-obs-update.timer" + echo "查看状态:systemctl --user status backup-obs-update.timer" + echo "查看日志:journalctl --user -u backup-obs-update.service -n 100" +fi diff --git a/readme.md b/readme.md index f10ec47..a1fad83 100644 --- a/readme.md +++ b/readme.md @@ -61,6 +61,111 @@ EOF ./clean_backups.sh --retain-days=5 ``` +## 首次部署 + +新机器首次部署推荐流程: + +```bash +git clone <你的仓库地址> /root/backup_OBS +cd /root/backup_OBS +./install.sh +``` + +`install.sh` 会自动完成: + +- 如果不存在,则创建 `env.conf` +- 如果不存在,则创建 `exclude.user.list` +- 修正脚本执行权限 +- 安装 `systemd` 更新任务 +- 启用 `backup-obs-update.timer` + +默认使用 system 模式,也就是安装到 `/etc/systemd/system`。 + +可选参数: + +```bash +./install.sh --mode=system +./install.sh --mode=user --user=root +``` + +说明: + +- `system` 模式需要 root 权限 +- `user` 模式会安装到 `~/.config/systemd/user` +- 安装脚本不会覆盖已有的 `env.conf` 和 `exclude.user.list` + +常用查看命令: + +```bash +systemctl status backup-obs-update.timer +journalctl -u backup-obs-update.service -n 100 +``` + +## 自动更新 + +仓库内提供了一个统一更新脚本: + +```bash +./update.sh +``` + +它会执行: + +- `git fetch --tags origin` +- 默认更新 `stable` 分支,或切换到你指定的 tag / 分支 +- 修正脚本执行权限 +- 自动执行一次 `./backup.sh --dry-run` 自检 + +也可以手动指定版本: + +```bash +./update.sh +./update.sh stable +./update.sh main +./update.sh v1.2.0 +``` + +配合 `systemd timer` 后,机器会定时自动执行 `update.sh`。 + +## GitHub 多机发布流程 + +推荐流程: + +1. 在一台测试机或开发环境修改脚本并提交到 GitHub。 +2. 在测试机手动运行: + - `./update.sh main` + - `./backup.sh --dry-run` + - `./backup.sh` + - `./clean_backups.sh --retain-days=100000` +3. 确认备份、清理、Telegram 通知都正常。 +4. 打稳定版本标签,例如: + +```bash +git tag v1.2.0 +git push origin v1.2.0 +``` + +5. 生产机器可以手动执行: + +```bash +./update.sh v1.2.0 +``` + +或者让 `systemd` 定时器长期跟随 `stable` 分支。 + +更稳的做法是: + +- `main` 用来持续迭代 +- `stable` 用来给其他机器自动更新 +- tag 用来留档和回滚 + +推荐的发布方式: + +1. 日常修改提交到 `main` +2. 测试机验证 `main` +3. 验证通过后,把 `main` 合并到 `stable` +4. 其他机器的 `systemd timer` 自动执行 `./update.sh stable` + ## 排除规则 脚本会同时读取两个排除文件: diff --git a/systemd/backup-obs-update.service b/systemd/backup-obs-update.service new file mode 100644 index 0000000..12e8a9d --- /dev/null +++ b/systemd/backup-obs-update.service @@ -0,0 +1,10 @@ +[Unit] +Description=Update backup_OBS scripts from Git +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +User=__RUN_USER__ +WorkingDirectory=__WORKDIR__ +ExecStart=__WORKDIR__/update.sh stable diff --git a/systemd/backup-obs-update.timer b/systemd/backup-obs-update.timer new file mode 100644 index 0000000..1679306 --- /dev/null +++ b/systemd/backup-obs-update.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run backup_OBS updater periodically + +[Timer] +OnBootSec=10m +OnUnitActiveSec=1h +Persistent=true +Unit=backup-obs-update.service + +[Install] +WantedBy=timers.target diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..aab0794 --- /dev/null +++ b/update.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +SCRIPT_PATH="${BASH_SOURCE[0]:-$0}" +SCRIPT_DIR="$(cd "$(dirname -- "$SCRIPT_PATH")" && pwd -P)" +cd "$SCRIPT_DIR" + +usage() { + cat <<'USAGE' +用法: + ./update.sh [branch-or-tag] + +说明: + - 不带参数时,默认更新 stable 分支 + - 带参数时,可以切换到指定分支或 tag + - 更新后会自动执行一次 ./backup.sh --dry-run 自检 +USAGE +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "缺少命令:$1" >&2 + exit 2 + } +} + +need_cmd git +need_cmd bash + +TARGET_REF="${1:-}" +if [[ -z "$TARGET_REF" ]]; then + TARGET_REF="stable" +fi + +git fetch --tags origin + +if git show-ref --verify --quiet "refs/tags/${TARGET_REF}"; then + git checkout "${TARGET_REF}" +else + git checkout "${TARGET_REF}" + git pull --ff-only origin "${TARGET_REF}" +fi + +chmod 755 \ + "${SCRIPT_DIR}/backup.sh" \ + "${SCRIPT_DIR}/clean_backups.sh" \ + "${SCRIPT_DIR}/lib/telegram.sh" \ + "${SCRIPT_DIR}/install.sh" \ + "${SCRIPT_DIR}/update.sh" + +bash "${SCRIPT_DIR}/backup.sh" --dry-run