บล็อกของฉัน // คลังเก็บข้อมูลดิจิทัล

ความคิด โปรเจกต์ และบันทึกทางเทคนิค

ตอนนี้ผมได้เปลี่ยนระบบสำรองข้อมูล (Backup) ของโฮมเซิร์ฟเวอร์ NixOS จากเดิมที่ใช้สคริปต์ง่ายๆ รายวัน มาเป็นแบบรายชั่วโมงแล้ว อาจจะไม่หรูหราเท่ากับอินสแตนซ์ Mastodon ที่เพื่อนของผมดูแล ซึ่งที่นั่นมีการทำ ZFS Snapshot ทุกๆ 20 นาที แต่สำหรับโซลูชันนี้ผมก็พอใจแล้ว และต้องบอกเลยว่าผมเริ่มชอบ NixOS มากขึ้นเรื่อยๆ ถ้าไม่มี AI มันคงจะซับซ้อนเกินกว่าที่ผมจะเข้าใจได้ แต่ตอนนี้อย่างน้อยผมก็สามารถอ่านไฟล์ Nix ที่เสร็จสมบูรณ์ได้แล้ว เคยคิดจะเปลี่ยนเซิร์ฟเวอร์ไปใช้ Linux ทั่วไปอยู่เหมือนกัน แต่ตราบใดที่มันยังทำงานได้ดีขนาดนี้...

  1. /etc/nixos: การตั้งค่า NixOS ทั้งหมด (ไฟล์ .nix ทั้งหมด)

  2. /opt/minecraft: โลกและข้อมูลทั้งหมดของเซิร์ฟเวอร์ Minecraft

  3. /opt/satisfactory: ไฟล์เซฟเกมและการตั้งค่าของเซิร์ฟเวอร์ Satisfactory

  4. /opt/Blog: ซอร์สโค้ดและเนื้อหาของบล็อก

  5. /opt/ui: ไฟล์ Mastodon-UI

  6. /opt/audiobookshelf: หนังสือเสียงและฐานข้อมูลที่เกี่ยวข้อง

ข้อมูลทั้งหมดจะถูกเก็บไว้ใน S3 Storage ที่เมานต์ผ่าน RClone

การตั้งค่า NixOS


{ config, pkgs, ... }:

{
  # การตั้งค่า BorgBackup
  services.borgbackup.jobs.hourly = {
    # พาธไปยัง Repository ใน S3-mount
    repo = "/opt/S3/Backup/NixOSHomeServer_Borg";

    # ไม่ต้องใช้รหัสผ่าน
    encryption.mode = "none";

    # การบีบอัดข้อมูลเพื่อประหยัดพื้นที่มากขึ้น
    compression = "auto,zstd";

    # พาธที่ต้องการสำรองข้อมูล
    paths = [
      "/etc/nixos"
      "/opt/minecraft"
      "/opt/satisfactory"
      "/opt/Blog"
      "/opt/ui"
      "/opt/audiobookshelf"
    ];

    # ทำงานทุกชั่วโมง
    startAt = "hourly";

    # เริ่มต้น Repository โดยอัตโนมัติหากยังไม่มี
    doInit = true;

    # กฎการเก็บรักษาข้อมูล (Pruning)
    prune.keep = {
      within = "1d"; # Snapshot ทั้งหมดในช่วง 24 ชั่วโมงที่ผ่านมา
      daily = 7;
      weekly = 4;
      monthly = 12;
    };
  };

  # ตรวจสอบให้แน่ใจว่า S3-mount พร้อมใช้งานก่อนที่ Borg จะเริ่มทำงาน
  systemd.services.borgbackup-job-hourly = {
    requires = [ "rclone-s3-mount.service" ];
    after = [ "rclone-s3-mount.service" ];
  };
}

เดิมทีการตั้งค่าบล็อกของผมถูกวางแผนให้เป็น โปรเจกต์ IPv6 ล้วนผ่าน WireGuard เนื่องจากรันอยู่บนโฮมเซิร์ฟเวอร์ (คุณสามารถขอที่อยู่ IPv6 ได้ฟรีที่ route64.org) เพื่อเพิ่มความสามารถในการเข้าถึง ตอนนี้ผมได้เพิ่ม IPv4 Proxy ภายนอกเข้ามาแล้ว (ขอบคุณ @Larvitz)

