[{"data":1,"prerenderedAt":8477},["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,"__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","mHkUvpW3arMO9Ilhjx2jqKudxxKXJ91mo0f-Uoevcpw",[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,"__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","ysRLE_0FyCjnNaxas0CmI7-MhEH2U1XUr6fn9BkBUyA",[1215,2528,2629,2796,3343,3533,4468,5855,6434,6614,6767,7071,7242,7360],{"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,"__hash__":2527},"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 สามารถนำไปใช้งานได้ง่ายยิ่งขึ้น",[580,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},[580,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":113,"text":1352},{"id":2116,"depth":113,"text":2117},"2024-01-20T00:00:00.000Z","Code ตัวอย่างคือการใช้งานใน Express + Typescript + Mongoose",{},"Express Simple Paginate","/blogs/2024-01-20-simple-paginate",{"title":1217,"description":2519},{"loc":2522},"blogs/2024-01-20-simple-paginate",[968],"yfJFDG2gdwKeg2oQvKQnBwJNymHvcE1__IA-XnBCTVc",{"id":2529,"title":2530,"body":2531,"date":2619,"description":2538,"draft":956,"extension":957,"meta":2620,"navigation":365,"next":959,"ogTitle":2621,"path":2622,"pin":956,"prev":959,"recommends":2623,"seo":2624,"sitemap":2625,"stem":2626,"tags":2627,"__hash__":2628},"content/blogs/2024-06-06-openvpn-exclude-network.md","ตั้งค่าให้สามารถเชื่อมต่อ Local Network ได้ ตอนที่เชื่อม openvpn",{"type":7,"value":2532,"toc":2615},[2533,2536,2539,2543,2568,2571,2606,2609,2612],[14,2534,2535],{},"วิธีการแก้ไขปัญหาเชื่อมต่อ Internal Network ไม่ได้เมื่อมีการต่อ OPENVPN อยู่",[14,2537,2538],{},"แก้ไขไฟล์ openvpn config หากมีการ import เข้าไปใน openvpn gui แล้วสามารถค้นหาไฟล์การตั้งค่าได้ที่",[580,2540,2542],{"id":2541},"สำหรับ-macos","สำหรับ macos",[23,2544,2547],{"className":75,"code":2545,"filename":2546,"language":77,"meta":31,"style":31},"/Users/\u003Cyour_username_here>/Library/Application\\ Support/OpenVPN\\ Connect/profiles\n","⌘",[18,2548,2549],{"__ignoreMap":31},[81,2550,2551,2554,2557,2560,2563,2565],{"class":83,"line":84},[81,2552,2553],{"class":87},"/Users/",[81,2555,2556],{"class":1449},"\u003Cyour_username_here>/Library/Application",[81,2558,2559],{"class":98},"\\ ",[81,2561,2562],{"class":1449},"Support/OpenVPN",[81,2564,2559],{"class":98},[81,2566,2567],{"class":1449},"Connect/profiles\n",[14,2569,2570],{},"เพิ่ม config",[23,2572,2574],{"className":75,"code":2573,"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,2575,2576,2581,2595],{"__ignoreMap":31},[81,2577,2578],{"class":83,"line":84},[81,2579,2580],{"class":1064},"# Exclude my home network routes\n",[81,2582,2583,2586,2589,2592],{"class":83,"line":102},[81,2584,2585],{"class":87},"route",[81,2587,2588],{"class":655}," 192.168.1.0",[81,2590,2591],{"class":655}," 255.255.255.0",[81,2593,2594],{"class":90}," net_gateway\n",[81,2596,2597,2599,2602,2604],{"class":83,"line":113},[81,2598,2585],{"class":87},[81,2600,2601],{"class":655}," 192.168.2.0",[81,2603,2591],{"class":655},[81,2605,2594],{"class":90},[580,2607,2608],{"id":2608},"ทดสอบ",[14,2610,2611],{},"ทำการเชื่อมต่อ openvpn ใหม่ จะพบว่าสามารถเชื่อมต่อ local network ได้แล้ว หลักการง่าย ๆ ก็คือการทำให้ request ที่ไปยัง destination ดังกล่าวไม่ถูกส่งเข้าไปยัง openvpn นั้นเอง",[938,2613,2614],{},"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":2616},[2617,2618],{"id":2541,"depth":113,"text":2542},{"id":2608,"depth":113,"text":2608},"2024-06-06T00:00:00.000Z",{},"Openvpn Split Tunnel","/blogs/2024-06-06-openvpn-exclude-network",[],{"title":2530,"description":2538},{"loc":2622},"blogs/2024-06-06-openvpn-exclude-network",[968],"iwqqkGrIWNt77I1BcOLmGkvarDO_Hsc1kuKug1s02O4",{"id":2630,"title":2631,"body":2632,"date":2786,"description":2642,"draft":956,"extension":957,"meta":2787,"navigation":365,"next":959,"ogTitle":2788,"path":2789,"pin":956,"prev":959,"recommends":2790,"seo":2791,"sitemap":2792,"stem":2793,"tags":2794,"__hash__":2795},"content/blogs/2025-04-14-dotenv-leak-lab.md","วิธีการลบ .env file ออกจาก git แบบถูกวิธี",{"type":7,"value":2633,"toc":2780},[2634,2640,2643,2646,2654,2669,2675,2683,2687,2713,2719,2729,2733,2738,2759,2765,2772,2777],[14,2635,2636],{},[979,2637],{"alt":2638,"src":2639},".env leak","/images/2025-04-14-dotenv-leak-lab/image-0.png",[14,2641,2642],{},"สมมติว่าเราเผลอ commit ไฟล์ .env ที่มี API key ไปแล้ว และ push ขึ้น GitHub เราต้องรีบลบมันออกทันที เพราะข้อมูลอาจถูกนำไปใช้ได้",[14,2644,2645],{},"สำหรับวิธีการลบ .env file แบบถูกวิธีให้ทำแบบนี้นะครับ พร้อมแล้วไปลุยกันเลยย",[580,2647,2649,2650,2653],{"id":2648},"_1-นำ-env-file-ไปใส่ใน-gitignore-ก่อน","1. นำ .env file ไปใส่ใน ",[18,2651,2652],{},".gitignore"," ก่อน",[23,2655,2657],{"className":75,"code":2656,"filename":2652,"language":77,"meta":31,"style":31},".env\n.env.*\n",[18,2658,2659,2664],{"__ignoreMap":31},[81,2660,2661],{"class":83,"line":84},[81,2662,2663],{"class":87},".env\n",[81,2665,2666],{"class":83,"line":102},[81,2667,2668],{"class":87},".env.*\n",[2670,2671,2672],"blockquote",{},[14,2673,2674],{},".env.:* เช่น .env.production .env.local .env.development",[14,2676,2677,2678,2682],{},"แนะนำให้ลองใช้ ",[910,2679,2680],{"href":2680,"rel":2681},"https://www.toptal.com/developers/gitignore",[914]," ผมใช้เป็นประจำครับ เป็นเครื่องมือในการช่วยสร้าง gitignore ไฟล์ชั้นดีเลย",[580,2684,2686],{"id":2685},"_2-ลบ-env-file-ออกจาก-git-แบบในทุก-ๆ-commits","2. ลบ .env file ออกจาก git แบบในทุก ๆ commits",[23,2688,2690],{"className":75,"code":2689,"language":77,"meta":31,"style":31},"git filter-branch --index-filter \"git rm -rf --cached --ignore-unmatch .env\" HEAD\n",[18,2691,2692],{"__ignoreMap":31},[81,2693,2694,2697,2700,2703,2705,2708,2710],{"class":83,"line":84},[81,2695,2696],{"class":87},"git",[81,2698,2699],{"class":90}," filter-branch",[81,2701,2702],{"class":94}," --index-filter",[81,2704,641],{"class":128},[81,2706,2707],{"class":90},"git rm -rf --cached --ignore-unmatch .env",[81,2709,1255],{"class":128},[81,2711,2712],{"class":90}," HEAD\n",[14,2714,2715],{},[979,2716],{"alt":2717,"src":2718},"Output","/images/2025-04-14-dotenv-leak-lab/image-1.png",[14,2720,2721,2722,818,2725,2728],{},"ถ้าลบออกเฉย ๆ ด้วยคำสั่ง ",[18,2723,2724],{},"git rm -r --cached .env",[18,2726,2727],{},"rm .env"," แล้ว commit ไปใหม่ สิ่งที่จะเกิดขึ้นคือไฟล์จะหายไปแค่ commit ล่าสุดเท่านั้น แต่ถ้าไปดูใน commit history ไฟล์ดังกล่าวจะยังอยู่ครับ",[580,2730,2732],{"id":2731},"_3-push-git-ไฟล์อันใหม่ขึ้นไปด้วยคำสั่ง","3. push .git ไฟล์อันใหม่ขึ้นไปด้วยคำสั่ง",[2670,2734,2735],{},[14,2736,2737],{},"คำเตือน: คำสั่งนี้จะเปลี่ยนแปลงประวัติทั้งหมดของ Project ควรแน่ใจว่าไม่มีใครใช้งาน repository นี้อยู่ก่อนจะ push ด้วย --force",[23,2739,2741],{"className":75,"code":2740,"language":77,"meta":31,"style":31},"git push origin main --force\n",[18,2742,2743],{"__ignoreMap":31},[81,2744,2745,2747,2750,2753,2756],{"class":83,"line":84},[81,2746,2696],{"class":87},[81,2748,2749],{"class":90}," push",[81,2751,2752],{"class":90}," origin",[81,2754,2755],{"class":90}," main",[81,2757,2758],{"class":94}," --force\n",[14,2760,2761],{},[979,2762],{"alt":2763,"src":2764},"Result","/images/2025-04-14-dotenv-leak-lab/image-2.png",[14,2766,2767,2768,2771],{},"เพียงแค่นี้ ",[18,2769,2770],{},".env"," file ก็จะถูกลบออกไปจาก Git ของเราเรียบร้อยแล้วครับ",[2670,2773,2774],{},[14,2775,2776],{},"Tip: ถ้าเคย push .env ขึ้น GitHub, Gitlab, Gitea หรือ Git Platform อื่น ๆ แล้ว อย่าลืมรีเซ็ต API key หรือ Token ที่อยู่ในไฟล์ด้วยนะครับ เพื่อความปลอดภัยสูงสุด",[938,2778,2779],{},"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":2781},[2782,2784,2785],{"id":2648,"depth":113,"text":2783},"1. นำ .env file ไปใส่ใน .gitignore ก่อน",{"id":2685,"depth":113,"text":2686},{"id":2731,"depth":113,"text":2732},"2025-04-14T00:00:00.000Z",{},"How To Remove .env From Git","/blogs/2025-04-14-dotenv-leak-lab",[],{"title":2631,"description":2642},{"loc":2789},"blogs/2025-04-14-dotenv-leak-lab",[968],"_rppCmKhXtTHagenWhqCVs8oZJwTQsHy-HkKf2Q3jps",{"id":2797,"title":2798,"body":2799,"date":3333,"description":3334,"draft":956,"extension":957,"meta":3335,"navigation":365,"next":959,"ogTitle":3336,"path":3337,"pin":956,"prev":959,"recommends":959,"seo":3338,"sitemap":3339,"stem":3340,"tags":3341,"__hash__":3342},"content/blogs/2025-04-25-setup-nuxt-medium-zoom.md","วิธีการติดตั้งและใช้งาน Medium Zoom ใน Nuxt 3",{"type":7,"value":2800,"toc":3324},[2801,2806,2809,2812,2816,2860,2864,2873,2877,3102,3106,3114,3207,3214,3307,3310,3312,3321],[14,2802,2803],{},[979,2804],{"alt":31,"src":2805},"/images/2025-04-25-setup-nuxt-medium-zoom/medium-zoom.png",[14,2807,2808],{},"วันนี้จะมาแนะนำวิธีการติดตั้ง Library นึงครับซึ่งจะทำให้รูปภายในเว็บไซต์ของเรามีลูกเล่นเหมือนกับรูปที่อยู่ภายในเว็บไซต์ Medium.com ครับ อยากรู้ว่าเป็นยังไงลองคลิกที่รูปด้านบนได้เลยครับ 👆",[10,2810,2811],{"id":2811},"วิธีการติดตั้ง",[580,2813,2815],{"id":2814},"_1-ติดตั้ง-library-ดังกล่าว","1. ติดตั้ง Library ดังกล่าว",[23,2817,2819],{"className":1355,"code":2818,"language":1357,"meta":31,"style":31},"npm install medium-zoom\n# หรือ\nyarn add medium-zoom\n",[18,2820,2821,2838,2846],{"__ignoreMap":31},[81,2822,2823,2826,2829,2832,2835],{"class":83,"line":84},[81,2824,2825],{"class":1624},"npm",[81,2827,2828],{"class":1624}," install",[81,2830,2831],{"class":1624}," medium",[81,2833,2834],{"class":1598},"-",[81,2836,2837],{"class":1624},"zoom\n",[81,2839,2840,2843],{"class":83,"line":102},[81,2841,2842],{"class":1449},"# ",[81,2844,2845],{"class":1624},"หรือ\n",[81,2847,2848,2851,2854,2856,2858],{"class":83,"line":113},[81,2849,2850],{"class":1624},"yarn",[81,2852,2853],{"class":1624}," add",[81,2855,2831],{"class":1624},[81,2857,2834],{"class":1598},[81,2859,2837],{"class":1624},[580,2861,2863],{"id":2862},"_2-สร้าง-folder-และ-file-plugins","2. สร้าง Folder และ File Plugins",[23,2865,2867],{"className":75,"code":2866,"language":77,"meta":31,"style":31},"# เติม .client.ts เพื่อกำหนดว่า plugins นี้เป็นของฝั่ง client อย่างเดียวนะครับ\n",[18,2868,2869],{"__ignoreMap":31},[81,2870,2871],{"class":83,"line":84},[81,2872,2866],{"class":1064},[580,2874,2876],{"id":2875},"_3-นำ-source-code-ไปวางภายในไฟล์ดังกล่าวได้เลยครับ","3. นำ Source Code ไปวางภายในไฟล์ดังกล่าวได้เลยครับ",[23,2878,2882],{"className":2879,"code":2880,"language":2881,"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,2883,2884,2906,2931,2935,2958,2974,3001,3005,3034,3061,3068,3072,3096],{"__ignoreMap":31},[81,2885,2886,2889,2891,2894,2896,2899,2901,2904],{"class":83,"line":84},[81,2887,2888],{"class":604},"import",[81,2890,2130],{"class":628},[81,2892,2893],{"class":1624}," defineNuxtPlugin",[81,2895,2140],{"class":628},[81,2897,2898],{"class":604}," from",[81,2900,129],{"class":128},[81,2902,2903],{"class":90},"#app",[81,2905,135],{"class":128},[81,2907,2908,2910,2913,2915,2917,2920,2922,2924,2926,2929],{"class":83,"line":102},[81,2909,2888],{"class":604},[81,2911,2912],{"class":1624}," mediumZoom",[81,2914,1724],{"class":628},[81,2916,2130],{"class":628},[81,2918,2919],{"class":1624}," Zoom",[81,2921,2140],{"class":628},[81,2923,2898],{"class":604},[81,2925,129],{"class":128},[81,2927,2928],{"class":90},"medium-zoom",[81,2930,135],{"class":128},[81,2932,2933],{"class":83,"line":113},[81,2934,366],{"emptyLinePlaceholder":365},[81,2936,2937,2939,2942,2944,2946,2948,2951,2953,2956],{"class":83,"line":247},[81,2938,1522],{"class":604},[81,2940,2941],{"class":604}," default",[81,2943,2893],{"class":1528},[81,2945,1616],{"class":1449},[81,2947,1616],{"class":628},[81,2949,2950],{"class":1538},"nuxtApp",[81,2952,1573],{"class":628},[81,2954,2955],{"class":1364}," =>",[81,2957,1372],{"class":628},[81,2959,2960,2962,2965,2967,2969,2972],{"class":83,"line":393},[81,2961,1591],{"class":1364},[81,2963,2964],{"class":1594}," selector",[81,2966,1599],{"class":1598},[81,2968,129],{"class":128},[81,2970,2971],{"class":90},".image-zoomable",[81,2973,135],{"class":128},[81,2975,2976,2978,2981,2983,2985,2987,2989,2991,2994,2996,2999],{"class":83,"line":404},[81,2977,1591],{"class":1364},[81,2979,2980],{"class":1594}," zoom",[81,2982,1258],{"class":618},[81,2984,2919],{"class":1368},[81,2986,1599],{"class":1598},[81,2988,2912],{"class":1528},[81,2990,1616],{"class":1615},[81,2992,2993],{"class":1624},"selector",[81,2995,1724],{"class":628},[81,2997,2998],{"class":628}," {}",[81,3000,1730],{"class":1615},[81,3002,3003],{"class":83,"line":415},[81,3004,366],{"emptyLinePlaceholder":365},[81,3006,3007,3010,3012,3015,3017,3020,3023,3025,3027,3030,3032],{"class":83,"line":1438},[81,3008,3009],{"class":1605},"  nuxtApp",[81,3011,1609],{"class":628},[81,3013,3014],{"class":1528},"hook",[81,3016,1616],{"class":1615},[81,3018,3019],{"class":128},"'",[81,3021,3022],{"class":90},"page:finish",[81,3024,3019],{"class":128},[81,3026,1724],{"class":628},[81,3028,3029],{"class":628}," ()",[81,3031,2955],{"class":1364},[81,3033,1372],{"class":628},[81,3035,3036,3039,3041,3044,3046,3048,3050,3052,3055,3057,3059],{"class":83,"line":1455},[81,3037,3038],{"class":1605},"    zoom",[81,3040,1609],{"class":628},[81,3042,3043],{"class":1528},"detach",[81,3045,1616],{"class":1615},[81,3047,2993],{"class":1624},[81,3049,1573],{"class":1615},[81,3051,1609],{"class":628},[81,3053,3054],{"class":1528},"attach",[81,3056,1616],{"class":1615},[81,3058,2993],{"class":1624},[81,3060,1730],{"class":1615},[81,3062,3063,3066],{"class":83,"line":1467},[81,3064,3065],{"class":628},"  }",[81,3067,1730],{"class":1615},[81,3069,3070],{"class":83,"line":1483},[81,3071,366],{"emptyLinePlaceholder":365},[81,3073,3074,3076,3078,3081,3083,3085,3088,3090,3092,3094],{"class":83,"line":1497},[81,3075,3009],{"class":1605},[81,3077,1609],{"class":628},[81,3079,3080],{"class":1528},"provide",[81,3082,1616],{"class":1615},[81,3084,3019],{"class":128},[81,3086,3087],{"class":90},"mediumZoom",[81,3089,3019],{"class":128},[81,3091,1724],{"class":628},[81,3093,2980],{"class":1624},[81,3095,1730],{"class":1615},[81,3097,3098,3100],{"class":83,"line":1509},[81,3099,2191],{"class":628},[81,3101,1730],{"class":1449},[580,3103,3105],{"id":3104},"_4-วิธีการใช้งาน","4. วิธีการใช้งาน",[14,3107,3108],{},[39,3109,3110,3111],{},"ใช้กับ ",[18,3112,3113],{},"\u003Cimg />",[23,3115,3117],{"className":2879,"code":3116,"language":2881,"meta":31,"style":31},"\u003Cimg src=\"/images/logo.jpeg\" alt=\"logo\" class=\"image-zoomable\" />\n\n# เพิ่ม \"image-zoomable\" เข้าไปใน class ได้เลย\n",[18,3118,3119,3165,3169],{"__ignoreMap":31},[81,3120,3121,3123,3125,3128,3131,3133,3136,3138,3141,3143,3145,3148,3150,3153,3155,3157,3160,3162],{"class":83,"line":84},[81,3122,1427],{"class":1598},[81,3124,979],{"class":1624},[81,3126,3127],{"class":1624}," src",[81,3129,3130],{"class":1598},"=",[81,3132,1255],{"class":128},[81,3134,3135],{"class":90},"/images/logo.jpeg",[81,3137,1255],{"class":128},[81,3139,3140],{"class":1624}," alt",[81,3142,3130],{"class":1598},[81,3144,1255],{"class":128},[81,3146,3147],{"class":90},"logo",[81,3149,1255],{"class":128},[81,3151,3152],{"class":1624}," class",[81,3154,3130],{"class":1598},[81,3156,1255],{"class":128},[81,3158,3159],{"class":90},"image-zoomable",[81,3161,1255],{"class":128},[81,3163,3164],{"class":1598}," />\n",[81,3166,3167],{"class":83,"line":102},[81,3168,366],{"emptyLinePlaceholder":365},[81,3170,3171,3173,3176,3179,3182,3184,3186,3188,3191,3194,3197,3199,3202,3204],{"class":83,"line":113},[81,3172,2842],{"class":1449},[81,3174,3175],{"class":1624},"เพิ",[81,3177,3178],{"class":1449},"่",[81,3180,3181],{"class":1624},"ม",[81,3183,641],{"class":128},[81,3185,3159],{"class":90},[81,3187,1255],{"class":128},[81,3189,3190],{"class":1624}," เข",[81,3192,3193],{"class":1449},"้",[81,3195,3196],{"class":1624},"าไปใน",[81,3198,3152],{"class":1364},[81,3200,3201],{"class":1368}," ได",[81,3203,3193],{"class":1449},[81,3205,3206],{"class":1368},"เลย\n",[14,3208,3209],{},[39,3210,3110,3211],{},[18,3212,3213],{},"\u003Cnuxt-img />",[23,3215,3217],{"className":2879,"code":3216,"language":2881,"meta":31,"style":31},"\u003Cnuxt-img\n    src=\"/images/logo.jpeg\"\n    alt=\"logo\"\n    class=\"image-zoomable\"\n/>\n\n# ซึ่งเป็นการเติม class เช่นเดียวกันครับ\n",[18,3218,3219,3231,3244,3257,3270,3275,3279],{"__ignoreMap":31},[81,3220,3221,3223,3226,3228],{"class":83,"line":84},[81,3222,1427],{"class":1598},[81,3224,3225],{"class":1624},"nuxt",[81,3227,2834],{"class":1598},[81,3229,3230],{"class":1624},"img\n",[81,3232,3233,3236,3238,3240,3242],{"class":83,"line":102},[81,3234,3235],{"class":1624},"    src",[81,3237,3130],{"class":1598},[81,3239,1255],{"class":128},[81,3241,3135],{"class":90},[81,3243,647],{"class":128},[81,3245,3246,3249,3251,3253,3255],{"class":83,"line":113},[81,3247,3248],{"class":1624},"    alt",[81,3250,3130],{"class":1598},[81,3252,1255],{"class":128},[81,3254,3147],{"class":90},[81,3256,647],{"class":128},[81,3258,3259,3262,3264,3266,3268],{"class":83,"line":247},[81,3260,3261],{"class":1624},"    class",[81,3263,3130],{"class":1598},[81,3265,1255],{"class":128},[81,3267,3159],{"class":90},[81,3269,647],{"class":128},[81,3271,3272],{"class":83,"line":393},[81,3273,3274],{"class":1598},"/>\n",[81,3276,3277],{"class":83,"line":404},[81,3278,366],{"emptyLinePlaceholder":365},[81,3280,3281,3283,3286,3288,3291,3294,3297,3299,3302,3304],{"class":83,"line":415},[81,3282,2842],{"class":1449},[81,3284,3285],{"class":1624},"ซึ",[81,3287,3178],{"class":1449},[81,3289,3290],{"class":1624},"งเป",[81,3292,3293],{"class":1449},"็",[81,3295,3296],{"class":1624},"นการเติม",[81,3298,3152],{"class":1364},[81,3300,3301],{"class":1368}," เช",[81,3303,3178],{"class":1449},[81,3305,3306],{"class":1368},"นเดียวกันครับ\n",[3308,3309],"hr",{},[10,3311,902],{"id":901},[904,3313,3314],{},[907,3315,3316],{},[910,3317,3320],{"href":3318,"rel":3319},"https://www.npmjs.com/package/medium-zoom",[914],"Medium Zoom",[938,3322,3323],{},"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":3325},[3326,3332],{"id":2811,"depth":102,"text":2811,"children":3327},[3328,3329,3330,3331],{"id":2814,"depth":113,"text":2815},{"id":2862,"depth":113,"text":2863},{"id":2875,"depth":113,"text":2876},{"id":3104,"depth":113,"text":3105},{"id":901,"depth":102,"text":902},"2025-04-25T00:00:00.000Z","วิธีการทำให้รูปของเรามีลูกเล่นเหมือนกับ Medium.com",{},"NuxtJS Medium Zoom","/blogs/2025-04-25-setup-nuxt-medium-zoom",{"title":2798,"description":3334},{"loc":3337},"blogs/2025-04-25-setup-nuxt-medium-zoom",[968],"whVJldCP8LaTo3ruC26HfUr-1LznfTJBskZT-Cb3kN0",{"id":3344,"title":3345,"body":3346,"date":3520,"description":3521,"draft":956,"extension":957,"meta":3522,"navigation":365,"next":959,"ogTitle":3523,"path":3524,"pin":956,"prev":959,"recommends":3525,"seo":3528,"sitemap":3529,"stem":3530,"tags":3531,"__hash__":3532},"content/blogs/2025-05-10-n8n-with-telegram-notify.md","ส่งการแจ้งเตือนไปยัง Telegram Bot ด้วย N8N",{"type":7,"value":3347,"toc":3515},[3348,3353,3357,3364,3367,3371,3374,3379,3386,3391,3408,3413,3420,3425,3436,3441,3448,3458,3463,3470,3475,3478,3483,3488,3494,3497,3502,3504],[14,3349,3350],{},[979,3351],{"alt":31,"src":3352},"/images/2025-05-10-n8n-with-telegram-notify/images-d022bdb9-7169-4052-baaf-c1da8325bce2.jpg",[10,3354,3356],{"id":3355},"telegram-bot-คืออะไร","Telegram Bot คืออะไร",[14,3358,3359,3360,3363],{},"หลังจากที่ Line Notify ประกาศยุติการให้บริการไป Telegram ก็ขึ้นมาเป็นอันดับต้น ๆ ของการทำ Notification เนื่องด้วยเหตุผลหลัก ๆ คือ ",[39,3361,3362],{},"FREE"," และใช้งานส่งได้ไม่จำกัด แต่ผมเองจริง ๆ ย้ายมาใช้ Telegram ได้หลายปีแล้วครับ ช่วงแรก ๆ ก็ใช้ Line Notify แล้วก็ไปใช้ Discord แทน สุดท้ายมาจบที่ Telegram เพราะว่าต้องการจะใช้ Line เฉพาะคุยเรื่องส่วนตัว (ไม่อยากใช้ Line ในการทำงาน) ส่วน Discord ก็อยากเอาไว้ใช้ในการพูดคุยกับเพื่อน ๆ มากกว่า",[14,3365,3366],{},"ข้อดีของ Telegram Bot หลัก ๆ ในส่วนของการพัฒนาถือว่าง่ายมากครับมีทั้งแบบ Polling และ Webhook ให้ใช้งาน หลัง ๆ มาเลยใช้แต่ Telegram เป็นหลักเลยครับ",[10,3368,3370],{"id":3369},"วิธีการสร้าง-telegram-bot","วิธีการสร้าง Telegram Bot",[14,3372,3373],{},"ทำการ Add telegram ที่ชื่อว่า \"BotFather\" สามารถสแกน QR Code นี้ได้เลยครับผม โดยที่ BotFater จะเป็น Chat Bot ที่เอาไว้ใช้ในการสร้าง/จัดการ Bot ของเราเองนะครับ",[14,3375,3376],{},[979,3377],{"alt":31,"src":3378},"/images/2025-05-10-n8n-with-telegram-notify/images-91fa3609-6307-47b8-9298-70fa23a34007.jpg",[14,3380,3381,3382,3385],{},"จากนั้นใช้ command ",[18,3383,3384],{},"/newbot"," เพื่อเป็นการเริ่มขั้นตอนในการสร้าง Bot ตัวใหม่ขึ้นมา แล้วจะมีการถามชื่อของ Bot ที่เราต้องการจะสร้างขึ้นมานะครับ",[14,3387,3388],{},[979,3389],{"alt":31,"src":3390},"/images/2025-05-10-n8n-with-telegram-notify/images-c191577e-5e9c-40a9-8cbc-c43ee57882b1.jpg",[14,3392,3393,3394,3397,3398,3401,3402,818,3405],{},"เมื่อส่งชื่อไปแล้ว จะมีการถาม ",[18,3395,3396],{},"username"," ที่ต้องการใช้งาน ซึ่งจำเป็นต้องมีคำว่า ",[18,3399,3400],{},"bot"," ต่อท้าย เช่น ",[18,3403,3404],{},"demo_n8n_bot",[18,3406,3407],{},"DemoN8NBot",[14,3409,3410],{},[979,3411],{"alt":31,"src":3412},"/images/2025-05-10-n8n-with-telegram-notify/images-88651bc7-c8c2-4dcf-8fd0-401709b44bce.jpg",[14,3414,3415,3416,3419],{},"จากนั้นจะได้รับ Token สำหรับจัดการ Bot มานะครับ ซึ่งตรงนี้ต้องรักษาเป็นความลับอย่างดี เพราะถ้าหากมีคนได้ Token นี้ไปจะทำให้สามารถเข้าถึงข้อมูลของ Bot ได้ครับ Copy เอา Token เก็บไว้ แล้วกดตรง ",[18,3417,3418],{},"t.me/\u003Cusername>"," เพื่อไปยัง Chat กับ Bot ได้เลย",[14,3421,3422],{},[979,3423],{"alt":31,"src":3424},"/images/2025-05-10-n8n-with-telegram-notify/images-da1134ab-a6b2-4644-bd8a-11ef539abf44.jpg",[14,3426,3427,3428,3431,3432,3435],{},"จากนั้นเปิด browser แล้วไปที่ ",[18,3429,3430],{},"https://api.telegram.org/bot\u003CTOKEN>/getUpdates"," โดยให้แทนที่ ",[18,3433,3434],{},"\u003CTOKEN>"," ด้วย token ที่ copy เอาไว้ได้เลยครับ",[14,3437,3438],{},[979,3439],{"alt":31,"src":3440},"/images/2025-05-10-n8n-with-telegram-notify/images-7f8a4bc4-8437-411f-8222-baa8ee3e25fe.jpg",[14,3442,3443,3444,3447],{},"จากนั้นให้ลองส่ง message อะไรก็ได้ไปยัง telegram bot ของเรา เพื่อค้นหา ",[18,3445,3446],{},"chat id"," เพื่อนำไปใช้งานใน N8N",[14,3449,3450,3451,3453,3454,3457],{},"หลังจากพิมพ์เสร็จแล้วเมื่อ refresh หน้าเว็บดูจะพบกับข้อมูลนะครับ ถ้าขึ้นแบบนี้แสดงว่าถูกต้อง แต่ถ้าไม่ขึ้นอาจจะลองเชค URL ดูว่าใส่ token ลงไปถูกรึเปล่า โดย ",[18,3452,3446],{}," จะอยู่ที่ ",[18,3455,3456],{},"chat: {id}"," ตามที่วงเอาไว้ สามารถ copy เก็บไว้ได้เลยครับ",[14,3459,3460],{},[979,3461],{"alt":31,"src":3462},"/images/2025-05-10-n8n-with-telegram-notify/images-a198f69c-6c16-436f-a836-666e14a49c13.jpg",[14,3464,3465,3466,3469],{},"เข้าไปที่ N8N และเลือก Node ",[18,3467,3468],{},"Send a text message"," ของ telegram",[14,3471,3472],{},[979,3473],{"alt":31,"src":3474},"/images/2025-05-10-n8n-with-telegram-notify/images-80f7301f-dd22-48e9-a4b9-b11166c4e11d.jpg",[14,3476,3477],{},"กดสร้าง Credential ใหม่ และนำ Token จาก Bot Father มาใส่",[14,3479,3480],{},[979,3481],{"alt":31,"src":3482},"/images/2025-05-10-n8n-with-telegram-notify/images-579dd0eb-09a0-455f-94d4-2d401f8635e2.jpg",[14,3484,3485],{},[979,3486],{"alt":31,"src":3487},"/images/2025-05-10-n8n-with-telegram-notify/images-205b9bf5-77c4-450f-be07-9bf7c3537e87.jpg",[14,3489,3490,3491],{},"จากนั้นกรอก Chat ID ที่ copy เก็บเอาไว้และกด \"Test Step\" ได้เลยครับ\n",[979,3492],{"alt":31,"src":3493},"/images/2025-05-10-n8n-with-telegram-notify/images-b3fd3007-4360-4e16-afa7-8bc0d51c48e8.jpg",[14,3495,3496],{},"เพียงเท่านี้ก็จะสามารถส่งการแจ้งเตือนไปยัง Telegram ของเราด้วย Telegram Bot ได้แล้ว",[14,3498,3499],{},[979,3500],{"alt":31,"src":3501},"/images/2025-05-10-n8n-with-telegram-notify/images-c7148489-9d38-4647-a3bf-e7f696f4ddcd.jpg",[10,3503,845],{"id":845},[14,3505,3506,3507,3510,3511,3514],{},"การส่งแจ้งเตือนไปยัง Telegram Bot ต้องการข้อมูล 2 อย่างคือ ",[18,3508,3509],{},"Telegram Token"," และ ",[18,3512,3513],{},"Chat ID"," ซึ่งเป็น Chat ของผู้ใช้งานที่ต้องการส่งข้อความไป โดยวิธีนี้ไม่จำเป็นต้องตั้งค่า Webhook ของ Telegram นะครับ ง่าย ๆ และใช้งานได้จริง ย้ายมาใช้ Telegram กันเยอะ ๆ นะครับ ของดีจริง",{"title":31,"searchDepth":102,"depth":102,"links":3516},[3517,3518,3519],{"id":3355,"depth":102,"text":3356},{"id":3369,"depth":102,"text":3370},{"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",[3526,3527],"2025-04-24-n8n-part-1.md","2025-04-28-n8n-part-2.md",{"title":3345,"description":3521},{"loc":3524},"blogs/2025-05-10-n8n-with-telegram-notify",[968],"WofFEH6vrbSolptMHO5dZKKXe47HlZj8WyAz6ERRcLM",{"id":3534,"title":3535,"body":3536,"date":4458,"description":4459,"draft":956,"extension":957,"meta":4460,"navigation":365,"next":959,"ogTitle":4461,"path":4462,"pin":956,"prev":959,"recommends":959,"seo":4463,"sitemap":4464,"stem":4465,"tags":4466,"__hash__":4467},"content/blogs/2025-05-23-nuxt-tailwindcss-nuxtui-setup.md","Cheat Sheet Nuxt x Tailwindcss x NuxtUI",{"type":7,"value":3537,"toc":4452},[3538,3542,3549,3574,3945,3949,4056,4060,4066,4083,4086,4103,4113,4147,4150,4200,4204,4210,4263,4269,4336,4343,4449],[10,3539,3541],{"id":3540},"init-nuxtjs","Init NuxtJS",[14,3543,3544,3545],{},"Docs: ",[910,3546,3547],{"href":3547,"rel":3548},"https://nuxt.com/docs/getting-started/installation",[914],[23,3550,3552],{"className":75,"code":3551,"language":77,"meta":31,"style":31},"npm create nuxt \u003Cproject-name>\n",[18,3553,3554],{"__ignoreMap":31},[81,3555,3556,3558,3560,3563,3565,3568,3571],{"class":83,"line":84},[81,3557,2825],{"class":87},[81,3559,357],{"class":90},[81,3561,3562],{"class":90}," nuxt",[81,3564,1969],{"class":618},[81,3566,3567],{"class":90},"project-nam",[81,3569,3570],{"class":1449},"e",[81,3572,3573],{"class":618},">\n",[23,3575,3577],{"className":75,"code":3576,"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,3578,3579,3608,3613,3617,3630,3635,3648,3652,3683,3721,3751,3783,3824,3859,3885,3906],{"__ignoreMap":31},[81,3580,3581,3584,3587,3590,3593,3596,3599,3602,3605],{"class":83,"line":84},[81,3582,3583],{"class":87},"✔",[81,3585,3586],{"class":90}," Which",[81,3588,3589],{"class":90}," package",[81,3591,3592],{"class":90}," manager",[81,3594,3595],{"class":90}," would",[81,3597,3598],{"class":90}," you",[81,3600,3601],{"class":90}," like",[81,3603,3604],{"class":90}," to",[81,3606,3607],{"class":90}," use?\n",[81,3609,3610],{"class":83,"line":102},[81,3611,3612],{"class":87},"npm\n",[81,3614,3615],{"class":83,"line":113},[81,3616,366],{"emptyLinePlaceholder":365},[81,3618,3619,3621,3624,3627],{"class":83,"line":247},[81,3620,3583],{"class":87},[81,3622,3623],{"class":90}," Initialize",[81,3625,3626],{"class":90}," git",[81,3628,3629],{"class":90}," repository?\n",[81,3631,3632],{"class":83,"line":393},[81,3633,3634],{"class":87},"Yes\n",[81,3636,3637,3640,3643,3645],{"class":83,"line":404},[81,3638,3639],{"class":87},"ℹ",[81,3641,3642],{"class":90}," Initializing",[81,3644,3626],{"class":90},[81,3646,3647],{"class":90}," repository...\n",[81,3649,3650],{"class":83,"line":415},[81,3651,366],{"emptyLinePlaceholder":365},[81,3653,3654,3657,3660,3662,3664,3666,3668,3671,3674,3677,3680],{"class":83,"line":1438},[81,3655,3656],{"class":87},"❯",[81,3658,3659],{"class":90}," Would",[81,3661,3598],{"class":90},[81,3663,3601],{"class":90},[81,3665,3604],{"class":90},[81,3667,2828],{"class":90},[81,3669,3670],{"class":90}," any",[81,3672,3673],{"class":90}," of",[81,3675,3676],{"class":90}," the",[81,3678,3679],{"class":90}," official",[81,3681,3682],{"class":90}," modules?\n",[81,3684,3685,3688,3691,3694,3697,3700,3703,3706,3709,3712,3715,3718],{"class":83,"line":1455},[81,3686,3687],{"class":87},"◼",[81,3689,3690],{"class":90}," @nuxt/content",[81,3692,3693],{"class":90}," –",[81,3695,3696],{"class":90}," The",[81,3698,3699],{"class":90}," file-based",[81,3701,3702],{"class":90}," CMS",[81,3704,3705],{"class":90}," with",[81,3707,3708],{"class":90}," support",[81,3710,3711],{"class":90}," for",[81,3713,3714],{"class":90}," Markdown,",[81,3716,3717],{"class":90}," YAML,",[81,3719,3720],{"class":90}," JSON\n",[81,3722,3723,3725,3728,3730,3733,3736,3739,3742,3745,3748],{"class":83,"line":1467},[81,3724,3687],{"class":87},[81,3726,3727],{"class":90}," @nuxt/eslint",[81,3729,3693],{"class":90},[81,3731,3732],{"class":90}," Project-aware,",[81,3734,3735],{"class":90}," easy-to-use,",[81,3737,3738],{"class":90}," extensible",[81,3740,3741],{"class":90}," and",[81,3743,3744],{"class":90}," future-proof",[81,3746,3747],{"class":90}," ESLint",[81,3749,3750],{"class":90}," integration\n",[81,3752,3753,3755,3758,3760,3763,3766,3769,3772,3774,3777,3780],{"class":83,"line":1483},[81,3754,3687],{"class":87},[81,3756,3757],{"class":90}," @nuxt/fonts",[81,3759,3693],{"class":90},[81,3761,3762],{"class":90}," Add",[81,3764,3765],{"class":90}," custom",[81,3767,3768],{"class":90}," web",[81,3770,3771],{"class":90}," fonts",[81,3773,3705],{"class":90},[81,3775,3776],{"class":90}," performance",[81,3778,3779],{"class":90}," in",[81,3781,3782],{"class":90}," mind\n",[81,3784,3785,3787,3790,3792,3795,3798,3800,3803,3805,3808,3811,3813,3816,3819,3821],{"class":83,"line":1497},[81,3786,3687],{"class":87},[81,3788,3789],{"class":90}," @nuxt/icon",[81,3791,3693],{"class":90},[81,3793,3794],{"class":90}," Icon",[81,3796,3797],{"class":90}," module",[81,3799,3711],{"class":90},[81,3801,3802],{"class":90}," Nuxt",[81,3804,3705],{"class":90},[81,3806,3807],{"class":90}," 200,000+",[81,3809,3810],{"class":90}," ready",[81,3812,3604],{"class":90},[81,3814,3815],{"class":90}," use",[81,3817,3818],{"class":90}," icons",[81,3820,2898],{"class":90},[81,3822,3823],{"class":90}," Iconify\n",[81,3825,3826,3828,3831,3833,3835,3837,3839,3842,3845,3848,3851,3853,3856],{"class":83,"line":1509},[81,3827,3687],{"class":87},[81,3829,3830],{"class":90}," @nuxt/image",[81,3832,3693],{"class":90},[81,3834,3762],{"class":90},[81,3836,566],{"class":90},[81,3838,3705],{"class":90},[81,3840,3841],{"class":90}," progressive",[81,3843,3844],{"class":90}," processing,",[81,3846,3847],{"class":90}," lazy-loading,",[81,3849,3850],{"class":90}," resizing",[81,3852,3741],{"class":90},[81,3854,3855],{"class":90}," providers",[81,3857,3858],{"class":90}," support\n",[81,3860,3861,3863,3866,3868,3870,3873,3876,3879,3882],{"class":83,"line":1514},[81,3862,3687],{"class":87},[81,3864,3865],{"class":90}," @nuxt/scripts",[81,3867,3693],{"class":90},[81,3869,3762],{"class":90},[81,3871,3872],{"class":90}," 3rd-party",[81,3874,3875],{"class":90}," scripts",[81,3877,3878],{"class":90}," without",[81,3880,3881],{"class":90}," sacrificing",[81,3883,3884],{"class":90}," performance\n",[81,3886,3887,3890,3893,3895,3898,3901,3903],{"class":83,"line":1519},[81,3888,3889],{"class":87},"◻",[81,3891,3892],{"class":90}," @nuxt/test-utils",[81,3894,3693],{"class":90},[81,3896,3897],{"class":90}," Test",[81,3899,3900],{"class":90}," utilities",[81,3902,3711],{"class":90},[81,3904,3905],{"class":90}," Nuxt\n",[81,3907,3908,3910,3913,3915,3917,3920,3923,3926,3929,3932,3935,3937,3939,3942],{"class":83,"line":1535},[81,3909,3687],{"class":87},[81,3911,3912],{"class":90}," @nuxt/ui",[81,3914,3693],{"class":90},[81,3916,3696],{"class":90},[81,3918,3919],{"class":90}," Intuitive",[81,3921,3922],{"class":90}," UI",[81,3924,3925],{"class":90}," Library",[81,3927,3928],{"class":90}," powered",[81,3930,3931],{"class":90}," by",[81,3933,3934],{"class":90}," Reka",[81,3936,3922],{"class":90},[81,3938,3741],{"class":90},[81,3940,3941],{"class":90}," Tailwind",[81,3943,3944],{"class":90}," CSS\n",[10,3946,3948],{"id":3947},"preventing-yarn-allow-only-npm","Preventing Yarn (Allow only NPM)",[23,3950,3952],{"className":75,"code":3951,"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,3953,3954,3963,3979,3995,4009,4014,4018,4023,4040],{"__ignoreMap":31},[81,3955,3956,3959,3961],{"class":83,"line":84},[81,3957,3958],{"class":87},"\"engines\"",[81,3960,1258],{"class":637},[81,3962,1372],{"class":90},[81,3964,3965,3968,3970,3972,3975,3977],{"class":83,"line":102},[81,3966,3967],{"class":87},"    \"yarn\"",[81,3969,1258],{"class":637},[81,3971,641],{"class":128},[81,3973,3974],{"class":90},"Please use NPM!",[81,3976,1255],{"class":128},[81,3978,1282],{"class":90},[81,3980,3981,3984,3986,3988,3991,3993],{"class":83,"line":113},[81,3982,3983],{"class":87},"    \"node\"",[81,3985,1258],{"class":637},[81,3987,641],{"class":128},[81,3989,3990],{"class":90},"22.x",[81,3992,1255],{"class":128},[81,3994,1282],{"class":90},[81,3996,3997,4000,4002,4004,4007],{"class":83,"line":247},[81,3998,3999],{"class":87},"    \"npm\"",[81,4001,1258],{"class":637},[81,4003,641],{"class":128},[81,4005,4006],{"class":90},">=6",[81,4008,647],{"class":128},[81,4010,4011],{"class":83,"line":393},[81,4012,4013],{"class":1449},"},\n",[81,4015,4016],{"class":83,"line":404},[81,4017,366],{"emptyLinePlaceholder":365},[81,4019,4020],{"class":83,"line":415},[81,4021,4022],{"class":1064},"# shell\n",[81,4024,4025,4028,4030,4033,4035,4037],{"class":83,"line":1438},[81,4026,4027],{"class":637},"echo",[81,4029,641],{"class":128},[81,4031,4032],{"class":90},"22",[81,4034,1255],{"class":128},[81,4036,619],{"class":618},[81,4038,4039],{"class":90}," .nvmrc\n",[81,4041,4042,4044,4046,4049,4051,4053],{"class":83,"line":1455},[81,4043,4027],{"class":637},[81,4045,641],{"class":128},[81,4047,4048],{"class":90},"engine-strict=true",[81,4050,1255],{"class":128},[81,4052,619],{"class":618},[81,4054,4055],{"class":90}," .npmrc\n",[10,4057,4059],{"id":4058},"install-tailwindcss-version-4","Install Tailwindcss Version 4",[14,4061,3544,4062],{},[910,4063,4064],{"href":4064,"rel":4065},"https://tailwindcss.com/docs/installation/framework-guides/nuxt",[914],[23,4067,4069],{"className":75,"code":4068,"language":77,"meta":31,"style":31},"npm install tailwindcss @tailwindcss/vite\n",[18,4070,4071],{"__ignoreMap":31},[81,4072,4073,4075,4077,4080],{"class":83,"line":84},[81,4074,2825],{"class":87},[81,4076,2828],{"class":90},[81,4078,4079],{"class":90}," tailwindcss",[81,4081,4082],{"class":90}," @tailwindcss/vite\n",[14,4084,4085],{},"Edit",[23,4087,4091],{"className":4088,"code":4089,"language":4090,"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,4092,4093,4098],{"__ignoreMap":31},[81,4094,4095],{"class":83,"line":84},[81,4096,4097],{"class":1449},"import tailwindcss from \"@tailwindcss/vite\"; export default defineNuxtConfig({\n",[81,4099,4100],{"class":83,"line":102},[81,4101,4102],{"class":1449},"... vite: { plugins: [ tailwindcss(), ], }, });\n",[14,4104,4105,4106,4109,4110],{},"Create a tailwindcss config file ",[18,4107,4108],{},"mkdir -p assets/css"," ",[18,4111,4112],{},"touch assets/css/main.css",[23,4114,4118],{"className":4115,"code":4116,"language":4117,"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,4119,4120,4134],{"__ignoreMap":31},[81,4121,4122,4125,4127,4130,4132],{"class":83,"line":84},[81,4123,4124],{"class":604},"@import",[81,4126,129],{"class":128},[81,4128,4129],{"class":90},"tailwindcss",[81,4131,3019],{"class":128},[81,4133,1387],{"class":628},[81,4135,4136,4138,4140,4143,4145],{"class":83,"line":102},[81,4137,4124],{"class":604},[81,4139,129],{"class":128},[81,4141,4142],{"class":90},"@nuxt/ui",[81,4144,3019],{"class":128},[81,4146,1387],{"class":628},[14,4148,4149],{},"edit nuxt config file",[23,4151,4153],{"className":2879,"code":4152,"language":2881,"meta":31,"style":31},"export default defineNuxtConfig({\n    ...\n    css: ['~/assets/css/main.css'],\n});\n",[18,4154,4155,4168,4173,4192],{"__ignoreMap":31},[81,4156,4157,4159,4161,4164,4166],{"class":83,"line":84},[81,4158,1522],{"class":604},[81,4160,2941],{"class":604},[81,4162,4163],{"class":1528}," defineNuxtConfig",[81,4165,1616],{"class":1449},[81,4167,1242],{"class":628},[81,4169,4170],{"class":83,"line":102},[81,4171,4172],{"class":618},"    ...\n",[81,4174,4175,4178,4181,4183,4186,4188,4190],{"class":83,"line":113},[81,4176,4177],{"class":1624},"    css",[81,4179,4180],{"class":1449},": [",[81,4182,3019],{"class":128},[81,4184,4185],{"class":90},"~/assets/css/main.css",[81,4187,3019],{"class":128},[81,4189,2246],{"class":1449},[81,4191,1282],{"class":628},[81,4193,4194,4196,4198],{"class":83,"line":247},[81,4195,2191],{"class":628},[81,4197,1573],{"class":1449},[81,4199,1387],{"class":628},[10,4201,4203],{"id":4202},"nuxt-layout","Nuxt Layout",[14,4205,3544,4206],{},[910,4207,4208],{"href":4208,"rel":4209},"https://nuxt.com/docs/guide/directory-structure/layouts",[914],[23,4211,4213],{"className":4088,"code":4212,"language":4090,"meta":31,"style":31},"\u003Ctemplate>\n  \u003CNuxtLayout>\n    \u003CNuxtPage />\n  \u003C/NuxtLayout>\n\u003C/template>\n",[18,4214,4215,4225,4235,4245,4254],{"__ignoreMap":31},[81,4216,4217,4219,4223],{"class":83,"line":84},[81,4218,1427],{"class":628},[81,4220,4222],{"class":4221},"s0Prt","template",[81,4224,3573],{"class":628},[81,4226,4227,4230,4233],{"class":83,"line":102},[81,4228,4229],{"class":628},"  \u003C",[81,4231,4232],{"class":4221},"NuxtLayout",[81,4234,3573],{"class":628},[81,4236,4237,4240,4243],{"class":83,"line":113},[81,4238,4239],{"class":628},"    \u003C",[81,4241,4242],{"class":4221},"NuxtPage",[81,4244,3164],{"class":628},[81,4246,4247,4250,4252],{"class":83,"line":247},[81,4248,4249],{"class":628},"  \u003C/",[81,4251,4232],{"class":4221},[81,4253,3573],{"class":628},[81,4255,4256,4259,4261],{"class":83,"line":393},[81,4257,4258],{"class":628},"\u003C/",[81,4260,4222],{"class":4221},[81,4262,3573],{"class":628},[14,4264,4265,4266],{},"Create a new file ",[18,4267,4268],{},"layouts/default.vue",[23,4270,4272],{"className":4088,"code":4271,"language":4090,"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,4273,4274,4282,4291,4308,4320,4328],{"__ignoreMap":31},[81,4275,4276,4278,4280],{"class":83,"line":84},[81,4277,1427],{"class":628},[81,4279,4222],{"class":4221},[81,4281,3573],{"class":628},[81,4283,4284,4286,4289],{"class":83,"line":102},[81,4285,4229],{"class":628},[81,4287,4288],{"class":4221},"div",[81,4290,3573],{"class":628},[81,4292,4293,4295,4297,4299,4302,4304,4306],{"class":83,"line":113},[81,4294,4239],{"class":628},[81,4296,14],{"class":4221},[81,4298,1433],{"class":628},[81,4300,4301],{"class":1449},"Some default layout content shared across all pages",[81,4303,4258],{"class":628},[81,4305,14],{"class":4221},[81,4307,3573],{"class":628},[81,4309,4310,4312,4315,4318],{"class":83,"line":247},[81,4311,4239],{"class":628},[81,4313,4314],{"class":4221},"slot",[81,4316,1944],{"class":4317},"sRwpp",[81,4319,3573],{"class":628},[81,4321,4322,4324,4326],{"class":83,"line":393},[81,4323,4249],{"class":628},[81,4325,4288],{"class":4221},[81,4327,3573],{"class":628},[81,4329,4330,4332,4334],{"class":83,"line":404},[81,4331,4258],{"class":628},[81,4333,4222],{"class":4221},[81,4335,3573],{"class":628},[14,4337,4338,4339,4342],{},"Create a ",[18,4340,4341],{},"pages"," folder",[23,4344,4346],{"className":4088,"code":4345,"language":4090,"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,4347,4348,4356,4373,4381,4385,4410,4419,4435,4441],{"__ignoreMap":31},[81,4349,4350,4352,4354],{"class":83,"line":84},[81,4351,1427],{"class":628},[81,4353,4222],{"class":4221},[81,4355,3573],{"class":628},[81,4357,4358,4360,4362,4364,4367,4369,4371],{"class":83,"line":102},[81,4359,4229],{"class":628},[81,4361,4288],{"class":4221},[81,4363,1433],{"class":628},[81,4365,4366],{"class":1449},"Login Page",[81,4368,4258],{"class":628},[81,4370,4288],{"class":4221},[81,4372,3573],{"class":628},[81,4374,4375,4377,4379],{"class":83,"line":113},[81,4376,4258],{"class":628},[81,4378,4222],{"class":4221},[81,4380,3573],{"class":628},[81,4382,4383],{"class":83,"line":247},[81,4384,366],{"emptyLinePlaceholder":365},[81,4386,4387,4389,4392,4396,4399,4401,4403,4406,4408],{"class":83,"line":393},[81,4388,1427],{"class":628},[81,4390,4391],{"class":4221},"script",[81,4393,4395],{"class":4394},"s4x1N"," setup",[81,4397,4398],{"class":4394}," lang",[81,4400,3130],{"class":628},[81,4402,1255],{"class":128},[81,4404,4405],{"class":90},"ts",[81,4407,1255],{"class":128},[81,4409,3573],{"class":628},[81,4411,4412,4415,4417],{"class":83,"line":404},[81,4413,4414],{"class":1528},"definePageMeta",[81,4416,1616],{"class":1449},[81,4418,1242],{"class":628},[81,4420,4421,4424,4426,4428,4431,4433],{"class":83,"line":415},[81,4422,4423],{"class":1784},"  layout",[81,4425,1258],{"class":628},[81,4427,129],{"class":128},[81,4429,4430],{"class":90},"no-auth",[81,4432,3019],{"class":128},[81,4434,1282],{"class":628},[81,4436,4437,4439],{"class":83,"line":1438},[81,4438,2191],{"class":628},[81,4440,1730],{"class":1449},[81,4442,4443,4445,4447],{"class":83,"line":1455},[81,4444,4258],{"class":628},[81,4446,4391],{"class":4221},[81,4448,3573],{"class":628},[938,4450,4451],{},"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":4453},[4454,4455,4456,4457],{"id":3540,"depth":102,"text":3541},{"id":3947,"depth":102,"text":3948},{"id":4058,"depth":102,"text":4059},{"id":4202,"depth":102,"text":4203},"2025-05-23T00:00:00.000Z","Note วิธีการ setup",{},"NuxtJS with Tailwindcss","/blogs/2025-05-23-nuxt-tailwindcss-nuxtui-setup",{"title":3535,"description":4459},{"loc":4462},"blogs/2025-05-23-nuxt-tailwindcss-nuxtui-setup",[968],"R9fIxzq655lJgo3otne9RiXEDsMi86tRH4PhYutZ9vE",{"id":4469,"title":4470,"body":4471,"date":5844,"description":5845,"draft":956,"extension":957,"meta":5846,"navigation":365,"next":959,"ogTitle":5847,"path":5848,"pin":956,"prev":959,"recommends":5849,"seo":5850,"sitemap":5851,"stem":5852,"tags":5853,"__hash__":5854},"content/blogs/2025-08-30-pagecms-opensource.md","จัดการ Content ด้วย pagecms opensource สุดคูล",{"type":7,"value":4472,"toc":5837},[4473,4477,4492,4501,4510,4519,4523,4526,4534,4541,4546,4550,4564,4569,4572,4575,4582,4585,4588,4592,4595,4616,4641,4659,4669,4679,4687,4690,5834],[10,4474,4476],{"id":4475},"introduction","Introduction",[14,4478,4479,4480,4485,4486,4491],{},"เว็บไซต์นี้พัฒนาโดยใช้ ",[910,4481,4484],{"href":4482,"rel":4483},"https://nuxt.com/",[914],"Nuxt"," ร่วมกับ ",[910,4487,4490],{"href":4488,"rel":4489},"https://content.nuxt.com/",[914],"NuxtContent"," ซึ่งการจะเป็นการนำเอา Content ในรูปแบบของ Markdown มาสร้างเป็น Web Application โดยเป็นไปตามที่มีการตั้งค่าเอาไว้ ซึ่งวิธีนี้จะทำให้สามารถ Custom ตัวเว็บไซต์ได้เกือบ 100% เลย",[14,4493,4494,4495,4500],{},"ผมเก็บ source code ของ website ไว้ที่ github และ deploy ไปยัง ",[910,4496,4499],{"href":4497,"rel":4498},"https://pages.cloudflare.com/",[914],"cloudflare page"," โดยใช้ CICD ของ cloudflare page ครับ ซึ่งวิธีการนี้ทำให้มีค่าใช้จ่ายของเว็บไซต์นี้เกือบจะเป็น 0 บาทต่อเดือนเลยครับ เพราะทุกอย่างใช้จบได้ภายใน Free Tier",[14,4502,4503,4504,4509],{},"แต่ความยากอย่างนึงที่ผมเจอก็คือการที่ต้องจัดการ content ซึ่งอยู่ในรูปแบบของ markdown ช่วงแรก ๆ ก็จะใช้งานผ่าน vscode เอาเลย แต่ช่วงหลังก็เปลี่ยนมาใช้ ",[910,4505,4508],{"href":4506,"rel":4507},"https://obsidian.md/",[914],"obsidian"," โดยปรับแต่งให้สามารถจัดการ content ได้ แต่ก็ยังรู้สึกไม่ค่อยสะดวกเท่าไหร่",[14,4511,4512,4513,4518],{},"จนผมมาเจอเครื่องมือ opensource ตัวนึงชื่อว่า ",[910,4514,4517],{"href":4515,"rel":4516},"https://pagescms.org",[914],"pagecms"," ซึ่งสามารถแก้ไขปัญหาตรงนี้ให้ผมได้และสะดวกมากขึ้นเยอะเลยครับ",[10,4520,4522],{"id":4521},"what-is-pagecms","What is pagecms?",[14,4524,4525],{},"pagecms คือ Content Management System สำหรับ Website ที่เป็นประเภท Static Site เช่น Jekyll, Next.js, VuePress, Hugo หรือประเภทใดก็ตามที่มีการเก็บ content ในลักษณะของ File Markdown",[14,4527,4528,4529,4533],{},"ซึ่งตัวเครื่องมือนี้สามารถใช้งานได้แบบง่าย ๆ โดยการเข้าใช้งานที่ ",[910,4530,4531],{"href":4531,"rel":4532},"https://app.pagescms.org/",[914]," จากนั้นทำการเชื่อมต่อกับ Github และเลือก Repo ที่ต้องการได้เลย",[14,4535,4536,4537],{},"สามารถกำหนดการตั้งค่าต่าง ๆ ได้ผ่านตัวเว็บไซต์เลยครับ กำหนด config ให้ตรงกับ project structure โดยดูเพิ่มเติมได้จาก ",[910,4538,4539],{"href":4539,"rel":4540},"https://pagescms.org/docs/configuration/",[914],[14,4542,4543],{},[979,4544],{"alt":31,"src":4545},"/images/2025-08-30-pagecms-opensource/page-cms-1.png",[10,4547,4549],{"id":4548},"สิ่งที่-pagecms-สามารถทำได้","สิ่งที่ pagecms สามารถทำได้",[904,4551,4552,4555,4558,4561],{},[907,4553,4554],{},"เพิ่ม/ลบ/แก้ไข content ของ website ได้ โดยจะเป็นการ commit เพื่อแก้ไข repo บน github โดยตรง",[907,4556,4557],{},"สามารถเพิ่ม/ลบ จัดการ media ภายใน website หรือ repo ได้",[907,4559,4560],{},"สลับ branch ในการจัดการได้",[907,4562,4563],{},"สามารถ invite คนอื่นเข้ามาร่วมแก้ไขได้ด้วย โดยอันนี้จะเป็นการ invite เข้า repo บน github เลยนะครับ",[14,4565,4566],{},[979,4567],{"alt":31,"src":4568},"/images/2025-08-30-pagecms-opensource/page-cms-2.png",[14,4570,4571],{},"ซึ่งทั้งหมดจะทำงานบน github อีกทีนึง ตัว pagecms จะเป็นแค่หน้ากากทำให้ใช้งานได้ง่ายยิ่งขึ้นครับ",[10,4573,4574],{"id":4574},"คำแนะนำ",[14,4576,4577,4578],{},"จากที่ลองใช้งานมารู้สึกโอเคนะครับ แต่ตอนนี้ project กำลังอยู่ในช่วงพัฒนาคิดว่าจะมีฟีเจอร์มาให้ใช้งานอีกเพียบ สามารถติดตามได้ที่ ",[910,4579,4580],{"href":4580,"rel":4581},"https://github.com/hunvreus/pagescms",[914],[14,4583,4584],{},"แนะนำให้สร้าง branch แยกกับ branch ที่มีการเปิด CICD เอาไว้นะครับ เพราะว่าทุกครั้งที่มีการ Upload File ในช่อง \"Media\" มันจะเป็นการสร้าง commit ใหม่ ซึ่งจะ trigger CICD เลยครับ แนะนำว่าให้สร้าง branch ใหม่แล้วถ้าต้องการ update ค่อย merge เข้าไปใน branch หลักอีกที",[14,4586,4587],{},"สำหรับผมมองว่าเป็นแค่ Add-on ที่เพิ่มเข้ามา ถ้าวันนึงมันไม่ work แล้วก็สามารถนำออกไปได้เลยครับ",[10,4589,4591],{"id":4590},"อัปเดต-2026","อัปเดต 2026",[14,4593,4594],{},"โพสต์นี้เขียนไว้ตั้งแต่ปี 2025 พอกลับมารื้อ config ใหม่อีกรอบ ก็เจอว่า Pages CMS ขยับไปไกลกว่าที่คิด ของบางอย่างที่เคยเขียนไว้ข้างบนใช้ไม่ได้แล้วครับ",[14,4596,4597,4603,4604,4607,4608,4611,4612,4615],{},[39,4598,4599,4602],{},[18,4600,4601],{},"creatable"," ใน select หายไปแล้ว"," เดิมผมใส่ ",[18,4605,4606],{},"creatable: true"," ไว้ที่ field ",[18,4609,4610],{},"tags"," เพื่อจะได้พิมพ์แท็กใหม่จากในหน้าเว็บได้เลย ตอนนี้ไม่มีโค้ดส่วนไหนอ่านคีย์นี้แล้ว select เหลือแค่ค่าที่ประกาศไว้ล่วงหน้าอย่างเดียว อยากเพิ่มแท็กใหม่ก็ต้องมาแก้ที่ ",[18,4613,4614],{},"values"," เอง",[14,4617,4618,4625,4626,4629,4630,4629,4633,4636,4637,4640],{},[39,4619,4620,4621,4624],{},"มี field ชนิด ",[18,4622,4623],{},"reference"," มาแทน"," ชี้ไปที่ collection อื่นหรือ collection ตัวเองก็ได้ แล้วเลือกเอาจาก dropdown ผมย้าย ",[18,4627,4628],{},"prev"," / ",[18,4631,4632],{},"next",[18,4634,4635],{},"recommends"," จาก string มาเป็นอันนี้หมด ไม่ต้องพิมพ์ slug เองอีก จุดที่ต้องระวังคือค่าที่มันเขียนลงไฟล์เป็นชื่อไฟล์เต็มพร้อม ",[18,4638,4639],{},".md"," ถ้าฝั่งเว็บเอาไปต่อเป็น URL ตรง ๆ ต้องตัดนามสกุลทิ้งก่อน",[14,4642,4643,4646,4647,4650,4651,4654,4655,4658],{},[39,4644,4645],{},"ตั้งชื่อไฟล์ให้อัตโนมัติได้"," ผ่าน ",[18,4648,4649],{},"filename.template"," เขียนเป็น pattern อย่าง ",[18,4652,4653],{},"{year}-{month}-{day}-{ogTitle}.md"," แต่ token วันที่ตรงนี้คือวันที่ตอนกดสร้าง ไม่ได้ดึงจาก field ",[18,4656,4657],{},"date"," ที่กรอกไว้ ถ้าจะย้อนวันที่ก็ต้องแก้ชื่อไฟล์เองอยู่ดี",[14,4660,4661,4664,4665,4668],{},[39,4662,4663],{},"ล็อกการลบได้"," ด้วย ",[18,4666,4667],{},"operations"," เปิดปิด create, rename, delete แยกกันได้ ผมปิด delete ไว้ กันมือลั่นลบโพสต์จากหน้าเว็บ",[14,4670,4671,4674,4675,4678],{},[39,4672,4673],{},"ปุ่มยิง GitHub Actions"," อันนี้สนุกสุด ประกาศ ",[18,4676,4677],{},"actions"," ไว้ใน config แล้ว Pages CMS จะเอาปุ่มไปแปะให้ กดแล้วมัน dispatch workflow พร้อมส่ง payload บอกว่าใครกดและกดมาจากโพสต์ไหน ผมเอามาทำปุ่มรัน typecheck, lint กับ build ก่อน publish",[14,4680,4681,4686],{},[39,4682,4683],{},[18,4684,4685],{},"settings"," คุม commit message ที่ CMS สร้างขึ้นมา กับสั่งให้ merge field ที่ไม่ได้ประกาศไว้แทนที่จะลบทิ้ง อันหลังนี่สำคัญ ถ้าไม่เปิดไว้ field ที่มีอยู่ในไฟล์แต่ไม่มีใน config จะหายไปตอน save",[14,4688,4689],{},"แล้วนี่คือ config ที่ใช้อยู่ตอนนี้ครับ",[23,4691,4695],{"className":4692,"code":4693,"language":4694,"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,4696,4697,4705,4715,4725,4765,4769,4775,4782,4787,4798,4805,4812,4826,4840,4854,4868,4872,4879,4892,4902,4912,4922,4929,4934,4939,4944,4957,4967,4974,4982,4990,4999,5006,5018,5028,5038,5047,5057,5064,5093,5103,5121,5143,5150,5160,5170,5177,5187,5196,5206,5215,5226,5235,5243,5251,5262,5271,5280,5288,5299,5309,5318,5327,5335,5346,5358,5368,5378,5385,5395,5434,5446,5456,5466,5476,5488,5498,5507,5516,5528,5538,5548,5555,5565,5580,5595,5610,5622,5632,5641,5648,5657,5670,5683,5696,5708,5718,5727,5734,5743,5752,5763,5776,5789,5802,5814,5824],{"__ignoreMap":31},[81,4698,4699,4702],{"class":83,"line":84},[81,4700,4701],{"class":4221},"media",[81,4703,4704],{"class":628},":\n",[81,4706,4707,4710,4712],{"class":83,"line":102},[81,4708,4709],{"class":4221},"  input",[81,4711,1258],{"class":628},[81,4713,4714],{"class":90}," public/images\n",[81,4716,4717,4720,4722],{"class":83,"line":113},[81,4718,4719],{"class":4221},"  output",[81,4721,1258],{"class":628},[81,4723,4724],{"class":90}," /images\n",[81,4726,4727,4730,4732,4734,4737,4739,4742,4744,4747,4749,4752,4754,4757,4759,4762],{"class":83,"line":247},[81,4728,4729],{"class":4221},"  extensions",[81,4731,1258],{"class":628},[81,4733,2235],{"class":628},[81,4735,4736],{"class":90},"jpg",[81,4738,1724],{"class":628},[81,4740,4741],{"class":90}," jpeg",[81,4743,1724],{"class":628},[81,4745,4746],{"class":90}," png",[81,4748,1724],{"class":628},[81,4750,4751],{"class":90}," webp",[81,4753,1724],{"class":628},[81,4755,4756],{"class":90}," gif",[81,4758,1724],{"class":628},[81,4760,4761],{"class":90}," svg",[81,4763,4764],{"class":628},"]\n",[81,4766,4767],{"class":83,"line":393},[81,4768,366],{"emptyLinePlaceholder":365},[81,4770,4771,4773],{"class":83,"line":404},[81,4772,4685],{"class":4221},[81,4774,4704],{"class":628},[81,4776,4777,4780],{"class":83,"line":415},[81,4778,4779],{"class":4221},"  content",[81,4781,4704],{"class":628},[81,4783,4784],{"class":83,"line":1438},[81,4785,4786],{"class":1064},"    # เก็บ field ที่ไม่ได้ประกาศข้างล่างไว้ ไม่ต้องลบทิ้งตอน save\n",[81,4788,4789,4792,4794],{"class":83,"line":1455},[81,4790,4791],{"class":4221},"    merge",[81,4793,1258],{"class":628},[81,4795,4797],{"class":4796},"salyU"," true\n",[81,4799,4800,4803],{"class":83,"line":1467},[81,4801,4802],{"class":4221},"  commit",[81,4804,4704],{"class":628},[81,4806,4807,4810],{"class":83,"line":1483},[81,4808,4809],{"class":4221},"    templates",[81,4811,4704],{"class":628},[81,4813,4814,4817,4819,4821,4824],{"class":83,"line":1497},[81,4815,4816],{"class":4221},"      create",[81,4818,1258],{"class":628},[81,4820,129],{"class":128},[81,4822,4823],{"class":90},"content(blogs): add {path}",[81,4825,135],{"class":128},[81,4827,4828,4831,4833,4835,4838],{"class":83,"line":1509},[81,4829,4830],{"class":4221},"      update",[81,4832,1258],{"class":628},[81,4834,129],{"class":128},[81,4836,4837],{"class":90},"content(blogs): update {path}",[81,4839,135],{"class":128},[81,4841,4842,4845,4847,4849,4852],{"class":83,"line":1514},[81,4843,4844],{"class":4221},"      delete",[81,4846,1258],{"class":628},[81,4848,129],{"class":128},[81,4850,4851],{"class":90},"content(blogs): remove {path}",[81,4853,135],{"class":128},[81,4855,4856,4859,4861,4863,4866],{"class":83,"line":1519},[81,4857,4858],{"class":4221},"      rename",[81,4860,1258],{"class":628},[81,4862,129],{"class":128},[81,4864,4865],{"class":90},"content(blogs): rename {oldPath} to {newPath}",[81,4867,135],{"class":128},[81,4869,4870],{"class":83,"line":1535},[81,4871,366],{"emptyLinePlaceholder":365},[81,4873,4874,4877],{"class":83,"line":1548},[81,4875,4876],{"class":4221},"content",[81,4878,4704],{"class":628},[81,4880,4881,4884,4887,4889],{"class":83,"line":1559},[81,4882,4883],{"class":628},"  -",[81,4885,4886],{"class":4221}," name",[81,4888,1258],{"class":628},[81,4890,4891],{"class":90}," blogs\n",[81,4893,4894,4897,4899],{"class":83,"line":1570},[81,4895,4896],{"class":4221},"    label",[81,4898,1258],{"class":628},[81,4900,4901],{"class":90}," Blog Posts\n",[81,4903,4904,4907,4909],{"class":83,"line":1582},[81,4905,4906],{"class":4221},"    type",[81,4908,1258],{"class":628},[81,4910,4911],{"class":90}," collection\n",[81,4913,4914,4917,4919],{"class":83,"line":1588},[81,4915,4916],{"class":4221},"    path",[81,4918,1258],{"class":628},[81,4920,4921],{"class":90}," content/blogs\n",[81,4923,4924,4927],{"class":83,"line":1647},[81,4925,4926],{"class":4221},"    filename",[81,4928,4704],{"class":628},[81,4930,4931],{"class":83,"line":1692},[81,4932,4933],{"class":1064},"      # slug ของ route คือชื่อไฟล์ ตัดนามสกุล ตัว date prefix เลยสำคัญ\n",[81,4935,4936],{"class":83,"line":1697},[81,4937,4938],{"class":1064},"      # แต่ {year}/{month}/{day} คือวันที่ตอนกดสร้าง ไม่ได้ตามค่าใน field date\n",[81,4940,4941],{"class":83,"line":1703},[81,4942,4943],{"class":1064},"      # เป็นแค่ค่าตั้งต้น field: create เปิดให้แก้เองได้\n",[81,4945,4946,4949,4951,4953,4955],{"class":83,"line":1733},[81,4947,4948],{"class":4221},"      template",[81,4950,1258],{"class":628},[81,4952,129],{"class":128},[81,4954,4653],{"class":90},[81,4956,135],{"class":128},[81,4958,4959,4962,4964],{"class":83,"line":1738},[81,4960,4961],{"class":4221},"      field",[81,4963,1258],{"class":628},[81,4965,4966],{"class":90}," create\n",[81,4968,4969,4972],{"class":83,"line":1768},[81,4970,4971],{"class":4221},"    operations",[81,4973,4704],{"class":628},[81,4975,4976,4978,4980],{"class":83,"line":1773},[81,4977,4816],{"class":4221},[81,4979,1258],{"class":628},[81,4981,4797],{"class":4796},[81,4983,4984,4986,4988],{"class":83,"line":1781},[81,4985,4858],{"class":4221},[81,4987,1258],{"class":628},[81,4989,4797],{"class":4796},[81,4991,4992,4994,4996],{"class":83,"line":1794},[81,4993,4844],{"class":4221},[81,4995,1258],{"class":628},[81,4997,4998],{"class":4796}," false\n",[81,5000,5001,5004],{"class":83,"line":1806},[81,5002,5003],{"class":4221},"    actions",[81,5005,4704],{"class":628},[81,5007,5008,5011,5013,5015],{"class":83,"line":1814},[81,5009,5010],{"class":628},"      -",[81,5012,4886],{"class":4221},[81,5014,1258],{"class":628},[81,5016,5017],{"class":90}," run-checks\n",[81,5019,5020,5023,5025],{"class":83,"line":1820},[81,5021,5022],{"class":4221},"        label",[81,5024,1258],{"class":628},[81,5026,5027],{"class":90}," Run checks\n",[81,5029,5030,5033,5035],{"class":83,"line":1825},[81,5031,5032],{"class":4221},"        workflow",[81,5034,1258],{"class":628},[81,5036,5037],{"class":90}," pages-cms-checks.yml\n",[81,5039,5040,5043,5045],{"class":83,"line":1830},[81,5041,5042],{"class":4221},"        scope",[81,5044,1258],{"class":628},[81,5046,4911],{"class":90},[81,5048,5049,5052,5054],{"class":83,"line":1847},[81,5050,5051],{"class":4221},"        ref",[81,5053,1258],{"class":628},[81,5055,5056],{"class":90}," current\n",[81,5058,5059,5062],{"class":83,"line":1860},[81,5060,5061],{"class":4221},"    view",[81,5063,4704],{"class":628},[81,5065,5066,5069,5071,5073,5076,5078,5081,5083,5086,5088,5091],{"class":83,"line":1871},[81,5067,5068],{"class":4221},"      fields",[81,5070,1258],{"class":628},[81,5072,2235],{"class":628},[81,5074,5075],{"class":90},"title",[81,5077,1724],{"class":628},[81,5079,5080],{"class":90}," date",[81,5082,1724],{"class":628},[81,5084,5085],{"class":90}," tags",[81,5087,1724],{"class":628},[81,5089,5090],{"class":90}," draft",[81,5092,4764],{"class":628},[81,5094,5095,5098,5100],{"class":83,"line":1882},[81,5096,5097],{"class":4221},"      primary",[81,5099,1258],{"class":628},[81,5101,5102],{"class":90}," title\n",[81,5104,5105,5108,5110,5112,5114,5116,5119],{"class":83,"line":1892},[81,5106,5107],{"class":4221},"      sort",[81,5109,1258],{"class":628},[81,5111,2235],{"class":628},[81,5113,4657],{"class":90},[81,5115,1724],{"class":628},[81,5117,5118],{"class":90}," title",[81,5120,4764],{"class":628},[81,5122,5123,5126,5128,5130,5132,5134,5137,5139,5141],{"class":83,"line":1909},[81,5124,5125],{"class":4221},"      search",[81,5127,1258],{"class":628},[81,5129,2235],{"class":628},[81,5131,5075],{"class":90},[81,5133,1724],{"class":628},[81,5135,5136],{"class":90}," description",[81,5138,1724],{"class":628},[81,5140,5085],{"class":90},[81,5142,4764],{"class":628},[81,5144,5145,5148],{"class":83,"line":1922},[81,5146,5147],{"class":4221},"      default",[81,5149,4704],{"class":628},[81,5151,5152,5155,5157],{"class":83,"line":1952},[81,5153,5154],{"class":4221},"        sort",[81,5156,1258],{"class":628},[81,5158,5159],{"class":90}," date\n",[81,5161,5162,5165,5167],{"class":83,"line":1957},[81,5163,5164],{"class":4221},"        order",[81,5166,1258],{"class":628},[81,5168,5169],{"class":90}," desc\n",[81,5171,5172,5175],{"class":83,"line":1975},[81,5173,5174],{"class":4221},"    fields",[81,5176,4704],{"class":628},[81,5178,5179,5181,5183,5185],{"class":83,"line":1991},[81,5180,5010],{"class":628},[81,5182,4886],{"class":4221},[81,5184,1258],{"class":628},[81,5186,5102],{"class":90},[81,5188,5189,5191,5193],{"class":83,"line":1996},[81,5190,5022],{"class":4221},[81,5192,1258],{"class":628},[81,5194,5195],{"class":90}," Title\n",[81,5197,5198,5201,5203],{"class":83,"line":2025},[81,5199,5200],{"class":4221},"        type",[81,5202,1258],{"class":628},[81,5204,5205],{"class":90}," string\n",[81,5207,5208,5211,5213],{"class":83,"line":2049},[81,5209,5210],{"class":4221},"        required",[81,5212,1258],{"class":628},[81,5214,4797],{"class":4796},[81,5216,5217,5219,5221,5223],{"class":83,"line":2054},[81,5218,5010],{"class":628},[81,5220,4886],{"class":4221},[81,5222,1258],{"class":628},[81,5224,5225],{"class":90}," ogTitle\n",[81,5227,5228,5230,5232],{"class":83,"line":2061},[81,5229,5022],{"class":4221},[81,5231,1258],{"class":628},[81,5233,5234],{"class":90}," OG Text\n",[81,5236,5237,5239,5241],{"class":83,"line":2069},[81,5238,5200],{"class":4221},[81,5240,1258],{"class":628},[81,5242,5205],{"class":90},[81,5244,5245,5247,5249],{"class":83,"line":2077},[81,5246,5210],{"class":4221},[81,5248,1258],{"class":628},[81,5250,4797],{"class":4796},[81,5252,5253,5255,5257,5259],{"class":83,"line":2085},[81,5254,5010],{"class":628},[81,5256,4886],{"class":4221},[81,5258,1258],{"class":628},[81,5260,5261],{"class":90}," description\n",[81,5263,5264,5266,5268],{"class":83,"line":2093},[81,5265,5022],{"class":4221},[81,5267,1258],{"class":628},[81,5269,5270],{"class":90}," Description\n",[81,5272,5273,5275,5277],{"class":83,"line":2106},[81,5274,5200],{"class":4221},[81,5276,1258],{"class":628},[81,5278,5279],{"class":90}," text\n",[81,5281,5282,5284,5286],{"class":83,"line":2111},[81,5283,5210],{"class":4221},[81,5285,1258],{"class":628},[81,5287,4797],{"class":4796},[81,5289,5291,5293,5295,5297],{"class":83,"line":5290},59,[81,5292,5010],{"class":628},[81,5294,4886],{"class":4221},[81,5296,1258],{"class":628},[81,5298,5159],{"class":90},[81,5300,5302,5304,5306],{"class":83,"line":5301},60,[81,5303,5022],{"class":4221},[81,5305,1258],{"class":628},[81,5307,5308],{"class":90}," Date\n",[81,5310,5312,5314,5316],{"class":83,"line":5311},61,[81,5313,5200],{"class":4221},[81,5315,1258],{"class":628},[81,5317,5159],{"class":90},[81,5319,5321,5323,5325],{"class":83,"line":5320},62,[81,5322,5210],{"class":4221},[81,5324,1258],{"class":628},[81,5326,4797],{"class":4796},[81,5328,5330,5333],{"class":83,"line":5329},63,[81,5331,5332],{"class":4221},"        options",[81,5334,4704],{"class":628},[81,5336,5338,5341,5343],{"class":83,"line":5337},64,[81,5339,5340],{"class":4221},"          format",[81,5342,1258],{"class":628},[81,5344,5345],{"class":90}," yyyy-MM-dd\n",[81,5347,5349,5351,5353,5355],{"class":83,"line":5348},65,[81,5350,5010],{"class":628},[81,5352,4886],{"class":4221},[81,5354,1258],{"class":628},[81,5356,5357],{"class":90}," tags\n",[81,5359,5361,5363,5365],{"class":83,"line":5360},66,[81,5362,5022],{"class":4221},[81,5364,1258],{"class":628},[81,5366,5367],{"class":90}," Tags\n",[81,5369,5371,5373,5375],{"class":83,"line":5370},67,[81,5372,5200],{"class":4221},[81,5374,1258],{"class":628},[81,5376,5377],{"class":90}," select\n",[81,5379,5381,5383],{"class":83,"line":5380},68,[81,5382,5332],{"class":4221},[81,5384,4704],{"class":628},[81,5386,5388,5391,5393],{"class":83,"line":5387},69,[81,5389,5390],{"class":4221},"          multiple",[81,5392,1258],{"class":628},[81,5394,4797],{"class":4796},[81,5396,5398,5401,5403,5405,5407,5409,5412,5414,5417,5419,5422,5424,5427,5429,5432],{"class":83,"line":5397},70,[81,5399,5400],{"class":4221},"          values",[81,5402,1258],{"class":628},[81,5404,2235],{"class":628},[81,5406,968],{"class":90},[81,5408,1724],{"class":628},[81,5410,5411],{"class":90}," Homelab",[81,5413,1724],{"class":628},[81,5415,5416],{"class":90}," AI",[81,5418,1724],{"class":628},[81,5420,5421],{"class":90}," Tools",[81,5423,1724],{"class":628},[81,5425,5426],{"class":90}," Python",[81,5428,1724],{"class":628},[81,5430,5431],{"class":90}," Lifestyle",[81,5433,4764],{"class":628},[81,5435,5437,5439,5441,5443],{"class":83,"line":5436},71,[81,5438,5010],{"class":628},[81,5440,4886],{"class":4221},[81,5442,1258],{"class":628},[81,5444,5445],{"class":90}," draft\n",[81,5447,5449,5451,5453],{"class":83,"line":5448},72,[81,5450,5022],{"class":4221},[81,5452,1258],{"class":628},[81,5454,5455],{"class":90}," Draft\n",[81,5457,5459,5461,5463],{"class":83,"line":5458},73,[81,5460,5200],{"class":4221},[81,5462,1258],{"class":628},[81,5464,5465],{"class":90}," boolean\n",[81,5467,5469,5472,5474],{"class":83,"line":5468},74,[81,5470,5471],{"class":4221},"        default",[81,5473,1258],{"class":628},[81,5475,4797],{"class":4796},[81,5477,5479,5481,5483,5485],{"class":83,"line":5478},75,[81,5480,5010],{"class":628},[81,5482,4886],{"class":4221},[81,5484,1258],{"class":628},[81,5486,5487],{"class":90}," pin\n",[81,5489,5491,5493,5495],{"class":83,"line":5490},76,[81,5492,5022],{"class":4221},[81,5494,1258],{"class":628},[81,5496,5497],{"class":90}," Pin\n",[81,5499,5501,5503,5505],{"class":83,"line":5500},77,[81,5502,5200],{"class":4221},[81,5504,1258],{"class":628},[81,5506,5465],{"class":90},[81,5508,5510,5512,5514],{"class":83,"line":5509},78,[81,5511,5471],{"class":4221},[81,5513,1258],{"class":628},[81,5515,4998],{"class":4796},[81,5517,5519,5521,5523,5525],{"class":83,"line":5518},79,[81,5520,5010],{"class":628},[81,5522,4886],{"class":4221},[81,5524,1258],{"class":628},[81,5526,5527],{"class":90}," prev\n",[81,5529,5531,5533,5535],{"class":83,"line":5530},80,[81,5532,5022],{"class":4221},[81,5534,1258],{"class":628},[81,5536,5537],{"class":90}," Previous Post\n",[81,5539,5541,5543,5545],{"class":83,"line":5540},81,[81,5542,5200],{"class":4221},[81,5544,1258],{"class":628},[81,5546,5547],{"class":90}," reference\n",[81,5549,5551,5553],{"class":83,"line":5550},82,[81,5552,5332],{"class":4221},[81,5554,4704],{"class":628},[81,5556,5558,5561,5563],{"class":83,"line":5557},83,[81,5559,5560],{"class":4221},"          collection",[81,5562,1258],{"class":628},[81,5564,4891],{"class":90},[81,5566,5568,5571,5573,5575,5578],{"class":83,"line":5567},84,[81,5569,5570],{"class":4221},"          value",[81,5572,1258],{"class":628},[81,5574,129],{"class":128},[81,5576,5577],{"class":90},"{name}",[81,5579,135],{"class":128},[81,5581,5583,5586,5588,5590,5593],{"class":83,"line":5582},85,[81,5584,5585],{"class":4221},"          label",[81,5587,1258],{"class":628},[81,5589,129],{"class":128},[81,5591,5592],{"class":90},"{primary}",[81,5594,135],{"class":128},[81,5596,5598,5601,5603,5605,5608],{"class":83,"line":5597},86,[81,5599,5600],{"class":4221},"          search",[81,5602,1258],{"class":628},[81,5604,129],{"class":128},[81,5606,5607],{"class":90},"title,description",[81,5609,135],{"class":128},[81,5611,5613,5615,5617,5619],{"class":83,"line":5612},87,[81,5614,5010],{"class":628},[81,5616,4886],{"class":4221},[81,5618,1258],{"class":628},[81,5620,5621],{"class":90}," next\n",[81,5623,5625,5627,5629],{"class":83,"line":5624},88,[81,5626,5022],{"class":4221},[81,5628,1258],{"class":628},[81,5630,5631],{"class":90}," Next Post\n",[81,5633,5635,5637,5639],{"class":83,"line":5634},89,[81,5636,5200],{"class":4221},[81,5638,1258],{"class":628},[81,5640,5547],{"class":90},[81,5642,5644,5646],{"class":83,"line":5643},90,[81,5645,5332],{"class":4221},[81,5647,4704],{"class":628},[81,5649,5651,5653,5655],{"class":83,"line":5650},91,[81,5652,5560],{"class":4221},[81,5654,1258],{"class":628},[81,5656,4891],{"class":90},[81,5658,5660,5662,5664,5666,5668],{"class":83,"line":5659},92,[81,5661,5570],{"class":4221},[81,5663,1258],{"class":628},[81,5665,129],{"class":128},[81,5667,5577],{"class":90},[81,5669,135],{"class":128},[81,5671,5673,5675,5677,5679,5681],{"class":83,"line":5672},93,[81,5674,5585],{"class":4221},[81,5676,1258],{"class":628},[81,5678,129],{"class":128},[81,5680,5592],{"class":90},[81,5682,135],{"class":128},[81,5684,5686,5688,5690,5692,5694],{"class":83,"line":5685},94,[81,5687,5600],{"class":4221},[81,5689,1258],{"class":628},[81,5691,129],{"class":128},[81,5693,5607],{"class":90},[81,5695,135],{"class":128},[81,5697,5699,5701,5703,5705],{"class":83,"line":5698},95,[81,5700,5010],{"class":628},[81,5702,4886],{"class":4221},[81,5704,1258],{"class":628},[81,5706,5707],{"class":90}," recommends\n",[81,5709,5711,5713,5715],{"class":83,"line":5710},96,[81,5712,5022],{"class":4221},[81,5714,1258],{"class":628},[81,5716,5717],{"class":90}," Recommended Posts\n",[81,5719,5721,5723,5725],{"class":83,"line":5720},97,[81,5722,5200],{"class":4221},[81,5724,1258],{"class":628},[81,5726,5547],{"class":90},[81,5728,5730,5732],{"class":83,"line":5729},98,[81,5731,5332],{"class":4221},[81,5733,4704],{"class":628},[81,5735,5737,5739,5741],{"class":83,"line":5736},99,[81,5738,5560],{"class":4221},[81,5740,1258],{"class":628},[81,5742,4891],{"class":90},[81,5744,5746,5748,5750],{"class":83,"line":5745},100,[81,5747,5390],{"class":4221},[81,5749,1258],{"class":628},[81,5751,4797],{"class":4796},[81,5753,5755,5758,5760],{"class":83,"line":5754},101,[81,5756,5757],{"class":4221},"          max",[81,5759,1258],{"class":628},[81,5761,5762],{"class":655}," 3\n",[81,5764,5766,5768,5770,5772,5774],{"class":83,"line":5765},102,[81,5767,5570],{"class":4221},[81,5769,1258],{"class":628},[81,5771,129],{"class":128},[81,5773,5577],{"class":90},[81,5775,135],{"class":128},[81,5777,5779,5781,5783,5785,5787],{"class":83,"line":5778},103,[81,5780,5585],{"class":4221},[81,5782,1258],{"class":628},[81,5784,129],{"class":128},[81,5786,5592],{"class":90},[81,5788,135],{"class":128},[81,5790,5792,5794,5796,5798,5800],{"class":83,"line":5791},104,[81,5793,5600],{"class":4221},[81,5795,1258],{"class":628},[81,5797,129],{"class":128},[81,5799,5607],{"class":90},[81,5801,135],{"class":128},[81,5803,5805,5807,5809,5811],{"class":83,"line":5804},105,[81,5806,5010],{"class":628},[81,5808,4886],{"class":4221},[81,5810,1258],{"class":628},[81,5812,5813],{"class":90}," body\n",[81,5815,5817,5819,5821],{"class":83,"line":5816},106,[81,5818,5022],{"class":4221},[81,5820,1258],{"class":628},[81,5822,5823],{"class":90}," Body\n",[81,5825,5827,5829,5831],{"class":83,"line":5826},107,[81,5828,5200],{"class":4221},[81,5830,1258],{"class":628},[81,5832,5833],{"class":90}," rich-text\n",[938,5835,5836],{},"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":5838},[5839,5840,5841,5842,5843],{"id":4475,"depth":102,"text":4476},{"id":4521,"depth":102,"text":4522},{"id":4548,"depth":102,"text":4549},{"id":4574,"depth":102,"text":4574},{"id":4590,"depth":102,"text":4591},"2025-08-30T00:00:00.000Z","แนะนำเครื่องมือสำหรับการจัดการ content สำหรับเว็บไซต์ประเภท static site",{},"First Time With PageCMS","/blogs/2025-08-30-pagecms-opensource",[1206],{"title":4470,"description":5845},{"loc":5848},"blogs/2025-08-30-pagecms-opensource",[968],"9PKbWdfCB6mvCgxZ08pX8Ia2UL0uQ519fgSrhNPYVXI",{"id":5856,"title":5857,"body":5858,"date":6424,"description":6425,"draft":956,"extension":957,"meta":6426,"navigation":365,"next":959,"ogTitle":6427,"path":6428,"pin":956,"prev":959,"recommends":959,"seo":6429,"sitemap":6430,"stem":6431,"tags":6432,"__hash__":6433},"content/blogs/2025-11-03-dirsearch-uv.md","ติดตั้ง dirsearch ด้วย uv แล้วใช้งานไม่ได้",{"type":7,"value":5859,"toc":6422},[5860,5863,5884,5887,6096,6099,6109,6125,6416,6419],[14,5861,5862],{},"หากทำการติดตั้ง dirsearch ด้วยเครื่องมือ",[23,5864,5866],{"className":75,"code":5865,"language":77,"meta":31,"style":31},"uv tool install dirsearch==0.4.3\n",[18,5867,5868],{"__ignoreMap":31},[81,5869,5870,5873,5876,5878,5881],{"class":83,"line":84},[81,5871,5872],{"class":87},"uv",[81,5874,5875],{"class":90}," tool",[81,5877,2828],{"class":90},[81,5879,5880],{"class":90}," dirsearch==",[81,5882,5883],{"class":655},"0.4.3\n",[14,5885,5886],{},"จะเจอกับ error",[23,5888,5890],{"className":75,"code":5889,"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,5891,5892,5923,5943,5968,5990,6019,6033,6061,6076],{"__ignoreMap":31},[81,5893,5894,5897,5900,5903,5906,5909,5912,5915,5917,5920],{"class":83,"line":84},[81,5895,5896],{"class":87},"PS",[81,5898,5899],{"class":90}," C:",[81,5901,5902],{"class":98},"\\U",[81,5904,5905],{"class":90},"sers",[81,5907,5908],{"class":98},"\\r",[81,5910,5911],{"class":90},"obi",[81,5913,5914],{"class":1449},"n",[81,5916,1433],{"class":618},[81,5918,5919],{"class":90}," dirsearch",[81,5921,5922],{"class":94}," --version\n",[81,5924,5925,5928,5931,5934,5937,5940],{"class":83,"line":102},[81,5926,5927],{"class":87},"Traceback",[81,5929,5930],{"class":1449}," (most ",[81,5932,5933],{"class":90},"recent",[81,5935,5936],{"class":90}," call",[81,5938,5939],{"class":90}," last",[81,5941,5942],{"class":1449},"):\n",[81,5944,5945,5948,5950,5953,5955,5957,5960,5963,5965],{"class":83,"line":113},[81,5946,5947],{"class":87},"  File",[81,5949,641],{"class":128},[81,5951,5952],{"class":90},"\u003Cfrozen runpy>",[81,5954,1255],{"class":128},[81,5956,1724],{"class":90},[81,5958,5959],{"class":90}," line",[81,5961,5962],{"class":90}," 198,",[81,5964,3779],{"class":90},[81,5966,5967],{"class":90}," _run_module_as_main\n",[81,5969,5970,5972,5974,5976,5978,5980,5982,5985,5987],{"class":83,"line":247},[81,5971,5947],{"class":87},[81,5973,641],{"class":128},[81,5975,5952],{"class":90},[81,5977,1255],{"class":128},[81,5979,1724],{"class":90},[81,5981,5959],{"class":90},[81,5983,5984],{"class":90}," 88,",[81,5986,3779],{"class":90},[81,5988,5989],{"class":90}," _run_code\n",[81,5991,5992,5994,5996,5999,6001,6003,6005,6008,6010,6012,6015,6017],{"class":83,"line":393},[81,5993,5947],{"class":87},[81,5995,641],{"class":128},[81,5997,5998],{"class":90},"C:\\Users\\robin\\.local\\bin\\dirsearch.exe\\__main__.py",[81,6000,1255],{"class":128},[81,6002,1724],{"class":90},[81,6004,5959],{"class":90},[81,6006,6007],{"class":90}," 4,",[81,6009,3779],{"class":90},[81,6011,1969],{"class":618},[81,6013,6014],{"class":90},"modul",[81,6016,3570],{"class":1449},[81,6018,3573],{"class":618},[81,6020,6021,6024,6027,6030],{"class":83,"line":404},[81,6022,6023],{"class":87},"    from",[81,6025,6026],{"class":90}," dirsearch.dirsearch",[81,6028,6029],{"class":90}," import",[81,6031,6032],{"class":90}," main\n",[81,6034,6035,6037,6039,6042,6044,6046,6048,6051,6053,6055,6057,6059],{"class":83,"line":415},[81,6036,5947],{"class":87},[81,6038,641],{"class":128},[81,6040,6041],{"class":90},"C:\\Users\\robin\\AppData\\Roaming\\uv\\tools\\dirsearch\\Lib\\site-packages\\dirsearch\\dirsearch.py",[81,6043,1255],{"class":128},[81,6045,1724],{"class":90},[81,6047,5959],{"class":90},[81,6049,6050],{"class":90}," 23,",[81,6052,3779],{"class":90},[81,6054,1969],{"class":618},[81,6056,6014],{"class":90},[81,6058,3570],{"class":1449},[81,6060,3573],{"class":618},[81,6062,6063,6065,6068,6070,6073],{"class":83,"line":1438},[81,6064,6023],{"class":87},[81,6066,6067],{"class":90}," pkg_resources",[81,6069,6029],{"class":90},[81,6071,6072],{"class":90}," DistributionNotFound,",[81,6074,6075],{"class":90}," VersionConflict\n",[81,6077,6078,6081,6084,6086,6089,6091,6094],{"class":83,"line":1455},[81,6079,6080],{"class":87},"ModuleNotFoundError:",[81,6082,6083],{"class":90}," No",[81,6085,3797],{"class":90},[81,6087,6088],{"class":90}," named",[81,6090,129],{"class":128},[81,6092,6093],{"class":90},"pkg_resources",[81,6095,135],{"class":128},[14,6097,6098],{},"สำหรับวิธีการแก้ไข ให้ติดตั้งใหม่ด้วย",[2670,6100,6101],{},[14,6102,6103,6104],{},"จำเป็นต้องมี git cli ด้วยนะครับ (windows: ",[39,6105,6106],{},[1005,6107,6108],{},"winget install --id Git.Git -e --source winget)",[23,6110,6112],{"className":75,"code":6111,"language":77,"meta":31,"style":31},"uv tool install git+https://github.com/maurosoria/dirsearch\n",[18,6113,6114],{"__ignoreMap":31},[81,6115,6116,6118,6120,6122],{"class":83,"line":84},[81,6117,5872],{"class":87},[81,6119,5875],{"class":90},[81,6121,2828],{"class":90},[81,6123,6124],{"class":90}," git+https://github.com/maurosoria/dirsearch\n",[23,6126,6128],{"className":75,"code":6127,"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,6129,6130,6157,6168,6184,6197,6212,6227,6242,6252,6262,6272,6280,6297,6307,6317,6327,6337,6347,6357,6366,6376,6386,6396,6406],{"__ignoreMap":31},[81,6131,6132,6134,6136,6138,6140,6142,6144,6146,6148,6151,6153,6155],{"class":83,"line":84},[81,6133,5896],{"class":87},[81,6135,5899],{"class":90},[81,6137,5902],{"class":98},[81,6139,5905],{"class":90},[81,6141,5908],{"class":98},[81,6143,5911],{"class":90},[81,6145,5914],{"class":1449},[81,6147,1433],{"class":618},[81,6149,6150],{"class":90}," uv",[81,6152,5875],{"class":90},[81,6154,2828],{"class":90},[81,6156,6124],{"class":90},[81,6158,6159,6162,6165],{"class":83,"line":102},[81,6160,6161],{"class":87},"    Updated",[81,6163,6164],{"class":90}," https://github.com/maurosoria/dirsearch",[81,6166,6167],{"class":1449}," (70a763556e5dd894e1eeb2e62f77d83cb3fc5603)\n",[81,6169,6170,6173,6176,6179,6181],{"class":83,"line":113},[81,6171,6172],{"class":87},"Resolved",[81,6174,6175],{"class":655}," 35",[81,6177,6178],{"class":90}," packages",[81,6180,3779],{"class":90},[81,6182,6183],{"class":90}," 1.06s\n",[81,6185,6186,6189,6191,6194],{"class":83,"line":247},[81,6187,6188],{"class":87},"      Built",[81,6190,5919],{"class":90},[81,6192,6193],{"class":90}," @",[81,6195,6196],{"class":90}," git+https://github.com/maurosoria/dirsearch@70a763556e5dd894e1eeb2e62f77d83cb3fc5603\n",[81,6198,6199,6202,6205,6207,6209],{"class":83,"line":393},[81,6200,6201],{"class":87},"Prepared",[81,6203,6204],{"class":655}," 13",[81,6206,6178],{"class":90},[81,6208,3779],{"class":90},[81,6210,6211],{"class":90}," 1.95s\n",[81,6213,6214,6217,6220,6222,6224],{"class":83,"line":404},[81,6215,6216],{"class":87},"Uninstalled",[81,6218,6219],{"class":655}," 3",[81,6221,6178],{"class":90},[81,6223,3779],{"class":90},[81,6225,6226],{"class":90}," 34ms\n",[81,6228,6229,6232,6235,6237,6239],{"class":83,"line":415},[81,6230,6231],{"class":87},"Installed",[81,6233,6234],{"class":655}," 14",[81,6236,6178],{"class":90},[81,6238,3779],{"class":90},[81,6240,6241],{"class":90}," 242ms\n",[81,6243,6244,6246,6249],{"class":83,"line":1438},[81,6245,2013],{"class":87},[81,6247,6248],{"class":90}," anyio==",[81,6250,6251],{"class":655},"4.11.0\n",[81,6253,6254,6256,6259],{"class":83,"line":1455},[81,6255,1754],{"class":87},[81,6257,6258],{"class":90}," chardet==",[81,6260,6261],{"class":655},"5.2.0\n",[81,6263,6264,6266,6269],{"class":83,"line":1467},[81,6265,2013],{"class":87},[81,6267,6268],{"class":90}," defusedcsv==",[81,6270,6271],{"class":655},"3.0.0\n",[81,6273,6274,6276,6278],{"class":83,"line":1483},[81,6275,1754],{"class":87},[81,6277,5880],{"class":90},[81,6279,5883],{"class":655},[81,6281,6282,6284,6286,6289,6292,6295],{"class":83,"line":1497},[81,6283,2013],{"class":87},[81,6285,5880],{"class":90},[81,6287,6288],{"class":655},"0.4.3",[81,6290,6291],{"class":1449}," (from ",[81,6293,6294],{"class":90},"git+https://github.com/maurosoria/dirsearch@70a763556e5dd894e1eeb2e62f77d83cb3fc5603",[81,6296,1730],{"class":1449},[81,6298,6299,6301,6304],{"class":83,"line":1509},[81,6300,2013],{"class":87},[81,6302,6303],{"class":90}," h11==",[81,6305,6306],{"class":655},"0.16.0\n",[81,6308,6309,6311,6314],{"class":83,"line":1514},[81,6310,2013],{"class":87},[81,6312,6313],{"class":90}," httpcore==",[81,6315,6316],{"class":655},"1.0.9\n",[81,6318,6319,6321,6324],{"class":83,"line":1519},[81,6320,2013],{"class":87},[81,6322,6323],{"class":90}," httpx==",[81,6325,6326],{"class":655},"0.28.1\n",[81,6328,6329,6331,6334],{"class":83,"line":1535},[81,6330,2013],{"class":87},[81,6332,6333],{"class":90}," httpx-ntlm==",[81,6335,6336],{"class":655},"1.4.0\n",[81,6338,6339,6341,6344],{"class":83,"line":1548},[81,6340,2013],{"class":87},[81,6342,6343],{"class":90}," mysql-connector-python==",[81,6345,6346],{"class":655},"9.5.0\n",[81,6348,6349,6351,6354],{"class":83,"line":1559},[81,6350,2013],{"class":87},[81,6352,6353],{"class":90}," psycopg==",[81,6355,6356],{"class":655},"3.2.12\n",[81,6358,6359,6361,6364],{"class":83,"line":1570},[81,6360,2013],{"class":87},[81,6362,6363],{"class":90}," psycopg-binary==",[81,6365,6356],{"class":655},[81,6367,6368,6370,6373],{"class":83,"line":1582},[81,6369,1754],{"class":87},[81,6371,6372],{"class":90}," pyparsing==",[81,6374,6375],{"class":655},"3.2.5\n",[81,6377,6378,6380,6383],{"class":83,"line":1588},[81,6379,2013],{"class":87},[81,6381,6382],{"class":90}," requests-toolbelt==",[81,6384,6385],{"class":655},"1.0.0\n",[81,6387,6388,6390,6393],{"class":83,"line":1647},[81,6389,2013],{"class":87},[81,6391,6392],{"class":90}," setuptools==",[81,6394,6395],{"class":655},"80.9.0\n",[81,6397,6398,6400,6403],{"class":83,"line":1692},[81,6399,2013],{"class":87},[81,6401,6402],{"class":90}," sniffio==",[81,6404,6405],{"class":655},"1.3.1\n",[81,6407,6408,6410,6413],{"class":83,"line":1697},[81,6409,2013],{"class":87},[81,6411,6412],{"class":90}," tzdata==",[81,6414,6415],{"class":655},"2025.2\n",[14,6417,6418],{},"เพียงเท่านี้ก็จะสามารถใช้งานได้แล้วครับ",[938,6420,6421],{},"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":6423},[],"2025-11-03T00:00:00.000Z","แก้ปัญหาติดตั้ง dirsearch ด้วย uv แล้วใช้งานไม่ได้",{},"Dirsearch with UV","/blogs/2025-11-03-dirsearch-uv",{"title":5857,"description":6425},{"loc":6428},"blogs/2025-11-03-dirsearch-uv",[968],"wg9DvOylgKVxZWjmRSV9UK150jcI1OwsrQLf4e4O5b4",{"id":6435,"title":6436,"body":6437,"date":6604,"description":6605,"draft":956,"extension":957,"meta":6606,"navigation":365,"next":959,"ogTitle":6607,"path":6608,"pin":956,"prev":959,"recommends":959,"seo":6609,"sitemap":6610,"stem":6611,"tags":6612,"__hash__":6613},"content/blogs/2025-11-06-environment-mac-keychains.md","วิธีเก็บ Environment บน Mac Keychains แบบปลอดภัยมากขึ้น",{"type":7,"value":6438,"toc":6602},[6439,6442,6445,6481,6486,6489,6492,6522,6525,6567,6578,6581,6599],[14,6440,6441],{},"โดยวิธีการนี้ค่าจะถูกนำไปเก็บเอาไว้ใน keychains ของ macos ซึ่งจะปลอดภัยกว่าการเก็บไว้ใน .zshrc, .bashrc file โดยตรง",[14,6443,6444],{},"วิธีการ",[23,6446,6448],{"className":75,"code":6447,"language":77,"meta":31,"style":31},"security add-generic-password -a \"$USER\" -s 'name_of_your_key' -w\n",[18,6449,6450],{"__ignoreMap":31},[81,6451,6452,6455,6458,6461,6463,6466,6468,6471,6473,6476,6478],{"class":83,"line":84},[81,6453,6454],{"class":87},"security",[81,6456,6457],{"class":90}," add-generic-password",[81,6459,6460],{"class":94}," -a",[81,6462,641],{"class":128},[81,6464,6465],{"class":1624},"$USER",[81,6467,1255],{"class":128},[81,6469,6470],{"class":94}," -s",[81,6472,129],{"class":128},[81,6474,6475],{"class":90},"name_of_your_key",[81,6477,3019],{"class":128},[81,6479,6480],{"class":94}," -w\n",[2670,6482,6483],{},[14,6484,6485],{},"name_of_your_key ตั้งเป็น key ตามที่ต้องการจะเก็บ",[14,6487,6488],{},"จากนั้น application จะมีการแสดง Prompt `password data for new item:` โดยให้ใส่ value ของ key ที่ต้องการเก็บได้เลย",[14,6490,6491],{},"และใช้คำสั่งนี้เพื่อดึงค่าจาก keychains ออกมา",[23,6493,6495],{"className":75,"code":6494,"language":77,"meta":31,"style":31},"security find-generic-password -a \"$USER\" -s 'name_of_your_key' -w\n",[18,6496,6497],{"__ignoreMap":31},[81,6498,6499,6501,6504,6506,6508,6510,6512,6514,6516,6518,6520],{"class":83,"line":84},[81,6500,6454],{"class":87},[81,6502,6503],{"class":90}," find-generic-password",[81,6505,6460],{"class":94},[81,6507,641],{"class":128},[81,6509,6465],{"class":1624},[81,6511,1255],{"class":128},[81,6513,6470],{"class":94},[81,6515,129],{"class":128},[81,6517,6475],{"class":90},[81,6519,3019],{"class":128},[81,6521,6480],{"class":94},[14,6523,6524],{},"หากต้องการให้ default shell ของเรา load ค่าจาก envronment ดังกล่าวออกมา สามารถนำ",[23,6526,6528],{"className":75,"code":6527,"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,6529,6530],{"__ignoreMap":31},[81,6531,6532,6534,6537,6539,6542,6544,6546,6548,6550,6552,6554,6556,6558,6560,6562,6565],{"class":83,"line":84},[81,6533,1522],{"class":1364},[81,6535,6536],{"class":1624}," NAME_OF_YOUR_KEY",[81,6538,3130],{"class":1598},[81,6540,6541],{"class":628},"$(",[81,6543,6454],{"class":87},[81,6545,6503],{"class":90},[81,6547,6460],{"class":94},[81,6549,641],{"class":128},[81,6551,6465],{"class":1624},[81,6553,1255],{"class":128},[81,6555,6470],{"class":94},[81,6557,641],{"class":128},[81,6559,6475],{"class":90},[81,6561,1255],{"class":128},[81,6563,6564],{"class":94}," -w",[81,6566,1730],{"class":628},[14,6568,6569,6570,6573,6574,6577],{},"ไปใส่ใน default shell เช่น ",[18,6571,6572],{},"~/.zshrc",", ",[18,6575,6576],{},"~/.bashrc"," แล้ว reload ได้เลย ค่าดังกล่าวก็จะถูกดึงออกมาโดยอัตโนมัติ",[14,6579,6580],{},"วิธีการตรวจสอบว่าถูก Load เข้าไปไหม",[23,6582,6584],{"className":75,"code":6583,"language":77,"meta":31,"style":31},"printenv NAME_OF_YOUR_KEY\n# ถ้าถูกตรงนี้จะขึ้น Value มา\n",[18,6585,6586,6594],{"__ignoreMap":31},[81,6587,6588,6591],{"class":83,"line":84},[81,6589,6590],{"class":87},"printenv",[81,6592,6593],{"class":90}," NAME_OF_YOUR_KEY\n",[81,6595,6596],{"class":83,"line":102},[81,6597,6598],{"class":1064},"# ถ้าถูกตรงนี้จะขึ้น Value มา\n",[938,6600,6601],{},"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}",{"title":31,"searchDepth":102,"depth":102,"links":6603},[],"2025-11-06T00:00:00.000Z","เก็บ sensitive environment บน Mac",{},"MacOS Environment Settings","/blogs/2025-11-06-environment-mac-keychains",{"title":6436,"description":6605},{"loc":6608},"blogs/2025-11-06-environment-mac-keychains",[968],"-pYnFQz6U4BZUIgFdUKyptfRbSeBLOCv0ndErD0NRkg",{"id":6615,"title":6616,"body":6617,"date":6757,"description":6758,"draft":956,"extension":957,"meta":6759,"navigation":365,"next":959,"ogTitle":6760,"path":6761,"pin":956,"prev":959,"recommends":959,"seo":6762,"sitemap":6763,"stem":6764,"tags":6765,"__hash__":6766},"content/blogs/2025-12-28-powerlevel10k-spaceship.md","ย้ายจาก Powerlevel10k มาใช้ Spaceship",{"type":7,"value":6618,"toc":6754},[6619,6627,6630,6632,6699,6702,6735,6748,6751],[14,6620,6621,6622,6626],{},"ช่วงหลัง ๆ มาผมรู้สึกว่าเริ่มเบื่อกับ Terminal ที่กำลังใช้อยู่ (zsh+oh-my-zsh+powerlevel10k) เลยลองหา ๆ ดูว่ามีทางเลือกอื่นให้ติดตั้งอีกมั้ย แล้วก็ได้มาเจอกับ ",[910,6623,6624],{"href":6624,"rel":6625},"https://spaceship-prompt.sh/",[914]," ซึ่งสามารถลงเป็น add-on บน zsh ได้เลย (แทนที่ powerlevel10k)",[14,6628,6629],{},"จากที่ได้ลองใช้มาสักพักนึงรู้สึกลื่นนนกว่ามาก",[10,6631,2811],{"id":2811},[23,6633,6635],{"className":75,"code":6634,"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,6636,6637,6642,6665,6669,6674],{"__ignoreMap":31},[81,6638,6639],{"class":83,"line":84},[81,6640,6641],{"class":1064},"# Clone the repo\n",[81,6643,6644,6646,6649,6652,6654,6657,6660,6662],{"class":83,"line":102},[81,6645,2696],{"class":87},[81,6647,6648],{"class":90}," clone",[81,6650,6651],{"class":90}," https://github.com/spaceship-prompt/spaceship-prompt.git",[81,6653,641],{"class":128},[81,6655,6656],{"class":1624},"$ZSH_CUSTOM",[81,6658,6659],{"class":90},"/themes/spaceship-prompt",[81,6661,1255],{"class":128},[81,6663,6664],{"class":94}," --depth=1\n",[81,6666,6667],{"class":83,"line":113},[81,6668,366],{"emptyLinePlaceholder":365},[81,6670,6671],{"class":83,"line":247},[81,6672,6673],{"class":1064},"# Link the theme\n",[81,6675,6676,6679,6681,6683,6685,6688,6690,6692,6694,6697],{"class":83,"line":393},[81,6677,6678],{"class":87},"ln",[81,6680,6470],{"class":94},[81,6682,641],{"class":128},[81,6684,6656],{"class":1624},[81,6686,6687],{"class":90},"/themes/spaceship-prompt/spaceship.zsh-theme",[81,6689,1255],{"class":128},[81,6691,641],{"class":128},[81,6693,6656],{"class":1624},[81,6695,6696],{"class":90},"/themes/spaceship.zsh-theme",[81,6698,647],{"class":128},[14,6700,6701],{},"จากนั้นสามารถแก้ไข",[23,6703,6705],{"className":75,"code":6704,"language":77,"meta":31,"style":31},"# .zshrc แก้ไข theme ที่ใช้อยู่\n\n#ZSH_THEME=\"powerlevel10k/powerlevel10k\"\nZSH_THEME=\"spaceship\"\n",[18,6706,6707,6712,6716,6721],{"__ignoreMap":31},[81,6708,6709],{"class":83,"line":84},[81,6710,6711],{"class":1064},"# .zshrc แก้ไข theme ที่ใช้อยู่\n",[81,6713,6714],{"class":83,"line":102},[81,6715,366],{"emptyLinePlaceholder":365},[81,6717,6718],{"class":83,"line":113},[81,6719,6720],{"class":1064},"#ZSH_THEME=\"powerlevel10k/powerlevel10k\"\n",[81,6722,6723,6726,6728,6730,6733],{"class":83,"line":247},[81,6724,6725],{"class":1624},"ZSH_THEME",[81,6727,3130],{"class":1598},[81,6729,1255],{"class":128},[81,6731,6732],{"class":90},"spaceship",[81,6734,647],{"class":128},[23,6736,6738],{"className":75,"code":6737,"language":77,"meta":31,"style":31},"source ~/.zshrc\n",[18,6739,6740],{"__ignoreMap":31},[81,6741,6742,6745],{"class":83,"line":84},[81,6743,6744],{"class":637},"source",[81,6746,6747],{"class":90}," ~/.zshrc\n",[14,6749,6750],{},"เรียบร้อยฮะ",[938,6752,6753],{},"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":6755},[6756],{"id":2811,"depth":102,"text":2811},"2025-12-28T00:00:00.000Z","Note วิธีการติดตั้ง Spaceship บน Macos",{},"Moving from Powerlevel10k to Spaceship","/blogs/2025-12-28-powerlevel10k-spaceship",{"title":6616,"description":6758},{"loc":6761},"blogs/2025-12-28-powerlevel10k-spaceship",[968],"k3hbYPncsBdblTN3iqrLHAbA6C1dexqidSvDq0LDAKk",{"id":6768,"title":6769,"body":6770,"date":6757,"description":7062,"draft":956,"extension":957,"meta":7063,"navigation":365,"next":959,"ogTitle":7064,"path":7065,"pin":956,"prev":959,"recommends":959,"seo":7066,"sitemap":7067,"stem":7068,"tags":7069,"__hash__":7070},"content/blogs/2025-12-28-zshrc-config-folder.md","ย้าย .zshrc ไปยัง .config folder",{"type":7,"value":6771,"toc":7057},[6772,6775,6778,6829,6832,6868,6871,6875,6878,6906,6909,6913,6916,6923,6938,6941,6985,6988,7000,7003,7009,7048,7054],[580,6773,6774],{"id":6774},"วิธีการตั้งค่า",[14,6776,6777],{},"Note วิธีย้าย .zshrc เข้าไปยัง .config folder บน macos เพื่อให้ง่ายต่อการ backup",[23,6779,6781],{"className":75,"code":6780,"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,6782,6783,6788,6799,6803,6808,6819],{"__ignoreMap":31},[81,6784,6785],{"class":83,"line":84},[81,6786,6787],{"class":1064},"# Create new folder\n",[81,6789,6790,6793,6796],{"class":83,"line":102},[81,6791,6792],{"class":87},"mkdir",[81,6794,6795],{"class":94}," -p",[81,6797,6798],{"class":90}," ~/.config/zsh\n",[81,6800,6801],{"class":83,"line":113},[81,6802,366],{"emptyLinePlaceholder":365},[81,6804,6805],{"class":83,"line":247},[81,6806,6807],{"class":1064},"# Move config\n",[81,6809,6810,6813,6816],{"class":83,"line":393},[81,6811,6812],{"class":87},"mv",[81,6814,6815],{"class":90}," ~/.zshrc",[81,6817,6818],{"class":90}," ~/.config/zsh/.zshrc\n",[81,6820,6821,6823,6826],{"class":83,"line":404},[81,6822,6812],{"class":87},[81,6824,6825],{"class":90}," ~/.zsh_history",[81,6827,6828],{"class":90}," ~/.config/zsh/.zsh_history\n",[14,6830,6831],{},"จากนั้นแก้ไขไฟล์ .zshenv เพื่อเปลี่ยน working directory",[23,6833,6835],{"className":75,"code":6834,"language":77,"meta":31,"style":31},"nano ~/.zshenv\n\nexport ZDOTDIR=\"$HOME/.config/zsh\"\n",[18,6836,6837,6845,6849],{"__ignoreMap":31},[81,6838,6839,6842],{"class":83,"line":84},[81,6840,6841],{"class":87},"nano",[81,6843,6844],{"class":90}," ~/.zshenv\n",[81,6846,6847],{"class":83,"line":102},[81,6848,366],{"emptyLinePlaceholder":365},[81,6850,6851,6853,6856,6858,6860,6863,6866],{"class":83,"line":113},[81,6852,1522],{"class":1364},[81,6854,6855],{"class":1624}," ZDOTDIR",[81,6857,3130],{"class":1598},[81,6859,1255],{"class":128},[81,6861,6862],{"class":1624},"$HOME",[81,6864,6865],{"class":90},"/.config/zsh",[81,6867,647],{"class":128},[14,6869,6870],{},"ลองปิดเปิดใหม่เพื่อทดสอบว่า config ถูก load ถูกต้องมั้ย (ปิด terminal ไปเลยนะครับ)",[580,6872,6874],{"id":6873},"ป้องกันไม่ให้เอา-zsh_history-ขึ้น-git","ป้องกันไม่ให้เอา .zsh_history ขึ้น git",[14,6876,6877],{},"แนะนำให้สร้าง .gitignore แล้วเพิ่ม",[23,6879,6881],{"className":75,"code":6880,"language":77,"meta":31,"style":31},"# Ignore everything in zsh...\nzsh/*\n# ...Except .zshrc\n!zsh/.zshrc\n",[18,6882,6883,6888,6893,6898],{"__ignoreMap":31},[81,6884,6885],{"class":83,"line":84},[81,6886,6887],{"class":1064},"# Ignore everything in zsh...\n",[81,6889,6890],{"class":83,"line":102},[81,6891,6892],{"class":87},"zsh/*\n",[81,6894,6895],{"class":83,"line":113},[81,6896,6897],{"class":1064},"# ...Except .zshrc\n",[81,6899,6900,6903],{"class":83,"line":247},[81,6901,6902],{"class":618},"!",[81,6904,6905],{"class":87},"zsh/.zshrc\n",[14,6907,6908],{},"เพื่อให้ backup เฉพาะส่วนที่เป็น config ไว้เท่านั้นครับ เนื่องจากใน .zsh_history อาจจะมี secret ต่าง ๆ อยู่ได้",[580,6910,6912],{"id":6911},"ใช้งานร่วมกับ-gitleak-pre-commit","ใช้งานร่วมกับ gitleak + pre-commit",[14,6914,6915],{},"เพื่อเพิ่มความปลอดภัยอีกชั้น แนะนำให้ติด pre-commit hook เพื่อดักจับ secret (เช่น API Key, Token ต่าง ๆ) ก่อนที่เราจะเผลอ commit ลงไปใน git ครับ",[14,6917,6918,6919,6922],{},"เริ่มจากติดตั้ง ",[18,6920,6921],{},"pre-commit"," (หากยังไม่มี)",[23,6924,6926],{"className":75,"code":6925,"language":77,"meta":31,"style":31},"brew install pre-commit\n",[18,6927,6928],{"__ignoreMap":31},[81,6929,6930,6933,6935],{"class":83,"line":84},[81,6931,6932],{"class":87},"brew",[81,6934,2828],{"class":90},[81,6936,6937],{"class":90}," pre-commit\n",[14,6939,6940],{},"จากนั้นสร้างไฟล์ .pre-commit-config.yaml ไว้ที่ root directory ของโปรเจกต์ และกำหนด version ของ gitleaks เป็น v8.30.0",[23,6942,6945],{"className":75,"code":6943,"filename":6944,"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,6946,6947,6952,6962,6970,6975],{"__ignoreMap":31},[81,6948,6949],{"class":83,"line":84},[81,6950,6951],{"class":87},"repos:\n",[81,6953,6954,6956,6959],{"class":83,"line":102},[81,6955,4883],{"class":87},[81,6957,6958],{"class":90}," repo:",[81,6960,6961],{"class":90}," https://github.com/gitleaks/gitleaks\n",[81,6963,6964,6967],{"class":83,"line":113},[81,6965,6966],{"class":87},"    rev:",[81,6968,6969],{"class":90}," v8.30.0\n",[81,6971,6972],{"class":83,"line":247},[81,6973,6974],{"class":87},"    hooks:\n",[81,6976,6977,6979,6982],{"class":83,"line":393},[81,6978,5010],{"class":87},[81,6980,6981],{"class":90}," id:",[81,6983,6984],{"class":90}," gitleaks\n",[14,6986,6987],{},"สั่ง install hook เข้าไปใน git directory",[23,6989,6991],{"className":75,"code":6990,"language":77,"meta":31,"style":31},"pre-commit install\n",[18,6992,6993],{"__ignoreMap":31},[81,6994,6995,6997],{"class":83,"line":84},[81,6996,6921],{"class":87},[81,6998,6999],{"class":90}," install\n",[14,7001,7002],{},"ผลลัพธ์: หลังจากนี้ทุกครั้งที่เราพิมพ์คำสั่ง git commit ตัว pre-commit จะทำการเรียก Gitleaks มาสแกนไฟล์ที่เรากำลังจะ commit ให้โดยอัตโนมัติ ถ้าเจอ secret มันจะแจ้ง error และหยุดการ commit ให้ทันทีครับ",[14,7004,7005,7008],{},[1005,7006,7007],{},"เพิ่มเติม (Optional)","\nถ้าอยากให้บทความสมบูรณ์ขึ้น อาจจะปิดท้ายด้วยคำสั่งสำหรับ Test ดูครับ ทดสอบการทำงาน ลองสร้างไฟล์ที่มี Mock Secret ดูครับ",[23,7010,7012],{"className":75,"code":7011,"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,7013,7014,7022,7031],{"__ignoreMap":31},[81,7015,7016,7019],{"class":83,"line":84},[81,7017,7018],{"class":87},"wget",[81,7020,7021],{"class":90}," https://raw.githubusercontent.com/trufflesecurity/test_keys/refs/heads/main/keys\n",[81,7023,7024,7026,7028],{"class":83,"line":102},[81,7025,2696],{"class":87},[81,7027,2853],{"class":90},[81,7029,7030],{"class":90}," keys\n",[81,7032,7033,7035,7038,7041,7043,7046],{"class":83,"line":113},[81,7034,2696],{"class":87},[81,7036,7037],{"class":90}," commit",[81,7039,7040],{"class":94}," -m",[81,7042,641],{"class":128},[81,7044,7045],{"class":90},"test secret",[81,7047,647],{"class":128},[14,7049,7050],{},[979,7051],{"alt":7052,"src":7053},"ตัวอย่าง","/images/2025-12-28-zshrc-config-folder/gitleak-demo.jpg",[938,7055,7056],{},"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":7058},[7059,7060,7061],{"id":6774,"depth":113,"text":6774},{"id":6873,"depth":113,"text":6874},{"id":6911,"depth":113,"text":6912},"ย้าย .zshrc เพื่อให้ง่ายต่อการ backup",{},"Moving the .zshrc to .config folder","/blogs/2025-12-28-zshrc-config-folder",{"title":6769,"description":7062},{"loc":7065},"blogs/2025-12-28-zshrc-config-folder",[968],"iKFZQKdTxZcMKUFq1PPWqyrKypAeRH0qAumAOjH8JEQ",{"id":7072,"title":7073,"body":7074,"date":7232,"description":7233,"draft":956,"extension":957,"meta":7234,"navigation":365,"next":959,"ogTitle":7235,"path":7236,"pin":956,"prev":959,"recommends":959,"seo":7237,"sitemap":7238,"stem":7239,"tags":7240,"__hash__":7241},"content/blogs/2026-01-02-cli-macos-sips.md","ย่อรูปผ่าน CLI บน MacOS ด้วย sips",{"type":7,"value":7075,"toc":7229},[7076,7180,7183,7186,7206,7209,7226],[23,7077,7079],{"className":75,"code":7078,"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,7080,7081,7100,7143],{"__ignoreMap":31},[81,7082,7083,7086,7088,7091,7094,7097],{"class":83,"line":84},[81,7084,7085],{"class":87},"sips",[81,7087,1754],{"class":90},[81,7089,7090],{"class":90}," scriptable",[81,7092,7093],{"class":90}," image",[81,7095,7096],{"class":90}," processing",[81,7098,7099],{"class":90}," system.\n",[81,7101,7102,7105,7107,7110,7113,7115,7118,7121,7124,7127,7129,7132,7134,7137,7140],{"class":83,"line":102},[81,7103,7104],{"class":87},"This",[81,7106,5875],{"class":90},[81,7108,7109],{"class":90}," is",[81,7111,7112],{"class":90}," used",[81,7114,3604],{"class":90},[81,7116,7117],{"class":90}," query",[81,7119,7120],{"class":90}," or",[81,7122,7123],{"class":90}," modify",[81,7125,7126],{"class":90}," raster",[81,7128,7093],{"class":90},[81,7130,7131],{"class":90}," files",[81,7133,3741],{"class":90},[81,7135,7136],{"class":90}," ColorSync",[81,7138,7139],{"class":90}," ICC",[81,7141,7142],{"class":90}," profiles.\n",[81,7144,7145,7148,7151,7154,7157,7160,7162,7165,7167,7169,7172,7174,7177],{"class":83,"line":113},[81,7146,7147],{"class":87},"Its",[81,7149,7150],{"class":90}," functionality",[81,7152,7153],{"class":90}," can",[81,7155,7156],{"class":90}," also",[81,7158,7159],{"class":90}," be",[81,7161,7112],{"class":90},[81,7163,7164],{"class":90}," through",[81,7166,3676],{"class":90},[81,7168,641],{"class":128},[81,7170,7171],{"class":90},"Image Events",[81,7173,1255],{"class":128},[81,7175,7176],{"class":90}," AppleScript",[81,7178,7179],{"class":90}," suite.\n",[10,7181,7182],{"id":7182},"วิธีใช้งาน",[14,7184,7185],{},"To reduce JPEG quality (e.g., to 70%):",[23,7187,7189],{"className":75,"code":7188,"language":77,"meta":31,"style":31},"sips -s formatOptions 70 input.jpg\n",[18,7190,7191],{"__ignoreMap":31},[81,7192,7193,7195,7197,7200,7203],{"class":83,"line":84},[81,7194,7085],{"class":87},[81,7196,6470],{"class":94},[81,7198,7199],{"class":90}," formatOptions",[81,7201,7202],{"class":655}," 70",[81,7204,7205],{"class":90}," input.jpg\n",[14,7207,7208],{},"To resize the image (maintaining aspect ratio, max dimension 1024px):",[23,7210,7212],{"className":75,"code":7211,"language":77,"meta":31,"style":31},"sips -Z 1024 input.jpg\n",[18,7213,7214],{"__ignoreMap":31},[81,7215,7216,7218,7221,7224],{"class":83,"line":84},[81,7217,7085],{"class":87},[81,7219,7220],{"class":94}," -Z",[81,7222,7223],{"class":655}," 1024",[81,7225,7205],{"class":90},[938,7227,7228],{},"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}",{"title":31,"searchDepth":102,"depth":102,"links":7230},[7231],{"id":7182,"depth":102,"text":7182},"2026-01-02T00:00:00.000Z","วิธีการย่อรูปบน macos ด้วย cli แบบไม่ต้องลงโปรแกรมเพิ่ม",{},"sips 101","/blogs/2026-01-02-cli-macos-sips",{"title":7073,"description":7233},{"loc":7236},"blogs/2026-01-02-cli-macos-sips",[968],"bv5n0pnj1nuhSOs2bL2fDbf3vb3DbAOWNX4hg3vh9Ew",{"id":7243,"title":7244,"body":7245,"date":7351,"description":7352,"draft":956,"extension":957,"meta":7353,"navigation":365,"next":959,"ogTitle":7352,"path":7354,"pin":956,"prev":959,"recommends":959,"seo":7355,"sitemap":7356,"stem":7357,"tags":7358,"__hash__":7359},"content/blogs/2026-01-17-zshhistory.md","วิธีแก้ไข zsh_history เสียหาย",{"type":7,"value":7246,"toc":7347},[7247,7250,7253,7275,7278,7281,7284,7287,7341,7344],[580,7248,7249],{"id":7249},"ปัญหาที่เจอ",[14,7251,7252],{},"สำหรับชาว Dev ที่ใช้ ZSH อาจจะเคยเจอเหตุการณ์เปิด Terminal ขึ้นมาแล้วเจอ Error แจ้งเตือนเกี่ยวกับ History file พังแบบนี้ครับ",[23,7254,7256],{"className":75,"code":7255,"language":77,"meta":31,"style":31},"zsh: corrupt history file /home/pi/.zsh_history\n",[18,7257,7258],{"__ignoreMap":31},[81,7259,7260,7263,7266,7269,7272],{"class":83,"line":84},[81,7261,7262],{"class":87},"zsh:",[81,7264,7265],{"class":90}," corrupt",[81,7267,7268],{"class":90}," history",[81,7270,7271],{"class":90}," file",[81,7273,7274],{"class":90}," /home/pi/.zsh_history\n",[14,7276,7277],{},"ซึ่งปัญหานี้จะทำให้เราไม่สามารถกดลูกศรขึ้น-ลง เพื่อเรียกดูคำสั่งเก่า ๆ ที่เคยพิมพ์ไปได้ ซึ่งสาเหตุมักเกิดจากเครื่องดับไปดื้อ ๆ หรือโปรแกรมปิดไม่สมบูรณ์ครับ",[580,7279,7280],{"id":7280},"วิธีแก้ไข",[14,7282,7283],{},"วิธีแก้นั้นง่ายมากครับ ไม่ต้องลบ History ทิ้งทั้งหมด หลักการคือเราจะย้ายไฟล์ที่เสียไปพักไว้ก่อน แล้วใช้คำสั่ง strings เพื่อดึงเฉพาะข้อความที่อ่านรู้เรื่องกลับมาใส่ไฟล์ใหม่ครับ",[14,7285,7286],{},"พิมพ์ตามนี้ได้เลย หรือจะก๊อปไปวางทีละบรรทัดก็ได้ครับ",[23,7288,7290],{"className":75,"code":7289,"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,7291,7292,7300,7310,7323,7333],{"__ignoreMap":31},[81,7293,7294,7297],{"class":83,"line":84},[81,7295,7296],{"class":637},"cd",[81,7298,7299],{"class":90}," ~\n",[81,7301,7302,7304,7307],{"class":83,"line":102},[81,7303,6812],{"class":87},[81,7305,7306],{"class":90}," .zsh_history",[81,7308,7309],{"class":90}," .zsh_history_bad\n",[81,7311,7312,7315,7318,7320],{"class":83,"line":113},[81,7313,7314],{"class":87},"strings",[81,7316,7317],{"class":90}," .zsh_history_bad",[81,7319,619],{"class":618},[81,7321,7322],{"class":90}," .zsh_history\n",[81,7324,7325,7328,7331],{"class":83,"line":247},[81,7326,7327],{"class":637},"fc",[81,7329,7330],{"class":94}," -R",[81,7332,7322],{"class":90},[81,7334,7335,7338],{"class":83,"line":393},[81,7336,7337],{"class":87},"rm",[81,7339,7340],{"class":90}," ~/.zsh_history_bad\n",[14,7342,7343],{},"เท่านี้ zsh_history ของเราก็จะกลับมาใช้งานได้ปกติ กดดูคำสั่งย้อนหลังได้เหมือนเดิมแล้วครับ!",[938,7345,7346],{},"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":7348},[7349,7350],{"id":7249,"depth":113,"text":7249},{"id":7280,"depth":113,"text":7280},"2026-01-17T00:00:00.000Z","How to fix a corrupt zsh history file",{},"/blogs/2026-01-17-zshhistory",{"title":7244,"description":7352},{"loc":7354},"blogs/2026-01-17-zshhistory",[968],"6qPf4edRLJO7GVqo4AM5yJbKTVrovf45MU2cFmepxbs",{"id":7361,"title":7362,"body":7363,"date":8466,"description":8467,"draft":956,"extension":957,"meta":8468,"navigation":365,"next":959,"ogTitle":8469,"path":8470,"pin":956,"prev":959,"recommends":959,"seo":8471,"sitemap":8472,"stem":8473,"tags":8474,"__hash__":8476},"content/blogs/2026-03-30-how-to-use-uv.md","เริ่มต้นใช้งาน uv ตัวจัดการ Python แห่งยุค 2026",{"type":7,"value":7364,"toc":8439},[7365,7369,7380,7384,7388,7402,7406,7428,7431,7443,7446,7484,7488,7494,7498,7559,7563,7569,7629,7633,7636,7695,7698,7733,7736,7750,7754,7758,7779,7782,7788,7792,7902,7906,7961,7973,7977,8036,8040,8044,8052,8207,8221,8224,8228,8279,8283,8351,8355,8365,8413,8415,8418,8422,8436],[10,7366,7368],{"id":7367},"uv-คืออะไร","uv คืออะไร",[14,7370,7371,7373,7374,7379],{},[39,7372,5872],{}," เป็นเครื่องมือจัดการ Python แบบครบวงจรที่เขียนด้วย Rust ทำงานได้เร็วกว่า pip ถึง 10-100 เท่า ใช้แทน pip, pip-tools, pipx, poetry, pyenv ได้ในตัวเดียว พัฒนาโดยทีม ",[910,7375,7378],{"href":7376,"rel":7377},"https://astral.sh/",[914],"Astral"," ทีมเดียวกับที่ทำ Ruff (Python linter)",[10,7381,7383],{"id":7382},"_1-วิธีติดตั้ง-uv-บน-macos","1. วิธีติดตั้ง uv บน macOS",[580,7385,7387],{"id":7386},"ติดตั้งผ่าน-homebrew-แนะนำ","ติดตั้งผ่าน Homebrew (แนะนำ)",[23,7389,7391],{"className":75,"code":7390,"language":77,"meta":31,"style":31},"brew install uv\n",[18,7392,7393],{"__ignoreMap":31},[81,7394,7395,7397,7399],{"class":83,"line":84},[81,7396,6932],{"class":87},[81,7398,2828],{"class":90},[81,7400,7401],{"class":90}," uv\n",[580,7403,7405],{"id":7404},"ติดตั้งผ่าน-standalone-installer","ติดตั้งผ่าน Standalone Installer",[23,7407,7409],{"className":75,"code":7408,"language":77,"meta":31,"style":31},"curl -LsSf https://astral.sh/uv/install.sh | sh\n",[18,7410,7411],{"__ignoreMap":31},[81,7412,7413,7416,7419,7422,7425],{"class":83,"line":84},[81,7414,7415],{"class":87},"curl",[81,7417,7418],{"class":94}," -LsSf",[81,7420,7421],{"class":90}," https://astral.sh/uv/install.sh",[81,7423,7424],{"class":618}," |",[81,7426,7427],{"class":87}," sh\n",[14,7429,7430],{},"ตรวจสอบว่าติดตั้งสำเร็จด้วย",[23,7432,7434],{"className":75,"code":7433,"language":77,"meta":31,"style":31},"uv version\n",[18,7435,7436],{"__ignoreMap":31},[81,7437,7438,7440],{"class":83,"line":84},[81,7439,5872],{"class":87},[81,7441,7442],{"class":90}," version\n",[580,7444,7445],{"id":7445},"อัปเดตเวอร์ชัน",[23,7447,7449],{"className":75,"code":7448,"language":77,"meta":31,"style":31},"# ถ้าติดตั้งผ่าน Homebrew\nbrew upgrade uv\n\n# ถ้าติดตั้งผ่าน Standalone Installer\nuv self update\n",[18,7450,7451,7456,7465,7469,7474],{"__ignoreMap":31},[81,7452,7453],{"class":83,"line":84},[81,7454,7455],{"class":1064},"# ถ้าติดตั้งผ่าน Homebrew\n",[81,7457,7458,7460,7463],{"class":83,"line":102},[81,7459,6932],{"class":87},[81,7461,7462],{"class":90}," upgrade",[81,7464,7401],{"class":90},[81,7466,7467],{"class":83,"line":113},[81,7468,366],{"emptyLinePlaceholder":365},[81,7470,7471],{"class":83,"line":247},[81,7472,7473],{"class":1064},"# ถ้าติดตั้งผ่าน Standalone Installer\n",[81,7475,7476,7478,7481],{"class":83,"line":393},[81,7477,5872],{"class":87},[81,7479,7480],{"class":90}," self",[81,7482,7483],{"class":90}," update\n",[10,7485,7487],{"id":7486},"_2-รันเครื่องมือแบบง่าย-ๆ-ด้วย-uvx","2. รันเครื่องมือแบบง่าย ๆ ด้วย uvx",[14,7489,7490,7493],{},[18,7491,7492],{},"uvx"," คือคำสั่งสำหรับรันเครื่องมือ Python แบบไม่ต้องติดตั้งถาวร จะสร้าง virtual environment ชั่วคราวให้อัตโนมัติ ใช้เสร็จก็ทิ้งไป เหมาะมากสำหรับเครื่องมือที่ใช้เป็นครั้งคราว",[580,7495,7497],{"id":7496},"รัน-tool-แบบครั้งเดียว","รัน tool แบบครั้งเดียว",[23,7499,7501],{"className":75,"code":7500,"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,7502,7503,7508,7521,7525,7530,7540,7544,7549],{"__ignoreMap":31},[81,7504,7505],{"class":83,"line":84},[81,7506,7507],{"class":1064},"# รัน ruff (Python linter) โดยไม่ต้องติดตั้ง\n",[81,7509,7510,7512,7515,7518],{"class":83,"line":102},[81,7511,7492],{"class":87},[81,7513,7514],{"class":90}," ruff",[81,7516,7517],{"class":90}," check",[81,7519,7520],{"class":90}," .\n",[81,7522,7523],{"class":83,"line":113},[81,7524,366],{"emptyLinePlaceholder":365},[81,7526,7527],{"class":83,"line":247},[81,7528,7529],{"class":1064},"# รัน black (code formatter)\n",[81,7531,7532,7534,7537],{"class":83,"line":393},[81,7533,7492],{"class":87},[81,7535,7536],{"class":90}," black",[81,7538,7539],{"class":90}," my_script.py\n",[81,7541,7542],{"class":83,"line":404},[81,7543,366],{"emptyLinePlaceholder":365},[81,7545,7546],{"class":83,"line":415},[81,7547,7548],{"class":1064},"# รัน httpie\n",[81,7550,7551,7553,7556],{"class":83,"line":1438},[81,7552,7492],{"class":87},[81,7554,7555],{"class":90}," httpie",[81,7557,7558],{"class":90}," https://api.github.com\n",[580,7560,7562],{"id":7561},"ติดตั้ง-tool-แบบถาวร","ติดตั้ง tool แบบถาวร",[14,7564,7565,7566],{},"ถ้าเป็นเครื่องมือที่ใช้บ่อย ๆ ติดตั้งถาวรได้ด้วย ",[18,7567,7568],{},"uv tool install",[23,7570,7572],{"className":75,"code":7571,"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,7573,7574,7579,7590,7594,7599,7610,7614,7619],{"__ignoreMap":31},[81,7575,7576],{"class":83,"line":84},[81,7577,7578],{"class":1064},"# ติดตั้ง ruff แบบถาวร\n",[81,7580,7581,7583,7585,7587],{"class":83,"line":102},[81,7582,5872],{"class":87},[81,7584,5875],{"class":90},[81,7586,2828],{"class":90},[81,7588,7589],{"class":90}," ruff\n",[81,7591,7592],{"class":83,"line":113},[81,7593,366],{"emptyLinePlaceholder":365},[81,7595,7596],{"class":83,"line":247},[81,7597,7598],{"class":1064},"# ติดตั้ง youtube-dl\n",[81,7600,7601,7603,7605,7607],{"class":83,"line":393},[81,7602,5872],{"class":87},[81,7604,5875],{"class":90},[81,7606,2828],{"class":90},[81,7608,7609],{"class":90}," yt-dlp\n",[81,7611,7612],{"class":83,"line":404},[81,7613,366],{"emptyLinePlaceholder":365},[81,7615,7616],{"class":83,"line":415},[81,7617,7618],{"class":1064},"# ติดตั้งจาก git repository\n",[81,7620,7621,7623,7625,7627],{"class":83,"line":1438},[81,7622,5872],{"class":87},[81,7624,5875],{"class":90},[81,7626,2828],{"class":90},[81,7628,6124],{"class":90},[580,7630,7632],{"id":7631},"รัน-tool-แบบระบุเวอร์ชัน","รัน tool แบบระบุเวอร์ชัน",[14,7634,7635],{},"ระบุเวอร์ชันของ tool ที่ต้องการรันได้เลย เหมาะสำหรับกรณีที่ต้องใช้เวอร์ชันเฉพาะ หรืออยากทดสอบหลาย ๆ เวอร์ชัน",[23,7637,7639],{"className":75,"code":7638,"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,7640,7641,7646,7657,7661,7666,7677,7681,7686],{"__ignoreMap":31},[81,7642,7643],{"class":83,"line":84},[81,7644,7645],{"class":1064},"# รัน ruff เวอร์ชันที่ต้องการ\n",[81,7647,7648,7650,7653,7655],{"class":83,"line":102},[81,7649,7492],{"class":87},[81,7651,7652],{"class":90}," ruff@0.11.0",[81,7654,7517],{"class":90},[81,7656,7520],{"class":90},[81,7658,7659],{"class":83,"line":113},[81,7660,366],{"emptyLinePlaceholder":365},[81,7662,7663],{"class":83,"line":247},[81,7664,7665],{"class":1064},"# รัน ruff เวอร์ชันล่าสุด\n",[81,7667,7668,7670,7673,7675],{"class":83,"line":393},[81,7669,7492],{"class":87},[81,7671,7672],{"class":90}," ruff@latest",[81,7674,7517],{"class":90},[81,7676,7520],{"class":90},[81,7678,7679],{"class":83,"line":404},[81,7680,366],{"emptyLinePlaceholder":365},[81,7682,7683],{"class":83,"line":415},[81,7684,7685],{"class":1064},"# รัน black เวอร์ชันเฉพาะ\n",[81,7687,7688,7690,7693],{"class":83,"line":1438},[81,7689,7492],{"class":87},[81,7691,7692],{"class":90}," black@24.4.2",[81,7694,7539],{"class":90},[14,7696,7697],{},"ติดตั้งถาวรแบบระบุเวอร์ชันก็ทำได้เช่นกัน",[23,7699,7701],{"className":75,"code":7700,"language":77,"meta":31,"style":31},"uv tool install 'ruff==0.11.0'\nuv tool install 'black>=24.0,\u003C25.0'\n",[18,7702,7703,7718],{"__ignoreMap":31},[81,7704,7705,7707,7709,7711,7713,7716],{"class":83,"line":84},[81,7706,5872],{"class":87},[81,7708,5875],{"class":90},[81,7710,2828],{"class":90},[81,7712,129],{"class":128},[81,7714,7715],{"class":90},"ruff==0.11.0",[81,7717,135],{"class":128},[81,7719,7720,7722,7724,7726,7728,7731],{"class":83,"line":102},[81,7721,5872],{"class":87},[81,7723,5875],{"class":90},[81,7725,2828],{"class":90},[81,7727,129],{"class":128},[81,7729,7730],{"class":90},"black>=24.0,\u003C25.0",[81,7732,135],{"class":128},[14,7734,7735],{},"ดูรายการเครื่องมือที่ติดตั้งไว้",[23,7737,7739],{"className":75,"code":7738,"language":77,"meta":31,"style":31},"uv tool list\n",[18,7740,7741],{"__ignoreMap":31},[81,7742,7743,7745,7747],{"class":83,"line":84},[81,7744,5872],{"class":87},[81,7746,5875],{"class":90},[81,7748,7749],{"class":90}," list\n",[10,7751,7753],{"id":7752},"_3-ใช้งานกับ-python-project","3. ใช้งานกับ Python Project",[580,7755,7757],{"id":7756},"สร้าง-project-ใหม่","สร้าง Project ใหม่",[23,7759,7761],{"className":75,"code":7760,"language":77,"meta":31,"style":31},"uv init my-project\ncd my-project\n",[18,7762,7763,7773],{"__ignoreMap":31},[81,7764,7765,7767,7770],{"class":83,"line":84},[81,7766,5872],{"class":87},[81,7768,7769],{"class":90}," init",[81,7771,7772],{"class":90}," my-project\n",[81,7774,7775,7777],{"class":83,"line":102},[81,7776,7296],{"class":637},[81,7778,7772],{"class":90},[14,7780,7781],{},"uv จะสร้างไฟล์ให้อัตโนมัติ",[23,7783,7786],{"className":7784,"code":7785,"language":28},[26],"my-project/\n├── .python-version\n├── README.md\n├── main.py\n└── pyproject.toml\n",[18,7787,7785],{"__ignoreMap":31},[580,7789,7791],{"id":7790},"เพิ่มลบ-dependencies","เพิ่ม/ลบ Dependencies",[23,7793,7795],{"className":75,"code":7794,"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,7796,7797,7802,7811,7823,7827,7832,7845,7849,7854,7863,7867,7872,7884,7888,7893],{"__ignoreMap":31},[81,7798,7799],{"class":83,"line":84},[81,7800,7801],{"class":1064},"# เพิ่ม package\n",[81,7803,7804,7806,7808],{"class":83,"line":102},[81,7805,5872],{"class":87},[81,7807,2853],{"class":90},[81,7809,7810],{"class":90}," requests\n",[81,7812,7813,7815,7817,7820],{"class":83,"line":113},[81,7814,5872],{"class":87},[81,7816,2853],{"class":90},[81,7818,7819],{"class":90}," flask",[81,7821,7822],{"class":90}," sqlalchemy\n",[81,7824,7825],{"class":83,"line":247},[81,7826,366],{"emptyLinePlaceholder":365},[81,7828,7829],{"class":83,"line":393},[81,7830,7831],{"class":1064},"# เพิ่มแบบระบุเวอร์ชัน\n",[81,7833,7834,7836,7838,7840,7843],{"class":83,"line":404},[81,7835,5872],{"class":87},[81,7837,2853],{"class":90},[81,7839,129],{"class":128},[81,7841,7842],{"class":90},"requests==2.32.0",[81,7844,135],{"class":128},[81,7846,7847],{"class":83,"line":415},[81,7848,366],{"emptyLinePlaceholder":365},[81,7850,7851],{"class":83,"line":1438},[81,7852,7853],{"class":1064},"# เพิ่มจาก git\n",[81,7855,7856,7858,7860],{"class":83,"line":1455},[81,7857,5872],{"class":87},[81,7859,2853],{"class":90},[81,7861,7862],{"class":90}," git+https://github.com/psf/requests\n",[81,7864,7865],{"class":83,"line":1467},[81,7866,366],{"emptyLinePlaceholder":365},[81,7868,7869],{"class":83,"line":1483},[81,7870,7871],{"class":1064},"# เพิ่มจาก requirements.txt ที่มีอยู่แล้ว\n",[81,7873,7874,7876,7878,7881],{"class":83,"line":1497},[81,7875,5872],{"class":87},[81,7877,2853],{"class":90},[81,7879,7880],{"class":94}," -r",[81,7882,7883],{"class":90}," requirements.txt\n",[81,7885,7886],{"class":83,"line":1509},[81,7887,366],{"emptyLinePlaceholder":365},[81,7889,7890],{"class":83,"line":1514},[81,7891,7892],{"class":1064},"# ลบ package\n",[81,7894,7895,7897,7900],{"class":83,"line":1519},[81,7896,5872],{"class":87},[81,7898,7899],{"class":90}," remove",[81,7901,7810],{"class":90},[580,7903,7905],{"id":7904},"รัน-project","รัน Project",[23,7907,7909],{"className":75,"code":7908,"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,7910,7911,7916,7925,7929,7934,7943],{"__ignoreMap":31},[81,7912,7913],{"class":83,"line":84},[81,7914,7915],{"class":1064},"# รันไฟล์ Python ใน project\n",[81,7917,7918,7920,7922],{"class":83,"line":102},[81,7919,5872],{"class":87},[81,7921,91],{"class":90},[81,7923,7924],{"class":90}," main.py\n",[81,7926,7927],{"class":83,"line":113},[81,7928,366],{"emptyLinePlaceholder":365},[81,7930,7931],{"class":83,"line":247},[81,7932,7933],{"class":1064},"# รัน framework command เช่น Flask\n",[81,7935,7936,7938,7940],{"class":83,"line":393},[81,7937,5872],{"class":87},[81,7939,2853],{"class":90},[81,7941,7942],{"class":90}," flask\n",[81,7944,7945,7947,7949,7952,7954,7956,7958],{"class":83,"line":404},[81,7946,5872],{"class":87},[81,7948,91],{"class":90},[81,7950,7951],{"class":94}," --",[81,7953,7819],{"class":90},[81,7955,91],{"class":90},[81,7957,6795],{"class":94},[81,7959,7960],{"class":655}," 3000\n",[14,7962,7963,7966,7967,818,7970,4615],{},[18,7964,7965],{},"uv run"," จะ sync dependencies และสร้าง virtual environment ให้อัตโนมัติ ไม่ต้อง ",[18,7968,7969],{},"pip install",[18,7971,7972],{},"source .venv/bin/activate",[580,7974,7976],{"id":7975},"จัดการเวอร์ชัน-python","จัดการเวอร์ชัน Python",[23,7978,7980],{"className":75,"code":7979,"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,7981,7982,7987,7996,8000,8005,8016,8020,8025],{"__ignoreMap":31},[81,7983,7984],{"class":83,"line":84},[81,7985,7986],{"class":1064},"# ดูเวอร์ชัน Python ที่มีให้ติดตั้ง\n",[81,7988,7989,7991,7994],{"class":83,"line":102},[81,7990,5872],{"class":87},[81,7992,7993],{"class":90}," python",[81,7995,7749],{"class":90},[81,7997,7998],{"class":83,"line":113},[81,7999,366],{"emptyLinePlaceholder":365},[81,8001,8002],{"class":83,"line":247},[81,8003,8004],{"class":1064},"# ติดตั้ง Python เวอร์ชันที่ต้องการ\n",[81,8006,8007,8009,8011,8013],{"class":83,"line":393},[81,8008,5872],{"class":87},[81,8010,7993],{"class":90},[81,8012,2828],{"class":90},[81,8014,8015],{"class":655}," 3.12\n",[81,8017,8018],{"class":83,"line":404},[81,8019,366],{"emptyLinePlaceholder":365},[81,8021,8022],{"class":83,"line":415},[81,8023,8024],{"class":1064},"# ตรึงเวอร์ชัน Python ของ project\n",[81,8026,8027,8029,8031,8034],{"class":83,"line":1438},[81,8028,5872],{"class":87},[81,8030,7993],{"class":90},[81,8032,8033],{"class":90}," pin",[81,8035,8015],{"class":655},[10,8037,8039],{"id":8038},"_4-อื่น-ๆ-ที่น่าสนใจ","4. อื่น ๆ ที่น่าสนใจ",[580,8041,8043],{"id":8042},"รัน-script-พร้อม-dependencies-แบบ-inline","รัน Script พร้อม Dependencies แบบ Inline",[14,8045,8046,8047],{},"ประกาศ dependencies ไว้ในไฟล์ Python ได้เลย ผ่าน inline metadata ตาม ",[910,8048,8051],{"href":8049,"rel":8050},"https://peps.python.org/pep-0723/",[914],"PEP 723",[23,8053,8058],{"className":8054,"code":8055,"filename":8056,"language":8057,"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,8059,8060,8068,8073,8078,8083,8090,8094,8101,8105,8132,8149],{"__ignoreMap":31},[81,8061,8062,8065],{"class":83,"line":84},[81,8063,8064],{"class":1064},"#",[81,8066,8067],{"class":1064}," /// script\n",[81,8069,8070],{"class":83,"line":102},[81,8071,8072],{"class":1064},"# dependencies = [\n",[81,8074,8075],{"class":83,"line":113},[81,8076,8077],{"class":1064},"#   \"httpx\",\n",[81,8079,8080],{"class":83,"line":247},[81,8081,8082],{"class":1064},"# ]\n",[81,8084,8085,8087],{"class":83,"line":393},[81,8086,8064],{"class":1064},[81,8088,8089],{"class":1064}," ///\n",[81,8091,8092],{"class":83,"line":404},[81,8093,366],{"emptyLinePlaceholder":365},[81,8095,8096,8098],{"class":83,"line":415},[81,8097,2888],{"class":604},[81,8099,8100],{"class":1449}," httpx\n",[81,8102,8103],{"class":83,"line":1438},[81,8104,366],{"emptyLinePlaceholder":365},[81,8106,8107,8110,8112,8115,8117,8121,8123,8125,8128,8130],{"class":83,"line":1455},[81,8108,8109],{"class":1449},"resp ",[81,8111,3130],{"class":1598},[81,8113,8114],{"class":1449}," httpx",[81,8116,1609],{"class":628},[81,8118,8120],{"class":8119},"s5BH7","get",[81,8122,1616],{"class":628},[81,8124,1255],{"class":128},[81,8126,8127],{"class":90},"https://peps.python.org/api/peps.json",[81,8129,1255],{"class":128},[81,8131,1730],{"class":628},[81,8133,8134,8137,8139,8142,8144,8146],{"class":83,"line":1467},[81,8135,8136],{"class":1449},"data ",[81,8138,3130],{"class":1598},[81,8140,8141],{"class":1449}," resp",[81,8143,1609],{"class":628},[81,8145,1235],{"class":8119},[81,8147,8148],{"class":628},"()\n",[81,8150,8151,8154,8157,8161,8163,8166,8169,8171,8173,8175,8178,8180,8183,8185,8188,8191,8194,8196,8199,8202,8205],{"class":83,"line":1483},[81,8152,8153],{"class":637},"print",[81,8155,8156],{"class":628},"([(",[81,8158,8160],{"class":8159},"sb6-j","k",[81,8162,1724],{"class":628},[81,8164,8165],{"class":8159}," v",[81,8167,8168],{"class":628},"[",[81,8170,1255],{"class":128},[81,8172,5075],{"class":90},[81,8174,1255],{"class":128},[81,8176,8177],{"class":628},"])",[81,8179,3711],{"class":604},[81,8181,8182],{"class":8159}," k",[81,8184,1724],{"class":628},[81,8186,8187],{"class":8159}," v ",[81,8189,8190],{"class":604},"in",[81,8192,8193],{"class":8159}," data",[81,8195,1609],{"class":628},[81,8197,8198],{"class":8119},"items",[81,8200,8201],{"class":628},"()][:",[81,8203,8204],{"class":655},"10",[81,8206,2341],{"class":628},[23,8208,8210],{"className":75,"code":8209,"language":77,"meta":31,"style":31},"uv run fetch_peps.py\n",[18,8211,8212],{"__ignoreMap":31},[81,8213,8214,8216,8218],{"class":83,"line":84},[81,8215,5872],{"class":87},[81,8217,91],{"class":90},[81,8219,8220],{"class":90}," fetch_peps.py\n",[14,8222,8223],{},"uv จะอ่าน dependencies จาก comment ในไฟล์แล้วติดตั้งให้อัตโนมัติ เหมาะสำหรับ script เล็ก ๆ ที่ไม่อยากสร้าง project เต็มรูปแบบ",[580,8225,8227],{"id":8226},"รัน-script-พร้อมระบุ-dependencies-ผ่าน-cli","รัน Script พร้อมระบุ Dependencies ผ่าน CLI",[23,8229,8231],{"className":75,"code":8230,"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,8232,8233,8238,8253,8257,8262],{"__ignoreMap":31},[81,8234,8235],{"class":83,"line":84},[81,8236,8237],{"class":1064},"# รัน script พร้อมติดตั้ง rich ให้อัตโนมัติ\n",[81,8239,8240,8242,8244,8247,8250],{"class":83,"line":102},[81,8241,5872],{"class":87},[81,8243,91],{"class":90},[81,8245,8246],{"class":94}," --with",[81,8248,8249],{"class":90}," rich",[81,8251,8252],{"class":90}," example.py\n",[81,8254,8255],{"class":83,"line":113},[81,8256,366],{"emptyLinePlaceholder":365},[81,8258,8259],{"class":83,"line":247},[81,8260,8261],{"class":1064},"# ระบุเวอร์ชันได้\n",[81,8263,8264,8266,8268,8270,8272,8275,8277],{"class":83,"line":393},[81,8265,5872],{"class":87},[81,8267,91],{"class":90},[81,8269,8246],{"class":94},[81,8271,129],{"class":128},[81,8273,8274],{"class":90},"rich>12,\u003C13",[81,8276,3019],{"class":128},[81,8278,8252],{"class":90},[580,8280,8282],{"id":8281},"ใช้-uv-ใน-docker","ใช้ uv ใน Docker",[23,8284,8288],{"className":8285,"code":8286,"language":8287,"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,8289,8290,8295,8299,8304,8309,8313,8318,8323,8328,8332,8337,8342,8346],{"__ignoreMap":31},[81,8291,8292],{"class":83,"line":84},[81,8293,8294],{},"FROM python:3.12-slim\n",[81,8296,8297],{"class":83,"line":102},[81,8298,366],{"emptyLinePlaceholder":365},[81,8300,8301],{"class":83,"line":113},[81,8302,8303],{},"# ติดตั้ง uv\n",[81,8305,8306],{"class":83,"line":247},[81,8307,8308],{},"COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/\n",[81,8310,8311],{"class":83,"line":393},[81,8312,366],{"emptyLinePlaceholder":365},[81,8314,8315],{"class":83,"line":404},[81,8316,8317],{},"# ติดตั้ง dependencies\n",[81,8319,8320],{"class":83,"line":415},[81,8321,8322],{},"COPY pyproject.toml uv.lock ./\n",[81,8324,8325],{"class":83,"line":1438},[81,8326,8327],{},"RUN uv sync --frozen --no-dev\n",[81,8329,8330],{"class":83,"line":1455},[81,8331,366],{"emptyLinePlaceholder":365},[81,8333,8334],{"class":83,"line":1467},[81,8335,8336],{},"# คัดลอก source code\n",[81,8338,8339],{"class":83,"line":1483},[81,8340,8341],{},"COPY . .\n",[81,8343,8344],{"class":83,"line":1497},[81,8345,366],{"emptyLinePlaceholder":365},[81,8347,8348],{"class":83,"line":1509},[81,8349,8350],{},"CMD [\"uv\", \"run\", \"main.py\"]\n",[580,8352,8354],{"id":8353},"ใช้-uv-แทน-pip-ได้เลย","ใช้ uv แทน pip ได้เลย",[14,8356,8357,8358,8361,8362,8364],{},"คนที่ยังชินกับ pip ใช้ ",[18,8359,8360],{},"uv pip"," แทนได้เลย แค่เติม ",[18,8363,5872],{}," ข้างหน้า",[23,8366,8368],{"className":75,"code":8367,"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,8369,8370,8381,8393,8402],{"__ignoreMap":31},[81,8371,8372,8374,8377,8379],{"class":83,"line":84},[81,8373,5872],{"class":87},[81,8375,8376],{"class":90}," pip",[81,8378,2828],{"class":90},[81,8380,7810],{"class":90},[81,8382,8383,8385,8387,8389,8391],{"class":83,"line":102},[81,8384,5872],{"class":87},[81,8386,8376],{"class":90},[81,8388,2828],{"class":90},[81,8390,7880],{"class":94},[81,8392,7883],{"class":90},[81,8394,8395,8397,8399],{"class":83,"line":113},[81,8396,5872],{"class":87},[81,8398,8376],{"class":90},[81,8400,8401],{"class":90}," freeze\n",[81,8403,8404,8406,8408,8411],{"class":83,"line":247},[81,8405,5872],{"class":87},[81,8407,8376],{"class":90},[81,8409,8410],{"class":90}," uninstall",[81,8412,7810],{"class":90},[10,8414,845],{"id":845},[14,8416,8417],{},"uv ช่วยให้จัดการ Python ง่ายขึ้นเยอะ ตั้งแต่ติดตั้งตัว Python เอง จัดการ dependencies ไปจนถึงรันเครื่องมือต่าง ๆ ทำได้ด้วยคำสั่งเดียว และที่สำคัญคือเร็วมาก ๆ ลองใช้ดูครับ",[10,8419,8421],{"id":8420},"ref","Ref",[904,8423,8424,8430],{},[907,8425,8426],{},[910,8427,8428],{"href":8428,"rel":8429},"https://docs.astral.sh/uv/",[914],[907,8431,8432],{},[910,8433,8434],{"href":8434,"rel":8435},"https://github.com/astral-sh/uv",[914],[938,8437,8438],{},"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":8440},[8441,8442,8447,8452,8458,8464,8465],{"id":7367,"depth":102,"text":7368},{"id":7382,"depth":102,"text":7383,"children":8443},[8444,8445,8446],{"id":7386,"depth":113,"text":7387},{"id":7404,"depth":113,"text":7405},{"id":7445,"depth":113,"text":7445},{"id":7486,"depth":102,"text":7487,"children":8448},[8449,8450,8451],{"id":7496,"depth":113,"text":7497},{"id":7561,"depth":113,"text":7562},{"id":7631,"depth":113,"text":7632},{"id":7752,"depth":102,"text":7753,"children":8453},[8454,8455,8456,8457],{"id":7756,"depth":113,"text":7757},{"id":7790,"depth":113,"text":7791},{"id":7904,"depth":113,"text":7905},{"id":7975,"depth":113,"text":7976},{"id":8038,"depth":102,"text":8039,"children":8459},[8460,8461,8462,8463],{"id":8042,"depth":113,"text":8043},{"id":8226,"depth":113,"text":8227},{"id":8281,"depth":113,"text":8282},{"id":8353,"depth":113,"text":8354},{"id":845,"depth":102,"text":845},{"id":8420,"depth":102,"text":8421},"2026-03-30T00:00:00.000Z","แนะนำวิธีติดตั้งและใช้งาน uv เครื่องมือจัดการ Python ที่เร็วสุด ๆ ตั้งแต่ติดตั้ง รันเครื่องมือ ไปจนถึงจัดการ Project",{},"How To Use UV","/blogs/2026-03-30-how-to-use-uv",{"title":7362,"description":8467},{"loc":8470},"blogs/2026-03-30-how-to-use-uv",[968,8475],"Python","5zmBbVVLRP_QG30Gjt2PGYbATEjwpFqmj9fEVntBbCA",1785765445162]