มหาวิทยาลัยมหามกุฏราชวิทยาลัย · ระบบสาธิต (Demo)
ข้ามไปยังเนื้อหา

คู่มือการสำรองและกู้คืนข้อมูล (Backup & Restore Guide)

งวดที่ 3 · ส่งมอบ

ดาวน์โหลด .md

คู่มือการสำรองและกู้คืนข้อมูล (Backup & Restore Guide)

อ้างอิง TOR: ข้อ 4.11.2 (Backup Guide ในชุดเอกสารถ่ายโอนระบบ) และภาคผนวก ก ข้อ 1.6.3 (คู่มือ Backup/Restore + ทดสอบ Restore อย่างน้อย 1 ครั้ง + รายงาน) ฉบับ: 1.0 — วันที่ 3 ตุลาคม พ.ศ. 2569 สคริปต์ที่อ้างถึง: scripts/_db-common.sh, scripts/backup-db.sh, scripts/restore-db.sh, scripts/restore-test.sh รายงานผลทดสอบ: /documents/restore-test-report (สร้างอัตโนมัติด้วย npm run report:restore-test)

หัวข้อ 2–6 ใช้กับระบบสาธิต (SQLite + โฟลเดอร์ไฟล์อัปโหลด) ซึ่งทดสอบแล้ว หัวข้อ 7 เป็นแนวทางสำหรับ Production (PostgreSQL 16 + Object Storage) ซึ่ง ยังไม่ได้ดำเนินการจริง เพราะยังไม่ได้ติดตั้งบนโครงสร้างพื้นฐานของมหาวิทยาลัย


1. ข้อมูลที่ต้องสำรอง

ข้อมูลตำแหน่ง (ระบบสาธิต)สำรองโดยหมายเหตุ
ฐานข้อมูลทั้งหมด (ผู้ใช้ บทสนทนา คลังความรู้ config prompt ผลประเมิน Audit Log ฯลฯ)prisma/dev.db (หรือไฟล์ตาม DATABASE_URL/DB_PATH)backup-db.shใช้ sqlite3 .backup ปลอดภัยแม้แอปกำลังเขียน (โหมด WAL)
ไฟล์อัปโหลดต้นฉบับของฐานองค์ความรู้storage/uploads/ (STORAGE_DIR)backup-db.sh (tar)จำเป็นต่อการ re-index
ไฟล์สำเนาข้อมูลส่วนบุคคล (คำขอ PDPA)โฟลเดอร์ข้าง STORAGE_DIR (storage/privacy-exports)ไม่รวมในสคริปต์สร้างใหม่ได้จากคำขอ; สำรองเพิ่มเองถ้าต้องการ
คีย์เข้ารหัสDATA_ENCRYPTION_KEY, AUTH_SECRET ใน .envต้องเก็บแยกอย่างปลอดภัยหาก DATA_ENCRYPTION_KEY หาย ข้อมูลอีเมล/ชื่อที่เข้ารหัสในไฟล์สำรองจะถอดรหัสไม่ได้
ซอร์สโค้ด/config/prompt ต้นฉบับ/seedGit repositoryระบบควบคุมเวอร์ชัน—
Loglogs/app.logระบบ log ของเครื่อง/คลัสเตอร์ไม่จำเป็นต่อการกู้คืน

2. ความต้องการ

  • bash (รองรับ bash 3.2 บน macOS และ bash 5 บน Debian), sqlite3 (ถ้าไม่มีสคริปต์ออกด้วยรหัส 127 พร้อมคำแนะนำติดตั้ง), tar, เครื่องมือ SHA-256 (sha256sum / shasum / openssl)
  • รันจากโฟลเดอร์ใดก็ได้ สคริปต์ย้ายไปที่รากโปรเจกต์เอง

2.1 การหาไฟล์ฐานข้อมูล (_db-common.sh → resolve_db_path)

ลำดับความสำคัญ:

  1. DB_PATH (สัมพัทธ์กับรากโปรเจกต์)
  2. DATABASE_URL ใน environment
  3. DATABASE_URL ในไฟล์ .env
  4. ค่าเริ่มต้น prisma/dev.db

DATABASE_URL แบบ file:./dev.db ตีความสัมพัทธ์กับโฟลเดอร์ prisma/ (เหมือน Prisma) และตัด query string ออก ถ้า DATABASE_URL ไม่ใช่ file: (เช่น PostgreSQL) สคริปต์ปฏิเสธพร้อมแนะนำให้ใช้ pg_dump/PITR ตามหัวข้อ 7

