EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / DirectAdmin / แก้ quota ผ่าน DA API แล้วสิทธิ์ redis กับ login key หายเงียบ ๆ
DirectAdmin

แก้ quota ผ่าน DA API แล้วสิทธิ์ redis กับ login key หายเงียบ ๆ

โดย Ton-Or ·3 พ.ย. 2026·อ่าน 10 นาที
แก้ 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 ก่อนหลัง ผมไม่มีทางรู้เรื่องนี้เลย หน้าแผงยังขึ้นโควตาใหม่ถูกต้องทุกอย่าง

เดาผิดสามข้อ

ก่อนจะเห็นว่าเกิดอะไรขึ้น ผมเข้าใจผิดสามเรื่องที่ฟังดูสมเหตุสมผลหมด

  1. คิดว่า API ฝั่งอ่านคืนทุกอย่างที่ API ฝั่งเขียนรับ ซึ่งไม่จริง
  2. คิดว่าแก้ช่องเดียว ช่องอื่นต้องคงเดิม เหมือนที่รู้สึกเวลากดในแผง
  3. หลังแก้เจ้าของขอให้เปิด 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
อ่านแล้วส่งกลับ ไม่ได้แปลว่าไม่มีอะไรหายGETSHOW_USER_CONFIGได้ limits กับ ON/OFFแต่ไม่มีสวิตช์บางตัวที่ DA เก็บไว้เองPOST MODIFY_USERaction=customizeส่งเท่าที่อ่านมาบวก quota ใหม่DA เขียน user.conf ใหม่สวิตช์ที่ไม่อยู่ใน POSTถือว่า OFF ทั้งหมดที่ API อ่านไม่คืนมาredislogin_keyssuspend_at_limitไม่ถูกส่งกลับไปredis = OFFlogin_keys = OFFsuspend_at_limit = OFFหลัง POST ไม่มีข้อความเตือน เห็นได้เมื่อ diff user.conf เท่านั้น
สวิตช์สามตัวที่ API อ่านไม่ออกไม่ถูกส่งกลับ DirectAdmin เลยตีความว่าปิด และ redis ของ user หยุดไปด้วย
API ฝั่งอ่านกับฝั่งเขียนไม่ได้สมมาตรกัน

อย่าสมมติว่าอะไรที่ GET ไม่เห็น คือไม่มี และอย่าสมมติว่าอะไรที่ POST ไม่ได้ส่ง คือคงเดิม สองข้อนี้ผิดพร้อมกันตรงนี้ ผลคือสิทธิ์หายเงียบ ๆ ไม่มี error ไม่มีคำเตือน แหล่งความจริงของสวิตช์คือไฟล์ user.conf ไม่ใช่ผลของ API

แผงควบคุมที่มีสวิตช์สลับสิบกว่าตัว ช่างหมุนปุ่มใหญ่ปุ่มเดียว แล้วฝาครอบสปริงปัดสวิตช์ที่ไม่มีป้ายปิดลงทั้งหมด
หมุนปุ่มเดียว (quota) แต่ฝาครอบที่ลั่นด้วยสปริงปัดสวิตช์ไร้ป้ายปิดทั้งแถว ผลที่ได้คือสวิตช์ที่ไม่มีชื่ออยู่ในรายการอ่านถูกปิดไปด้วย

ทำไมต้องทำในนามของ 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 เป็นแค่ช่องทางส่ง ขั้นตอนมีแปดข้อ

  1. สำรอง user.conf ของ user ลงโฟลเดอร์ที่มีเวลาในชื่อ (mode 700) พร้อมไฟล์ RESTORE
  2. อ่านค่า creator= ใน user.conf เพราะต้องทำในนามของ reseller ที่สร้าง user นี้ (reseller แก้ได้เฉพาะ user ของตัวเอง และฟีเจอร์ผูกกับสิทธิ์ของ reseller)
  3. ขอ API login ของ reseller นั้นด้วย directadmin api-url --user=reseller-a
  4. ค่าตัวเลข (bandwidth, quota, vdomains ฯลฯ) ดึงจาก SHOW_USER_CONFIG ส่วนสวิตช์ ON/OFF ดึงจาก user.conf ก่อน แล้วค่อย fallback ไปที่ API
  5. POST action=customize พร้อมโควตาใหม่
  6. diff user.conf ก่อนกับหลัง โดยไม่นับบรรทัด date_, last_ และ ip=
  7. รัน repquota -s /home ดู used, soft, hard, grace ของ user
  8. ทดสอบ 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 หยุดทำงาน
  • ไม่มีใครรู้จนกว่าจะมีคนบ่น
user.conf เป็นหลัก

ทางที่ใช้ตอนนี้

  • สวิตช์อ่านจากไฟล์ที่สำรองไว้
  • diff ก่อนหลังทุกครั้ง
  • มีไฟล์ RESTORE พร้อมถอย

