ผมเปิด PHPStorm แค่เพื่อเข้าเซิร์ฟเวอร์ของโปรเจกต์
หลังจากย้ายงาน git มาไว้ใน terminal ด้วย git-prompt แล้ว ยังมีอีกเรื่องที่ผมต้องเปิด PHPStorm อยู่ คือหน้า Deployment หรือ Remote Host ที่จำไว้ว่าโปรเจกต์นี้ deploy ไปที่เซิร์ฟเวอร์ไหน โฟลเดอร์อะไร อยากดู log บน production ก็เปิดโปรเจกต์ใน PHPStorm คลิกเข้าไปหาไฟล์ อยากแก้ .env บน UAT ก็ทำแบบเดียวกัน บางวันเปิด IDE ขึ้นมาแค่เพื่อเรื่องนี้เรื่องเดียว
ส่วนการ ssh เข้าไปเองก็ต้องจำว่าโปรเจกต์นี้อยู่ที่ alias ไหน แล้วต้อง cd ไปโฟลเดอร์ไหนต่อ ซึ่งยิ่งมีหลายโปรเจกต์ยิ่งจำยาก เพราะโปรเจกต์หนึ่งมีได้หลายเซิร์ฟเวอร์ ทั้ง QAS, UAT และ production ส่วนเซิร์ฟเวอร์หนึ่งก็รับได้หลายโปรเจกต์ หรือมี production กับ UAT วางคู่กันในเครื่องเดียว คนละโฟลเดอร์

shop-api มีสามที่ deploy อยู่บนสองเครื่อง ส่วนเครื่อง shop-qas รับทั้ง shop-api และ shop-web
เลยทำ rh ขึ้นมา (ย่อจาก remote host) เหมือนตอนที่ทำ g แทนหน้าต่าง branch ของ PHPStorm พิมพ์ rh ในโฟลเดอร์โปรเจกต์ จะเห็นแค่เซิร์ฟเวอร์ของโปรเจกต์นั้น

กด Enter ที่ target ไหน ก็ได้ shell บนเครื่องนั้นที่ cd เข้าโฟลเดอร์ deploy ให้แล้ว
ติดตั้งครั้งเดียว ใช้ได้ทุกโปรเจกต์
remote-prompt เป็นโปรแกรม command line ที่ติดตั้งผ่าน composer global เหมือน git-prompt ไม่ต้องลงในแอป จึงใช้ได้ทั้งโปรเจกต์ Laravel, โปรเจกต์ frontend หรือโปรเจกต์อะไรก็ได้ที่ deploy ขึ้นเซิร์ฟเวอร์ Linux
composer global require phattarachai/remote-prompt
แล้วเพิ่ม alias ไว้ใน ~/.zshrc หรือใส่ ~/.composer/vendor/bin ไว้ใน PATH เลยก็ได้
alias rh="$HOME/.composer/vendor/bin/rh"
ต้องมี PHP 8.3 ขึ้นไป หน้าจอสร้างด้วย Laravel Prompts ส่วนงานบนเซิร์ฟเวอร์เรียก ssh กับ scp ในเครื่องตรง ๆ คำสั่งบนเซิร์ฟเวอร์เขียนเป็น shell มาตรฐาน (POSIX) จึงใช้ได้กับ Linux แทบทุกเครื่อง ไม่ต้องติดตั้งอะไรเพิ่มฝั่งเซิร์ฟเวอร์
ย้ายเซิร์ฟเวอร์จาก PHPStorm มาด้วย rh import
ถ้าตั้ง Deployment ไว้ใน PHPStorm อยู่แล้ว ไม่ต้องกรอกใหม่ สั่ง rh import ในโฟลเดอร์โปรเจกต์ หรือในโฟลเดอร์ที่รวมทุกโปรเจกต์ไว้อย่าง ~/www ก็ได้ rh จะไล่อ่านทุกโปรเจกต์ข้างในให้