3. การสำรองข้อมูล (scripts/backup-db.sh)

bash scripts/backup-db.sh
# ตัวอย่างระบุปลายทาง/ฐานข้อมูล
BACKUP_DIR=/mnt/backup/mbu DATABASE_URL=file:./prod.db bash scripts/backup-db.sh

ขั้นตอนที่สคริปต์ทำ:

  1. หาไฟล์ฐานข้อมูล (หัวข้อ 2.1) — ไม่พบไฟล์ = หยุดพร้อมข้อความแนะนำ
  2. ตั้งชื่อไฟล์ตามเวลา YYYYMMDD-HHMMSS (ถ้าซ้ำในวินาทีเดียวกันต่อท้าย -1, -2, …)
  3. sqlite3 <db> ".backup '<BACKUP_DIR>/<ts>.db'"
  4. ตรวจ PRAGMA integrity_check ของไฟล์สำรอง — ไม่ใช่ ok จะลบไฟล์สำรองและออกด้วยข้อผิดพลาด; ลบไฟล์ -wal/-shm ว่างที่ค้าง ให้เหลือไฟล์ .db ไฟล์เดียว
  5. ถ้ามีโฟลเดอร์ไฟล์อัปโหลด สร้าง <ts>-storage.tar (ตั้ง COPYFILE_DISABLE=1 กัน macOS ใส่ไฟล์ ._*)
  6. พิมพ์คำสั่งกู้คืน และ บรรทัดสุดท้ายของ stdout เป็น path ของไฟล์ .db (ให้โปรแกรมอื่นอ่านต่อได้ — หน้า /admin/backup ใช้ค่านี้)
ตัวแปรค่าเริ่มต้นความหมาย
DB_PATH / DATABASE_URLprisma/dev.dbฐานข้อมูลต้นทาง
BACKUP_DIRbackupsโฟลเดอร์ปลายทาง
STORAGE_DIRstorage/uploadsโฟลเดอร์ไฟล์อัปโหลด

ผลลัพธ์: backups/<ts>.db และ backups/<ts>-storage.tar (ถ้ามีไฟล์อัปโหลด)

3.1 สำรองผ่านหน้าเว็บ

SUPER_ADMIN ใช้หน้า /admin/backup → สำรองข้อมูลตอนนี้ (API POST /api/admin/backup เรียก scripts/backup-db.sh, timeout 120 วินาที) หน้าเว็บแสดงรายการไฟล์ในโฟลเดอร์สำรองพร้อมคำสั่งกู้คืนของแต่ละไฟล์ — ไม่มีปุ่มกู้คืนในหน้าเว็บเพราะต้องหยุดแอปก่อน

3.2 กำหนดเวลาสำรองอัตโนมัติ (แนะนำ)

# สำรองทุกวันเวลา 02:00 และเก็บ log
0 2 * * * cd /opt/mbu-buddhist-ai && bash scripts/backup-db.sh >> logs/backup.log 2>&1
# ลบไฟล์สำรองที่เก่ากว่า 30 วัน (ปรับตามนโยบายของมหาวิทยาลัย)
30 2 * * * find /opt/mbu-buddhist-ai/backups -maxdepth 1 -type f -mtime +30 -delete

แนวปฏิบัติ: เก็บสำเนาไว้นอกเครื่อง (อีกเครื่อง/Object Storage) อย่างน้อย 1 ชุด, เข้ารหัสไฟล์สำรองที่ส่งออกนอกเครื่อง (ไฟล์มีข้อมูลบทสนทนา), จำกัดสิทธิ์อ่านโฟลเดอร์ backups/

4. การกู้คืนข้อมูล (scripts/restore-db.sh)

# 1) หยุดแอป (npm run dev / npm start / server.https.js)
# 2) กู้คืน
bash scripts/restore-db.sh backups/20261002-182126.db
# 3) เปิดแอปใหม่ แล้วตรวจที่ /api/health และ /admin/dashboard

