Open source

remote-prompt เข้าเซิร์ฟเวอร์ ดูไฟล์ และอัปโหลดจาก terminal แบบ Deployment ของ PHPStorm

พิมพ์ rh ในโปรเจกต์ไหนก็ได้ แล้วเลือกเซิร์ฟเวอร์ของโปรเจกต์นั้น เข้า shell ที่อยู่ในโฟลเดอร์ deploy แล้ว ดูไฟล์ ดาวน์โหลด อัปโหลด เทียบไฟล์ อ่าน .env และ log โดยใช้การเชื่อมต่อจาก ~/.ssh/config ที่มีอยู่แล้ว

เผยแพร่
เวลาอ่าน
10 นาที

ผมเปิด 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 ในโฟลเดอร์โปรเจกต์ จะเห็นแค่เซิร์ฟเวอร์ของโปรเจกต์นั้น

พิมพ์ rh ในโฟลเดอร์โปรเจกต์ แล้วกด Enter ที่ target

กด 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 อ่าน Deployment ของทุกโปรเจกต์ในโฟลเดอร์

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 /shop rh รวมให้เป็น /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 และ dev target ที่ไม่ได้ใส่ 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 เดียวก็ไม่ถาม

แท็บ Actions เลือกงานก่อน แล้วค่อยเลือก 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 ระวังให้หลายชั้น

  1. ไฟล์ลงไปอยู่ในโฟลเดอร์ชั่วคราวที่อ่านได้แค่ user เรา (โฟลเดอร์ 0700 ไฟล์ 0600) และลบทิ้งตอน rh ปิด
  2. เปิดด้วย editor ใน $VISUAL หรือ $EDITOR ถ้าไม่ได้ตั้งไว้จะใช้ vi
  3. ปิด editor แล้ว rh แสดง diff ระหว่างไฟล์บนเซิร์ฟเวอร์กับที่เราแก้ ไม่มีอะไรเปลี่ยนก็จบตรงนั้น
  4. ก่อนบันทึก rh เช็ค cksum ของไฟล์บนเซิร์ฟเวอร์อีกรอบ ถ้าใครแก้ไฟล์นั้นระหว่างที่เราเปิดอยู่ rh จะไม่เขียนทับ และเก็บสำเนาที่เราแก้ไว้ให้
  5. บันทึกด้วย cat > ลงไฟล์เดิม แทนการอัปโหลดไฟล์ใหม่ไปวางทับ owner กับสิทธิ์ของไฟล์จึงอยู่เหมือนเดิม

เขียนลง production ต้องมีคนกดยืนยันที่ terminal

ทุกครั้งที่ rh put หรือ rh edit จะเขียนไฟล์ลง target ที่เป็น production rh จะแสดง diff ก่อน แล้วถามโดยตั้งค่าเริ่มต้นไว้ที่ No กด Enter ผ่านไปเฉย ๆ จึงไม่บันทึก

แก้ไฟล์บน production เห็น diff ก่อน และค่าเริ่มต้นคือ No

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 เองไม่ได้

รันจาก script หรือ agent อ่านได้ แต่เขียน 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

อ่านต่อ

ล่าสุด

ดูทั้งหมด →