rh แสดงทุกอย่างที่จะ import ให้ดูก่อน แล้วถามยืนยันครั้งเดียว
rh อ่านจากไฟล์ที่ PHPStorm เก็บไว้ใน .idea/ ของแต่ละโปรเจกต์ คือ deployment.xml, webServers.xml และ sshConfigs.xml รวมถึงชุดที่ตั้งไว้ระดับ IDE ด้วย มีสามเรื่องที่ทำให้ใช้ต่อได้ทันที
- path จริงได้จากสองช่องรวมกัน ใน PHPStorm เราตั้ง Root path ของ server ไว้ช่องหนึ่ง แล้วตั้ง Deployment path ของ mapping อีกช่อง เช่น root
/var/wwwกับ deployment path/shoprhรวมให้เป็น/var/www/shop - ใช้ alias ใน ~/.ssh/config ที่มีอยู่แล้ว ถ้าเครื่องนั้นมี
Hostที่HostNameตรงกันอยู่แล้วrhใช้ alias นั้นเลย tunnel, jump host หรือ key ที่ตั้งไว้จึงใช้ได้เหมือนเดิม เครื่องที่ยังไม่มี alias จะได้ alias ใหม่ ส่วน path ของ key ที่ PHPStorm เขียนเป็น$USER_HOME$ก็แปลงเป็นโฟลเดอร์ home ให้ - server ที่ PHPStorm เข้าด้วยรหัสผ่าน
rhจะเตือนว่าต้องใช้ key และบอกคำสั่งssh-copy-id <alias>ให้รันครั้งเดียว
ชื่อ target ได้จากชื่อ server ใน PHPStorm และเดา environment จากชื่อ เช่นชื่อที่มี uat เป็น uat ชื่อที่มี qas หรือ qa เป็น qas ส่วนชื่อที่เดาไม่ออกนับเป็น production ไว้ก่อน เพื่อให้ถามทุกครั้งที่จะเขียน ถ้า import ซ้ำ target ของโปรเจกต์นั้นจะถูกแทนด้วยชุดใหม่ โปรเจกต์อื่นอยู่เหมือนเดิม import เสร็จแล้วสั่งเช็คการเชื่อมต่อทุกตัวได้เลย
rh doctor --all
doctor ต่อไปทุก target แล้วดูว่าโฟลเดอร์ deploy มีอยู่จริง
เพิ่มเซิร์ฟเวอร์เองด้วย rh add
ไม่ได้ใช้ PHPStorm ก็เพิ่มเองได้ สั่ง rh add ในโฟลเดอร์โปรเจกต์ แล้วเลือก alias จาก ~/.ssh/config จากนั้น rh จะเปิดโฟลเดอร์บนเซิร์ฟเวอร์ให้เดินเข้าไปเลือก โดยเริ่มที่ /var/www ถ้ามี โฟลเดอร์ที่ชื่อคล้ายโปรเจกต์จะมี ★ อยู่บนสุด เจอแล้วเลือก ✓ Use ได้เลย ไม่ต้องพิมพ์ path เอง
เครื่องที่ยังไม่เคยเข้า ให้เลือก New host… แล้วกรอก host, user, port เลือก key และตั้งชื่อ alias rh จะเขียน alias ลงไฟล์ของตัวเองแล้วลองต่อด้วย key ของเรา ถ้าเซิร์ฟเวอร์ยังไม่รู้จัก key นี้ rh จะเสนอ ssh-copy-id ให้ เราพิมพ์รหัสผ่านของเซิร์ฟเวอร์ครั้งเดียวลงใน ssh ไม่ใช่ใน rh หลังจากนั้นก็เข้าด้วย key ตลอด
~/.ssh/config เป็นที่เก็บการเชื่อมต่อที่เดียว
rh ไม่ได้เก็บ host, user หรือรหัสผ่านไว้เอง target หนึ่งตัวมีแค่สี่ค่า คือชื่อ, alias ใน ~/.ssh/config, path บนเครื่องนั้น และ environment ทั้งหมดอยู่ใน ~/.config/rh/config.json
{
"projects": {
"~/www/shop-api": {
"targets": [
{ "name": "qas", "host": "shop-qas", "path": "/var/www/shop", "env": "qas" },
{ "name": "uat", "host": "shop-prod", "path": "/var/www/uat-shop", "env": "uat" },
{ "name": "prod", "host": "shop-prod", "path": "/var/www/shop", "env": "production" }
]
},
"~/www/shop-*": {
"targets": [{ "name": "qas", "host": "shop-qas", "path": "/var/www/shop-web", "env": "qas" }]
}
}
}
envเป็นได้ห้าค่า คือproduction,uat,qas,stagingและdevtarget ที่ไม่ได้ใส่envนับเป็น production- key ของโปรเจกต์เป็น glob ได้ อย่าง
~/www/shop-*ใช้ target ชุดเดียวกันกับทุกโฟลเดอร์ที่ชื่อขึ้นต้นด้วย shop- ถ้ามีหลาย key ตรงกันrhเลือกตัวที่ตรงกับโฟลเดอร์ลึกที่สุด และชื่อเต็มชนะ glob - เปิด
rhจาก git worktree ของโปรเจกต์ ก็ได้ target ชุดเดียวกับโฟลเดอร์หลักrhถามgit rev-parse --git-common-dirว่า worktree นี้แตกมาจาก repo ไหน จึงไม่ต้องตั้งใหม่ทุกครั้งที่เปิด worktree ให้ agent ทำงาน
alias ที่ rh สร้างให้ตอน import หรือ New host… จะอยู่ในไฟล์แยก ~/.ssh/config.d/rh.conf แล้วเพิ่มบรรทัด Include ไว้บนสุดของ ~/.ssh/config ครั้งเดียว โดยสำรองไฟล์เดิมไว้ที่ ~/.ssh/config.rh-backup ก่อน นอกจากบรรทัดนี้ rh ไม่แก้ส่วนที่เราเขียนเองเลย
Include ~/.ssh/config.d/rh.conf
Host shop-prod
HostName 203.0.113.10
User deploy
ข้อดีคือ alias ทุกตัวใช้กับ ssh, scp, rsync หรือ git ธรรมดาได้ด้วย ไม่มีชุดการเชื่อมต่อที่รู้กันแค่ในโปรแกรมเดียวอย่างใน PHPStorm ถ้าอยากรู้เพิ่มว่า ~/.ssh/config จดอะไรไว้ได้บ้าง ผมเขียนไว้ในโพสต์ SSH tunnel
กด Enter ที่ target ก็ได้ shell ในโฟลเดอร์ deploy
แท็บแรกคือ Targets กด Enter แล้ว rh จะ ssh เข้าเครื่องนั้น cd ไปที่ path ของ target แล้วเปิด login shell ให้ พอ exit ก็กลับมาที่ terminal เดิม ถ้ารู้อยู่แล้วว่าจะเข้าที่ไหน สั่งตรง ๆ ได้ และต่อท้ายด้วยโฟลเดอร์ย่อยได้
rh ssh prod
rh ssh prod:storage/logs
path ที่ไม่ขึ้นต้นด้วย / นับจากโฟลเดอร์ deploy ของ target เสมอ เขียน prod:storage/logs ก็คือ /var/www/shop/storage/logs บนเครื่อง shop-prod
แท็บ Actions รวมงานที่เคยทำในหน้า Deployment
กด Tab ไปแท็บ Actions เลือกงานก่อน แล้ว rh ค่อยถามว่าจะทำกับ target ไหน โดยเลือก target ที่ใช้ล่าสุดไว้ให้ ถ้าโปรเจกต์มี target เดียวก็ไม่ถาม