อย่างไรก็ตาม มีปัญหาเรื่อง SSL เกิดขึ้นทันที: เนื่องจากในตอนแรกทั้งเรคคอร์ด A และ AAAA วิ่งผ่าน Proxy ทำให้การตรวจสอบความถูกต้อง (Validation) ของ Let's Encrypt บนเซิร์ฟเวอร์ของผมล้มเหลว

วิธีแก้ปัญหา: "IPv6-Hack"

วิธีแก้คือการกำหนดให้ AAAA-Record ชี้ไปยัง IP ของ WireGuard บนเซิร์ฟเวอร์ของผมโดยตรง แทนที่จะส่งผ่าน Proxy เหมือนเดิม

  • โดเมน: blog.burningboard.org
  • A-Record (Proxy): 194.28.98.217
  • AAAA-Record (Server): 2a11:6c7:f05:a8::2 (WireGuard)

ด้วยการชี้ AAAA-Record ไปยัง IP ของ WireGuard โดยตรง Let’s Encrypt จะยังคงเข้าถึงเซิร์ฟเวอร์ของผมได้โดยตรงผ่าน IPv6 (เนื่องจากปกติแล้ว AAAA-Record จะได้รับความสำคัญก่อน) และออกใบรับรอง SSL ให้ ส่วนทราฟฟิก IPv4 จะถูกส่งต่อจาก Proxy มายังผมในรูปแบบที่เข้ารหัสไว้

การกำหนดค่าขั้นสุดท้าย

เพื่อให้การสื่อสารทำงานได้อย่างราบรื่น เราจำเป็นต้องปรับแต่งเซิร์ฟเวอร์ Caddy:

1. บนเซิร์ฟเวอร์ของผม (NixOS, blog.nix)

เพื่อให้ IP จริงของผู้เข้าชมส่งมาถึงอย่างถูกต้องและไม่ถูกเขียนทับด้วย IP ของ Proxy เราต้องกำหนดให้ Proxy นั้นเป็นที่น่าเชื่อถือ (trusted):

services.caddy.globalConfig = ''
  servers {
      trusted_proxies static 2a06:9801:1c:1000::10
  }
'';

2. บน Proxy ภายนอก (Caddy)

เพื่อให้ Proxy ติดต่อกับเซิร์ฟเวอร์ของผมผ่าน HTTPS ได้อย่างถูกต้อง จะต้องส่งชื่อโฮสต์ (SNI) ไปด้วย:

reverse_proxy [https://[2a11:6c7:f05:a8::2]:443](https://[2a11:6c7:f05:a8::2]:443) {
    header_up Host {host}
    transport http {
        tls_server_name blog.burningboard.org
    }
}

ตอนนี้บล็อกสามารถเข้าถึงได้ทั้งผ่าน IPv4 และ IPv6 มีการเข้ารหัสที่ปลอดภัย และ IP ที่บ้านของผมก็ยังคงเป็นส่วนตัวอยู่! 🚀

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

ผมได้ปรับแต่งการตั้งค่าไปหลายอย่าง:

📂 ไฟล์ MD: โครงสร้างบล็อกยังคงเรียบง่ายบนพื้นฐานของ Markdown