ขั้นตอนที่สคริปต์ทำ:

  1. ตรวจอาร์กิวเมนต์ (ไม่มี = รหัสออก 2) และหาไฟล์สำรอง (absolute, สัมพัทธ์กับโฟลเดอร์ที่เรียก หรือสัมพัทธ์กับรากโปรเจกต์)
  2. ปฏิเสธถ้าไฟล์สำรองเป็นไฟล์เดียวกับฐานข้อมูลปลายทาง
  3. ตรวจ PRAGMA integrity_check ของไฟล์สำรองก่อนเขียนทับ — ไม่ผ่าน = ยกเลิก ฐานข้อมูลเดิมไม่ถูกแตะต้อง
  4. คัดลอกทับแบบ atomic (คัดลอกเป็นไฟล์ชั่วคราวในโฟลเดอร์เดียวกันแล้ว mv) และลบ -wal/-shm/-journal ที่ค้าง
  5. ถ้ามี <ชื่อเดียวกัน>-storage.tar แตกไฟล์กลับเข้า STORAGE_DIR (เลือกข้าม/ล้างก่อนได้)
  6. ตรวจ PRAGMA integrity_check หลังกู้คืนและรายงานจำนวนตาราง
ตัวแปรค่าเริ่มต้นความหมาย
DB_PATH / DATABASE_URLprisma/dev.dbฐานข้อมูลปลายทาง
STORAGE_DIRstorage/uploadsโฟลเดอร์ไฟล์อัปโหลดปลายทาง
RESTORE_STORAGE=0(กู้ไฟล์อัปโหลด)กู้เฉพาะฐานข้อมูล
RESTORE_STORAGE_CLEAN=1(ไม่ล้าง)ล้างโฟลเดอร์อัปโหลดก่อนแตกไฟล์ ให้ตรงกับ ณ เวลาสำรองทุกประการ

รหัสออก: 0 สำเร็จ, 1 ผิดพลาด/ไม่ผ่าน integrity_check, 2 ใช้คำสั่งไม่ถูกต้อง

4.1 หลังกู้คืน

  1. เปิดแอปใหม่ — ดัชนีค้นหาในหน่วยความจำสร้างใหม่จากฐานข้อมูลที่กู้คืนเมื่อมีคำขอแรก
  2. ตรวจ GET /api/health (จำนวน chunk ต้องไม่เป็น 0 ถ้ามีคลังความรู้)
  3. ที่ /admin/audit-log กด ตรวจสอบความสมบูรณ์ (hash chain)
  4. ถ้าไฟล์อัปโหลดกู้คืนไม่ครบ ใช้ Re-index ที่ /admin/knowledge-base เพื่อตรวจว่าทุกเอกสารอ่านไฟล์ต้นฉบับได้ (เอกสารที่ไฟล์หายจะล้มเหลวพร้อมข้อความ)
  5. ข้อมูลที่เกิดหลังเวลาสำรองจะหายไป (RPO = ช่วงห่างของการสำรอง) — แจ้งผู้ใช้ตามแผนสื่อสาร

5. การทดสอบการกู้คืน (scripts/restore-test.sh)

npm run report:restore-test      # = bash scripts/restore-test.sh
KEEP_RESTORE_TEST_FILES=1 bash scripts/restore-test.sh   # เก็บไฟล์ทดสอบไว้ตรวจย้อนหลัง

ความปลอดภัย: สคริปต์ไม่แตะฐานข้อมูลจริงและ storage/uploads จริง — อ่านฐานข้อมูลจริงครั้งเดียวเพื่อทำสำเนา (sqlite3 .backup) ไปที่ prisma/restore-test.db และคัดลอกไฟล์อัปโหลดไปโฟลเดอร์ชั่วคราวใต้ backups/.restore-test-* แล้วลบทั้งหมดเมื่อจบ

ขั้นตอน (ทุกขั้นบันทึกผล ok/fail/skip ลงรายงาน):

  1. ทำสำเนาฐานข้อมูลและไฟล์อัปโหลดเพื่อทดสอบ
  2. สำรองด้วย scripts/backup-db.sh
  3. บันทึกจำนวนแถวทุกตาราง + SHA-256 ของเนื้อหาทั้งฐานข้อมูล (.dump)
  4. เพิ่มตาราง marker _restore_test_marker (ข้อมูลที่เกิดหลังการสำรอง) แล้ว ลบไฟล์ฐานข้อมูลและไฟล์อัปโหลด (จำลองความเสียหาย)
  5. กู้คืนด้วย scripts/restore-db.sh (วัดเวลา = RTO ที่วัดได้)
  6. ตรวจ: จำนวนแถวรายตารางตรงกับก่อนสำรอง, PRAGMA integrity_check = ok, ตาราง marker ต้องหายไป, SHA-256 ของ .dump ตรงกัน, จำนวนไฟล์อัปโหลดตรงกัน
  7. เขียน docs/deliverables/restore-test-report.md (frontmatter ของศูนย์เอกสาร, สรุปผล, ตารางรายตาราง, สภาพแวดล้อม)

