diff --git a/CLAUDE.md b/CLAUDE.md index b888ff0..58d906d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - [`hospital_media_antmedia.md`](hospital_media_antmedia.md) — 主機完整運維筆記(容器一覽、Caddy、Ant Media、SSL 憑證、備份) - [`xensor_cydroned_edr.md`](xensor_cydroned_edr.md) — CyCraft Xensor EDR agent(`cydroned`)安裝方法、連線跳板與現況 +- [`nfs_backup/`](nfs_backup/) — 每日 NFS 自動備份腳本(`backup_all_to_nfs.sh`)與備份內容/還原說明(`README.md`) ## 伺服器操作 diff --git a/nfs_backup/README.md b/nfs_backup/README.md new file mode 100644 index 0000000..f87518a --- /dev/null +++ b/nfs_backup/README.md @@ -0,0 +1,105 @@ +# 備份說明 — 主機 106.105.181.40(hospital_media) + +本目錄存放主機 `106.105.181.40` 各服務的**每日自動備份**。 +備份來源主機 SSH 別名:`hospital_media`。 + +- NFS 來源:`hdsnas02.tpech.gov.tw:/TEL_BACKUP_NFS` +- 主機掛載點:`/mnt/tel_backup_nfs`(本目錄即 `/mnt/tel_backup_nfs/106_105_181_40/`) +- 備份腳本:主機上 `/opt/backup_all_to_nfs.sh` +- 排程:`/etc/cron.d/backup-nfs`,每天 **03:30** 以 root 執行 +- 保留天數:**14 天**(更舊的自動刪除,可在腳本 `RETAIN_DAYS` 調整) +- 紀錄檔:本目錄 `backup.log` + +## 目錄結構 + +``` +106_105_181_40/ +├── README.md # 本文件 +├── backup.log # 每次備份的紀錄 +└── YYYYMMDD_HHMMSS/ # 每天一個資料夾(備份時間戳) + ├── caddy.tar.gz + ├── php.tar.gz + ├── php-staging.tar.gz + ├── phpmyadmin.tar.gz + ├── coturn.tar.gz + ├── antmedia_volume.tar.gz + └── mysql_all_databases.sql.gz +``` + +## 各檔案備份的是什麼 + +| 檔案 | 內容 | 主機來源 | 對應容器 | +|------|------|----------|----------| +| `caddy.tar.gz` | Caddy 設定、SSL 憑證、`tel-server` 靜態檔 | `/opt/caddy` | `caddy` | +| `php.tar.gz` | 正式環境 PHP 程式碼 | `/opt/php`(網站根:`www/`) | `php` | +| `php-staging.tar.gz` | 測試環境 PHP 程式碼 | `/opt/php-staging` | `php-staging` | +| `phpmyadmin.tar.gz` | phpMyAdmin compose 設定(無資料,stateless) | `/opt/phpmyadmin` | `phpmyadmin` | +| `coturn.tar.gz` | TURN/STUN 設定 `turnserver.conf` | `/opt/coturn` | `coturn` | +| `antmedia_volume.tar.gz` | Ant Media Server **實際資料卷**(串流設定、應用、錄影等) | docker volume `antmedia_volume`(容器內 `/usr/local/antmedia`) | `antmedia` | +| `mysql_all_databases.sql.gz` | **所有資料庫**的邏輯匯出(含 `tpech_tel`、`myapp` 等) | `mysqldump --all-databases` | `mysql-container` | + +> 注意:NAS 啟用 root_squash,檔案 owner 會顯示 `nobody:nogroup`,屬正常現象。 + +--- + +# 還原方法 + +> 以下指令都在主機 `106.105.181.40` 上以 root(`sudo`)執行。 +> 先設定要還原的備份時間戳,例如: +> ```bash +> BK=/mnt/tel_backup_nfs/106_105_181_40/20260629_142059 +> ``` + +## 1. 檔案型服務(Caddy / PHP / PHP-staging / phpMyAdmin / Coturn) + +解壓回 `/opt`(會覆蓋現有檔案),再重啟容器: + +```bash +# 範例:還原 Caddy +sudo tar xzf "$BK/caddy.tar.gz" -C /opt +cd /opt/caddy && ./restart.sh # 或 docker restart caddy + +# 其餘服務同理(換檔名與容器名): +sudo tar xzf "$BK/php.tar.gz" -C /opt && docker restart php +sudo tar xzf "$BK/php-staging.tar.gz" -C /opt && docker restart php-staging +sudo tar xzf "$BK/phpmyadmin.tar.gz" -C /opt && docker restart phpmyadmin +sudo tar xzf "$BK/coturn.tar.gz" -C /opt && docker restart coturn +``` + +## 2. Ant Media Server(資料卷) + +必須先停容器,清空舊資料卷再解壓,避免殘留檔案: + +```bash +docker stop antmedia +VOL=/var/lib/docker/volumes/antmedia_volume/_data +sudo rm -rf "$VOL"/* # 清空(確認 BK 正確再執行!) +sudo tar xzf "$BK/antmedia_volume.tar.gz" -C "$VOL" +docker start antmedia +``` + +## 3. MySQL(所有資料庫) + +把 dump 灌回執行中的容器(會覆蓋同名資料庫): + +```bash +zcat "$BK/mysql_all_databases.sql.gz" | \ + docker exec -i mysql-container sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' +``` + +只還原單一資料庫: + +```bash +# 先從整包過濾出該 DB(或用 sed 擷取對應段落),簡單作法是全部灌回後只保留需要的 +zcat "$BK/mysql_all_databases.sql.gz" | \ + docker exec -i mysql-container sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" tpech_tel' +# ↑ 注意:dump 為 --all-databases 格式,含 USE 語句;指定單庫還原前請確認段落, +# 或改用 mysqldump 重新針對單一資料庫匯出。 +``` + +## 還原後檢查 + +- 容器狀態:`docker ps` +- Caddy 設定語法:`docker exec caddy caddy validate --config /etc/caddy/Caddyfile` +- MySQL:`docker exec -it mysql-container mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e 'SHOW DATABASES;'` +- Ant Media:瀏覽器開管理介面確認應用與設定 diff --git a/nfs_backup/backup_all_to_nfs.sh b/nfs_backup/backup_all_to_nfs.sh new file mode 100755 index 0000000..c23ce2b --- /dev/null +++ b/nfs_backup/backup_all_to_nfs.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# 每日備份主機 106.105.181.40 各服務到 NAS NFS(hdsnas02:/TEL_BACKUP_NFS) +# 由 cron 以 root 執行(見 /etc/cron.d/backup-nfs) +# 手動執行:sudo /opt/backup_all_to_nfs.sh +set -u + +NFS=/mnt/tel_backup_nfs +BASE=$NFS/106_105_181_40 +RETAIN_DAYS=14 # 保留天數,超過即刪除 +TS=$(date +%Y%m%d_%H%M%S) +DEST=$BASE/$TS +LOG=$BASE/backup.log + +log(){ echo "[$(date '+%F %T')] $*" | tee -a "$LOG"; } + +# 安全檢查:NFS 一定要掛載,否則中止(避免寫進本機根磁碟把它撐爆) +if ! mountpoint -q "$NFS"; then + echo "[$(date '+%F %T')] ERROR: $NFS 未掛載,中止備份" | tee -a "$LOG" >&2 + exit 1 +fi + +mkdir -p "$DEST" +log "===== 備份開始 -> $DEST =====" +ok=0; fail=0 + +# do_tar <名稱> <來源上層目錄> <項目> +do_tar(){ + if tar czf "$DEST/$1.tar.gz" -C "$2" "$3" 2>"$DEST/$1.err"; then + log "$1 OK $(du -h "$DEST/$1.tar.gz" | cut -f1)"; rm -f "$DEST/$1.err"; ok=$((ok+1)) + else + log "$1 FAIL: $(cat "$DEST/$1.err")"; fail=$((fail+1)) + fi +} + +do_tar caddy /opt caddy +do_tar php /opt php +do_tar php-staging /opt php-staging +do_tar phpmyadmin /opt phpmyadmin +do_tar coturn /opt coturn +do_tar antmedia_volume /var/lib/docker/volumes/antmedia_volume/_data . + +# MySQL:邏輯備份(--single-transaction = InnoDB 一致性快照,不鎖表) +# 密碼從容器內環境變數取,不出現在主機 process list +if docker exec mysql-container sh -c \ + 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases --single-transaction --routines --triggers --events' \ + 2>"$DEST/mysql.err" | gzip > "$DEST/mysql_all_databases.sql.gz" \ + && [ -s "$DEST/mysql_all_databases.sql.gz" ]; then + log "mysql OK $(du -h "$DEST/mysql_all_databases.sql.gz" | cut -f1)"; rm -f "$DEST/mysql.err"; ok=$((ok+1)) +else + log "mysql FAIL: $(cat "$DEST/mysql.err" 2>/dev/null)"; fail=$((fail+1)) +fi + +log "===== 完成:成功 $ok / 失敗 $fail,本次大小 $(du -sh "$DEST" | cut -f1) =====" + +# 保留天數內,清掉更舊的備份 +find "$BASE" -maxdepth 1 -type d -name '20*' -mtime +"$RETAIN_DAYS" -exec rm -rf {} \; 2>/dev/null +log "===== 已清理超過 ${RETAIN_DAYS} 天的舊備份 =====" + +[ "$fail" -eq 0 ] || exit 1