ยืนยันที่ระบบไฟล์ ไม่ใช่แค่หน้าแผง

อีกข้อที่สคริปต์พิมพ์ให้ดูคือ repquota -s เพราะโควตาในแผงกับโควตาจริงของระบบไฟล์เป็นคนละที่กัน ถ้า API บอกสำเร็จแต่ระบบไฟล์ไม่เปลี่ยน ผู้ใช้จะยังเขียนไฟล์ไม่ได้ทั้งที่แผงขึ้นตัวเลขใหม่ ตอนที่ผมตั้ง soft ไว้ 4000M ตัว hard ขึ้น 4400M ซึ่งเป็น 10% เหนือ soft ตามที่ระบบนี้ตั้งไว้ และยืนยันว่าระบบไฟล์เปลี่ยนจริง

  fs quota: used=2220M soft=4000M hard=4400M grace=
2 บัญชีที่เกินโควตาและต้องเพิ่ม
111%บัญชีแรกก่อนเพิ่ม
105%บัญชีที่สองก่อนเพิ่ม
7 วันผ่อนผันค่าเริ่มต้น
3 สวิตช์ที่ API อ่านไม่คืน
10%hard เหนือ soft

ถ้ามีเหตุให้ต้องถอยก็ไม่ยาก ใส่ user.conf ที่สำรองไว้กลับที่เดิม แล้วรันสคริปต์ซ้ำด้วยโควตาเดิม เพราะสิ่งที่ถอยด้วยไฟล์อย่างเดียวคือค่าในไฟล์ ส่วนโควตาของระบบไฟล์ต้องถูก apply ใหม่อีกรอบ มิฉะนั้นค่าเก่าจะค้างอยู่ฝั่งระบบไฟล์

กับดัก ที่ diff บอกให้เห็นแต่แก้เองไม่ได้

เรื่องของบัญชีที่สองสอนอีกอย่างหนึ่ง reseller ที่ไม่มีฟีเจอร์ใดฟีเจอร์หนึ่งให้แจก จะคืนฟีเจอร์นั้นให้ user ของตัวเองไม่ได้ การทำในนามของ reseller เลยมีขีดจำกัดที่ต้องรู้ล่วงหน้า วิธีรู้คือดูจาก diff ที่ยังเหลือบรรทัดหายหลังสคริปต์รัน วิธีแก้คือคืนธงนั้นในนามแอดมิน หรือให้ฟีเจอร์นั้นกับ reseller ก่อน ผมเคยเขียนวิธีเขียนค่าลง DirectAdmin ผ่านฟอร์มของแผงเองไว้ใน เขียนค่าลง DirectAdmin ตอนที่ API มีแต่ฝั่งอ่าน ต้องขับฟอร์มของแผงเอง ซึ่งเป็นเรื่องของ package ไม่ใช่ user แต่หลักเดียวกัน คือถ้า API ไม่ครบ ต้องรู้ว่าตรงไหนขาด

ภาพก่อนหลัง ซ้ายมีรูปถ่ายสแนปช็อตของแผงสวิตช์ติดไว้ข้างแผง ขวาช่างกำลังเทียบแผงกับรูปถ่ายด้วยเช็กลิสต์
สำรองรูปแผงสวิตช์ไว้ก่อนแตะ (user.conf ที่สำรอง) แล้วเทียบกับแผงจริงหลังแตะ ทุกสวิตช์ที่ต่างไปต้องมีคำอธิบาย
แก้ quota ทีละ user ด้วยขั้นตอนที่มีทางถอย1 สำรอง user.confโฟลเดอร์ mode 700พร้อมไฟล์ RESTORE2 อ่าน creator=reseller เจ้าของคือคนที่ต้องทำแทน3 ขอ API loginapi-url ของ resellerห้ามเอาคีย์ไปแปะ4 ประกอบ POSTlimits จาก SHOWสวิตช์จาก user.conf5 POST customizequota ใหม่ในนั้นที่เหลือเหมือนเดิม6 diff user.confก่อนกับหลังไม่นับ date_ last_ ip=7 repquota -sused soft hard graceยืนยันที่ระบบไฟล์8 ทดสอบ login จริงssh กับ ftp ของ userredis ยัง active ไหมสวิตช์ไหนคืนไม่ได้ diff จะเป็นตัวบอกreseller ที่ไม่มีฟีเจอร์นั้นคืนให้ไม่ได้ ต้องคืนในนามแอดมิน
ขั้นที่ 6 คือด่านที่จับสวิตช์หาย และขั้นที่ 7 คือด่านที่ยืนยันว่า quota เปลี่ยนที่ระบบไฟล์จริง
ทดลองกับ user หนึ่งคนก่อนเสมอ

ก่อนวนใช้กับหลายบัญชี ให้ลองกับ 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

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