ข้ามไปยังเนื้อหา

สำรองและกู้คืนข้อมูล

เว็บที่ใช้ TomeCMS เก็บเนื้อหาไว้สองที่ บทความ เพจ การตั้งค่า และข้อมูลการเข้าสู่ระบบอยู่ใน PostgreSQL ส่วนไฟล์ในคลังไฟล์ รวมถึงโลโก้ เป็น object อยู่ใน bucket ของมีเดีย การสำรองแค่ฐานข้อมูลจึงไม่ครบ npm run backup จะคัดลอกทั้งสองที่ในการรันครั้งเดียว โดยทำตอนที่แอปหยุดอยู่ ข้อมูลสองฝั่งจึงตรงกัน

คำสั่งสำรองข้อมูลและคำสั่งตรวจการกู้คืนรันบนเซิร์ฟเวอร์จากโฟลเดอร์โค้ดด้วย Node.js สคริปต์ deploy สร้างแอปไว้ใน Docker และไม่ได้ติดตั้งแพ็กเกจที่สองคำสั่งนี้ต้องใช้ลงในโฟลเดอร์โค้ด จึงต้องติดตั้งเองหนึ่งครั้ง

Terminal window
npm ci

ทั้งสองคำสั่งอ่าน .env.local จากโฟลเดอร์โค้ด คำสั่งสำรองข้อมูลเข้าถึง PostgreSQL ผ่าน Compose และเข้าถึง bucket ผ่าน S3_ENDPOINT ดังนั้นทั้งสอง service และ proxy ของ origin มีเดียต้องทำงานอยู่

หยุดแอปก่อน เพื่อไม่ให้มีอะไรเขียนข้อมูลระหว่างคัดลอก

Terminal window
docker compose -f compose.yaml --env-file .env.local stop app

แล้วสำรองไปไว้ในโฟลเดอร์ที่อยู่นอกโฟลเดอร์โค้ด

Terminal window
npm run backup -- --offline --output-root "$HOME/tomecms-backups"

ต้องใส่ --offline ทุกครั้ง เป็นการยืนยันว่าไม่มีอะไรเขียนข้อมูลอยู่ และคำสั่งจะไม่ยอมเริ่มถ้าคอนเทนเนอร์แอปยังทำงานอยู่ เมื่อเสร็จแล้วจะพิมพ์ว่าชุดสำรองอยู่ที่ไหนและมีข้อมูลเท่าไร

Backup complete: /home/deploy/tomecms-backups/tomecms-20260913T120000000Z
Database records: 42; media objects: 118

ตัวเลขแรกคือจำนวนบทความกับเพจรวมกัน เมื่อเสร็จแล้วให้เปิดแอปอีกครั้ง เว้นแต่คุณกำลังจะอัปเดตต่อ

Terminal window
docker compose -f compose.yaml --env-file .env.local start app

ถ้าใน bucket มี object ที่ TomeCMS ไม่ได้เป็นคนตั้งชื่อ คำสั่งจะหยุดพร้อมข้อความ The media bucket contains an unsupported object key. ให้ย้าย object เหล่านั้นออกจาก bucket แล้วรันใหม่

การรันแต่ละครั้งจะสร้างโฟลเดอร์ใหม่หนึ่งโฟลเดอร์ใต้ output root ตั้งชื่อตามเวลาที่เริ่มรันเป็น UTC เช่น tomecms-20260913T120000000Z ข้างในมี

ไฟล์หรือโฟลเดอร์ คืออะไร
database.dump ฐานข้อมูล tomecms ทั้งหมด dump ด้วย pg_dump ในรูปแบบ custom โดยไม่มีข้อมูล owner และสิทธิ์
objects/ object ทุกตัวใน bucket วางไว้ที่ path เดียวกับ key ของมัน
manifest.json เวอร์ชันของ TomeCMS ที่สร้างชุดสำรอง ที่อยู่ของเว็บ ชื่อฐานข้อมูลและชื่อ bucket จำนวนการตั้งค่า บทความ เพจ และรายการในคลังไฟล์ที่ฐานข้อมูลมีตอนนั้น checksum SHA-256 ของไฟล์ dump และ checksum, content type และขนาดของ object แต่ละตัว

คำสั่งเขียน manifest.json เป็นไฟล์สุดท้าย ถ้าโฟลเดอร์ไหนไม่มีไฟล์นี้ แปลว่าการสำรองครั้งนั้นล้มเหลวหรือถูกขัดจังหวะ ห้ามนำไปกู้คืน

--database-only จะไม่สำรอง bucket โฟลเดอร์จะไม่มี objects/ และ manifest จะมี "scope": "database" โดยไม่มีรายการไฟล์ ตัวอัปเดตใช้แบบนี้ก่อนการอัปเดตที่ไม่มี migration ตั้งแต่ 1.3.0 เพราะการอัปเดตแบบนั้นไม่เปลี่ยนตารางและไม่ได้คาดว่าจะเขียนไฟล์ใหม่ ถ้าจะกู้คืนจากชุดแบบนี้ ให้กู้ dump แล้วปล่อย bucket ไว้อย่างเดิม คำสั่งตรวจชุดสำรองใช้ได้กับทั้งสองแบบ และบอกว่าตรวจแบบไหน

ชุดสำรองไม่ได้เก็บค่าลับใน .env.local ไว้เลย เว็บที่กู้คืนจากชุดสำรองต้องใช้ค่าลับชุดเดิม จึงต้องเก็บสำเนา .env.local ไว้ในที่ปลอดภัย แยกจากชุดสำรอง ตามที่อธิบายไว้ในหน้าการตั้งค่า แต่ไฟล์ dump ยังมีทุกอย่างในฐานข้อมูล รวมถึงข้อมูลการเข้าสู่ระบบ จึงต้องเก็บชุดสำรองให้มิดชิดเท่ากับไฟล์นั้น