พิมพ์กรองได้ทุกหน้า เหมือนใน git-prompt
- Browse files เดินดูโฟลเดอร์บนเซิร์ฟเวอร์ โฟลเดอร์ขึ้นก่อนไฟล์ ในแต่ละโฟลเดอร์มี Find a file under here, Shell here และ Download this folder เลือกไฟล์แล้วได้ View, Download หรือ Edit
- Find a file… รวมรายชื่อไฟล์ทั้งหมดใต้โฟลเดอร์ deploy มาให้พิมพ์ค้น ข้าม
.git,vendorและnode_modulesให้ เหมาะกับตอนที่จำชื่อไฟล์ได้แต่จำโฟลเดอร์ไม่ได้ - Download a file… หาไฟล์แบบเดียวกัน แล้วดาวน์โหลดลงโฟลเดอร์ที่เราอยู่
- Upload a local file… เลือกไฟล์จากโปรเจกต์ในเครื่อง แล้ว
rhอัปโหลดไปที่ path เดียวกันบนเซิร์ฟเวอร์ แบบเดียวกับ Upload to… ของ PHPStorm ถ้าไฟล์บนเซิร์ฟเวอร์มีอยู่แล้ว จะแสดง diff ให้ดูก่อน ถ้าไม่ต่างกันก็ไม่อัปโหลด - Compare a local file with the server… เทียบไฟล์ในเครื่องกับตัวที่ deploy อยู่ เวลาสงสัยว่าบนเซิร์ฟเวอร์เป็นเวอร์ชันไหน
- View .env / Edit .env อ่านหรือแก้
.envของ target นั้น (ดูหัวข้อถัดไป) - Tail the latest log หาไฟล์
storage/logs/*.logที่แก้ล่าสุด แล้วtail -Fให้ดูสด ๆ กด Ctrl+C เพื่อหยุด
รายการไฟล์ในเครื่องตอน Upload มาจาก git ls-files รวมไฟล์ที่ .gitignore ไว้อย่าง .env ด้วย แต่ไม่เอาโฟลเดอร์ vendor กับ node_modules และ cache ของ framework มาปน
ทุกอย่างในแท็บนี้มีคำสั่งตรงด้วย เช่น
rh get prod:storage/logs/laravel.log
rh put app/Models/Order.php uat
rh diff config/app.php prod
rh tail prod
rh get ใช้กับ alias ไหนก็ได้ใน ~/.ssh/config ด้วย แม้จะไม่ใช่ target ของโปรเจกต์นี้ แค่ให้ path แบบเต็ม เช่น rh get backup-box:/backups/db.sql.gz
ส่วนแท็บที่สาม Setup รวมงานตั้งค่าไว้ คือ Test every connection, Add a target, Remove a target, Import from PHPStorm (ขึ้นมาเมื่อโฟลเดอร์นั้นมี .idea), List every project และ Edit the config file
อ่าน .env บนเซิร์ฟเวอร์ ไม่ต้องดาวน์โหลดลงเครื่อง
.env ของ production มีรหัสผ่านฐานข้อมูลกับ API key อยู่ ถ้าดาวน์โหลดลงมาเปิด ไฟล์นั้นก็ค้างอยู่ในโฟลเดอร์ Downloads ต่อไปอีกนาน rh cat prod:.env หรือ View .env จึงเปิดไฟล์ด้วย less ของเซิร์ฟเวอร์เอง ผ่าน ssh -t ตัวไฟล์ไม่ได้ลงมาอยู่ในเครื่องเราเลย
rh cat prod:.env
บรรทัดล่างของ less บอกไว้ว่ากด q เพื่อออก / เพื่อค้น และ Space เพื่อไปหน้าถัดไป กด Ctrl+C ก็ออกได้ ไฟล์ที่สั้นพอจะพิมพ์ออกมาทั้งไฟล์เลยไม่ต้องกดอะไร ถ้าเซิร์ฟเวอร์ไม่มี less ก็ใช้ cat แทน
ส่วน rh edit prod:.env หรือ Edit .env ต้องดึงไฟล์ลงมาเปิดใน editor แต่ rh ระวังให้หลายชั้น
- ไฟล์ลงไปอยู่ในโฟลเดอร์ชั่วคราวที่อ่านได้แค่ user เรา (โฟลเดอร์ 0700 ไฟล์ 0600) และลบทิ้งตอน
rhปิด - เปิดด้วย editor ใน
$VISUALหรือ$EDITORถ้าไม่ได้ตั้งไว้จะใช้vi - ปิด editor แล้ว
rhแสดง diff ระหว่างไฟล์บนเซิร์ฟเวอร์กับที่เราแก้ ไม่มีอะไรเปลี่ยนก็จบตรงนั้น - ก่อนบันทึก
rhเช็คcksumของไฟล์บนเซิร์ฟเวอร์อีกรอบ ถ้าใครแก้ไฟล์นั้นระหว่างที่เราเปิดอยู่rhจะไม่เขียนทับ และเก็บสำเนาที่เราแก้ไว้ให้ - บันทึกด้วย
cat >ลงไฟล์เดิม แทนการอัปโหลดไฟล์ใหม่ไปวางทับ owner กับสิทธิ์ของไฟล์จึงอยู่เหมือนเดิม
เขียนลง production ต้องมีคนกดยืนยันที่ terminal
ทุกครั้งที่ rh put หรือ rh edit จะเขียนไฟล์ลง target ที่เป็น production rh จะแสดง diff ก่อน แล้วถามโดยตั้งค่าเริ่มต้นไว้ที่ No กด Enter ผ่านไปเฉย ๆ จึงไม่บันทึก

hint ใต้กล่องบอกเครื่องกับโฟลเดอร์ที่จะเขียน ดูอีกทีก่อนกด Yes
target ที่ไม่ใช่ production ก็ถามเหมือนกัน แต่ค่าเริ่มต้นเป็น Yes และถ้ารันจาก script ก็เขียนให้เลย ส่วน alias ที่เรียกตรง ๆ โดยไม่ได้เป็น target ของโปรเจกต์ (rh put file my-box:/path) นับเป็น production เพราะ rh ไม่รู้ว่าเครื่องนั้นคืออะไร
ส่วนที่ผมตั้งใจที่สุดคือตอนที่ไม่มี terminal เช่นรันจาก script หรือให้ AI agent อย่าง Claude Code เรียกใช้ คำสั่งที่จะเขียนลง production จะถูกปฏิเสธทันที เพราะไม่มีคนอยู่หน้าจอให้ถาม ส่วนการอ่านทำได้ตามปกติ agent จึงช่วยไล่ log หรือเทียบไฟล์บน production ได้ แต่แก้อะไรบน production เองไม่ได้

รันโดยไม่มี terminal rh cat พิมพ์เนื้อหาไฟล์ออกมาแทนการเปิด less ให้เอาไปต่อกับคำสั่งอื่นได้
rh มีคำสั่งสองตัวไว้ให้ agent ใช้โดยเฉพาะ คือ rh list --json บอก target ทั้งหมดของโปรเจกต์ และ rh add แบบใส่ flag ที่เพิ่ม target ได้โดยไม่ต้องตอบคำถาม
rh add --host=shop-prod --path=/var/www/uat-shop --name=uat --env=uat
ชื่อกับ environment ที่ไม่ได้ใส่จะเดาแบบเดียวกับตอนเพิ่มเอง ถ้าโฟลเดอร์ไม่มีอยู่บนเครื่องนั้นจะไม่เพิ่มให้ จนกว่าจะใส่ --force แนวคิดเดียวกับตอนที่ผมให้ AI อ่านฐานข้อมูลบน production ด้วย role ที่อ่านได้อย่างเดียว คือให้ agent เห็นของจริงได้ แต่การเปลี่ยนแปลงต้องผ่านคน
เปิด connection ครั้งเดียว ใช้ต่อได้สิบนาที
ทุกคำสั่งที่ rh เรียก ssh หรือ scp ใช้ ControlMaster ของ OpenSSH (ssh_config(5)) ครั้งแรกที่ต่อเครื่องไหน ssh จะเปิด connection หลักทิ้งไว้ คำสั่งถัดไปไปเครื่องเดียวกันใช้ connection นั้นต่อ ไม่ต้อง handshake ใหม่ และปิดเองเมื่อไม่มีใครใช้ครบสิบนาที
ssh -o ControlMaster=auto -o ControlPath=~/.ssh/rh-%C -o ControlPersist=10m shop-prod
เรื่องนี้เห็นผลชัดกับเครื่องที่ต่อช้า อย่างเซิร์ฟเวอร์บน Google Cloud ที่ปิด port 22 แล้วเข้าผ่าน IAP tunnel เครื่องแบบนี้ที่ผมดูแลอยู่ใช้เวลาต่อแต่ละครั้งราว 3–5 วินาที ตอน Browse files ที่ต้องเรียก ssh ทุกครั้งที่เปิดโฟลเดอร์ ถ้าไม่มี ControlMaster ก็ต้องรอแบบนั้นทุกคลิก พอเปิดไว้แล้วจะช้าแค่ครั้งแรก
rh doctor รู้จักเครื่องแบบนี้ด้วย ถ้า alias ไหนต่อผ่าน gcloud จะเช็คก่อนว่า gcloud ยัง login อยู่ เพราะตอน login หมดอายุ อาการจะเหมือนเซิร์ฟเวอร์ล่ม
คำสั่งลัด
พิมพ์ rh เฉย ๆ ได้หน้าสามแท็บครบทุกอย่าง ถ้ารู้อยู่แล้วว่าจะทำอะไร สั่งตรงได้
| คำสั่ง | ทำอะไร |
|---|---|
rh |
หน้าแท็บ Targets, Actions และ Setup |
rh ssh [target[:path]] |
shell บนเซิร์ฟเวอร์ ในโฟลเดอร์ deploy |
rh browse [target[:path]] |
เดินดูโฟลเดอร์ ดู ดาวน์โหลด แก้ หรือเทียบไฟล์ |
rh get <target:path> [local] |
ดาวน์โหลดไฟล์หรือโฟลเดอร์ ค่าเริ่มต้นคือโฟลเดอร์ที่อยู่ |
rh put <file> [target[:path]] |
อัปโหลดไปยัง path ที่ตรงกัน ดู diff ก่อน |
rh cat <target:path> |
อ่านไฟล์ใน less ของเซิร์ฟเวอร์ |
rh edit <target:path> |
แก้ใน editor ดู diff แล้วบันทึกกลับที่เดิม |
rh diff <file> [target] |
เทียบไฟล์ในเครื่องกับตัวที่ deploy อยู่ |
rh tail [target[:file]] |
tail -F log ล่าสุดใน storage/logs |
rh list [--all] [--json] |
target ของโปรเจกต์นี้ หรือทุกโปรเจกต์ |
rh add / rh remove / rh config |
เพิ่ม ลบ target หรือเปิดไฟล์ config |
rh import [folder] [--yes] |
import จาก Deployment ของ PHPStorm |
rh doctor [--all] |
ต่อทุก target แล้วเช็คโฟลเดอร์ |
ทุกหน้าใช้ปุ่มชุดเดียวกับ git-prompt พิมพ์เพื่อกรอง Enter เลือก Tab สลับแท็บ Esc ย้อนกลับ และ Ctrl+C ออกทันที
ทดสอบได้โดยไม่ต้องมีเซิร์ฟเวอร์จริง
ทุกโปรแกรมที่ rh เรียก ทั้ง ssh, scp, editor และ pager ผ่านคลาส Runner ตัวเดียว ใน test จึงสลับเป็น FakeRunner ที่เล่นเป็นเซิร์ฟเวอร์อยู่ในเครื่อง คำสั่ง ssh รัน script ที่จะส่งไปเซิร์ฟเวอร์ด้วย sh -c ส่วน scp กลายเป็น cp หน้าจอที่ต้องกดปุ่มก็ป้อนด้วย Prompt::fake() ของ Laravel Prompts ตอนนี้มี test ด้วย Pest 43 ตัว และ CI รันบน PHP 8.3 ถึง 8.5 ทั้ง Ubuntu และ macOS ใครกำลังทำ CLI ที่ต้องคุยกับเซิร์ฟเวอร์ ลองใช้วิธีนี้ดูได้ครับ
GitHub: phattarachai/remote-prompt






