แก้ quota ผ่าน DA API แล้วสิทธิ์ redis กับ login key หายเงียบ ๆ
การแก้ค่าเดียวผ่าน API ที่ออกแบบให้ส่งค่าทั้งชุด คือการเดิมพันว่าที่เราอ่านมาครบทุกตัวที่มันรับ และกรณีนี้ API ฝั่งอ่านไม่คืนสวิตช์ที่ API ฝั่งเขียนยังเอาไปตีความว่าปิด
สรุปใน 1 นาที
- อาการ/ปัญหา
- แก้ quota ผ่าน DA API แล้วสิทธิ์ redis, login keys และ suspend_at_limit ถูกปิดเงียบ ๆ
- ใครควรอ่าน
- คนที่เขียนสคริปต์แก้ค่า user ผ่าน DirectAdmin API
- ทางแก้แบบสั้น
- CMD_API_MODIFY_USER แบบ customize ถือว่าสวิตช์ที่ไม่ส่งคือปิด ต้องส่งค่าทั้งชุด ทำในนามของ reseller และ diff user.conf กับ repquota ยืนยัน
พอดีเจ้าของเครื่องสั่งให้เพิ่มโควตาดิสก์ให้บัญชีโฮสติ้งสองบัญชีที่ใช้เกินไปแล้ว ตัวหนึ่งอยู่ที่ราว 111% อีกตัวราว 105% ของโควตา DirectAdmin ให้เวลาผ่อนผันหลังเกินอยู่ช่วงหนึ่ง (ค่าเริ่มต้น 7 วัน) พ้นจากนั้นเขียนไฟล์ไม่ได้และเมลเข้าไม่ได้ ผมเลยเลือกทำผ่าน API ด้วยสคริปต์สั้น ๆ แทนที่จะกดในแผงทีละช่อง ผลคือโควตาเปลี่ยนตามที่ต้องการ แต่พอ diff ไฟล์ user.conf ก่อนกับหลัง กลับมีสิทธิ์อื่นหายไปด้วย โดยที่ไม่มีใครสั่งให้แตะ
ชื่อ user ชื่อ reseller และตัวเลขโควตาของจริงผมเปลี่ยนเป็นค่าตัวอย่างทั้งหมด (webuser, reseller-a, 2000 เป็น 4000 MB) กลไกกับพฤติกรรมของ API เป็นของจริง
อาการ: โควตาเปลี่ยน แต่สวิตช์อื่นหายไปด้วย
สคริปต์ของผมสำรอง user.conf ก่อนแล้ว diff หลังทำเสมอ ผลที่เห็นหลังเพิ่มโควตาแบ่งเป็นสองบัญชี
- บัญชีแรก
login_keysกับsuspend_at_limitถูกเปลี่ยนเป็น OFF ผมตั้งกลับเป็น ON ให้ตรงค่าเดิม - บัญชีที่สอง
redis=ONหายไปจากไฟล์ และตั้งกลับด้วยบัญชีเจ้าของไม่ได้ เพราะ reseller ที่สร้างบัญชีนี้ไม่มีฟีเจอร์ redis ให้แจก
# บัญชีแรก: diff user.conf ก่อน (<) กับหลัง (>)
< login_keys=ON
< suspend_at_limit=ON
> login_keys=OFF
> suspend_at_limit=OFF
# บัญชีที่สอง: บรรทัดนี้หายไปจากไฟล์เลย
< redis=ON
เรื่อง redis ต้องบอกเพิ่มว่าเป็นสวิตช์ที่ผูกกับบริการของ user ตัวนั้น พอธงปิด ตัว [email protected] ก็หยุดไปด้วย แอปที่ใช้ redis เป็น cache หรือ session ของบัญชีนั้นจะเริ่มผิดปกติโดยไม่มีข้อความเตือนจาก API เลย เรื่อง redis แบบ per-user บน DirectAdmin ผมเขียนแยกไว้แล้วใน ทำ Redis แบบ per-user บน DirectAdmin โดยไม่ต้องซื้อ pro_pack บทความนี้ขอเล่าเฉพาะว่าธงนี้หายได้ยังไง
ถ้าผมไม่ได้เขียนให้สคริปต์ diff ก่อนหลัง ผมไม่มีทางรู้เรื่องนี้เลย หน้าแผงยังขึ้นโควตาใหม่ถูกต้องทุกอย่าง
เดาผิดสามข้อ
ก่อนจะเห็นว่าเกิดอะไรขึ้น ผมเข้าใจผิดสามเรื่องที่ฟังดูสมเหตุสมผลหมด
- คิดว่า API ฝั่งอ่านคืนทุกอย่างที่ API ฝั่งเขียนรับ ซึ่งไม่จริง
- คิดว่าแก้ช่องเดียว ช่องอื่นต้องคงเดิม เหมือนที่รู้สึกเวลากดในแผง
- หลังแก้เจ้าของขอให้เปิด SSH ให้สองบัญชีอีกครั้ง ผมเลยคิดว่าสิทธิ์ SSH น่าจะหายไปด้วย แต่ตรวจแล้วไม่มีอะไรต้องกู้ SSH ไม่เคยถูกแตะ ทดสอบด้วยการ login จริงแล้วเข้าได้
ข้อสามไม่ใช่ความเสียหาย แต่เป็นเหตุผลที่ผมทดสอบด้วยการ login จริงเสมอ ไม่เดาจากค่าในไฟล์ ส่วนสองข้อแรกคือต้นเหตุของเรื่องทั้งหมด
ต้นเหตุ: customize แทนที่ทั้งชุด
CMD_API_MODIFY_USER ที่ส่ง action=customize ไม่ได้ปรับเฉพาะช่องที่ส่ง มันแทนที่ชุดของ limit และสวิตช์ทั้งหมดของ user ด้วยสิ่งที่อยู่ใน POST สวิตช์ใดที่ไม่ได้ส่งไปถูกตีความเป็น OFF วิธีที่ดูเป็นธรรมชาติที่สุดคือ GET ค่าปัจจุบัน เปลี่ยนโควตา แล้ว POST กลับ ซึ่งผิดตรงที่ CMD_API_SHOW_USER_CONFIG ไม่ได้คืนสวิตช์ครบ redis, login_keys และ suspend_at_limit ไม่อยู่ในผลลัพธ์
# วิธีที่ดูถูกแต่ผิด — อ่านแล้วส่งกลับ
CFG=$(curl -sk -u "$K" "$B/CMD_API_SHOW_USER_CONFIG?user=webuser")
# ... แก้ quota ใน $CFG แล้ว POST กลับ
curl -sk -u "$K" "$B/CMD_API_MODIFY_USER" -d action=customize -d user=webuser \
-d quota=4000 -d bandwidth=... -d ssh=ON # ไม่มี redis / login_keys / suspend_at_limit
# ผล: สามตัวที่ไม่ได้ส่งกลายเป็น OFF
อย่าสมมติว่าอะไรที่ GET ไม่เห็น คือไม่มี และอย่าสมมติว่าอะไรที่ POST ไม่ได้ส่ง คือคงเดิม สองข้อนี้ผิดพร้อมกันตรงนี้ ผลคือสิทธิ์หายเงียบ ๆ ไม่มี error ไม่มีคำเตือน แหล่งความจริงของสวิตช์คือไฟล์ user.conf ไม่ใช่ผลของ API

