# แนะนำ Synx: การซิงก์ไฟล์สำหรับการพัฒนาแบบรีโมต

> Synx คือการซิงก์ไดเรกทอรีสองทิศทางแบบเรียลไทม์ผ่าน SSH ที่เป็นโอเพนซอร์ส เป็นทางเลือกแทน Mutagen สำหรับนักพัฒนาที่แก้โค้ดบนเครื่องตัวเองแต่บิลด์บนเครื่องรีโมต เขียนด้วย Rust หนึ่งคำสั่ง ไม่มีเดมอน

# แนะนำ Synx: การซิงก์ไฟล์สำหรับการพัฒนาแบบรีโมต

การพัฒนาแบบรีโมตลงเอยที่รูปทรงเดียวกันเสมอ และถ้าคุณเคยผ่านมันมา คุณจะจำมันได้จากประโยคเดียว: **เครื่องที่คุณใช้คิด กับเครื่องที่งานจริงรันอยู่ ไม่ใช่เครื่องเดียวกัน**

แล็ปท็อปคือที่ที่คุณใช้คิด มันมีเอดิเตอร์ที่คีย์บอร์ดชอร์ตคัตฝังอยู่ในนิ้วคุณ มีธีมของคุณ fuzzy finder ของคุณ เครื่องมือดูดิฟฟ์ของคุณ คลิปบอร์ดของคุณ language server ที่ปรับจูนไว้ตรงใจคุณเป๊ะ และเอเจนต์ AI ที่คุณชี้ให้มันดูโปรเจกต์นี้ มันคือเครื่องเดียวที่คุณเร็วจริง ๆ

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

ทั้งสองทำงานแทนกันไม่ได้ และคุณก็ไม่ได้อยากให้ทำ คุณอยากแก้โค้ดตรงนี้ต่อไป และบิลด์ตรงโน้นต่อไป

ซึ่งย่อการพัฒนาแบบรีโมตทั้งหมดลงเหลือคำถามที่ไม่หวือหวาข้อเดียว: **ไฟล์เดินทางจากฝั่งหนึ่งไปอีกฝั่งได้อย่างไร อย่างต่อเนื่อง ในทั้งสองทิศทาง และเร็วพอจนคุณลืมไปเลยว่ามันกำลังเกิดขึ้น?** ทำข้อนี้ให้ถูก แล้วทั้งชุดเครื่องมือจะหายไป — คุณพิมพ์ คุณกดเซฟ คุณสลับไปเทอร์มินัลที่บังเอิญอยู่คนละทวีป แล้วมันก็บิลด์ ทำข้อนี้ผิด แล้วคุณจะใช้ทั้งวันเป็นพนักงานส่งของให้ซอร์สโค้ดของตัวเอง

---

## คำตอบที่เอาไม่อยู่

**ก็ทำงานบนเครื่องนั้นเลยสิ** SSH เข้าไป เปิด `vim` แล้วปัญหาก็ระเหยไป — มีสำเนาเดียวตลอด บางคนมีความสุขกับแบบนี้จริง ๆ และผมก็ไม่คิดจะไปเถียงเขา แต่ราคาที่จ่ายคือสภาพแวดล้อมบนเครื่องคุณทั้งหมด: คอนฟิกเอดิเตอร์ ส่วนขยาย git client แบบกราฟิก คลิปบอร์ด เครื่องมือแคปหน้าจอ ผู้ช่วย AI ที่รันบนเครื่องคุณเอง และตอนนี้ทุกการกดแป้นอยู่หลังเครือข่าย บนไฟเบอร์ก็ไม่เป็นไร บนไวไฟโรงแรมคุณจะรู้สึกได้ถึงทุกตัวอักษรที่ร่วงลงมา

**เมานต์รีโมตผ่านเครือข่าย** SSHFS เปลี่ยนไดเรกทอรีรีโมตให้เป็นพาธบนเครื่อง ซึ่งฟังดูตรงกับที่คุณต้องการเป๊ะ จนกระทั่งคุณเห็นว่าอะไรกำลังวิ่งอยู่บนสาย: ทุก `stat()` ตัวเฝ้าไฟล์ของเอดิเตอร์ ตัวทำดัชนีของ language server `git status` ของคุณ — แต่ละตัวเดินผ่านพาธนับพัน และทุกการเดินคือการวิ่งไปกลับ รายงานตรงกันหมด: ประสิทธิภาพร่วงเป็นหน้าผาทันทีที่มีอะไรแตะไฟล์ทีละมาก ๆ อย่าง `git checkout` หรือ `npm install` บน macOS ยิ่งแย่ เพราะเรื่อง FUSE ที่นั่นกระอักกระอ่วนมาหลายปี นามธรรมนี้น่ารักจนถึงวินาทีที่ความหน่วงทำให้มันใช้ไม่ได้พอดี

**`rsync` ในลูป** คือการแก้ขัดแบบซื่อ ๆ ที่ทุกคนเคยเขียนอย่างน้อยหนึ่งครั้ง: `fswatch | rsync` หรือ `while true; sleep 1` สำหรับสแน็ปช็อตทิศทางเดียวมันถูกต้อง แต่พอเป็นแบบสดมันเสื่อม — ไม่ก็ยิงทุกการกดแป้นจนถล่มลิงก์ ไม่ก็รวมเป็นชุดแล้วตามหลังการเซฟของคุณ มันเป็นทิศทางเดียวโดยการออกแบบ ดังนั้นถ้าคุณอยากให้ไฟล์ที่ถูกสร้างหรือผลลัพธ์การบิลด์เดินทางกลับมา คุณก็รันตัวที่สอง และตอนนี้สองโพรเซสที่ไม่มีความเข้าใจร่วมกันว่าอะไรคือความจริงก็แข่งกันอยู่บนต้นไม้เดียวกัน ที่คนกลัวกันน่ะถูกแล้ว: `rsync` ที่ใส่แฟล็กผิดในทิศทางผิดลบงานจริง ๆ ทิ้ง

**ปล่อยให้ IDE จัดการ** VS Code Remote-SSH และ JetBrains Gateway แก้เรื่องนี้ได้อย่างถูกต้อง — _สำหรับเอดิเตอร์_: มีคอมโพเนนต์ฝั่งเซิร์ฟเวอร์รันบนรีโมต ส่วน UI อยู่บนเครื่องคุณ ถ้าโลกทั้งใบของคุณอยู่ใน IDE ตัวเดียว นี่คือคำตอบที่ดี แต่ไฟล์ก็ยังคงมีอยู่แค่ฝั่งโน้น ทุกอย่างที่อยู่ _นอก_ เอดิเตอร์จึงตาบอด: เทอร์มินัลของคุณ git client แยกต่างหากของคุณ สคริปต์บนเครื่องคุณ เอเจนต์เขียนโค้ดที่รันอยู่บนแล็ปท็อปคุณ อีกทั้งคุณยังผูกตัวเองไว้กับโปรโตคอลรีโมตของผู้ผลิตรายเดียว ติดตั้งส่วนขยายซ้ำอีกฝั่ง และยอมรับว่ารีโมตต้องน่าเชื่อถือพอที่จะรันมันได้

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