--output-root ต้องเป็น absolute path ที่อยู่นอกโฟลเดอร์โค้ด คำสั่งไม่รับ path แบบ relative, path ในโฟลเดอร์โค้ด และ root ของระบบไฟล์ ถ้าโฟลเดอร์ยังไม่มี คำสั่งจะสร้างให้ โฟลเดอร์ในชุดสำรองตั้งสิทธิ์ให้เจ้าของเข้าถึงได้คนเดียว (0700) และไฟล์ก็เช่นกัน (0600)

ชุดสำรองที่อยู่บนดิสก์ของเซิร์ฟเวอร์เองจะหายไปพร้อมดิสก์นั้น จึงควรคัดลอกแต่ละชุดไปเก็บไว้ที่เครื่องอื่น และเพราะแต่ละชุดคัดลอกทั้งฐานข้อมูลและทุกไฟล์ใน bucket ซ้ำอีกรอบ ให้เผื่อพื้นที่ดิสก์ตามจำนวนชุดที่เก็บไว้บนเซิร์ฟเวอร์

ตรวจชุดสำรองทุกชุดก่อนนำไปใช้จริง คำสั่งตรวจการกู้คืนจะกู้ชุดสำรองลงใน Compose project แยกที่ใช้แล้วทิ้ง แล้วเทียบผลกับ manifest

Terminal window
npm run restore:check -- \
--backup /home/deploy/tomecms-backups/tomecms-20260913T120000000Z \
--project tomecms-restore-check-20260913

--backup คือ absolute path ของชุดสำรอง --project คือชื่อ project ที่ใช้แล้วทิ้ง ต้องขึ้นต้นด้วย tomecms-restore-check- แล้วตามด้วยตัวพิมพ์เล็ก ตัวเลข หรือขีดกลางไม่เกิน 40 ตัว โดยขึ้นต้นและลงท้ายด้วยตัวอักษรหรือตัวเลข ใช้ชื่อใหม่ทุกครั้ง เพราะคำสั่งจะไม่ยอมใช้ project ที่มีคอนเทนเนอร์อยู่แล้ว และจะไม่แตะ project ของเว็บจริงเลย

คำสั่งทำงานตามลำดับนี้

  1. ตรวจไฟล์ dump และ object ทุกตัวกับ checksum ใน manifest
  2. เปิด PostgreSQL และ SeaweedFS ชุดของตัวเองที่พอร์ต 55432 และ 59000 บน 127.0.0.1 ระหว่างที่คำสั่งทำงานต้องปล่อยสองพอร์ตนี้ให้ว่าง
  3. กู้ไฟล์ dump ลงใน PostgreSQL ชุดนั้น
  4. ใส่ object ทุกตัวกลับเข้าไปพร้อม content type เอกสารแต่ละไฟล์จะได้ header Content-Disposition คืนมาด้วย ซึ่งเป็น header ที่กำหนดชื่อไฟล์ตอนดาวน์โหลด และกำหนดว่า PDF จะเปิดในเบราว์เซอร์หรือไม่ header นี้ติดอยู่กับ object ใน bucket และชุดสำรองไม่ได้เก็บไว้ คำสั่งจึงสร้างขึ้นใหม่จากแถวของเอกสารนั้นใน media_items
  5. เทียบจำนวนการตั้งค่า บทความ เพจ และรายการในคลังไฟล์ที่กู้คืนได้กับตัวเลขใน manifest และเทียบ object ที่กู้คืนได้กับรายการใน manifest
  6. ลบคอนเทนเนอร์และ volume ที่ใช้แล้วทิ้งออก ไม่ว่าการตรวจจะผ่านหรือไม่

ชุดสำรองที่ผ่านจะจบด้วย Restore verified in disposable project tomecms-restore-check-20260913. ถ้าไม่ผ่าน บรรทัดสุดท้ายจะขึ้นต้นด้วย Error: เช่น Database dump checksum does not match its manifest. หรือ Restored object inventory does not match the backup. อย่าใช้ชุดสำรองที่ตรวจไม่ผ่าน

ตอนนี้ TomeCMS ยังไม่มีคำสั่งที่กู้ชุดสำรองกลับเข้าเว็บจริง คำสั่งตรวจการกู้คืนแสดงให้เห็นทุกขั้น การกู้คืนเองต้องทำแบบเดียวกันกับ service ของเว็บจริง โดยหยุดแอปไว้ก่อน

  • กู้ database.dump ลงใน PostgreSQL ของเว็บด้วย pg_restore
  • ใส่ทุกไฟล์ใต้ objects/ กลับเข้า bucket ที่ key เดิม พร้อม content type ตามที่ manifest บันทึกไว้
  • ใส่ header Content-Disposition ให้เอกสารทุกไฟล์ โดยสร้างแบบเดียวกับที่ contentDisposition ใน src/server/media/disposition.ts สร้างจากชื่อและชนิดของเอกสารใน media_items ถ้าไม่มี header นี้ เอกสารจะไม่ได้ดาวน์โหลดด้วยชื่อของมันเอง
  • เปิดแอปด้วย .env.local ชุดที่เว็บใช้อยู่ตอนสำรองข้อมูล

ลองกู้คืนบนเซิร์ฟเวอร์สำรองก่อนถึงวันที่ต้องใช้จริง และเก็บข้อมูลเดิมไว้จนกว่าเว็บที่กู้คืนแล้วจะใช้งานได้