ทำไมต้องทำในนามของ reseller
เรื่องนี้เป็นอีกข้อที่ผมเกือบมองข้าม บัญชีโฮสติ้งทุกบัญชีใน DirectAdmin มีเจ้าของคือ reseller ที่สร้างมัน ระบุไว้ใน user.conf ด้วยบรรทัด creator= reseller แก้ได้เฉพาะ user ของตัวเอง และฟีเจอร์ที่ user จะได้ก็ไม่เกินสิทธิ์ที่ reseller คนนั้นถือ ผมเลยไม่ใช้ login ของแอดมินยิงทุกอย่างทีเดียว แต่ขอ API login ของ reseller เจ้าของแต่ละบัญชีมาใช้
ข้อดีคือถ้ามีสวิตช์ที่ reseller ให้ไม่ได้ ผลจะปรากฏใน diff ทันที ไม่ถูกแอดมินที่มีสิทธิ์ทุกอย่างกลบไว้ ข้อเสียคือต้องขอ login หลายชุด ซึ่งผมถือว่าคุ้ม เพราะมันตรงกับวิธีที่ DirectAdmin ออกแบบสิทธิ์มาอยู่แล้ว
อีกจุดเล็ก ๆ คือค่า unlimited ของ limit ตัวเลข ต้องส่งเป็นชื่อที่นำหน้าด้วย u แล้วตั้งเป็น ON เช่น uquota=ON แทนที่จะส่งตัวเลข ในสคริปต์เลยมีเงื่อนไขแยกเรื่องนี้ไว้ ไม่งั้น user ที่เคยไม่จำกัดจะกลายเป็นมีตัวเลขติดไปด้วย
ขั้นตอนที่ปลอดภัย
หลังเรื่องนี้ผมเขียนสคริปต์ใหม่ให้ถือ user.conf เป็นแหล่งความจริงของสวิตช์ และให้ API เป็นแค่ช่องทางส่ง ขั้นตอนมีแปดข้อ
- สำรอง
user.confของ user ลงโฟลเดอร์ที่มีเวลาในชื่อ (mode 700) พร้อมไฟล์ RESTORE - อ่านค่า
creator=ในuser.confเพราะต้องทำในนามของ reseller ที่สร้าง user นี้ (reseller แก้ได้เฉพาะ user ของตัวเอง และฟีเจอร์ผูกกับสิทธิ์ของ reseller) - ขอ API login ของ reseller นั้นด้วย
directadmin api-url --user=reseller-a - ค่าตัวเลข (bandwidth, quota, vdomains ฯลฯ) ดึงจาก SHOW_USER_CONFIG ส่วนสวิตช์ ON/OFF ดึงจาก
user.confก่อน แล้วค่อย fallback ไปที่ API - POST
action=customizeพร้อมโควตาใหม่ - diff
user.confก่อนกับหลัง โดยไม่นับบรรทัดdate_,last_และip= - รัน
repquota -s /homeดู used, soft, hard, grace ของ user - ทดสอบ login จริงของ user (SSH, FTP) และเช็กว่า
redis@webuserยังทำงาน
ส่วนขั้นที่ 3 ต้องระวังเป็นพิเศษ คำสั่ง api-url พิมพ์ URL ที่มี login key ของ reseller ติดมาด้วย key นี้เทียบเท่ารหัสผ่าน ห้ามเก็บลง log ห้ามแปะในแชท และห้ามลงในบทความ ผมเลยเก็บไว้เฉพาะในตัวแปรของสคริปต์ และตัดออกจากผลลัพธ์ที่พิมพ์เสมอ
สคริปต์ที่ผมใช้ยาวราว 30 บรรทัด ผมย่อเหลือส่วนที่เป็นแก่นของเรื่องนี้ คือการแยกว่าอะไรมาจาก API อะไรมาจาก user.conf
#!/bin/bash
# da_set_quota.sh <user> <quota_MB> [old_user.conf]
U=${1:?user}; Q=${2:?quota MB}
R=/usr/local/directadmin/data/users/$U
CR=$(grep -m1 '^creator=' $R/user.conf | cut -d= -f2) # reseller เจ้าของ
D=/root/snapshots/$(date +%Y%m%d-%H%M%S)-quota-$U; mkdir -p "$D"; chmod 700 "$D"
cp -a $R/user.conf "$D/"
URL=$(/usr/local/directadmin/directadmin api-url --user="$CR" 2>/dev/null | tail -1) # มี login key
B=$(echo "$URL" | sed -E 's#https?://[^@]+@([^/]+).*#https://\1#')
K=$(echo "$URL" | sed -E 's#https?://([^@]+)@.*#\1#')
CFG=$(curl -sk -u "$K" "$B/CMD_API_SHOW_USER_CONFIG?user=$U")
ARGS=(-d action=customize -d "user=$U")
# 1) ตัวเลข: จาก API; "unlimited" ต้องส่งเป็น u<ชื่อ>=ON
for f in bandwidth quota vdomains nsubdomains nemails nemailf nemailml nemailr mysql domainptr ftp inode; do
v=$(echo "$CFG" | tr '&' '\n' | grep -m1 "^$f=" | cut -d= -f2)
[ "$f" = quota ] && v=$Q
if [ "$v" = unlimited ]; then ARGS+=(-d "u$f=ON"); else ARGS+=(-d "$f=$v"); fi
done
# 2) สวิตช์ ON/OFF: จาก user.conf ก่อน (หรือไฟล์เก่าที่ส่งมาเป็นอาร์กิวเมนต์ที่ 3) แล้วค่อย fallback API
for f in aftp cgi php spam cron catchall ssl ssh sysinfo dnscontrol login_keys suspend_at_limit git redis wordpress clamav jail; do
v=$(grep -m1 "^$f=" "${3:-$D/user.conf}" | cut -d= -f2)
[ -z "$v" ] && v=$(echo "$CFG" | tr '&' '\n' | grep -m1 "^$f=" | cut -d= -f2)
[ -n "$v" ] && ARGS+=(-d "$f=$v")
done
curl -sk -u "$K" "$B/CMD_API_MODIFY_USER" "${ARGS[@]}" >/dev/null
หลัง POST สคริปต์ตัวเดียวกันพิมพ์สิ่งที่เปลี่ยนใน user.conf กับโควตาจริงของระบบไฟล์ ทั้งสองอย่างนี้คือตัวที่จับเรื่องนี้ได้และตัวที่ผมอยากให้ทุกคนที่แก้ผ่าน API ใส่ไว้
sleep 2
echo "changed in user.conf:"
diff <(grep -vE '^(date_|last_|ip=)' "$D/user.conf" | sort) \
<(grep -vE '^(date_|last_|ip=)' $R/user.conf | sort) | grep '^[<>]' | sed 's/^/ /'
repquota -s /home 2>/dev/null | awk -v u=$U '$1==u {print " fs quota: used="$3" soft="$4" hard="$5" grace="$6}'
echo "cp -a $D/user.conf $R/user.conf # แล้วรันซ้ำด้วยโควตาเดิม" > "$D/RESTORE.md"
ทางที่พลาด
- สวิตช์ที่ API ไม่คืนกลายเป็น OFF
- redis ของ user หยุดทำงาน
- ไม่มีใครรู้จนกว่าจะมีคนบ่น
ทางที่ใช้ตอนนี้
- สวิตช์อ่านจากไฟล์ที่สำรองไว้
- diff ก่อนหลังทุกครั้ง
- มีไฟล์ RESTORE พร้อมถอย
ยืนยันที่ระบบไฟล์ ไม่ใช่แค่หน้าแผง
อีกข้อที่สคริปต์พิมพ์ให้ดูคือ repquota -s เพราะโควตาในแผงกับโควตาจริงของระบบไฟล์เป็นคนละที่กัน ถ้า API บอกสำเร็จแต่ระบบไฟล์ไม่เปลี่ยน ผู้ใช้จะยังเขียนไฟล์ไม่ได้ทั้งที่แผงขึ้นตัวเลขใหม่ ตอนที่ผมตั้ง soft ไว้ 4000M ตัว hard ขึ้น 4400M ซึ่งเป็น 10% เหนือ soft ตามที่ระบบนี้ตั้งไว้ และยืนยันว่าระบบไฟล์เปลี่ยนจริง
fs quota: used=2220M soft=4000M hard=4400M grace=
ถ้ามีเหตุให้ต้องถอยก็ไม่ยาก ใส่ user.conf ที่สำรองไว้กลับที่เดิม แล้วรันสคริปต์ซ้ำด้วยโควตาเดิม เพราะสิ่งที่ถอยด้วยไฟล์อย่างเดียวคือค่าในไฟล์ ส่วนโควตาของระบบไฟล์ต้องถูก apply ใหม่อีกรอบ มิฉะนั้นค่าเก่าจะค้างอยู่ฝั่งระบบไฟล์
กับดัก ที่ diff บอกให้เห็นแต่แก้เองไม่ได้
เรื่องของบัญชีที่สองสอนอีกอย่างหนึ่ง reseller ที่ไม่มีฟีเจอร์ใดฟีเจอร์หนึ่งให้แจก จะคืนฟีเจอร์นั้นให้ user ของตัวเองไม่ได้ การทำในนามของ reseller เลยมีขีดจำกัดที่ต้องรู้ล่วงหน้า วิธีรู้คือดูจาก diff ที่ยังเหลือบรรทัดหายหลังสคริปต์รัน วิธีแก้คือคืนธงนั้นในนามแอดมิน หรือให้ฟีเจอร์นั้นกับ reseller ก่อน ผมเคยเขียนวิธีเขียนค่าลง DirectAdmin ผ่านฟอร์มของแผงเองไว้ใน เขียนค่าลง DirectAdmin ตอนที่ API มีแต่ฝั่งอ่าน ต้องขับฟอร์มของแผงเอง ซึ่งเป็นเรื่องของ package ไม่ใช่ user แต่หลักเดียวกัน คือถ้า API ไม่ครบ ต้องรู้ว่าตรงไหนขาด