รูปทรงแบบนั้นมีน้ำหนัก มีเดมอนที่อยู่ประจำบนเครื่องคุณ บวกกับเอเจนต์หนึ่งตัวต่อหนึ่งเอนด์พอยต์ มีชีวิตอยู่ไม่ว่าคุณจะทำงานอยู่หรือไม่ และในตัวติดตามปัญหาก็มีรายงานยาวเหยียดว่ามันเผา CPU ในช่วงที่[ไฟล์ไม่ได้เปลี่ยนอะไรเลย](https://github.com/mutagen-io/mutagen/issues/44) บน[ต้นไม้ขนาดใหญ่](https://github.com/mutagen-io/mutagen/issues/108) และ[ภายใต้ WSL](https://github.com/mutagen-io/mutagen/issues/201) มันมีน้ำหนักบนดิสก์ด้วย: ในเวิร์กโฟลว์ Docker ที่มันถูกสร้างมาเพื่อรองรับ โปรเจกต์ของคุณมีอยู่สองชุด และเอกสารของ DDEV เองก็บอกให้คุณ[คอยดูวอลุ่มที่ซ้ำซ้อนนั้น](https://docs.ddev.com/en/stable/users/install/performance/) — เกิน 5 GB คือคำเตือน เกิน 10 GB คือขั้นวิกฤต

ต่อมาคือพื้นที่เชิงปฏิบัติการ คุณสร้างเซสชัน ไล่ดูรายการ เรียนรู้ `mutagen sync terminate` — แล้วก็เรียนรู้ว่ามันค้างได้ระหว่างจัดเตรียมไฟล์ โดยสูตรพื้นบ้านที่วนอยู่ในเธรดปัญหาคือให้หยุดเดมอน ลบไฟล์ล็อก แล้วเริ่มใหม่ โหมดจัดการความขัดแย้งที่ปลอดภัยโดยดีฟอลต์ของมันจะหยุดรอมนุษย์ ซึ่งเป็นการตัดสินใจที่ถูก แต่ก็แปลว่าเซสชันอาจเลิกลู่เข้าหากันอย่างเงียบ ๆ ขณะที่คุณคิดว่าทุกอย่างเรียบร้อย มีคำขอค้างมานานเพียงเพื่อให้มีวิธีที่เชื่อถือได้ในการ _รู้_ ว่าเซสชันพังหรือเปล่า บนต้นไม้ใหญ่ การสแกนรอบแรกกินเวลาเป็นนาทีก่อนที่อะไรจะเริ่มทำงาน สัญญาณที่ชัดที่สุดว่าพื้นที่สะสมไปมากแค่ไหนคือ DDEV ซึ่งพึ่ง Mutagen หนักมาก สุดท้ายต้องออก[ซับคอมมานด์วินิจฉัย](https://ddev.com/blog/mutagen-functionality-issues-debugging/)ทั้งชุด — `ddev utility mutagen-diagnose` — เพื่อบอกคุณว่าทำไมการซิงก์ของคุณถึงไม่มีความสุข

แล้วพื้นก็ขยับ Docker ซื้อ Mutagen ไปในปี 2023 Mutagen Compose ถูกประกาศเลิกใช้ตรง ๆ — [เอกสารบอกไว้ชัด](https://mutagen.io/documentation/orchestration/compose/): _"Mutagen Compose is now deprecated with the release of v0.18.0."_ ส่วนตัว Mutagen เองไม่ได้ออกรีลีสที่ติดแท็กเลยนับตั้งแต่ **v0.18.1 เมื่อกุมภาพันธ์ 2025** รีโพยังได้รับคอมมิตอัปเดต dependency เป็นครั้งคราว มันจึงยังไม่ตาย แต่ก็ไม่ได้ขยับ

ทั้งหมดนี้ไม่ใช่เรื่องอื้อฉาว และผมอยากยุติธรรมกับมัน: น้ำหนักที่ว่านั้นคือราคาที่เอนจินซิงก์อเนกประสงค์ต้องจ่าย เมื่อมันต้องรองรับเอนด์พอยต์ทั้งแบบโลคัล SSH และ Docker บนสี่ระบบปฏิบัติการ ภายใต้ตัวออร์เคสเตรต ผมแค่ไม่ต้องการอะไรในนั้นเลย ผมต้องการแค่ไดเรกทอรีหนึ่งกับพาธหนึ่ง

ผมจึงออกไปหาสิ่งที่เป็นแค่นั้น แล้วก็ไม่มี มี Unison ที่คุณต้องเรียน CLI ของมันใหม่ทุกครั้งที่แตะ มี Syncthing ที่สร้างขึ้นรอบ ๆ อุปกรณ์และโฟลเดอร์ที่แชร์กัน มากกว่ารอบต้นไม้ซอร์สโค้ดของนักพัฒนาคนหนึ่ง ถัดจากนั้นก็เป็นหางยาว ๆ ของโปรเจกต์ GitHub ที่ทำค้างไว้ ไม่มีอะไรที่เป็นแค่: _ไดเรกทอรีนี้ พาธนั้น ผ่าน SSH เดี๋ยวนี้ เคารพ `.gitignore` ของผม — แล้วก็หลีกทางไป_

ผมจึงเขียนมันเอง

---

## ผมต้องการอะไรจากมัน

รายการข้อกำหนดสั้นมาก และทุกบรรทัดมาจากสิ่งที่เคยกวนใจผม:

- **หนึ่งคำสั่ง ไม่ใช่เซสชัน** ไม่มีเดมอนให้ต้องคอยดูแล ไม่มีการเต้น `status` / `terminate` / ไฟล์ล็อก มันรันตราบที่คุณอยากให้รัน และ `ctrl+c` แปลว่าหยุด
- **ไม่มีอะไรอยู่ประจำ** ตอนที่ผมไม่ได้ซิงก์ ก็ไม่มีโพรเซสอยู่เลย ไม่มีอะไรอุ่นแคช ไม่มีอะไรสแกนต้นไม้ ไม่มีอะไรให้ต้องสงสัยใน Activity Monitor ตอนตีสอง
- **ไม่มีไฟล์คอนฟิก** สองพาธนั่นแหละคืออาร์กิวเมนต์ ไม่มีอะไรต้องคอมมิตเข้ารีโพ ไม่มีอะไรต้องจำ ไม่มีอะไรให้ล้าสมัย
- **`.gitignore` คือกฎหมาย** รีโพของคุณประกาศไว้แล้วว่าอะไรไม่ใช่ซอร์ส `target/`, `node_modules/`, `.venv/` — ไดเรกทอรีที่ทำให้เครื่องมือซิงก์ไร้เดียงสาทุกตัวคลาน — ไม่ควรเข้ามาอยู่ในแมนิเฟสต์ตั้งแต่แรก ไม่ว่าทิศทางไหน
- **ห้ามทำข้อมูลหาย** เครื่องมือซิงก์ที่ลบสิ่งที่คุณตั้งใจจะเก็บไว้ ย่อมแย่กว่าการไม่มีเครื่องมือซิงก์เลย เรื่องอื่นต่อรองได้ทั้งหมด เรื่องนี้ไม่ได้
- **ถูกเงินบนต้นไม้ที่ซิงก์ไว้แล้ว** การรันครั้งที่สองควรมีต้นทุนเท่ากับการเดินสำรวจไดเรกทอรี ไม่ใช่การแฮชใหม่ทั้งหมด
- **แค่ SSH** กุญแจของคุณ `~/.ssh/config` ของคุณ `ProxyJump` ของคุณ ไม่มีพื้นที่การยืนยันตัวตนใหม่ ไม่มีพอร์ตใหม่ให้ต้องเปิด

อ่านรายการนั้นอีกครั้งแล้วสังเกตว่าอะไรไม่อยู่ในนั้น: คอนเทนเนอร์ ออร์เคสเตรชัน การฟอร์เวิร์ดเครือข่าย Windows GUI สคีมาคอนฟิก ระบบปลั๊กอิน ทุกข้อล้วนเป็นสิ่งที่สมเหตุสมผลที่จะอยากได้ และทุกข้อก็คือเหตุผลที่เครื่องมือซิงก์งอกเดมอนออกมา การตัดมันออกไม่ใช่ความถ่อมตัว — มันคือทั้งหมดของการออกแบบ เครื่องมือยังเล็กอยู่ได้เพราะปัญหาที่มันยอมรับนั้นเล็ก

นั่นคือ Synx: ไบนารี Rust โอเพนซอร์สตัวเดียว — วันนี้คือเวอร์ชัน 0.1.2 — ที่มิเรอร์ไดเรกทอรีบนเครื่องไปยังพาธรีโมตผ่าน SSH และทำให้ทั้งสองเดินไปพร้อมกันขณะที่คุณทำงาน หนึ่งโพรเซส รันเฉพาะตอนที่คุณกำลังทำงาน ทำงานเดียว มันยังเป็นช่วงต้น และมันก็ทำงานที่ผมต้องการให้มันทำในทุก ๆ วันได้แล้ว

---

## Synx คืออะไร

คุณรันมันบนเครื่องของคุณและชี้ไปยังเป้าหมายรีโมต:

```sh
synx ./src dev@beefy:/srv/app/src
```

นั่นคืออินเทอร์เฟซทั้งหมด ไบนารีตัวเดียวกันรันบนทั้งสองปลาย — บนเครื่องคือไคลเอนต์ บนรีโมตมันรันในโหมด `--agent` ที่ซ่อนอยู่ ซึ่งไคลเอนต์เปิดให้คุณผ่าน SSH ไม่มีบริการให้ติดตั้ง ไม่มีอะไรต้องลงทะเบียน ไม่มี YAML

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

```
synx  /Users/dk/proj  ◀─▶  dev@beefy:/srv/proj
✓ connected
• manifests:  local 1243  •  remote 1180 (47 ignored)
• plan: push 78 files (4.2 MiB) 6 dirs 0 links  •  pull 14 entries
✓ initial sync: 4.2 MiB sent, 312 KiB received in 1.4s
• watching for changes — ctrl+c to stop
  → src/main.rs  3.1 KiB
  ← README.md   824 B
```

มันรันบน macOS และ Linux ทรานสปอร์ตคือ SSH ล้วน ๆ — คีย์ของคุณ เอเจนต์ของคุณ `~/.ssh/config` ของคุณ `ProxyJump` ของคุณ ทั้งหมด Synx ไม่ได้คิดค้นกลไกยืนยันตัวตนใหม่ มันยืมตัวที่คุณเชื่อถืออยู่แล้วมาใช้

---

## โหมดการซิงก์และการจัดการความขัดแย้ง

ทิศทางคือแฟล็กเดียว ค่าเริ่มต้นเป็นสองทิศทาง

| โหมด                 | ทิศทาง          | กฎความขัดแย้ง     | การซิงก์เริ่มต้น                         |
| -------------------- | --------------- | ----------------- | ---------------------------------------- |
| `push`               | เครื่อง → รีโมต | เครื่องชนะเสมอ    | ส่งเฉพาะไฟล์ที่มีแค่บนเครื่องหรือต่างกัน |
| `pull`               | รีโมต → เครื่อง | รีโมตชนะเสมอ      | ดึงเฉพาะไฟล์ที่มีแค่บนรีโมตหรือต่างกัน   |
| `both` (ค่าเริ่มต้น) | สองทิศทาง       | mtime ใหม่กว่าชนะ | ผสาน **ไม่มีการลบ**                      |

```sh
# สองทิศทาง (ค่าเริ่มต้น)
synx ./src dev@host:/srv/app/src

# push ทิศทางเดียว
synx ./build host:/var/www --mode push

# pull ทิศทางเดียว
synx ./nginx host:/etc/nginx --mode pull
```

ผมจะพูดตรง ๆ เรื่องโมเดลความขัดแย้งเพราะมันสำคัญ: ในโหมด `both` เมื่อไฟล์เดียวกันเปลี่ยนทั้งสองด้าน **เวลาที่แก้ไขล่าสุดกว่าจะชนะ** แค่นั้น ไม่มีการผสานสามทางที่รู้บรรพบุรุษร่วม ไม่มีเครื่องหมายความขัดแย้ง สิ่งนี้เชื่อถือได้ตราบเท่าที่นาฬิกาของทั้งสองเครื่องปกติดี — ดังนั้นถ้าคุณเห็นไฟล์เด้งไปเด้งมา สิ่งแรกที่ต้องตรวจคือความคลาดเคลื่อนของนาฬิกา (NTP แก้ได้) และถ้าคุณเชื่อนาฬิกาไม่ได้ ก็เลือกทิศทาง `push` หรือ `pull` ให้ชัดเจนไปเลย

อีกหนึ่งการตัดสินใจที่จงใจ: **การซิงก์เริ่มต้นไม่เคยลบอะไรเลย** ถ้าคุณชี้ Synx ไปยังพาธรีโมตที่ล้าสมัย มันจะไม่ล้างข้อมูล — มันผสาน การลบจะแพร่กระจายก็ต่อเมื่อ Synx ทำงานสดและกำลังเฝ้าดู และต่อเมื่อมันมี _baseline_ เท่านั้น — สแน็ปช็อตที่บันทึกไว้ว่าทั้งสองด้านตกลงกันครั้งล่าสุดเป็นอย่างไร baseline อยู่ในไดเรกทอรีแคชของคุณ และทำให้ Synx แยกแยะได้ระหว่าง "ไฟล์นี้ถูกลบ" กับ "ไฟล์นี้ไม่เคยมีอยู่บนอีกด้านเลย" การรันใหม่ที่ไม่มี baseline จะเก็บทุกอย่างไว้ การลบจะเริ่มแพร่กระจายตั้งแต่การซิงก์ครั้งที่สองเป็นต้นไป ความไม่สมมาตรนี้ตั้งใจให้เป็นแบบนั้น การสูญข้อมูลเพราะเครื่องมือซิงก์ที่กระตือรือร้นเกินไปคือโหมดความล้มเหลวเดียวที่ผมปฏิเสธจะปล่อยออกไป

---

## กฎการละเว้นคือผู้มีอำนาจสูงสุด

Synx โหลด **`.gitignore` ทุกไฟล์** ใต้รากการซิงก์ของคุณ — แบบซ้อนกันที่ความลึกใดก็ตาม — บวกกับ `.synxignore` ที่เป็นทางเลือกซึ่งมีไวยากรณ์เหมือนกันทุกประการ อะไรก็ตามที่ตรงกันจะไม่ถูกซิงก์เลย ไม่ว่าทิศทางใด นี่ไม่ใช่ฟิลเตอร์แบบ "พยายามให้ดีที่สุด" ที่ใช้ที่ต้นทาง มันถูกบังคับใช้ที่สามจุด:

1. **การเดินสำรวจเริ่มต้น** — ไฟล์ที่ถูกละเว้นจะไม่เข้าไปอยู่ในแมนิเฟสต์ตั้งแต่แรก
2. **แมนิเฟสต์ฝั่งรีโมต** — ไฟล์ที่รีโมตรายงานมาและตรงกับกฎการละเว้น _บนเครื่องคุณ_ จะถูกกรองออกก่อนที่แผน diff จะทำงาน ดังนั้น `target/` หรือ `node_modules/` ที่บังเอิญมีอยู่บนรีโมตจะไม่ถูกดึงลงมาเลย
3. **เหตุการณ์แบบสด** — ทั้งการนำเข้ามาใช้และการแจ้งเตือนขาออกต่างก็ข้ามพาธที่ถูกละเว้น

สิ่งหนึ่งที่ทำให้คนแปลกใจ: **ไฟล์ที่ขึ้นต้นด้วยจุด (dotfiles) ไม่ใช่อะไรพิเศษ** `.env` `.vscode/` `.git/` — ทั้งหมดถูกซิงก์เหมือนอย่างอื่นเว้นแต่คุณจะกันมันออก ถ้าคุณไม่อยากให้ไดเรกทอรี `.git/` ถูกมิเรอร์ ก็บอกไปเลย:

```sh
echo '/.git' >> .synxignore
```

พูดถึง `.git/` — Synx ระมัดระวังกับมัน Git มองไดเรกทอรีนั้นเป็นสถานะแบบทรานแซกชัน และการเปลี่ยนชื่อแบบอะตอมมิกของอ็อบเจกต์ที่เขียนค้างหรือไฟล์ล็อกไปยังพีร์กลางคันของคอมมิตจะทำให้รีโพเสียหาย Synx จึงเฝ้าดูเครื่องหมายปฏิบัติการที่กำลังดำเนินอยู่ของ git (`index.lock` และพวกพ้อง) และ **หยุดการซิงก์พาธของ `.git/` ชั่วคราว** ขณะที่ปฏิบัติการ git กำลังทำงานอยู่ แล้วเล่นการเปลี่ยนแปลงที่หน่วงไว้ซ้ำเมื่อ git เสร็จ working tree ของคุณซิงก์ต่อไปตลอดเวลา มีแค่ `.git/` ที่รอ ถ้าคุณกำลังจะมิเรอร์รีโพที่มีชีวิต คุณต้องการสิ่งนี้

ในทิศทางกลับกันก็มีการป้องกันเช่นกัน ถ้า `.git/` บนเครื่องคุณเป็นของเหลือจากปฏิบัติการที่ถูกขัดจังหวะ และฝั่งรีโมตไม่มี `.git/` เลย Synx จะมิเรอร์การลบนั้น **ก็ต่อเมื่อ baseline พิสูจน์ได้ว่า `.git/` เป็นส่วนหนึ่งของสถานะล่าสุดที่ทั้งสองฝั่งตกลงกันไว้** เท่านั้น หากไม่มีหลักฐานนั้น `.git/` ของคุณก็เป็นเพียงข้อมูลที่ยังไม่เคยซิงก์ — Synx จะเก็บมันไว้แล้วส่งขึ้นไปยังรีโมตแทน

---

## มันทำงานอย่างไรภายใน

```
┌─ local (client) ──────────────┐         ┌─ remote (agent) ─────────────┐
│  watcher (notify)             │   ssh   │  watcher (notify)            │
│  parallel walker (blake3)     │ ◀─────▶ │  parallel walker (blake3)    │
│  persistent hash cache        │  stdio  │  persistent hash cache       │
│  diff plan + executor         │ postcard│  message dispatcher          │
└───────────────────────────────┘  + zstd └──────────────────────────────┘
```

มีบางส่วนที่ควรอธิบาย เพราะความเร็วมาจากตรงนั้น

**การแฮชแบบขนานพร้อมแคชถาวร** ทั้งสองด้านเดินสำรวจต้นไม้ของตนแบบขนาน (ผ่านตัวเดินสำรวจแบบขนานของเครต `ignore`) และแฮชทุกไฟล์ด้วย **blake3** ไฟล์ ignore ที่ซ้อนกันถูกค้นพบภายในการเดินสำรวจครั้งเดียวกันนั้น ไม่ใช่ในรอบสำรวจแยกต่างหาก การเริ่มต้นจึงเสียการเดินสำรวจต้นไม้เพียงรอบเดียวแทนที่จะเป็นสองรอบ — และตัวเฝ้าดูถูกเปิดใช้ _ก่อน_ ที่การเดินสำรวจจะเริ่ม สิ่งที่คุณบันทึกระหว่างที่มันยังสแกนอยู่จึงเข้าคิวไว้แทนที่จะหายไป แฮชเข้าไปอยู่ในแคชถาวรในไดเรกทอรีแคชของแพลตฟอร์มคุณ (`~/.cache/synx/` บน Linux, `~/Library/Caches/synx/` บน macOS) ทำดัชนีด้วย `(path, size, mtime)` การรัน Synx ซ้ำบนรีโพที่ไม่เปลี่ยนจะข้ามการแฮชซ้ำทั้งหมด — การซิงก์ครั้งที่สองของต้นไม้แสนไฟล์ถูกจำกัดด้วยการเดินสำรวจไดเรกทอรีเพียงอย่างเดียว ซึ่งประมาณหนึ่งวินาที การอ่านแคชเป็นแบบ immutable และผลลัพธ์ของตัวเดินสำรวจถูกรวมเป็นชุดต่อ worker จึงไม่มีล็อกส่วนกลางในลูปที่ร้อน และแคชที่ไม่มีการเปลี่ยนแปลงก็ไม่ถูกเขียนกลับลงดิสก์เลย

**การถ่ายโอนแบบเดลตาสำหรับไฟล์ใหญ่** ไฟล์เล็กหรือไฟล์ที่เปลี่ยนทั้งก้อนจะเดินทางผ่านสายแบบเต็มไฟล์ แต่สำหรับไฟล์ **ขนาดระหว่าง 256 KiB ถึง 256 MiB** ที่ฝั่งพีร์มีเวอร์ชันอื่นอยู่แล้ว Synx ทำเดลตาสไตล์ rsync มันใช้ `fast_rsync` ซึ่งเป็นพอร์ตของ librsync ที่เร่งด้วย SIMD: ฝั่งรับคำนวณลายเซ็นของไฟล์ที่มีอยู่แล้ว ฝั่งส่ง diff เทียบกับลายเซ็นนั้น และมีเพียงบล็อกที่เปลี่ยนเท่านั้นที่เดินทาง เนื่องจากการแฮชบล็อกภายในของ librsync เก่ากว่าแฮชเชิงเข้ารหัสสมัยใหม่ Synx จึงตรวจสอบผลของทุกเดลตาที่นำมาใช้เทียบกับแฮช blake3 ใหม่ก่อนคอมมิต สายส่งจะไม่มีโอกาสโกหกคุณ

**การบีบอัดและการแบ่งชังก์** ข้อความเป็น `postcard` แบบนำหน้าด้วยความยาว (รูปแบบไบนารีกระชับที่ได้รับการดูแลอย่างต่อเนื่อง) และถูกบีบอัดด้วย zstd เมื่อการบีบอัดประหยัดพื้นที่จริง รูปแบบที่ถูกบีบอัดมาแล้ว — ไฟล์บีบอัด สื่อ แพ็กเกจ — จะข้าม zstd ตามนามสกุลไฟล์ เพื่อไม่ให้เปลือง CPU ไปกับการพิสูจน์ว่ามันเล็กลงไม่ได้ ไฟล์ที่ใหญ่กว่า 16 MiB จะถูกสตรีมเป็นชังก์ขนาด 4 MiB ลงในไฟล์ชั่วคราว แล้วเปลี่ยนชื่อแบบอะตอมมิกเข้าที่โดยรักษาโหมดและ mtime เดิม — ดังนั้นการแครชกลางการถ่ายโอนจะไม่มีวันทิ้งไฟล์ที่เขียนค้างไว้ที่พาธจริง

**การกดเสียงสะท้อนตามสถานะ** นี่คือส่วนที่ละเอียดอ่อนของการซิงก์สองทิศทางใด ๆ เมื่อ Synx นำการเปลี่ยนแปลงขาเข้ามาใช้ ตัวเฝ้าดูบนเครื่องของคุณกำลังจะทำงานสำหรับพาธเดียวกันนั้น — และถ้าคุณส่งมันกลับไปอย่างซื่อ ๆ คุณก็ได้เสียงสะท้อนไม่รู้จบ ทางแก้แบบขี้เกียจคือหน้าต่างเวลา ("ละเว้นเหตุการณ์ในอีก N มิลลิวินาทีข้างหน้า") ซึ่งก็ทิ้งการแก้ไข _ที่ถูกต้อง_ ที่ผู้ใช้ทำในช่วงนั้นไปด้วย Synx กลับบันทึกสถานะผลลัพธ์บนดิสก์แทน (mtime หรือ "ถูกลบ") และเมื่อตัวเฝ้าดูทำงาน มันเปรียบเทียบไฟล์ _ปัจจุบัน_ กับสิ่งที่บันทึกไว้ มีเพียงค่าที่ตรงกันเท่านั้นที่ถือว่าเป็นเสียงสะท้อนและถูกทิ้ง ถ้าคุณแก้ไขไฟล์ในระหว่างนั้น เหตุการณ์ก็ไหลผ่านตามปกติ ไม่มีหน้าต่างตาบอดที่การกดแป้นของคุณถูกกลืนหายไป

**การใช้การเชื่อมต่อซ้ำ** SSH ทำงานด้วย `ControlMaster auto` และระยะคงอยู่สั้น ๆ ดังนั้นการเรียก Synx หลายครั้งไปยังโฮสต์เดียวกันจึงใช้การเชื่อมต่อ TCP เดียวกันร่วมกันแทนที่จะเจรจาใหม่

---

## ลองใช้ใน 5 นาที

คุณต้องมี Synx **บนทั้งสองปลาย** — เครื่องของคุณและรีโมต ตัวติดตั้งแบบบรรทัดเดียวคือทางที่เร็วที่สุดบนแต่ละเครื่อง:

```sh
# Linux และ macOS, x86_64 + ARM64
curl -fsSL https://raw.githubusercontent.com/Muvon/synx/master/install.sh | sh

# หรือจาก crates.io
cargo install synx
```

ถ้าคุณมีบิลด์ release บนเครื่องอยู่แล้ว ก็แค่คัดลอกมันไป:

```sh
scp target/release/synx user@host:~/.local/bin/synx
ssh user@host 'chmod +x ~/.local/bin/synx'
```

จากนั้นเริ่มเซสชัน:

```sh
# ซิงก์สองทิศทาง แบบสด
synx ./project dev@host:/srv/project

# ดูแผน ไม่เปลี่ยนอะไร
synx ./project dev@host:/srv/project --dry-run

# ซิงก์เริ่มต้นอย่างเดียว แล้วออก
synx ./project dev@host:/srv/project --once
```

แฟล็กบางตัวที่คุณจะหยิบมาใช้:

```sh
# พอร์ต SSH ที่ไม่มาตรฐาน (หรืออาร์กิวเมนต์ ssh เพิ่มเติมใด ๆ)
synx ./code host:/work --ssh-opts "-p 2222 -i ~/.ssh/devkey"

# synx ไม่อยู่ใน PATH บนรีโมต
synx ./code host:/work --remote-synx ~/.local/bin/synx

# ข้ามการบีบอัด (เร็วกว่าบน LAN ที่เร็วซึ่งมี blob ที่บีบอัดไม่ได้)
synx ./code host:/work --no-compress

# ล็อกมากขึ้น
synx ./code host:/work -v     # debug
synx ./code host:/work -vv    # trace
```

ถ้ารีโมตหาไบนารีไม่เจอ คุณจะได้ `synx: command not found` จากเชลล์ล็อกอิน — นั่นคือกรณีของ `--remote-synx` ไม่ใช่บั๊ก และทั้งสองปลายต้องรันโปรโตคอลเวอร์ชันเดียวกัน 0.1.x พูดโปรโตคอล v1 และเข้ากันไม่ได้ในระดับสายส่งกับสิ่งที่จะออกมาในอนาคต ดังนั้นจงอัปเกรดทั้งสองด้านพร้อมกัน

---

## สิ่งที่มันยังทำไม่ได้

Synx คือ 0.1.2 ผมขอบอกคุณถึงขอบมุมของมันเองดีกว่าปล่อยให้คุณไปเจอเอง

- **ไม่มีโหมดเดมอน** มันรันแบบ foreground ส่งไป background ด้วย `&` หรืออยู่ใน `tmux`/`screen` ส่วน `synx status` / `synx stop` ที่เหมาะสมอยู่ในรายการ
- **ไม่มีการผสานเนื้อหาแบบสามทาง** การลบมี baseline หนุนหลัง แต่ความขัดแย้งของ _เนื้อหา_ ไฟล์ตัดสินด้วย mtime ที่ใหม่กว่า ไม่รู้บรรพบุรุษร่วม ต้องการนาฬิกาที่ถูกต้อง
- **แคชแฮชใช้คีย์ `(size, mtime)`** ไฟล์ที่ถูกเขียนทับที่เดิมด้วยขนาดและเวลาประทับเท่ากันจะไม่ถูกแฮชใหม่ นี่คือฮิวริสติกเดียวกับที่ git ใช้ และในทางปฏิบัติมันถูกต้อง
- **รีสตาร์ตเพื่อรับกฎการละเว้นใหม่** เปลี่ยน `.gitignore` กลางเซสชัน แล้ว Synx จะไม่รู้จนกว่าคุณจะรีสตาร์ตมัน
- **เฉพาะ macOS และ Linux** ไม่รองรับ Windows

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

---

## โอเพนซอร์ส

Synx อยู่บน GitHub ภายใต้ Apache-2.0 มันเป็น Rust เพราะการซิงก์ไฟล์ต้องการความถูกต้องในเวลาคอมไพล์ ความหน่วงที่คาดเดาได้ และไม่มีการหยุดของ GC กลางการถ่ายโอน — และเพราะไบนารีสแตติกตัวเดียวที่คุณ `scp` ขึ้นเซิร์ฟเวอร์ได้คือรูปทรงที่ถูกต้องสำหรับเครื่องมือแบบนี้

มันสร้างโดยทีมเดียวกับที่อยู่เบื้องหลังเครื่องมือนักพัฒนาตัวอื่น ๆ ของเรา ถ้าคุณอยากรู้ว่าทำไมเราถึงปล่อยยูทิลิตีโอเพนซอร์สเล็ก ๆ คม ๆ ออกมาเรื่อย ๆ แทนที่จะเป็นแพลตฟอร์มใหญ่ตัวเดียว นั่นเป็น[บทสนทนาที่ยาวกว่านี้](/blog/we-are-builders) Synx คือตัวล่าสุดในนั้น และเป็นตัวที่ผมเองใช้มากที่สุด

ถ้ามันมีประโยชน์กับคุณ โค้ดก็อยู่ตรงนั้น ถ้ามันพัง ตัวติดตามปัญหาก็อยู่ตรงนั้นเช่นกัน — รายงานบั๊กจากการตั้งค่าการพัฒนาแบบรีโมตจริง ๆ คือทางที่เร็วที่สุดในการทำให้ 0.2 ดีกว่า 0.1

— Vladimir

_Synx เป็นโอเพนซอร์สภายใต้ Apache-2.0 ดาวน์โหลดมัน อ่านซอร์ส หรือแจ้งปัญหาได้ที่ [github.com/Muvon/synx](https://github.com/Muvon/synx)_