รหัสออก 0 = ผ่าน, 1 = ไม่ผ่าน (รายงานถูกเขียนทั้งสองกรณี) ตัวแปรเสริม: RESTORE_TEST_DB, BACKUP_DIR, STORAGE_DIR, REPORT_PATH, KEEP_RESTORE_TEST_FILES

ผลการทดสอบล่าสุด ดูที่ /documents/restore-test-report — แนะนำให้ทดสอบซ้ำอย่างน้อยทุกไตรมาส และทุกครั้งที่เปลี่ยน schema หรือโครงสร้างพื้นฐาน

6. ขั้นตอนกู้คืนเหตุฉุกเฉิน (Runbook)

สถานการณ์ขั้นตอน
ไฟล์ฐานข้อมูลเสีย/ลบโดยไม่ตั้งใจหยุดแอป → เลือกไฟล์สำรองล่าสุดที่ผ่าน integrity → restore-db.sh → เปิดแอป → ตรวจตามหัวข้อ 4.1
ข้อมูลผิดจากการตั้งค่า AI/promptไม่ต้องกู้ฐานข้อมูล — ย้อนกลับ snapshot ที่ /admin/ai-config หรือเวอร์ชัน prompt ที่ /admin/prompts
ไฟล์อัปโหลดหายแต่ฐานข้อมูลปกติRESTORE_STORAGE_CLEAN=1 แล้วแตกเฉพาะ tar ด้วย tar -xf backups/<ts>-storage.tar -C storage/uploads หรือกู้คืนพร้อม DB แล้ว Re-index
เครื่องเสียทั้งเครื่องติดตั้งระบบใหม่ตาม /documents/deployment-guide (ไม่ต้อง seed) → ใส่ .env เดิม (คีย์เข้ารหัสเดิม) → คัดลอกไฟล์สำรองจากที่เก็บนอกเครื่อง → restore-db.sh
อัปเกรดแล้ว migration ผิดพลาดกู้จากไฟล์สำรองก่อนอัปเกรด แล้วย้อนแอปเป็นเวอร์ชันเดิม

7. แนวทางสำหรับ Production (PostgreSQL 16 + Object Storage)

สคริปต์ข้างต้นใช้กับ SQLite เท่านั้น สำหรับ Production ตาม Solution Architecture แนะนำ:

องค์ประกอบวิธีสำรองวิธีกู้คืน
PostgreSQL 16สำรองเต็มรายวันด้วย pg_dump -Fc หรือ snapshot ของบริการฐานข้อมูลคลาวด์ + WAL archiving สำหรับ Point-in-Time Recovery (PITR)pg_restore เข้าฐานข้อมูลใหม่ หรือ PITR ไปยังเวลาก่อนเกิดเหตุ แล้วสลับ DATABASE_URL
Object Storage (ไฟล์อัปโหลด)เปิด versioning + replication ข้าม region/บัญชีกู้ object version ก่อนหน้า
Elasticsearchsnapshot repository หรือสร้างดัชนีใหม่จากฐานข้อมูล/ไฟล์ต้นฉบับ (re-index)restore snapshot หรือ reindex_all
Redisไม่จำเป็นต้องกู้ (ข้อมูลชั่วคราว: rate limit, คิว, cache)—
Secret (mbu-buddhist-ai-secrets)เก็บใน secret manager ของคลาวด์ / Sealed Secrets ที่สำรอง key ไว้สร้าง Secret ใหม่จาก secret manager
Helm releasehelm history + Git ของ valueshelm rollback / ติดตั้งใหม่จาก Git

เป้าหมายที่เสนอ (ต้องตกลงกับมหาวิทยาลัย): RPO ≤24 ชั่วโมง (สำรองรายวัน) หรือ ≤15 นาทีเมื่อใช้ PITR, RTO ≤4 ชั่วโมง และทดสอบกู้คืนบน namespace mbu-test อย่างน้อยไตรมาสละครั้ง พร้อมออกรายงานในรูปแบบเดียวกับ /documents/restore-test-report