🌍 เป็นสากลยิ่งกว่าที่เคย: ตอนนี้บล็อกของผมรองรับการแปลถึง 43 ภาษา ใช่แล้ว รวมภาษาคลิงออนด้วย! 🖖 (Qapla'!)

เดิมทีผมวางแผนจะให้มีการแปลแบบเรียลไทม์อัตโนมัติทั้งหมดโดยอิงจากการตรวจจับภาษาของเบราว์เซอร์ สปอยล์เลยครับ: มันทำงานได้แค่บางส่วนเท่านั้น จะเห็นได้ว่า: AI นั้นน่าทึ่งมาก แต่ก็ยังไม่ถึงจุดที่เราต้องการให้เป็นเสียทีเดียว

วิธีแก้ปัญหา: ตอนนี้ผมใช้วิธีแปลทุกบทความล่วงหน้าเป็น ทุก ภาษาที่กำหนดไว้ ซึ่งส่งผลดีต่อการทำอันดับในเครื่องมือค้นหา (SEO) มากกว่าด้วย หากการตรวจจับอัตโนมัติไม่ทำงาน คุณสามารถเลือกภาษาที่ต้องการได้ด้วยตนเองผ่านไอคอนรูปโลก ซึ่งจะถูกบันทึกไว้ผ่านคุกกี้อย่างง่ายดาย

ตอนนี้การแปลดำเนินการด้วย Gemini 3 Flash ซึ่งให้ผลลัพธ์ที่ดีอย่างน่าประหลาดใจ อย่างไรก็ตาม เรายังต้องคอยตรวจสอบการทำงานของ AI อย่างใกล้ชิด: ในการรันแบบกลุ่ม (Bulk) ครั้งแรก แท็กต่างๆ ถูกแปลไปด้วยโดยผิดพลาด ซึ่งแน่นอนว่าไม่ได้วางแผนไว้แบบนั้น

โค้ดยังคงมีให้ใช้งาน (ถ้าสนใจก็ส่งข้อความมาหาผมได้เลย) 👍 แต่โปรดทราบว่าตอนนี้ระบบจำเป็นต้องใช้ Gemini API Key 🔑 ของตัวเองแล้ว

ฉันตัดสินใจเปลี่ยนบล็อกของฉันจาก WriteFreely มาเป็นระบบที่พัฒนาขึ้นเองอย่างรวดเร็ว: MD-Blog (แน่นอนว่า MD ย่อมาจาก Markdown) จุดเริ่มต้นเกิดจากการอัปเดตระบบเก่าที่ล้มเหลว แต่สุดท้ายมันกลับเป็นแรงผลักดันที่สมบูรณ์แบบในการทำให้ทุกอย่างง่ายขึ้นอย่างสิ้นเชิง และทำให้ฉันสามารถ ควบคุมการออกแบบ ได้อย่างเต็มที่

หัวใจสำคัญคือไฟล์ Markdown ง่ายๆ ในโฟลเดอร์ data/ ซึ่งจะถูกแปลงเป็น HTML ที่ทันสมัยในขณะประมวลผล ผลลัพธ์ที่ได้นั้นรวดเร็วมาก ไม่ต้องใช้ฐานข้อมูล และด้วยระบบการออกแบบของตัวเอง (รวมถึง Dark Mode) ตอนนี้มันจึงดูตรงตามที่ฉันจินตนาการไว้ทุกประการ แถมยังมี ปุ่มแชร์ไปยัง Mastodon ที่ทันสมัยติดตั้งมาให้ในตัวเลยด้วย

หากใครสนใจโค้ดหรือการตั้งค่าที่เรียบง่ายนี้ สามารถติดต่อฉันผ่าน Mastodon ได้เลย!

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

จู่ๆ อิมเมจก็แจ้งเตือนว่าหน่วยความจำ (RAM) ไม่เพียงพอ ผมพยายามแก้ไขปัญหาด้วยตัวเองแล้ว แต่นั่นกลับทำให้ระบบใช้งานไม่ได้อย่างถาวร แทนที่จะเสียเวลาไปกับการหาสาเหตุของปัญหา ผมจึงตัดสินใจเปลี่ยนมาใช้ Dockurr Windows-Image โดยตรง ซึ่งจริงๆ แล้วมันคือพื้นฐานทางเทคนิคของ Winboat นั่นเอง

Fehlermeldung

1. การเตรียมการ

เนื่องจากผมใช้ Podman ผมจึงเริ่มจากการสร้างไดเรกทอรีที่จำเป็นบนระบบโฮสต์ของผมก่อน เพื่อให้ข้อมูลยังคงอยู่ครบถ้วนในกรณีที่ต้องสร้างคอนเทนเนอร์ใหม่:

mkdir -p $HOME/Windows/System
mkdir -p $HOME/Windows/Shared

2. คำสั่งเริ่มต้น

ข้อควรระวัง: ให้แทนที่ตัวยึดตำแหน่ง (placeholder) ในตัวแปร -e USERNAME และ -e PASSWORD ด้วยข้อมูลการเข้าสู่ระบบส่วนตัวของคุณ

podman run -d \
  --name windows \
  -p 8006:8006 \
  --device=/dev/kvm \
  --cap-add NET_ADMIN \
  -e RAM_SIZE="8G" \
  -e USERNAME="Carsten" \
  -e PASSWORD="1234" \
  -e LANGUAGE="German" \
  -v $HOME/Windows/System:/storage:Z \
  -v $HOME/Windows/Shared:/shared:Z \
  --stop-timeout 120 \
  dockurr/windows

ทันทีที่คอนเทนเนอร์ทำงาน คุณสามารถเข้าถึงอินสแตนซ์ Windows ได้โดยตรงผ่านเบราว์เซอร์ของคุณ:

http://127.0.0.1:8006

Laufender Container

3. สรุป

ผมต้องรันคำสั่งข้างต้นเพียงครั้งเดียวเท่านั้น ในการใช้งานประจำวัน ตอนนี้คุณสามารถควบคุมสภาพแวดล้อม Windows ได้อย่างสะดวกสบายผ่านคำสั่งลัดเหล่านี้:

  • เริ่มทำงาน: podman start windows
  • หยุดทำงาน: podman stop windows (หรือสั่งปิดเครื่องจากภายใน Windows โดยตรง)
  • ตรวจสอบสถานะ: podman ps -a

ลิงก์เพิ่มเติม:

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

WriteFreely ตอบโจทย์นี้ได้ดีมาก ทั้งความเรียบง่าย ติดตั้งได้รวดเร็ว และไม่มีฟีเจอร์ที่เกินความจำเป็น เหมาะอย่างยิ่งสำหรับการเริ่มต้นเขียนและเรียนรู้สิ่งใหม่ๆ ไปพร้อมกัน การตั้งค่าก็ดูสะอาดตาและเข้าใจง่าย เพียงแค่กำหนดตัวเลือกไม่กี่อย่าง เตรียมไดเรกทอรี ตั้งค่า Reverse Proxy ไว้ข้างหน้า เท่านี้ก็เรียบร้อย

นี่คือการตั้งค่า NixOS ปัจจุบันของผมสำหรับโปรเจกต์นี้:

{ config, pkgs, ... }:

{
  services.writefreely = {
    enable = true;
    host = "blog.burningboard.org"; 
    settings = {
      server = {
        port = 8080;
        min_log_level = "debug";
      };
      app = {
        host = "https://blog.burningboard.org";
        single_user = true;
        landing = "/read";
        wf_modesty = true;
        federation = true;
        public_stats = true;
        theme = "write";
      };
    };
    stateDir = "/opt/writefreely";
  };

  # แก้ไขสำหรับการสร้างคีย์ ActivityPub: Federation จำเป็นต้องใช้ openssl
  systemd.services.writefreely.path = [ pkgs.openssl ];

  # สร้างไดเรกทอรีข้อมูลโดยอัตโนมัติพร้อมกำหนดสิทธิ์ที่ถูกต้อง
  systemd.tmpfiles.rules = [
    "d /opt/writefreely 0700 writefreely writefreely -"
  ];

  services.caddy.virtualHosts."blog.burningboard.org".extraConfig = ''
    reverse_proxy 127.0.0.1:8080 {
      header_up Host {host}
      header_up X-Real-IP {remote_host}
      header_up X-Forwarded-For {remote_host}
      header_up X-Forwarded-Proto {scheme}
    }
  '';
}

หลักๆ ก็มีเพียงเท่านี้ NixOS ช่วยให้การตั้งค่าบริการต่างๆ เป็นไปอย่างเป็นระเบียบและสามารถทำซ้ำได้ง่ายจริงๆ