ก่อนวนใช้กับหลายบัญชี ให้ลองกับ user หนึ่งคนด้วยการเปลี่ยนค่าที่ไม่สำคัญก่อน แล้วอ่าน diff ให้ละเอียดว่ามีบรรทัดไหนที่ไม่ได้ตั้งใจเปลี่ยนไหม บัญชีที่ใช้ฟีเจอร์ต่างกันจะเจอสวิตช์หายคนละแบบ ซึ่งไม่ใช่สิ่งที่อยากรู้ตอนทำจริงกับลูกค้า
สรุปคือ
API ที่ออกแบบให้ส่งค่าทั้งชุดเป็น API ที่ต้องการให้เรารู้ว่าชุดนั้นมีอะไรบ้าง และเมื่อฝั่งอ่านไม่ได้คืนทุกตัว เราต้องหาแหล่งอื่นมาเติมเอง ในกรณีนี้คือไฟล์ user.conf ที่ DirectAdmin เก็บไว้เอง สิ่งที่ผมเก็บไว้ใช้ต่อมีดังนี้
- ก่อนแก้ผ่าน
action=customizeสำรองuser.confทุกครั้ง และเอาสวิตช์ ON/OFF จากไฟล์ ไม่ใช่จาก SHOW_USER_CONFIG - ทำในนามของ reseller เจ้าของ user (
creator=) และจำไว้ว่า reseller ให้ฟีเจอร์ที่ตัวเองไม่มีไม่ได้ - diff ก่อนหลังโดยไม่นับ
date_,last_,ip=แล้วอ่านทุกบรรทัดที่เหลือ - ยืนยันโควตาด้วย
repquota -sเพราะโควตาของแผงกับระบบไฟล์แยกกัน - ทดสอบ login จริงและ service ของ user (เช่น redis) หลังแก้เสมอ
จะใช้สคริปต์ของผมเป็นแม่แบบ หรือเขียนใหม่ให้เข้ากับชุดสวิตช์ของเครื่องตัวเองก็ตามสะดวกครับ แต่ถ้ายังไม่ได้ diff ก่อนหลัง ผมไม่แนะนำให้ปล่อยสคริปต์ไปแก้ user ของจริง ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
อยากให้เราทำให้ไหม?
ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย