⭐ หลักการข้อแรกของคู่มือเล่มนี้: อย่าใช้เครื่องแบบไร้จอ (headless)
ตั้งเครื่องคู่กับจอ + คีย์บอร์ด + เมาส์เป็นหลัก — หรืออย่างน้อยเก็บชุดนี้ไว้ใกล้เครื่อง พร้อมเสียบได้ทันที
เหตุผลตรงๆ จากประสบการณ์จริงของเรา: การอัปเดตระบบของ NVIDIA เคยทำให้เครื่อง “หายไปจากเครือข่าย” มาแล้ว — driver ที่ต้องรีบูตถึงใช้ได้, ชื่อเครื่องถูกเปลี่ยนอัตโนมัติ, กระทั่ง WiFi ถูกล้างจนเครื่องกลับสู่โหมดแกะกล่อง เหตุการณ์พวกนี้ถ้าเครื่องไร้จอ = เข้าไม่ได้เลยจนกว่าจะหาจอมาต่อ แต่ถ้ามีจออยู่แล้ว = แก้จบในไม่กี่นาทีทุกกรณี
SSH/การเข้าเครื่องระยะไกล (สอนในบทนี้) คือความสะดวกสำหรับงานประจำวัน — ไม่ใช่สิ่งทดแทนจอ
1.1 ของในกล่องและการต่อครั้งแรก
ในกล่อง: ตัวเครื่อง DGX Spark, อะแดปเตอร์ไฟ (USB-C 240W), สาย USB-C — ตัวเครื่องมีพอร์ต USB-C 4 ช่อง, HDMI 2.1a, พอร์ต LAN 10GbE (RJ45), พอร์ต QSFP 200GbE สองช่อง (ไว้ต่อ Spark สองเครื่องเข้าด้วยกัน — cluster), WiFi 7, Bluetooth
ต่อจอ + คีย์บอร์ด + เมาส์ตั้งแต่แรก และวางแผนให้มันอยู่กับเครื่องถาวร (ตามหลักการข้อแรกด้านบน) — ทุกการตั้งค่าเครือข่ายในคู่มือนี้ทำผ่านหน้าจอ Settings ทั้งหมด
การเปิดเครื่องครั้งแรกแบ่งเป็น 2 ช่วง: ช่วง wizard ตั้งค่าบนจอ (ยังไม่มี Terminal ให้ใช้) และช่วงเปิด Terminal ครั้งแรก — เดินตามลำดับทั้งหมด อย่าข้าม
ช่วงที่ 1: Setup wizard — 9 หน้าจอก่อนจะได้เห็น Desktop (ภาพจริงจากเครื่องเรา ส.ค. 2026)
เครื่อง Spark มีหลายยี่ห้อ (ของ NVIDIA เอง, ASUS Ascent GX10, ฯลฯ) — หน้าจอ wizard จะขึ้นชื่อตามยี่ห้อ เช่น “Set Up Your ASUS Ascent GX10” แต่ขั้นตอนเหมือนกันทุกยี่ห้อ เพราะข้างในคือ DGX OS ตัวเดียวกัน ต่างแค่ชื่อเครื่องเริ่มต้น (
gx10-xxxxของ ASUS,spark-xxxxของ NVIDIA)
เวลาที่ควรเผื่อ: ~20–60 นาที (ส่วนใหญ่หมดไปกับขั้นดาวน์โหลดอัปเดตอัตโนมัติ — ขึ้นกับความเร็วเน็ต)
(ภาพประกอบทั้งหมดวาดขึ้นใหม่จากหน้าจอจริงเพื่อความคมชัด — หน้าตา/ข้อความตรงกับของจริง)
- Welcome — “Let’s setup your new personal AI supercomputer” → กด Get Started
- Select Language / Timezone — ภาษาเลือก
US - English(ยังไม่มีภาษาไทยให้เลือกในนี้ — ไปเพิ่มทีหลังใน Settings ได้), Timezone เลือกAsia - Bangkok→ Continue
3. Keyboard Layout — เลือก layout English (US) และ variant English (US) (คีย์บอร์ดไทยค่อยเพิ่มทีหลังเช่นกัน) → Continue
4. Terms Of Use — สัญญาอนุญาตซอฟต์แวร์ของ NVIDIA เลื่อนดูแล้วกด Accept
5. Create a new account for this device — ตั้ง username (แนะนำสั้นๆ ตัวพิมพ์เล็กล้วน) + password → Continue
– นี่คือบัญชีหลัก (sudo) ของเครื่อง — จด username/password ลงใบส่งมอบทันที ลืมแล้วกู้ยากมาก
– ปุ่ม Continue จะเป็นสีเทาจนกว่าจะกรอกครบและรหัสผ่านผ่านเงื่อนไขความซับซ้อน — ถ้ากดไม่ได้ให้ตั้งรหัสให้ยาวขึ้น/ผสมตัวเลข-อักขระพิเศษ แล้วกรอก Confirm ให้ตรงกัน
6. Help NVIDIA Improve Your Experience — ช่องส่ง telemetry ให้ NVIDIA 2 ช่อง (Analytics / Crash data) ระบบติ๊กมาให้แล้วทั้งคู่ — เอาออกได้ถ้าไม่ต้องการส่งข้อมูล ไม่กระทบการใช้งาน → Continue
7. Connect To Wi-Fi — เลือกเครือข่ายจากรายการ ใส่รหัส กด Join
– ⚠️ SSID ชื่อภาษาไทยจะแสดงเป็น ��������� อ่านไม่ออก (ฟอนต์หน้า wizard ยังไม่รองรับ) — ใช้เครือข่ายที่ตั้งชื่อเป็นภาษาอังกฤษ หรือถ้าจำเป็นให้เดาจากลำดับ/ความแรงสัญญาณ
– ขั้นนี้ข้ามไม่ได้ในทางปฏิบัติ เพราะขั้นถัดไปเครื่องต้องใช้เน็ตโหลดอัปเดต
8. Updating Your … — “Downloading and installing system updates” เครื่องโหลด+ติดตั้งอัปเดตระบบอัตโนมัติ มีแถบ progress
– ห้ามปิดเครื่อง/ถอดปลั๊กเด็ดขาด ปล่อยให้วิ่งจนจบ (บน WiFi อาจนานหลายสิบนาที — ปกติ)
- Almost Done! — “Please wait while your … is rebooted to complete setup” เครื่องรีบูตตัวเอง จะเห็นโลโก้ยี่ห้อ + แถบ progress อีกครั้ง (บางเครื่องมีอัปเดต firmware ช่วงนี้ด้วย — ปกติ ไม่ต้องกดอะไร) → เข้าหน้า login → ใส่รหัสผ่าน → ถึง Desktop ครั้งแรก
สิ่งที่จะเห็นหลังถึง Desktop (ของแถมจากระบบ ไม่ใช่ความผิดปกติ):
– Firefox เปิดหน้า build.nvidia.com/spark ให้อัตโนมัติ — รวม quickstart/playbook ของ NVIDIA (Ollama, ComfyUI, DGX Dashboard, vLLM) น่า bookmark เก็บไว้
– เครื่องมี DGX Dashboard เป็น service ในตัวที่ http://localhost:11000 — เวลาระบบแจ้งเตือนว่ามีอัปเดต (OTA) จะพาเข้าหน้านี้เพื่อดู Available Updates (การกดอัปเดตจากหน้านี้ = อัปเดตใหญ่ ควรอ่านหัวข้อ 1.10 ก่อน)
ช่วงที่ 2: เปิด Terminal ครั้งแรก (5 ขั้น — ขั้น 2–4 คือหัวใจ อย่าข้าม)
เครื่องเข้าสู่ DGX OS (Ubuntu ปรับแต่งโดย NVIDIA มาพร้อม driver/CUDA ครบ ไม่ต้องลงเอง) และต่อ WiFi แล้วจากช่วงที่ 1 (อยากเสียบสาย LAN เพิ่มก็ทำได้ที่ Settings → Network)
- เปิด Terminal — จดชื่อเครื่องก่อน (
hostname— รูปแบบspark-xxxxหรือgx10-xxxxตามยี่ห้อ) แล้วอัปเดตระบบ:
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y build-essential cmake git curl tmux htop rsync libcurl4-openssl-dev
(สองแพ็กเกจแรกสำคัญมากบนเครื่อง ARM — จะต้อง compile ของเองบ่อย และเครื่องเปล่าไม่มี gcc ซึ่งทำให้บาง engine พังแบบงงๆ เราเจอมาแล้ว — wizard เพิ่งอัปเดตให้รอบหนึ่งก็จริง แต่รอบนั้นเป็นอัปเดตของระบบ ไม่ได้ลงเครื่องมือ dev พวกนี้ให้)
- จด IP ของเครื่องตอนนี้เลย (อย่ารอ): หน้า Settings → Wi-Fi → กด ⚙ ข้างเครือข่ายที่ต่ออยู่ → จดค่า IPv4 Address (ถ้าต่อสาย LAN: Settings → Network → ⚙ เช่นกัน) — หรือพิมพ์ใน Terminal:
ip -br addr | grep -E 'wl|en' # บรรทัด wl... = WiFi, en... = สาย LAN — จด IP ไว้
- ทดสอบ ssh จากโน้ตบุ๊กทันที (ตอนนี้จอยังต่ออยู่ พลาดอะไรก็แก้ได้เลย): เปิด Terminal บนโน้ตบุ๊กที่ต่อเครือข่ายเดียวกัน:
ssh <user>@<IP ที่จดจากขั้น 2>
เข้าได้ = remote access ใช้งานได้จริง ✓
- สั่งรีบูตผ่าน ssh นั้นเลย:
sudo reboot
ขั้นนี้บังคับ ไม่ใช่ทางเลือก — full-upgrade เพิ่งอัปเดต NVIDIA driver การรีบูตทำให้ kernel โหลดโมดูลตัวใหม่ให้ตรงกัน (ถ้าข้ามขั้นนี้ nvidia-smi จะพัง — ดูหัวข้อ 1.10 Troubleshooting) และการรีบูตผ่าน ssh ก็คือการซ้อมใช้งานจริงครั้งแรกไปในตัว — รอ ~1 นาทีแล้ว ssh กลับเข้าไปใหม่ได้เลย
- หลังรีบูต ~1 นาที ssh กลับเข้าไปใหม่ แล้วเช็คสุขภาพเครื่อง — คำสั่งชุดนี้ควรรันได้ทุกตัวก่อนไปต่อ:
uname -m # ต้องได้ aarch64
nvidia-smi # เห็น GPU GB10 + CUDA version (ถ้าพังตรงนี้ = ยังไม่ได้รีบูตหลังอัปเกรด)
free -h # เห็น memory ~120GB
df -h / # ดิสก์เหลือเท่าไหร่ (รุ่น 4TB ควรว่าง ~3.6T)
hostname # ยืนยันชื่อเครื่องอีกรอบ (อัปเกรดใหญ่อาจเปลี่ยนชื่อ — ดู 1.10)
ครบทุกข้อ = เครื่องพร้อม, remote access พร้อม, IP อยู่ในมือ — จบพิธีเปิดกล่องอย่างสมบูรณ์
อ่านผล nvidia-smi ให้เป็น (หน้าตาบน Spark ไม่เหมือนการ์ดจอทั่วไป)
ตัวอย่าง output จริงจากเครื่องเรา พร้อมจุดที่ต้องดู:
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.173.02 Driver Version: 580.173.02 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GB10 On | 0000000F:01:00.0 On | N/A |
| N/A 48C P8 4W / N/A | Not Supported | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| 0 N/A N/A 7243 G /usr/lib/xorg/Xorg 67MiB |
| 0 N/A N/A 7394 G /usr/bin/gnome-shell 143MiB |
+-----------------------------------------------------------------------------------------+
| จุด | อ่านว่า | ค่าปกติ |
|---|---|---|
| ① Driver Version | เวอร์ชัน driver — สองตำแหน่งนี้ต้องตรงกัน (ถ้าเพี้ยนจะขึ้น error mismatch → ดู 1.10) | ตัวเลขชุดเดียวกัน |
| ② CUDA Version | รุ่น CUDA ของเครื่อง | 13.0 ขึ้นไป |
| ③ Name | ชื่อ GPU อยู่ตรงนี้ — ในตาราง ไม่ใช่หัวกระดาษ: NVIDIA GB10 |
ต้องเห็น GB10 |
| ④ Temp | อุณหภูมิ GPU | ว่าง ~40–50C, งานหนัก 70–85C |
| ⑤ Perf state | ระดับพลังงาน: P8 = พักผ่อน, P0 = ทำงานเต็มที่ |
เปลี่ยนตามโหลด |
| ⑥ Power | กำลังไฟที่ใช้ตอนนี้ (/N/A = ไม่แสดงเพดาน — ปกติบนเครื่องนี้) |
ว่าง ~4W, เต็มสูบหลายสิบ W |
⑦ Memory-Usage = Not Supported |
ไม่ใช่เครื่องเสีย! GB10 ใช้ unified memory — ไม่มี VRAM แยกให้รายงาน | ดูหน่วยความจำจาก free -h แทนเสมอ |
| ⑧ GPU-Util | เปอร์เซ็นต์การใช้งาน GPU | 0% ตอนว่าง / 90–100% ตอนโมเดลคิด |
ส่วนตาราง Processes ข้างล่าง: เห็น Xorg กับ gnome-shell กินไม่กี่ร้อย MiB เป็นเรื่องปกติ (หน้าจอ GUI ใช้ GPU วาดภาพ) — เวลารันโมเดลจะเห็น process ของ engine โผล่เพิ่มพร้อมตัวเลขหน่วยความจำก้อนใหญ่
สรุปวิธีเช็คเร็ว: เห็นตาราง = driver ปกติ / เห็น GB10 = เครื่องถูกตัว / Not Supported ตรง Memory = เรื่องปกติของ Spark อย่าตกใจ
อ่านผล free -h ให้เป็น (นี่แหละ “มิเตอร์ VRAM” ของ Spark)
เพราะ Spark ใช้ unified memory — หน่วยความจำก้อนเดียวเป็นทั้ง RAM และ VRAM — คำสั่งนี้จึงเป็นมิเตอร์หลักที่ต้องอ่านเป็นก่อนโหลดโมเดลทุกครั้ง เทียบ output จริงจากเครื่องเราสองสถานะ:
เครื่องเพิ่งเปิด ยังไม่โหลดอะไร:
total used free shared buff/cache available
Mem: 121Gi 11Gi 114Gi 13Mi 2.7Gi 110Gi
Swap: 15Gi 0B 15Gi
เครื่องเดียวกันขณะมีโมเดลโหลดอยู่ + ใช้งานมาทั้งวัน:
total used free shared buff/cache available
Mem: 121Gi 48Gi 29Gi 33Mi 53Gi 73Gi
Swap: 80Gi 13Mi 80Gi
สังเกตความต่าง: used โต 11→48Gi (โมเดลเข้ามาอยู่) และ buff/cache โต 2.7→53Gi (ใช้เครื่องไปเรื่อยๆ Linux ยืมที่ว่างเก็บ cache ไฟล์มากขึ้นเอง — ปกติ) แต่ available คือตัวที่บอกความจริงทั้งสองสถานะ
| คอลัมน์ | อ่านว่า | ตัวอย่างข้างบน |
|---|---|---|
total |
หน่วยความจำทั้งหมด (128GB เครื่องจะโชว์ ~121Gi หลังหักส่วนระบบ) | 121Gi |
used |
ที่ถูกใช้จริงตอนนี้ — โมเดลที่โหลดอยู่โผล่ตรงนี้ | 48Gi (มีโมเดลอยู่ตัวหนึ่ง) |
free |
ที่ว่างเปล่าจริงๆ — ตัวเลขนี้หลอก! อย่าใช้ตัดสิน | 29Gi |
buff/cache |
Linux ยืมที่ว่างมาเก็บ cache ไฟล์ — คืนให้ทันทีเมื่อโปรแกรมต้องการ | 53Gi |
available |
★ ตัวเลขเดียวที่ควรดู = free + cache ที่คืนได้ ≈ ที่เหลือให้โมเดลตัวถัดไปจริงๆ | 73Gi |
แถว Swap |
หน่วยความจำสำรองบนดิสก์ (ช้ามาก) — ค่า used ควรเกือบศูนย์เสมอ | 13Mi ✓ |
สองกฎที่ใช้ทุกวัน:
- จะโหลดโมเดลเพิ่ม → ดู
availableไม่ใช่free— มือใหม่เห็น free 29Gi แล้วคิดว่าโหลดโมเดล 40GB ไม่ได้ ทั้งที่จริง available มี 73Gi (cache จะหลบให้เอง) — นี่คือพฤติกรรมปกติของ Linux: ความจำว่างจะถูกยืมไปทำ cache เสมอ “free ต่ำ” ไม่ใช่ปัญหา - Swap used พุ่งเป็น GB ขณะรันโมเดล = สัญญาณอันตราย — แปลว่ายัดเกินเครื่องแล้ว ระบบกำลังหน่วงหนัก (ทุกอย่างช้าลง 10–100 เท่า) → ปิดโมเดลบางตัวหรือใช้ quant เล็กลง
ดูสด ๆ ระหว่างโหลดโมเดล: watch -n 2 free -h — จะเห็น used ค่อยๆ บวมขึ้นตามโมเดลที่กำลังโหลด สนุกดีและได้ intuition เร็วมาก
ปรับ swap ให้พอสำหรับงานโมเดลใหญ่ (ทำครั้งเดียว — แนะนำทำเลยตอน setup)
ค่าจากโรงงานให้ swap มาแค่ ~15GB ซึ่งเล็กเกินไปสำหรับเครื่องที่จะโหลดโมเดลระดับ 50–100GB: จังหวะที่โมเดลใหญ่กำลังโหลด/สลับตัว หน่วยความจำจะตึงชั่วขณะและระบบต้องการที่ระบาย — swap เล็กทำให้เคลียร์ cache ไม่ทัน อาการคือเครื่องอืดหนัก ค้าง หรือโปรแกรมโดนฆ่ากลางทาง (OOM) ทั้งที่ความจริงแค่ต้องการพื้นที่หายใจชั่วคราว
แนะนำ: เพิ่ม swapfile อีก ~64GB (วิธีเดียวกับเครื่องเราจริงๆ — เพิ่มไฟล์ใหม่ ไม่แตะของเดิม ปลอดภัยและถอนง่าย) — ดิสก์หายไป 64GB แลกกับความนิ่ง คุ้มมาก:
sudo fallocate -l 64G /swapfile-models # สร้างไฟล์ 64GB
sudo chmod 600 /swapfile-models # จำกัดสิทธิ์ (จำเป็น)
sudo mkswap /swapfile-models # ฟอร์แมตเป็น swap
sudo swapon /swapfile-models # เปิดใช้ทันที
echo '/swapfile-models none swap sw 0 0' | sudo tee -a /etc/fstab # ให้รอดรีบูต
swapon --show # ตรวจ: ต้องเห็น 2 บรรทัด (ของเดิม + ตัวใหม่ 64G)
free -h # แถว Swap รวมควรเป็น ~79Gi
ความเข้าใจที่ถูกต้องเรื่อง swap ใหญ่: มันคือเข็มขัดนิรภัย ไม่ใช่ RAM เพิ่ม — การใช้งานปกติค่า Swap used ควรใกล้ศูนย์เหมือนเดิม (ตามกฎในหัวข้อ free -h) ประโยชน์ของมันคือรับแรงกระแทกชั่วคราวตอนโหลด/สลับโมเดลไม่ให้ระบบล้ม ถ้าเห็น swap ถูกใช้หลาย GB ค้างตลอดเวลาที่โมเดลรัน = ยัดเกินเครื่องจริง ต้องลดขนาดโมเดลอยู่ดี
(ถอนคืนถ้าต้องการ: sudo swapoff /swapfile-models && sudo rm /swapfile-models แล้วลบบรรทัดใน /etc/fstab)
อ่านผล df -h ให้เป็น (ดิสก์ — ทรัพยากรที่โมเดลกินเร็วที่สุด)
output จริงจากเครื่องเรา (รุ่นดิสก์ 1TB) — มือใหม่เห็นครั้งแรกมักตกใจว่าทำไมบรรทัดเยอะจัง:
Filesystem Size Used Avail Use% Mounted on
tmpfs 13G 2.8M 13G 1% /run ← ไม่ใช่ดิสก์ (RAM จำลอง) — ข้าม
efivarfs 256K 37K 220K 15% /sys/firmware/efi/efivars ← ระบบ boot — ห้ามยุ่ง
/dev/nvme0n1p2 916G 405G 464G 47% / ← ★ ดิสก์จริงของเรา — ดูบรรทัดนี้บรรทัดเดียว
tmpfs 61G 0 61G 0% /dev/shm ← RAM จำลอง — ข้าม
tmpfs 5.0M 8.0K 5.0M 1% /run/lock ← ข้าม
/dev/nvme0n1p1 511M 6.4M 505M 2% /boot/efi ← ระบบ boot — ห้ามยุ่ง
tmpfs 13G 124K 13G 1% /run/user/1000 ← ข้าม
กติกาการอ่าน:
- สนใจบรรทัดเดียว: บรรทัดที่ Mounted on เป็น
/(ชื่อ Filesystem จะเป็น/dev/nvme...) — นั่นคือ SSD จริงของเครื่อง อ่านสามค่า: Size (ความจุรุ่นเครื่อง เช่น 916G = รุ่น 1TB, ~3.6T = รุ่น 4TB), Avail (เหลือใช้จริง), Use% (ใช้ไปกี่เปอร์เซ็นต์) - บรรทัด
tmpfsทั้งหลาย = พื้นที่จำลองใน RAM ที่ระบบใช้ภายใน — ไม่ใช่ดิสก์ ไม่ต้องอ่าน /efivarfsกับ/boot/efi= ส่วนของระบบ boot — รู้ว่ามีอยู่ก็พอ - ทางลัดตัดความรก:
df -h /— แสดงเฉพาะบรรทัดที่สำคัญบรรทัดเดียวจบ (คู่มือนี้ใช้แบบนี้ตลอด)
ทำไมต้องดูบ่อย: โมเดลกินดิสก์เร็วกว่าที่คิดมาก — DeepSeek 2-bit ตัวเดียว ~80GB, Qwen3.6 ~20GB, ยังไม่รวม docker image และ cache ที่งอกเงียบๆ เครื่องรุ่น 1TB โหลดโมเดลใหญ่ 3–4 ตัวก็ตึงแล้ว กฎที่ใช้: เช็ค Avail ก่อนดาวน์โหลดทุกครั้ง (ไฟล์ที่จะโหลด + เผื่อ 20%) และอย่าปล่อยให้ Use% เกิน ~90% — ดิสก์เต็มทำให้ทุกอย่างพังแบบงงๆ (ดาวน์โหลดค้าง, โมเดลเซฟไม่ได้, ระบบอืด)
เมื่อดิสก์ใกล้เต็มแล้วหาไม่เจอว่าอะไรกิน:
du -xh --max-depth=1 ~ | sort -rh | head # ไล่ดูว่าโฟลเดอร์ไหนในบ้านเรากินเท่าไหร่ เรียงจากใหญ่สุด
ผู้ต้องสงสัยประจำ: ~/models/, ~/.cache/huggingface/ (cache ดาวน์โหลดที่มักถูกลืม), และ docker images (docker system df)
1.2 เข้าเครื่องจากคอมตัวเอง: SSH + mDNS
หลังต่อเน็ตแล้ว เครื่องจะประกาศชื่อตัวเองในวง LAN ผ่าน mDNS — จากเครื่อง Mac/Linux ในบ้านเดียวกัน:
ssh <user>@spark-xxxx.local
ใช้ชื่อ .local เสมอ อย่าจำ IP — IP เป็น DHCP และเปลี่ยนได้ (เราโดนมาแล้ว: IP เปลี่ยนแล้ว config ที่ fix IP ไว้พังเป็นแถบ) ถ้าอยากจบปัญหาถาวรให้ตั้ง DHCP reservation ที่ router
ติด SSH key จะได้ไม่ต้องพิมพ์รหัสทุกครั้ง:
ssh-copy-id <user>@spark-xxxx.local
กฎเหล็กข้อแรกของการทำงานผ่าน SSH: งานยาวต้องรันใน tmux เสมอ — สำคัญจนแยกเป็นหัวข้อของตัวเองในข้อถัดไป
1.3 tmux — เครื่องมือบังคับเมื่อทำงานผ่าน SSH (อ่านให้จบก่อนรันงานยาวครั้งแรก)
ทำไมถึง “บังคับ” ไม่ใช่ “แนะนำ”
เวลา ssh เข้าเครื่อง โปรแกรมทุกตัวที่รันจะผูกชีวิตติดกับ session ssh นั้น — WiFi กระตุกสองวินาที, ปิดฝาโน้ตบุ๊ก, sleep — ssh หลุดเมื่อไหร่ งานที่รันอยู่ตายทันที ลองนึกภาพ: ดาวน์โหลดโมเดล 80GB มาแล้ว 5 ชั่วโมง เหลืออีก 10 นาที แล้วแมวเดินเหยียบ router… บนเครื่องที่ต่อ WiFi แบบ Spark เรื่องแบบนี้ไม่ใช่ “ถ้า” แต่คือ “เมื่อไหร่”
tmux แก้เรื่องนี้ด้วยแนวคิดเดียว: จอทำงานของเราอยู่บนเครื่อง Spark ไม่ใช่บนเครื่องที่ ssh มา — ssh เป็นแค่ “หน้าต่างแอบดู” จอนั้น หลุดแล้วจอจริงยังอยู่ งานยังวิ่ง กลับมาเปิดหน้าต่างดูใหม่เมื่อไหร่ก็ได้
คำถามแรกของมือใหม่: แล้วพิมพ์คำสั่ง tmux ที่ไหน — ต้อง ssh ก่อนไหม?
ต้อง ssh ก่อนเสมอ — tmux เป็นโปรแกรมที่รันบนเครื่อง Spark ไม่ใช่บนโน้ตบุ๊กเรา ลำดับที่ถูกคือ:
[โน้ตบุ๊กเรา] [เครื่อง Spark]
เปิด Terminal
└─ ssh user@spark-xxxx.local ──────→ ได้ shell ของ Spark
(สังเกต prompt เปลี่ยนเป็น user@spark-xxxx)
└─ พิมพ์ tmux new -s dl ← พิมพ์ตรงนี้!
└─ รันงานยาวในนั้น
└─ Ctrl+b, d (detach)
└─ ปิดโน้ตบุ๊กได้เลย ✓ งานยังวิ่งต่อบน Spark ✓
--- วันถัดมา ---
เปิด Terminal ใหม่
└─ ssh user@spark-xxxx.local ──────→ (ssh รอบใหม่ คนละอันกับเมื่อวาน — ไม่เป็นไร)
└─ tmux attach -t dl → งานเดิมอยู่ครบ
วิธีเช็คง่ายๆ ว่ากำลังพิมพ์อยู่ฝั่งไหน: ดู prompt — ถ้าขึ้น user@spark-xxxx:~$ คืออยู่บน Spark (พิมพ์ tmux ได้) ถ้าเป็นชื่อเครื่องตัวเองคือยังไม่ได้ ssh — และจำง่ายๆ ว่า ssh คือประตู, tmux คือโต๊ะทำงานหลังประตูนั้น ประตูปิด (ssh หลุด) โต๊ะไม่หาย เปิดประตูใหม่ก็เจอโต๊ะเดิม
ศัพท์ 3 คำที่ต้องรู้
- Session = โต๊ะทำงานหนึ่งตัว มีชื่อเรียก (เช่น
dl,train) — อยู่ถาวรจนกว่าจะสั่งปิดหรือรีบูตเครื่อง - Window = แท็บบนโต๊ะนั้น (เหมือนแท็บ browser) — หนึ่ง session มีได้หลาย window
- Attach / Detach = เปิดหน้าต่างเข้าไปดูโต๊ะ / ถอยออกมาโดยโต๊ะยังอยู่
ชุดคำสั่งที่ใช้จริง 90% ของเวลา
tmux new -s dl # เปิดโต๊ะใหม่ชื่อ "dl" (แล้วเข้าไปอยู่ในนั้นเลย)
# ... สั่งดาวน์โหลด/เทรน/build อะไรก็ว่าไป ...
# ออกจากโต๊ะโดยงานยังวิ่งต่อ (detach):
# กด Ctrl+b แล้วปล่อย แล้วกด d
tmux ls # อยู่ข้างนอก: ดูว่ามีโต๊ะอะไรเปิดอยู่บ้าง
tmux attach -t dl # กลับเข้าโต๊ะ "dl" — เห็นทุกอย่างที่เกิดขึ้นระหว่างเราไม่อยู่
tmux kill-session -t dl # ปิดโต๊ะเมื่องานจบแล้วจริงๆ
เรื่องปุ่ม Ctrl+b (เรียกว่า prefix): ทุกคำสั่งคีย์บอร์ดของ tmux เริ่มด้วยการกด Ctrl+b ปล่อยก่อน แล้วค่อยกดตัวอักษรคำสั่ง — มันคือการบอกว่า “ปุ่มถัดไปคุยกับ tmux นะ ไม่ใช่กับโปรแกรมในจอ”
สถานการณ์จริง 3 แบบ
แบบที่ 1 — ดาวน์โหลดโมเดลข้ามคืน:
tmux new -s dl
HF_HUB_DISABLE_XET=1 hf download unsloth/DeepSeek-V4-Flash-0731-GGUF --include "*UD-IQ2_M*" --local-dir ~/models/gguf/deepseek-0731/
# กด Ctrl+b, d → ปิดโน้ตบุ๊กไปนอนได้เลย
# ตื่นเช้า: ssh เข้ามา → tmux attach -t dl → เห็นผลลัพธ์
แบบที่ 2 — รัน server ทดลองก่อนทำเป็น systemd:
tmux new -s llm
~/llama.cpp/build/bin/llama-server -m ~/models/gguf/... --port 8080 ...
# Ctrl+b, d → server รันต่อ ทดสอบยิง API จากเครื่องอื่นได้
# อยากดู log สด: attach กลับเข้าไป
(server ที่ใช้ประจำควร “จบการทดลอง” ด้วยการย้ายไป systemd — tmux คือสนามซ้อม ไม่ใช่บ้านถาวร เพราะไม่รอดรีบูต)
แบบที่ 3 — งานเทรนที่อยากเหลือบดูเป็นระยะ:
tmux new -s train
python train.py 2>&1 | tee train.log # tee = เห็นบนจอด้วย เก็บลงไฟล์ด้วย
# Ctrl+b, d → ระหว่างวัน ssh มา attach ดู loss แล้ว detach ออกกี่รอบก็ได้
การเลื่อนดูข้อความเก่า (จุดที่มือใหม่งงที่สุด)
ใน tmux การ scroll ด้วยเมาส์/trackpad ตรงๆ มักไม่ทำงาน — ต้องเข้า copy mode: กด Ctrl+b แล้วกด [ จากนั้นเลื่อนด้วยลูกศร/PageUp หรือพิมพ์ ? เพื่อค้นหาย้อนหลัง เสร็จแล้วกด q ออก — หรือถ้าชอบใช้เมาส์: tmux set -g mouse on (พิมพ์ครั้งเดียวใน session, scroll ได้เลย)
ตาราง cheat sheet ติดโต๊ะ
| อยากทำอะไร | คำสั่ง/ปุ่ม |
|---|---|
| เปิดโต๊ะใหม่ชื่อ X | tmux new -s X |
| ออกโดยงานวิ่งต่อ | Ctrl+b แล้ว d |
| ดูรายชื่อโต๊ะ | tmux ls |
| กลับเข้าโต๊ะ X | tmux attach -t X |
| เปิดแท็บใหม่ในโต๊ะ | Ctrl+b แล้ว c |
| สลับแท็บ | Ctrl+b แล้ว n (next) / ตัวเลข |
| แบ่งจอซ้ายขวา | Ctrl+b แล้ว % (บนล่าง: ") |
| ย้าย pane | Ctrl+b แล้วลูกศร |
| เลื่อนดูของเก่า | Ctrl+b แล้ว [ (ออก: q) |
| ปิดโต๊ะทิ้ง | tmux kill-session -t X |
ข้อผิดพลาดยอดฮิต
- ลืมว่าตัวเองอยู่ใน tmux แล้วเปิด tmux ซ้อน — สังเกตแถบสีเขียวขอบล่างจอ: มีแถบ = อยู่ใน tmux แล้ว อย่า
tmux newซ้ำ - พิมพ์
exitเพื่อจะ detach —exitคือปิดโต๊ะทิ้ง (งานตาย!) — ถ้าจะออกเฉยๆ ต้องCtrl+b, dเท่านั้น - เก็บ server ถาวรไว้ใน tmux — รีบูตเมื่อไหร่หายหมดแบบเงียบๆ (เราเคยเจอ demo หายหลังรีบูตเพราะเรื่องนี้) — ใช้ประจำ = systemd
- ตั้งชื่อ session มั่ว — ตั้งชื่อตามงาน (
dl,train,llm) แล้วจะขอบคุณตัวเองตอนtmux lsเจอ 5 sessions
1.4 ย้ายเครื่อง/เข้าเครื่องไม่ได้: ต่อจอแล้วตั้ง WiFi — จบทุกกรณี
หลักการของหัวข้อนี้: เครื่องที่ส่งมอบถูกตั้งโปรไฟล์ WiFi เริ่มต้น ไว้แล้ว (SSID และรหัสผ่านอยู่ในใบส่งมอบ/หน้าแรกของคู่มือเล่มนี้) — โปรไฟล์นี้คือกุญแจสำรองของเครื่อง: ไม่ว่าหอบเครื่องไปที่ไหน เราเรียกมันกลับมาหาเราได้เสมอ ห้ามลบหรือแก้โปรไฟล์นี้เด็ดขาด และคู่มือนี้จงใจไม่สอนคำสั่งแก้ไข WiFi ของเครื่อง — เพื่อไม่ให้กุญแจสำรองดอกนี้ถูกทำหายโดยไม่ตั้งใจ
สถานการณ์: ย้ายเครื่องไปบ้านใหม่/ออฟฟิศ/หน้างานลูกค้า → เครื่องไม่รู้จัก WiFi ที่นั่น → เปิดมาก็หายเงียบ เข้าไม่ได้
วิธีแก้หลัก (ง่ายสุด ทำเองได้เสมอ): ต่อจอ + คีย์บอร์ด + เมาส์
- เสียบจอ (HDMI), คีย์บอร์ด, เมาส์ เข้าเครื่อง Spark
- ต่อ WiFi ของสถานที่ใหม่ผ่านหน้า Settings → Wi-Fi ตามปกติ (การเพิ่มเครือข่ายใหม่ไม่กระทบโปรไฟล์เริ่มต้น)
- ดู IP WiFi ของเครื่อง: Settings → Wi-Fi → กดไอคอน ⚙ ข้างชื่อเครือข่ายที่ต่ออยู่ → ดูค่า IPv4 Address (หรือเปิด Terminal พิมพ์
ip -br addrแล้วดูบรรทัดwl...ตามหัวข้อ “ทักษะเอาตัวรอด” ข้างล่าง) - กลับไปที่โน้ตบุ๊ก (ต่อ WiFi วงเดียวกัน) →
ssh <user>@<IP ที่จดมา>— ใช้งานต่อได้เลย
ทางเลือกเมื่อไม่มีจอติดตัว: เปิด Personal Hotspot บนมือถือ ตั้งชื่อ+รหัสให้ตรงกับ WiFi เริ่มต้นในใบส่งมอบเป๊ะๆ → เครื่องต่อเข้ามาเอง → ดู IP จากหน้าจอ hotspot ของมือถือ → ssh ด้วย IP นั้น / หรือเสียบสาย LAN (RJ45) จาก router ก็ได้ IP ทันทีเช่นกัน
(ถ้าตั้ง Tailscale ไว้ตามหัวข้อ 1.5 — ขั้นที่ 3–4 ยิ่งง่าย: พอเครื่องออกเน็ตได้ผ่าน hotspot ก็ ssh ด้วยชื่อ ts.net ได้ทันที ไม่ต้องหา IP ด้วยซ้ำ)
หมายเหตุสำหรับทีมส่งมอบ (ภายใน): ก่อนส่งทุกเครื่อง — ตั้งโปรไฟล์ WiFi เริ่มต้น + ทดสอบว่าเครื่อง auto-join hotspot ชื่อนั้นจริง + จด SSID/รหัสลงใบส่งมอบ
ทักษะเอาตัวรอด: ดู IP ของเครื่องให้เป็น
เมื่อชื่อ .local ใช้ไม่ได้ (ซึ่งจะเกิดสักวัน) เส้นทางสำรองคือ ssh ด้วย IP ตรงๆ — แต่ต้องรู้ว่า IP ไหนคือ “IP WiFi” เพราะเครื่อง Spark มีหลาย interface และหลาย IP พร้อมกัน:
ip -br addr
lo UNKNOWN 127.0.0.1/8 ← ตัวเครื่องเอง (ไม่ใช้)
enp1s0f1np1 UP 192.168.100.1/24 ← สาย cluster (ถ้ามี)
wlP9s9 UP 192.168.0.156/24 ← ★ WiFi — ตัวนี้แหละที่ใช้ ssh
docker0 UP 172.17.0.1/16 ← เครือข่ายภายใน docker (ไม่ใช้)
วิธีอ่าน: มองหาบรรทัดที่ชื่อ interface ขึ้นต้นด้วย wl (wireless) — IP ในบรรทัดนั้นคือ IP WiFi / ระวังคำสั่ง hostname -I ที่หลายคนชอบใช้: มันพ่นทุก IP รวมกันไม่บอกว่าอันไหนเป็นอะไร มือใหม่มักคว้า IP ผิดตัว (เช่น 172.17.x.x ของ docker) ไป ssh แล้วงงว่าทำไมไม่ติด
เกร็ดจากเหตุการณ์จริงของเรา: บางครั้ง .local เหมือนพัง แต่จริงๆ คือเครื่องหลายเครือข่ายประกาศ IP ผิดวงออกไป (mDNS ตอบ IP ของสาย cluster ที่โน้ตบุ๊กเราไปไม่ถึง) — วิธีเช็คจากฝั่งโน้ตบุ๊ก: ping spark-xxxx.local ดูว่า IP ที่ตอบมาตรงกับ IP WiFi ของเครื่องไหม ถ้าไม่ตรง ให้ ssh ด้วย IP WiFi ตรงๆ ไปก่อน แล้วแก้ที่ config avahi (deny-interfaces ตัด interface ที่ไม่ใช่ WiFi ออก)
คำเตือนจากประสบการณ์จริง: อย่าแก้ network config ทิ้งไว้แบบไม่ทดสอบ — mDNS/WiFi ที่ทำงานอยู่คือเส้นเลือดเดียวของเครื่อง headless พังแล้วต้องหอบจอไปต่อสถานเดียว และถ้าใช้ WiFi เป็นเน็ตหลัก: การโหลดไฟล์ใหญ่ให้จำกัดจำนวน connection (เช่นใช้ aria2c -x4 แทนการเปิดหลาย connection พร้อมกัน) เพราะ WiFi อิ่มแล้วทั้งเครื่องจะช้าจนเหมือนค้าง
1.5 เข้าเครื่องจากนอกบ้าน: Tailscale
ทุกวิธีที่ผ่านมา (.local, VNC) มีข้อจำกัดเดียวกัน: ใช้ได้เฉพาะเมื่ออยู่ในเครือข่ายเดียวกับเครื่อง — ออกไปร้านกาแฟ ไปออฟฟิศ อยู่ต่างจังหวัด = เข้าเครื่องไม่ได้ วิธีแก้แบบเก่า (forward port ที่ router, fix IP) ทั้งยุ่งและเสี่ยงเปิดเครื่องให้ทั้งอินเทอร์เน็ตเห็น
Tailscale คือทางที่เราใช้จริง: มันสร้าง “เครือข่ายส่วนตัว” เชื่อมอุปกรณ์ทุกตัวของเรา (Spark, โน้ตบุ๊ก, มือถือ) ถึงกันโดยตรงแบบเข้ารหัส ไม่ว่าแต่ละตัวอยู่ที่ไหนในโลก — ไม่ต้องตั้งค่า router, เครื่องอยู่หลัง NAT/WiFi หอพักก็ถึงกัน, และคนนอกมองไม่เห็นอะไรเลย (ฟรีสำหรับใช้ส่วนตัว)
# บนเครื่อง Spark (มี ARM64 build อย่างเป็นทางการ):
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up # จะให้ลิงก์ไป login (บัญชี Google/GitHub/MS ก็ได้)
tailscale status # เห็นชื่อ+IP ของเครื่องในเครือข่ายเรา
แล้วลงแอป Tailscale บนโน้ตบุ๊ก/มือถือ ล็อกอินบัญชีเดียวกัน — เท่านี้จากที่ไหนก็ได้:
ssh <user>@spark-xxxx.tailXXXX.ts.net # ชื่อ MagicDNS ถาวร (ดูจาก tailscale status)
# หรือใช้ IP 100.x.x.x ที่ Tailscale แจก — ไม่เปลี่ยนตาม DHCP!
จุดที่มีประโยชน์เกิน ssh: ทุก service บนเครื่องใช้ผ่านชื่อนี้ได้หมด — Web UI ของโมเดล, API endpoint, VNC — เช่นเราเปิดหน้า ComfyUI จากนอกบ้านด้วย http://spark-xxxx.tailXXXX.ts.net:8188 เป็นประจำ และเพราะเป็นเครือข่ายปิด ไม่ต้องกังวลว่า endpoint ที่ไม่มีรหัสผ่านจะโดนคนแปลกหน้าเจอ
เคล็ดสำหรับเครื่อง server แบบ Spark: ไปที่ admin console (เว็บของ Tailscale) → เครื่อง Spark → Disable key expiry — ไม่งั้นทุก ~6 เดือน key หมดอายุแล้วต้องมา login ใหม่ (ถ้าลืมตอนอยู่ไกลเครื่องคือจบ)
1.6 GUI ระยะไกล (VNC) — สำหรับคนที่อยากเห็นหน้าจอ
DGX OS มี GNOME desktop เต็มรูปแบบ ใช้ผ่าน VNC ได้:
sudo apt install -y tigervnc-standalone-server
vncpasswd # ตั้งรหัส VNC
# รัน VNC บนจอจริง (display :1) ให้เข้าถึงจาก LAN:
x0vncserver -display :1 -rfbport 5900 -PasswordFile=~/.vnc/passwd -SecurityTypes VncAuth -AlwaysShared &
จากเครื่องอื่นใช้ VNC client (บน Mac: Finder → Cmd+K → vnc://spark-xxxx.local:5900)
ความจริงจากการใช้งาน: ~95% ของงานทำผ่าน SSH สะดวกกว่า GUI มีไว้กรณีเฉพาะ (ดู browser ในเครื่อง, app มี UI) — ถ้าไม่ใช้เลยก็ไม่ต้องตั้ง
1.7 Docker + NVIDIA runtime (ทำครั้งเดียว ใช้ตลอด)
หลาย engine/เครื่องมือมาเป็น container การให้ container เห็น GPU ต้องลงทะเบียน NVIDIA runtime ก่อน — เครื่องใหม่ยังไม่ได้ตั้งค่านี้ (เราก็เจอตอน compose ตัวแรก: unknown or invalid runtime name: nvidia):
sudo usermod -aG docker $USER # ให้ user ใช้ docker ได้ (logout/login ใหม่)
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker # ⚠️ container ที่รันอยู่จะ restart ด้วย
# ทดสอบ:
docker run --rm --runtime=nvidia nvcr.io/nvidia/cuda:13.0.0-base-ubuntu24.04 nvidia-smi
1.8 กฎเหล็กของเครื่อง ARM (aarch64) — อ่านก่อนติดตั้งทุกอย่าง
นี่คือจุดที่ผู้ใช้ใหม่เจ็บบ่อยที่สุด สรุปจากแผลจริงของเรา:
- Binary/wheel ที่ compile สำหรับ x86 ใช้ไม่ได้เด็ดขาด — ก่อนลงอะไรถามเสมอว่า “มี ARM64 build ไหม” ถ้าไม่มีต้อง compile จาก source
- PyTorch ต้องเป็นรุ่น CUDA 13 aarch64 — ใช้ container ของ NVIDIA (
nvcr.io/nvidia/pytorch) หรือ wheelcu130เท่านั้น pip ธรรมดาอาจได้รุ่น CPU - Python venv แยกทุกโปรเจกต์ — อย่าลงแพ็กเกจปนกับระบบ (เราเคยเจอ numpy คนละรุ่นชนกับแพ็กเกจระบบ ไล่แก้กันหลายชั่วโมง)
- GPU นี้คือ sm_121 — software บางตัวยัง JIT/compile ให้ไม่ได้ (เช่น torch.compile ยังใช้ไม่ได้ ให้ปิด) ถ้าเจอ “no kernel image available” คือเรื่องนี้
pkill -f <ชื่อ>ระวัง match คำสั่งตัวเอง/session ssh ตัวเอง — ใช้ pattern แคบๆ และดูpgrep -afก่อนฆ่าเสมอ (ทีมเราโดนมา 4 รอบในวันเดียว)
1.9 Checklist ก่อนไปบทถัดไป
- [ ]
uname -m= aarch64,nvidia-smiเห็น GB10 - [ ] ssh จากเครื่องตัวเองด้วยชื่อ
.local+ key ติดแล้ว - [ ] ใช้ tmux เป็น: new / detach (Ctrl+b,d) / attach — และรู้ว่า exit ≠ detach
- [ ] docker รัน
--runtime=nvidiaผ่าน - [ ] (แนะนำ) Tailscale ติดแล้ว — ทดสอบ ssh ผ่านชื่อ ts.net จากมือถือ (ปิด WiFi ใช้ 4G) หนึ่งครั้ง
- [ ] รู้ว่าดิสก์เหลือเท่าไหร่ (โมเดลจะกินที่เร็วมาก — เช็ค
df -h /ก่อนดาวน์โหลดโมเดลทุกครั้ง)
1.10 เมื่อมีปัญหา (Troubleshooting)
อาการ: nvidia-smi ขึ้น Failed to initialize NVML: Driver/library version mismatch
ดูเหมือนเครื่องเสีย แต่จริงๆ ไม่ใช่ — สาเหตุ: มีการอัปเดต NVIDIA driver (จาก full-upgrade หรือ auto-update) แล้วยังไม่ได้รีบูต โมดูลเก่าใน kernel เลยไม่ตรงกับ library ตัวใหม่บนดิสก์
วิธีแก้: sudo reboot — จบ (เจอบ่อยกับเครื่องที่เปิดต่อเนื่องนานๆ เพราะระบบ auto-update ทำงานเงียบๆ)
อาการ: หลังอัปเดตใหญ่ ชื่อเครื่องเปลี่ยนเอง (เช่น gx10-xxxx กลายเป็น spark-xxxx)
สาเหตุ: อัปเดตใหญ่ของ DGX OS เปลี่ยน template ชื่อเครื่องอัตโนมัติ ทำให้ระบบที่อ้างชื่อเดิม (.local, config ต่างๆ) หาเครื่องไม่เจอ
วิธีแก้: ใช้ชื่อใหม่ (เช็คด้วยคำสั่ง hostname บนเครื่อง) แล้วไล่อัปเดตที่ที่อ้างชื่อเก่า
วิธีกันเกิดซ้ำ: ตรึงชื่อด้วย sudo hostnamectl set-hostname <ชื่อที่ต้องการ> — ชื่อที่ตั้งมือจะไม่ถูกเปลี่ยนทับอีก
อาการ: ssh หาเครื่องไม่เจอเลยทุกชื่อทุก IP แต่มือถือเห็น WiFi ชื่อ spark-xxxx โผล่มา
สาเหตุ (เจอกับตัวเรา): อัปเดตใหญ่ล้าง WiFi profile ทั้งหมด เครื่องเลยถอยเข้าโหมดแกะกล่อง — เปิด hotspot ตั้งค่าของตัวเองแทนการต่อเน็ต (มี service เฝ้าเปิด hotspot ซ้ำด้วย ปิดเฉยๆ ไม่อยู่)
วิธีแก้: ต่อจอ + คีย์บอร์ด + เมาส์ → เครื่องจะพาเข้าหน้าตั้งค่าเหมือนเปิดครั้งแรก ทำตามหน้าจอจนจบ (ต่อ WiFi ใหม่) → ดู IP WiFi (หัวข้อ 1.4) → ใช้งานต่อได้ตามปกติ
สำหรับผู้ดูแลระบบที่ถนัด terminal และอยากปิด hotspot ให้ขาดด้วยมือ:
sudo systemctl disable --now dgx-oobe-hotspot-watchdog.service dgx-oobe-hotspot.service
sudo nmcli connection delete OobeHotspot
sudo nmcli device wifi rescan && sleep 4
sudo nmcli device wifi connect "ชื่อWiFi" password "รหัสผ่าน"
เสร็จแล้วอย่าลืมสร้างโปรไฟล์ WiFi เริ่มต้น (ตามใบส่งมอบ) กลับคืนด้วย — ไม่งั้นกุญแจสำรองของหัวข้อ 1.4 จะหายถาวร
อาการ: .local ใช้ไม่ได้ / ping แล้วได้ IP แปลกๆ ที่เข้าไม่ถึง
ดูหัวข้อ “ทักษะเอาตัวรอด: ดู IP ของเครื่องให้เป็น” ใน 1.4 — ส่วนใหญ่คือเครื่องหลายเครือข่ายประกาศ IP ผิดวง ทางลัดคือ ssh ด้วย IP WiFi ตรงๆ