[{"data":1,"prerenderedAt":8768},["ShallowReactive",2],{"blogs-2026-08-03-docker-dind-vs-dood":3,"next-blog-2026-08-03-docker-dind-vs-dood":959,"prev-blog-2026-08-03-docker-dind-vs-dood":959,"recommended-blog-2026-08-03-docker-dind-vs-dood":970,"related-blog-2026-08-03-docker-dind-vs-dood":1214},{"id":4,"title":5,"body":6,"date":954,"description":955,"draft":956,"extension":957,"meta":958,"navigation":365,"next":959,"ogTitle":960,"path":961,"pin":956,"prev":959,"recommends":962,"seo":964,"sitemap":965,"stem":966,"tags":967,"updated":959,"__hash__":969},"content/blogs/2026-08-03-docker-dind-vs-dood.md","DinD vs DooD เลือกแบบไหนดี ตอนต้อง build image ใน CI",{"type":7,"value":8,"toc":941},"minimark",[9,13,22,32,35,55,58,62,73,136,142,149,187,193,196,203,206,213,263,269,272,283,286,296,324,330,333,337,343,419,426,432,435,438,483,489,492,495,546,552,555,570,576,579,584,595,662,669,675,678,684,718,724,731,737,743,772,778,792,795,798,801,804,827,833,843,846,896,899,903,937],[10,11,12],"h2",{"id":12},"ที่มาของบทความนี้",[14,15,16,17,21],"p",{},"สวัสดีครับ พอดีช่วงนี้ผมย้าย CI runner มารันบนเครื่องที่บ้านตัวเอง ตัว runner ก็รันเป็น container ตามปกตินี่แหละครับ ทุกอย่างก็ดูดีมาตลอดจนกระทั่งไปเจอ job ที่ต้อง ",[18,19,20],"code",{},"docker build"," แล้วก็ได้บรรทัดนี้กลับมา",[23,24,29],"pre",{"className":25,"code":27,"language":28},[26],"language-text","Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?\n","text",[18,30,27],{"__ignoreMap":31},"",[14,33,34],{},"ตอนแรกก็งงอยู่พักนึงครับ เพราะในตัว image ของ runner ก็มี docker ติดมาให้แล้ว ที่จริงเหตุผลมันง่ายมากเลย คือใน container มีแต่ตัว client ไม่ได้มี Docker daemon ให้คุยด้วยนั่นเอง",[14,36,37,38,42,43,46,47,50,51,54],{},"ทางแก้ของเรื่องนี้มีอยู่สองสายที่คนเถียงกันมาสิบกว่าปีแล้ว สายแรกคือ ",[39,40,41],"strong",{},"DooD"," (Docker out of Docker) เป็นการไปยืม daemon ของเครื่อง host มาใช้ ด้วยการ mount ",[18,44,45],{},"docker.sock"," เข้าไปใน container ส่วนอีกสายคือ ",[39,48,49],{},"DinD"," (Docker in Docker) ซึ่งจะรัน daemon ตัวใหม่ขึ้นมาใน container เลย โดยต้องเปิด ",[18,52,53],{},"--privileged"," ให้ด้วย",[14,56,57],{},"ผมเลยลองรันเทียบกันดูจริง ๆ ทั้งสองแบบ output ที่เอามาแปะในบทความนี้มาจากเครื่องผมทั้งหมดนะครับ (Docker 29.4.0 บน OrbStack) ไม่ได้พิมพ์ขึ้นมาเอง",[10,59,61],{"id":60},"dood-คืออะไร","DooD คืออะไร",[14,63,64,65,68,69,72],{},"เรื่องที่ต้องเข้าใจก่อนคือคำสั่ง ",[18,66,67],{},"docker"," ที่เราพิมพ์กันอยู่ทุกวันมันเป็นแค่ client นะครับ ตัวมันเองไม่ได้สร้าง container แต่จะยิงคำสั่งผ่าน unix socket ที่ ",[18,70,71],{},"/var/run/docker.sock"," ไปให้ daemon ทำงานอีกที พอรู้ตรงนี้แล้ววิธีแก้ก็ตรงไปตรงมาเลยครับ คือโยน socket ตัวนั้นเข้าไปใน container ซะ",[23,74,78],{"className":75,"code":76,"language":77,"meta":31,"style":31},"language-sh shiki shiki-themes material-theme-lighter one-dark-pro github-dark","docker run --rm \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  docker:cli docker ps --format 'table {{.Names}}\\t{{.Image}}'\n","sh",[18,79,80,100,111],{"__ignoreMap":31},[81,82,85,88,92,96],"span",{"class":83,"line":84},"line",1,[81,86,67],{"class":87},"sosa_",[81,89,91],{"class":90},"sAlJX"," run",[81,93,95],{"class":94},"sEoV_"," --rm",[81,97,99],{"class":98},"sicAX"," \\\n",[81,101,103,106,109],{"class":83,"line":102},2,[81,104,105],{"class":94},"  -v",[81,107,108],{"class":90}," /var/run/docker.sock:/var/run/docker.sock",[81,110,99],{"class":98},[81,112,114,117,120,123,126,130,133],{"class":83,"line":113},3,[81,115,116],{"class":90},"  docker:cli",[81,118,119],{"class":90}," docker",[81,121,122],{"class":90}," ps",[81,124,125],{"class":94}," --format",[81,127,129],{"class":128},"snfNA"," '",[81,131,132],{"class":90},"table {{.Names}}\\t{{.Image}}",[81,134,135],{"class":128},"'\n",[23,137,140],{"className":138,"code":139,"language":28},[26],"NAMES               IMAGE\nelegant_mayer       docker:cli\ndemo-web            alpine\nredis-cache         redis:7-alpine\napp-postgres        postgres:16-alpine\n",[18,141,139],{"__ignoreMap":31},[14,143,144,145,148],{},"จะเห็นว่า container ที่เพิ่งรันไปมันมองเห็น container ตัวอื่นที่ตัวเองไม่ได้สร้างด้วย แถมยังเห็นตัวเองอยู่ในลิสต์ด้วย (",[18,146,147],{},"elegant_mayer"," นั่นแหละครับ) ลองถาม daemon ตรง ๆ อีกทีก็จะยิ่งชัด",[23,150,152],{"className":75,"code":151,"language":77,"meta":31,"style":31},"docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \\\n  docker:cli docker info --format 'ServerVersion={{.ServerVersion}} Containers={{.Containers}} Name={{.Name}}'\n",[18,153,154,169],{"__ignoreMap":31},[81,155,156,158,160,162,165,167],{"class":83,"line":84},[81,157,67],{"class":87},[81,159,91],{"class":90},[81,161,95],{"class":94},[81,163,164],{"class":94}," -v",[81,166,108],{"class":90},[81,168,99],{"class":98},[81,170,171,173,175,178,180,182,185],{"class":83,"line":102},[81,172,116],{"class":90},[81,174,119],{"class":90},[81,176,177],{"class":90}," info",[81,179,125],{"class":94},[81,181,129],{"class":128},[81,183,184],{"class":90},"ServerVersion={{.ServerVersion}} Containers={{.Containers}} Name={{.Name}}",[81,186,135],{"class":128},[23,188,191],{"className":189,"code":190,"language":28},[26],"ServerVersion=29.4.0 Containers=23 Name=orbstack\n",[18,192,190],{"__ignoreMap":31},[14,194,195],{},"เวอร์ชันตรงกับ host เป๊ะ ๆ เลยครับ เพราะมันคือ daemon ตัวเดียวกันนั่นแหละ ซึ่งแปลว่า container ที่ job ของเราสั่งสร้างออกมามันไม่ใช่ child ของ runner นะครับ แต่เป็น sibling ที่รันอยู่บน host ในระดับเดียวกันต่างหาก",[10,197,199,200],{"id":198},"กับดักเรื่อง-path-ใน-v","กับดักเรื่อง path ใน ",[18,201,202],{},"-v",[14,204,205],{},"อันนี้เป็นตัวที่กินเวลา debug ผมนานที่สุดครับ เพราะไม่มี error โผล่ขึ้นมาให้เห็นเลยสักบรรทัด เลยยิ่งหาสาเหตุไม่เจอ",[14,207,208,209,212],{},"ผมลองสร้างไฟล์ไว้ในโฟลเดอร์ที่ mount เข้า container ชั้นนอก แล้วให้ container ชั้นนอกสั่ง ",[18,210,211],{},"docker run -v /work:/work"," ต่ออีกทีนึง",[23,214,216],{"className":75,"code":215,"language":77,"meta":31,"style":31},"docker run --rm \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v /tmp/hostdir:/work \\\n  docker:cli sh -c 'ls /work; docker run --rm -v /work:/work alpine ls -la /work'\n",[18,217,218,228,236,245],{"__ignoreMap":31},[81,219,220,222,224,226],{"class":83,"line":84},[81,221,67],{"class":87},[81,223,91],{"class":90},[81,225,95],{"class":94},[81,227,99],{"class":98},[81,229,230,232,234],{"class":83,"line":102},[81,231,105],{"class":94},[81,233,108],{"class":90},[81,235,99],{"class":98},[81,237,238,240,243],{"class":83,"line":113},[81,239,105],{"class":94},[81,241,242],{"class":90}," /tmp/hostdir:/work",[81,244,99],{"class":98},[81,246,248,250,253,256,258,261],{"class":83,"line":247},4,[81,249,116],{"class":90},[81,251,252],{"class":90}," sh",[81,254,255],{"class":94}," -c",[81,257,129],{"class":128},[81,259,260],{"class":90},"ls /work; docker run --rm -v /work:/work alpine ls -la /work",[81,262,135],{"class":128},[23,264,267],{"className":265,"code":266,"language":28},[26],"--- ls /work จากคอนเทนเนอร์ชั้นนอก:\nhello.txt\n\n--- ls /work จากคอนเทนเนอร์ที่ spawn ออกมา:\ntotal 0\ndrwxr-xr-x    2 root     root            40 Aug  3 04:35 .\ndrwxr-xr-x    1 root     root            20 Aug  3 04:35 ..\n",[18,268,266],{"__ignoreMap":31},[14,270,271],{},"ไฟล์หายไปเฉย ๆ ครับ ตอนเจอครั้งแรกผมนึกว่า mount ไม่ติดหรือพิมพ์ path ผิดอยู่ตั้งนาน",[14,273,274,275,278,279,282],{},"สาเหตุจริง ๆ คือคนที่แปล ",[18,276,277],{},"-v /work:/work"," ให้เราคือ daemon ครับ ซึ่ง daemon มันอยู่บน host ไม่ได้อยู่ใน container ชั้นนอกกับเรา พอบน host ไม่มีโฟลเดอร์ชื่อ ",[18,280,281],{},"/work"," มันก็เลยสร้างโฟลเดอร์เปล่าขึ้นมาให้ตามน้ำ ไม่บ่นอะไรสักคำ",[14,284,285],{},"พอมาอยู่ในบริบทของ CI ก็แปลว่า job ที่พยายาม mount workspace ของตัวเองเข้าไปใน container ที่สั่งสร้างออกมา จะได้โฟลเดอร์ว่างเปล่ากลับมาแทน ทางออกคือต้องส่ง path ฝั่ง host เข้าไปเสมอครับ ซึ่ง runner ส่วนใหญ่ก็จะมี environment variable เตรียมไว้ให้อยู่แล้ว หรือถ้าไม่อยากปวดหัวก็เปลี่ยนไปใช้ named volume แทน bind mount ไปเลยก็ได้",[14,287,288,289,291,292,295],{},"อีกวิธีที่ผมชอบคือเปลี่ยนจาก ",[18,290,202],{}," ไปใช้ ",[18,293,294],{},"--mount type=bind"," แทนครับ พอ source ไม่มีอยู่บน host มันจะ fail ทันทีแทนที่จะเงียบ ๆ สร้างโฟลเดอร์เปล่ามาให้",[23,297,299],{"className":75,"code":298,"language":77,"meta":31,"style":31},"docker run --rm --mount type=bind,source=/ไม่มีจริง,target=/work alpine ls /work\n",[18,300,301],{"__ignoreMap":31},[81,302,303,305,307,309,312,315,318,321],{"class":83,"line":84},[81,304,67],{"class":87},[81,306,91],{"class":90},[81,308,95],{"class":94},[81,310,311],{"class":94}," --mount",[81,313,314],{"class":90}," type=bind,source=/ไม่มีจริง,target=/work",[81,316,317],{"class":90}," alpine",[81,319,320],{"class":90}," ls",[81,322,323],{"class":90}," /work\n",[23,325,328],{"className":326,"code":327,"language":28},[26],"docker: Error response from daemon: invalid mount config for type \"bind\": bind source path does not exist: /ไม่มีจริง\n",[18,329,327],{"__ignoreMap":31},[14,331,332],{},"ใน CI ผมว่าให้มันเด้ง error ตั้งแต่บรรทัดแรกไปเลยดีกว่าได้โฟลเดอร์ว่างมาแล้วต้องไปงงเอาตอน build เสร็จเยอะครับ",[10,334,336],{"id":335},"dind-คืออะไร","DinD คืออะไร",[14,338,339,340,342],{},"ฝั่งนี้จะเป็นการเอา daemon ยัดเข้าไปใน container เลยครับ ซึ่งจำเป็นต้องใช้ ",[18,341,53],{}," เพราะตัว daemon ต้องจัดการ cgroup, iptables แล้วก็ overlay filesystem ของตัวเอง",[23,344,346],{"className":75,"code":345,"language":77,"meta":31,"style":31},"docker network create dind-net\n\ndocker run -d --name dind-daemon \\\n  --privileged \\\n  --network dind-net \\\n  -e DOCKER_TLS_CERTDIR= \\\n  docker:dind\n",[18,347,348,361,367,384,391,402,413],{"__ignoreMap":31},[81,349,350,352,355,358],{"class":83,"line":84},[81,351,67],{"class":87},[81,353,354],{"class":90}," network",[81,356,357],{"class":90}," create",[81,359,360],{"class":90}," dind-net\n",[81,362,363],{"class":83,"line":102},[81,364,366],{"emptyLinePlaceholder":365},true,"\n",[81,368,369,371,373,376,379,382],{"class":83,"line":113},[81,370,67],{"class":87},[81,372,91],{"class":90},[81,374,375],{"class":94}," -d",[81,377,378],{"class":94}," --name",[81,380,381],{"class":90}," dind-daemon",[81,383,99],{"class":98},[81,385,386,389],{"class":83,"line":247},[81,387,388],{"class":94},"  --privileged",[81,390,99],{"class":98},[81,392,394,397,400],{"class":83,"line":393},5,[81,395,396],{"class":94},"  --network",[81,398,399],{"class":90}," dind-net",[81,401,99],{"class":98},[81,403,405,408,411],{"class":83,"line":404},6,[81,406,407],{"class":94},"  -e",[81,409,410],{"class":90}," DOCKER_TLS_CERTDIR=",[81,412,99],{"class":98},[81,414,416],{"class":83,"line":415},7,[81,417,418],{"class":90},"  docker:dind\n",[14,420,421,422,425],{},"ตรง ",[18,423,424],{},"DOCKER_TLS_CERTDIR="," ที่ปล่อยเป็นค่าว่างไว้คือการปิด TLS เพื่อให้ต่อผ่าน port 2375 ตรง ๆ ได้ครับ ถ้าไม่ตั้งไว้ image มันจะ generate cert ให้เองแล้วไปเปิดที่ port 2376 แทน ซึ่งตัว daemon ก็จะเตือนไว้ใน log ให้ด้วย",[23,427,430],{"className":428,"code":429,"language":28},[26],"level=warning msg=\"Binding to an IP address without --tlsverify is insecure\"\n",[18,431,429],{"__ignoreMap":31},[14,433,434],{},"ในเครื่องตัวเองลองเล่นแบบนี้ก็พอไหวครับ แต่ถ้าจะเอาไปใช้จริงบน network ที่มีคนอื่นอยู่ด้วยอย่าปิด TLS นะครับ",[14,436,437],{},"ทีนี้ลองต่อเข้าไปจาก client อีกตัวดู",[23,439,441],{"className":75,"code":440,"language":77,"meta":31,"style":31},"docker run --rm --network dind-net \\\n  -e DOCKER_HOST=tcp://dind-daemon:2375 \\\n  docker:cli docker info --format 'ServerVersion={{.ServerVersion}} Containers={{.Containers}} Name={{.Name}}'\n",[18,442,443,458,467],{"__ignoreMap":31},[81,444,445,447,449,451,454,456],{"class":83,"line":84},[81,446,67],{"class":87},[81,448,91],{"class":90},[81,450,95],{"class":94},[81,452,453],{"class":94}," --network",[81,455,399],{"class":90},[81,457,99],{"class":98},[81,459,460,462,465],{"class":83,"line":102},[81,461,407],{"class":94},[81,463,464],{"class":90}," DOCKER_HOST=tcp://dind-daemon:2375",[81,466,99],{"class":98},[81,468,469,471,473,475,477,479,481],{"class":83,"line":113},[81,470,116],{"class":90},[81,472,119],{"class":90},[81,474,177],{"class":90},[81,476,125],{"class":94},[81,478,129],{"class":128},[81,480,184],{"class":90},[81,482,135],{"class":128},[23,484,487],{"className":485,"code":486,"language":28},[26],"ServerVersion=29.7.0 Containers=0 Name=dc782fdd39de\n",[18,488,486],{"__ignoreMap":31},[14,490,491],{},"อันนี้ต่างจาก DooD คนละเรื่องเลยครับ เวอร์ชันขึ้นเป็น 29.7.0 ทั้งที่ host ของผมเป็น 29.4.0 ชื่อ daemon ก็กลายเป็น container id ไปแล้ว ส่วน container ทั้ง 23 ตัวที่รันอยู่บน host ก็หายไปหมดเหลือ 0 เพราะมันคนละ daemon กันจริง ๆ",[14,493,494],{},"ลอง build image ข้างในดูอีกที",[23,496,498],{"className":75,"code":497,"language":77,"meta":31,"style":31},"docker run --rm --network dind-net -e DOCKER_HOST=tcp://dind-daemon:2375 docker:cli \\\n  sh -c 'mkdir -p /ctx && printf \"FROM alpine\\nRUN echo hi > /hi.txt\\n\" > /ctx/Dockerfile\n         docker build -q -t demo-image:1.0 /ctx > /dev/null\n         docker images --format \"{{.Repository}}:{{.Tag}}\"'\n",[18,499,500,522,534,539],{"__ignoreMap":31},[81,501,502,504,506,508,510,512,515,517,520],{"class":83,"line":84},[81,503,67],{"class":87},[81,505,91],{"class":90},[81,507,95],{"class":94},[81,509,453],{"class":94},[81,511,399],{"class":90},[81,513,514],{"class":94}," -e",[81,516,464],{"class":90},[81,518,519],{"class":90}," docker:cli",[81,521,99],{"class":98},[81,523,524,527,529,531],{"class":83,"line":102},[81,525,526],{"class":90},"  sh",[81,528,255],{"class":94},[81,530,129],{"class":128},[81,532,533],{"class":90},"mkdir -p /ctx && printf \"FROM alpine\\nRUN echo hi > /hi.txt\\n\" > /ctx/Dockerfile\n",[81,535,536],{"class":83,"line":113},[81,537,538],{"class":90},"         docker build -q -t demo-image:1.0 /ctx > /dev/null\n",[81,540,541,544],{"class":83,"line":247},[81,542,543],{"class":90},"         docker images --format \"{{.Repository}}:{{.Tag}}\"",[81,545,135],{"class":128},[23,547,550],{"className":548,"code":549,"language":28},[26],"demo-image:1.0\n",[18,551,549],{"__ignoreMap":31},[14,553,554],{},"แล้วกลับมาถามที่ host ดูบ้าง",[23,556,558],{"className":75,"code":557,"language":77,"meta":31,"style":31},"docker images demo-image\n",[18,559,560],{"__ignoreMap":31},[81,561,562,564,567],{"class":83,"line":84},[81,563,67],{"class":87},[81,565,566],{"class":90}," images",[81,568,569],{"class":90}," demo-image\n",[23,571,574],{"className":572,"code":573,"language":28},[26],"(ไม่มี)\n",[18,575,573],{"__ignoreMap":31},[14,577,578],{},"image ที่ build เสร็จมันอยู่แค่ใน DinD ตัวนั้นตัวเดียวครับ พอ container ตายเมื่อไหร่ก็หายไปพร้อมกันหมดเลย",[580,581,583],"h3",{"id":582},"ข้อควรระวังของ-dind","ข้อควรระวังของ DinD",[14,585,586,587,590,591,594],{},"เรื่องแรกคือ daemon มันใช้เวลาบูตครับ รอบแรกผมรอไป 8 วินาทีแล้วยิงคำสั่งเข้าไปเลย ได้ ",[18,588,589],{},"Cannot connect to the Docker daemon"," กลับมาเต็ม ๆ ทั้งที่ status ของ container ขึ้นเป็น ",[18,592,593],{},"running"," เรียบร้อยแล้ว (ตัว container มันขึ้นก่อน ส่วน daemon ค่อยพร้อมทีหลัง) เพราะฉะนั้นใน CI ควรมี wait loop คั่นไว้ด้วยนะครับ",[23,596,598],{"className":75,"code":597,"language":77,"meta":31,"style":31},"until docker -H tcp://dind-daemon:2375 info > /dev/null 2>&1; do\n  echo \"รอ dind...\"\n  sleep 1\ndone\n",[18,599,600,633,648,657],{"__ignoreMap":31},[81,601,602,606,608,611,614,616,620,623,626,630],{"class":83,"line":84},[81,603,605],{"class":604},"s36IK","until",[81,607,119],{"class":87},[81,609,610],{"class":94}," -H",[81,612,613],{"class":90}," tcp://dind-daemon:2375",[81,615,177],{"class":90},[81,617,619],{"class":618},"sbGrh"," >",[81,621,622],{"class":90}," /dev/null",[81,624,625],{"class":618}," 2>&1",[81,627,629],{"class":628},"stlKB",";",[81,631,632],{"class":604}," do\n",[81,634,635,639,642,645],{"class":83,"line":102},[81,636,638],{"class":637},"sJZfL","  echo",[81,640,641],{"class":128}," \"",[81,643,644],{"class":90},"รอ dind...",[81,646,647],{"class":128},"\"\n",[81,649,650,653],{"class":83,"line":113},[81,651,652],{"class":87},"  sleep",[81,654,656],{"class":655},"snOyU"," 1\n",[81,658,659],{"class":83,"line":247},[81,660,661],{"class":604},"done\n",[14,663,664,665,668],{},"อีกเรื่องคือมันไม่มี layer cache ติดมาให้เลย daemon เกิดใหม่ก็เริ่มจากศูนย์ ทุก job เลยต้องดึง base image ใหม่หมดทุกรอบ ถ้าไม่อยากให้ CI ช้าลงเป็นเท่าตัวก็ต้องไป mount volume ไว้ที่ ",[18,666,667],{},"/var/lib/docker"," ของ DinD ครับ แต่พอทำแบบนั้นแล้ว job ก็จะเริ่มแชร์ state กัน ความสะอาดที่เป็นข้อดีข้อเดียวของ DinD ก็หายไปพอสมควรเหมือนกันนะ",[14,670,671,672,674],{},"ที่สำคัญกว่านั้นคือ ห้ามให้ DinD หลายตัวชี้มาที่ volume ก้อนเดียวกันเด็ดขาดครับ เพราะ Docker daemon ถูกออกแบบมาให้ครอบครอง ",[18,673,667],{}," อยู่ตัวเดียว ถ้ามีสอง daemon มาแย่งกันใช้ไม่ใช่แค่แชร์ cache กันนะ แต่ข้างในจะพังเลย (jpetazzo เขียนเตือนเรื่องนี้ไว้ตั้งแต่ปี 2015 แล้ว ลิงก์อยู่ท้ายบทความ) ถ้าจะใช้ cache volume จริง ๆ ก็ต้องแยก volume ให้แต่ละ job หรือยอมรันทีละ job ไปเลย",[10,676,677],{"id":677},"เรื่องความปลอดภัย",[14,679,680,681,683],{},"ผมเชื่อว่าหลายคนเลือก DooD เพราะรู้สึกว่ามันเบากว่า ไม่ต้องใช้ ",[18,682,53],{}," เลยน่าจะปลอดภัยกว่า ซึ่งจริง ๆ แล้วไม่ใช่แบบนั้นเลยครับ ลองดูอันนี้",[23,685,687],{"className":75,"code":686,"language":77,"meta":31,"style":31},"docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker:cli \\\n  sh -c 'docker run --rm -v /:/host:ro alpine sh -c \"id -un; cat /host/etc/hostname\"'\n",[18,688,689,705],{"__ignoreMap":31},[81,690,691,693,695,697,699,701,703],{"class":83,"line":84},[81,692,67],{"class":87},[81,694,91],{"class":90},[81,696,95],{"class":94},[81,698,164],{"class":94},[81,700,108],{"class":90},[81,702,519],{"class":90},[81,704,99],{"class":98},[81,706,707,709,711,713,716],{"class":83,"line":102},[81,708,526],{"class":90},[81,710,255],{"class":94},[81,712,129],{"class":128},[81,714,715],{"class":90},"docker run --rm -v /:/host:ro alpine sh -c \"id -un; cat /host/etc/hostname\"",[81,717,135],{"class":128},[23,719,722],{"className":720,"code":721,"language":28},[26],"whoami-ในคอนเทนเนอร์ที่ spawn: root\nอ่าน /host/etc/hostname ของ host ได้:\norbstack\n",[18,723,721],{"__ignoreMap":31},[14,725,726,727,730],{},"จาก container ที่ไม่ได้เปิด privileged เลยสักนิด ผมสั่ง mount root filesystem ของ host เข้ามาอ่านได้สบาย ๆ ในฐานะ root แล้วถ้าไม่ใส่ ",[18,728,729],{},":ro"," ไว้ก็เขียนทับได้ด้วยนะครับ",[14,732,733,734,736],{},"พูดง่าย ๆ คือการ mount ",[18,735,45],{}," บน Docker แบบปกติมันเท่ากับการให้สิทธิ์ root บนเครื่อง host ไปเลยครับ ใครก็ตามที่แก้ไฟล์ CI ได้ ก็เท่ากับเป็นเจ้าของเครื่องนั้นไปโดยปริยาย (จะลดขอบเขตลงได้ก็ต่อเมื่อรัน Docker แบบ rootless หรือมี socket proxy คั่นไว้เท่านั้น)",[14,738,739,740,742],{},"ทีนี้หลายคนน่าจะคิดว่าถ้างั้น DinD ที่ต้องเปิด ",[18,741,53],{}," ก็คงปลอดภัยกว่าอยู่ดี เพราะกว่าจะหลุดออกมาได้ต้องไปหาช่องโหว่ kernel ก่อน ตอนแรกผมก็เข้าใจแบบนั้นครับ แต่พอลองจริงแล้วไม่ใช่เลย",[23,744,746],{"className":75,"code":745,"language":77,"meta":31,"style":31},"docker run --rm --privileged alpine sh -c 'mkdir -p /mnt/hostdisk; mount /dev/vdb1 /mnt/hostdisk; ls /mnt/hostdisk'\n",[18,747,748],{"__ignoreMap":31},[81,749,750,752,754,756,759,761,763,765,767,770],{"class":83,"line":84},[81,751,67],{"class":87},[81,753,91],{"class":90},[81,755,95],{"class":94},[81,757,758],{"class":94}," --privileged",[81,760,317],{"class":90},[81,762,252],{"class":90},[81,764,255],{"class":94},[81,766,129],{"class":128},[81,768,769],{"class":90},"mkdir -p /mnt/hostdisk; mount /dev/vdb1 /mnt/hostdisk; ls /mnt/hostdisk",[81,771,135],{"class":128},[23,773,776],{"className":774,"code":775,"language":28},[26],"docker\nguest-state\nk8s\nscon\nversion\n",[18,777,775],{"__ignoreMap":31},[14,779,780,781,784,785,787,788,791],{},"นั่นคือไส้ในของดิสก์ host ที่โผล่มาให้เห็นเฉย ๆ ครับ ต่อจากนี้แค่ ",[18,782,783],{},"chroot"," เข้าไปก็ได้ shell ที่เป็น root บน host แล้ว ไม่ได้ใช้ช่องโหว่อะไรเลยสักอย่าง เป็นแค่การใช้ความสามารถที่ ",[18,786,53],{}," เปิดให้ตามปกติ (ยังมีท่า cgroup ",[18,789,790],{},"release_agent"," อีกท่าที่ดังกว่านี้ แต่ใช้ได้เฉพาะ host ที่ยังเป็น cgroup v1)",[14,793,794],{},"สรุปคือทั้งสองแบบให้ root บน host ได้เหมือนกันครับ ต่างกันแค่ DooD พิมพ์คำสั่งเดียวก็ถึง ส่วน DinD พิมพ์เพิ่มอีกบรรทัดสองบรรทัด ไม่มีอันไหนเป็นกำแพงกันคนที่ตั้งใจจะเข้ามาจริง ๆ",[14,796,797],{},"ข้อสรุปของทั้งคู่เลยเหมือนกันครับ คืออย่าเอาไปรัน job ที่มาจาก repo ที่เราคุมคนเขียนไม่ได้",[10,799,800],{"id":800},"ทางเลือกอื่น",[14,802,803],{},"หลายเคสที่คนไปจนถึงขั้นต้องเลือกระหว่าง DinD กับ DooD จริง ๆ แล้วต้องการแค่ \"ได้ image ออกมาสัก tag นึง\" เท่านั้นเองครับ ซึ่งงานแค่นี้มีทางที่เบากว่าอยู่",[14,805,806,807,810,811,814,815,818,819,822,823,826],{},"ตัวแรกคือ BuildKit ที่รันแบบ rootless ได้ ไม่ต้องเปิด privileged เต็ม ๆ แต่ต้องบอกไว้ก่อนว่ามันก็มีเงื่อนไขของมันอยู่ดีนะครับ ยังต้องปลด seccomp กับ AppArmor ต่อ ",[18,808,809],{},"/dev/fuse"," แล้วก็ตั้ง subuid/subgid ให้ถูกอยู่ดี แล้วถ้าใช้ผ่าน ",[18,812,813],{},"docker buildx"," เฉย ๆ ตัว driver ปกติของมันยังคุยผ่าน daemon อยู่ ถ้าจะเลี่ยง daemon จริง ๆ ต้องไปใช้ driver แบบ ",[18,816,817],{},"remote"," หรือ ",[18,820,821],{},"kubernetes"," หรือเรียก ",[18,824,825],{},"buildctl"," ตรง ๆ",[14,828,829,830,832],{},"อีกตัวคือ Podman ที่ไม่มี daemon มาตั้งแต่แรก รันเป็น rootless ได้ถ้าเรียกด้วย user ธรรมดา คำสั่งก็เกือบจะเหมือน docker หมด (แต่พอเอาไปซ้อนใน CI container อีกทีก็ยังต้องจัด subuid/subgid กับ ",[18,831,809],{}," ให้มันเหมือนกัน)",[14,834,835,836,818,839,842],{},"สองตัวนี้ไม่ได้แปลว่าไม่ต้องตั้งค่าอะไรเลยนะครับ แต่อย่างน้อยก็ไม่ต้องยกสิทธิ์ root ของทั้งเครื่องให้ CI ถือไว้ ถ้า pipeline ของเราไม่ได้ต้อง ",[18,837,838],{},"docker run",[18,840,841],{},"docker compose up"," จริง ๆ ลองทางนี้ก่อนน่าจะสบายใจกว่า",[10,844,845],{"id":845},"สรุป",[847,848,849,862],"table",{},[850,851,852],"thead",{},[853,854,855,859],"tr",{},[856,857,858],"th",{},"โจทย์",[856,860,861],{},"เลือก",[863,864,865,873,880,888],"tbody",{},[853,866,867,871],{},[868,869,870],"td",{},"runner บนเครื่องตัวเอง คนใช้เชื่อถือได้ อยากได้ cache",[868,872,41],{},[853,874,875,878],{},[868,876,877],{},"อยากให้แต่ละ job สะอาด ไม่ปน state กัน",[868,879,49],{},[853,881,882,885],{},[868,883,884],{},"แค่อยากได้ image ออกมา",[868,886,887],{},"buildx rootless / Podman",[853,889,890,893],{},[868,891,892],{},"job มาจาก repo ที่คุมคนเขียนไม่ได้",[868,894,895],{},"ไม่เอาทั้งคู่ แยก VM ไปเลย",[14,897,898],{},"ส่วนตัวผมใช้ DooD กับ runner ที่บ้านครับ เพราะ cache มันเร็วกว่าเยอะแล้วก็ไม่ต้องมานั่งจูน wait loop ให้ปวดหัว แต่ก็ยอมรับตรง ๆ นะว่ามันคือการยกกุญแจบ้านให้ CI ถือไว้ พอเป็นเครื่องที่มีผมใช้อยู่คนเดียวก็พอแลกกันได้อยู่ ถ้าเป็นเครื่องที่มีคนอื่นเข้ามาใช้ด้วยผมคงไม่กล้าทำแบบนี้ครับ",[10,900,902],{"id":901},"references","References",[904,905,906,916,923,930],"ul",{},[907,908,909],"li",{},[910,911,915],"a",{"href":912,"rel":913},"https://docs.docker.com/engine/security/protect-access/",[914],"nofollow","Docker Engine — Protect the Docker daemon socket",[907,917,918],{},[910,919,922],{"href":920,"rel":921},"https://hub.docker.com/_/docker",[914],"docker image บน Docker Hub (docker:dind, docker:cli)",[907,924,925],{},[910,926,929],{"href":927,"rel":928},"https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/",[914],"Using Docker-in-Docker for your CI or testing environment? Think twice. — jpetazzo",[907,931,932],{},[910,933,936],{"href":934,"rel":935},"https://docs.docker.com/engine/security/rootless/",[914],"Rootless mode",[938,939,940],"style",{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .sicAX, html code.shiki .sicAX{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s36IK, html code.shiki .s36IK{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#C678DD;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sJZfL, html code.shiki .sJZfL{--shiki-light:#6182B8;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}html pre.shiki code .snOyU, html code.shiki .snOyU{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#79B8FF}",{"title":31,"searchDepth":102,"depth":102,"links":942},[943,944,945,947,950,951,952,953],{"id":12,"depth":102,"text":12},{"id":60,"depth":102,"text":61},{"id":198,"depth":102,"text":946},"กับดักเรื่อง path ใน -v",{"id":335,"depth":102,"text":336,"children":948},[949],{"id":582,"depth":113,"text":583},{"id":677,"depth":102,"text":677},{"id":800,"depth":102,"text":800},{"id":845,"depth":102,"text":845},{"id":901,"depth":102,"text":902},"2026-08-03T00:00:00.000Z","เทียบ Docker in Docker กับ Docker out of Docker สำหรับ self-hosted CI runner ที่ต้อง build image เอง พร้อมกับดักเรื่อง volume path และความปลอดภัยที่คนมักมองข้าม",false,"md",{},null,"Docker DinD vs DooD","/blogs/2026-08-03-docker-dind-vs-dood",[963],"2025-05-18-from-old-notebook-to-home-lab-server.md",{"title":5,"description":955},{"loc":961},"blogs/2026-08-03-docker-dind-vs-dood",[968],"Development","CIsdiJUS1jVVi379Rkhvms-TiuNx9bR1p2UgvbDiA84",[971],{"id":972,"title":973,"body":974,"date":1200,"description":1201,"draft":956,"extension":957,"meta":1202,"navigation":365,"next":959,"ogTitle":1203,"path":1204,"pin":956,"prev":959,"recommends":1205,"seo":1208,"sitemap":1209,"stem":1210,"tags":1211,"updated":959,"__hash__":1213},"content/blogs/2025-05-18-from-old-notebook-to-home-lab-server.md","เปลี่ยน Notebook (เก่า) ที่ไม่ได้ใช้งานเป็น Home Lab Server",{"type":7,"value":975,"toc":1193},[976,982,989,993,996,1017,1023,1035,1040,1043,1046,1066,1071,1082,1087,1093,1098,1101,1105,1123,1128,1131,1147,1151,1154,1156,1159,1162,1182,1187,1190],[14,977,978],{},[979,980],"img",{"alt":31,"src":981},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-f67e4b1b-db83-40d3-82ed-3fbdc5562404.jpg",[14,983,984,985,988],{},"สวัสดีครับ พอดีผมมีโปรเจคนึงที่ต้องการใช้งาน Server ขนาดเล็ก ๆ เครื่องนึง กำลังจะหาซื้อเครื่อง Mini PC ใหม่มาเพิ่มพอดี แต่โปรเจคนี้ก็ยังไม่มั่นใจว่าจะไปต่อได้แค่ไหนก่อนที่จะซื้อ hardware มาใหม่ เลยจะลองหยิบเอา Notebook เครื่องเก่ามาลองรันดูก่อนครับ ไหน ๆ ก็ละเลยมาเขียนเป็นบทความสั้น ๆ เอาไว้แชร์เป็นไอเดียสำหรับคนที่ต้องการจะลองเล่น Home Lab แต่ยังไม่อยากจะซื้อเครื่องใหม่ จริง ๆ แล้ว ",[39,986,987],{},"notebook"," นายเองก็เป็นได้นะ server อะ !",[10,990,992],{"id":991},"ข้อดีของการนำ-notebook-มาใช้งาน","ข้อดีของการนำ notebook มาใช้งาน",[14,994,995],{},"หลาย ๆ คนคงจะคุ้นชินกับการใช้งาน notebook ในแบบปกติคือการใช้งาน windows ทั่ว ๆ ไป แต่ก็อาจจะมีหลาย ๆ คนที่เป็น advanced user ขึ้นมาหน่อยจับมาลง Linux ตัวต่าง ๆ ใช้งาน วันนี้จะลองพามาเปลี่ยนจาก notebook ให้กลายเป็น server ขนาดเล็ก ๆ กันครับ แล้วมันเหมาะสมยังไง",[904,997,998,1001,1011,1014],{},[907,999,1000],{},"มีแบตเตอร์รี่ในตัว ปกติแล้ว server หรือ home server ที่ดีควรจะมี UPS เพื่อช่วยสำรองไฟในกรณีที่ไฟตกหรือไฟดับจะช่วยทำให้อุปกรณ์เสียหายได้ยากขึ้น ซึ่ง notebook (ในสภาพปกติ) จะมีแบตเตอร์รี่ในตัวอยู่แล้ว",[907,1002,1003,1004,1008],{},"กินไฟน้อย ถ้าเป็น notebook ที่ไม่ใช้ gaming notebook นะครับ ปกติแล้วจะกินไฟน้อย อันนี้คือจังหวะที่กำลังชาร์จแบต + idle อยู่นะครับกินไฟอยู่ที่ประมาณ 30W เอง (อันนี้ใช้ performance mode ถ้าปรับเป็น balanced mode จะลงไปอยู่ที่ราว ๆ 15 W) (แต่ก็เยอะถ้าเทียบกับตระกูล mini PC ที่มี CPU N100) ",[1005,1006,1007],"em",{},"เดี๋ยวจะมาอัพเดทอีกทีหลังผ่านไป 1 เดือนนะครับว่ากินไฟไปกี่หน่วย",[979,1009],{"alt":31,"src":1010},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-661534a0-e4a4-4d14-9238-67f04d5de3a7.jpg",[907,1012,1013],{},"มีจอ, trackpad, keyboard ในตัว เวลาเครื่องมีปัญหาก็จะสามารถตรวจสอบได้เลย ไม่ต้องหาอุปกรณ์มาต่อเพิ่ม",[907,1015,1016],{},"ประหยัดค่าใช้จ่าย หลาย ๆ คนอยากจะเล่น Home Lab เป็นงานอดิเรกนะครับ ซึ่งการเอาอุปกรณ์ที่มีอยู่แล้วมาใช้งานก็จะช่วยประหยัดไปได้อีก",[14,1018,1019,1020],{},"ตอนที่ไม่ได้มีการใช้งานเปิดเครื่องทิ้งไว้เฉย ๆ กินไฟ 3W เอง (น้อยมากก)\n",[979,1021],{"alt":31,"src":1022},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-0bafd825-77ca-4f2c-b5ac-8ab9394dfb8f.jpg",[14,1024,1025,1026,1030,1031,1034],{},"ผมขอข้ามการติดตั้ง OS ไปนะครับ (ผมติดตั้งเป็น Ubuntu version 24 ไปกดรับ version pro ได้นะ ",[910,1027,1029],{"href":1028},"/blogs/ubuntu-pro","วิธีรับ Ubuntu Pro",") เพราะทำแบบปกติทั่ว ๆ ไปเลยไม่ได้มีอะไรผิดปกติ แนะนำว่าให้เสียบสายแลนแทนการใช้งาน Wifi นะครับ เผื่อความ stable ของการใช้งาน หลังจากนั้นก็สามารถใช้ ",[18,1032,1033],{},"ssh"," เพื่อเชื่อมต่อเข้าไปได้เลย (IP address แนะนำให้ fixed ไว้ตอนติดตั้งนะครับ ไม่แนะนำให้ใช้ DHCP จะปวดหัวเอาทีหลัง)",[14,1036,1037],{},[979,1038],{"alt":31,"src":1039},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-1.png",[10,1041,1042],{"id":1042},"วิธีการทำให้หน้าจอของเครื่องดับ",[14,1044,1045],{},"ปกติแล้วในการใช้งาน ubuntu server จะไม่ได้ใช้งานหน้าจอหรืออุปกรณ์โดยตรงนะครับ ซึ่งตรงนี้เราจะสามารถปิดการใช้หลาย ๆ function ออกไปได้ ซึ่งในการปิดหน้าจอสามารถทำได้ตามนี้เลยครับ",[23,1047,1049],{"className":75,"code":1048,"language":77,"meta":31,"style":31},"sudo vim /etc/default/grub # หรือจะใช้ nano ก็ได้\n",[18,1050,1051],{"__ignoreMap":31},[81,1052,1053,1056,1059,1062],{"class":83,"line":84},[81,1054,1055],{"class":87},"sudo",[81,1057,1058],{"class":90}," vim",[81,1060,1061],{"class":90}," /etc/default/grub",[81,1063,1065],{"class":1064},"sDKbk"," # หรือจะใช้ nano ก็ได้\n",[14,1067,1068],{},[979,1069],{"alt":31,"src":1070},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-33c75f2a-a1f7-4294-a2d8-7b769330b6c6.jpg",[14,1072,1073,1074,1077,1078,1081],{},"โดยเพิ่ม ",[18,1075,1076],{},"consoleblank=120"," ไปยัง ",[18,1079,1080],{},"GRUB_CMDLINE_LINUX_DEFAULT="," สามารถปรับเวลาที่ต้องการได้เลย หน่วยเป็นวินาที",[14,1083,1084],{},[979,1085],{"alt":31,"src":1086},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-fd057b5b-b310-4d8c-b7e0-088c6481403f.jpg",[14,1088,1089,1090],{},"จากนั้นออกมาที่ terminal เหมือมเดิมแล้วรัน ",[18,1091,1092],{},"sudo update-grub",[14,1094,1095],{},[979,1096],{"alt":31,"src":1097},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-cad0302a-9626-4746-99da-7ef8acb21a57.jpg",[14,1099,1100],{},"จากนั้น reboot เครื่องแล้วรอตามเวลาที่ตั้งไว้ได้เลย ว่าหน้าจอจะดับหรือไม่ ของผมตั้งไว้ 120 ก็คือรอ 2 นาทีครับ",[10,1102,1104],{"id":1103},"ตรวจสอบว่า-sleep-mode-ถูกปิดใช้งานอยู่","ตรวจสอบว่า sleep mode ถูกปิดใช้งานอยู่",[23,1106,1108],{"className":75,"code":1107,"language":77,"meta":31,"style":31},"sudo systemctl status sleep.target\n",[18,1109,1110],{"__ignoreMap":31},[81,1111,1112,1114,1117,1120],{"class":83,"line":84},[81,1113,1055],{"class":87},[81,1115,1116],{"class":90}," systemctl",[81,1118,1119],{"class":90}," status",[81,1121,1122],{"class":90}," sleep.target\n",[14,1124,1125],{},[979,1126],{"alt":31,"src":1127},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-0f23e13d-d614-4174-852e-6257096028a1.jpg",[14,1129,1130],{},"วิธีการปิดใช้งาน",[23,1132,1134],{"className":75,"code":1133,"language":77,"meta":31,"style":31},"sudo systemctl mask sleep.target\n",[18,1135,1136],{"__ignoreMap":31},[81,1137,1138,1140,1142,1145],{"class":83,"line":84},[81,1139,1055],{"class":87},[81,1141,1116],{"class":90},[81,1143,1144],{"class":90}," mask",[81,1146,1122],{"class":90},[10,1148,1150],{"id":1149},"วิธีการปิดไฟ-black-light-ของ-keyboard","วิธีการปิดไฟ black light ของ keyboard",[14,1152,1153],{},"อันนี้จะขึ้นอยู่กับแต่ละรุ่นนะครับว่ารองรับการปิดใช้มั้ย ต้องเข้าไปดูใน bios เลยครับ ซึ่งของผมอันนี้ไม่รองรับ ต้องเดินไปกดปิดไฟหน้าเครื่องเอง (sad) ไว้จะมาลองหาวิธีอีกทีครับ",[10,1155,845],{"id":845},[14,1157,1158],{},"อันนี้ก็เป็นไอเดียนะครับว่าเราสามารถจับเอา notebook มาทำเป็น home server ขนาดเล็ก ๆ ได้ แต่อาจจะต้องปรับเปลี่ยนการตั้งค่านิดหน่อยเพื่อให้เหมาะสมกับการเป็น server จากนี้ก็ขึ้นอยู่กับจุดประสงค์ว่าจะเอาไปทำอะไรต่อ เช่น ลง docker + portainer ทำเป็น web server หรือจะลองลง proxmox ก็ได้นะ ผมมีเครื่อง notebook ที่ลองลง proxmox ไว้เหมือนกัน",[14,1160,1161],{},"สิ่งที่ทำเพิ่มได้",[904,1163,1164,1167,1170,1173,1176,1179],{},[907,1165,1166],{},"ติดตั้ง docker",[907,1168,1169],{},"ติดตั้ง zsh เพื่อปรับแต่ง terminal",[907,1171,1172],{},"ลง license เพื่อใช้งาน ubuntu pro (เคยเขียนบทความไว้แล้ว)",[907,1174,1175],{},"ติดตั้ง tailscale เพื่อใช้งานเครื่องนี้จากภายนอก",[907,1177,1178],{},"ติดตั้ง coder เพื่อทำเป็น remote dev server",[907,1180,1181],{},"ทำระบบ backup",[14,1183,1184],{},[979,1185],{"alt":31,"src":1186},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-ff8a1e4b-de92-477b-9e8a-a93b64ee8878.jpg",[14,1188,1189],{},"ขอบคุณที่อ่านจนจบครับบ เย่ ~",[938,1191,1192],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":31,"searchDepth":102,"depth":102,"links":1194},[1195,1196,1197,1198,1199],{"id":991,"depth":102,"text":992},{"id":1042,"depth":102,"text":1042},{"id":1103,"depth":102,"text":1104},{"id":1149,"depth":102,"text":1150},{"id":845,"depth":102,"text":845},"2025-05-18T00:00:00.000Z","อยากลองเล่น Home Lab แต่ไม่อยากซื้ออุปกรณ์ใหม่ ลองเอา notebook เครื่องเก่ามาลองใช้ก่อนได้นะ",{},"Home Lab Server","/blogs/2025-05-18-from-old-notebook-to-home-lab-server",[1206,1207],"2025-04-15-ubuntu-pro.md","2024-01-14-setup-ubuntu-server.md",{"title":973,"description":1201},{"loc":1204},"blogs/2025-05-18-from-old-notebook-to-home-lab-server",[1212],"Homelab","h9IeFWVHTQTbxdgOil0HF02queMa9aGWkJO1uV6H9sk",[1215,2529,2630,2797,3344,3534,4469,5856,6482,6775,6928,7232,7537,7654],{"id":1216,"title":1217,"body":1218,"date":2518,"description":2519,"draft":956,"extension":957,"meta":2520,"navigation":365,"next":959,"ogTitle":2521,"path":2522,"pin":956,"prev":959,"recommends":959,"seo":2523,"sitemap":2524,"stem":2525,"tags":2526,"updated":2527,"__hash__":2528},"content/blogs/2024-01-20-simple-paginate.md","Simple Paginate",{"type":7,"value":1219,"toc":2514},[1220,1228,1231,1346,1349,1353,2114,2118,2506,2511],[904,1221,1222,1225],{},[907,1223,1224],{},"ปล. เป็น Code แบบ Simple",[907,1226,1227],{},"ปล2. Code ตัวอย่างคือการใช้งานใน Express + Typescript + Mongoose",[14,1229,1230],{},"ใน output ที่ตอบกลับจะเป็น",[23,1232,1236],{"className":1233,"code":1234,"language":1235,"meta":31,"style":31},"language-json shiki shiki-themes material-theme-lighter one-dark-pro github-dark","{\n  \"data\": [],\n  \"currentPage\": \"หน้าปัจจุบัน\",\n  \"nextPage\": \"หน้าถัดไปถ้ามี แต่ถ้าไม่มีจะเป็น Null\",\n  \"prevPage\": \"หน้าก่อนหน้าถ้ามี แต่ถ้าไม่มีจะเป็น Null\",\n  \"count\": \"จำนวน page ที่มี\"\n}\n","json",[18,1237,1238,1243,1262,1283,1303,1323,1341],{"__ignoreMap":31},[81,1239,1240],{"class":83,"line":84},[81,1241,1242],{"class":628},"{\n",[81,1244,1245,1249,1253,1256,1259],{"class":83,"line":102},[81,1246,1248],{"class":1247},"sObbn","  \"",[81,1250,1252],{"class":1251},"sVeMc","data",[81,1254,1255],{"class":1247},"\"",[81,1257,1258],{"class":628},":",[81,1260,1261],{"class":628}," [],\n",[81,1263,1264,1266,1269,1271,1273,1275,1278,1280],{"class":83,"line":113},[81,1265,1248],{"class":1247},[81,1267,1268],{"class":1251},"currentPage",[81,1270,1255],{"class":1247},[81,1272,1258],{"class":628},[81,1274,641],{"class":128},[81,1276,1277],{"class":90},"หน้าปัจจุบัน",[81,1279,1255],{"class":128},[81,1281,1282],{"class":628},",\n",[81,1284,1285,1287,1290,1292,1294,1296,1299,1301],{"class":83,"line":247},[81,1286,1248],{"class":1247},[81,1288,1289],{"class":1251},"nextPage",[81,1291,1255],{"class":1247},[81,1293,1258],{"class":628},[81,1295,641],{"class":128},[81,1297,1298],{"class":90},"หน้าถัดไปถ้ามี แต่ถ้าไม่มีจะเป็น Null",[81,1300,1255],{"class":128},[81,1302,1282],{"class":628},[81,1304,1305,1307,1310,1312,1314,1316,1319,1321],{"class":83,"line":393},[81,1306,1248],{"class":1247},[81,1308,1309],{"class":1251},"prevPage",[81,1311,1255],{"class":1247},[81,1313,1258],{"class":628},[81,1315,641],{"class":128},[81,1317,1318],{"class":90},"หน้าก่อนหน้าถ้ามี แต่ถ้าไม่มีจะเป็น Null",[81,1320,1255],{"class":128},[81,1322,1282],{"class":628},[81,1324,1325,1327,1330,1332,1334,1336,1339],{"class":83,"line":404},[81,1326,1248],{"class":1247},[81,1328,1329],{"class":1251},"count",[81,1331,1255],{"class":1247},[81,1333,1258],{"class":628},[81,1335,641],{"class":128},[81,1337,1338],{"class":90},"จำนวน page ที่มี",[81,1340,647],{"class":128},[81,1342,1343],{"class":83,"line":415},[81,1344,1345],{"class":628},"}\n",[14,1347,1348],{},"ซึ่งจะทำให้ Frontend สามารถนำไปใช้งานได้ง่ายยิ่งขึ้น",[10,1350,1352],{"id":1351},"_1-สร้างไฟล์-utilspaginationutiljs","1. สร้างไฟล์ utils/pagination.util.js",[23,1354,1358],{"className":1355,"code":1356,"language":1357,"meta":31,"style":31},"language-js shiki shiki-themes material-theme-lighter one-dark-pro github-dark","interface PaginationParams {\n  perPage: number;\n  page: number;\n  skip: number;\n}\n\ninterface PaginationResponse\u003CT> {\n  data: T[];\n  currentPage: number;\n  nextPage?: number;\n  prevPage?: number;\n  count: number;\n}\n\nexport function buildPaginationParam(\n  page: string,\n  perPage: string,\n  limit: number\n): PaginationParams {\n  // Pagination\n  const pageInt = !Number.isNaN(parseInt(page)) ? parseInt(page) : 1\n  let perPageInt = !Number.isNaN(parseInt(perPage)) ? parseInt(perPage) : 10\n\n  // Set a maximum limit\n  perPageInt = Math.min(perPageInt, limit)\n\n  const skip = (pageInt - 1) * perPageInt\n\n  return {\n    perPage: perPageInt,\n    page: pageInt,\n    skip,\n  }\n}\n\nexport function buildResponsePagination\u003CT>(\n  data: T[],\n  page: number,\n  perPage: number,\n  totalCount: number\n): PaginationResponse\u003CT> {\n  const currentPage = page\n  const totalPages = Math.ceil(totalCount / perPage)\n\n  const hasNextPage = currentPage \u003C totalPages\n  const hasPrevPage = currentPage > 1\n\n  const nextPage = hasNextPage ? currentPage + 1 : undefined\n  const prevPage = hasPrevPage ? currentPage - 1 : undefined\n\n  return {\n    data,\n    currentPage,\n    nextPage,\n    prevPage,\n    count: totalCount,\n  }\n}\n","js",[18,1359,1360,1373,1388,1399,1410,1414,1418,1436,1453,1465,1481,1495,1507,1512,1517,1533,1546,1557,1568,1580,1586,1645,1690,1695,1701,1731,1736,1766,1771,1779,1792,1804,1812,1818,1823,1828,1845,1858,1869,1880,1890,1907,1920,1950,1955,1973,1989,1994,2023,2047,2052,2059,2067,2075,2083,2091,2104,2109],{"__ignoreMap":31},[81,1361,1362,1366,1370],{"class":83,"line":84},[81,1363,1365],{"class":1364},"sOHk9","interface",[81,1367,1369],{"class":1368},"sGnaQ"," PaginationParams",[81,1371,1372],{"class":628}," {\n",[81,1374,1375,1379,1381,1385],{"class":83,"line":102},[81,1376,1378],{"class":1377},"slt33","  perPage",[81,1380,1258],{"class":618},[81,1382,1384],{"class":1383},"s6Wr0"," number",[81,1386,1387],{"class":628},";\n",[81,1389,1390,1393,1395,1397],{"class":83,"line":113},[81,1391,1392],{"class":1377},"  page",[81,1394,1258],{"class":618},[81,1396,1384],{"class":1383},[81,1398,1387],{"class":628},[81,1400,1401,1404,1406,1408],{"class":83,"line":247},[81,1402,1403],{"class":1377},"  skip",[81,1405,1258],{"class":618},[81,1407,1384],{"class":1383},[81,1409,1387],{"class":628},[81,1411,1412],{"class":83,"line":393},[81,1413,1345],{"class":628},[81,1415,1416],{"class":83,"line":404},[81,1417,366],{"emptyLinePlaceholder":365},[81,1419,1420,1422,1425,1428,1431,1434],{"class":83,"line":415},[81,1421,1365],{"class":1364},[81,1423,1424],{"class":1368}," PaginationResponse",[81,1426,1427],{"class":628},"\u003C",[81,1429,1430],{"class":1368},"T",[81,1432,1433],{"class":628},">",[81,1435,1372],{"class":628},[81,1437,1439,1442,1444,1447,1451],{"class":83,"line":1438},8,[81,1440,1441],{"class":1377},"  data",[81,1443,1258],{"class":618},[81,1445,1446],{"class":1368}," T",[81,1448,1450],{"class":1449},"sZDfs","[]",[81,1452,1387],{"class":628},[81,1454,1456,1459,1461,1463],{"class":83,"line":1455},9,[81,1457,1458],{"class":1377},"  currentPage",[81,1460,1258],{"class":618},[81,1462,1384],{"class":1383},[81,1464,1387],{"class":628},[81,1466,1468,1471,1475,1477,1479],{"class":83,"line":1467},10,[81,1469,1470],{"class":1377},"  nextPage",[81,1472,1474],{"class":1473},"s_AEr","?",[81,1476,1258],{"class":618},[81,1478,1384],{"class":1383},[81,1480,1387],{"class":628},[81,1482,1484,1487,1489,1491,1493],{"class":83,"line":1483},11,[81,1485,1486],{"class":1377},"  prevPage",[81,1488,1474],{"class":1473},[81,1490,1258],{"class":618},[81,1492,1384],{"class":1383},[81,1494,1387],{"class":628},[81,1496,1498,1501,1503,1505],{"class":83,"line":1497},12,[81,1499,1500],{"class":1377},"  count",[81,1502,1258],{"class":618},[81,1504,1384],{"class":1383},[81,1506,1387],{"class":628},[81,1508,1510],{"class":83,"line":1509},13,[81,1511,1345],{"class":628},[81,1513,1515],{"class":83,"line":1514},14,[81,1516,366],{"emptyLinePlaceholder":365},[81,1518,1520,1523,1526,1530],{"class":83,"line":1519},15,[81,1521,1522],{"class":604},"export",[81,1524,1525],{"class":1364}," function",[81,1527,1529],{"class":1528},"suMxZ"," buildPaginationParam",[81,1531,1532],{"class":628},"(\n",[81,1534,1536,1539,1541,1544],{"class":83,"line":1535},16,[81,1537,1392],{"class":1538},"stkEM",[81,1540,1258],{"class":618},[81,1542,1543],{"class":1383}," string",[81,1545,1282],{"class":628},[81,1547,1549,1551,1553,1555],{"class":83,"line":1548},17,[81,1550,1378],{"class":1538},[81,1552,1258],{"class":618},[81,1554,1543],{"class":1383},[81,1556,1282],{"class":628},[81,1558,1560,1563,1565],{"class":83,"line":1559},18,[81,1561,1562],{"class":1538},"  limit",[81,1564,1258],{"class":618},[81,1566,1567],{"class":1383}," number\n",[81,1569,1571,1574,1576,1578],{"class":83,"line":1570},19,[81,1572,1573],{"class":628},")",[81,1575,1258],{"class":618},[81,1577,1369],{"class":1368},[81,1579,1372],{"class":628},[81,1581,1583],{"class":83,"line":1582},20,[81,1584,1585],{"class":1064},"  // Pagination\n",[81,1587,1589,1592,1596,1600,1603,1607,1610,1613,1617,1620,1622,1626,1629,1631,1634,1636,1638,1641,1643],{"class":83,"line":1588},21,[81,1590,1591],{"class":1364},"  const",[81,1593,1595],{"class":1594},"sUzmo"," pageInt",[81,1597,1599],{"class":1598},"sMTEB"," =",[81,1601,1602],{"class":1598}," !",[81,1604,1606],{"class":1605},"sU9mH","Number",[81,1608,1609],{"class":628},".",[81,1611,1612],{"class":1528},"isNaN",[81,1614,1616],{"class":1615},"sbHgf","(",[81,1618,1619],{"class":1528},"parseInt",[81,1621,1616],{"class":1615},[81,1623,1625],{"class":1624},"sLHrO","page",[81,1627,1628],{"class":1615},")) ",[81,1630,1474],{"class":1473},[81,1632,1633],{"class":1528}," parseInt",[81,1635,1616],{"class":1615},[81,1637,1625],{"class":1624},[81,1639,1640],{"class":1615},") ",[81,1642,1258],{"class":1473},[81,1644,656],{"class":655},[81,1646,1648,1651,1654,1656,1658,1660,1662,1664,1666,1668,1670,1673,1675,1677,1679,1681,1683,1685,1687],{"class":83,"line":1647},22,[81,1649,1650],{"class":1364},"  let",[81,1652,1653],{"class":1624}," perPageInt",[81,1655,1599],{"class":1598},[81,1657,1602],{"class":1598},[81,1659,1606],{"class":1605},[81,1661,1609],{"class":628},[81,1663,1612],{"class":1528},[81,1665,1616],{"class":1615},[81,1667,1619],{"class":1528},[81,1669,1616],{"class":1615},[81,1671,1672],{"class":1624},"perPage",[81,1674,1628],{"class":1615},[81,1676,1474],{"class":1473},[81,1678,1633],{"class":1528},[81,1680,1616],{"class":1615},[81,1682,1672],{"class":1624},[81,1684,1640],{"class":1615},[81,1686,1258],{"class":1473},[81,1688,1689],{"class":655}," 10\n",[81,1691,1693],{"class":83,"line":1692},23,[81,1694,366],{"emptyLinePlaceholder":365},[81,1696,1698],{"class":83,"line":1697},24,[81,1699,1700],{"class":1064},"  // Set a maximum limit\n",[81,1702,1704,1707,1709,1712,1714,1717,1719,1722,1725,1728],{"class":83,"line":1703},25,[81,1705,1706],{"class":1624},"  perPageInt",[81,1708,1599],{"class":1598},[81,1710,1711],{"class":1605}," Math",[81,1713,1609],{"class":628},[81,1715,1716],{"class":1528},"min",[81,1718,1616],{"class":1615},[81,1720,1721],{"class":1624},"perPageInt",[81,1723,1724],{"class":628},",",[81,1726,1727],{"class":1624}," limit",[81,1729,1730],{"class":1615},")\n",[81,1732,1734],{"class":83,"line":1733},26,[81,1735,366],{"emptyLinePlaceholder":365},[81,1737,1739,1741,1744,1746,1749,1752,1755,1758,1760,1763],{"class":83,"line":1738},27,[81,1740,1591],{"class":1364},[81,1742,1743],{"class":1594}," skip",[81,1745,1599],{"class":1598},[81,1747,1748],{"class":1615}," (",[81,1750,1751],{"class":1624},"pageInt",[81,1753,1754],{"class":1598}," -",[81,1756,1757],{"class":655}," 1",[81,1759,1640],{"class":1615},[81,1761,1762],{"class":1598},"*",[81,1764,1765],{"class":1624}," perPageInt\n",[81,1767,1769],{"class":83,"line":1768},28,[81,1770,366],{"emptyLinePlaceholder":365},[81,1772,1774,1777],{"class":83,"line":1773},29,[81,1775,1776],{"class":604},"  return",[81,1778,1372],{"class":628},[81,1780,1782,1786,1788,1790],{"class":83,"line":1781},30,[81,1783,1785],{"class":1784},"sTQd7","    perPage",[81,1787,1258],{"class":628},[81,1789,1653],{"class":1624},[81,1791,1282],{"class":628},[81,1793,1795,1798,1800,1802],{"class":83,"line":1794},31,[81,1796,1797],{"class":1784},"    page",[81,1799,1258],{"class":628},[81,1801,1595],{"class":1624},[81,1803,1282],{"class":628},[81,1805,1807,1810],{"class":83,"line":1806},32,[81,1808,1809],{"class":1624},"    skip",[81,1811,1282],{"class":628},[81,1813,1815],{"class":83,"line":1814},33,[81,1816,1817],{"class":628},"  }\n",[81,1819,1821],{"class":83,"line":1820},34,[81,1822,1345],{"class":628},[81,1824,1826],{"class":83,"line":1825},35,[81,1827,366],{"emptyLinePlaceholder":365},[81,1829,1831,1833,1835,1838,1840,1842],{"class":83,"line":1830},36,[81,1832,1522],{"class":604},[81,1834,1525],{"class":1364},[81,1836,1837],{"class":1528}," buildResponsePagination",[81,1839,1427],{"class":628},[81,1841,1430],{"class":1368},[81,1843,1844],{"class":628},">(\n",[81,1846,1848,1850,1852,1854,1856],{"class":83,"line":1847},37,[81,1849,1441],{"class":1538},[81,1851,1258],{"class":618},[81,1853,1446],{"class":1368},[81,1855,1450],{"class":1449},[81,1857,1282],{"class":628},[81,1859,1861,1863,1865,1867],{"class":83,"line":1860},38,[81,1862,1392],{"class":1538},[81,1864,1258],{"class":618},[81,1866,1384],{"class":1383},[81,1868,1282],{"class":628},[81,1870,1872,1874,1876,1878],{"class":83,"line":1871},39,[81,1873,1378],{"class":1538},[81,1875,1258],{"class":618},[81,1877,1384],{"class":1383},[81,1879,1282],{"class":628},[81,1881,1883,1886,1888],{"class":83,"line":1882},40,[81,1884,1885],{"class":1538},"  totalCount",[81,1887,1258],{"class":618},[81,1889,1567],{"class":1383},[81,1891,1893,1895,1897,1899,1901,1903,1905],{"class":83,"line":1892},41,[81,1894,1573],{"class":628},[81,1896,1258],{"class":618},[81,1898,1424],{"class":1368},[81,1900,1427],{"class":628},[81,1902,1430],{"class":1368},[81,1904,1433],{"class":628},[81,1906,1372],{"class":628},[81,1908,1910,1912,1915,1917],{"class":83,"line":1909},42,[81,1911,1591],{"class":1364},[81,1913,1914],{"class":1594}," currentPage",[81,1916,1599],{"class":1598},[81,1918,1919],{"class":1624}," page\n",[81,1921,1923,1925,1928,1930,1932,1934,1937,1939,1942,1945,1948],{"class":83,"line":1922},43,[81,1924,1591],{"class":1364},[81,1926,1927],{"class":1594}," totalPages",[81,1929,1599],{"class":1598},[81,1931,1711],{"class":1605},[81,1933,1609],{"class":628},[81,1935,1936],{"class":1528},"ceil",[81,1938,1616],{"class":1615},[81,1940,1941],{"class":1624},"totalCount",[81,1943,1944],{"class":1598}," /",[81,1946,1947],{"class":1624}," perPage",[81,1949,1730],{"class":1615},[81,1951,1953],{"class":83,"line":1952},44,[81,1954,366],{"emptyLinePlaceholder":365},[81,1956,1958,1960,1963,1965,1967,1970],{"class":83,"line":1957},45,[81,1959,1591],{"class":1364},[81,1961,1962],{"class":1594}," hasNextPage",[81,1964,1599],{"class":1598},[81,1966,1914],{"class":1624},[81,1968,1969],{"class":1598}," \u003C",[81,1971,1972],{"class":1624}," totalPages\n",[81,1974,1976,1978,1981,1983,1985,1987],{"class":83,"line":1975},46,[81,1977,1591],{"class":1364},[81,1979,1980],{"class":1594}," hasPrevPage",[81,1982,1599],{"class":1598},[81,1984,1914],{"class":1624},[81,1986,619],{"class":1598},[81,1988,656],{"class":655},[81,1990,1992],{"class":83,"line":1991},47,[81,1993,366],{"emptyLinePlaceholder":365},[81,1995,1997,1999,2002,2004,2006,2009,2011,2014,2016,2019],{"class":83,"line":1996},48,[81,1998,1591],{"class":1364},[81,2000,2001],{"class":1594}," nextPage",[81,2003,1599],{"class":1598},[81,2005,1962],{"class":1624},[81,2007,2008],{"class":1473}," ?",[81,2010,1914],{"class":1624},[81,2012,2013],{"class":1598}," +",[81,2015,1757],{"class":655},[81,2017,2018],{"class":1473}," :",[81,2020,2022],{"class":2021},"scCEF"," undefined\n",[81,2024,2026,2028,2031,2033,2035,2037,2039,2041,2043,2045],{"class":83,"line":2025},49,[81,2027,1591],{"class":1364},[81,2029,2030],{"class":1594}," prevPage",[81,2032,1599],{"class":1598},[81,2034,1980],{"class":1624},[81,2036,2008],{"class":1473},[81,2038,1914],{"class":1624},[81,2040,1754],{"class":1598},[81,2042,1757],{"class":655},[81,2044,2018],{"class":1473},[81,2046,2022],{"class":2021},[81,2048,2050],{"class":83,"line":2049},50,[81,2051,366],{"emptyLinePlaceholder":365},[81,2053,2055,2057],{"class":83,"line":2054},51,[81,2056,1776],{"class":604},[81,2058,1372],{"class":628},[81,2060,2062,2065],{"class":83,"line":2061},52,[81,2063,2064],{"class":1624},"    data",[81,2066,1282],{"class":628},[81,2068,2070,2073],{"class":83,"line":2069},53,[81,2071,2072],{"class":1624},"    currentPage",[81,2074,1282],{"class":628},[81,2076,2078,2081],{"class":83,"line":2077},54,[81,2079,2080],{"class":1624},"    nextPage",[81,2082,1282],{"class":628},[81,2084,2086,2089],{"class":83,"line":2085},55,[81,2087,2088],{"class":1624},"    prevPage",[81,2090,1282],{"class":628},[81,2092,2094,2097,2099,2102],{"class":83,"line":2093},56,[81,2095,2096],{"class":1784},"    count",[81,2098,1258],{"class":628},[81,2100,2101],{"class":1624}," totalCount",[81,2103,1282],{"class":628},[81,2105,2107],{"class":83,"line":2106},57,[81,2108,1817],{"class":628},[81,2110,2112],{"class":83,"line":2111},58,[81,2113,1345],{"class":628},[10,2115,2117],{"id":2116},"_2-usage","2. Usage",[23,2119,2121],{"className":1355,"code":2120,"language":1357,"meta":31,"style":31},"const { page, perPage } = req.query\n\nconst {\n    perPage: perPageInt,\n    page: pageInt,\n    skip,\n} = buildPaginationParam(page as string, perPage as string, 10)\n\n// Find all post\nconst [posts, count] = await Promise.all([\n    await Post.find({})\n        .skip(skip)\n        .limit(perPageInt)\n        .lean(),\n    await Post.countDocuments(),\n])\n\nconst output = buildResponsePagination(\n    posts,\n    pageInt,\n    perPageInt,\n    count\n)\n\nres.send({\n    message: SuccessMessages.GetSuccess,\n    data: output.data,\n    currentPage: output.currentPage,\n    nextPage: output.nextPage || null,\n    prevPage: output.prevPage || null,\n    count: count,\n})\n",[18,2122,2123,2151,2155,2161,2171,2181,2187,2220,2224,2229,2263,2283,2297,2310,2322,2337,2342,2346,2359,2366,2373,2380,2385,2389,2393,2407,2424,2438,2452,2472,2490,2500],{"__ignoreMap":31},[81,2124,2125,2128,2131,2134,2136,2138,2141,2143,2146,2148],{"class":83,"line":84},[81,2126,2127],{"class":1364},"const",[81,2129,2130],{"class":628}," {",[81,2132,2133],{"class":1594}," page",[81,2135,1724],{"class":628},[81,2137,1947],{"class":1594},[81,2139,2140],{"class":628}," }",[81,2142,1599],{"class":1598},[81,2144,2145],{"class":1605}," req",[81,2147,1609],{"class":628},[81,2149,2150],{"class":1624},"query\n",[81,2152,2153],{"class":83,"line":102},[81,2154,366],{"emptyLinePlaceholder":365},[81,2156,2157,2159],{"class":83,"line":113},[81,2158,2127],{"class":1364},[81,2160,1372],{"class":628},[81,2162,2163,2165,2167,2169],{"class":83,"line":247},[81,2164,1785],{"class":1377},[81,2166,1258],{"class":628},[81,2168,1653],{"class":1594},[81,2170,1282],{"class":628},[81,2172,2173,2175,2177,2179],{"class":83,"line":393},[81,2174,1797],{"class":1377},[81,2176,1258],{"class":628},[81,2178,1595],{"class":1594},[81,2180,1282],{"class":628},[81,2182,2183,2185],{"class":83,"line":404},[81,2184,1809],{"class":1594},[81,2186,1282],{"class":628},[81,2188,2189,2192,2194,2196,2198,2200,2203,2205,2207,2209,2211,2213,2215,2218],{"class":83,"line":415},[81,2190,2191],{"class":628},"}",[81,2193,1599],{"class":1598},[81,2195,1529],{"class":1528},[81,2197,1616],{"class":1449},[81,2199,1625],{"class":1624},[81,2201,2202],{"class":604}," as",[81,2204,1543],{"class":1383},[81,2206,1724],{"class":628},[81,2208,1947],{"class":1624},[81,2210,2202],{"class":604},[81,2212,1543],{"class":1383},[81,2214,1724],{"class":628},[81,2216,2217],{"class":655}," 10",[81,2219,1730],{"class":1449},[81,2221,2222],{"class":83,"line":1438},[81,2223,366],{"emptyLinePlaceholder":365},[81,2225,2226],{"class":83,"line":1455},[81,2227,2228],{"class":1064},"// Find all post\n",[81,2230,2231,2233,2236,2239,2241,2244,2247,2249,2252,2255,2257,2260],{"class":83,"line":1467},[81,2232,2127],{"class":1364},[81,2234,2235],{"class":628}," [",[81,2237,2238],{"class":1594},"posts",[81,2240,1724],{"class":628},[81,2242,2243],{"class":1594}," count",[81,2245,2246],{"class":628},"]",[81,2248,1599],{"class":1598},[81,2250,2251],{"class":604}," await",[81,2253,2254],{"class":1383}," Promise",[81,2256,1609],{"class":628},[81,2258,2259],{"class":1528},"all",[81,2261,2262],{"class":1449},"([\n",[81,2264,2265,2268,2271,2273,2276,2278,2281],{"class":83,"line":1483},[81,2266,2267],{"class":604},"    await",[81,2269,2270],{"class":1605}," Post",[81,2272,1609],{"class":628},[81,2274,2275],{"class":1528},"find",[81,2277,1616],{"class":1449},[81,2279,2280],{"class":628},"{}",[81,2282,1730],{"class":1449},[81,2284,2285,2288,2291,2293,2295],{"class":83,"line":1497},[81,2286,2287],{"class":628},"        .",[81,2289,2290],{"class":1528},"skip",[81,2292,1616],{"class":1449},[81,2294,2290],{"class":1624},[81,2296,1730],{"class":1449},[81,2298,2299,2301,2304,2306,2308],{"class":83,"line":1509},[81,2300,2287],{"class":628},[81,2302,2303],{"class":1528},"limit",[81,2305,1616],{"class":1449},[81,2307,1721],{"class":1624},[81,2309,1730],{"class":1449},[81,2311,2312,2314,2317,2320],{"class":83,"line":1514},[81,2313,2287],{"class":628},[81,2315,2316],{"class":1528},"lean",[81,2318,2319],{"class":1449},"()",[81,2321,1282],{"class":628},[81,2323,2324,2326,2328,2330,2333,2335],{"class":83,"line":1519},[81,2325,2267],{"class":604},[81,2327,2270],{"class":1605},[81,2329,1609],{"class":628},[81,2331,2332],{"class":1528},"countDocuments",[81,2334,2319],{"class":1449},[81,2336,1282],{"class":628},[81,2338,2339],{"class":83,"line":1535},[81,2340,2341],{"class":1449},"])\n",[81,2343,2344],{"class":83,"line":1548},[81,2345,366],{"emptyLinePlaceholder":365},[81,2347,2348,2350,2353,2355,2357],{"class":83,"line":1559},[81,2349,2127],{"class":1364},[81,2351,2352],{"class":1594}," output",[81,2354,1599],{"class":1598},[81,2356,1837],{"class":1528},[81,2358,1532],{"class":1449},[81,2360,2361,2364],{"class":83,"line":1570},[81,2362,2363],{"class":1624},"    posts",[81,2365,1282],{"class":628},[81,2367,2368,2371],{"class":83,"line":1582},[81,2369,2370],{"class":1624},"    pageInt",[81,2372,1282],{"class":628},[81,2374,2375,2378],{"class":83,"line":1588},[81,2376,2377],{"class":1624},"    perPageInt",[81,2379,1282],{"class":628},[81,2381,2382],{"class":83,"line":1647},[81,2383,2384],{"class":1624},"    count\n",[81,2386,2387],{"class":83,"line":1692},[81,2388,1730],{"class":1449},[81,2390,2391],{"class":83,"line":1697},[81,2392,366],{"emptyLinePlaceholder":365},[81,2394,2395,2398,2400,2403,2405],{"class":83,"line":1703},[81,2396,2397],{"class":1605},"res",[81,2399,1609],{"class":628},[81,2401,2402],{"class":1528},"send",[81,2404,1616],{"class":1449},[81,2406,1242],{"class":628},[81,2408,2409,2412,2414,2417,2419,2422],{"class":83,"line":1733},[81,2410,2411],{"class":1784},"    message",[81,2413,1258],{"class":628},[81,2415,2416],{"class":1605}," SuccessMessages",[81,2418,1609],{"class":628},[81,2420,2421],{"class":1624},"GetSuccess",[81,2423,1282],{"class":628},[81,2425,2426,2428,2430,2432,2434,2436],{"class":83,"line":1738},[81,2427,2064],{"class":1784},[81,2429,1258],{"class":628},[81,2431,2352],{"class":1605},[81,2433,1609],{"class":628},[81,2435,1252],{"class":1624},[81,2437,1282],{"class":628},[81,2439,2440,2442,2444,2446,2448,2450],{"class":83,"line":1768},[81,2441,2072],{"class":1784},[81,2443,1258],{"class":628},[81,2445,2352],{"class":1605},[81,2447,1609],{"class":628},[81,2449,1268],{"class":1624},[81,2451,1282],{"class":628},[81,2453,2454,2456,2458,2460,2462,2464,2467,2470],{"class":83,"line":1773},[81,2455,2080],{"class":1784},[81,2457,1258],{"class":628},[81,2459,2352],{"class":1605},[81,2461,1609],{"class":628},[81,2463,1289],{"class":1624},[81,2465,2466],{"class":1598}," ||",[81,2468,2469],{"class":2021}," null",[81,2471,1282],{"class":628},[81,2473,2474,2476,2478,2480,2482,2484,2486,2488],{"class":83,"line":1781},[81,2475,2088],{"class":1784},[81,2477,1258],{"class":628},[81,2479,2352],{"class":1605},[81,2481,1609],{"class":628},[81,2483,1309],{"class":1624},[81,2485,2466],{"class":1598},[81,2487,2469],{"class":2021},[81,2489,1282],{"class":628},[81,2491,2492,2494,2496,2498],{"class":83,"line":1794},[81,2493,2096],{"class":1784},[81,2495,1258],{"class":628},[81,2497,2243],{"class":1624},[81,2499,1282],{"class":628},[81,2501,2502,2504],{"class":83,"line":1806},[81,2503,2191],{"class":628},[81,2505,1730],{"class":1449},[904,2507,2508],{},[907,2509,2510],{},"ที่ต้องกำหนด Limit ไว้ด้วย เนื่องจากป้องกันการ Dump ข้อมูลจาก database และลดการทำงานของ Server ได้",[938,2512,2513],{},"html pre.shiki code .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sObbn, html code.shiki .sObbn{--shiki-light:#39ADB5;--shiki-default:#E06C75;--shiki-dark:#79B8FF}html pre.shiki code .sVeMc, html code.shiki .sVeMc{--shiki-light:#9C3EDA;--shiki-default:#E06C75;--shiki-dark:#79B8FF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOHk9, html code.shiki .sOHk9{--shiki-light:#9C3EDA;--shiki-default:#C678DD;--shiki-dark:#F97583}html pre.shiki code .sGnaQ, html code.shiki .sGnaQ{--shiki-light:#E2931D;--shiki-default:#E5C07B;--shiki-dark:#B392F0}html pre.shiki code .slt33, html code.shiki .slt33{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#FFAB70}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .s6Wr0, html code.shiki .s6Wr0{--shiki-light:#E2931D;--shiki-default:#E5C07B;--shiki-dark:#79B8FF}html pre.shiki code .sZDfs, html code.shiki .sZDfs{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .s_AEr, html code.shiki .s_AEr{--shiki-light:#39ADB5;--shiki-default:#C678DD;--shiki-dark:#F97583}html pre.shiki code .s36IK, html code.shiki .s36IK{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#C678DD;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .suMxZ, html code.shiki .suMxZ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .stkEM, html code.shiki .stkEM{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E06C75;--shiki-default-font-style:italic;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sUzmo, html code.shiki .sUzmo{--shiki-light:#90A4AE;--shiki-default:#E5C07B;--shiki-dark:#79B8FF}html pre.shiki code .sMTEB, html code.shiki .sMTEB{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F97583}html pre.shiki code .sU9mH, html code.shiki .sU9mH{--shiki-light:#90A4AE;--shiki-default:#E5C07B;--shiki-dark:#E1E4E8}html pre.shiki code .sbHgf, html code.shiki .sbHgf{--shiki-light:#E53935;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sLHrO, html code.shiki .sLHrO{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}html pre.shiki code .snOyU, html code.shiki .snOyU{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .sTQd7, html code.shiki .sTQd7{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}html pre.shiki code .scCEF, html code.shiki .scCEF{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#79B8FF}",{"title":31,"searchDepth":102,"depth":102,"links":2515},[2516,2517],{"id":1351,"depth":102,"text":1352},{"id":2116,"depth":102,"text":2117},"2024-01-20T00:00:00.000Z","โค้ดตัวอย่างการทำ pagination แบบง่าย ๆ ด้วย Express + TypeScript + Mongoose พร้อมหน้าตา response",{},"Express Simple Paginate","/blogs/2024-01-20-simple-paginate",{"title":1217,"description":2519},{"loc":2522},"blogs/2024-01-20-simple-paginate",[968],"2026-08-09T00:00:00.000Z","msUkB89AwjPKbV7ZyOfOlRGwPy8R1UqCxvgnHTuQ3-s",{"id":2530,"title":2531,"body":2532,"date":2620,"description":2539,"draft":956,"extension":957,"meta":2621,"navigation":365,"next":959,"ogTitle":2622,"path":2623,"pin":956,"prev":959,"recommends":2624,"seo":2625,"sitemap":2626,"stem":2627,"tags":2628,"updated":2527,"__hash__":2629},"content/blogs/2024-06-06-openvpn-exclude-network.md","ตั้งค่าให้สามารถเชื่อมต่อ Local Network ได้ ตอนที่เชื่อม openvpn",{"type":7,"value":2533,"toc":2616},[2534,2537,2540,2544,2569,2572,2607,2610,2613],[14,2535,2536],{},"วิธีการแก้ไขปัญหาเชื่อมต่อ Internal Network ไม่ได้เมื่อมีการต่อ OPENVPN อยู่",[14,2538,2539],{},"แก้ไขไฟล์ openvpn config หากมีการ import เข้าไปใน openvpn gui แล้วสามารถค้นหาไฟล์การตั้งค่าได้ที่",[10,2541,2543],{"id":2542},"สำหรับ-macos","สำหรับ macos",[23,2545,2548],{"className":75,"code":2546,"filename":2547,"language":77,"meta":31,"style":31},"/Users/\u003Cyour_username_here>/Library/Application\\ Support/OpenVPN\\ Connect/profiles\n","⌘",[18,2549,2550],{"__ignoreMap":31},[81,2551,2552,2555,2558,2561,2564,2566],{"class":83,"line":84},[81,2553,2554],{"class":87},"/Users/",[81,2556,2557],{"class":1449},"\u003Cyour_username_here>/Library/Application",[81,2559,2560],{"class":98},"\\ ",[81,2562,2563],{"class":1449},"Support/OpenVPN",[81,2565,2560],{"class":98},[81,2567,2568],{"class":1449},"Connect/profiles\n",[14,2570,2571],{},"เพิ่ม config",[23,2573,2575],{"className":75,"code":2574,"language":77,"meta":31,"style":31},"# Exclude my home network routes\nroute 192.168.1.0 255.255.255.0 net_gateway\nroute 192.168.2.0 255.255.255.0 net_gateway\n",[18,2576,2577,2582,2596],{"__ignoreMap":31},[81,2578,2579],{"class":83,"line":84},[81,2580,2581],{"class":1064},"# Exclude my home network routes\n",[81,2583,2584,2587,2590,2593],{"class":83,"line":102},[81,2585,2586],{"class":87},"route",[81,2588,2589],{"class":655}," 192.168.1.0",[81,2591,2592],{"class":655}," 255.255.255.0",[81,2594,2595],{"class":90}," net_gateway\n",[81,2597,2598,2600,2603,2605],{"class":83,"line":113},[81,2599,2586],{"class":87},[81,2601,2602],{"class":655}," 192.168.2.0",[81,2604,2592],{"class":655},[81,2606,2595],{"class":90},[10,2608,2609],{"id":2609},"ทดสอบ",[14,2611,2612],{},"ทำการเชื่อมต่อ openvpn ใหม่ จะพบว่าสามารถเชื่อมต่อ local network ได้แล้ว หลักการง่าย ๆ ก็คือการทำให้ request ที่ไปยัง destination ดังกล่าวไม่ถูกส่งเข้าไปยัง openvpn นั้นเอง",[938,2614,2615],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sZDfs, html code.shiki .sZDfs{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sicAX, html code.shiki .sicAX{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .snOyU, html code.shiki .snOyU{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}",{"title":31,"searchDepth":102,"depth":102,"links":2617},[2618,2619],{"id":2542,"depth":102,"text":2543},{"id":2609,"depth":102,"text":2609},"2024-06-06T00:00:00.000Z",{},"Openvpn Split Tunnel","/blogs/2024-06-06-openvpn-exclude-network",[],{"title":2531,"description":2539},{"loc":2623},"blogs/2024-06-06-openvpn-exclude-network",[968],"U3NvL_9A6_sRDN8-TB5LxeMjQMn06U6_j1wnk-LlJs8",{"id":2631,"title":2632,"body":2633,"date":2787,"description":2643,"draft":956,"extension":957,"meta":2788,"navigation":365,"next":959,"ogTitle":2789,"path":2790,"pin":956,"prev":959,"recommends":2791,"seo":2792,"sitemap":2793,"stem":2794,"tags":2795,"updated":2527,"__hash__":2796},"content/blogs/2025-04-14-dotenv-leak-lab.md","วิธีการลบ .env file ออกจาก git แบบถูกวิธี",{"type":7,"value":2634,"toc":2781},[2635,2641,2644,2647,2655,2670,2676,2684,2688,2714,2720,2730,2734,2739,2760,2766,2773,2778],[14,2636,2637],{},[979,2638],{"alt":2639,"src":2640},".env leak","/images/2025-04-14-dotenv-leak-lab/image-0.png",[14,2642,2643],{},"สมมติว่าเราเผลอ commit ไฟล์ .env ที่มี API key ไปแล้ว และ push ขึ้น GitHub เราต้องรีบลบมันออกทันที เพราะข้อมูลอาจถูกนำไปใช้ได้",[14,2645,2646],{},"สำหรับวิธีการลบ .env file แบบถูกวิธีให้ทำแบบนี้นะครับ พร้อมแล้วไปลุยกันเลยย",[10,2648,2650,2651,2654],{"id":2649},"_1-นำ-env-file-ไปใส่ใน-gitignore-ก่อน","1. นำ .env file ไปใส่ใน ",[18,2652,2653],{},".gitignore"," ก่อน",[23,2656,2658],{"className":75,"code":2657,"filename":2653,"language":77,"meta":31,"style":31},".env\n.env.*\n",[18,2659,2660,2665],{"__ignoreMap":31},[81,2661,2662],{"class":83,"line":84},[81,2663,2664],{"class":87},".env\n",[81,2666,2667],{"class":83,"line":102},[81,2668,2669],{"class":87},".env.*\n",[2671,2672,2673],"blockquote",{},[14,2674,2675],{},".env.:* เช่น .env.production .env.local .env.development",[14,2677,2678,2679,2683],{},"แนะนำให้ลองใช้ ",[910,2680,2681],{"href":2681,"rel":2682},"https://www.toptal.com/developers/gitignore",[914]," ผมใช้เป็นประจำครับ เป็นเครื่องมือในการช่วยสร้าง gitignore ไฟล์ชั้นดีเลย",[10,2685,2687],{"id":2686},"_2-ลบ-env-file-ออกจาก-git-แบบในทุก-ๆ-commits","2. ลบ .env file ออกจาก git แบบในทุก ๆ commits",[23,2689,2691],{"className":75,"code":2690,"language":77,"meta":31,"style":31},"git filter-branch --index-filter \"git rm -rf --cached --ignore-unmatch .env\" HEAD\n",[18,2692,2693],{"__ignoreMap":31},[81,2694,2695,2698,2701,2704,2706,2709,2711],{"class":83,"line":84},[81,2696,2697],{"class":87},"git",[81,2699,2700],{"class":90}," filter-branch",[81,2702,2703],{"class":94}," --index-filter",[81,2705,641],{"class":128},[81,2707,2708],{"class":90},"git rm -rf --cached --ignore-unmatch .env",[81,2710,1255],{"class":128},[81,2712,2713],{"class":90}," HEAD\n",[14,2715,2716],{},[979,2717],{"alt":2718,"src":2719},"Output","/images/2025-04-14-dotenv-leak-lab/image-1.png",[14,2721,2722,2723,818,2726,2729],{},"ถ้าลบออกเฉย ๆ ด้วยคำสั่ง ",[18,2724,2725],{},"git rm -r --cached .env",[18,2727,2728],{},"rm .env"," แล้ว commit ไปใหม่ สิ่งที่จะเกิดขึ้นคือไฟล์จะหายไปแค่ commit ล่าสุดเท่านั้น แต่ถ้าไปดูใน commit history ไฟล์ดังกล่าวจะยังอยู่ครับ",[10,2731,2733],{"id":2732},"_3-push-git-ไฟล์อันใหม่ขึ้นไปด้วยคำสั่ง","3. push .git ไฟล์อันใหม่ขึ้นไปด้วยคำสั่ง",[2671,2735,2736],{},[14,2737,2738],{},"คำเตือน: คำสั่งนี้จะเปลี่ยนแปลงประวัติทั้งหมดของ Project ควรแน่ใจว่าไม่มีใครใช้งาน repository นี้อยู่ก่อนจะ push ด้วย --force",[23,2740,2742],{"className":75,"code":2741,"language":77,"meta":31,"style":31},"git push origin main --force\n",[18,2743,2744],{"__ignoreMap":31},[81,2745,2746,2748,2751,2754,2757],{"class":83,"line":84},[81,2747,2697],{"class":87},[81,2749,2750],{"class":90}," push",[81,2752,2753],{"class":90}," origin",[81,2755,2756],{"class":90}," main",[81,2758,2759],{"class":94}," --force\n",[14,2761,2762],{},[979,2763],{"alt":2764,"src":2765},"Result","/images/2025-04-14-dotenv-leak-lab/image-2.png",[14,2767,2768,2769,2772],{},"เพียงแค่นี้ ",[18,2770,2771],{},".env"," file ก็จะถูกลบออกไปจาก Git ของเราเรียบร้อยแล้วครับ",[2671,2774,2775],{},[14,2776,2777],{},"Tip: ถ้าเคย push .env ขึ้น GitHub, Gitlab, Gitea หรือ Git Platform อื่น ๆ แล้ว อย่าลืมรีเซ็ต API key หรือ Token ที่อยู่ในไฟล์ด้วยนะครับ เพื่อความปลอดภัยสูงสุด",[938,2779,2780],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}",{"title":31,"searchDepth":102,"depth":102,"links":2782},[2783,2785,2786],{"id":2649,"depth":102,"text":2784},"1. นำ .env file ไปใส่ใน .gitignore ก่อน",{"id":2686,"depth":102,"text":2687},{"id":2732,"depth":102,"text":2733},"2025-04-14T00:00:00.000Z",{},"How To Remove .env From Git","/blogs/2025-04-14-dotenv-leak-lab",[],{"title":2632,"description":2643},{"loc":2790},"blogs/2025-04-14-dotenv-leak-lab",[968],"X6tLTCscPI0dhvHTjnwInQQZatm8nYP-iNhV8COZjV4",{"id":2798,"title":2799,"body":2800,"date":3334,"description":3335,"draft":956,"extension":957,"meta":3336,"navigation":365,"next":959,"ogTitle":3337,"path":3338,"pin":956,"prev":959,"recommends":959,"seo":3339,"sitemap":3340,"stem":3341,"tags":3342,"updated":959,"__hash__":3343},"content/blogs/2025-04-25-setup-nuxt-medium-zoom.md","วิธีการติดตั้งและใช้งาน Medium Zoom ใน Nuxt 3",{"type":7,"value":2801,"toc":3325},[2802,2807,2810,2813,2817,2861,2865,2874,2878,3103,3107,3115,3208,3215,3308,3311,3313,3322],[14,2803,2804],{},[979,2805],{"alt":31,"src":2806},"/images/2025-04-25-setup-nuxt-medium-zoom/medium-zoom.png",[14,2808,2809],{},"วันนี้จะมาแนะนำวิธีการติดตั้ง Library นึงครับซึ่งจะทำให้รูปภายในเว็บไซต์ของเรามีลูกเล่นเหมือนกับรูปที่อยู่ภายในเว็บไซต์ Medium.com ครับ อยากรู้ว่าเป็นยังไงลองคลิกที่รูปด้านบนได้เลยครับ 👆",[10,2811,2812],{"id":2812},"วิธีการติดตั้ง",[580,2814,2816],{"id":2815},"_1-ติดตั้ง-library-ดังกล่าว","1. ติดตั้ง Library ดังกล่าว",[23,2818,2820],{"className":1355,"code":2819,"language":1357,"meta":31,"style":31},"npm install medium-zoom\n# หรือ\nyarn add medium-zoom\n",[18,2821,2822,2839,2847],{"__ignoreMap":31},[81,2823,2824,2827,2830,2833,2836],{"class":83,"line":84},[81,2825,2826],{"class":1624},"npm",[81,2828,2829],{"class":1624}," install",[81,2831,2832],{"class":1624}," medium",[81,2834,2835],{"class":1598},"-",[81,2837,2838],{"class":1624},"zoom\n",[81,2840,2841,2844],{"class":83,"line":102},[81,2842,2843],{"class":1449},"# ",[81,2845,2846],{"class":1624},"หรือ\n",[81,2848,2849,2852,2855,2857,2859],{"class":83,"line":113},[81,2850,2851],{"class":1624},"yarn",[81,2853,2854],{"class":1624}," add",[81,2856,2832],{"class":1624},[81,2858,2835],{"class":1598},[81,2860,2838],{"class":1624},[580,2862,2864],{"id":2863},"_2-สร้าง-folder-และ-file-plugins","2. สร้าง Folder และ File Plugins",[23,2866,2868],{"className":75,"code":2867,"language":77,"meta":31,"style":31},"# เติม .client.ts เพื่อกำหนดว่า plugins นี้เป็นของฝั่ง client อย่างเดียวนะครับ\n",[18,2869,2870],{"__ignoreMap":31},[81,2871,2872],{"class":83,"line":84},[81,2873,2867],{"class":1064},[580,2875,2877],{"id":2876},"_3-นำ-source-code-ไปวางภายในไฟล์ดังกล่าวได้เลยครับ","3. นำ Source Code ไปวางภายในไฟล์ดังกล่าวได้เลยครับ",[23,2879,2883],{"className":2880,"code":2881,"language":2882,"meta":31,"style":31},"language-typescript shiki shiki-themes material-theme-lighter one-dark-pro github-dark","import { defineNuxtPlugin } from '#app'\nimport mediumZoom, { Zoom } from 'medium-zoom'\n\nexport default defineNuxtPlugin((nuxtApp) => {\n  const selector = '.image-zoomable'\n  const zoom: Zoom = mediumZoom(selector, {})\n\n  nuxtApp.hook('page:finish', () => {\n    zoom.detach(selector).attach(selector)\n  })\n\n  nuxtApp.provide('mediumZoom', zoom)\n})\n","typescript",[18,2884,2885,2907,2932,2936,2959,2975,3002,3006,3035,3062,3069,3073,3097],{"__ignoreMap":31},[81,2886,2887,2890,2892,2895,2897,2900,2902,2905],{"class":83,"line":84},[81,2888,2889],{"class":604},"import",[81,2891,2130],{"class":628},[81,2893,2894],{"class":1624}," defineNuxtPlugin",[81,2896,2140],{"class":628},[81,2898,2899],{"class":604}," from",[81,2901,129],{"class":128},[81,2903,2904],{"class":90},"#app",[81,2906,135],{"class":128},[81,2908,2909,2911,2914,2916,2918,2921,2923,2925,2927,2930],{"class":83,"line":102},[81,2910,2889],{"class":604},[81,2912,2913],{"class":1624}," mediumZoom",[81,2915,1724],{"class":628},[81,2917,2130],{"class":628},[81,2919,2920],{"class":1624}," Zoom",[81,2922,2140],{"class":628},[81,2924,2899],{"class":604},[81,2926,129],{"class":128},[81,2928,2929],{"class":90},"medium-zoom",[81,2931,135],{"class":128},[81,2933,2934],{"class":83,"line":113},[81,2935,366],{"emptyLinePlaceholder":365},[81,2937,2938,2940,2943,2945,2947,2949,2952,2954,2957],{"class":83,"line":247},[81,2939,1522],{"class":604},[81,2941,2942],{"class":604}," default",[81,2944,2894],{"class":1528},[81,2946,1616],{"class":1449},[81,2948,1616],{"class":628},[81,2950,2951],{"class":1538},"nuxtApp",[81,2953,1573],{"class":628},[81,2955,2956],{"class":1364}," =>",[81,2958,1372],{"class":628},[81,2960,2961,2963,2966,2968,2970,2973],{"class":83,"line":393},[81,2962,1591],{"class":1364},[81,2964,2965],{"class":1594}," selector",[81,2967,1599],{"class":1598},[81,2969,129],{"class":128},[81,2971,2972],{"class":90},".image-zoomable",[81,2974,135],{"class":128},[81,2976,2977,2979,2982,2984,2986,2988,2990,2992,2995,2997,3000],{"class":83,"line":404},[81,2978,1591],{"class":1364},[81,2980,2981],{"class":1594}," zoom",[81,2983,1258],{"class":618},[81,2985,2920],{"class":1368},[81,2987,1599],{"class":1598},[81,2989,2913],{"class":1528},[81,2991,1616],{"class":1615},[81,2993,2994],{"class":1624},"selector",[81,2996,1724],{"class":628},[81,2998,2999],{"class":628}," {}",[81,3001,1730],{"class":1615},[81,3003,3004],{"class":83,"line":415},[81,3005,366],{"emptyLinePlaceholder":365},[81,3007,3008,3011,3013,3016,3018,3021,3024,3026,3028,3031,3033],{"class":83,"line":1438},[81,3009,3010],{"class":1605},"  nuxtApp",[81,3012,1609],{"class":628},[81,3014,3015],{"class":1528},"hook",[81,3017,1616],{"class":1615},[81,3019,3020],{"class":128},"'",[81,3022,3023],{"class":90},"page:finish",[81,3025,3020],{"class":128},[81,3027,1724],{"class":628},[81,3029,3030],{"class":628}," ()",[81,3032,2956],{"class":1364},[81,3034,1372],{"class":628},[81,3036,3037,3040,3042,3045,3047,3049,3051,3053,3056,3058,3060],{"class":83,"line":1455},[81,3038,3039],{"class":1605},"    zoom",[81,3041,1609],{"class":628},[81,3043,3044],{"class":1528},"detach",[81,3046,1616],{"class":1615},[81,3048,2994],{"class":1624},[81,3050,1573],{"class":1615},[81,3052,1609],{"class":628},[81,3054,3055],{"class":1528},"attach",[81,3057,1616],{"class":1615},[81,3059,2994],{"class":1624},[81,3061,1730],{"class":1615},[81,3063,3064,3067],{"class":83,"line":1467},[81,3065,3066],{"class":628},"  }",[81,3068,1730],{"class":1615},[81,3070,3071],{"class":83,"line":1483},[81,3072,366],{"emptyLinePlaceholder":365},[81,3074,3075,3077,3079,3082,3084,3086,3089,3091,3093,3095],{"class":83,"line":1497},[81,3076,3010],{"class":1605},[81,3078,1609],{"class":628},[81,3080,3081],{"class":1528},"provide",[81,3083,1616],{"class":1615},[81,3085,3020],{"class":128},[81,3087,3088],{"class":90},"mediumZoom",[81,3090,3020],{"class":128},[81,3092,1724],{"class":628},[81,3094,2981],{"class":1624},[81,3096,1730],{"class":1615},[81,3098,3099,3101],{"class":83,"line":1509},[81,3100,2191],{"class":628},[81,3102,1730],{"class":1449},[580,3104,3106],{"id":3105},"_4-วิธีการใช้งาน","4. วิธีการใช้งาน",[14,3108,3109],{},[39,3110,3111,3112],{},"ใช้กับ ",[18,3113,3114],{},"\u003Cimg />",[23,3116,3118],{"className":2880,"code":3117,"language":2882,"meta":31,"style":31},"\u003Cimg src=\"/images/logo.jpeg\" alt=\"logo\" class=\"image-zoomable\" />\n\n# เพิ่ม \"image-zoomable\" เข้าไปใน class ได้เลย\n",[18,3119,3120,3166,3170],{"__ignoreMap":31},[81,3121,3122,3124,3126,3129,3132,3134,3137,3139,3142,3144,3146,3149,3151,3154,3156,3158,3161,3163],{"class":83,"line":84},[81,3123,1427],{"class":1598},[81,3125,979],{"class":1624},[81,3127,3128],{"class":1624}," src",[81,3130,3131],{"class":1598},"=",[81,3133,1255],{"class":128},[81,3135,3136],{"class":90},"/images/logo.jpeg",[81,3138,1255],{"class":128},[81,3140,3141],{"class":1624}," alt",[81,3143,3131],{"class":1598},[81,3145,1255],{"class":128},[81,3147,3148],{"class":90},"logo",[81,3150,1255],{"class":128},[81,3152,3153],{"class":1624}," class",[81,3155,3131],{"class":1598},[81,3157,1255],{"class":128},[81,3159,3160],{"class":90},"image-zoomable",[81,3162,1255],{"class":128},[81,3164,3165],{"class":1598}," />\n",[81,3167,3168],{"class":83,"line":102},[81,3169,366],{"emptyLinePlaceholder":365},[81,3171,3172,3174,3177,3180,3183,3185,3187,3189,3192,3195,3198,3200,3203,3205],{"class":83,"line":113},[81,3173,2843],{"class":1449},[81,3175,3176],{"class":1624},"เพิ",[81,3178,3179],{"class":1449},"่",[81,3181,3182],{"class":1624},"ม",[81,3184,641],{"class":128},[81,3186,3160],{"class":90},[81,3188,1255],{"class":128},[81,3190,3191],{"class":1624}," เข",[81,3193,3194],{"class":1449},"้",[81,3196,3197],{"class":1624},"าไปใน",[81,3199,3153],{"class":1364},[81,3201,3202],{"class":1368}," ได",[81,3204,3194],{"class":1449},[81,3206,3207],{"class":1368},"เลย\n",[14,3209,3210],{},[39,3211,3111,3212],{},[18,3213,3214],{},"\u003Cnuxt-img />",[23,3216,3218],{"className":2880,"code":3217,"language":2882,"meta":31,"style":31},"\u003Cnuxt-img\n    src=\"/images/logo.jpeg\"\n    alt=\"logo\"\n    class=\"image-zoomable\"\n/>\n\n# ซึ่งเป็นการเติม class เช่นเดียวกันครับ\n",[18,3219,3220,3232,3245,3258,3271,3276,3280],{"__ignoreMap":31},[81,3221,3222,3224,3227,3229],{"class":83,"line":84},[81,3223,1427],{"class":1598},[81,3225,3226],{"class":1624},"nuxt",[81,3228,2835],{"class":1598},[81,3230,3231],{"class":1624},"img\n",[81,3233,3234,3237,3239,3241,3243],{"class":83,"line":102},[81,3235,3236],{"class":1624},"    src",[81,3238,3131],{"class":1598},[81,3240,1255],{"class":128},[81,3242,3136],{"class":90},[81,3244,647],{"class":128},[81,3246,3247,3250,3252,3254,3256],{"class":83,"line":113},[81,3248,3249],{"class":1624},"    alt",[81,3251,3131],{"class":1598},[81,3253,1255],{"class":128},[81,3255,3148],{"class":90},[81,3257,647],{"class":128},[81,3259,3260,3263,3265,3267,3269],{"class":83,"line":247},[81,3261,3262],{"class":1624},"    class",[81,3264,3131],{"class":1598},[81,3266,1255],{"class":128},[81,3268,3160],{"class":90},[81,3270,647],{"class":128},[81,3272,3273],{"class":83,"line":393},[81,3274,3275],{"class":1598},"/>\n",[81,3277,3278],{"class":83,"line":404},[81,3279,366],{"emptyLinePlaceholder":365},[81,3281,3282,3284,3287,3289,3292,3295,3298,3300,3303,3305],{"class":83,"line":415},[81,3283,2843],{"class":1449},[81,3285,3286],{"class":1624},"ซึ",[81,3288,3179],{"class":1449},[81,3290,3291],{"class":1624},"งเป",[81,3293,3294],{"class":1449},"็",[81,3296,3297],{"class":1624},"นการเติม",[81,3299,3153],{"class":1364},[81,3301,3302],{"class":1368}," เช",[81,3304,3179],{"class":1449},[81,3306,3307],{"class":1368},"นเดียวกันครับ\n",[3309,3310],"hr",{},[10,3312,902],{"id":901},[904,3314,3315],{},[907,3316,3317],{},[910,3318,3321],{"href":3319,"rel":3320},"https://www.npmjs.com/package/medium-zoom",[914],"Medium Zoom",[938,3323,3324],{},"html pre.shiki code .sLHrO, html code.shiki .sLHrO{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}html pre.shiki code .sMTEB, html code.shiki .sMTEB{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F97583}html pre.shiki code .sZDfs, html code.shiki .sZDfs{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .s36IK, html code.shiki .s36IK{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#C678DD;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .suMxZ, html code.shiki .suMxZ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .stkEM, html code.shiki .stkEM{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E06C75;--shiki-default-font-style:italic;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sOHk9, html code.shiki .sOHk9{--shiki-light:#9C3EDA;--shiki-default:#C678DD;--shiki-dark:#F97583}html pre.shiki code .sUzmo, html code.shiki .sUzmo{--shiki-light:#90A4AE;--shiki-default:#E5C07B;--shiki-dark:#79B8FF}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .sGnaQ, html code.shiki .sGnaQ{--shiki-light:#E2931D;--shiki-default:#E5C07B;--shiki-dark:#B392F0}html pre.shiki code .sbHgf, html code.shiki .sbHgf{--shiki-light:#E53935;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sU9mH, html code.shiki .sU9mH{--shiki-light:#90A4AE;--shiki-default:#E5C07B;--shiki-dark:#E1E4E8}",{"title":31,"searchDepth":102,"depth":102,"links":3326},[3327,3333],{"id":2812,"depth":102,"text":2812,"children":3328},[3329,3330,3331,3332],{"id":2815,"depth":113,"text":2816},{"id":2863,"depth":113,"text":2864},{"id":2876,"depth":113,"text":2877},{"id":3105,"depth":113,"text":3106},{"id":901,"depth":102,"text":902},"2025-04-25T00:00:00.000Z","วิธีติดตั้ง medium-zoom ใน Nuxt 3 เพื่อให้คลิกรูปแล้วซูมขึ้นมาเหมือนกับเว็บ Medium.com",{},"NuxtJS Medium Zoom","/blogs/2025-04-25-setup-nuxt-medium-zoom",{"title":2799,"description":3335},{"loc":3338},"blogs/2025-04-25-setup-nuxt-medium-zoom",[968],"FykPIEiuh51H4A_JN82_JGiJm72AuP8YQKYJ6RJbaWE",{"id":3345,"title":3346,"body":3347,"date":3521,"description":3522,"draft":956,"extension":957,"meta":3523,"navigation":365,"next":959,"ogTitle":3524,"path":3525,"pin":956,"prev":959,"recommends":3526,"seo":3529,"sitemap":3530,"stem":3531,"tags":3532,"updated":959,"__hash__":3533},"content/blogs/2025-05-10-n8n-with-telegram-notify.md","ส่งการแจ้งเตือนไปยัง Telegram Bot ด้วย N8N",{"type":7,"value":3348,"toc":3516},[3349,3354,3358,3365,3368,3372,3375,3380,3387,3392,3409,3414,3421,3426,3437,3442,3449,3459,3464,3471,3476,3479,3484,3489,3495,3498,3503,3505],[14,3350,3351],{},[979,3352],{"alt":31,"src":3353},"/images/2025-05-10-n8n-with-telegram-notify/images-d022bdb9-7169-4052-baaf-c1da8325bce2.jpg",[10,3355,3357],{"id":3356},"telegram-bot-คืออะไร","Telegram Bot คืออะไร",[14,3359,3360,3361,3364],{},"หลังจากที่ Line Notify ประกาศยุติการให้บริการไป Telegram ก็ขึ้นมาเป็นอันดับต้น ๆ ของการทำ Notification เนื่องด้วยเหตุผลหลัก ๆ คือ ",[39,3362,3363],{},"FREE"," และใช้งานส่งได้ไม่จำกัด แต่ผมเองจริง ๆ ย้ายมาใช้ Telegram ได้หลายปีแล้วครับ ช่วงแรก ๆ ก็ใช้ Line Notify แล้วก็ไปใช้ Discord แทน สุดท้ายมาจบที่ Telegram เพราะว่าต้องการจะใช้ Line เฉพาะคุยเรื่องส่วนตัว (ไม่อยากใช้ Line ในการทำงาน) ส่วน Discord ก็อยากเอาไว้ใช้ในการพูดคุยกับเพื่อน ๆ มากกว่า",[14,3366,3367],{},"ข้อดีของ Telegram Bot หลัก ๆ ในส่วนของการพัฒนาถือว่าง่ายมากครับมีทั้งแบบ Polling และ Webhook ให้ใช้งาน หลัง ๆ มาเลยใช้แต่ Telegram เป็นหลักเลยครับ",[10,3369,3371],{"id":3370},"วิธีการสร้าง-telegram-bot","วิธีการสร้าง Telegram Bot",[14,3373,3374],{},"ทำการ Add telegram ที่ชื่อว่า \"BotFather\" สามารถสแกน QR Code นี้ได้เลยครับผม โดยที่ BotFater จะเป็น Chat Bot ที่เอาไว้ใช้ในการสร้าง/จัดการ Bot ของเราเองนะครับ",[14,3376,3377],{},[979,3378],{"alt":31,"src":3379},"/images/2025-05-10-n8n-with-telegram-notify/images-91fa3609-6307-47b8-9298-70fa23a34007.jpg",[14,3381,3382,3383,3386],{},"จากนั้นใช้ command ",[18,3384,3385],{},"/newbot"," เพื่อเป็นการเริ่มขั้นตอนในการสร้าง Bot ตัวใหม่ขึ้นมา แล้วจะมีการถามชื่อของ Bot ที่เราต้องการจะสร้างขึ้นมานะครับ",[14,3388,3389],{},[979,3390],{"alt":31,"src":3391},"/images/2025-05-10-n8n-with-telegram-notify/images-c191577e-5e9c-40a9-8cbc-c43ee57882b1.jpg",[14,3393,3394,3395,3398,3399,3402,3403,818,3406],{},"เมื่อส่งชื่อไปแล้ว จะมีการถาม ",[18,3396,3397],{},"username"," ที่ต้องการใช้งาน ซึ่งจำเป็นต้องมีคำว่า ",[18,3400,3401],{},"bot"," ต่อท้าย เช่น ",[18,3404,3405],{},"demo_n8n_bot",[18,3407,3408],{},"DemoN8NBot",[14,3410,3411],{},[979,3412],{"alt":31,"src":3413},"/images/2025-05-10-n8n-with-telegram-notify/images-88651bc7-c8c2-4dcf-8fd0-401709b44bce.jpg",[14,3415,3416,3417,3420],{},"จากนั้นจะได้รับ Token สำหรับจัดการ Bot มานะครับ ซึ่งตรงนี้ต้องรักษาเป็นความลับอย่างดี เพราะถ้าหากมีคนได้ Token นี้ไปจะทำให้สามารถเข้าถึงข้อมูลของ Bot ได้ครับ Copy เอา Token เก็บไว้ แล้วกดตรง ",[18,3418,3419],{},"t.me/\u003Cusername>"," เพื่อไปยัง Chat กับ Bot ได้เลย",[14,3422,3423],{},[979,3424],{"alt":31,"src":3425},"/images/2025-05-10-n8n-with-telegram-notify/images-da1134ab-a6b2-4644-bd8a-11ef539abf44.jpg",[14,3427,3428,3429,3432,3433,3436],{},"จากนั้นเปิด browser แล้วไปที่ ",[18,3430,3431],{},"https://api.telegram.org/bot\u003CTOKEN>/getUpdates"," โดยให้แทนที่ ",[18,3434,3435],{},"\u003CTOKEN>"," ด้วย token ที่ copy เอาไว้ได้เลยครับ",[14,3438,3439],{},[979,3440],{"alt":31,"src":3441},"/images/2025-05-10-n8n-with-telegram-notify/images-7f8a4bc4-8437-411f-8222-baa8ee3e25fe.jpg",[14,3443,3444,3445,3448],{},"จากนั้นให้ลองส่ง message อะไรก็ได้ไปยัง telegram bot ของเรา เพื่อค้นหา ",[18,3446,3447],{},"chat id"," เพื่อนำไปใช้งานใน N8N",[14,3450,3451,3452,3454,3455,3458],{},"หลังจากพิมพ์เสร็จแล้วเมื่อ refresh หน้าเว็บดูจะพบกับข้อมูลนะครับ ถ้าขึ้นแบบนี้แสดงว่าถูกต้อง แต่ถ้าไม่ขึ้นอาจจะลองเชค URL ดูว่าใส่ token ลงไปถูกรึเปล่า โดย ",[18,3453,3447],{}," จะอยู่ที่ ",[18,3456,3457],{},"chat: {id}"," ตามที่วงเอาไว้ สามารถ copy เก็บไว้ได้เลยครับ",[14,3460,3461],{},[979,3462],{"alt":31,"src":3463},"/images/2025-05-10-n8n-with-telegram-notify/images-a198f69c-6c16-436f-a836-666e14a49c13.jpg",[14,3465,3466,3467,3470],{},"เข้าไปที่ N8N และเลือก Node ",[18,3468,3469],{},"Send a text message"," ของ telegram",[14,3472,3473],{},[979,3474],{"alt":31,"src":3475},"/images/2025-05-10-n8n-with-telegram-notify/images-80f7301f-dd22-48e9-a4b9-b11166c4e11d.jpg",[14,3477,3478],{},"กดสร้าง Credential ใหม่ และนำ Token จาก Bot Father มาใส่",[14,3480,3481],{},[979,3482],{"alt":31,"src":3483},"/images/2025-05-10-n8n-with-telegram-notify/images-579dd0eb-09a0-455f-94d4-2d401f8635e2.jpg",[14,3485,3486],{},[979,3487],{"alt":31,"src":3488},"/images/2025-05-10-n8n-with-telegram-notify/images-205b9bf5-77c4-450f-be07-9bf7c3537e87.jpg",[14,3490,3491,3492],{},"จากนั้นกรอก Chat ID ที่ copy เก็บเอาไว้และกด \"Test Step\" ได้เลยครับ\n",[979,3493],{"alt":31,"src":3494},"/images/2025-05-10-n8n-with-telegram-notify/images-b3fd3007-4360-4e16-afa7-8bc0d51c48e8.jpg",[14,3496,3497],{},"เพียงเท่านี้ก็จะสามารถส่งการแจ้งเตือนไปยัง Telegram ของเราด้วย Telegram Bot ได้แล้ว",[14,3499,3500],{},[979,3501],{"alt":31,"src":3502},"/images/2025-05-10-n8n-with-telegram-notify/images-c7148489-9d38-4647-a3bf-e7f696f4ddcd.jpg",[10,3504,845],{"id":845},[14,3506,3507,3508,3511,3512,3515],{},"การส่งแจ้งเตือนไปยัง Telegram Bot ต้องการข้อมูล 2 อย่างคือ ",[18,3509,3510],{},"Telegram Token"," และ ",[18,3513,3514],{},"Chat ID"," ซึ่งเป็น Chat ของผู้ใช้งานที่ต้องการส่งข้อความไป โดยวิธีนี้ไม่จำเป็นต้องตั้งค่า Webhook ของ Telegram นะครับ ง่าย ๆ และใช้งานได้จริง ย้ายมาใช้ Telegram กันเยอะ ๆ นะครับ ของดีจริง",{"title":31,"searchDepth":102,"depth":102,"links":3517},[3518,3519,3520],{"id":3356,"depth":102,"text":3357},{"id":3370,"depth":102,"text":3371},{"id":845,"depth":102,"text":845},"2025-05-10T00:00:00.000Z","แนะนำวิธีการเชื่อมต่อ Telegram Bot และ N8N เพื่อส่งการแจ้งเตือนไปยัง Telegram สำหรับมือใหม่",{},"Telegram Bot With N8N","/blogs/2025-05-10-n8n-with-telegram-notify",[3527,3528],"2025-04-24-n8n-part-1.md","2025-04-28-n8n-part-2.md",{"title":3346,"description":3522},{"loc":3525},"blogs/2025-05-10-n8n-with-telegram-notify",[968],"jF_x7ieOtg5MjCZXInwfS-O0_8W7_uNCLvKBjX18-xI",{"id":3535,"title":3536,"body":3537,"date":4459,"description":4460,"draft":956,"extension":957,"meta":4461,"navigation":365,"next":959,"ogTitle":4462,"path":4463,"pin":956,"prev":959,"recommends":959,"seo":4464,"sitemap":4465,"stem":4466,"tags":4467,"updated":959,"__hash__":4468},"content/blogs/2025-05-23-nuxt-tailwindcss-nuxtui-setup.md","Cheat Sheet Nuxt x Tailwindcss x NuxtUI",{"type":7,"value":3538,"toc":4453},[3539,3543,3550,3575,3946,3950,4057,4061,4067,4084,4087,4104,4114,4148,4151,4201,4205,4211,4264,4270,4337,4344,4450],[10,3540,3542],{"id":3541},"init-nuxtjs","Init NuxtJS",[14,3544,3545,3546],{},"Docs: ",[910,3547,3548],{"href":3548,"rel":3549},"https://nuxt.com/docs/getting-started/installation",[914],[23,3551,3553],{"className":75,"code":3552,"language":77,"meta":31,"style":31},"npm create nuxt \u003Cproject-name>\n",[18,3554,3555],{"__ignoreMap":31},[81,3556,3557,3559,3561,3564,3566,3569,3572],{"class":83,"line":84},[81,3558,2826],{"class":87},[81,3560,357],{"class":90},[81,3562,3563],{"class":90}," nuxt",[81,3565,1969],{"class":618},[81,3567,3568],{"class":90},"project-nam",[81,3570,3571],{"class":1449},"e",[81,3573,3574],{"class":618},">\n",[23,3576,3578],{"className":75,"code":3577,"language":77,"meta":31,"style":31},"✔ Which package manager would you like to use?\nnpm\n\n✔ Initialize git repository?\nYes\nℹ Initializing git repository...\n\n❯ Would you like to install any of the official modules?\n◼ @nuxt/content – The file-based CMS with support for Markdown, YAML, JSON\n◼ @nuxt/eslint – Project-aware, easy-to-use, extensible and future-proof ESLint integration\n◼ @nuxt/fonts – Add custom web fonts with performance in mind\n◼ @nuxt/icon – Icon module for Nuxt with 200,000+ ready to use icons from Iconify\n◼ @nuxt/image – Add images with progressive processing, lazy-loading, resizing and providers support\n◼ @nuxt/scripts – Add 3rd-party scripts without sacrificing performance\n◻ @nuxt/test-utils – Test utilities for Nuxt\n◼ @nuxt/ui – The Intuitive UI Library powered by Reka UI and Tailwind CSS\n",[18,3579,3580,3609,3614,3618,3631,3636,3649,3653,3684,3722,3752,3784,3825,3860,3886,3907],{"__ignoreMap":31},[81,3581,3582,3585,3588,3591,3594,3597,3600,3603,3606],{"class":83,"line":84},[81,3583,3584],{"class":87},"✔",[81,3586,3587],{"class":90}," Which",[81,3589,3590],{"class":90}," package",[81,3592,3593],{"class":90}," manager",[81,3595,3596],{"class":90}," would",[81,3598,3599],{"class":90}," you",[81,3601,3602],{"class":90}," like",[81,3604,3605],{"class":90}," to",[81,3607,3608],{"class":90}," use?\n",[81,3610,3611],{"class":83,"line":102},[81,3612,3613],{"class":87},"npm\n",[81,3615,3616],{"class":83,"line":113},[81,3617,366],{"emptyLinePlaceholder":365},[81,3619,3620,3622,3625,3628],{"class":83,"line":247},[81,3621,3584],{"class":87},[81,3623,3624],{"class":90}," Initialize",[81,3626,3627],{"class":90}," git",[81,3629,3630],{"class":90}," repository?\n",[81,3632,3633],{"class":83,"line":393},[81,3634,3635],{"class":87},"Yes\n",[81,3637,3638,3641,3644,3646],{"class":83,"line":404},[81,3639,3640],{"class":87},"ℹ",[81,3642,3643],{"class":90}," Initializing",[81,3645,3627],{"class":90},[81,3647,3648],{"class":90}," repository...\n",[81,3650,3651],{"class":83,"line":415},[81,3652,366],{"emptyLinePlaceholder":365},[81,3654,3655,3658,3661,3663,3665,3667,3669,3672,3675,3678,3681],{"class":83,"line":1438},[81,3656,3657],{"class":87},"❯",[81,3659,3660],{"class":90}," Would",[81,3662,3599],{"class":90},[81,3664,3602],{"class":90},[81,3666,3605],{"class":90},[81,3668,2829],{"class":90},[81,3670,3671],{"class":90}," any",[81,3673,3674],{"class":90}," of",[81,3676,3677],{"class":90}," the",[81,3679,3680],{"class":90}," official",[81,3682,3683],{"class":90}," modules?\n",[81,3685,3686,3689,3692,3695,3698,3701,3704,3707,3710,3713,3716,3719],{"class":83,"line":1455},[81,3687,3688],{"class":87},"◼",[81,3690,3691],{"class":90}," @nuxt/content",[81,3693,3694],{"class":90}," –",[81,3696,3697],{"class":90}," The",[81,3699,3700],{"class":90}," file-based",[81,3702,3703],{"class":90}," CMS",[81,3705,3706],{"class":90}," with",[81,3708,3709],{"class":90}," support",[81,3711,3712],{"class":90}," for",[81,3714,3715],{"class":90}," Markdown,",[81,3717,3718],{"class":90}," YAML,",[81,3720,3721],{"class":90}," JSON\n",[81,3723,3724,3726,3729,3731,3734,3737,3740,3743,3746,3749],{"class":83,"line":1467},[81,3725,3688],{"class":87},[81,3727,3728],{"class":90}," @nuxt/eslint",[81,3730,3694],{"class":90},[81,3732,3733],{"class":90}," Project-aware,",[81,3735,3736],{"class":90}," easy-to-use,",[81,3738,3739],{"class":90}," extensible",[81,3741,3742],{"class":90}," and",[81,3744,3745],{"class":90}," future-proof",[81,3747,3748],{"class":90}," ESLint",[81,3750,3751],{"class":90}," integration\n",[81,3753,3754,3756,3759,3761,3764,3767,3770,3773,3775,3778,3781],{"class":83,"line":1483},[81,3755,3688],{"class":87},[81,3757,3758],{"class":90}," @nuxt/fonts",[81,3760,3694],{"class":90},[81,3762,3763],{"class":90}," Add",[81,3765,3766],{"class":90}," custom",[81,3768,3769],{"class":90}," web",[81,3771,3772],{"class":90}," fonts",[81,3774,3706],{"class":90},[81,3776,3777],{"class":90}," performance",[81,3779,3780],{"class":90}," in",[81,3782,3783],{"class":90}," mind\n",[81,3785,3786,3788,3791,3793,3796,3799,3801,3804,3806,3809,3812,3814,3817,3820,3822],{"class":83,"line":1497},[81,3787,3688],{"class":87},[81,3789,3790],{"class":90}," @nuxt/icon",[81,3792,3694],{"class":90},[81,3794,3795],{"class":90}," Icon",[81,3797,3798],{"class":90}," module",[81,3800,3712],{"class":90},[81,3802,3803],{"class":90}," Nuxt",[81,3805,3706],{"class":90},[81,3807,3808],{"class":90}," 200,000+",[81,3810,3811],{"class":90}," ready",[81,3813,3605],{"class":90},[81,3815,3816],{"class":90}," use",[81,3818,3819],{"class":90}," icons",[81,3821,2899],{"class":90},[81,3823,3824],{"class":90}," Iconify\n",[81,3826,3827,3829,3832,3834,3836,3838,3840,3843,3846,3849,3852,3854,3857],{"class":83,"line":1509},[81,3828,3688],{"class":87},[81,3830,3831],{"class":90}," @nuxt/image",[81,3833,3694],{"class":90},[81,3835,3763],{"class":90},[81,3837,566],{"class":90},[81,3839,3706],{"class":90},[81,3841,3842],{"class":90}," progressive",[81,3844,3845],{"class":90}," processing,",[81,3847,3848],{"class":90}," lazy-loading,",[81,3850,3851],{"class":90}," resizing",[81,3853,3742],{"class":90},[81,3855,3856],{"class":90}," providers",[81,3858,3859],{"class":90}," support\n",[81,3861,3862,3864,3867,3869,3871,3874,3877,3880,3883],{"class":83,"line":1514},[81,3863,3688],{"class":87},[81,3865,3866],{"class":90}," @nuxt/scripts",[81,3868,3694],{"class":90},[81,3870,3763],{"class":90},[81,3872,3873],{"class":90}," 3rd-party",[81,3875,3876],{"class":90}," scripts",[81,3878,3879],{"class":90}," without",[81,3881,3882],{"class":90}," sacrificing",[81,3884,3885],{"class":90}," performance\n",[81,3887,3888,3891,3894,3896,3899,3902,3904],{"class":83,"line":1519},[81,3889,3890],{"class":87},"◻",[81,3892,3893],{"class":90}," @nuxt/test-utils",[81,3895,3694],{"class":90},[81,3897,3898],{"class":90}," Test",[81,3900,3901],{"class":90}," utilities",[81,3903,3712],{"class":90},[81,3905,3906],{"class":90}," Nuxt\n",[81,3908,3909,3911,3914,3916,3918,3921,3924,3927,3930,3933,3936,3938,3940,3943],{"class":83,"line":1535},[81,3910,3688],{"class":87},[81,3912,3913],{"class":90}," @nuxt/ui",[81,3915,3694],{"class":90},[81,3917,3697],{"class":90},[81,3919,3920],{"class":90}," Intuitive",[81,3922,3923],{"class":90}," UI",[81,3925,3926],{"class":90}," Library",[81,3928,3929],{"class":90}," powered",[81,3931,3932],{"class":90}," by",[81,3934,3935],{"class":90}," Reka",[81,3937,3923],{"class":90},[81,3939,3742],{"class":90},[81,3941,3942],{"class":90}," Tailwind",[81,3944,3945],{"class":90}," CSS\n",[10,3947,3949],{"id":3948},"preventing-yarn-allow-only-npm","Preventing Yarn (Allow only NPM)",[23,3951,3953],{"className":75,"code":3952,"language":77,"meta":31,"style":31},"\"engines\": {\n    \"yarn\": \"Please use NPM!\",\n    \"node\": \"22.x\",\n    \"npm\": \">=6\"\n},\n\n# shell\necho \"22\" > .nvmrc\necho \"engine-strict=true\" > .npmrc\n",[18,3954,3955,3964,3980,3996,4010,4015,4019,4024,4041],{"__ignoreMap":31},[81,3956,3957,3960,3962],{"class":83,"line":84},[81,3958,3959],{"class":87},"\"engines\"",[81,3961,1258],{"class":637},[81,3963,1372],{"class":90},[81,3965,3966,3969,3971,3973,3976,3978],{"class":83,"line":102},[81,3967,3968],{"class":87},"    \"yarn\"",[81,3970,1258],{"class":637},[81,3972,641],{"class":128},[81,3974,3975],{"class":90},"Please use NPM!",[81,3977,1255],{"class":128},[81,3979,1282],{"class":90},[81,3981,3982,3985,3987,3989,3992,3994],{"class":83,"line":113},[81,3983,3984],{"class":87},"    \"node\"",[81,3986,1258],{"class":637},[81,3988,641],{"class":128},[81,3990,3991],{"class":90},"22.x",[81,3993,1255],{"class":128},[81,3995,1282],{"class":90},[81,3997,3998,4001,4003,4005,4008],{"class":83,"line":247},[81,3999,4000],{"class":87},"    \"npm\"",[81,4002,1258],{"class":637},[81,4004,641],{"class":128},[81,4006,4007],{"class":90},">=6",[81,4009,647],{"class":128},[81,4011,4012],{"class":83,"line":393},[81,4013,4014],{"class":1449},"},\n",[81,4016,4017],{"class":83,"line":404},[81,4018,366],{"emptyLinePlaceholder":365},[81,4020,4021],{"class":83,"line":415},[81,4022,4023],{"class":1064},"# shell\n",[81,4025,4026,4029,4031,4034,4036,4038],{"class":83,"line":1438},[81,4027,4028],{"class":637},"echo",[81,4030,641],{"class":128},[81,4032,4033],{"class":90},"22",[81,4035,1255],{"class":128},[81,4037,619],{"class":618},[81,4039,4040],{"class":90}," .nvmrc\n",[81,4042,4043,4045,4047,4050,4052,4054],{"class":83,"line":1455},[81,4044,4028],{"class":637},[81,4046,641],{"class":128},[81,4048,4049],{"class":90},"engine-strict=true",[81,4051,1255],{"class":128},[81,4053,619],{"class":618},[81,4055,4056],{"class":90}," .npmrc\n",[10,4058,4060],{"id":4059},"install-tailwindcss-version-4","Install Tailwindcss Version 4",[14,4062,3545,4063],{},[910,4064,4065],{"href":4065,"rel":4066},"https://tailwindcss.com/docs/installation/framework-guides/nuxt",[914],[23,4068,4070],{"className":75,"code":4069,"language":77,"meta":31,"style":31},"npm install tailwindcss @tailwindcss/vite\n",[18,4071,4072],{"__ignoreMap":31},[81,4073,4074,4076,4078,4081],{"class":83,"line":84},[81,4075,2826],{"class":87},[81,4077,2829],{"class":90},[81,4079,4080],{"class":90}," tailwindcss",[81,4082,4083],{"class":90}," @tailwindcss/vite\n",[14,4085,4086],{},"Edit",[23,4088,4092],{"className":4089,"code":4090,"language":4091,"meta":31,"style":31},"language-vue shiki shiki-themes material-theme-lighter one-dark-pro github-dark","import tailwindcss from \"@tailwindcss/vite\"; export default defineNuxtConfig({\n... vite: { plugins: [ tailwindcss(), ], }, });\n","vue",[18,4093,4094,4099],{"__ignoreMap":31},[81,4095,4096],{"class":83,"line":84},[81,4097,4098],{"class":1449},"import tailwindcss from \"@tailwindcss/vite\"; export default defineNuxtConfig({\n",[81,4100,4101],{"class":83,"line":102},[81,4102,4103],{"class":1449},"... vite: { plugins: [ tailwindcss(), ], }, });\n",[14,4105,4106,4107,4110,4111],{},"Create a tailwindcss config file ",[18,4108,4109],{},"mkdir -p assets/css"," ",[18,4112,4113],{},"touch assets/css/main.css",[23,4115,4119],{"className":4116,"code":4117,"language":4118,"meta":31,"style":31},"language-css shiki shiki-themes material-theme-lighter one-dark-pro github-dark","@import 'tailwindcss';\n@import '@nuxt/ui';\n","css",[18,4120,4121,4135],{"__ignoreMap":31},[81,4122,4123,4126,4128,4131,4133],{"class":83,"line":84},[81,4124,4125],{"class":604},"@import",[81,4127,129],{"class":128},[81,4129,4130],{"class":90},"tailwindcss",[81,4132,3020],{"class":128},[81,4134,1387],{"class":628},[81,4136,4137,4139,4141,4144,4146],{"class":83,"line":102},[81,4138,4125],{"class":604},[81,4140,129],{"class":128},[81,4142,4143],{"class":90},"@nuxt/ui",[81,4145,3020],{"class":128},[81,4147,1387],{"class":628},[14,4149,4150],{},"edit nuxt config file",[23,4152,4154],{"className":2880,"code":4153,"language":2882,"meta":31,"style":31},"export default defineNuxtConfig({\n    ...\n    css: ['~/assets/css/main.css'],\n});\n",[18,4155,4156,4169,4174,4193],{"__ignoreMap":31},[81,4157,4158,4160,4162,4165,4167],{"class":83,"line":84},[81,4159,1522],{"class":604},[81,4161,2942],{"class":604},[81,4163,4164],{"class":1528}," defineNuxtConfig",[81,4166,1616],{"class":1449},[81,4168,1242],{"class":628},[81,4170,4171],{"class":83,"line":102},[81,4172,4173],{"class":618},"    ...\n",[81,4175,4176,4179,4182,4184,4187,4189,4191],{"class":83,"line":113},[81,4177,4178],{"class":1624},"    css",[81,4180,4181],{"class":1449},": [",[81,4183,3020],{"class":128},[81,4185,4186],{"class":90},"~/assets/css/main.css",[81,4188,3020],{"class":128},[81,4190,2246],{"class":1449},[81,4192,1282],{"class":628},[81,4194,4195,4197,4199],{"class":83,"line":247},[81,4196,2191],{"class":628},[81,4198,1573],{"class":1449},[81,4200,1387],{"class":628},[10,4202,4204],{"id":4203},"nuxt-layout","Nuxt Layout",[14,4206,3545,4207],{},[910,4208,4209],{"href":4209,"rel":4210},"https://nuxt.com/docs/guide/directory-structure/layouts",[914],[23,4212,4214],{"className":4089,"code":4213,"language":4091,"meta":31,"style":31},"\u003Ctemplate>\n  \u003CNuxtLayout>\n    \u003CNuxtPage />\n  \u003C/NuxtLayout>\n\u003C/template>\n",[18,4215,4216,4226,4236,4246,4255],{"__ignoreMap":31},[81,4217,4218,4220,4224],{"class":83,"line":84},[81,4219,1427],{"class":628},[81,4221,4223],{"class":4222},"s0Prt","template",[81,4225,3574],{"class":628},[81,4227,4228,4231,4234],{"class":83,"line":102},[81,4229,4230],{"class":628},"  \u003C",[81,4232,4233],{"class":4222},"NuxtLayout",[81,4235,3574],{"class":628},[81,4237,4238,4241,4244],{"class":83,"line":113},[81,4239,4240],{"class":628},"    \u003C",[81,4242,4243],{"class":4222},"NuxtPage",[81,4245,3165],{"class":628},[81,4247,4248,4251,4253],{"class":83,"line":247},[81,4249,4250],{"class":628},"  \u003C/",[81,4252,4233],{"class":4222},[81,4254,3574],{"class":628},[81,4256,4257,4260,4262],{"class":83,"line":393},[81,4258,4259],{"class":628},"\u003C/",[81,4261,4223],{"class":4222},[81,4263,3574],{"class":628},[14,4265,4266,4267],{},"Create a new file ",[18,4268,4269],{},"layouts/default.vue",[23,4271,4273],{"className":4089,"code":4272,"language":4091,"meta":31,"style":31},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cp>Some default layout content shared across all pages\u003C/p>\n    \u003Cslot />\n  \u003C/div>\n\u003C/template>\n",[18,4274,4275,4283,4292,4309,4321,4329],{"__ignoreMap":31},[81,4276,4277,4279,4281],{"class":83,"line":84},[81,4278,1427],{"class":628},[81,4280,4223],{"class":4222},[81,4282,3574],{"class":628},[81,4284,4285,4287,4290],{"class":83,"line":102},[81,4286,4230],{"class":628},[81,4288,4289],{"class":4222},"div",[81,4291,3574],{"class":628},[81,4293,4294,4296,4298,4300,4303,4305,4307],{"class":83,"line":113},[81,4295,4240],{"class":628},[81,4297,14],{"class":4222},[81,4299,1433],{"class":628},[81,4301,4302],{"class":1449},"Some default layout content shared across all pages",[81,4304,4259],{"class":628},[81,4306,14],{"class":4222},[81,4308,3574],{"class":628},[81,4310,4311,4313,4316,4319],{"class":83,"line":247},[81,4312,4240],{"class":628},[81,4314,4315],{"class":4222},"slot",[81,4317,1944],{"class":4318},"sRwpp",[81,4320,3574],{"class":628},[81,4322,4323,4325,4327],{"class":83,"line":393},[81,4324,4250],{"class":628},[81,4326,4289],{"class":4222},[81,4328,3574],{"class":628},[81,4330,4331,4333,4335],{"class":83,"line":404},[81,4332,4259],{"class":628},[81,4334,4223],{"class":4222},[81,4336,3574],{"class":628},[14,4338,4339,4340,4343],{},"Create a ",[18,4341,4342],{},"pages"," folder",[23,4345,4347],{"className":4089,"code":4346,"language":4091,"meta":31,"style":31},"\u003Ctemplate>\n  \u003Cdiv>Login Page\u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'no-auth',\n})\n\u003C/script>\n",[18,4348,4349,4357,4374,4382,4386,4411,4420,4436,4442],{"__ignoreMap":31},[81,4350,4351,4353,4355],{"class":83,"line":84},[81,4352,1427],{"class":628},[81,4354,4223],{"class":4222},[81,4356,3574],{"class":628},[81,4358,4359,4361,4363,4365,4368,4370,4372],{"class":83,"line":102},[81,4360,4230],{"class":628},[81,4362,4289],{"class":4222},[81,4364,1433],{"class":628},[81,4366,4367],{"class":1449},"Login Page",[81,4369,4259],{"class":628},[81,4371,4289],{"class":4222},[81,4373,3574],{"class":628},[81,4375,4376,4378,4380],{"class":83,"line":113},[81,4377,4259],{"class":628},[81,4379,4223],{"class":4222},[81,4381,3574],{"class":628},[81,4383,4384],{"class":83,"line":247},[81,4385,366],{"emptyLinePlaceholder":365},[81,4387,4388,4390,4393,4397,4400,4402,4404,4407,4409],{"class":83,"line":393},[81,4389,1427],{"class":628},[81,4391,4392],{"class":4222},"script",[81,4394,4396],{"class":4395},"s4x1N"," setup",[81,4398,4399],{"class":4395}," lang",[81,4401,3131],{"class":628},[81,4403,1255],{"class":128},[81,4405,4406],{"class":90},"ts",[81,4408,1255],{"class":128},[81,4410,3574],{"class":628},[81,4412,4413,4416,4418],{"class":83,"line":404},[81,4414,4415],{"class":1528},"definePageMeta",[81,4417,1616],{"class":1449},[81,4419,1242],{"class":628},[81,4421,4422,4425,4427,4429,4432,4434],{"class":83,"line":415},[81,4423,4424],{"class":1784},"  layout",[81,4426,1258],{"class":628},[81,4428,129],{"class":128},[81,4430,4431],{"class":90},"no-auth",[81,4433,3020],{"class":128},[81,4435,1282],{"class":628},[81,4437,4438,4440],{"class":83,"line":1438},[81,4439,2191],{"class":628},[81,4441,1730],{"class":1449},[81,4443,4444,4446,4448],{"class":83,"line":1455},[81,4445,4259],{"class":628},[81,4447,4392],{"class":4222},[81,4449,3574],{"class":628},[938,4451,4452],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .sZDfs, html code.shiki .sZDfs{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJZfL, html code.shiki .sJZfL{--shiki-light:#6182B8;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .s36IK, html code.shiki .s36IK{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#C678DD;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .suMxZ, html code.shiki .suMxZ{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sLHrO, html code.shiki .sLHrO{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}html pre.shiki code .s0Prt, html code.shiki .s0Prt{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#85E89D}html pre.shiki code .sRwpp, html code.shiki .sRwpp{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#FFFFFF;--shiki-default-font-style:inherit;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .s4x1N, html code.shiki .s4x1N{--shiki-light:#9C3EDA;--shiki-default:#D19A66;--shiki-dark:#B392F0}html pre.shiki code .sTQd7, html code.shiki .sTQd7{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}",{"title":31,"searchDepth":102,"depth":102,"links":4454},[4455,4456,4457,4458],{"id":3541,"depth":102,"text":3542},{"id":3948,"depth":102,"text":3949},{"id":4059,"depth":102,"text":4060},{"id":4203,"depth":102,"text":4204},"2025-05-23T00:00:00.000Z","Cheat sheet คำสั่งเริ่มโปรเจกต์ Nuxt พร้อม Tailwind CSS และ Nuxt UI ตั้งแต่ init จนตั้งค่าเสร็จ",{},"NuxtJS with Tailwindcss","/blogs/2025-05-23-nuxt-tailwindcss-nuxtui-setup",{"title":3536,"description":4460},{"loc":4463},"blogs/2025-05-23-nuxt-tailwindcss-nuxtui-setup",[968],"ic8kCE0ASnABkbEFd9HEMuUBgrQofT64wgDB-pRaDaY",{"id":4470,"title":4471,"body":4472,"date":5845,"description":5846,"draft":956,"extension":957,"meta":5847,"navigation":365,"next":959,"ogTitle":5848,"path":5849,"pin":956,"prev":959,"recommends":5850,"seo":5851,"sitemap":5852,"stem":5853,"tags":5854,"updated":959,"__hash__":5855},"content/blogs/2025-08-30-pagecms-opensource.md","จัดการ Content ด้วย pagecms opensource สุดคูล",{"type":7,"value":4473,"toc":5838},[4474,4478,4493,4502,4511,4520,4524,4527,4535,4542,4547,4551,4565,4570,4573,4576,4583,4586,4589,4593,4596,4617,4642,4660,4670,4680,4688,4691,5835],[10,4475,4477],{"id":4476},"introduction","Introduction",[14,4479,4480,4481,4486,4487,4492],{},"เว็บไซต์นี้พัฒนาโดยใช้ ",[910,4482,4485],{"href":4483,"rel":4484},"https://nuxt.com/",[914],"Nuxt"," ร่วมกับ ",[910,4488,4491],{"href":4489,"rel":4490},"https://content.nuxt.com/",[914],"NuxtContent"," ซึ่งการจะเป็นการนำเอา Content ในรูปแบบของ Markdown มาสร้างเป็น Web Application โดยเป็นไปตามที่มีการตั้งค่าเอาไว้ ซึ่งวิธีนี้จะทำให้สามารถ Custom ตัวเว็บไซต์ได้เกือบ 100% เลย",[14,4494,4495,4496,4501],{},"ผมเก็บ source code ของ website ไว้ที่ github และ deploy ไปยัง ",[910,4497,4500],{"href":4498,"rel":4499},"https://pages.cloudflare.com/",[914],"cloudflare page"," โดยใช้ CICD ของ cloudflare page ครับ ซึ่งวิธีการนี้ทำให้มีค่าใช้จ่ายของเว็บไซต์นี้เกือบจะเป็น 0 บาทต่อเดือนเลยครับ เพราะทุกอย่างใช้จบได้ภายใน Free Tier",[14,4503,4504,4505,4510],{},"แต่ความยากอย่างนึงที่ผมเจอก็คือการที่ต้องจัดการ content ซึ่งอยู่ในรูปแบบของ markdown ช่วงแรก ๆ ก็จะใช้งานผ่าน vscode เอาเลย แต่ช่วงหลังก็เปลี่ยนมาใช้ ",[910,4506,4509],{"href":4507,"rel":4508},"https://obsidian.md/",[914],"obsidian"," โดยปรับแต่งให้สามารถจัดการ content ได้ แต่ก็ยังรู้สึกไม่ค่อยสะดวกเท่าไหร่",[14,4512,4513,4514,4519],{},"จนผมมาเจอเครื่องมือ opensource ตัวนึงชื่อว่า ",[910,4515,4518],{"href":4516,"rel":4517},"https://pagescms.org",[914],"pagecms"," ซึ่งสามารถแก้ไขปัญหาตรงนี้ให้ผมได้และสะดวกมากขึ้นเยอะเลยครับ",[10,4521,4523],{"id":4522},"what-is-pagecms","What is pagecms?",[14,4525,4526],{},"pagecms คือ Content Management System สำหรับ Website ที่เป็นประเภท Static Site เช่น Jekyll, Next.js, VuePress, Hugo หรือประเภทใดก็ตามที่มีการเก็บ content ในลักษณะของ File Markdown",[14,4528,4529,4530,4534],{},"ซึ่งตัวเครื่องมือนี้สามารถใช้งานได้แบบง่าย ๆ โดยการเข้าใช้งานที่ ",[910,4531,4532],{"href":4532,"rel":4533},"https://app.pagescms.org/",[914]," จากนั้นทำการเชื่อมต่อกับ Github และเลือก Repo ที่ต้องการได้เลย",[14,4536,4537,4538],{},"สามารถกำหนดการตั้งค่าต่าง ๆ ได้ผ่านตัวเว็บไซต์เลยครับ กำหนด config ให้ตรงกับ project structure โดยดูเพิ่มเติมได้จาก ",[910,4539,4540],{"href":4540,"rel":4541},"https://pagescms.org/docs/configuration/",[914],[14,4543,4544],{},[979,4545],{"alt":31,"src":4546},"/images/2025-08-30-pagecms-opensource/page-cms-1.png",[10,4548,4550],{"id":4549},"สิ่งที่-pagecms-สามารถทำได้","สิ่งที่ pagecms สามารถทำได้",[904,4552,4553,4556,4559,4562],{},[907,4554,4555],{},"เพิ่ม/ลบ/แก้ไข content ของ website ได้ โดยจะเป็นการ commit เพื่อแก้ไข repo บน github โดยตรง",[907,4557,4558],{},"สามารถเพิ่ม/ลบ จัดการ media ภายใน website หรือ repo ได้",[907,4560,4561],{},"สลับ branch ในการจัดการได้",[907,4563,4564],{},"สามารถ invite คนอื่นเข้ามาร่วมแก้ไขได้ด้วย โดยอันนี้จะเป็นการ invite เข้า repo บน github เลยนะครับ",[14,4566,4567],{},[979,4568],{"alt":31,"src":4569},"/images/2025-08-30-pagecms-opensource/page-cms-2.png",[14,4571,4572],{},"ซึ่งทั้งหมดจะทำงานบน github อีกทีนึง ตัว pagecms จะเป็นแค่หน้ากากทำให้ใช้งานได้ง่ายยิ่งขึ้นครับ",[10,4574,4575],{"id":4575},"คำแนะนำ",[14,4577,4578,4579],{},"จากที่ลองใช้งานมารู้สึกโอเคนะครับ แต่ตอนนี้ project กำลังอยู่ในช่วงพัฒนาคิดว่าจะมีฟีเจอร์มาให้ใช้งานอีกเพียบ สามารถติดตามได้ที่ ",[910,4580,4581],{"href":4581,"rel":4582},"https://github.com/hunvreus/pagescms",[914],[14,4584,4585],{},"แนะนำให้สร้าง branch แยกกับ branch ที่มีการเปิด CICD เอาไว้นะครับ เพราะว่าทุกครั้งที่มีการ Upload File ในช่อง \"Media\" มันจะเป็นการสร้าง commit ใหม่ ซึ่งจะ trigger CICD เลยครับ แนะนำว่าให้สร้าง branch ใหม่แล้วถ้าต้องการ update ค่อย merge เข้าไปใน branch หลักอีกที",[14,4587,4588],{},"สำหรับผมมองว่าเป็นแค่ Add-on ที่เพิ่มเข้ามา ถ้าวันนึงมันไม่ work แล้วก็สามารถนำออกไปได้เลยครับ",[10,4590,4592],{"id":4591},"อัปเดต-2026","อัปเดต 2026",[14,4594,4595],{},"โพสต์นี้เขียนไว้ตั้งแต่ปี 2025 พอกลับมารื้อ config ใหม่อีกรอบ ก็เจอว่า Pages CMS ขยับไปไกลกว่าที่คิด ของบางอย่างที่เคยเขียนไว้ข้างบนใช้ไม่ได้แล้วครับ",[14,4597,4598,4604,4605,4608,4609,4612,4613,4616],{},[39,4599,4600,4603],{},[18,4601,4602],{},"creatable"," ใน select หายไปแล้ว"," เดิมผมใส่ ",[18,4606,4607],{},"creatable: true"," ไว้ที่ field ",[18,4610,4611],{},"tags"," เพื่อจะได้พิมพ์แท็กใหม่จากในหน้าเว็บได้เลย ตอนนี้ไม่มีโค้ดส่วนไหนอ่านคีย์นี้แล้ว select เหลือแค่ค่าที่ประกาศไว้ล่วงหน้าอย่างเดียว อยากเพิ่มแท็กใหม่ก็ต้องมาแก้ที่ ",[18,4614,4615],{},"values"," เอง",[14,4618,4619,4626,4627,4630,4631,4630,4634,4637,4638,4641],{},[39,4620,4621,4622,4625],{},"มี field ชนิด ",[18,4623,4624],{},"reference"," มาแทน"," ชี้ไปที่ collection อื่นหรือ collection ตัวเองก็ได้ แล้วเลือกเอาจาก dropdown ผมย้าย ",[18,4628,4629],{},"prev"," / ",[18,4632,4633],{},"next",[18,4635,4636],{},"recommends"," จาก string มาเป็นอันนี้หมด ไม่ต้องพิมพ์ slug เองอีก จุดที่ต้องระวังคือค่าที่มันเขียนลงไฟล์เป็นชื่อไฟล์เต็มพร้อม ",[18,4639,4640],{},".md"," ถ้าฝั่งเว็บเอาไปต่อเป็น URL ตรง ๆ ต้องตัดนามสกุลทิ้งก่อน",[14,4643,4644,4647,4648,4651,4652,4655,4656,4659],{},[39,4645,4646],{},"ตั้งชื่อไฟล์ให้อัตโนมัติได้"," ผ่าน ",[18,4649,4650],{},"filename.template"," เขียนเป็น pattern อย่าง ",[18,4653,4654],{},"{year}-{month}-{day}-{ogTitle}.md"," แต่ token วันที่ตรงนี้คือวันที่ตอนกดสร้าง ไม่ได้ดึงจาก field ",[18,4657,4658],{},"date"," ที่กรอกไว้ ถ้าจะย้อนวันที่ก็ต้องแก้ชื่อไฟล์เองอยู่ดี",[14,4661,4662,4665,4666,4669],{},[39,4663,4664],{},"ล็อกการลบได้"," ด้วย ",[18,4667,4668],{},"operations"," เปิดปิด create, rename, delete แยกกันได้ ผมปิด delete ไว้ กันมือลั่นลบโพสต์จากหน้าเว็บ",[14,4671,4672,4675,4676,4679],{},[39,4673,4674],{},"ปุ่มยิง GitHub Actions"," อันนี้สนุกสุด ประกาศ ",[18,4677,4678],{},"actions"," ไว้ใน config แล้ว Pages CMS จะเอาปุ่มไปแปะให้ กดแล้วมัน dispatch workflow พร้อมส่ง payload บอกว่าใครกดและกดมาจากโพสต์ไหน ผมเอามาทำปุ่มรัน typecheck, lint กับ build ก่อน publish",[14,4681,4682,4687],{},[39,4683,4684],{},[18,4685,4686],{},"settings"," คุม commit message ที่ CMS สร้างขึ้นมา กับสั่งให้ merge field ที่ไม่ได้ประกาศไว้แทนที่จะลบทิ้ง อันหลังนี่สำคัญ ถ้าไม่เปิดไว้ field ที่มีอยู่ในไฟล์แต่ไม่มีใน config จะหายไปตอน save",[14,4689,4690],{},"แล้วนี่คือ config ที่ใช้อยู่ตอนนี้ครับ",[23,4692,4696],{"className":4693,"code":4694,"language":4695,"meta":31,"style":31},"language-yaml shiki shiki-themes material-theme-lighter one-dark-pro github-dark","media:\n  input: public/images\n  output: /images\n  extensions: [jpg, jpeg, png, webp, gif, svg]\n\nsettings:\n  content:\n    # เก็บ field ที่ไม่ได้ประกาศข้างล่างไว้ ไม่ต้องลบทิ้งตอน save\n    merge: true\n  commit:\n    templates:\n      create: 'content(blogs): add {path}'\n      update: 'content(blogs): update {path}'\n      delete: 'content(blogs): remove {path}'\n      rename: 'content(blogs): rename {oldPath} to {newPath}'\n\ncontent:\n  - name: blogs\n    label: Blog Posts\n    type: collection\n    path: content/blogs\n    filename:\n      # slug ของ route คือชื่อไฟล์ ตัดนามสกุล ตัว date prefix เลยสำคัญ\n      # แต่ {year}/{month}/{day} คือวันที่ตอนกดสร้าง ไม่ได้ตามค่าใน field date\n      # เป็นแค่ค่าตั้งต้น field: create เปิดให้แก้เองได้\n      template: '{year}-{month}-{day}-{ogTitle}.md'\n      field: create\n    operations:\n      create: true\n      rename: true\n      delete: false\n    actions:\n      - name: run-checks\n        label: Run checks\n        workflow: pages-cms-checks.yml\n        scope: collection\n        ref: current\n    view:\n      fields: [title, date, tags, draft]\n      primary: title\n      sort: [date, title]\n      search: [title, description, tags]\n      default:\n        sort: date\n        order: desc\n    fields:\n      - name: title\n        label: Title\n        type: string\n        required: true\n      - name: ogTitle\n        label: OG Text\n        type: string\n        required: true\n      - name: description\n        label: Description\n        type: text\n        required: true\n      - name: date\n        label: Date\n        type: date\n        required: true\n        options:\n          format: yyyy-MM-dd\n      - name: tags\n        label: Tags\n        type: select\n        options:\n          multiple: true\n          values: [Development, Homelab, AI, Tools, Python, Lifestyle]\n      - name: draft\n        label: Draft\n        type: boolean\n        default: true\n      - name: pin\n        label: Pin\n        type: boolean\n        default: false\n      - name: prev\n        label: Previous Post\n        type: reference\n        options:\n          collection: blogs\n          value: '{name}'\n          label: '{primary}'\n          search: 'title,description'\n      - name: next\n        label: Next Post\n        type: reference\n        options:\n          collection: blogs\n          value: '{name}'\n          label: '{primary}'\n          search: 'title,description'\n      - name: recommends\n        label: Recommended Posts\n        type: reference\n        options:\n          collection: blogs\n          multiple: true\n          max: 3\n          value: '{name}'\n          label: '{primary}'\n          search: 'title,description'\n      - name: body\n        label: Body\n        type: rich-text\n","yaml",[18,4697,4698,4706,4716,4726,4766,4770,4776,4783,4788,4799,4806,4813,4827,4841,4855,4869,4873,4880,4893,4903,4913,4923,4930,4935,4940,4945,4958,4968,4975,4983,4991,5000,5007,5019,5029,5039,5048,5058,5065,5094,5104,5122,5144,5151,5161,5171,5178,5188,5197,5207,5216,5227,5236,5244,5252,5263,5272,5281,5289,5300,5310,5319,5328,5336,5347,5359,5369,5379,5386,5396,5435,5447,5457,5467,5477,5489,5499,5508,5517,5529,5539,5549,5556,5566,5581,5596,5611,5623,5633,5642,5649,5658,5671,5684,5697,5709,5719,5728,5735,5744,5753,5764,5777,5790,5803,5815,5825],{"__ignoreMap":31},[81,4699,4700,4703],{"class":83,"line":84},[81,4701,4702],{"class":4222},"media",[81,4704,4705],{"class":628},":\n",[81,4707,4708,4711,4713],{"class":83,"line":102},[81,4709,4710],{"class":4222},"  input",[81,4712,1258],{"class":628},[81,4714,4715],{"class":90}," public/images\n",[81,4717,4718,4721,4723],{"class":83,"line":113},[81,4719,4720],{"class":4222},"  output",[81,4722,1258],{"class":628},[81,4724,4725],{"class":90}," /images\n",[81,4727,4728,4731,4733,4735,4738,4740,4743,4745,4748,4750,4753,4755,4758,4760,4763],{"class":83,"line":247},[81,4729,4730],{"class":4222},"  extensions",[81,4732,1258],{"class":628},[81,4734,2235],{"class":628},[81,4736,4737],{"class":90},"jpg",[81,4739,1724],{"class":628},[81,4741,4742],{"class":90}," jpeg",[81,4744,1724],{"class":628},[81,4746,4747],{"class":90}," png",[81,4749,1724],{"class":628},[81,4751,4752],{"class":90}," webp",[81,4754,1724],{"class":628},[81,4756,4757],{"class":90}," gif",[81,4759,1724],{"class":628},[81,4761,4762],{"class":90}," svg",[81,4764,4765],{"class":628},"]\n",[81,4767,4768],{"class":83,"line":393},[81,4769,366],{"emptyLinePlaceholder":365},[81,4771,4772,4774],{"class":83,"line":404},[81,4773,4686],{"class":4222},[81,4775,4705],{"class":628},[81,4777,4778,4781],{"class":83,"line":415},[81,4779,4780],{"class":4222},"  content",[81,4782,4705],{"class":628},[81,4784,4785],{"class":83,"line":1438},[81,4786,4787],{"class":1064},"    # เก็บ field ที่ไม่ได้ประกาศข้างล่างไว้ ไม่ต้องลบทิ้งตอน save\n",[81,4789,4790,4793,4795],{"class":83,"line":1455},[81,4791,4792],{"class":4222},"    merge",[81,4794,1258],{"class":628},[81,4796,4798],{"class":4797},"salyU"," true\n",[81,4800,4801,4804],{"class":83,"line":1467},[81,4802,4803],{"class":4222},"  commit",[81,4805,4705],{"class":628},[81,4807,4808,4811],{"class":83,"line":1483},[81,4809,4810],{"class":4222},"    templates",[81,4812,4705],{"class":628},[81,4814,4815,4818,4820,4822,4825],{"class":83,"line":1497},[81,4816,4817],{"class":4222},"      create",[81,4819,1258],{"class":628},[81,4821,129],{"class":128},[81,4823,4824],{"class":90},"content(blogs): add {path}",[81,4826,135],{"class":128},[81,4828,4829,4832,4834,4836,4839],{"class":83,"line":1509},[81,4830,4831],{"class":4222},"      update",[81,4833,1258],{"class":628},[81,4835,129],{"class":128},[81,4837,4838],{"class":90},"content(blogs): update {path}",[81,4840,135],{"class":128},[81,4842,4843,4846,4848,4850,4853],{"class":83,"line":1514},[81,4844,4845],{"class":4222},"      delete",[81,4847,1258],{"class":628},[81,4849,129],{"class":128},[81,4851,4852],{"class":90},"content(blogs): remove {path}",[81,4854,135],{"class":128},[81,4856,4857,4860,4862,4864,4867],{"class":83,"line":1519},[81,4858,4859],{"class":4222},"      rename",[81,4861,1258],{"class":628},[81,4863,129],{"class":128},[81,4865,4866],{"class":90},"content(blogs): rename {oldPath} to {newPath}",[81,4868,135],{"class":128},[81,4870,4871],{"class":83,"line":1535},[81,4872,366],{"emptyLinePlaceholder":365},[81,4874,4875,4878],{"class":83,"line":1548},[81,4876,4877],{"class":4222},"content",[81,4879,4705],{"class":628},[81,4881,4882,4885,4888,4890],{"class":83,"line":1559},[81,4883,4884],{"class":628},"  -",[81,4886,4887],{"class":4222}," name",[81,4889,1258],{"class":628},[81,4891,4892],{"class":90}," blogs\n",[81,4894,4895,4898,4900],{"class":83,"line":1570},[81,4896,4897],{"class":4222},"    label",[81,4899,1258],{"class":628},[81,4901,4902],{"class":90}," Blog Posts\n",[81,4904,4905,4908,4910],{"class":83,"line":1582},[81,4906,4907],{"class":4222},"    type",[81,4909,1258],{"class":628},[81,4911,4912],{"class":90}," collection\n",[81,4914,4915,4918,4920],{"class":83,"line":1588},[81,4916,4917],{"class":4222},"    path",[81,4919,1258],{"class":628},[81,4921,4922],{"class":90}," content/blogs\n",[81,4924,4925,4928],{"class":83,"line":1647},[81,4926,4927],{"class":4222},"    filename",[81,4929,4705],{"class":628},[81,4931,4932],{"class":83,"line":1692},[81,4933,4934],{"class":1064},"      # slug ของ route คือชื่อไฟล์ ตัดนามสกุล ตัว date prefix เลยสำคัญ\n",[81,4936,4937],{"class":83,"line":1697},[81,4938,4939],{"class":1064},"      # แต่ {year}/{month}/{day} คือวันที่ตอนกดสร้าง ไม่ได้ตามค่าใน field date\n",[81,4941,4942],{"class":83,"line":1703},[81,4943,4944],{"class":1064},"      # เป็นแค่ค่าตั้งต้น field: create เปิดให้แก้เองได้\n",[81,4946,4947,4950,4952,4954,4956],{"class":83,"line":1733},[81,4948,4949],{"class":4222},"      template",[81,4951,1258],{"class":628},[81,4953,129],{"class":128},[81,4955,4654],{"class":90},[81,4957,135],{"class":128},[81,4959,4960,4963,4965],{"class":83,"line":1738},[81,4961,4962],{"class":4222},"      field",[81,4964,1258],{"class":628},[81,4966,4967],{"class":90}," create\n",[81,4969,4970,4973],{"class":83,"line":1768},[81,4971,4972],{"class":4222},"    operations",[81,4974,4705],{"class":628},[81,4976,4977,4979,4981],{"class":83,"line":1773},[81,4978,4817],{"class":4222},[81,4980,1258],{"class":628},[81,4982,4798],{"class":4797},[81,4984,4985,4987,4989],{"class":83,"line":1781},[81,4986,4859],{"class":4222},[81,4988,1258],{"class":628},[81,4990,4798],{"class":4797},[81,4992,4993,4995,4997],{"class":83,"line":1794},[81,4994,4845],{"class":4222},[81,4996,1258],{"class":628},[81,4998,4999],{"class":4797}," false\n",[81,5001,5002,5005],{"class":83,"line":1806},[81,5003,5004],{"class":4222},"    actions",[81,5006,4705],{"class":628},[81,5008,5009,5012,5014,5016],{"class":83,"line":1814},[81,5010,5011],{"class":628},"      -",[81,5013,4887],{"class":4222},[81,5015,1258],{"class":628},[81,5017,5018],{"class":90}," run-checks\n",[81,5020,5021,5024,5026],{"class":83,"line":1820},[81,5022,5023],{"class":4222},"        label",[81,5025,1258],{"class":628},[81,5027,5028],{"class":90}," Run checks\n",[81,5030,5031,5034,5036],{"class":83,"line":1825},[81,5032,5033],{"class":4222},"        workflow",[81,5035,1258],{"class":628},[81,5037,5038],{"class":90}," pages-cms-checks.yml\n",[81,5040,5041,5044,5046],{"class":83,"line":1830},[81,5042,5043],{"class":4222},"        scope",[81,5045,1258],{"class":628},[81,5047,4912],{"class":90},[81,5049,5050,5053,5055],{"class":83,"line":1847},[81,5051,5052],{"class":4222},"        ref",[81,5054,1258],{"class":628},[81,5056,5057],{"class":90}," current\n",[81,5059,5060,5063],{"class":83,"line":1860},[81,5061,5062],{"class":4222},"    view",[81,5064,4705],{"class":628},[81,5066,5067,5070,5072,5074,5077,5079,5082,5084,5087,5089,5092],{"class":83,"line":1871},[81,5068,5069],{"class":4222},"      fields",[81,5071,1258],{"class":628},[81,5073,2235],{"class":628},[81,5075,5076],{"class":90},"title",[81,5078,1724],{"class":628},[81,5080,5081],{"class":90}," date",[81,5083,1724],{"class":628},[81,5085,5086],{"class":90}," tags",[81,5088,1724],{"class":628},[81,5090,5091],{"class":90}," draft",[81,5093,4765],{"class":628},[81,5095,5096,5099,5101],{"class":83,"line":1882},[81,5097,5098],{"class":4222},"      primary",[81,5100,1258],{"class":628},[81,5102,5103],{"class":90}," title\n",[81,5105,5106,5109,5111,5113,5115,5117,5120],{"class":83,"line":1892},[81,5107,5108],{"class":4222},"      sort",[81,5110,1258],{"class":628},[81,5112,2235],{"class":628},[81,5114,4658],{"class":90},[81,5116,1724],{"class":628},[81,5118,5119],{"class":90}," title",[81,5121,4765],{"class":628},[81,5123,5124,5127,5129,5131,5133,5135,5138,5140,5142],{"class":83,"line":1909},[81,5125,5126],{"class":4222},"      search",[81,5128,1258],{"class":628},[81,5130,2235],{"class":628},[81,5132,5076],{"class":90},[81,5134,1724],{"class":628},[81,5136,5137],{"class":90}," description",[81,5139,1724],{"class":628},[81,5141,5086],{"class":90},[81,5143,4765],{"class":628},[81,5145,5146,5149],{"class":83,"line":1922},[81,5147,5148],{"class":4222},"      default",[81,5150,4705],{"class":628},[81,5152,5153,5156,5158],{"class":83,"line":1952},[81,5154,5155],{"class":4222},"        sort",[81,5157,1258],{"class":628},[81,5159,5160],{"class":90}," date\n",[81,5162,5163,5166,5168],{"class":83,"line":1957},[81,5164,5165],{"class":4222},"        order",[81,5167,1258],{"class":628},[81,5169,5170],{"class":90}," desc\n",[81,5172,5173,5176],{"class":83,"line":1975},[81,5174,5175],{"class":4222},"    fields",[81,5177,4705],{"class":628},[81,5179,5180,5182,5184,5186],{"class":83,"line":1991},[81,5181,5011],{"class":628},[81,5183,4887],{"class":4222},[81,5185,1258],{"class":628},[81,5187,5103],{"class":90},[81,5189,5190,5192,5194],{"class":83,"line":1996},[81,5191,5023],{"class":4222},[81,5193,1258],{"class":628},[81,5195,5196],{"class":90}," Title\n",[81,5198,5199,5202,5204],{"class":83,"line":2025},[81,5200,5201],{"class":4222},"        type",[81,5203,1258],{"class":628},[81,5205,5206],{"class":90}," string\n",[81,5208,5209,5212,5214],{"class":83,"line":2049},[81,5210,5211],{"class":4222},"        required",[81,5213,1258],{"class":628},[81,5215,4798],{"class":4797},[81,5217,5218,5220,5222,5224],{"class":83,"line":2054},[81,5219,5011],{"class":628},[81,5221,4887],{"class":4222},[81,5223,1258],{"class":628},[81,5225,5226],{"class":90}," ogTitle\n",[81,5228,5229,5231,5233],{"class":83,"line":2061},[81,5230,5023],{"class":4222},[81,5232,1258],{"class":628},[81,5234,5235],{"class":90}," OG Text\n",[81,5237,5238,5240,5242],{"class":83,"line":2069},[81,5239,5201],{"class":4222},[81,5241,1258],{"class":628},[81,5243,5206],{"class":90},[81,5245,5246,5248,5250],{"class":83,"line":2077},[81,5247,5211],{"class":4222},[81,5249,1258],{"class":628},[81,5251,4798],{"class":4797},[81,5253,5254,5256,5258,5260],{"class":83,"line":2085},[81,5255,5011],{"class":628},[81,5257,4887],{"class":4222},[81,5259,1258],{"class":628},[81,5261,5262],{"class":90}," description\n",[81,5264,5265,5267,5269],{"class":83,"line":2093},[81,5266,5023],{"class":4222},[81,5268,1258],{"class":628},[81,5270,5271],{"class":90}," Description\n",[81,5273,5274,5276,5278],{"class":83,"line":2106},[81,5275,5201],{"class":4222},[81,5277,1258],{"class":628},[81,5279,5280],{"class":90}," text\n",[81,5282,5283,5285,5287],{"class":83,"line":2111},[81,5284,5211],{"class":4222},[81,5286,1258],{"class":628},[81,5288,4798],{"class":4797},[81,5290,5292,5294,5296,5298],{"class":83,"line":5291},59,[81,5293,5011],{"class":628},[81,5295,4887],{"class":4222},[81,5297,1258],{"class":628},[81,5299,5160],{"class":90},[81,5301,5303,5305,5307],{"class":83,"line":5302},60,[81,5304,5023],{"class":4222},[81,5306,1258],{"class":628},[81,5308,5309],{"class":90}," Date\n",[81,5311,5313,5315,5317],{"class":83,"line":5312},61,[81,5314,5201],{"class":4222},[81,5316,1258],{"class":628},[81,5318,5160],{"class":90},[81,5320,5322,5324,5326],{"class":83,"line":5321},62,[81,5323,5211],{"class":4222},[81,5325,1258],{"class":628},[81,5327,4798],{"class":4797},[81,5329,5331,5334],{"class":83,"line":5330},63,[81,5332,5333],{"class":4222},"        options",[81,5335,4705],{"class":628},[81,5337,5339,5342,5344],{"class":83,"line":5338},64,[81,5340,5341],{"class":4222},"          format",[81,5343,1258],{"class":628},[81,5345,5346],{"class":90}," yyyy-MM-dd\n",[81,5348,5350,5352,5354,5356],{"class":83,"line":5349},65,[81,5351,5011],{"class":628},[81,5353,4887],{"class":4222},[81,5355,1258],{"class":628},[81,5357,5358],{"class":90}," tags\n",[81,5360,5362,5364,5366],{"class":83,"line":5361},66,[81,5363,5023],{"class":4222},[81,5365,1258],{"class":628},[81,5367,5368],{"class":90}," Tags\n",[81,5370,5372,5374,5376],{"class":83,"line":5371},67,[81,5373,5201],{"class":4222},[81,5375,1258],{"class":628},[81,5377,5378],{"class":90}," select\n",[81,5380,5382,5384],{"class":83,"line":5381},68,[81,5383,5333],{"class":4222},[81,5385,4705],{"class":628},[81,5387,5389,5392,5394],{"class":83,"line":5388},69,[81,5390,5391],{"class":4222},"          multiple",[81,5393,1258],{"class":628},[81,5395,4798],{"class":4797},[81,5397,5399,5402,5404,5406,5408,5410,5413,5415,5418,5420,5423,5425,5428,5430,5433],{"class":83,"line":5398},70,[81,5400,5401],{"class":4222},"          values",[81,5403,1258],{"class":628},[81,5405,2235],{"class":628},[81,5407,968],{"class":90},[81,5409,1724],{"class":628},[81,5411,5412],{"class":90}," Homelab",[81,5414,1724],{"class":628},[81,5416,5417],{"class":90}," AI",[81,5419,1724],{"class":628},[81,5421,5422],{"class":90}," Tools",[81,5424,1724],{"class":628},[81,5426,5427],{"class":90}," Python",[81,5429,1724],{"class":628},[81,5431,5432],{"class":90}," Lifestyle",[81,5434,4765],{"class":628},[81,5436,5438,5440,5442,5444],{"class":83,"line":5437},71,[81,5439,5011],{"class":628},[81,5441,4887],{"class":4222},[81,5443,1258],{"class":628},[81,5445,5446],{"class":90}," draft\n",[81,5448,5450,5452,5454],{"class":83,"line":5449},72,[81,5451,5023],{"class":4222},[81,5453,1258],{"class":628},[81,5455,5456],{"class":90}," Draft\n",[81,5458,5460,5462,5464],{"class":83,"line":5459},73,[81,5461,5201],{"class":4222},[81,5463,1258],{"class":628},[81,5465,5466],{"class":90}," boolean\n",[81,5468,5470,5473,5475],{"class":83,"line":5469},74,[81,5471,5472],{"class":4222},"        default",[81,5474,1258],{"class":628},[81,5476,4798],{"class":4797},[81,5478,5480,5482,5484,5486],{"class":83,"line":5479},75,[81,5481,5011],{"class":628},[81,5483,4887],{"class":4222},[81,5485,1258],{"class":628},[81,5487,5488],{"class":90}," pin\n",[81,5490,5492,5494,5496],{"class":83,"line":5491},76,[81,5493,5023],{"class":4222},[81,5495,1258],{"class":628},[81,5497,5498],{"class":90}," Pin\n",[81,5500,5502,5504,5506],{"class":83,"line":5501},77,[81,5503,5201],{"class":4222},[81,5505,1258],{"class":628},[81,5507,5466],{"class":90},[81,5509,5511,5513,5515],{"class":83,"line":5510},78,[81,5512,5472],{"class":4222},[81,5514,1258],{"class":628},[81,5516,4999],{"class":4797},[81,5518,5520,5522,5524,5526],{"class":83,"line":5519},79,[81,5521,5011],{"class":628},[81,5523,4887],{"class":4222},[81,5525,1258],{"class":628},[81,5527,5528],{"class":90}," prev\n",[81,5530,5532,5534,5536],{"class":83,"line":5531},80,[81,5533,5023],{"class":4222},[81,5535,1258],{"class":628},[81,5537,5538],{"class":90}," Previous Post\n",[81,5540,5542,5544,5546],{"class":83,"line":5541},81,[81,5543,5201],{"class":4222},[81,5545,1258],{"class":628},[81,5547,5548],{"class":90}," reference\n",[81,5550,5552,5554],{"class":83,"line":5551},82,[81,5553,5333],{"class":4222},[81,5555,4705],{"class":628},[81,5557,5559,5562,5564],{"class":83,"line":5558},83,[81,5560,5561],{"class":4222},"          collection",[81,5563,1258],{"class":628},[81,5565,4892],{"class":90},[81,5567,5569,5572,5574,5576,5579],{"class":83,"line":5568},84,[81,5570,5571],{"class":4222},"          value",[81,5573,1258],{"class":628},[81,5575,129],{"class":128},[81,5577,5578],{"class":90},"{name}",[81,5580,135],{"class":128},[81,5582,5584,5587,5589,5591,5594],{"class":83,"line":5583},85,[81,5585,5586],{"class":4222},"          label",[81,5588,1258],{"class":628},[81,5590,129],{"class":128},[81,5592,5593],{"class":90},"{primary}",[81,5595,135],{"class":128},[81,5597,5599,5602,5604,5606,5609],{"class":83,"line":5598},86,[81,5600,5601],{"class":4222},"          search",[81,5603,1258],{"class":628},[81,5605,129],{"class":128},[81,5607,5608],{"class":90},"title,description",[81,5610,135],{"class":128},[81,5612,5614,5616,5618,5620],{"class":83,"line":5613},87,[81,5615,5011],{"class":628},[81,5617,4887],{"class":4222},[81,5619,1258],{"class":628},[81,5621,5622],{"class":90}," next\n",[81,5624,5626,5628,5630],{"class":83,"line":5625},88,[81,5627,5023],{"class":4222},[81,5629,1258],{"class":628},[81,5631,5632],{"class":90}," Next Post\n",[81,5634,5636,5638,5640],{"class":83,"line":5635},89,[81,5637,5201],{"class":4222},[81,5639,1258],{"class":628},[81,5641,5548],{"class":90},[81,5643,5645,5647],{"class":83,"line":5644},90,[81,5646,5333],{"class":4222},[81,5648,4705],{"class":628},[81,5650,5652,5654,5656],{"class":83,"line":5651},91,[81,5653,5561],{"class":4222},[81,5655,1258],{"class":628},[81,5657,4892],{"class":90},[81,5659,5661,5663,5665,5667,5669],{"class":83,"line":5660},92,[81,5662,5571],{"class":4222},[81,5664,1258],{"class":628},[81,5666,129],{"class":128},[81,5668,5578],{"class":90},[81,5670,135],{"class":128},[81,5672,5674,5676,5678,5680,5682],{"class":83,"line":5673},93,[81,5675,5586],{"class":4222},[81,5677,1258],{"class":628},[81,5679,129],{"class":128},[81,5681,5593],{"class":90},[81,5683,135],{"class":128},[81,5685,5687,5689,5691,5693,5695],{"class":83,"line":5686},94,[81,5688,5601],{"class":4222},[81,5690,1258],{"class":628},[81,5692,129],{"class":128},[81,5694,5608],{"class":90},[81,5696,135],{"class":128},[81,5698,5700,5702,5704,5706],{"class":83,"line":5699},95,[81,5701,5011],{"class":628},[81,5703,4887],{"class":4222},[81,5705,1258],{"class":628},[81,5707,5708],{"class":90}," recommends\n",[81,5710,5712,5714,5716],{"class":83,"line":5711},96,[81,5713,5023],{"class":4222},[81,5715,1258],{"class":628},[81,5717,5718],{"class":90}," Recommended Posts\n",[81,5720,5722,5724,5726],{"class":83,"line":5721},97,[81,5723,5201],{"class":4222},[81,5725,1258],{"class":628},[81,5727,5548],{"class":90},[81,5729,5731,5733],{"class":83,"line":5730},98,[81,5732,5333],{"class":4222},[81,5734,4705],{"class":628},[81,5736,5738,5740,5742],{"class":83,"line":5737},99,[81,5739,5561],{"class":4222},[81,5741,1258],{"class":628},[81,5743,4892],{"class":90},[81,5745,5747,5749,5751],{"class":83,"line":5746},100,[81,5748,5391],{"class":4222},[81,5750,1258],{"class":628},[81,5752,4798],{"class":4797},[81,5754,5756,5759,5761],{"class":83,"line":5755},101,[81,5757,5758],{"class":4222},"          max",[81,5760,1258],{"class":628},[81,5762,5763],{"class":655}," 3\n",[81,5765,5767,5769,5771,5773,5775],{"class":83,"line":5766},102,[81,5768,5571],{"class":4222},[81,5770,1258],{"class":628},[81,5772,129],{"class":128},[81,5774,5578],{"class":90},[81,5776,135],{"class":128},[81,5778,5780,5782,5784,5786,5788],{"class":83,"line":5779},103,[81,5781,5586],{"class":4222},[81,5783,1258],{"class":628},[81,5785,129],{"class":128},[81,5787,5593],{"class":90},[81,5789,135],{"class":128},[81,5791,5793,5795,5797,5799,5801],{"class":83,"line":5792},104,[81,5794,5601],{"class":4222},[81,5796,1258],{"class":628},[81,5798,129],{"class":128},[81,5800,5608],{"class":90},[81,5802,135],{"class":128},[81,5804,5806,5808,5810,5812],{"class":83,"line":5805},105,[81,5807,5011],{"class":628},[81,5809,4887],{"class":4222},[81,5811,1258],{"class":628},[81,5813,5814],{"class":90}," body\n",[81,5816,5818,5820,5822],{"class":83,"line":5817},106,[81,5819,5023],{"class":4222},[81,5821,1258],{"class":628},[81,5823,5824],{"class":90}," Body\n",[81,5826,5828,5830,5832],{"class":83,"line":5827},107,[81,5829,5201],{"class":4222},[81,5831,1258],{"class":628},[81,5833,5834],{"class":90}," rich-text\n",[938,5836,5837],{},"html pre.shiki code .s0Prt, html code.shiki .s0Prt{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#85E89D}html pre.shiki code .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .salyU, html code.shiki .salyU{--shiki-light:#FF5370;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .snOyU, html code.shiki .snOyU{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":31,"searchDepth":102,"depth":102,"links":5839},[5840,5841,5842,5843,5844],{"id":4476,"depth":102,"text":4477},{"id":4522,"depth":102,"text":4523},{"id":4549,"depth":102,"text":4550},{"id":4575,"depth":102,"text":4575},{"id":4591,"depth":102,"text":4592},"2025-08-30T00:00:00.000Z","แนะนำเครื่องมือสำหรับการจัดการ content สำหรับเว็บไซต์ประเภท static site",{},"First Time With PageCMS","/blogs/2025-08-30-pagecms-opensource",[1206],{"title":4471,"description":5846},{"loc":5849},"blogs/2025-08-30-pagecms-opensource",[968],"ppyGtV0BHAZ8I5GfSQxi84-O9eSY9Y37rqQU7XpOYTs",{"id":5857,"title":5858,"body":5859,"date":6472,"description":6473,"draft":956,"extension":957,"meta":6474,"navigation":365,"next":959,"ogTitle":6475,"path":6476,"pin":956,"prev":959,"recommends":959,"seo":6477,"sitemap":6478,"stem":6479,"tags":6480,"updated":2527,"__hash__":6481},"content/blogs/2025-11-03-dirsearch-uv.md","ติดตั้ง dirsearch ด้วย uv แล้วใช้งานไม่ได้",{"type":7,"value":5860,"toc":6467},[5861,5864,5867,5888,5891,6100,6103,6111,6121,6131,6138,6141,6144,6154,6170,6461,6464],[10,5862,5863],{"id":5863},"ปัญหาที่เจอ",[14,5865,5866],{},"หากทำการติดตั้ง dirsearch ด้วยเครื่องมือ",[23,5868,5870],{"className":75,"code":5869,"language":77,"meta":31,"style":31},"uv tool install dirsearch==0.4.3\n",[18,5871,5872],{"__ignoreMap":31},[81,5873,5874,5877,5880,5882,5885],{"class":83,"line":84},[81,5875,5876],{"class":87},"uv",[81,5878,5879],{"class":90}," tool",[81,5881,2829],{"class":90},[81,5883,5884],{"class":90}," dirsearch==",[81,5886,5887],{"class":655},"0.4.3\n",[14,5889,5890],{},"จะเจอกับ error",[23,5892,5894],{"className":75,"code":5893,"language":77,"meta":31,"style":31},"PS C:\\Users\\robin> dirsearch --version\nTraceback (most recent call last):\n  File \"\u003Cfrozen runpy>\", line 198, in _run_module_as_main\n  File \"\u003Cfrozen runpy>\", line 88, in _run_code\n  File \"C:\\Users\\robin\\.local\\bin\\dirsearch.exe\\__main__.py\", line 4, in \u003Cmodule>\n    from dirsearch.dirsearch import main\n  File \"C:\\Users\\robin\\AppData\\Roaming\\uv\\tools\\dirsearch\\Lib\\site-packages\\dirsearch\\dirsearch.py\", line 23, in \u003Cmodule>\n    from pkg_resources import DistributionNotFound, VersionConflict\nModuleNotFoundError: No module named 'pkg_resources'\n",[18,5895,5896,5927,5947,5972,5994,6023,6037,6065,6080],{"__ignoreMap":31},[81,5897,5898,5901,5904,5907,5910,5913,5916,5919,5921,5924],{"class":83,"line":84},[81,5899,5900],{"class":87},"PS",[81,5902,5903],{"class":90}," C:",[81,5905,5906],{"class":98},"\\U",[81,5908,5909],{"class":90},"sers",[81,5911,5912],{"class":98},"\\r",[81,5914,5915],{"class":90},"obi",[81,5917,5918],{"class":1449},"n",[81,5920,1433],{"class":618},[81,5922,5923],{"class":90}," dirsearch",[81,5925,5926],{"class":94}," --version\n",[81,5928,5929,5932,5935,5938,5941,5944],{"class":83,"line":102},[81,5930,5931],{"class":87},"Traceback",[81,5933,5934],{"class":1449}," (most ",[81,5936,5937],{"class":90},"recent",[81,5939,5940],{"class":90}," call",[81,5942,5943],{"class":90}," last",[81,5945,5946],{"class":1449},"):\n",[81,5948,5949,5952,5954,5957,5959,5961,5964,5967,5969],{"class":83,"line":113},[81,5950,5951],{"class":87},"  File",[81,5953,641],{"class":128},[81,5955,5956],{"class":90},"\u003Cfrozen runpy>",[81,5958,1255],{"class":128},[81,5960,1724],{"class":90},[81,5962,5963],{"class":90}," line",[81,5965,5966],{"class":90}," 198,",[81,5968,3780],{"class":90},[81,5970,5971],{"class":90}," _run_module_as_main\n",[81,5973,5974,5976,5978,5980,5982,5984,5986,5989,5991],{"class":83,"line":247},[81,5975,5951],{"class":87},[81,5977,641],{"class":128},[81,5979,5956],{"class":90},[81,5981,1255],{"class":128},[81,5983,1724],{"class":90},[81,5985,5963],{"class":90},[81,5987,5988],{"class":90}," 88,",[81,5990,3780],{"class":90},[81,5992,5993],{"class":90}," _run_code\n",[81,5995,5996,5998,6000,6003,6005,6007,6009,6012,6014,6016,6019,6021],{"class":83,"line":393},[81,5997,5951],{"class":87},[81,5999,641],{"class":128},[81,6001,6002],{"class":90},"C:\\Users\\robin\\.local\\bin\\dirsearch.exe\\__main__.py",[81,6004,1255],{"class":128},[81,6006,1724],{"class":90},[81,6008,5963],{"class":90},[81,6010,6011],{"class":90}," 4,",[81,6013,3780],{"class":90},[81,6015,1969],{"class":618},[81,6017,6018],{"class":90},"modul",[81,6020,3571],{"class":1449},[81,6022,3574],{"class":618},[81,6024,6025,6028,6031,6034],{"class":83,"line":404},[81,6026,6027],{"class":87},"    from",[81,6029,6030],{"class":90}," dirsearch.dirsearch",[81,6032,6033],{"class":90}," import",[81,6035,6036],{"class":90}," main\n",[81,6038,6039,6041,6043,6046,6048,6050,6052,6055,6057,6059,6061,6063],{"class":83,"line":415},[81,6040,5951],{"class":87},[81,6042,641],{"class":128},[81,6044,6045],{"class":90},"C:\\Users\\robin\\AppData\\Roaming\\uv\\tools\\dirsearch\\Lib\\site-packages\\dirsearch\\dirsearch.py",[81,6047,1255],{"class":128},[81,6049,1724],{"class":90},[81,6051,5963],{"class":90},[81,6053,6054],{"class":90}," 23,",[81,6056,3780],{"class":90},[81,6058,1969],{"class":618},[81,6060,6018],{"class":90},[81,6062,3571],{"class":1449},[81,6064,3574],{"class":618},[81,6066,6067,6069,6072,6074,6077],{"class":83,"line":1438},[81,6068,6027],{"class":87},[81,6070,6071],{"class":90}," pkg_resources",[81,6073,6033],{"class":90},[81,6075,6076],{"class":90}," DistributionNotFound,",[81,6078,6079],{"class":90}," VersionConflict\n",[81,6081,6082,6085,6088,6090,6093,6095,6098],{"class":83,"line":1455},[81,6083,6084],{"class":87},"ModuleNotFoundError:",[81,6086,6087],{"class":90}," No",[81,6089,3798],{"class":90},[81,6091,6092],{"class":90}," named",[81,6094,129],{"class":128},[81,6096,6097],{"class":90},"pkg_resources",[81,6099,135],{"class":128},[10,6101,6102],{"id":6102},"ทำไมถึงพัง",[14,6104,6105,6107,6108],{},[18,6106,6097],{}," ไม่ใช่ของที่ติดมากับ python นะ มันเป็นส่วนหนึ่งของ package ชื่อ ",[18,6109,6110],{},"setuptools",[14,6112,6113,6114,6117,6118,6120],{},"ทีนี้ dirsearch 0.4.3 บน PyPI (ปล่อยตั้งแต่ปี 2022) มัน ",[18,6115,6116],{},"import pkg_resources"," แต่ไม่ได้ประกาศ ",[18,6119,6110],{}," เป็น dependency ไว้ ลองดู metadata ของมันได้ครับ มีแค่ Jinja2, PySocks, requests, urllib3 พวกนี้ ไม่มี setuptools สักตัว",[14,6122,6123,6124,6127,6128,6130],{},"สมัยก่อนไม่มีปัญหาเพราะ virtualenv จะแถม setuptools มาให้เสมอ แต่ ",[18,6125,6126],{},"uv tool install"," มันสร้าง environment แบบสะอาดจริง ๆ ไม่ยัด setuptools มาให้ พอ dirsearch ไปเรียก ",[18,6129,6097],{}," เลยไม่เจอ",[14,6132,6133,6134,6137],{},"เห็นได้จาก log ตอนลงจาก git ด้านล่างเลยครับ มีบรรทัด ",[18,6135,6136],{},"+ setuptools==80.9.0"," โผล่มา เพราะ version บน git แก้เรื่องนี้ไปแล้ว",[10,6139,6140],{"id":6140},"วิธีแก้ไข",[14,6142,6143],{},"ให้ติดตั้งใหม่ด้วย",[2671,6145,6146],{},[14,6147,6148,6149],{},"จำเป็นต้องมี git cli ด้วยนะครับ (windows: ",[39,6150,6151],{},[1005,6152,6153],{},"winget install --id Git.Git -e --source winget)",[23,6155,6157],{"className":75,"code":6156,"language":77,"meta":31,"style":31},"uv tool install git+https://github.com/maurosoria/dirsearch\n",[18,6158,6159],{"__ignoreMap":31},[81,6160,6161,6163,6165,6167],{"class":83,"line":84},[81,6162,5876],{"class":87},[81,6164,5879],{"class":90},[81,6166,2829],{"class":90},[81,6168,6169],{"class":90}," git+https://github.com/maurosoria/dirsearch\n",[23,6171,6173],{"className":75,"code":6172,"language":77,"meta":31,"style":31},"PS C:\\Users\\robin> uv tool install git+https://github.com/maurosoria/dirsearch\n    Updated https://github.com/maurosoria/dirsearch (70a763556e5dd894e1eeb2e62f77d83cb3fc5603)\nResolved 35 packages in 1.06s\n      Built dirsearch @ git+https://github.com/maurosoria/dirsearch@70a763556e5dd894e1eeb2e62f77d83cb3fc5603\nPrepared 13 packages in 1.95s\nUninstalled 3 packages in 34ms\nInstalled 14 packages in 242ms\n + anyio==4.11.0\n - chardet==5.2.0\n + defusedcsv==3.0.0\n - dirsearch==0.4.3\n + dirsearch==0.4.3 (from git+https://github.com/maurosoria/dirsearch@70a763556e5dd894e1eeb2e62f77d83cb3fc5603)\n + h11==0.16.0\n + httpcore==1.0.9\n + httpx==0.28.1\n + httpx-ntlm==1.4.0\n + mysql-connector-python==9.5.0\n + psycopg==3.2.12\n + psycopg-binary==3.2.12\n - pyparsing==3.2.5\n + requests-toolbelt==1.0.0\n + setuptools==80.9.0\n + sniffio==1.3.1\n + tzdata==2025.2\n",[18,6174,6175,6202,6213,6229,6242,6257,6272,6287,6297,6307,6317,6325,6342,6352,6362,6372,6382,6392,6402,6411,6421,6431,6441,6451],{"__ignoreMap":31},[81,6176,6177,6179,6181,6183,6185,6187,6189,6191,6193,6196,6198,6200],{"class":83,"line":84},[81,6178,5900],{"class":87},[81,6180,5903],{"class":90},[81,6182,5906],{"class":98},[81,6184,5909],{"class":90},[81,6186,5912],{"class":98},[81,6188,5915],{"class":90},[81,6190,5918],{"class":1449},[81,6192,1433],{"class":618},[81,6194,6195],{"class":90}," uv",[81,6197,5879],{"class":90},[81,6199,2829],{"class":90},[81,6201,6169],{"class":90},[81,6203,6204,6207,6210],{"class":83,"line":102},[81,6205,6206],{"class":87},"    Updated",[81,6208,6209],{"class":90}," https://github.com/maurosoria/dirsearch",[81,6211,6212],{"class":1449}," (70a763556e5dd894e1eeb2e62f77d83cb3fc5603)\n",[81,6214,6215,6218,6221,6224,6226],{"class":83,"line":113},[81,6216,6217],{"class":87},"Resolved",[81,6219,6220],{"class":655}," 35",[81,6222,6223],{"class":90}," packages",[81,6225,3780],{"class":90},[81,6227,6228],{"class":90}," 1.06s\n",[81,6230,6231,6234,6236,6239],{"class":83,"line":247},[81,6232,6233],{"class":87},"      Built",[81,6235,5923],{"class":90},[81,6237,6238],{"class":90}," @",[81,6240,6241],{"class":90}," git+https://github.com/maurosoria/dirsearch@70a763556e5dd894e1eeb2e62f77d83cb3fc5603\n",[81,6243,6244,6247,6250,6252,6254],{"class":83,"line":393},[81,6245,6246],{"class":87},"Prepared",[81,6248,6249],{"class":655}," 13",[81,6251,6223],{"class":90},[81,6253,3780],{"class":90},[81,6255,6256],{"class":90}," 1.95s\n",[81,6258,6259,6262,6265,6267,6269],{"class":83,"line":404},[81,6260,6261],{"class":87},"Uninstalled",[81,6263,6264],{"class":655}," 3",[81,6266,6223],{"class":90},[81,6268,3780],{"class":90},[81,6270,6271],{"class":90}," 34ms\n",[81,6273,6274,6277,6280,6282,6284],{"class":83,"line":415},[81,6275,6276],{"class":87},"Installed",[81,6278,6279],{"class":655}," 14",[81,6281,6223],{"class":90},[81,6283,3780],{"class":90},[81,6285,6286],{"class":90}," 242ms\n",[81,6288,6289,6291,6294],{"class":83,"line":1438},[81,6290,2013],{"class":87},[81,6292,6293],{"class":90}," anyio==",[81,6295,6296],{"class":655},"4.11.0\n",[81,6298,6299,6301,6304],{"class":83,"line":1455},[81,6300,1754],{"class":87},[81,6302,6303],{"class":90}," chardet==",[81,6305,6306],{"class":655},"5.2.0\n",[81,6308,6309,6311,6314],{"class":83,"line":1467},[81,6310,2013],{"class":87},[81,6312,6313],{"class":90}," defusedcsv==",[81,6315,6316],{"class":655},"3.0.0\n",[81,6318,6319,6321,6323],{"class":83,"line":1483},[81,6320,1754],{"class":87},[81,6322,5884],{"class":90},[81,6324,5887],{"class":655},[81,6326,6327,6329,6331,6334,6337,6340],{"class":83,"line":1497},[81,6328,2013],{"class":87},[81,6330,5884],{"class":90},[81,6332,6333],{"class":655},"0.4.3",[81,6335,6336],{"class":1449}," (from ",[81,6338,6339],{"class":90},"git+https://github.com/maurosoria/dirsearch@70a763556e5dd894e1eeb2e62f77d83cb3fc5603",[81,6341,1730],{"class":1449},[81,6343,6344,6346,6349],{"class":83,"line":1509},[81,6345,2013],{"class":87},[81,6347,6348],{"class":90}," h11==",[81,6350,6351],{"class":655},"0.16.0\n",[81,6353,6354,6356,6359],{"class":83,"line":1514},[81,6355,2013],{"class":87},[81,6357,6358],{"class":90}," httpcore==",[81,6360,6361],{"class":655},"1.0.9\n",[81,6363,6364,6366,6369],{"class":83,"line":1519},[81,6365,2013],{"class":87},[81,6367,6368],{"class":90}," httpx==",[81,6370,6371],{"class":655},"0.28.1\n",[81,6373,6374,6376,6379],{"class":83,"line":1535},[81,6375,2013],{"class":87},[81,6377,6378],{"class":90}," httpx-ntlm==",[81,6380,6381],{"class":655},"1.4.0\n",[81,6383,6384,6386,6389],{"class":83,"line":1548},[81,6385,2013],{"class":87},[81,6387,6388],{"class":90}," mysql-connector-python==",[81,6390,6391],{"class":655},"9.5.0\n",[81,6393,6394,6396,6399],{"class":83,"line":1559},[81,6395,2013],{"class":87},[81,6397,6398],{"class":90}," psycopg==",[81,6400,6401],{"class":655},"3.2.12\n",[81,6403,6404,6406,6409],{"class":83,"line":1570},[81,6405,2013],{"class":87},[81,6407,6408],{"class":90}," psycopg-binary==",[81,6410,6401],{"class":655},[81,6412,6413,6415,6418],{"class":83,"line":1582},[81,6414,1754],{"class":87},[81,6416,6417],{"class":90}," pyparsing==",[81,6419,6420],{"class":655},"3.2.5\n",[81,6422,6423,6425,6428],{"class":83,"line":1588},[81,6424,2013],{"class":87},[81,6426,6427],{"class":90}," requests-toolbelt==",[81,6429,6430],{"class":655},"1.0.0\n",[81,6432,6433,6435,6438],{"class":83,"line":1647},[81,6434,2013],{"class":87},[81,6436,6437],{"class":90}," setuptools==",[81,6439,6440],{"class":655},"80.9.0\n",[81,6442,6443,6445,6448],{"class":83,"line":1692},[81,6444,2013],{"class":87},[81,6446,6447],{"class":90}," sniffio==",[81,6449,6450],{"class":655},"1.3.1\n",[81,6452,6453,6455,6458],{"class":83,"line":1697},[81,6454,2013],{"class":87},[81,6456,6457],{"class":90}," tzdata==",[81,6459,6460],{"class":655},"2025.2\n",[14,6462,6463],{},"เพียงเท่านี้ก็จะสามารถใช้งานได้แล้วครับ",[938,6465,6466],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .snOyU, html code.shiki .snOyU{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sicAX, html code.shiki .sicAX{--shiki-light:#90A4AE;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}html pre.shiki code .sZDfs, html code.shiki .sZDfs{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}",{"title":31,"searchDepth":102,"depth":102,"links":6468},[6469,6470,6471],{"id":5863,"depth":102,"text":5863},{"id":6102,"depth":102,"text":6102},{"id":6140,"depth":102,"text":6140},"2025-11-03T00:00:00.000Z","แก้ปัญหา ModuleNotFoundError No module named pkg_resources ตอนติดตั้ง dirsearch ด้วย uv tool install",{},"Dirsearch with UV","/blogs/2025-11-03-dirsearch-uv",{"title":5858,"description":6473},{"loc":6476},"blogs/2025-11-03-dirsearch-uv",[968],"cD9RYrxcIqDr0EpCWp6ln9YIzYZsSjIpxwPeFvwVwPc",{"id":6483,"title":6484,"body":6485,"date":6765,"description":6766,"draft":956,"extension":957,"meta":6767,"navigation":365,"next":959,"ogTitle":6768,"path":6769,"pin":956,"prev":959,"recommends":959,"seo":6770,"sitemap":6771,"stem":6772,"tags":6773,"updated":2527,"__hash__":6774},"content/blogs/2025-11-06-environment-mac-keychains.md","วิธีเก็บ Environment บน Mac Keychains แบบปลอดภัยมากขึ้น",{"type":7,"value":6486,"toc":6757},[6487,6490,6493,6529,6534,6537,6540,6570,6574,6577,6619,6630,6633,6651,6654,6657,6673,6680,6712,6715,6743,6747,6754],[14,6488,6489],{},"โดยวิธีการนี้ค่าจะถูกนำไปเก็บเอาไว้ใน keychains ของ macos ซึ่งจะปลอดภัยกว่าการเก็บไว้ใน .zshrc, .bashrc file โดยตรง",[10,6491,6492],{"id":6492},"วิธีการเก็บค่า",[23,6494,6496],{"className":75,"code":6495,"language":77,"meta":31,"style":31},"security add-generic-password -a \"$USER\" -s 'name_of_your_key' -w\n",[18,6497,6498],{"__ignoreMap":31},[81,6499,6500,6503,6506,6509,6511,6514,6516,6519,6521,6524,6526],{"class":83,"line":84},[81,6501,6502],{"class":87},"security",[81,6504,6505],{"class":90}," add-generic-password",[81,6507,6508],{"class":94}," -a",[81,6510,641],{"class":128},[81,6512,6513],{"class":1624},"$USER",[81,6515,1255],{"class":128},[81,6517,6518],{"class":94}," -s",[81,6520,129],{"class":128},[81,6522,6523],{"class":90},"name_of_your_key",[81,6525,3020],{"class":128},[81,6527,6528],{"class":94}," -w\n",[2671,6530,6531],{},[14,6532,6533],{},"name_of_your_key ตั้งเป็น key ตามที่ต้องการจะเก็บ",[14,6535,6536],{},"จากนั้น application จะมีการแสดง Prompt `password data for new item:` โดยให้ใส่ value ของ key ที่ต้องการเก็บได้เลย",[10,6538,6539],{"id":6539},"วิธีการดึงค่าออกมา",[23,6541,6543],{"className":75,"code":6542,"language":77,"meta":31,"style":31},"security find-generic-password -a \"$USER\" -s 'name_of_your_key' -w\n",[18,6544,6545],{"__ignoreMap":31},[81,6546,6547,6549,6552,6554,6556,6558,6560,6562,6564,6566,6568],{"class":83,"line":84},[81,6548,6502],{"class":87},[81,6550,6551],{"class":90}," find-generic-password",[81,6553,6508],{"class":94},[81,6555,641],{"class":128},[81,6557,6513],{"class":1624},[81,6559,1255],{"class":128},[81,6561,6518],{"class":94},[81,6563,129],{"class":128},[81,6565,6523],{"class":90},[81,6567,3020],{"class":128},[81,6569,6528],{"class":94},[10,6571,6573],{"id":6572},"ให้-shell-load-ค่าให้อัตโนมัติ","ให้ shell load ค่าให้อัตโนมัติ",[14,6575,6576],{},"หากต้องการให้ default shell ของเรา load ค่าจาก envronment ดังกล่าวออกมา สามารถนำ",[23,6578,6580],{"className":75,"code":6579,"language":77,"meta":31,"style":31},"export NAME_OF_YOUR_KEY=$(security find-generic-password -a \"$USER\" -s \"name_of_your_key\" -w)\n",[18,6581,6582],{"__ignoreMap":31},[81,6583,6584,6586,6589,6591,6594,6596,6598,6600,6602,6604,6606,6608,6610,6612,6614,6617],{"class":83,"line":84},[81,6585,1522],{"class":1364},[81,6587,6588],{"class":1624}," NAME_OF_YOUR_KEY",[81,6590,3131],{"class":1598},[81,6592,6593],{"class":628},"$(",[81,6595,6502],{"class":87},[81,6597,6551],{"class":90},[81,6599,6508],{"class":94},[81,6601,641],{"class":128},[81,6603,6513],{"class":1624},[81,6605,1255],{"class":128},[81,6607,6518],{"class":94},[81,6609,641],{"class":128},[81,6611,6523],{"class":90},[81,6613,1255],{"class":128},[81,6615,6616],{"class":94}," -w",[81,6618,1730],{"class":628},[14,6620,6621,6622,6625,6626,6629],{},"ไปใส่ใน default shell เช่น ",[18,6623,6624],{},"~/.zshrc",", ",[18,6627,6628],{},"~/.bashrc"," แล้ว reload ได้เลย ค่าดังกล่าวก็จะถูกดึงออกมาโดยอัตโนมัติ",[14,6631,6632],{},"วิธีการตรวจสอบว่าถูก Load เข้าไปไหม",[23,6634,6636],{"className":75,"code":6635,"language":77,"meta":31,"style":31},"printenv NAME_OF_YOUR_KEY\n# ถ้าถูกตรงนี้จะขึ้น Value มา\n",[18,6637,6638,6646],{"__ignoreMap":31},[81,6639,6640,6643],{"class":83,"line":84},[81,6641,6642],{"class":87},"printenv",[81,6644,6645],{"class":90}," NAME_OF_YOUR_KEY\n",[81,6647,6648],{"class":83,"line":102},[81,6649,6650],{"class":1064},"# ถ้าถูกตรงนี้จะขึ้น Value มา\n",[10,6652,6653],{"id":6653},"แก้ค่าเดิม",[14,6655,6656],{},"อันนี้เจอบ่อยหลังใช้ไปสักพักนะ พอ key หมดอายุแล้วจะเอาค่าใหม่ใส่ทับ ยิงคำสั่งเดิมซ้ำจะโดนด่าแบบนี้",[23,6658,6660],{"className":75,"code":6659,"language":77,"meta":31,"style":31},"security: SecKeychainItemCreateFromContent (\u003Cdefault>): The specified item already exists in the keychain.\n",[18,6661,6662],{"__ignoreMap":31},[81,6663,6664,6667,6670],{"class":83,"line":84},[81,6665,6666],{"class":87},"security:",[81,6668,6669],{"class":90}," SecKeychainItemCreateFromContent",[81,6671,6672],{"class":1449}," (\u003Cdefault>): The specified item already exists in the keychain.\n",[14,6674,6675,6676,6679],{},"ให้ใส่ ",[18,6677,6678],{},"-U"," เพิ่มเข้าไป มันจะ update ทับให้เลยถ้ามีอยู่แล้ว",[23,6681,6683],{"className":75,"code":6682,"language":77,"meta":31,"style":31},"security add-generic-password -U -a \"$USER\" -s 'name_of_your_key' -w\n",[18,6684,6685],{"__ignoreMap":31},[81,6686,6687,6689,6691,6694,6696,6698,6700,6702,6704,6706,6708,6710],{"class":83,"line":84},[81,6688,6502],{"class":87},[81,6690,6505],{"class":90},[81,6692,6693],{"class":94}," -U",[81,6695,6508],{"class":94},[81,6697,641],{"class":128},[81,6699,6513],{"class":1624},[81,6701,1255],{"class":128},[81,6703,6518],{"class":94},[81,6705,129],{"class":128},[81,6707,6523],{"class":90},[81,6709,3020],{"class":128},[81,6711,6528],{"class":94},[10,6713,6714],{"id":6714},"ลบทิ้ง",[23,6716,6718],{"className":75,"code":6717,"language":77,"meta":31,"style":31},"security delete-generic-password -a \"$USER\" -s 'name_of_your_key'\n",[18,6719,6720],{"__ignoreMap":31},[81,6721,6722,6724,6727,6729,6731,6733,6735,6737,6739,6741],{"class":83,"line":84},[81,6723,6502],{"class":87},[81,6725,6726],{"class":90}," delete-generic-password",[81,6728,6508],{"class":94},[81,6730,641],{"class":128},[81,6732,6513],{"class":1624},[81,6734,1255],{"class":128},[81,6736,6518],{"class":94},[81,6738,129],{"class":128},[81,6740,6523],{"class":90},[81,6742,135],{"class":128},[10,6744,6746],{"id":6745},"ref","Ref",[904,6748,6749],{},[907,6750,6751],{},[18,6752,6753],{},"man security",[938,6755,6756],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sLHrO, html code.shiki .sLHrO{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOHk9, html code.shiki .sOHk9{--shiki-light:#9C3EDA;--shiki-default:#C678DD;--shiki-dark:#F97583}html pre.shiki code .sMTEB, html code.shiki .sMTEB{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F97583}html pre.shiki code .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sZDfs, html code.shiki .sZDfs{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}",{"title":31,"searchDepth":102,"depth":102,"links":6758},[6759,6760,6761,6762,6763,6764],{"id":6492,"depth":102,"text":6492},{"id":6539,"depth":102,"text":6539},{"id":6572,"depth":102,"text":6573},{"id":6653,"depth":102,"text":6653},{"id":6714,"depth":102,"text":6714},{"id":6745,"depth":102,"text":6746},"2025-11-06T00:00:00.000Z","เก็บ environment ที่เป็นความลับไว้ใน keychains ของ macos แทนการ hardcode ลง .zshrc ด้วยคำสั่ง security",{},"MacOS Environment Settings","/blogs/2025-11-06-environment-mac-keychains",{"title":6484,"description":6766},{"loc":6769},"blogs/2025-11-06-environment-mac-keychains",[968],"iCvOsRRzu6xRpk_G4hP0KXpaNTct4pacu3fgiuzW3zc",{"id":6776,"title":6777,"body":6778,"date":6918,"description":6919,"draft":956,"extension":957,"meta":6920,"navigation":365,"next":959,"ogTitle":6921,"path":6922,"pin":956,"prev":959,"recommends":959,"seo":6923,"sitemap":6924,"stem":6925,"tags":6926,"updated":959,"__hash__":6927},"content/blogs/2025-12-28-powerlevel10k-spaceship.md","ย้ายจาก Powerlevel10k มาใช้ Spaceship",{"type":7,"value":6779,"toc":6915},[6780,6788,6791,6793,6860,6863,6896,6909,6912],[14,6781,6782,6783,6787],{},"ช่วงหลัง ๆ มาผมรู้สึกว่าเริ่มเบื่อกับ Terminal ที่กำลังใช้อยู่ (zsh+oh-my-zsh+powerlevel10k) เลยลองหา ๆ ดูว่ามีทางเลือกอื่นให้ติดตั้งอีกมั้ย แล้วก็ได้มาเจอกับ ",[910,6784,6785],{"href":6785,"rel":6786},"https://spaceship-prompt.sh/",[914]," ซึ่งสามารถลงเป็น add-on บน zsh ได้เลย (แทนที่ powerlevel10k)",[14,6789,6790],{},"จากที่ได้ลองใช้มาสักพักนึงรู้สึกลื่นนนกว่ามาก",[10,6792,2812],{"id":2812},[23,6794,6796],{"className":75,"code":6795,"language":77,"meta":31,"style":31},"# Clone the repo\ngit clone https://github.com/spaceship-prompt/spaceship-prompt.git \"$ZSH_CUSTOM/themes/spaceship-prompt\" --depth=1\n\n# Link the theme\nln -s \"$ZSH_CUSTOM/themes/spaceship-prompt/spaceship.zsh-theme\" \"$ZSH_CUSTOM/themes/spaceship.zsh-theme\"\n",[18,6797,6798,6803,6826,6830,6835],{"__ignoreMap":31},[81,6799,6800],{"class":83,"line":84},[81,6801,6802],{"class":1064},"# Clone the repo\n",[81,6804,6805,6807,6810,6813,6815,6818,6821,6823],{"class":83,"line":102},[81,6806,2697],{"class":87},[81,6808,6809],{"class":90}," clone",[81,6811,6812],{"class":90}," https://github.com/spaceship-prompt/spaceship-prompt.git",[81,6814,641],{"class":128},[81,6816,6817],{"class":1624},"$ZSH_CUSTOM",[81,6819,6820],{"class":90},"/themes/spaceship-prompt",[81,6822,1255],{"class":128},[81,6824,6825],{"class":94}," --depth=1\n",[81,6827,6828],{"class":83,"line":113},[81,6829,366],{"emptyLinePlaceholder":365},[81,6831,6832],{"class":83,"line":247},[81,6833,6834],{"class":1064},"# Link the theme\n",[81,6836,6837,6840,6842,6844,6846,6849,6851,6853,6855,6858],{"class":83,"line":393},[81,6838,6839],{"class":87},"ln",[81,6841,6518],{"class":94},[81,6843,641],{"class":128},[81,6845,6817],{"class":1624},[81,6847,6848],{"class":90},"/themes/spaceship-prompt/spaceship.zsh-theme",[81,6850,1255],{"class":128},[81,6852,641],{"class":128},[81,6854,6817],{"class":1624},[81,6856,6857],{"class":90},"/themes/spaceship.zsh-theme",[81,6859,647],{"class":128},[14,6861,6862],{},"จากนั้นสามารถแก้ไข",[23,6864,6866],{"className":75,"code":6865,"language":77,"meta":31,"style":31},"# .zshrc แก้ไข theme ที่ใช้อยู่\n\n#ZSH_THEME=\"powerlevel10k/powerlevel10k\"\nZSH_THEME=\"spaceship\"\n",[18,6867,6868,6873,6877,6882],{"__ignoreMap":31},[81,6869,6870],{"class":83,"line":84},[81,6871,6872],{"class":1064},"# .zshrc แก้ไข theme ที่ใช้อยู่\n",[81,6874,6875],{"class":83,"line":102},[81,6876,366],{"emptyLinePlaceholder":365},[81,6878,6879],{"class":83,"line":113},[81,6880,6881],{"class":1064},"#ZSH_THEME=\"powerlevel10k/powerlevel10k\"\n",[81,6883,6884,6887,6889,6891,6894],{"class":83,"line":247},[81,6885,6886],{"class":1624},"ZSH_THEME",[81,6888,3131],{"class":1598},[81,6890,1255],{"class":128},[81,6892,6893],{"class":90},"spaceship",[81,6895,647],{"class":128},[23,6897,6899],{"className":75,"code":6898,"language":77,"meta":31,"style":31},"source ~/.zshrc\n",[18,6900,6901],{"__ignoreMap":31},[81,6902,6903,6906],{"class":83,"line":84},[81,6904,6905],{"class":637},"source",[81,6907,6908],{"class":90}," ~/.zshrc\n",[14,6910,6911],{},"เรียบร้อยฮะ",[938,6913,6914],{},"html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sLHrO, html code.shiki .sLHrO{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMTEB, html code.shiki .sMTEB{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F97583}html pre.shiki code .sJZfL, html code.shiki .sJZfL{--shiki-light:#6182B8;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}",{"title":31,"searchDepth":102,"depth":102,"links":6916},[6917],{"id":2812,"depth":102,"text":2812},"2025-12-28T00:00:00.000Z","วิธีติดตั้ง Spaceship prompt บน macos สำหรับคนที่เบื่อ Powerlevel10k แล้วอยากลองของใหม่",{},"Moving from Powerlevel10k to Spaceship","/blogs/2025-12-28-powerlevel10k-spaceship",{"title":6777,"description":6919},{"loc":6922},"blogs/2025-12-28-powerlevel10k-spaceship",[968],"lKSjY66MyYKLVkiN-l4R5UqNSZcvVoeEbS0MIsHLslA",{"id":6929,"title":6930,"body":6931,"date":6918,"description":7223,"draft":956,"extension":957,"meta":7224,"navigation":365,"next":959,"ogTitle":7225,"path":7226,"pin":956,"prev":959,"recommends":959,"seo":7227,"sitemap":7228,"stem":7229,"tags":7230,"updated":2527,"__hash__":7231},"content/blogs/2025-12-28-zshrc-config-folder.md","ย้าย .zshrc ไปยัง .config folder",{"type":7,"value":6932,"toc":7218},[6933,6936,6939,6990,6993,7029,7032,7036,7039,7067,7070,7074,7077,7084,7099,7102,7146,7149,7161,7164,7170,7209,7215],[10,6934,6935],{"id":6935},"วิธีการตั้งค่า",[14,6937,6938],{},"Note วิธีย้าย .zshrc เข้าไปยัง .config folder บน macos เพื่อให้ง่ายต่อการ backup",[23,6940,6942],{"className":75,"code":6941,"language":77,"meta":31,"style":31},"# Create new folder\nmkdir -p ~/.config/zsh\n\n# Move config\nmv ~/.zshrc ~/.config/zsh/.zshrc\nmv ~/.zsh_history ~/.config/zsh/.zsh_history\n",[18,6943,6944,6949,6960,6964,6969,6980],{"__ignoreMap":31},[81,6945,6946],{"class":83,"line":84},[81,6947,6948],{"class":1064},"# Create new folder\n",[81,6950,6951,6954,6957],{"class":83,"line":102},[81,6952,6953],{"class":87},"mkdir",[81,6955,6956],{"class":94}," -p",[81,6958,6959],{"class":90}," ~/.config/zsh\n",[81,6961,6962],{"class":83,"line":113},[81,6963,366],{"emptyLinePlaceholder":365},[81,6965,6966],{"class":83,"line":247},[81,6967,6968],{"class":1064},"# Move config\n",[81,6970,6971,6974,6977],{"class":83,"line":393},[81,6972,6973],{"class":87},"mv",[81,6975,6976],{"class":90}," ~/.zshrc",[81,6978,6979],{"class":90}," ~/.config/zsh/.zshrc\n",[81,6981,6982,6984,6987],{"class":83,"line":404},[81,6983,6973],{"class":87},[81,6985,6986],{"class":90}," ~/.zsh_history",[81,6988,6989],{"class":90}," ~/.config/zsh/.zsh_history\n",[14,6991,6992],{},"จากนั้นแก้ไขไฟล์ .zshenv เพื่อเปลี่ยน working directory",[23,6994,6996],{"className":75,"code":6995,"language":77,"meta":31,"style":31},"nano ~/.zshenv\n\nexport ZDOTDIR=\"$HOME/.config/zsh\"\n",[18,6997,6998,7006,7010],{"__ignoreMap":31},[81,6999,7000,7003],{"class":83,"line":84},[81,7001,7002],{"class":87},"nano",[81,7004,7005],{"class":90}," ~/.zshenv\n",[81,7007,7008],{"class":83,"line":102},[81,7009,366],{"emptyLinePlaceholder":365},[81,7011,7012,7014,7017,7019,7021,7024,7027],{"class":83,"line":113},[81,7013,1522],{"class":1364},[81,7015,7016],{"class":1624}," ZDOTDIR",[81,7018,3131],{"class":1598},[81,7020,1255],{"class":128},[81,7022,7023],{"class":1624},"$HOME",[81,7025,7026],{"class":90},"/.config/zsh",[81,7028,647],{"class":128},[14,7030,7031],{},"ลองปิดเปิดใหม่เพื่อทดสอบว่า config ถูก load ถูกต้องมั้ย (ปิด terminal ไปเลยนะครับ)",[10,7033,7035],{"id":7034},"ป้องกันไม่ให้เอา-zsh_history-ขึ้น-git","ป้องกันไม่ให้เอา .zsh_history ขึ้น git",[14,7037,7038],{},"แนะนำให้สร้าง .gitignore แล้วเพิ่ม",[23,7040,7042],{"className":75,"code":7041,"language":77,"meta":31,"style":31},"# Ignore everything in zsh...\nzsh/*\n# ...Except .zshrc\n!zsh/.zshrc\n",[18,7043,7044,7049,7054,7059],{"__ignoreMap":31},[81,7045,7046],{"class":83,"line":84},[81,7047,7048],{"class":1064},"# Ignore everything in zsh...\n",[81,7050,7051],{"class":83,"line":102},[81,7052,7053],{"class":87},"zsh/*\n",[81,7055,7056],{"class":83,"line":113},[81,7057,7058],{"class":1064},"# ...Except .zshrc\n",[81,7060,7061,7064],{"class":83,"line":247},[81,7062,7063],{"class":618},"!",[81,7065,7066],{"class":87},"zsh/.zshrc\n",[14,7068,7069],{},"เพื่อให้ backup เฉพาะส่วนที่เป็น config ไว้เท่านั้นครับ เนื่องจากใน .zsh_history อาจจะมี secret ต่าง ๆ อยู่ได้",[10,7071,7073],{"id":7072},"ใช้งานร่วมกับ-gitleak-pre-commit","ใช้งานร่วมกับ gitleak + pre-commit",[14,7075,7076],{},"เพื่อเพิ่มความปลอดภัยอีกชั้น แนะนำให้ติด pre-commit hook เพื่อดักจับ secret (เช่น API Key, Token ต่าง ๆ) ก่อนที่เราจะเผลอ commit ลงไปใน git ครับ",[14,7078,7079,7080,7083],{},"เริ่มจากติดตั้ง ",[18,7081,7082],{},"pre-commit"," (หากยังไม่มี)",[23,7085,7087],{"className":75,"code":7086,"language":77,"meta":31,"style":31},"brew install pre-commit\n",[18,7088,7089],{"__ignoreMap":31},[81,7090,7091,7094,7096],{"class":83,"line":84},[81,7092,7093],{"class":87},"brew",[81,7095,2829],{"class":90},[81,7097,7098],{"class":90}," pre-commit\n",[14,7100,7101],{},"จากนั้นสร้างไฟล์ .pre-commit-config.yaml ไว้ที่ root directory ของโปรเจกต์ และกำหนด version ของ gitleaks เป็น v8.30.0",[23,7103,7106],{"className":75,"code":7104,"filename":7105,"language":77,"meta":31,"style":31},"repos:\n  - repo: https://github.com/gitleaks/gitleaks\n    rev: v8.30.0\n    hooks:\n      - id: gitleaks\n",".pre-commit-config.yaml",[18,7107,7108,7113,7123,7131,7136],{"__ignoreMap":31},[81,7109,7110],{"class":83,"line":84},[81,7111,7112],{"class":87},"repos:\n",[81,7114,7115,7117,7120],{"class":83,"line":102},[81,7116,4884],{"class":87},[81,7118,7119],{"class":90}," repo:",[81,7121,7122],{"class":90}," https://github.com/gitleaks/gitleaks\n",[81,7124,7125,7128],{"class":83,"line":113},[81,7126,7127],{"class":87},"    rev:",[81,7129,7130],{"class":90}," v8.30.0\n",[81,7132,7133],{"class":83,"line":247},[81,7134,7135],{"class":87},"    hooks:\n",[81,7137,7138,7140,7143],{"class":83,"line":393},[81,7139,5011],{"class":87},[81,7141,7142],{"class":90}," id:",[81,7144,7145],{"class":90}," gitleaks\n",[14,7147,7148],{},"สั่ง install hook เข้าไปใน git directory",[23,7150,7152],{"className":75,"code":7151,"language":77,"meta":31,"style":31},"pre-commit install\n",[18,7153,7154],{"__ignoreMap":31},[81,7155,7156,7158],{"class":83,"line":84},[81,7157,7082],{"class":87},[81,7159,7160],{"class":90}," install\n",[14,7162,7163],{},"ผลลัพธ์: หลังจากนี้ทุกครั้งที่เราพิมพ์คำสั่ง git commit ตัว pre-commit จะทำการเรียก Gitleaks มาสแกนไฟล์ที่เรากำลังจะ commit ให้โดยอัตโนมัติ ถ้าเจอ secret มันจะแจ้ง error และหยุดการ commit ให้ทันทีครับ",[14,7165,7166,7169],{},[1005,7167,7168],{},"เพิ่มเติม (Optional)","\nถ้าอยากให้บทความสมบูรณ์ขึ้น อาจจะปิดท้ายด้วยคำสั่งสำหรับ Test ดูครับ ทดสอบการทำงาน ลองสร้างไฟล์ที่มี Mock Secret ดูครับ",[23,7171,7173],{"className":75,"code":7172,"language":77,"meta":31,"style":31},"wget https://raw.githubusercontent.com/trufflesecurity/test_keys/refs/heads/main/keys\ngit add keys\ngit commit -m \"test secret\"\n",[18,7174,7175,7183,7192],{"__ignoreMap":31},[81,7176,7177,7180],{"class":83,"line":84},[81,7178,7179],{"class":87},"wget",[81,7181,7182],{"class":90}," https://raw.githubusercontent.com/trufflesecurity/test_keys/refs/heads/main/keys\n",[81,7184,7185,7187,7189],{"class":83,"line":102},[81,7186,2697],{"class":87},[81,7188,2854],{"class":90},[81,7190,7191],{"class":90}," keys\n",[81,7193,7194,7196,7199,7202,7204,7207],{"class":83,"line":113},[81,7195,2697],{"class":87},[81,7197,7198],{"class":90}," commit",[81,7200,7201],{"class":94}," -m",[81,7203,641],{"class":128},[81,7205,7206],{"class":90},"test secret",[81,7208,647],{"class":128},[14,7210,7211],{},[979,7212],{"alt":7213,"src":7214},"ตัวอย่าง","/images/2025-12-28-zshrc-config-folder/gitleak-demo.jpg",[938,7216,7217],{},"html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOHk9, html code.shiki .sOHk9{--shiki-light:#9C3EDA;--shiki-default:#C678DD;--shiki-dark:#F97583}html pre.shiki code .sLHrO, html code.shiki .sLHrO{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}html pre.shiki code .sMTEB, html code.shiki .sMTEB{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F97583}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}",{"title":31,"searchDepth":102,"depth":102,"links":7219},[7220,7221,7222],{"id":6935,"depth":102,"text":6935},{"id":7034,"depth":102,"text":7035},{"id":7072,"depth":102,"text":7073},"วิธีย้าย .zshrc ไปไว้ใน ~/.config/zsh บน macos เพื่อให้ backup ขึ้น git ได้ง่าย พร้อมกัน .zsh_history หลุด",{},"Moving the .zshrc to .config folder","/blogs/2025-12-28-zshrc-config-folder",{"title":6930,"description":7223},{"loc":7226},"blogs/2025-12-28-zshrc-config-folder",[968],"acECMUChP_Z6AocZKhSlxL9b5rk5gfknM_EGWLR_Q8g",{"id":7233,"title":7234,"body":7235,"date":7527,"description":7528,"draft":956,"extension":957,"meta":7529,"navigation":365,"next":959,"ogTitle":7530,"path":7531,"pin":956,"prev":959,"recommends":959,"seo":7532,"sitemap":7533,"stem":7534,"tags":7535,"updated":2527,"__hash__":7536},"content/blogs/2026-01-02-cli-macos-sips.md","ย่อรูปผ่าน CLI บน MacOS ด้วย sips",{"type":7,"value":7236,"toc":7521},[7237,7243,7346,7349,7352,7372,7375,7392,7395,7398,7451,7458,7480,7484,7487,7509,7511,7518],[14,7238,7239,7242],{},[18,7240,7241],{},"sips"," ติดมากับ macos อยู่แล้วครับ ย่อรูปหรือแปลงไฟล์ได้เลยโดยไม่ต้องลงอะไรเพิ่ม",[23,7244,7246],{"className":75,"code":7245,"language":77,"meta":31,"style":31},"sips - scriptable image processing system.\nThis tool is used to query or modify raster image files and ColorSync ICC profiles.\nIts functionality can also be used through the \"Image Events\" AppleScript suite.\n",[18,7247,7248,7266,7309],{"__ignoreMap":31},[81,7249,7250,7252,7254,7257,7260,7263],{"class":83,"line":84},[81,7251,7241],{"class":87},[81,7253,1754],{"class":90},[81,7255,7256],{"class":90}," scriptable",[81,7258,7259],{"class":90}," image",[81,7261,7262],{"class":90}," processing",[81,7264,7265],{"class":90}," system.\n",[81,7267,7268,7271,7273,7276,7279,7281,7284,7287,7290,7293,7295,7298,7300,7303,7306],{"class":83,"line":102},[81,7269,7270],{"class":87},"This",[81,7272,5879],{"class":90},[81,7274,7275],{"class":90}," is",[81,7277,7278],{"class":90}," used",[81,7280,3605],{"class":90},[81,7282,7283],{"class":90}," query",[81,7285,7286],{"class":90}," or",[81,7288,7289],{"class":90}," modify",[81,7291,7292],{"class":90}," raster",[81,7294,7259],{"class":90},[81,7296,7297],{"class":90}," files",[81,7299,3742],{"class":90},[81,7301,7302],{"class":90}," ColorSync",[81,7304,7305],{"class":90}," ICC",[81,7307,7308],{"class":90}," profiles.\n",[81,7310,7311,7314,7317,7320,7323,7326,7328,7331,7333,7335,7338,7340,7343],{"class":83,"line":113},[81,7312,7313],{"class":87},"Its",[81,7315,7316],{"class":90}," functionality",[81,7318,7319],{"class":90}," can",[81,7321,7322],{"class":90}," also",[81,7324,7325],{"class":90}," be",[81,7327,7278],{"class":90},[81,7329,7330],{"class":90}," through",[81,7332,3677],{"class":90},[81,7334,641],{"class":128},[81,7336,7337],{"class":90},"Image Events",[81,7339,1255],{"class":128},[81,7341,7342],{"class":90}," AppleScript",[81,7344,7345],{"class":90}," suite.\n",[10,7347,7348],{"id":7348},"วิธีใช้งาน",[14,7350,7351],{},"ลดคุณภาพ jpeg (ตัวอย่างคือเหลือ 70%)",[23,7353,7355],{"className":75,"code":7354,"language":77,"meta":31,"style":31},"sips -s formatOptions 70 input.jpg\n",[18,7356,7357],{"__ignoreMap":31},[81,7358,7359,7361,7363,7366,7369],{"class":83,"line":84},[81,7360,7241],{"class":87},[81,7362,6518],{"class":94},[81,7364,7365],{"class":90}," formatOptions",[81,7367,7368],{"class":655}," 70",[81,7370,7371],{"class":90}," input.jpg\n",[14,7373,7374],{},"ย่อขนาดรูปโดยคงสัดส่วนเดิม ด้านที่ยาวสุดไม่เกิน 1024px",[23,7376,7378],{"className":75,"code":7377,"language":77,"meta":31,"style":31},"sips -Z 1024 input.jpg\n",[18,7379,7380],{"__ignoreMap":31},[81,7381,7382,7384,7387,7390],{"class":83,"line":84},[81,7383,7241],{"class":87},[81,7385,7386],{"class":94}," -Z",[81,7388,7389],{"class":655}," 1024",[81,7391,7371],{"class":90},[10,7393,7394],{"id":7394},"ระวังไฟล์ต้นฉบับหาย",[14,7396,7397],{},"สองคำสั่งข้างบนมันเขียนทับไฟล์เดิมเลยนะ ไม่ได้สร้างไฟล์ใหม่ให้ ลองเช็คด้วย md5 ดูได้",[23,7399,7401],{"className":75,"code":7400,"language":77,"meta":31,"style":31},"md5 -q photo.png\n# b2f2ab5ecf28186e34c6a2aecb315a48\n\nsips -Z 200 photo.png\n\nmd5 -q photo.png\n# 8461c2903f0af97517d9442e7d83917f   \u003C- ไฟล์เดิมโดนทับไปแล้ว\n",[18,7402,7403,7414,7419,7423,7434,7438,7446],{"__ignoreMap":31},[81,7404,7405,7408,7411],{"class":83,"line":84},[81,7406,7407],{"class":87},"md5",[81,7409,7410],{"class":94}," -q",[81,7412,7413],{"class":90}," photo.png\n",[81,7415,7416],{"class":83,"line":102},[81,7417,7418],{"class":1064},"# b2f2ab5ecf28186e34c6a2aecb315a48\n",[81,7420,7421],{"class":83,"line":113},[81,7422,366],{"emptyLinePlaceholder":365},[81,7424,7425,7427,7429,7432],{"class":83,"line":247},[81,7426,7241],{"class":87},[81,7428,7386],{"class":94},[81,7430,7431],{"class":655}," 200",[81,7433,7413],{"class":90},[81,7435,7436],{"class":83,"line":393},[81,7437,366],{"emptyLinePlaceholder":365},[81,7439,7440,7442,7444],{"class":83,"line":404},[81,7441,7407],{"class":87},[81,7443,7410],{"class":94},[81,7445,7413],{"class":90},[81,7447,7448],{"class":83,"line":415},[81,7449,7450],{"class":1064},"# 8461c2903f0af97517d9442e7d83917f   \u003C- ไฟล์เดิมโดนทับไปแล้ว\n",[14,7452,7453,7454,7457],{},"ถ้าอยากเก็บต้นฉบับไว้ให้ใส่ ",[18,7455,7456],{},"--out"," ทุกครั้งครับ",[23,7459,7461],{"className":75,"code":7460,"language":77,"meta":31,"style":31},"sips -Z 1024 photo.png --out photo-small.png\n",[18,7462,7463],{"__ignoreMap":31},[81,7464,7465,7467,7469,7471,7474,7477],{"class":83,"line":84},[81,7466,7241],{"class":87},[81,7468,7386],{"class":94},[81,7470,7389],{"class":655},[81,7472,7473],{"class":90}," photo.png",[81,7475,7476],{"class":94}," --out",[81,7478,7479],{"class":90}," photo-small.png\n",[10,7481,7483],{"id":7482},"ดูขนาดรูปเฉย-ๆ","ดูขนาดรูปเฉย ๆ",[14,7485,7486],{},"อันนี้ไม่แตะไฟล์ ปลอดภัย",[23,7488,7490],{"className":75,"code":7489,"language":77,"meta":31,"style":31},"sips -g pixelWidth -g pixelHeight photo.png\n",[18,7491,7492],{"__ignoreMap":31},[81,7493,7494,7496,7499,7502,7504,7507],{"class":83,"line":84},[81,7495,7241],{"class":87},[81,7497,7498],{"class":94}," -g",[81,7500,7501],{"class":90}," pixelWidth",[81,7503,7498],{"class":94},[81,7505,7506],{"class":90}," pixelHeight",[81,7508,7413],{"class":90},[10,7510,6746],{"id":6745},[904,7512,7513],{},[907,7514,7515],{},[18,7516,7517],{},"man sips",[938,7519,7520],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .snOyU, html code.shiki .snOyU{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}",{"title":31,"searchDepth":102,"depth":102,"links":7522},[7523,7524,7525,7526],{"id":7348,"depth":102,"text":7348},{"id":7394,"depth":102,"text":7394},{"id":7482,"depth":102,"text":7483},{"id":6745,"depth":102,"text":6746},"2026-01-02T00:00:00.000Z","วิธีย่อขนาดรูปและลดคุณภาพ jpeg บน macos ด้วยคำสั่ง sips ที่ติดมากับเครื่องอยู่แล้ว ไม่ต้องลงโปรแกรมเพิ่ม",{},"sips 101","/blogs/2026-01-02-cli-macos-sips",{"title":7234,"description":7528},{"loc":7531},"blogs/2026-01-02-cli-macos-sips",[968],"zFdTZnwPKp7hL338VFRR51aQoJ8K9zsnEgIwX6SKxSc",{"id":7538,"title":7539,"body":7540,"date":7644,"description":7645,"draft":956,"extension":957,"meta":7646,"navigation":365,"next":959,"ogTitle":7647,"path":7648,"pin":956,"prev":959,"recommends":959,"seo":7649,"sitemap":7650,"stem":7651,"tags":7652,"updated":2527,"__hash__":7653},"content/blogs/2026-01-17-zshhistory.md","วิธีแก้ไข zsh_history เสียหาย",{"type":7,"value":7541,"toc":7640},[7542,7544,7547,7569,7572,7574,7577,7580,7634,7637],[10,7543,5863],{"id":5863},[14,7545,7546],{},"สำหรับชาว Dev ที่ใช้ ZSH อาจจะเคยเจอเหตุการณ์เปิด Terminal ขึ้นมาแล้วเจอ Error แจ้งเตือนเกี่ยวกับ History file พังแบบนี้ครับ",[23,7548,7550],{"className":75,"code":7549,"language":77,"meta":31,"style":31},"zsh: corrupt history file /home/pi/.zsh_history\n",[18,7551,7552],{"__ignoreMap":31},[81,7553,7554,7557,7560,7563,7566],{"class":83,"line":84},[81,7555,7556],{"class":87},"zsh:",[81,7558,7559],{"class":90}," corrupt",[81,7561,7562],{"class":90}," history",[81,7564,7565],{"class":90}," file",[81,7567,7568],{"class":90}," /home/pi/.zsh_history\n",[14,7570,7571],{},"ซึ่งปัญหานี้จะทำให้เราไม่สามารถกดลูกศรขึ้น-ลง เพื่อเรียกดูคำสั่งเก่า ๆ ที่เคยพิมพ์ไปได้ ซึ่งสาเหตุมักเกิดจากเครื่องดับไปดื้อ ๆ หรือโปรแกรมปิดไม่สมบูรณ์ครับ",[10,7573,6140],{"id":6140},[14,7575,7576],{},"วิธีแก้นั้นง่ายมากครับ ไม่ต้องลบ History ทิ้งทั้งหมด หลักการคือเราจะย้ายไฟล์ที่เสียไปพักไว้ก่อน แล้วใช้คำสั่ง strings เพื่อดึงเฉพาะข้อความที่อ่านรู้เรื่องกลับมาใส่ไฟล์ใหม่ครับ",[14,7578,7579],{},"พิมพ์ตามนี้ได้เลย หรือจะก๊อปไปวางทีละบรรทัดก็ได้ครับ",[23,7581,7583],{"className":75,"code":7582,"language":77,"meta":31,"style":31},"cd ~\nmv .zsh_history .zsh_history_bad\nstrings .zsh_history_bad > .zsh_history\nfc -R .zsh_history\nrm ~/.zsh_history_bad\n",[18,7584,7585,7593,7603,7616,7626],{"__ignoreMap":31},[81,7586,7587,7590],{"class":83,"line":84},[81,7588,7589],{"class":637},"cd",[81,7591,7592],{"class":90}," ~\n",[81,7594,7595,7597,7600],{"class":83,"line":102},[81,7596,6973],{"class":87},[81,7598,7599],{"class":90}," .zsh_history",[81,7601,7602],{"class":90}," .zsh_history_bad\n",[81,7604,7605,7608,7611,7613],{"class":83,"line":113},[81,7606,7607],{"class":87},"strings",[81,7609,7610],{"class":90}," .zsh_history_bad",[81,7612,619],{"class":618},[81,7614,7615],{"class":90}," .zsh_history\n",[81,7617,7618,7621,7624],{"class":83,"line":247},[81,7619,7620],{"class":637},"fc",[81,7622,7623],{"class":94}," -R",[81,7625,7615],{"class":90},[81,7627,7628,7631],{"class":83,"line":393},[81,7629,7630],{"class":87},"rm",[81,7632,7633],{"class":90}," ~/.zsh_history_bad\n",[14,7635,7636],{},"เท่านี้ zsh_history ของเราก็จะกลับมาใช้งานได้ปกติ กดดูคำสั่งย้อนหลังได้เหมือนเดิมแล้วครับ!",[938,7638,7639],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJZfL, html code.shiki .sJZfL{--shiki-light:#6182B8;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}",{"title":31,"searchDepth":102,"depth":102,"links":7641},[7642,7643],{"id":5863,"depth":102,"text":5863},{"id":6140,"depth":102,"text":6140},"2026-01-17T00:00:00.000Z","วิธีแก้ error zsh corrupt history file ด้วยคำสั่ง strings โดยไม่ต้องลบ history เก่าทิ้งทั้งหมด",{},"How to fix a corrupt zsh history file","/blogs/2026-01-17-zshhistory",{"title":7539,"description":7645},{"loc":7648},"blogs/2026-01-17-zshhistory",[968],"2vuXK7tJZRC0YSxtlQCOoOLhrEaQRxrEFNJQMFFTBFo",{"id":7655,"title":7656,"body":7657,"date":8757,"description":8758,"draft":956,"extension":957,"meta":8759,"navigation":365,"next":959,"ogTitle":8760,"path":8761,"pin":956,"prev":959,"recommends":959,"seo":8762,"sitemap":8763,"stem":8764,"tags":8765,"updated":959,"__hash__":8767},"content/blogs/2026-03-30-how-to-use-uv.md","เริ่มต้นใช้งาน uv ตัวจัดการ Python แห่งยุค 2026",{"type":7,"value":7658,"toc":8730},[7659,7663,7674,7678,7682,7696,7700,7722,7725,7737,7740,7778,7782,7788,7792,7853,7857,7862,7922,7926,7929,7988,7991,8026,8029,8043,8047,8051,8072,8075,8081,8085,8195,8199,8254,8266,8270,8329,8333,8337,8345,8500,8514,8517,8521,8572,8576,8644,8648,8658,8706,8708,8711,8713,8727],[10,7660,7662],{"id":7661},"uv-คืออะไร","uv คืออะไร",[14,7664,7665,7667,7668,7673],{},[39,7666,5876],{}," เป็นเครื่องมือจัดการ Python แบบครบวงจรที่เขียนด้วย Rust ทำงานได้เร็วกว่า pip ถึง 10-100 เท่า ใช้แทน pip, pip-tools, pipx, poetry, pyenv ได้ในตัวเดียว พัฒนาโดยทีม ",[910,7669,7672],{"href":7670,"rel":7671},"https://astral.sh/",[914],"Astral"," ทีมเดียวกับที่ทำ Ruff (Python linter)",[10,7675,7677],{"id":7676},"_1-วิธีติดตั้ง-uv-บน-macos","1. วิธีติดตั้ง uv บน macOS",[580,7679,7681],{"id":7680},"ติดตั้งผ่าน-homebrew-แนะนำ","ติดตั้งผ่าน Homebrew (แนะนำ)",[23,7683,7685],{"className":75,"code":7684,"language":77,"meta":31,"style":31},"brew install uv\n",[18,7686,7687],{"__ignoreMap":31},[81,7688,7689,7691,7693],{"class":83,"line":84},[81,7690,7093],{"class":87},[81,7692,2829],{"class":90},[81,7694,7695],{"class":90}," uv\n",[580,7697,7699],{"id":7698},"ติดตั้งผ่าน-standalone-installer","ติดตั้งผ่าน Standalone Installer",[23,7701,7703],{"className":75,"code":7702,"language":77,"meta":31,"style":31},"curl -LsSf https://astral.sh/uv/install.sh | sh\n",[18,7704,7705],{"__ignoreMap":31},[81,7706,7707,7710,7713,7716,7719],{"class":83,"line":84},[81,7708,7709],{"class":87},"curl",[81,7711,7712],{"class":94}," -LsSf",[81,7714,7715],{"class":90}," https://astral.sh/uv/install.sh",[81,7717,7718],{"class":618}," |",[81,7720,7721],{"class":87}," sh\n",[14,7723,7724],{},"ตรวจสอบว่าติดตั้งสำเร็จด้วย",[23,7726,7728],{"className":75,"code":7727,"language":77,"meta":31,"style":31},"uv version\n",[18,7729,7730],{"__ignoreMap":31},[81,7731,7732,7734],{"class":83,"line":84},[81,7733,5876],{"class":87},[81,7735,7736],{"class":90}," version\n",[580,7738,7739],{"id":7739},"อัปเดตเวอร์ชัน",[23,7741,7743],{"className":75,"code":7742,"language":77,"meta":31,"style":31},"# ถ้าติดตั้งผ่าน Homebrew\nbrew upgrade uv\n\n# ถ้าติดตั้งผ่าน Standalone Installer\nuv self update\n",[18,7744,7745,7750,7759,7763,7768],{"__ignoreMap":31},[81,7746,7747],{"class":83,"line":84},[81,7748,7749],{"class":1064},"# ถ้าติดตั้งผ่าน Homebrew\n",[81,7751,7752,7754,7757],{"class":83,"line":102},[81,7753,7093],{"class":87},[81,7755,7756],{"class":90}," upgrade",[81,7758,7695],{"class":90},[81,7760,7761],{"class":83,"line":113},[81,7762,366],{"emptyLinePlaceholder":365},[81,7764,7765],{"class":83,"line":247},[81,7766,7767],{"class":1064},"# ถ้าติดตั้งผ่าน Standalone Installer\n",[81,7769,7770,7772,7775],{"class":83,"line":393},[81,7771,5876],{"class":87},[81,7773,7774],{"class":90}," self",[81,7776,7777],{"class":90}," update\n",[10,7779,7781],{"id":7780},"_2-รันเครื่องมือแบบง่าย-ๆ-ด้วย-uvx","2. รันเครื่องมือแบบง่าย ๆ ด้วย uvx",[14,7783,7784,7787],{},[18,7785,7786],{},"uvx"," คือคำสั่งสำหรับรันเครื่องมือ Python แบบไม่ต้องติดตั้งถาวร จะสร้าง virtual environment ชั่วคราวให้อัตโนมัติ ใช้เสร็จก็ทิ้งไป เหมาะมากสำหรับเครื่องมือที่ใช้เป็นครั้งคราว",[580,7789,7791],{"id":7790},"รัน-tool-แบบครั้งเดียว","รัน tool แบบครั้งเดียว",[23,7793,7795],{"className":75,"code":7794,"language":77,"meta":31,"style":31},"# รัน ruff (Python linter) โดยไม่ต้องติดตั้ง\nuvx ruff check .\n\n# รัน black (code formatter)\nuvx black my_script.py\n\n# รัน httpie\nuvx httpie https://api.github.com\n",[18,7796,7797,7802,7815,7819,7824,7834,7838,7843],{"__ignoreMap":31},[81,7798,7799],{"class":83,"line":84},[81,7800,7801],{"class":1064},"# รัน ruff (Python linter) โดยไม่ต้องติดตั้ง\n",[81,7803,7804,7806,7809,7812],{"class":83,"line":102},[81,7805,7786],{"class":87},[81,7807,7808],{"class":90}," ruff",[81,7810,7811],{"class":90}," check",[81,7813,7814],{"class":90}," .\n",[81,7816,7817],{"class":83,"line":113},[81,7818,366],{"emptyLinePlaceholder":365},[81,7820,7821],{"class":83,"line":247},[81,7822,7823],{"class":1064},"# รัน black (code formatter)\n",[81,7825,7826,7828,7831],{"class":83,"line":393},[81,7827,7786],{"class":87},[81,7829,7830],{"class":90}," black",[81,7832,7833],{"class":90}," my_script.py\n",[81,7835,7836],{"class":83,"line":404},[81,7837,366],{"emptyLinePlaceholder":365},[81,7839,7840],{"class":83,"line":415},[81,7841,7842],{"class":1064},"# รัน httpie\n",[81,7844,7845,7847,7850],{"class":83,"line":1438},[81,7846,7786],{"class":87},[81,7848,7849],{"class":90}," httpie",[81,7851,7852],{"class":90}," https://api.github.com\n",[580,7854,7856],{"id":7855},"ติดตั้ง-tool-แบบถาวร","ติดตั้ง tool แบบถาวร",[14,7858,7859,7860],{},"ถ้าเป็นเครื่องมือที่ใช้บ่อย ๆ ติดตั้งถาวรได้ด้วย ",[18,7861,6126],{},[23,7863,7865],{"className":75,"code":7864,"language":77,"meta":31,"style":31},"# ติดตั้ง ruff แบบถาวร\nuv tool install ruff\n\n# ติดตั้ง youtube-dl\nuv tool install yt-dlp\n\n# ติดตั้งจาก git repository\nuv tool install git+https://github.com/maurosoria/dirsearch\n",[18,7866,7867,7872,7883,7887,7892,7903,7907,7912],{"__ignoreMap":31},[81,7868,7869],{"class":83,"line":84},[81,7870,7871],{"class":1064},"# ติดตั้ง ruff แบบถาวร\n",[81,7873,7874,7876,7878,7880],{"class":83,"line":102},[81,7875,5876],{"class":87},[81,7877,5879],{"class":90},[81,7879,2829],{"class":90},[81,7881,7882],{"class":90}," ruff\n",[81,7884,7885],{"class":83,"line":113},[81,7886,366],{"emptyLinePlaceholder":365},[81,7888,7889],{"class":83,"line":247},[81,7890,7891],{"class":1064},"# ติดตั้ง youtube-dl\n",[81,7893,7894,7896,7898,7900],{"class":83,"line":393},[81,7895,5876],{"class":87},[81,7897,5879],{"class":90},[81,7899,2829],{"class":90},[81,7901,7902],{"class":90}," yt-dlp\n",[81,7904,7905],{"class":83,"line":404},[81,7906,366],{"emptyLinePlaceholder":365},[81,7908,7909],{"class":83,"line":415},[81,7910,7911],{"class":1064},"# ติดตั้งจาก git repository\n",[81,7913,7914,7916,7918,7920],{"class":83,"line":1438},[81,7915,5876],{"class":87},[81,7917,5879],{"class":90},[81,7919,2829],{"class":90},[81,7921,6169],{"class":90},[580,7923,7925],{"id":7924},"รัน-tool-แบบระบุเวอร์ชัน","รัน tool แบบระบุเวอร์ชัน",[14,7927,7928],{},"ระบุเวอร์ชันของ tool ที่ต้องการรันได้เลย เหมาะสำหรับกรณีที่ต้องใช้เวอร์ชันเฉพาะ หรืออยากทดสอบหลาย ๆ เวอร์ชัน",[23,7930,7932],{"className":75,"code":7931,"language":77,"meta":31,"style":31},"# รัน ruff เวอร์ชันที่ต้องการ\nuvx ruff@0.11.0 check .\n\n# รัน ruff เวอร์ชันล่าสุด\nuvx ruff@latest check .\n\n# รัน black เวอร์ชันเฉพาะ\nuvx black@24.4.2 my_script.py\n",[18,7933,7934,7939,7950,7954,7959,7970,7974,7979],{"__ignoreMap":31},[81,7935,7936],{"class":83,"line":84},[81,7937,7938],{"class":1064},"# รัน ruff เวอร์ชันที่ต้องการ\n",[81,7940,7941,7943,7946,7948],{"class":83,"line":102},[81,7942,7786],{"class":87},[81,7944,7945],{"class":90}," ruff@0.11.0",[81,7947,7811],{"class":90},[81,7949,7814],{"class":90},[81,7951,7952],{"class":83,"line":113},[81,7953,366],{"emptyLinePlaceholder":365},[81,7955,7956],{"class":83,"line":247},[81,7957,7958],{"class":1064},"# รัน ruff เวอร์ชันล่าสุด\n",[81,7960,7961,7963,7966,7968],{"class":83,"line":393},[81,7962,7786],{"class":87},[81,7964,7965],{"class":90}," ruff@latest",[81,7967,7811],{"class":90},[81,7969,7814],{"class":90},[81,7971,7972],{"class":83,"line":404},[81,7973,366],{"emptyLinePlaceholder":365},[81,7975,7976],{"class":83,"line":415},[81,7977,7978],{"class":1064},"# รัน black เวอร์ชันเฉพาะ\n",[81,7980,7981,7983,7986],{"class":83,"line":1438},[81,7982,7786],{"class":87},[81,7984,7985],{"class":90}," black@24.4.2",[81,7987,7833],{"class":90},[14,7989,7990],{},"ติดตั้งถาวรแบบระบุเวอร์ชันก็ทำได้เช่นกัน",[23,7992,7994],{"className":75,"code":7993,"language":77,"meta":31,"style":31},"uv tool install 'ruff==0.11.0'\nuv tool install 'black>=24.0,\u003C25.0'\n",[18,7995,7996,8011],{"__ignoreMap":31},[81,7997,7998,8000,8002,8004,8006,8009],{"class":83,"line":84},[81,7999,5876],{"class":87},[81,8001,5879],{"class":90},[81,8003,2829],{"class":90},[81,8005,129],{"class":128},[81,8007,8008],{"class":90},"ruff==0.11.0",[81,8010,135],{"class":128},[81,8012,8013,8015,8017,8019,8021,8024],{"class":83,"line":102},[81,8014,5876],{"class":87},[81,8016,5879],{"class":90},[81,8018,2829],{"class":90},[81,8020,129],{"class":128},[81,8022,8023],{"class":90},"black>=24.0,\u003C25.0",[81,8025,135],{"class":128},[14,8027,8028],{},"ดูรายการเครื่องมือที่ติดตั้งไว้",[23,8030,8032],{"className":75,"code":8031,"language":77,"meta":31,"style":31},"uv tool list\n",[18,8033,8034],{"__ignoreMap":31},[81,8035,8036,8038,8040],{"class":83,"line":84},[81,8037,5876],{"class":87},[81,8039,5879],{"class":90},[81,8041,8042],{"class":90}," list\n",[10,8044,8046],{"id":8045},"_3-ใช้งานกับ-python-project","3. ใช้งานกับ Python Project",[580,8048,8050],{"id":8049},"สร้าง-project-ใหม่","สร้าง Project ใหม่",[23,8052,8054],{"className":75,"code":8053,"language":77,"meta":31,"style":31},"uv init my-project\ncd my-project\n",[18,8055,8056,8066],{"__ignoreMap":31},[81,8057,8058,8060,8063],{"class":83,"line":84},[81,8059,5876],{"class":87},[81,8061,8062],{"class":90}," init",[81,8064,8065],{"class":90}," my-project\n",[81,8067,8068,8070],{"class":83,"line":102},[81,8069,7589],{"class":637},[81,8071,8065],{"class":90},[14,8073,8074],{},"uv จะสร้างไฟล์ให้อัตโนมัติ",[23,8076,8079],{"className":8077,"code":8078,"language":28},[26],"my-project/\n├── .python-version\n├── README.md\n├── main.py\n└── pyproject.toml\n",[18,8080,8078],{"__ignoreMap":31},[580,8082,8084],{"id":8083},"เพิ่มลบ-dependencies","เพิ่ม/ลบ Dependencies",[23,8086,8088],{"className":75,"code":8087,"language":77,"meta":31,"style":31},"# เพิ่ม package\nuv add requests\nuv add flask sqlalchemy\n\n# เพิ่มแบบระบุเวอร์ชัน\nuv add 'requests==2.32.0'\n\n# เพิ่มจาก git\nuv add git+https://github.com/psf/requests\n\n# เพิ่มจาก requirements.txt ที่มีอยู่แล้ว\nuv add -r requirements.txt\n\n# ลบ package\nuv remove requests\n",[18,8089,8090,8095,8104,8116,8120,8125,8138,8142,8147,8156,8160,8165,8177,8181,8186],{"__ignoreMap":31},[81,8091,8092],{"class":83,"line":84},[81,8093,8094],{"class":1064},"# เพิ่ม package\n",[81,8096,8097,8099,8101],{"class":83,"line":102},[81,8098,5876],{"class":87},[81,8100,2854],{"class":90},[81,8102,8103],{"class":90}," requests\n",[81,8105,8106,8108,8110,8113],{"class":83,"line":113},[81,8107,5876],{"class":87},[81,8109,2854],{"class":90},[81,8111,8112],{"class":90}," flask",[81,8114,8115],{"class":90}," sqlalchemy\n",[81,8117,8118],{"class":83,"line":247},[81,8119,366],{"emptyLinePlaceholder":365},[81,8121,8122],{"class":83,"line":393},[81,8123,8124],{"class":1064},"# เพิ่มแบบระบุเวอร์ชัน\n",[81,8126,8127,8129,8131,8133,8136],{"class":83,"line":404},[81,8128,5876],{"class":87},[81,8130,2854],{"class":90},[81,8132,129],{"class":128},[81,8134,8135],{"class":90},"requests==2.32.0",[81,8137,135],{"class":128},[81,8139,8140],{"class":83,"line":415},[81,8141,366],{"emptyLinePlaceholder":365},[81,8143,8144],{"class":83,"line":1438},[81,8145,8146],{"class":1064},"# เพิ่มจาก git\n",[81,8148,8149,8151,8153],{"class":83,"line":1455},[81,8150,5876],{"class":87},[81,8152,2854],{"class":90},[81,8154,8155],{"class":90}," git+https://github.com/psf/requests\n",[81,8157,8158],{"class":83,"line":1467},[81,8159,366],{"emptyLinePlaceholder":365},[81,8161,8162],{"class":83,"line":1483},[81,8163,8164],{"class":1064},"# เพิ่มจาก requirements.txt ที่มีอยู่แล้ว\n",[81,8166,8167,8169,8171,8174],{"class":83,"line":1497},[81,8168,5876],{"class":87},[81,8170,2854],{"class":90},[81,8172,8173],{"class":94}," -r",[81,8175,8176],{"class":90}," requirements.txt\n",[81,8178,8179],{"class":83,"line":1509},[81,8180,366],{"emptyLinePlaceholder":365},[81,8182,8183],{"class":83,"line":1514},[81,8184,8185],{"class":1064},"# ลบ package\n",[81,8187,8188,8190,8193],{"class":83,"line":1519},[81,8189,5876],{"class":87},[81,8191,8192],{"class":90}," remove",[81,8194,8103],{"class":90},[580,8196,8198],{"id":8197},"รัน-project","รัน Project",[23,8200,8202],{"className":75,"code":8201,"language":77,"meta":31,"style":31},"# รันไฟล์ Python ใน project\nuv run main.py\n\n# รัน framework command เช่น Flask\nuv add flask\nuv run -- flask run -p 3000\n",[18,8203,8204,8209,8218,8222,8227,8236],{"__ignoreMap":31},[81,8205,8206],{"class":83,"line":84},[81,8207,8208],{"class":1064},"# รันไฟล์ Python ใน project\n",[81,8210,8211,8213,8215],{"class":83,"line":102},[81,8212,5876],{"class":87},[81,8214,91],{"class":90},[81,8216,8217],{"class":90}," main.py\n",[81,8219,8220],{"class":83,"line":113},[81,8221,366],{"emptyLinePlaceholder":365},[81,8223,8224],{"class":83,"line":247},[81,8225,8226],{"class":1064},"# รัน framework command เช่น Flask\n",[81,8228,8229,8231,8233],{"class":83,"line":393},[81,8230,5876],{"class":87},[81,8232,2854],{"class":90},[81,8234,8235],{"class":90}," flask\n",[81,8237,8238,8240,8242,8245,8247,8249,8251],{"class":83,"line":404},[81,8239,5876],{"class":87},[81,8241,91],{"class":90},[81,8243,8244],{"class":94}," --",[81,8246,8112],{"class":90},[81,8248,91],{"class":90},[81,8250,6956],{"class":94},[81,8252,8253],{"class":655}," 3000\n",[14,8255,8256,8259,8260,818,8263,4616],{},[18,8257,8258],{},"uv run"," จะ sync dependencies และสร้าง virtual environment ให้อัตโนมัติ ไม่ต้อง ",[18,8261,8262],{},"pip install",[18,8264,8265],{},"source .venv/bin/activate",[580,8267,8269],{"id":8268},"จัดการเวอร์ชัน-python","จัดการเวอร์ชัน Python",[23,8271,8273],{"className":75,"code":8272,"language":77,"meta":31,"style":31},"# ดูเวอร์ชัน Python ที่มีให้ติดตั้ง\nuv python list\n\n# ติดตั้ง Python เวอร์ชันที่ต้องการ\nuv python install 3.12\n\n# ตรึงเวอร์ชัน Python ของ project\nuv python pin 3.12\n",[18,8274,8275,8280,8289,8293,8298,8309,8313,8318],{"__ignoreMap":31},[81,8276,8277],{"class":83,"line":84},[81,8278,8279],{"class":1064},"# ดูเวอร์ชัน Python ที่มีให้ติดตั้ง\n",[81,8281,8282,8284,8287],{"class":83,"line":102},[81,8283,5876],{"class":87},[81,8285,8286],{"class":90}," python",[81,8288,8042],{"class":90},[81,8290,8291],{"class":83,"line":113},[81,8292,366],{"emptyLinePlaceholder":365},[81,8294,8295],{"class":83,"line":247},[81,8296,8297],{"class":1064},"# ติดตั้ง Python เวอร์ชันที่ต้องการ\n",[81,8299,8300,8302,8304,8306],{"class":83,"line":393},[81,8301,5876],{"class":87},[81,8303,8286],{"class":90},[81,8305,2829],{"class":90},[81,8307,8308],{"class":655}," 3.12\n",[81,8310,8311],{"class":83,"line":404},[81,8312,366],{"emptyLinePlaceholder":365},[81,8314,8315],{"class":83,"line":415},[81,8316,8317],{"class":1064},"# ตรึงเวอร์ชัน Python ของ project\n",[81,8319,8320,8322,8324,8327],{"class":83,"line":1438},[81,8321,5876],{"class":87},[81,8323,8286],{"class":90},[81,8325,8326],{"class":90}," pin",[81,8328,8308],{"class":655},[10,8330,8332],{"id":8331},"_4-อื่น-ๆ-ที่น่าสนใจ","4. อื่น ๆ ที่น่าสนใจ",[580,8334,8336],{"id":8335},"รัน-script-พร้อม-dependencies-แบบ-inline","รัน Script พร้อม Dependencies แบบ Inline",[14,8338,8339,8340],{},"ประกาศ dependencies ไว้ในไฟล์ Python ได้เลย ผ่าน inline metadata ตาม ",[910,8341,8344],{"href":8342,"rel":8343},"https://peps.python.org/pep-0723/",[914],"PEP 723",[23,8346,8351],{"className":8347,"code":8348,"filename":8349,"language":8350,"meta":31,"style":31},"language-python shiki shiki-themes material-theme-lighter one-dark-pro github-dark","# /// script\n# dependencies = [\n#   \"httpx\",\n# ]\n# ///\n\nimport httpx\n\nresp = httpx.get(\"https://peps.python.org/api/peps.json\")\ndata = resp.json()\nprint([(k, v[\"title\"]) for k, v in data.items()][:10])\n","fetch_peps.py","python",[18,8352,8353,8361,8366,8371,8376,8383,8387,8394,8398,8425,8442],{"__ignoreMap":31},[81,8354,8355,8358],{"class":83,"line":84},[81,8356,8357],{"class":1064},"#",[81,8359,8360],{"class":1064}," /// script\n",[81,8362,8363],{"class":83,"line":102},[81,8364,8365],{"class":1064},"# dependencies = [\n",[81,8367,8368],{"class":83,"line":113},[81,8369,8370],{"class":1064},"#   \"httpx\",\n",[81,8372,8373],{"class":83,"line":247},[81,8374,8375],{"class":1064},"# ]\n",[81,8377,8378,8380],{"class":83,"line":393},[81,8379,8357],{"class":1064},[81,8381,8382],{"class":1064}," ///\n",[81,8384,8385],{"class":83,"line":404},[81,8386,366],{"emptyLinePlaceholder":365},[81,8388,8389,8391],{"class":83,"line":415},[81,8390,2889],{"class":604},[81,8392,8393],{"class":1449}," httpx\n",[81,8395,8396],{"class":83,"line":1438},[81,8397,366],{"emptyLinePlaceholder":365},[81,8399,8400,8403,8405,8408,8410,8414,8416,8418,8421,8423],{"class":83,"line":1455},[81,8401,8402],{"class":1449},"resp ",[81,8404,3131],{"class":1598},[81,8406,8407],{"class":1449}," httpx",[81,8409,1609],{"class":628},[81,8411,8413],{"class":8412},"s5BH7","get",[81,8415,1616],{"class":628},[81,8417,1255],{"class":128},[81,8419,8420],{"class":90},"https://peps.python.org/api/peps.json",[81,8422,1255],{"class":128},[81,8424,1730],{"class":628},[81,8426,8427,8430,8432,8435,8437,8439],{"class":83,"line":1467},[81,8428,8429],{"class":1449},"data ",[81,8431,3131],{"class":1598},[81,8433,8434],{"class":1449}," resp",[81,8436,1609],{"class":628},[81,8438,1235],{"class":8412},[81,8440,8441],{"class":628},"()\n",[81,8443,8444,8447,8450,8454,8456,8459,8462,8464,8466,8468,8471,8473,8476,8478,8481,8484,8487,8489,8492,8495,8498],{"class":83,"line":1483},[81,8445,8446],{"class":637},"print",[81,8448,8449],{"class":628},"([(",[81,8451,8453],{"class":8452},"sb6-j","k",[81,8455,1724],{"class":628},[81,8457,8458],{"class":8452}," v",[81,8460,8461],{"class":628},"[",[81,8463,1255],{"class":128},[81,8465,5076],{"class":90},[81,8467,1255],{"class":128},[81,8469,8470],{"class":628},"])",[81,8472,3712],{"class":604},[81,8474,8475],{"class":8452}," k",[81,8477,1724],{"class":628},[81,8479,8480],{"class":8452}," v ",[81,8482,8483],{"class":604},"in",[81,8485,8486],{"class":8452}," data",[81,8488,1609],{"class":628},[81,8490,8491],{"class":8412},"items",[81,8493,8494],{"class":628},"()][:",[81,8496,8497],{"class":655},"10",[81,8499,2341],{"class":628},[23,8501,8503],{"className":75,"code":8502,"language":77,"meta":31,"style":31},"uv run fetch_peps.py\n",[18,8504,8505],{"__ignoreMap":31},[81,8506,8507,8509,8511],{"class":83,"line":84},[81,8508,5876],{"class":87},[81,8510,91],{"class":90},[81,8512,8513],{"class":90}," fetch_peps.py\n",[14,8515,8516],{},"uv จะอ่าน dependencies จาก comment ในไฟล์แล้วติดตั้งให้อัตโนมัติ เหมาะสำหรับ script เล็ก ๆ ที่ไม่อยากสร้าง project เต็มรูปแบบ",[580,8518,8520],{"id":8519},"รัน-script-พร้อมระบุ-dependencies-ผ่าน-cli","รัน Script พร้อมระบุ Dependencies ผ่าน CLI",[23,8522,8524],{"className":75,"code":8523,"language":77,"meta":31,"style":31},"# รัน script พร้อมติดตั้ง rich ให้อัตโนมัติ\nuv run --with rich example.py\n\n# ระบุเวอร์ชันได้\nuv run --with 'rich>12,\u003C13' example.py\n",[18,8525,8526,8531,8546,8550,8555],{"__ignoreMap":31},[81,8527,8528],{"class":83,"line":84},[81,8529,8530],{"class":1064},"# รัน script พร้อมติดตั้ง rich ให้อัตโนมัติ\n",[81,8532,8533,8535,8537,8540,8543],{"class":83,"line":102},[81,8534,5876],{"class":87},[81,8536,91],{"class":90},[81,8538,8539],{"class":94}," --with",[81,8541,8542],{"class":90}," rich",[81,8544,8545],{"class":90}," example.py\n",[81,8547,8548],{"class":83,"line":113},[81,8549,366],{"emptyLinePlaceholder":365},[81,8551,8552],{"class":83,"line":247},[81,8553,8554],{"class":1064},"# ระบุเวอร์ชันได้\n",[81,8556,8557,8559,8561,8563,8565,8568,8570],{"class":83,"line":393},[81,8558,5876],{"class":87},[81,8560,91],{"class":90},[81,8562,8539],{"class":94},[81,8564,129],{"class":128},[81,8566,8567],{"class":90},"rich>12,\u003C13",[81,8569,3020],{"class":128},[81,8571,8545],{"class":90},[580,8573,8575],{"id":8574},"ใช้-uv-ใน-docker","ใช้ uv ใน Docker",[23,8577,8581],{"className":8578,"code":8579,"language":8580,"meta":31,"style":31},"language-dockerfile shiki shiki-themes material-theme-lighter one-dark-pro github-dark","FROM python:3.12-slim\n\n# ติดตั้ง uv\nCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/\n\n# ติดตั้ง dependencies\nCOPY pyproject.toml uv.lock ./\nRUN uv sync --frozen --no-dev\n\n# คัดลอก source code\nCOPY . .\n\nCMD [\"uv\", \"run\", \"main.py\"]\n","dockerfile",[18,8582,8583,8588,8592,8597,8602,8606,8611,8616,8621,8625,8630,8635,8639],{"__ignoreMap":31},[81,8584,8585],{"class":83,"line":84},[81,8586,8587],{},"FROM python:3.12-slim\n",[81,8589,8590],{"class":83,"line":102},[81,8591,366],{"emptyLinePlaceholder":365},[81,8593,8594],{"class":83,"line":113},[81,8595,8596],{},"# ติดตั้ง uv\n",[81,8598,8599],{"class":83,"line":247},[81,8600,8601],{},"COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/\n",[81,8603,8604],{"class":83,"line":393},[81,8605,366],{"emptyLinePlaceholder":365},[81,8607,8608],{"class":83,"line":404},[81,8609,8610],{},"# ติดตั้ง dependencies\n",[81,8612,8613],{"class":83,"line":415},[81,8614,8615],{},"COPY pyproject.toml uv.lock ./\n",[81,8617,8618],{"class":83,"line":1438},[81,8619,8620],{},"RUN uv sync --frozen --no-dev\n",[81,8622,8623],{"class":83,"line":1455},[81,8624,366],{"emptyLinePlaceholder":365},[81,8626,8627],{"class":83,"line":1467},[81,8628,8629],{},"# คัดลอก source code\n",[81,8631,8632],{"class":83,"line":1483},[81,8633,8634],{},"COPY . .\n",[81,8636,8637],{"class":83,"line":1497},[81,8638,366],{"emptyLinePlaceholder":365},[81,8640,8641],{"class":83,"line":1509},[81,8642,8643],{},"CMD [\"uv\", \"run\", \"main.py\"]\n",[580,8645,8647],{"id":8646},"ใช้-uv-แทน-pip-ได้เลย","ใช้ uv แทน pip ได้เลย",[14,8649,8650,8651,8654,8655,8657],{},"คนที่ยังชินกับ pip ใช้ ",[18,8652,8653],{},"uv pip"," แทนได้เลย แค่เติม ",[18,8656,5876],{}," ข้างหน้า",[23,8659,8661],{"className":75,"code":8660,"language":77,"meta":31,"style":31},"uv pip install requests\nuv pip install -r requirements.txt\nuv pip freeze\nuv pip uninstall requests\n",[18,8662,8663,8674,8686,8695],{"__ignoreMap":31},[81,8664,8665,8667,8670,8672],{"class":83,"line":84},[81,8666,5876],{"class":87},[81,8668,8669],{"class":90}," pip",[81,8671,2829],{"class":90},[81,8673,8103],{"class":90},[81,8675,8676,8678,8680,8682,8684],{"class":83,"line":102},[81,8677,5876],{"class":87},[81,8679,8669],{"class":90},[81,8681,2829],{"class":90},[81,8683,8173],{"class":94},[81,8685,8176],{"class":90},[81,8687,8688,8690,8692],{"class":83,"line":113},[81,8689,5876],{"class":87},[81,8691,8669],{"class":90},[81,8693,8694],{"class":90}," freeze\n",[81,8696,8697,8699,8701,8704],{"class":83,"line":247},[81,8698,5876],{"class":87},[81,8700,8669],{"class":90},[81,8702,8703],{"class":90}," uninstall",[81,8705,8103],{"class":90},[10,8707,845],{"id":845},[14,8709,8710],{},"uv ช่วยให้จัดการ Python ง่ายขึ้นเยอะ ตั้งแต่ติดตั้งตัว Python เอง จัดการ dependencies ไปจนถึงรันเครื่องมือต่าง ๆ ทำได้ด้วยคำสั่งเดียว และที่สำคัญคือเร็วมาก ๆ ลองใช้ดูครับ",[10,8712,6746],{"id":6745},[904,8714,8715,8721],{},[907,8716,8717],{},[910,8718,8719],{"href":8719,"rel":8720},"https://docs.astral.sh/uv/",[914],[907,8722,8723],{},[910,8724,8725],{"href":8725,"rel":8726},"https://github.com/astral-sh/uv",[914],[938,8728,8729],{},"html pre.shiki code .sosa_, html code.shiki .sosa_{--shiki-light:#E2931D;--shiki-default:#61AFEF;--shiki-dark:#B392F0}html pre.shiki code .sAlJX, html code.shiki .sAlJX{--shiki-light:#91B859;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sEoV_, html code.shiki .sEoV_{--shiki-light:#91B859;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .sDKbk, html code.shiki .sDKbk{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sJZfL, html code.shiki .sJZfL{--shiki-light:#6182B8;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}html pre.shiki code .snOyU, html code.shiki .snOyU{--shiki-light:#F76D47;--shiki-default:#D19A66;--shiki-dark:#79B8FF}html pre.shiki code .s36IK, html code.shiki .s36IK{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#C678DD;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sZDfs, html code.shiki .sZDfs{--shiki-light:#90A4AE;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .sMTEB, html code.shiki .sMTEB{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F97583}html pre.shiki code .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}html pre.shiki code .s5BH7, html code.shiki .s5BH7{--shiki-light:#6182B8;--shiki-default:#61AFEF;--shiki-dark:#E1E4E8}html pre.shiki code .sb6-j, html code.shiki .sb6-j{--shiki-light:#6182B8;--shiki-default:#ABB2BF;--shiki-dark:#E1E4E8}",{"title":31,"searchDepth":102,"depth":102,"links":8731},[8732,8733,8738,8743,8749,8755,8756],{"id":7661,"depth":102,"text":7662},{"id":7676,"depth":102,"text":7677,"children":8734},[8735,8736,8737],{"id":7680,"depth":113,"text":7681},{"id":7698,"depth":113,"text":7699},{"id":7739,"depth":113,"text":7739},{"id":7780,"depth":102,"text":7781,"children":8739},[8740,8741,8742],{"id":7790,"depth":113,"text":7791},{"id":7855,"depth":113,"text":7856},{"id":7924,"depth":113,"text":7925},{"id":8045,"depth":102,"text":8046,"children":8744},[8745,8746,8747,8748],{"id":8049,"depth":113,"text":8050},{"id":8083,"depth":113,"text":8084},{"id":8197,"depth":113,"text":8198},{"id":8268,"depth":113,"text":8269},{"id":8331,"depth":102,"text":8332,"children":8750},[8751,8752,8753,8754],{"id":8335,"depth":113,"text":8336},{"id":8519,"depth":113,"text":8520},{"id":8574,"depth":113,"text":8575},{"id":8646,"depth":113,"text":8647},{"id":845,"depth":102,"text":845},{"id":6745,"depth":102,"text":6746},"2026-03-30T00:00:00.000Z","แนะนำวิธีติดตั้งและใช้งาน uv เครื่องมือจัดการ Python ที่เร็วสุด ๆ ตั้งแต่ติดตั้ง รันเครื่องมือ ไปจนถึงจัดการ Project",{},"How To Use UV","/blogs/2026-03-30-how-to-use-uv",{"title":7656,"description":8758},{"loc":8761},"blogs/2026-03-30-how-to-use-uv",[968,8766],"Python","oFcMQu6zCgdFGMQZOGIOXl-YzPYC8dEYnhhDzkOomAU",1789544687321]