[{"data":1,"prerenderedAt":7903},["ShallowReactive",2],{"blogs-2026-08-07-node-exporter-mtls-basic-auth":3,"next-blog-2026-08-07-node-exporter-mtls-basic-auth":1804,"prev-blog-2026-08-07-node-exporter-mtls-basic-auth":1804,"recommended-blog-2026-08-07-node-exporter-mtls-basic-auth":1816,"related-blog-2026-08-07-node-exporter-mtls-basic-auth":3849},{"id":4,"title":5,"body":6,"date":1799,"description":1800,"draft":1801,"extension":1802,"meta":1803,"navigation":344,"next":1804,"ogTitle":1805,"path":1806,"pin":1801,"prev":1804,"recommends":1807,"seo":1810,"sitemap":1811,"stem":1812,"tags":1813,"updated":1804,"__hash__":1815},"content/blogs/2026-08-07-node-exporter-mtls-basic-auth.md","ตั้งค่า node_exporter ให้ปลอดภัยด้วย mTLS และ Basic auth",{"type":7,"value":8,"toc":1783},"minimark",[9,14,18,39,46,61,65,68,123,126,176,184,187,228,234,237,240,258,262,265,272,278,281,287,293,300,306,310,313,615,622,644,650,654,666,697,700,706,717,721,731,802,820,831,834,837,892,895,898,903,906,933,936,952,958,961,975,981,984,1003,1009,1016,1025,1028,1062,1068,1071,1133,1139,1143,1146,1297,1304,1310,1317,1332,1339,1375,1381,1388,1430,1436,1443,1447,1453,1505,1511,1514,1520,1523,1530,1534,1537,1543,1577,1583,1599,1620,1623,1643,1657,1667,1670,1673,1735,1738,1744,1747,1750,1754,1779],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"สวัสดีครับ วันนี้มาแชร์วิธีทำให้ node_exporter ปลอดภัยขึ้นกัน ด้วยการเปิด Basic auth กับ mTLS ครับ เพราะโดยปกติแล้ว Port ดังกล่าวจะสามารถดึงข้อมูลได้ทันที",[15,19,20,21,25,26,33,34,38],{},"ระหว่างทางผมไปเจอเรื่องที่ทำให้งงอยู่พักนึงด้วย คือไฟล์ ",[22,23,24],"code",{},"web-config.yml"," ที่ปกติเราเขียนให้ Prometheus (",[27,28,32],"a",{"href":29,"rel":30},"https://prometheus.io/docs/guides/basic-auth/",[31],"nofollow","Securing Prometheus API and UI endpoints using basic auth",") ",[35,36,37],"strong",{},"เอามาใช้กับ node_exporter ได้เลย"," ทั้งที่ผมเข้าใจมาตลอดว่ามันเป็น config ของ Prometheus อย่างเดียว",[15,40,41,42,45],{},"บทความนี้ผมรันด้วย docker ทั้งหมดนะครับ เพราะลองแล้วลบทิ้งได้ง่ายดี output ทุกก้อนก๊อปมาจาก terminal ตรง ๆ (มีแก้แค่ชื่อเครื่องกับ arch ให้เป็นค่าทั่วไปนะ) เวอร์ชันที่ใช้คือ node_exporter 1.12.1 กับ Prometheus 3.13.2 (ใครรันแบบ bare metal อยู่แล้วก็อ่านข้ามส่วน ",[22,43,44],{},"docker run"," ไปได้ ตัว config เหมือนกันหมด เดี๋ยวมีบอกท้ายบทความว่าแก้ systemd ตรงไหน)",[15,47,48,49,52,53,56,57,60],{},"ของที่ต้องมีคือ docker กับ ",[22,50,51],{},"jq"," ส่วน ",[22,54,55],{},"htpasswd"," กับ ",[22,58,59],{},"promtool"," เดี๋ยวเรียกผ่าน docker เอา ไม่ต้องลงเพิ่ม",[10,62,64],{"id":63},"ปัญหาของพอร์ต-9100","ปัญหาของพอร์ต 9100",[15,66,67],{},"ลองยกตัวที่ไม่ได้ตั้งอะไรไว้เลยขึ้นมาดูก่อนครับ",[69,70,75],"pre",{"className":71,"code":72,"language":73,"meta":74,"style":74},"language-sh shiki shiki-themes material-theme-lighter one-dark-pro github-dark","docker network create mon-net\ndocker run -d --name ne-open --network mon-net quay.io/prometheus/node-exporter:latest\n","sh","",[22,76,77,96],{"__ignoreMap":74},[78,79,82,86,90,93],"span",{"class":80,"line":81},"line",1,[78,83,85],{"class":84},"sosa_","docker",[78,87,89],{"class":88},"sAlJX"," network",[78,91,92],{"class":88}," create",[78,94,95],{"class":88}," mon-net\n",[78,97,99,101,104,108,111,114,117,120],{"class":80,"line":98},2,[78,100,85],{"class":84},[78,102,103],{"class":88}," run",[78,105,107],{"class":106},"sEoV_"," -d",[78,109,110],{"class":106}," --name",[78,112,113],{"class":88}," ne-open",[78,115,116],{"class":106}," --network",[78,118,119],{"class":88}," mon-net",[78,121,122],{"class":88}," quay.io/prometheus/node-exporter:latest\n",[15,124,125],{},"แล้วยิงเข้าไปเฉย ๆ โดยไม่ต้องมี credential",[69,127,129],{"className":71,"code":128,"language":73,"meta":74,"style":74},"docker run --rm --network mon-net curlimages/curl:latest \\\n  -s http://ne-open:9100/metrics | grep '^node_uname_info'\n",[22,130,131,151],{"__ignoreMap":74},[78,132,133,135,137,140,142,144,147],{"class":80,"line":81},[78,134,85],{"class":84},[78,136,103],{"class":88},[78,138,139],{"class":106}," --rm",[78,141,116],{"class":106},[78,143,119],{"class":88},[78,145,146],{"class":88}," curlimages/curl:latest",[78,148,150],{"class":149},"sicAX"," \\\n",[78,152,153,156,159,163,166,170,173],{"class":80,"line":98},[78,154,155],{"class":106},"  -s",[78,157,158],{"class":88}," http://ne-open:9100/metrics",[78,160,162],{"class":161},"sbGrh"," |",[78,164,165],{"class":84}," grep",[78,167,169],{"class":168},"snfNA"," '",[78,171,172],{"class":88},"^node_uname_info",[78,174,175],{"class":168},"'\n",[69,177,182],{"className":178,"code":180,"language":181},[179],"language-text","node_uname_info{domainname=\"(none)\",machine=\"x86_64\",nodename=\"homelab-01\",sysname=\"Linux\",version=\"#1 SMP PREEMPT Fri Jul 31 00:11:41 UTC 2026\"} 1\n","text",[22,183,180],{"__ignoreMap":74},[15,185,186],{},"ได้ชื่อเครื่อง สถาปัตยกรรม และเวอร์ชัน kernel มาครบ ลองนับทั้งหมดดู",[69,188,190],{"className":71,"code":189,"language":73,"meta":74,"style":74},"docker run --rm --network mon-net curlimages/curl:latest \\\n  -s http://ne-open:9100/metrics | grep -c '^node_'\n",[22,191,192,208],{"__ignoreMap":74},[78,193,194,196,198,200,202,204,206],{"class":80,"line":81},[78,195,85],{"class":84},[78,197,103],{"class":88},[78,199,139],{"class":106},[78,201,116],{"class":106},[78,203,119],{"class":88},[78,205,146],{"class":88},[78,207,150],{"class":149},[78,209,210,212,214,216,218,221,223,226],{"class":80,"line":98},[78,211,155],{"class":106},[78,213,158],{"class":88},[78,215,162],{"class":161},[78,217,165],{"class":84},[78,219,220],{"class":106}," -c",[78,222,169],{"class":168},[78,224,225],{"class":88},"^node_",[78,227,175],{"class":168},[69,229,232],{"className":230,"code":231,"language":181},[179],"906\n",[22,233,231],{"__ignoreMap":74},[15,235,236],{},"906 บรรทัดครับ มีทั้ง mount point ที่มีอยู่ พื้นที่ดิสก์ที่เหลือ interface ที่ต่ออยู่ แล้วก็เวอร์ชัน kernel ที่บอกกลาย ๆ ว่าเครื่องนี้แพตช์ล่าสุดเมื่อไหร่ ข้อมูลพวกนี้อย่างเดียวไม่ได้ทำให้ใครเข้าเครื่องได้ แต่มันก็นับว่าเป็นข้อมูลที่สำคัญในการนำไปหาข้อมูลต่อได้",[15,238,239],{},"ลบตัวทดลองทิ้งก่อนไปต่อ",[69,241,243],{"className":71,"code":242,"language":73,"meta":74,"style":74},"docker rm -f ne-open\n",[22,244,245],{"__ignoreMap":74},[78,246,247,249,252,255],{"class":80,"line":81},[78,248,85],{"class":84},[78,250,251],{"class":88}," rm",[78,253,254],{"class":106}," -f",[78,256,257],{"class":88}," ne-open\n",[10,259,261],{"id":260},"config-ที่ใช้ร่วมกัน","config ที่ใช้ร่วมกัน",[15,263,264],{},"ทีนี้มาถึงเรื่องที่ผมเข้าใจผิดมาตลอดครับ",[15,266,267,268,271],{},"พอเปิด ",[22,269,270],{},"--help"," ของ node_exporter ดู มันชี้ไปที่ repo ของ exporter-toolkit ตรง ๆ เลย",[69,273,276],{"className":274,"code":275,"language":181},[179],"--web.config.file=\"\"       Path to configuration file that can\n                           enable TLS or authentication. See:\n                           https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md\n",[22,277,275],{"__ignoreMap":74},[15,279,280],{},"ในเอกสารตัวนั้นก็เขียนไว้ตรง ๆ ว่า \"Exporters and services instrumented with the Exporter Toolkit share the same web configuration file format.\"",[15,282,283,284],{},"แปลว่า Prometheus, node_exporter, Alertmanager และ exporter ตัวอื่น ๆ ในตระกูลนี้ใช้ library ตัวเดียวกันจัดการเรื่อง TLS กับ auth ไฟล์ config เลยหน้าตาเหมือนกันหมด flag ที่ใช้เปิดก็ชื่อเดียวกันคือ ",[22,285,286],{},"--web.config.file",[15,288,289],{},[290,291],"img",{"alt":74,"src":292},"/images/2026-08-07-node-exporter-mtls-basic-auth/shared-config.png",[15,294,295,296,299],{},"ตอนรัน node_exporter จะเห็นใน log ด้วยว่ามันเรียก ",[22,297,298],{},"tls_config.go"," ซึ่งเป็นไฟล์ของ exporter-toolkit ไม่ใช่โค้ดของ node_exporter เอง",[69,301,304],{"className":302,"code":303,"language":181},[179],"level=INFO source=tls_config.go:415 msg=\"TLS is enabled.\" http2=true address=[::]:9100\n",[22,305,303],{"__ignoreMap":74},[10,307,309],{"id":308},"สร้าง-cert","สร้าง cert",[15,311,312],{},"mTLS แปลว่าทั้งสองฝั่งต้องยืนยันตัวตนกัน ไม่ใช่แค่ฝั่ง server เพราะฉะนั้นต้องมี CA ของตัวเองไว้เซ็นให้ทั้งคู่",[69,314,316],{"className":71,"code":315,"language":73,"meta":74,"style":74},"mkdir -p ~/node-exporter/certs && cd ~/node-exporter/certs\n\n# CA ของเราเอง\nopenssl req -x509 -newkey rsa:2048 -nodes -days 365 \\\n  -keyout ca.key -out ca.crt -subj \"/CN=homelab-ca\"\n\n# cert ฝั่ง node_exporter\nopenssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr \\\n  -subj \"/CN=node-exporter\"\nprintf \"subjectAltName=DNS:node-exporter,DNS:localhost,IP:127.0.0.1\" > san.ext\nopenssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \\\n  -out server.crt -days 365 -extfile san.ext\n\n# cert ฝั่ง Prometheus\nopenssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr \\\n  -subj \"/CN=prometheus\"\nopenssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \\\n  -out client.crt -days 365\n",[22,317,318,340,346,353,383,410,415,421,447,460,480,511,529,534,540,565,577,602],{"__ignoreMap":74},[78,319,320,323,326,329,333,337],{"class":80,"line":81},[78,321,322],{"class":84},"mkdir",[78,324,325],{"class":106}," -p",[78,327,328],{"class":88}," ~/node-exporter/certs",[78,330,332],{"class":331},"stlKB"," &&",[78,334,336],{"class":335},"sJZfL"," cd",[78,338,339],{"class":88}," ~/node-exporter/certs\n",[78,341,342],{"class":80,"line":98},[78,343,345],{"emptyLinePlaceholder":344},true,"\n",[78,347,349],{"class":80,"line":348},3,[78,350,352],{"class":351},"sDKbk","# CA ของเราเอง\n",[78,354,356,359,362,365,368,371,374,377,381],{"class":80,"line":355},4,[78,357,358],{"class":84},"openssl",[78,360,361],{"class":88}," req",[78,363,364],{"class":106}," -x509",[78,366,367],{"class":106}," -newkey",[78,369,370],{"class":88}," rsa:2048",[78,372,373],{"class":106}," -nodes",[78,375,376],{"class":106}," -days",[78,378,380],{"class":379},"snOyU"," 365",[78,382,150],{"class":149},[78,384,386,389,392,395,398,401,404,407],{"class":80,"line":385},5,[78,387,388],{"class":106},"  -keyout",[78,390,391],{"class":88}," ca.key",[78,393,394],{"class":106}," -out",[78,396,397],{"class":88}," ca.crt",[78,399,400],{"class":106}," -subj",[78,402,403],{"class":168}," \"",[78,405,406],{"class":88},"/CN=homelab-ca",[78,408,409],{"class":168},"\"\n",[78,411,413],{"class":80,"line":412},6,[78,414,345],{"emptyLinePlaceholder":344},[78,416,418],{"class":80,"line":417},7,[78,419,420],{"class":351},"# cert ฝั่ง node_exporter\n",[78,422,424,426,428,430,432,434,437,440,442,445],{"class":80,"line":423},8,[78,425,358],{"class":84},[78,427,361],{"class":88},[78,429,367],{"class":106},[78,431,370],{"class":88},[78,433,373],{"class":106},[78,435,436],{"class":106}," -keyout",[78,438,439],{"class":88}," server.key",[78,441,394],{"class":106},[78,443,444],{"class":88}," server.csr",[78,446,150],{"class":149},[78,448,450,453,455,458],{"class":80,"line":449},9,[78,451,452],{"class":106},"  -subj",[78,454,403],{"class":168},[78,456,457],{"class":88},"/CN=node-exporter",[78,459,409],{"class":168},[78,461,463,466,468,471,474,477],{"class":80,"line":462},10,[78,464,465],{"class":335},"printf",[78,467,403],{"class":168},[78,469,470],{"class":88},"subjectAltName=DNS:node-exporter,DNS:localhost,IP:127.0.0.1",[78,472,473],{"class":168},"\"",[78,475,476],{"class":161}," >",[78,478,479],{"class":88}," san.ext\n",[78,481,483,485,488,491,494,496,499,501,504,506,509],{"class":80,"line":482},11,[78,484,358],{"class":84},[78,486,487],{"class":88}," x509",[78,489,490],{"class":106}," -req",[78,492,493],{"class":106}," -in",[78,495,444],{"class":88},[78,497,498],{"class":106}," -CA",[78,500,397],{"class":88},[78,502,503],{"class":106}," -CAkey",[78,505,391],{"class":88},[78,507,508],{"class":106}," -CAcreateserial",[78,510,150],{"class":149},[78,512,514,517,520,522,524,527],{"class":80,"line":513},12,[78,515,516],{"class":106},"  -out",[78,518,519],{"class":88}," server.crt",[78,521,376],{"class":106},[78,523,380],{"class":379},[78,525,526],{"class":106}," -extfile",[78,528,479],{"class":88},[78,530,532],{"class":80,"line":531},13,[78,533,345],{"emptyLinePlaceholder":344},[78,535,537],{"class":80,"line":536},14,[78,538,539],{"class":351},"# cert ฝั่ง Prometheus\n",[78,541,543,545,547,549,551,553,555,558,560,563],{"class":80,"line":542},15,[78,544,358],{"class":84},[78,546,361],{"class":88},[78,548,367],{"class":106},[78,550,370],{"class":88},[78,552,373],{"class":106},[78,554,436],{"class":106},[78,556,557],{"class":88}," client.key",[78,559,394],{"class":106},[78,561,562],{"class":88}," client.csr",[78,564,150],{"class":149},[78,566,568,570,572,575],{"class":80,"line":567},16,[78,569,452],{"class":106},[78,571,403],{"class":168},[78,573,574],{"class":88},"/CN=prometheus",[78,576,409],{"class":168},[78,578,580,582,584,586,588,590,592,594,596,598,600],{"class":80,"line":579},17,[78,581,358],{"class":84},[78,583,487],{"class":88},[78,585,490],{"class":106},[78,587,493],{"class":106},[78,589,562],{"class":88},[78,591,498],{"class":106},[78,593,397],{"class":88},[78,595,503],{"class":106},[78,597,391],{"class":88},[78,599,508],{"class":106},[78,601,150],{"class":149},[78,603,605,607,610,612],{"class":80,"line":604},18,[78,606,516],{"class":106},[78,608,609],{"class":88}," client.crt",[78,611,376],{"class":106},[78,613,614],{"class":379}," 365\n",[15,616,617,618,621],{},"ตรง ",[22,619,620],{},"subjectAltName"," สำคัญมากครับ ต้องใส่ชื่อหรือ IP ที่ Prometheus จะใช้เรียกจริง ๆ ถ้าใส่ไม่ตรงจะ verify ไม่ผ่านตอน scrape เช็คได้ด้วย",[69,623,625],{"className":71,"code":624,"language":73,"meta":74,"style":74},"openssl verify -CAfile ca.crt server.crt client.crt\n",[22,626,627],{"__ignoreMap":74},[78,628,629,631,634,637,639,641],{"class":80,"line":81},[78,630,358],{"class":84},[78,632,633],{"class":88}," verify",[78,635,636],{"class":106}," -CAfile",[78,638,397],{"class":88},[78,640,519],{"class":88},[78,642,643],{"class":88}," client.crt\n",[69,645,648],{"className":646,"code":647,"language":181},[179],"server.crt: OK\nclient.crt: OK\n",[22,649,647],{"__ignoreMap":74},[10,651,653],{"id":652},"เปิด-basic-auth","เปิด basic auth",[15,655,656,657,659,660,663,664],{},"รหัสผ่านใน ",[22,658,24],{}," ต้องเป็น bcrypt hash ใส่เป็นข้อความเปล่าไม่ได้ (ถ้าใส่ไป node_exporter จะขึ้นมาแป๊บนึงแล้วดับ พร้อม log ว่า ",[22,661,662],{},"hashedSecret too short to be a bcrypted password",") สร้าง hash ด้วย ",[22,665,55],{},[69,667,669],{"className":71,"code":668,"language":73,"meta":74,"style":74},"docker run --rm -it httpd:2.4-alpine htpasswd -nBC 12 prometheus\n",[22,670,671],{"__ignoreMap":74},[78,672,673,675,677,679,682,685,688,691,694],{"class":80,"line":81},[78,674,85],{"class":84},[78,676,103],{"class":88},[78,678,139],{"class":106},[78,680,681],{"class":106}," -it",[78,683,684],{"class":88}," httpd:2.4-alpine",[78,686,687],{"class":88}," htpasswd",[78,689,690],{"class":106}," -nBC",[78,692,693],{"class":379}," 12",[78,695,696],{"class":88}," prometheus\n",[15,698,699],{},"มันจะถามรหัสให้พิมพ์สองรอบ แล้วพ่นออกมาแบบนี้",[69,701,704],{"className":702,"code":703,"language":181},[179],"prometheus:$2y$12$\u003Chash 53 ตัวอักษร>\n",[22,705,703],{"__ignoreMap":74},[15,707,708,709,712,713,716],{},"ที่ไม่ใช้ ",[22,710,711],{},"-b"," แล้วพิมพ์รหัสต่อท้ายคำสั่งไปเลย เพราะรหัสจะไปค้างใน shell history แล้วก็โผล่ใน ",[22,714,715],{},"ps"," ด้วยครับ",[10,718,720],{"id":719},"เปิด-mtls","เปิด mTLS",[15,722,723,724,727,728],{},"เอา hash ที่ได้มาใส่ แล้วเติม ",[22,725,726],{},"tls_server_config"," เข้าไปในไฟล์เดียวกัน สร้างที่ ",[22,729,730],{},"~/node-exporter/web-config.yml",[69,732,734],{"className":71,"code":733,"language":73,"meta":74,"style":74},"cat > ~/node-exporter/web-config.yml \u003C\u003C'EOF'\ntls_server_config:\n  cert_file: /etc/node_exporter/certs/server.crt\n  key_file: /etc/node_exporter/certs/server.key\n  client_auth_type: RequireAndVerifyClientCert\n  client_ca_file: /etc/node_exporter/certs/ca.crt\n  min_version: TLS12\n\nbasic_auth_users:\n  prometheus: $2y$12$\u003Chash ที่ได้จากขั้นตอนที่แล้ว>\nEOF\n",[22,735,736,753,758,763,768,773,778,783,787,792,797],{"__ignoreMap":74},[78,737,738,741,743,746,749],{"class":80,"line":81},[78,739,740],{"class":84},"cat",[78,742,476],{"class":161},[78,744,745],{"class":88}," ~/node-exporter/web-config.yml",[78,747,748],{"class":161}," \u003C\u003C",[78,750,752],{"class":751},"s2KDN","'EOF'\n",[78,754,755],{"class":80,"line":98},[78,756,757],{"class":88},"tls_server_config:\n",[78,759,760],{"class":80,"line":348},[78,761,762],{"class":88},"  cert_file: /etc/node_exporter/certs/server.crt\n",[78,764,765],{"class":80,"line":355},[78,766,767],{"class":88},"  key_file: /etc/node_exporter/certs/server.key\n",[78,769,770],{"class":80,"line":385},[78,771,772],{"class":88},"  client_auth_type: RequireAndVerifyClientCert\n",[78,774,775],{"class":80,"line":412},[78,776,777],{"class":88},"  client_ca_file: /etc/node_exporter/certs/ca.crt\n",[78,779,780],{"class":80,"line":417},[78,781,782],{"class":88},"  min_version: TLS12\n",[78,784,785],{"class":80,"line":423},[78,786,345],{"emptyLinePlaceholder":344},[78,788,789],{"class":80,"line":449},[78,790,791],{"class":88},"basic_auth_users:\n",[78,793,794],{"class":80,"line":462},[78,795,796],{"class":88},"  prometheus: $2y$12$\u003Chash ที่ได้จากขั้นตอนที่แล้ว>\n",[78,798,799],{"class":80,"line":482},[78,800,801],{"class":751},"EOF\n",[15,803,804,805,808,809,811,812,815,816,819],{},"path ใน config เป็น path ",[35,806,807],{},"ข้างใน container"," นะครับ เดี๋ยวตอน ",[22,810,44],{}," เรา mount ",[22,813,814],{},"~/node-exporter/certs"," ไปไว้ที่ ",[22,817,818],{},"/etc/node_exporter/certs"," ให้ตรงกัน ถ้ารันแบบ bare metal ก็เปลี่ยนเป็น path จริงบนเครื่องได้เลย",[15,821,822,823,826,827,830],{},"หัวใจอยู่ที่ ",[22,824,825],{},"client_auth_type: RequireAndVerifyClientCert"," ครับ อันนี้แหละที่ทำให้เป็น mTLS จริง ๆ คือ server จะบังคับให้ client ส่ง cert มาด้วย แล้วตรวจว่าเซ็นด้วย CA ใน ",[22,828,829],{},"client_ca_file"," หรือเปล่า",[15,832,833],{},"เอกสารของ exporter-toolkit เตือนไว้ตรง ๆ ว่า \"If you want to enable client authentication, you need to use RequireAndVerifyClientCert. Other values are insecure.\" อย่าใส่ค่าอื่นแม้ชื่อจะดูคล้ายกันนะครับ",[15,835,836],{},"รันได้แล้ว",[69,838,840],{"className":71,"code":839,"language":73,"meta":74,"style":74},"docker run -d --name node-exporter --network mon-net \\\n  -v ~/node-exporter/web-config.yml:/etc/node_exporter/web-config.yml:ro \\\n  -v ~/node-exporter/certs:/etc/node_exporter/certs:ro \\\n  quay.io/prometheus/node-exporter:latest \\\n  --web.config.file=/etc/node_exporter/web-config.yml\n",[22,841,842,861,871,880,887],{"__ignoreMap":74},[78,843,844,846,848,850,852,855,857,859],{"class":80,"line":81},[78,845,85],{"class":84},[78,847,103],{"class":88},[78,849,107],{"class":106},[78,851,110],{"class":106},[78,853,854],{"class":88}," node-exporter",[78,856,116],{"class":106},[78,858,119],{"class":88},[78,860,150],{"class":149},[78,862,863,866,869],{"class":80,"line":98},[78,864,865],{"class":106},"  -v",[78,867,868],{"class":88}," ~/node-exporter/web-config.yml:/etc/node_exporter/web-config.yml:ro",[78,870,150],{"class":149},[78,872,873,875,878],{"class":80,"line":348},[78,874,865],{"class":106},[78,876,877],{"class":88}," ~/node-exporter/certs:/etc/node_exporter/certs:ro",[78,879,150],{"class":149},[78,881,882,885],{"class":80,"line":355},[78,883,884],{"class":88},"  quay.io/prometheus/node-exporter:latest",[78,886,150],{"class":149},[78,888,889],{"class":80,"line":385},[78,890,891],{"class":106},"  --web.config.file=/etc/node_exporter/web-config.yml\n",[10,893,894],{"id":894},"ทดสอบทีละชั้น",[15,896,897],{},"ตรงนี้สนุกครับ เพราะขาดชั้นไหนไป error ที่ได้ก็คนละแบบกัน ใช้ไล่หาสาเหตุได้เลย",[15,899,900],{},[290,901],{"alt":74,"src":902},"/images/2026-08-07-node-exporter-mtls-basic-auth/auth-layers.png",[15,904,905],{},"ผมตั้ง alias สั้น ๆ ไว้ก่อน จะได้ไม่ต้องพิมพ์ยาว",[69,907,909],{"className":71,"code":908,"language":73,"meta":74,"style":74},"alias c='docker run --rm --network mon-net -v ~/node-exporter/certs:/c:ro curlimages/curl:latest'\n",[22,910,911],{"__ignoreMap":74},[78,912,913,917,921,925,928,931],{"class":80,"line":81},[78,914,916],{"class":915},"sOHk9","alias",[78,918,920],{"class":919},"sLHrO"," c",[78,922,924],{"class":923},"sMTEB","=",[78,926,927],{"class":168},"'",[78,929,930],{"class":88},"docker run --rm --network mon-net -v ~/node-exporter/certs:/c:ro curlimages/curl:latest",[78,932,175],{"class":168},[15,934,935],{},"เริ่มจากยิง http เปล่า ๆ แบบเดิม",[69,937,939],{"className":71,"code":938,"language":73,"meta":74,"style":74},"c -sS http://node-exporter:9100/metrics\n",[22,940,941],{"__ignoreMap":74},[78,942,943,946,949],{"class":80,"line":81},[78,944,945],{"class":84},"c",[78,947,948],{"class":106}," -sS",[78,950,951],{"class":88}," http://node-exporter:9100/metrics\n",[69,953,956],{"className":954,"code":955,"language":181},[179],"Client sent an HTTP request to an HTTPS server.\n",[22,957,955],{"__ignoreMap":74},[15,959,960],{},"เปลี่ยนเป็น https แต่ยังไม่บอก CA",[69,962,964],{"className":71,"code":963,"language":73,"meta":74,"style":74},"c -sS https://node-exporter:9100/metrics\n",[22,965,966],{"__ignoreMap":74},[78,967,968,970,972],{"class":80,"line":81},[78,969,945],{"class":84},[78,971,948],{"class":106},[78,973,974],{"class":88}," https://node-exporter:9100/metrics\n",[69,976,979],{"className":977,"code":978,"language":181},[179],"curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)\nMore details here: https://curl.se/docs/sslcerts.html\n",[22,980,978],{"__ignoreMap":74},[15,982,983],{},"ใส่ CA แล้วแต่ยังไม่มี client cert",[69,985,987],{"className":71,"code":986,"language":73,"meta":74,"style":74},"c -sS --cacert /c/ca.crt https://node-exporter:9100/metrics\n",[22,988,989],{"__ignoreMap":74},[78,990,991,993,995,998,1001],{"class":80,"line":81},[78,992,945],{"class":84},[78,994,948],{"class":106},[78,996,997],{"class":106}," --cacert",[78,999,1000],{"class":88}," /c/ca.crt",[78,1002,974],{"class":88},[69,1004,1007],{"className":1005,"code":1006,"language":181},[179],"curl: (56) OpenSSL SSL_read: OpenSSL/3.5.7: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0\n",[22,1008,1006],{"__ignoreMap":74},[15,1010,1011,1012,1015],{},"บรรทัด ",[22,1013,1014],{},"alert certificate required"," นี่แหละครับคือหลักฐานว่า mTLS ทำงานอยู่ ฝั่ง server ไม่ยอมคุยต่อเพราะเราไม่ได้ส่ง cert ไปให้",[1017,1018,1019],"blockquote",{},[15,1020,1021,1022,1024],{},"ข้อความพวกนี้มาจาก curl ที่ build กับ OpenSSL นะครับ ถ้ายิงจาก macOS ที่ curl ใช้ LibreSSL จะได้เลข exit code กับข้อความคนละแบบ และจะไม่มีคำว่า ",[22,1023,1014],{}," ให้เห็น ผมเลยยิงผ่าน container เอาเพื่อให้ทุกคนได้ผลเหมือนกัน",[15,1026,1027],{},"ใส่ client cert แล้วแต่ยังไม่ใส่ user กับ password",[69,1029,1031],{"className":71,"code":1030,"language":73,"meta":74,"style":74},"c -sS --cacert /c/ca.crt --cert /c/client.crt --key /c/client.key \\\n  https://node-exporter:9100/metrics\n",[22,1032,1033,1057],{"__ignoreMap":74},[78,1034,1035,1037,1039,1041,1043,1046,1049,1052,1055],{"class":80,"line":81},[78,1036,945],{"class":84},[78,1038,948],{"class":106},[78,1040,997],{"class":106},[78,1042,1000],{"class":88},[78,1044,1045],{"class":106}," --cert",[78,1047,1048],{"class":88}," /c/client.crt",[78,1050,1051],{"class":106}," --key",[78,1053,1054],{"class":88}," /c/client.key",[78,1056,150],{"class":149},[78,1058,1059],{"class":80,"line":98},[78,1060,1061],{"class":88},"  https://node-exporter:9100/metrics\n",[69,1063,1066],{"className":1064,"code":1065,"language":181},[179],"Unauthorized\n",[22,1067,1065],{"__ignoreMap":74},[15,1069,1070],{},"คราวนี้ผ่านชั้น TLS มาได้แล้ว มาตกที่ basic auth แทน (HTTP 401) ใส่ให้ครบทั้งหมด",[69,1072,1074],{"className":71,"code":1073,"language":73,"meta":74,"style":74},"c -sS -u prometheus:'รหัสที่ตั้งไว้' \\\n  --cacert /c/ca.crt --cert /c/client.crt --key /c/client.key \\\n  https://node-exporter:9100/metrics | grep -E '^node_(boot_time_seconds|uname_info)'\n",[22,1075,1076,1097,1114],{"__ignoreMap":74},[78,1077,1078,1080,1082,1085,1088,1090,1093,1095],{"class":80,"line":81},[78,1079,945],{"class":84},[78,1081,948],{"class":106},[78,1083,1084],{"class":106}," -u",[78,1086,1087],{"class":88}," prometheus:",[78,1089,927],{"class":168},[78,1091,1092],{"class":88},"รหัสที่ตั้งไว้",[78,1094,927],{"class":168},[78,1096,150],{"class":149},[78,1098,1099,1102,1104,1106,1108,1110,1112],{"class":80,"line":98},[78,1100,1101],{"class":106},"  --cacert",[78,1103,1000],{"class":88},[78,1105,1045],{"class":106},[78,1107,1048],{"class":88},[78,1109,1051],{"class":106},[78,1111,1054],{"class":88},[78,1113,150],{"class":149},[78,1115,1116,1119,1121,1123,1126,1128,1131],{"class":80,"line":348},[78,1117,1118],{"class":88},"  https://node-exporter:9100/metrics",[78,1120,162],{"class":161},[78,1122,165],{"class":84},[78,1124,1125],{"class":106}," -E",[78,1127,169],{"class":168},[78,1129,1130],{"class":88},"^node_(boot_time_seconds|uname_info)",[78,1132,175],{"class":168},[69,1134,1137],{"className":1135,"code":1136,"language":181},[179],"node_boot_time_seconds 1.786004256e+09\nnode_uname_info{domainname=\"(none)\",machine=\"x86_64\",nodename=\"homelab-01\",...} 1\n",[22,1138,1136],{"__ignoreMap":74},[10,1140,1142],{"id":1141},"ฝั่ง-prometheus","ฝั่ง Prometheus",[15,1144,1145],{},"ทีนี้ต้องบอก Prometheus ว่าให้ส่งอะไรไปบ้างตอน scrape",[69,1147,1149],{"className":71,"code":1148,"language":73,"meta":74,"style":74},"cat > ~/node-exporter/prometheus.yml \u003C\u003C'EOF'\nglobal:\n  scrape_interval: 15s\n\nscrape_configs:\n  - job_name: 'node'\n    scheme: https\n    static_configs:\n      - targets: ['node-exporter:9100']\n    basic_auth:\n      username: prometheus\n      password: 'รหัสที่ตั้งไว้'\n    tls_config:\n      ca_file: /etc/prometheus/certs/ca.crt\n      cert_file: /etc/prometheus/certs/client.crt\n      key_file: /etc/prometheus/certs/client.key\nEOF\n\ndocker run -d --name prometheus --network mon-net -p 9090:9090 \\\n  -v ~/node-exporter/prometheus.yml:/etc/prometheus/prometheus.yml:ro \\\n  -v ~/node-exporter/certs:/etc/prometheus/certs:ro \\\n  prom/prometheus:latest\n",[22,1150,1151,1164,1169,1174,1178,1183,1188,1193,1198,1203,1208,1213,1218,1223,1228,1233,1238,1242,1246,1271,1281,1291],{"__ignoreMap":74},[78,1152,1153,1155,1157,1160,1162],{"class":80,"line":81},[78,1154,740],{"class":84},[78,1156,476],{"class":161},[78,1158,1159],{"class":88}," ~/node-exporter/prometheus.yml",[78,1161,748],{"class":161},[78,1163,752],{"class":751},[78,1165,1166],{"class":80,"line":98},[78,1167,1168],{"class":88},"global:\n",[78,1170,1171],{"class":80,"line":348},[78,1172,1173],{"class":88},"  scrape_interval: 15s\n",[78,1175,1176],{"class":80,"line":355},[78,1177,345],{"emptyLinePlaceholder":344},[78,1179,1180],{"class":80,"line":385},[78,1181,1182],{"class":88},"scrape_configs:\n",[78,1184,1185],{"class":80,"line":412},[78,1186,1187],{"class":88},"  - job_name: 'node'\n",[78,1189,1190],{"class":80,"line":417},[78,1191,1192],{"class":88},"    scheme: https\n",[78,1194,1195],{"class":80,"line":423},[78,1196,1197],{"class":88},"    static_configs:\n",[78,1199,1200],{"class":80,"line":449},[78,1201,1202],{"class":88},"      - targets: ['node-exporter:9100']\n",[78,1204,1205],{"class":80,"line":462},[78,1206,1207],{"class":88},"    basic_auth:\n",[78,1209,1210],{"class":80,"line":482},[78,1211,1212],{"class":88},"      username: prometheus\n",[78,1214,1215],{"class":80,"line":513},[78,1216,1217],{"class":88},"      password: 'รหัสที่ตั้งไว้'\n",[78,1219,1220],{"class":80,"line":531},[78,1221,1222],{"class":88},"    tls_config:\n",[78,1224,1225],{"class":80,"line":536},[78,1226,1227],{"class":88},"      ca_file: /etc/prometheus/certs/ca.crt\n",[78,1229,1230],{"class":80,"line":542},[78,1231,1232],{"class":88},"      cert_file: /etc/prometheus/certs/client.crt\n",[78,1234,1235],{"class":80,"line":567},[78,1236,1237],{"class":88},"      key_file: /etc/prometheus/certs/client.key\n",[78,1239,1240],{"class":80,"line":579},[78,1241,801],{"class":751},[78,1243,1244],{"class":80,"line":604},[78,1245,345],{"emptyLinePlaceholder":344},[78,1247,1249,1251,1253,1255,1257,1260,1262,1264,1266,1269],{"class":80,"line":1248},19,[78,1250,85],{"class":84},[78,1252,103],{"class":88},[78,1254,107],{"class":106},[78,1256,110],{"class":106},[78,1258,1259],{"class":88}," prometheus",[78,1261,116],{"class":106},[78,1263,119],{"class":88},[78,1265,325],{"class":106},[78,1267,1268],{"class":88}," 9090:9090",[78,1270,150],{"class":149},[78,1272,1274,1276,1279],{"class":80,"line":1273},20,[78,1275,865],{"class":106},[78,1277,1278],{"class":88}," ~/node-exporter/prometheus.yml:/etc/prometheus/prometheus.yml:ro",[78,1280,150],{"class":149},[78,1282,1284,1286,1289],{"class":80,"line":1283},21,[78,1285,865],{"class":106},[78,1287,1288],{"class":88}," ~/node-exporter/certs:/etc/prometheus/certs:ro",[78,1290,150],{"class":149},[78,1292,1294],{"class":80,"line":1293},22,[78,1295,1296],{"class":88},"  prom/prometheus:latest\n",[15,1298,1299,1300,1303],{},"จุดที่คนพลาดกันบ่อยคือลืม ",[22,1301,1302],{},"scheme: https"," ครับ ค่าเริ่มต้นของ Prometheus คือ http ผมลองลบบรรทัดนั้นออกดูแล้ว หน้า target จะขึ้นแบบนี้",[69,1305,1308],{"className":1306,"code":1307,"language":181},[179],"url       = http://node-exporter:9100/metrics\nhealth    = down\nlastError = server returned HTTP status 400 Bad Request\n",[22,1309,1307],{"__ignoreMap":74},[15,1311,1312,1313,1316],{},"สังเกตว่ามันไม่ได้บอกว่า ",[22,1314,1315],{},"Client sent an HTTP request to an HTTPS server"," เหมือนตอนที่เรายิง curl เองนะครับ ข้อความนั้นเป็นแค่ body ที่ Prometheus ทิ้งไป เหลือให้เห็นแค่ 400 เฉย ๆ เพราะฉะนั้นถ้าเจอ 400 กับ target ที่เป็น node_exporter ให้สงสัยเรื่อง scheme ไว้ก่อนเลย",[15,1318,1319,1320,1323,1324,1327,1328,1331],{},"ส่วนที่ไม่ได้ใส่ไว้ในตัวอย่างคือ ",[22,1321,1322],{},"server_name"," ครับ มันเป็นตัว override ชื่อที่ใช้ตรวจ cert จำเป็นตอนที่ address ของ target ไม่ตรงกับ SAN เช่นเวลา scrape ด้วย IP ในที่นี้ ",[22,1325,1326],{},"node-exporter:9100"," ตรงกับ ",[22,1329,1330],{},"DNS:node-exporter"," อยู่แล้วเลยไม่ต้องใส่",[15,1333,1334,1335,1338],{},"รอสัก 20 วินาทีให้ scrape รอบแรกทำงานก่อนนะครับ แล้วค่อยเช็ค ถ้าถามเร็วเกินไปจะเห็น health เป็น ",[22,1336,1337],{},"unknown"," แล้วนึกว่าพัง",[69,1340,1342],{"className":71,"code":1341,"language":73,"meta":74,"style":74},"curl -s http://localhost:9090/api/v1/targets \\\n  | jq -r '.data.activeTargets[] | \"\\(.labels.job)  \\(.health)  \\(.scrapeUrl)\"'\n",[22,1343,1344,1357],{"__ignoreMap":74},[78,1345,1346,1349,1352,1355],{"class":80,"line":81},[78,1347,1348],{"class":84},"curl",[78,1350,1351],{"class":106}," -s",[78,1353,1354],{"class":88}," http://localhost:9090/api/v1/targets",[78,1356,150],{"class":149},[78,1358,1359,1362,1365,1368,1370,1373],{"class":80,"line":98},[78,1360,1361],{"class":161},"  |",[78,1363,1364],{"class":84}," jq",[78,1366,1367],{"class":106}," -r",[78,1369,169],{"class":168},[78,1371,1372],{"class":88},".data.activeTargets[] | \"\\(.labels.job)  \\(.health)  \\(.scrapeUrl)\"",[78,1374,175],{"class":168},[69,1376,1379],{"className":1377,"code":1378,"language":181},[179],"node  up  https://node-exporter:9100/metrics\n",[22,1380,1378],{"__ignoreMap":74},[15,1382,1383,1384,1387],{},"หรือจะ query ค่า ",[22,1385,1386],{},"up"," ตรง ๆ ก็ได้",[69,1389,1391],{"className":71,"code":1390,"language":73,"meta":74,"style":74},"curl -sG http://localhost:9090/api/v1/query --data-urlencode 'query=up{job=\"node\"}' \\\n  | jq -r '.data.result[] | \"up = \\(.value[1])  instance=\\(.metric.instance)\"'\n",[22,1392,1393,1415],{"__ignoreMap":74},[78,1394,1395,1397,1400,1403,1406,1408,1411,1413],{"class":80,"line":81},[78,1396,1348],{"class":84},[78,1398,1399],{"class":106}," -sG",[78,1401,1402],{"class":88}," http://localhost:9090/api/v1/query",[78,1404,1405],{"class":106}," --data-urlencode",[78,1407,169],{"class":168},[78,1409,1410],{"class":88},"query=up{job=\"node\"}",[78,1412,927],{"class":168},[78,1414,150],{"class":149},[78,1416,1417,1419,1421,1423,1425,1428],{"class":80,"line":98},[78,1418,1361],{"class":161},[78,1420,1364],{"class":84},[78,1422,1367],{"class":106},[78,1424,169],{"class":168},[78,1426,1427],{"class":88},".data.result[] | \"up = \\(.value[1])  instance=\\(.metric.instance)\"",[78,1429,175],{"class":168},[69,1431,1434],{"className":1432,"code":1433,"language":181},[179],"up = 1  instance=node-exporter:9100\n",[22,1435,1433],{"__ignoreMap":74},[15,1437,1438,1439,1442],{},"ได้ ",[22,1440,1441],{},"1"," แปลว่า scrape ผ่าน mTLS สำเร็จแล้วครับ",[10,1444,1446],{"id":1445},"ตรวจ-config","ตรวจ config",[15,1448,1449,1450,1452],{},"อันนี้เป็นของแถมที่ผมชอบมาก ",[22,1451,59],{}," ของ Prometheus ตรวจไฟล์ของ node_exporter ได้ด้วย เพราะมันเป็น format เดียวกันนั่นแหละ",[69,1454,1456],{"className":71,"code":1455,"language":73,"meta":74,"style":74},"docker run --rm --entrypoint promtool \\\n  -v ~/node-exporter/web-config.yml:/w.yml:ro \\\n  -v ~/node-exporter/certs:/etc/node_exporter/certs:ro \\\n  prom/prometheus:latest check web-config /w.yml\n",[22,1457,1458,1474,1483,1491],{"__ignoreMap":74},[78,1459,1460,1462,1464,1466,1469,1472],{"class":80,"line":81},[78,1461,85],{"class":84},[78,1463,103],{"class":88},[78,1465,139],{"class":106},[78,1467,1468],{"class":106}," --entrypoint",[78,1470,1471],{"class":88}," promtool",[78,1473,150],{"class":149},[78,1475,1476,1478,1481],{"class":80,"line":98},[78,1477,865],{"class":106},[78,1479,1480],{"class":88}," ~/node-exporter/web-config.yml:/w.yml:ro",[78,1482,150],{"class":149},[78,1484,1485,1487,1489],{"class":80,"line":348},[78,1486,865],{"class":106},[78,1488,877],{"class":88},[78,1490,150],{"class":149},[78,1492,1493,1496,1499,1502],{"class":80,"line":355},[78,1494,1495],{"class":88},"  prom/prometheus:latest",[78,1497,1498],{"class":88}," check",[78,1500,1501],{"class":88}," web-config",[78,1503,1504],{"class":88}," /w.yml\n",[69,1506,1509],{"className":1507,"code":1508,"language":181},[179],"/w.yml SUCCESS\n",[22,1510,1508],{"__ignoreMap":74},[15,1512,1513],{},"ลองพิมพ์ค่าผิดดูว่ามันจับได้ไหม",[69,1515,1518],{"className":1516,"code":1517,"language":181},[179],"/w.yml FAILED: invalid ClientAuth: RequireAnyClientCert2\n",[22,1519,1517],{"__ignoreMap":74},[15,1521,1522],{},"จับได้ครับ ควรรันทุกครั้งก่อน restart service เพราะถ้า config พังแล้ว service ไม่ขึ้น กว่าจะรู้ตัวก็ตอน metrics หายไปแล้ว",[15,1524,1525,1526,1529],{},"อย่างเดียวที่ต้องระวังคือมันเปิดไฟล์ cert ตาม path ใน config ไปด้วย เพราะฉะนั้นต้องรันบนเครื่องที่มีไฟล์พวกนั้นอยู่จริง (หรือ mount เข้าไปให้ตรง path เหมือนคำสั่งข้างบน) ไม่งั้นจะได้ ",[22,1527,1528],{},"failed to read cert_file"," ทั้งที่ config ถูกทุกอย่าง",[10,1531,1533],{"id":1532},"ถ้ารันแบบ-bare-metal","ถ้ารันแบบ bare metal",[15,1535,1536],{},"ที่ทำมาทั้งหมดใช้กับ systemd ได้เหมือนกันครับ ต่างแค่สองอย่าง",[15,1538,1539,1540,1542],{},"หนึ่ง เปลี่ยน path ใน ",[22,1541,24],{}," เป็น path จริงบนเครื่อง แล้วก๊อป cert ไปไว้ตรงนั้น",[69,1544,1546],{"className":71,"code":1545,"language":73,"meta":74,"style":74},"sudo mkdir -p /etc/node_exporter\nsudo cp -r ~/node-exporter/certs ~/node-exporter/web-config.yml /etc/node_exporter/\n",[22,1547,1548,1561],{"__ignoreMap":74},[78,1549,1550,1553,1556,1558],{"class":80,"line":81},[78,1551,1552],{"class":84},"sudo",[78,1554,1555],{"class":88}," mkdir",[78,1557,325],{"class":106},[78,1559,1560],{"class":88}," /etc/node_exporter\n",[78,1562,1563,1565,1568,1570,1572,1574],{"class":80,"line":98},[78,1564,1552],{"class":84},[78,1566,1567],{"class":88}," cp",[78,1569,1367],{"class":106},[78,1571,328],{"class":88},[78,1573,745],{"class":88},[78,1575,1576],{"class":88}," /etc/node_exporter/\n",[15,1578,1579,1580],{},"สอง เติม flag เข้าไปใน unit ที่ ",[22,1581,1582],{},"/etc/systemd/system/node_exporter.service",[69,1584,1588],{"className":1585,"code":1586,"language":1587,"meta":74,"style":74},"language-ini shiki shiki-themes material-theme-lighter one-dark-pro github-dark","ExecStart=/usr/local/bin/node_exporter \\\n  --web.config.file=/etc/node_exporter/web-config.yml\n","ini",[22,1589,1590,1595],{"__ignoreMap":74},[78,1591,1592],{"class":80,"line":81},[78,1593,1594],{},"ExecStart=/usr/local/bin/node_exporter \\\n",[78,1596,1597],{"class":80,"line":98},[78,1598,891],{},[15,1600,1601,1602,1605,1606,1608,1609,1611,1612,1615,1616,1619],{},"แล้ว ",[22,1603,1604],{},"sudo systemctl daemon-reload && sudo systemctl restart node_exporter"," ตามปกติ ส่วน ",[22,1607,59],{}," จะติดมากับ tarball ของ Prometheus อยู่แล้ว และ ",[22,1610,55],{}," อยู่ในแพ็กเกจ ",[22,1613,1614],{},"apache2-utils"," (Debian/Ubuntu) หรือ ",[22,1617,1618],{},"httpd-tools"," (RHEL)",[10,1621,1622],{"id":1622},"ข้อควรระวัง",[15,1624,1625,1628,1629,1631,1632,1635,1636,1639,1640,1642],{},[35,1626,1627],{},"รหัสผ่านอยู่หลายที่ ต้องเปลี่ยนพร้อมกัน"," ตอนหมุนรหัสต้องแก้ทั้ง ",[22,1630,24],{}," ฝั่ง node_exporter และ ",[22,1633,1634],{},"basic_auth"," ใน ",[22,1637,1638],{},"prometheus.yml"," ถ้าแก้ไม่ครบ target จะกลายเป็น down แล้วอาการที่เห็นมีอย่างเดียวคือ ",[22,1641,1386],{}," เป็น 0",[15,1644,1645,1648,1649,1652,1653,1656],{},[35,1646,1647],{},"cert มีวันหมดอายุ"," ที่ผมตั้งไว้คือ 365 วัน พอครบแล้ว target ตัวนั้นจะ down ทันที (ตัวอื่นที่ไม่ได้ใช้ cert ยังทำงานปกติ) ที่สำคัญคือ ",[35,1650,1651],{},"ต้องจำวันหมดอายุเอง"," เพราะสแตกที่ตั้งในบทความนี้ไม่มีตัวไหนคอย monitor cert ให้ ผมลอง query ",[22,1654,1655],{},"probe_ssl_earliest_cert_expiry"," ที่หลายคนแนะนำกันดูแล้ว ได้ผลลัพธ์ว่างเปล่า เพราะ metric ตัวนั้นเป็นของ blackbox_exporter ซึ่งไม่ได้อยู่ในบทความนี้ ถ้าจะใช้จริงต้องลง blackbox_exporter เพิ่มแล้วตั้ง module ให้ใส่ client cert ไปด้วย ไม่งั้นมันก็ทะลุ mTLS เข้าไปวัดไม่ได้เหมือนกัน ทางที่ง่ายกว่าคือจดวันหมดอายุใส่ปฏิทินไว้เลยครับ",[15,1658,1659,1662,1663,1666],{},[35,1660,1661],{},"ถ้า Prometheus อยู่เครื่องเดียวกับ node_exporter"," ผูก ",[22,1664,1665],{},"--web.listen-address=127.0.0.1:9100"," ไปเลยง่ายกว่าเยอะครับ ไม่ต้องยุ่งกับ cert เลยสักนิด mTLS มีประโยชน์ตอนที่ต้องวิ่งข้ามเครื่องข้ามวงจริง ๆ",[10,1668,1669],{"id":1669},"ลบของทิ้ง",[15,1671,1672],{},"ลองเสร็จแล้วเก็บกวาดด้วยนะครับ",[69,1674,1676],{"className":71,"code":1675,"language":73,"meta":74,"style":74},"docker rm -f node-exporter prometheus\ndocker network rm mon-net\ndocker rmi quay.io/prometheus/node-exporter:latest prom/prometheus:latest curlimages/curl:latest\nrm -rf ~/node-exporter\nunalias c\n",[22,1677,1678,1690,1700,1716,1727],{"__ignoreMap":74},[78,1679,1680,1682,1684,1686,1688],{"class":80,"line":81},[78,1681,85],{"class":84},[78,1683,251],{"class":88},[78,1685,254],{"class":106},[78,1687,854],{"class":88},[78,1689,696],{"class":88},[78,1691,1692,1694,1696,1698],{"class":80,"line":98},[78,1693,85],{"class":84},[78,1695,89],{"class":88},[78,1697,251],{"class":88},[78,1699,95],{"class":88},[78,1701,1702,1704,1707,1710,1713],{"class":80,"line":348},[78,1703,85],{"class":84},[78,1705,1706],{"class":88}," rmi",[78,1708,1709],{"class":88}," quay.io/prometheus/node-exporter:latest",[78,1711,1712],{"class":88}," prom/prometheus:latest",[78,1714,1715],{"class":88}," curlimages/curl:latest\n",[78,1717,1718,1721,1724],{"class":80,"line":355},[78,1719,1720],{"class":84},"rm",[78,1722,1723],{"class":106}," -rf",[78,1725,1726],{"class":88}," ~/node-exporter\n",[78,1728,1729,1732],{"class":80,"line":385},[78,1730,1731],{"class":335},"unalias",[78,1733,1734],{"class":88}," c\n",[10,1736,1737],{"id":1737},"สรุป",[15,1739,1740,1741,1743],{},"เรื่องที่ผมได้จากรอบนี้ไม่ใช่แค่วิธีเปิด mTLS ครับ แต่เป็นการรู้ว่า ",[22,1742,24],{}," มันเป็นไฟล์ที่ใช้ร่วมกันได้กับทุกตัวในตระกูล Prometheus ซึ่งแปลว่าถ้าเคยตั้งให้ตัวไหนไปแล้วตัวนึง ที่เหลือก็ก๊อปแล้วแก้ path เอาได้เลย ไม่ต้องไปเรียนรู้ใหม่",[15,1745,1746],{},"ส่วนใครที่ยังปล่อย node_exporter โล่งอยู่ ถ้าอยู่ในวงบ้านที่มีแต่เราคนเดียวก็อาจจะยังไม่ต้องรีบ แต่ถ้ามีเครื่องอื่นหรือคนอื่นอยู่ในวงด้วยก็ควรทำอะไรสักอย่างแล้วครับ",[15,1748,1749],{},"มีเรื่องนึงที่ต้องบอกไว้ด้วย ถ้าใส่แค่ basic auth โดยไม่เปิด TLS รหัสผ่านจะวิ่งไปแบบ base64 ซึ่งถอดกลับได้ทันที ใครดักอ่านในวงเดียวกันก็เห็นตั้งแต่ scrape รอบแรก มันพอกันคนที่บังเอิญเปิดเจอได้ก็จริง แต่ไม่ได้กันคนที่ตั้งใจดัก เพราะฉะนั้นถ้าจะใส่แค่ basic auth ก่อน อย่าเอารหัสที่ใช้ที่อื่นมาใช้ซ้ำนะครับ ตัวที่ปกป้องรหัสจริง ๆ คือ TLS",[10,1751,1753],{"id":1752},"references","References",[1755,1756,1757,1765,1772],"ul",{},[1758,1759,1760],"li",{},[27,1761,1764],{"href":1762,"rel":1763},"https://github.com/prometheus/node_exporter",[31],"node_exporter",[1758,1766,1767],{},[27,1768,1771],{"href":1769,"rel":1770},"https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md",[31],"exporter-toolkit — web configuration",[1758,1773,1774],{},[27,1775,1778],{"href":1776,"rel":1777},"https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config",[31],"Prometheus — tls_config",[1780,1781,1782],"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 .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 .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--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 .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 .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 .s2KDN, html code.shiki .s2KDN{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#9ECBFF}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}",{"title":74,"searchDepth":98,"depth":98,"links":1784},[1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798],{"id":12,"depth":98,"text":13},{"id":63,"depth":98,"text":64},{"id":260,"depth":98,"text":261},{"id":308,"depth":98,"text":309},{"id":652,"depth":98,"text":653},{"id":719,"depth":98,"text":720},{"id":894,"depth":98,"text":894},{"id":1141,"depth":98,"text":1142},{"id":1445,"depth":98,"text":1446},{"id":1532,"depth":98,"text":1533},{"id":1622,"depth":98,"text":1622},{"id":1669,"depth":98,"text":1669},{"id":1737,"depth":98,"text":1737},{"id":1752,"depth":98,"text":1753},"2026-08-07T00:00:00.000Z","วิธีเปิด Basic auth และ mTLS ให้ node_exporter ด้วย web-config.yml ไฟล์เดียวกับที่ Prometheus ใช้",false,"md",{},null,"Secure node_exporter With mTLS","/blogs/2026-08-07-node-exporter-mtls-basic-auth",[1808,1809],"2025-05-18-from-old-notebook-to-home-lab-server.md","2026-08-03-grafana-alloy-loki.md",{"title":5,"description":1800},{"loc":1806},"blogs/2026-08-07-node-exporter-mtls-basic-auth",[1814],"Homelab","S2mEz-GLvX5MgME4zSqk7wsBT3DXANv58iytOHzgooY",[1817,2056],{"id":1818,"title":1819,"body":1820,"date":2043,"description":2044,"draft":1801,"extension":1802,"meta":2045,"navigation":344,"next":1804,"ogTitle":2046,"path":2047,"pin":1801,"prev":1804,"recommends":2048,"seo":2051,"sitemap":2052,"stem":2053,"tags":2054,"updated":1804,"__hash__":2055},"content/blogs/2025-05-18-from-old-notebook-to-home-lab-server.md","เปลี่ยน Notebook (เก่า) ที่ไม่ได้ใช้งานเป็น Home Lab Server",{"type":7,"value":1821,"toc":2036},[1822,1827,1834,1838,1841,1862,1868,1880,1885,1888,1891,1909,1914,1925,1930,1936,1941,1944,1948,1966,1971,1974,1990,1994,1997,1999,2002,2005,2025,2030,2033],[15,1823,1824],{},[290,1825],{"alt":74,"src":1826},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-f67e4b1b-db83-40d3-82ed-3fbdc5562404.jpg",[15,1828,1829,1830,1833],{},"สวัสดีครับ พอดีผมมีโปรเจคนึงที่ต้องการใช้งาน Server ขนาดเล็ก ๆ เครื่องนึง กำลังจะหาซื้อเครื่อง Mini PC ใหม่มาเพิ่มพอดี แต่โปรเจคนี้ก็ยังไม่มั่นใจว่าจะไปต่อได้แค่ไหนก่อนที่จะซื้อ hardware มาใหม่ เลยจะลองหยิบเอา Notebook เครื่องเก่ามาลองรันดูก่อนครับ ไหน ๆ ก็ละเลยมาเขียนเป็นบทความสั้น ๆ เอาไว้แชร์เป็นไอเดียสำหรับคนที่ต้องการจะลองเล่น Home Lab แต่ยังไม่อยากจะซื้อเครื่องใหม่ จริง ๆ แล้ว ",[35,1831,1832],{},"notebook"," นายเองก็เป็นได้นะ server อะ !",[10,1835,1837],{"id":1836},"ข้อดีของการนำ-notebook-มาใช้งาน","ข้อดีของการนำ notebook มาใช้งาน",[15,1839,1840],{},"หลาย ๆ คนคงจะคุ้นชินกับการใช้งาน notebook ในแบบปกติคือการใช้งาน windows ทั่ว ๆ ไป แต่ก็อาจจะมีหลาย ๆ คนที่เป็น advanced user ขึ้นมาหน่อยจับมาลง Linux ตัวต่าง ๆ ใช้งาน วันนี้จะลองพามาเปลี่ยนจาก notebook ให้กลายเป็น server ขนาดเล็ก ๆ กันครับ แล้วมันเหมาะสมยังไง",[1755,1842,1843,1846,1856,1859],{},[1758,1844,1845],{},"มีแบตเตอร์รี่ในตัว ปกติแล้ว server หรือ home server ที่ดีควรจะมี UPS เพื่อช่วยสำรองไฟในกรณีที่ไฟตกหรือไฟดับจะช่วยทำให้อุปกรณ์เสียหายได้ยากขึ้น ซึ่ง notebook (ในสภาพปกติ) จะมีแบตเตอร์รี่ในตัวอยู่แล้ว",[1758,1847,1848,1849,1853],{},"กินไฟน้อย ถ้าเป็น notebook ที่ไม่ใช้ gaming notebook นะครับ ปกติแล้วจะกินไฟน้อย อันนี้คือจังหวะที่กำลังชาร์จแบต + idle อยู่นะครับกินไฟอยู่ที่ประมาณ 30W เอง (อันนี้ใช้ performance mode ถ้าปรับเป็น balanced mode จะลงไปอยู่ที่ราว ๆ 15 W) (แต่ก็เยอะถ้าเทียบกับตระกูล mini PC ที่มี CPU N100) ",[1850,1851,1852],"em",{},"เดี๋ยวจะมาอัพเดทอีกทีหลังผ่านไป 1 เดือนนะครับว่ากินไฟไปกี่หน่วย",[290,1854],{"alt":74,"src":1855},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-661534a0-e4a4-4d14-9238-67f04d5de3a7.jpg",[1758,1857,1858],{},"มีจอ, trackpad, keyboard ในตัว เวลาเครื่องมีปัญหาก็จะสามารถตรวจสอบได้เลย ไม่ต้องหาอุปกรณ์มาต่อเพิ่ม",[1758,1860,1861],{},"ประหยัดค่าใช้จ่าย หลาย ๆ คนอยากจะเล่น Home Lab เป็นงานอดิเรกนะครับ ซึ่งการเอาอุปกรณ์ที่มีอยู่แล้วมาใช้งานก็จะช่วยประหยัดไปได้อีก",[15,1863,1864,1865],{},"ตอนที่ไม่ได้มีการใช้งานเปิดเครื่องทิ้งไว้เฉย ๆ กินไฟ 3W เอง (น้อยมากก)\n",[290,1866],{"alt":74,"src":1867},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-0bafd825-77ca-4f2c-b5ac-8ab9394dfb8f.jpg",[15,1869,1870,1871,1875,1876,1879],{},"ผมขอข้ามการติดตั้ง OS ไปนะครับ (ผมติดตั้งเป็น Ubuntu version 24 ไปกดรับ version pro ได้นะ ",[27,1872,1874],{"href":1873},"/blogs/ubuntu-pro","วิธีรับ Ubuntu Pro",") เพราะทำแบบปกติทั่ว ๆ ไปเลยไม่ได้มีอะไรผิดปกติ แนะนำว่าให้เสียบสายแลนแทนการใช้งาน Wifi นะครับ เผื่อความ stable ของการใช้งาน หลังจากนั้นก็สามารถใช้ ",[22,1877,1878],{},"ssh"," เพื่อเชื่อมต่อเข้าไปได้เลย (IP address แนะนำให้ fixed ไว้ตอนติดตั้งนะครับ ไม่แนะนำให้ใช้ DHCP จะปวดหัวเอาทีหลัง)",[15,1881,1882],{},[290,1883],{"alt":74,"src":1884},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-1.png",[10,1886,1887],{"id":1887},"วิธีการทำให้หน้าจอของเครื่องดับ",[15,1889,1890],{},"ปกติแล้วในการใช้งาน ubuntu server จะไม่ได้ใช้งานหน้าจอหรืออุปกรณ์โดยตรงนะครับ ซึ่งตรงนี้เราจะสามารถปิดการใช้หลาย ๆ function ออกไปได้ ซึ่งในการปิดหน้าจอสามารถทำได้ตามนี้เลยครับ",[69,1892,1894],{"className":71,"code":1893,"language":73,"meta":74,"style":74},"sudo vim /etc/default/grub # หรือจะใช้ nano ก็ได้\n",[22,1895,1896],{"__ignoreMap":74},[78,1897,1898,1900,1903,1906],{"class":80,"line":81},[78,1899,1552],{"class":84},[78,1901,1902],{"class":88}," vim",[78,1904,1905],{"class":88}," /etc/default/grub",[78,1907,1908],{"class":351}," # หรือจะใช้ nano ก็ได้\n",[15,1910,1911],{},[290,1912],{"alt":74,"src":1913},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-33c75f2a-a1f7-4294-a2d8-7b769330b6c6.jpg",[15,1915,1916,1917,1920,1921,1924],{},"โดยเพิ่ม ",[22,1918,1919],{},"consoleblank=120"," ไปยัง ",[22,1922,1923],{},"GRUB_CMDLINE_LINUX_DEFAULT="," สามารถปรับเวลาที่ต้องการได้เลย หน่วยเป็นวินาที",[15,1926,1927],{},[290,1928],{"alt":74,"src":1929},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-fd057b5b-b310-4d8c-b7e0-088c6481403f.jpg",[15,1931,1932,1933],{},"จากนั้นออกมาที่ terminal เหมือมเดิมแล้วรัน ",[22,1934,1935],{},"sudo update-grub",[15,1937,1938],{},[290,1939],{"alt":74,"src":1940},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-cad0302a-9626-4746-99da-7ef8acb21a57.jpg",[15,1942,1943],{},"จากนั้น reboot เครื่องแล้วรอตามเวลาที่ตั้งไว้ได้เลย ว่าหน้าจอจะดับหรือไม่ ของผมตั้งไว้ 120 ก็คือรอ 2 นาทีครับ",[10,1945,1947],{"id":1946},"ตรวจสอบว่า-sleep-mode-ถูกปิดใช้งานอยู่","ตรวจสอบว่า sleep mode ถูกปิดใช้งานอยู่",[69,1949,1951],{"className":71,"code":1950,"language":73,"meta":74,"style":74},"sudo systemctl status sleep.target\n",[22,1952,1953],{"__ignoreMap":74},[78,1954,1955,1957,1960,1963],{"class":80,"line":81},[78,1956,1552],{"class":84},[78,1958,1959],{"class":88}," systemctl",[78,1961,1962],{"class":88}," status",[78,1964,1965],{"class":88}," sleep.target\n",[15,1967,1968],{},[290,1969],{"alt":74,"src":1970},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-0f23e13d-d614-4174-852e-6257096028a1.jpg",[15,1972,1973],{},"วิธีการปิดใช้งาน",[69,1975,1977],{"className":71,"code":1976,"language":73,"meta":74,"style":74},"sudo systemctl mask sleep.target\n",[22,1978,1979],{"__ignoreMap":74},[78,1980,1981,1983,1985,1988],{"class":80,"line":81},[78,1982,1552],{"class":84},[78,1984,1959],{"class":88},[78,1986,1987],{"class":88}," mask",[78,1989,1965],{"class":88},[10,1991,1993],{"id":1992},"วิธีการปิดไฟ-black-light-ของ-keyboard","วิธีการปิดไฟ black light ของ keyboard",[15,1995,1996],{},"อันนี้จะขึ้นอยู่กับแต่ละรุ่นนะครับว่ารองรับการปิดใช้มั้ย ต้องเข้าไปดูใน bios เลยครับ ซึ่งของผมอันนี้ไม่รองรับ ต้องเดินไปกดปิดไฟหน้าเครื่องเอง (sad) ไว้จะมาลองหาวิธีอีกทีครับ",[10,1998,1737],{"id":1737},[15,2000,2001],{},"อันนี้ก็เป็นไอเดียนะครับว่าเราสามารถจับเอา notebook มาทำเป็น home server ขนาดเล็ก ๆ ได้ แต่อาจจะต้องปรับเปลี่ยนการตั้งค่านิดหน่อยเพื่อให้เหมาะสมกับการเป็น server จากนี้ก็ขึ้นอยู่กับจุดประสงค์ว่าจะเอาไปทำอะไรต่อ เช่น ลง docker + portainer ทำเป็น web server หรือจะลองลง proxmox ก็ได้นะ ผมมีเครื่อง notebook ที่ลองลง proxmox ไว้เหมือนกัน",[15,2003,2004],{},"สิ่งที่ทำเพิ่มได้",[1755,2006,2007,2010,2013,2016,2019,2022],{},[1758,2008,2009],{},"ติดตั้ง docker",[1758,2011,2012],{},"ติดตั้ง zsh เพื่อปรับแต่ง terminal",[1758,2014,2015],{},"ลง license เพื่อใช้งาน ubuntu pro (เคยเขียนบทความไว้แล้ว)",[1758,2017,2018],{},"ติดตั้ง tailscale เพื่อใช้งานเครื่องนี้จากภายนอก",[1758,2020,2021],{},"ติดตั้ง coder เพื่อทำเป็น remote dev server",[1758,2023,2024],{},"ทำระบบ backup",[15,2026,2027],{},[290,2028],{"alt":74,"src":2029},"/images/2025-05-18-from-old-notebook-to-home-lab-server/images-ff8a1e4b-de92-477b-9e8a-a93b64ee8878.jpg",[15,2031,2032],{},"ขอบคุณที่อ่านจนจบครับบ เย่ ~",[1780,2034,2035],{},"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":74,"searchDepth":98,"depth":98,"links":2037},[2038,2039,2040,2041,2042],{"id":1836,"depth":98,"text":1837},{"id":1887,"depth":98,"text":1887},{"id":1946,"depth":98,"text":1947},{"id":1992,"depth":98,"text":1993},{"id":1737,"depth":98,"text":1737},"2025-05-18T00:00:00.000Z","อยากลองเล่น Home Lab แต่ไม่อยากซื้ออุปกรณ์ใหม่ ลองเอา notebook เครื่องเก่ามาลองใช้ก่อนได้นะ",{},"Home Lab Server","/blogs/2025-05-18-from-old-notebook-to-home-lab-server",[2049,2050],"2025-04-15-ubuntu-pro.md","2024-01-14-setup-ubuntu-server.md",{"title":1819,"description":2044},{"loc":2047},"blogs/2025-05-18-from-old-notebook-to-home-lab-server",[1814],"h9IeFWVHTQTbxdgOil0HF02queMa9aGWkJO1uV6H9sk",{"id":2057,"title":2058,"body":2059,"date":3838,"description":3839,"draft":1801,"extension":1802,"meta":3840,"navigation":344,"next":1804,"ogTitle":3841,"path":3842,"pin":1801,"prev":1804,"recommends":3843,"seo":3844,"sitemap":3845,"stem":3846,"tags":3847,"updated":1804,"__hash__":3848},"content/blogs/2026-08-03-grafana-alloy-loki.md","ลองใช้ Grafana Alloy เก็บ log เข้า Loki",{"type":7,"value":2060,"toc":3825},[2061,2063,2070,2077,2080,2094,2098,2101,2104,2107,2126,2132,2135,2138,2198,2201,2215,2226,2229,2260,2264,2271,2379,2400,2403,2442,2445,2462,2465,2549,2559,2563,2570,2575,2587,2591,2594,2706,2709,2712,2726,2807,2826,2829,2870,2873,3098,3104,3154,3160,3163,3169,3172,3222,3228,3232,3239,3270,3277,3352,3358,3361,3385,3389,3396,3493,3496,3713,3725,3728,3730,3732,3769,3771,3774,3777,3783,3785,3822],[10,2062,13],{"id":12},[15,2064,2065,2066,2069],{},"สวัสดีครับ พอดีที่บ้านผมมี container รันอยู่หลายตัว แล้วเวลาจะดู log ทีก็ต้อง ",[22,2067,2068],{},"docker logs"," ไล่ทีละตัว ซึ่งพอมันเยอะขึ้นเรื่อย ๆ ก็เริ่มไม่ไหวแล้วครับ เลยคิดว่าถึงเวลารวม log ไว้ที่เดียวสักที",[15,2071,2072,2073,2076],{},"ตัวที่คนใช้กันมานานคือ Promtail แต่พอไปเปิดเอกสารดูก็เจอบรรทัดนี้ครับ \"Promtail is end of life (EOL) as of March 2, 2026\" คือหมดอายุไปตั้งแต่ต้นปีแล้ว ไม่มี update ไม่มี security fix อีกต่อไป (Grafana Agent ก็หมดอายุไปก่อนหน้านั้นตั้งแต่ 1 พฤศจิกายน 2025 เหมือนกัน) ตัวที่ Grafana ให้ย้ายไปคือ ",[35,2074,2075],{},"Alloy"," ผมเลยข้ามไปลองตัวนี้เลยดีกว่า",[15,2078,2079],{},"บทความนี้ผมรันจริงทั้งหมดบนเครื่องตัวเองด้วย docker นะครับ output ที่แปะไว้ก๊อปมาจาก terminal ตรง ๆ ไม่ได้พิมพ์ขึ้นมาเอง เวอร์ชันที่ใช้คือ Alloy v1.18.0 กับ Loki 3.7.4",[15,2081,2082,2083,2085,2086,2089,2090,2093],{},"ของที่ต้องมีก่อนคือ docker กับ ",[22,2084,51],{}," (เอาไว้อ่าน JSON ที่ Loki ตอบกลับมา ถ้ายังไม่มีก็ ",[22,2087,2088],{},"brew install jq"," หรือ ",[22,2091,2092],{},"apt install jq",") แล้วก็ต้องมี port 3100 กับ 12345 ว่างอยู่ด้วย",[10,2095,2097],{"id":2096},"alloy-คืออะไร","Alloy คืออะไร",[15,2099,2100],{},"Alloy คือ distribution ของ OpenTelemetry Collector ที่ Grafana ทำขึ้นมาเองครับ (OpenTelemetry Collector คือตัวกลางมาตรฐานที่คอยรับข้อมูลพวก log, metrics, traces จากที่นึงแล้วส่งต่อไปอีกที่นึง) เอาไว้เก็บ log, metrics, traces แล้วก็ profiles ส่งไปยังปลายทางที่เราต้องการ พูดง่าย ๆ คือของเดิมที่เคยแยกเป็น Promtail (log) กับ Prometheus agent (metrics) ตอนนี้รวมมาอยู่ในตัวเดียวแล้ว",[15,2102,2103],{},"ที่ผมชอบคือมันเขียน config เป็นก้อน ๆ เรียกว่า component แล้วเอา output ของก้อนนึงต่อเข้า input ของอีกก้อนได้เลย ไม่ได้เขียนเป็น YAML ก้อนใหญ่ ๆ ที่ต้องมานั่งไล่ว่า key ไหนอยู่ใต้ key ไหน เดี๋ยวจะได้เห็นกันครับ",[15,2105,2106],{},"เช็คเวอร์ชันได้แบบนี้",[69,2108,2110],{"className":71,"code":2109,"language":73,"meta":74,"style":74},"docker run --rm grafana/alloy:latest --version\n",[22,2111,2112],{"__ignoreMap":74},[78,2113,2114,2116,2118,2120,2123],{"class":80,"line":81},[78,2115,85],{"class":84},[78,2117,103],{"class":88},[78,2119,139],{"class":106},[78,2121,2122],{"class":88}," grafana/alloy:latest",[78,2124,2125],{"class":106}," --version\n",[69,2127,2130],{"className":2128,"code":2129,"language":181},[179],"alloy, version v1.18.0 (branch: HEAD, revision: a435563)\n  build user:       root@buildkitsandbox\n  build date:       2026-07-20T09:02:30Z\n  go version:       go1.26.5\n  platform:         linux/arm64\n  tags:             gore2regex,netgo,embedalloyui,promtail_journal_enabled\n",[22,2131,2129],{"__ignoreMap":74},[10,2133,2134],{"id":2134},"วิธีการติดตั้ง",[15,2136,2137],{},"ผมยก Loki ขึ้นมาก่อนเพื่อให้มีปลายทางให้ส่ง แล้วค่อยตาม Alloy ทีหลัง สร้าง network ให้สองตัวคุยกันได้ด้วย",[69,2139,2141],{"className":71,"code":2140,"language":73,"meta":74,"style":74},"docker network create alloy-net\n\ndocker run -d --name loki-demo \\\n  --network alloy-net \\\n  -p 3100:3100 \\\n  grafana/loki:latest\n",[22,2142,2143,2154,2158,2173,2183,2193],{"__ignoreMap":74},[78,2144,2145,2147,2149,2151],{"class":80,"line":81},[78,2146,85],{"class":84},[78,2148,89],{"class":88},[78,2150,92],{"class":88},[78,2152,2153],{"class":88}," alloy-net\n",[78,2155,2156],{"class":80,"line":98},[78,2157,345],{"emptyLinePlaceholder":344},[78,2159,2160,2162,2164,2166,2168,2171],{"class":80,"line":348},[78,2161,85],{"class":84},[78,2163,103],{"class":88},[78,2165,107],{"class":106},[78,2167,110],{"class":106},[78,2169,2170],{"class":88}," loki-demo",[78,2172,150],{"class":149},[78,2174,2175,2178,2181],{"class":80,"line":355},[78,2176,2177],{"class":106},"  --network",[78,2179,2180],{"class":88}," alloy-net",[78,2182,150],{"class":149},[78,2184,2185,2188,2191],{"class":80,"line":385},[78,2186,2187],{"class":106},"  -p",[78,2189,2190],{"class":88}," 3100:3100",[78,2192,150],{"class":149},[78,2194,2195],{"class":80,"line":412},[78,2196,2197],{"class":88},"  grafana/loki:latest\n",[15,2199,2200],{},"Loki ใช้เวลาบูตพอสมควรครับ ของผมรอไป 17 วินาทีกว่าจะพร้อมรับ query เช็คได้แบบนี้",[69,2202,2204],{"className":71,"code":2203,"language":73,"meta":74,"style":74},"curl -s http://localhost:3100/ready\n",[22,2205,2206],{"__ignoreMap":74},[78,2207,2208,2210,2212],{"class":80,"line":81},[78,2209,1348],{"class":84},[78,2211,1351],{"class":106},[78,2213,2214],{"class":88}," http://localhost:3100/ready\n",[15,2216,2217,2218,2221,2222,2225],{},"ถ้ายังไม่พร้อมจะได้ ",[22,2219,2220],{},"Ingester not ready: waiting for 15s after being ready"," กลับมา รอจนกว่าจะขึ้นคำว่า ",[22,2223,2224],{},"ready"," แล้วค่อยไปต่อ",[15,2227,2228],{},"จากนั้นเตรียมโฟลเดอร์กับไฟล์ log ตัวอย่างที่จะให้ Alloy อ่าน",[69,2230,2232],{"className":71,"code":2231,"language":73,"meta":74,"style":74},"mkdir -p ~/alloy/logs\necho '{\"level\":\"info\",\"msg\":\"service started\"}' > ~/alloy/logs/app.log\n",[22,2233,2234,2243],{"__ignoreMap":74},[78,2235,2236,2238,2240],{"class":80,"line":81},[78,2237,322],{"class":84},[78,2239,325],{"class":106},[78,2241,2242],{"class":88}," ~/alloy/logs\n",[78,2244,2245,2248,2250,2253,2255,2257],{"class":80,"line":98},[78,2246,2247],{"class":335},"echo",[78,2249,169],{"class":168},[78,2251,2252],{"class":88},"{\"level\":\"info\",\"msg\":\"service started\"}",[78,2254,927],{"class":168},[78,2256,476],{"class":161},[78,2258,2259],{"class":88}," ~/alloy/logs/app.log\n",[10,2261,2263],{"id":2262},"เขียน-config","เขียน config",[15,2265,2266,2267,2270],{},"สร้างไฟล์ ",[22,2268,2269],{},"~/alloy/config.alloy"," ขึ้นมาครับ จะใช้ editor เปิดเขียนเองก็ได้ หรือก๊อปทั้งก้อนนี้ไปวางใน terminal เลยก็ได้",[69,2272,2274],{"className":71,"code":2273,"language":73,"meta":74,"style":74},"cat > ~/alloy/config.alloy \u003C\u003C'EOF'\nlocal.file_match \"applog\" {\n  path_targets = [{\n    __path__ = \"/var/log/demo/*.log\",\n    job      = \"demo-app\",\n    host     = \"homelab\",\n  }]\n}\n\nloki.source.file \"applog\" {\n  targets    = local.file_match.applog.targets\n  forward_to = [loki.write.default.receiver]\n}\n\nloki.write \"default\" {\n  endpoint {\n    url = \"http://loki-demo:3100/loki/api/v1/push\"\n  }\n}\nEOF\n",[22,2275,2276,2289,2294,2299,2304,2309,2314,2319,2324,2328,2333,2338,2343,2347,2351,2356,2361,2366,2371,2375],{"__ignoreMap":74},[78,2277,2278,2280,2282,2285,2287],{"class":80,"line":81},[78,2279,740],{"class":84},[78,2281,476],{"class":161},[78,2283,2284],{"class":88}," ~/alloy/config.alloy",[78,2286,748],{"class":161},[78,2288,752],{"class":751},[78,2290,2291],{"class":80,"line":98},[78,2292,2293],{"class":88},"local.file_match \"applog\" {\n",[78,2295,2296],{"class":80,"line":348},[78,2297,2298],{"class":88},"  path_targets = [{\n",[78,2300,2301],{"class":80,"line":355},[78,2302,2303],{"class":88},"    __path__ = \"/var/log/demo/*.log\",\n",[78,2305,2306],{"class":80,"line":385},[78,2307,2308],{"class":88},"    job      = \"demo-app\",\n",[78,2310,2311],{"class":80,"line":412},[78,2312,2313],{"class":88},"    host     = \"homelab\",\n",[78,2315,2316],{"class":80,"line":417},[78,2317,2318],{"class":88},"  }]\n",[78,2320,2321],{"class":80,"line":423},[78,2322,2323],{"class":88},"}\n",[78,2325,2326],{"class":80,"line":449},[78,2327,345],{"emptyLinePlaceholder":344},[78,2329,2330],{"class":80,"line":462},[78,2331,2332],{"class":88},"loki.source.file \"applog\" {\n",[78,2334,2335],{"class":80,"line":482},[78,2336,2337],{"class":88},"  targets    = local.file_match.applog.targets\n",[78,2339,2340],{"class":80,"line":513},[78,2341,2342],{"class":88},"  forward_to = [loki.write.default.receiver]\n",[78,2344,2345],{"class":80,"line":531},[78,2346,2323],{"class":88},[78,2348,2349],{"class":80,"line":536},[78,2350,345],{"emptyLinePlaceholder":344},[78,2352,2353],{"class":80,"line":542},[78,2354,2355],{"class":88},"loki.write \"default\" {\n",[78,2357,2358],{"class":80,"line":567},[78,2359,2360],{"class":88},"  endpoint {\n",[78,2362,2363],{"class":80,"line":579},[78,2364,2365],{"class":88},"    url = \"http://loki-demo:3100/loki/api/v1/push\"\n",[78,2367,2368],{"class":80,"line":604},[78,2369,2370],{"class":88},"  }\n",[78,2372,2373],{"class":80,"line":1248},[78,2374,2323],{"class":88},[78,2376,2377],{"class":80,"line":1273},[78,2378,801],{"class":751},[15,2380,2381,2382,2384,2385,2388,2389,2392,2393,2396,2397,2399],{},"ขั้นตอนนี้ห้ามข้ามนะครับ ถ้ายังไม่มีไฟล์นี้แล้วไปสั่ง ",[22,2383,44],{}," เลย docker จะสร้าง",[35,2386,2387],{},"โฟลเดอร์","ชื่อ ",[22,2390,2391],{},"config.alloy"," ให้แทน แล้ว container จะพังตั้งแต่ยังไม่ทันขึ้น พร้อม error ที่งงมากว่า ",[22,2394,2395],{},"not a directory"," แถม ",[22,2398,2068],{}," ก็ว่างเปล่าไม่มีอะไรให้ดูด้วย",[15,2401,2402],{},"อ่าน config จากบนลงล่างได้เลยครับ",[1755,2404,2405,2418,2436],{},[1758,2406,2407,2410,2411,56,2414,2417],{},[22,2408,2409],{},"local.file_match"," คือตัวบอกว่าจะอ่านไฟล์ไหนบ้าง ตรง ",[22,2412,2413],{},"job",[22,2415,2416],{},"host"," คือ label ที่จะติดไปกับ log ทุกบรรทัด เอาไว้ค้นทีหลัง",[1758,2419,2420,2423,2424,2427,2428,2431,2432,2435],{},[22,2421,2422],{},"loki.source.file"," คือตัวอ่านไฟล์ สังเกตว่า ",[22,2425,2426],{},"targets"," ชี้ไปที่ผลลัพธ์ของบล็อกแรกตรง ๆ (",[22,2429,2430],{},"local.file_match.applog.targets",") แล้ว ",[22,2433,2434],{},"forward_to"," ก็ชี้ต่อไปยังบล็อกที่สาม",[1758,2437,2438,2441],{},[22,2439,2440],{},"loki.write"," คือตัวส่งออก",[15,2443,2444],{},"การต่อกันแบบนี้แหละครับที่เป็นจุดขายของ Alloy คือ output ของ component นึงเสียบเข้า input ของอีกตัวได้เลย",[15,2446,2447,2448,2450,2451,2454,2455,2458,2459,2461],{},"อีกเรื่องที่ควรรู้ไว้คือ ",[22,2449,2422],{}," มีบล็อก ",[22,2452,2453],{},"file_match"," ในตัวอยู่แล้ว ใส่ glob ลงไปใน ",[22,2456,2457],{},"__path__"," ตรง ๆ ได้โดยไม่ต้องมี ",[22,2460,2409],{}," แยกออกมา แต่ผมเลือกเขียนแยกเพราะอยากให้เห็นภาพว่า component ต่อกันยังไง แล้วถ้าวันหลังมี component อื่นอยากใช้ targets ชุดเดียวกันก็ดึงไปใช้ต่อได้",[15,2463,2464],{},"ทีนี้รันได้แล้ว",[69,2466,2468],{"className":71,"code":2467,"language":73,"meta":74,"style":74},"docker run -d --name alloy-demo \\\n  --network alloy-net \\\n  -p 12345:12345 \\\n  -v ~/alloy/config.alloy:/etc/alloy/config.alloy \\\n  -v ~/alloy/logs:/var/log/demo \\\n  grafana/alloy:latest \\\n    run --server.http.listen-addr=0.0.0.0:12345 \\\n    --storage.path=/var/lib/alloy/data \\\n    /etc/alloy/config.alloy\n",[22,2469,2470,2485,2493,2502,2511,2520,2527,2537,2544],{"__ignoreMap":74},[78,2471,2472,2474,2476,2478,2480,2483],{"class":80,"line":81},[78,2473,85],{"class":84},[78,2475,103],{"class":88},[78,2477,107],{"class":106},[78,2479,110],{"class":106},[78,2481,2482],{"class":88}," alloy-demo",[78,2484,150],{"class":149},[78,2486,2487,2489,2491],{"class":80,"line":98},[78,2488,2177],{"class":106},[78,2490,2180],{"class":88},[78,2492,150],{"class":149},[78,2494,2495,2497,2500],{"class":80,"line":348},[78,2496,2187],{"class":106},[78,2498,2499],{"class":88}," 12345:12345",[78,2501,150],{"class":149},[78,2503,2504,2506,2509],{"class":80,"line":355},[78,2505,865],{"class":106},[78,2507,2508],{"class":88}," ~/alloy/config.alloy:/etc/alloy/config.alloy",[78,2510,150],{"class":149},[78,2512,2513,2515,2518],{"class":80,"line":385},[78,2514,865],{"class":106},[78,2516,2517],{"class":88}," ~/alloy/logs:/var/log/demo",[78,2519,150],{"class":149},[78,2521,2522,2525],{"class":80,"line":412},[78,2523,2524],{"class":88},"  grafana/alloy:latest",[78,2526,150],{"class":149},[78,2528,2529,2532,2535],{"class":80,"line":417},[78,2530,2531],{"class":88},"    run",[78,2533,2534],{"class":106}," --server.http.listen-addr=0.0.0.0:12345",[78,2536,150],{"class":149},[78,2538,2539,2542],{"class":80,"line":423},[78,2540,2541],{"class":106},"    --storage.path=/var/lib/alloy/data",[78,2543,150],{"class":149},[78,2545,2546],{"class":80,"line":449},[78,2547,2548],{"class":88},"    /etc/alloy/config.alloy\n",[15,2550,617,2551,2554,2555,2558],{},[22,2552,2553],{},"--server.http.listen-addr=0.0.0.0:12345"," อย่าลืมใส่นะครับ ถ้าไม่ใส่มันจะไป bind ที่ ",[22,2556,2557],{},"127.0.0.1"," ข้างใน container แล้วเราจะเข้าหน้า UI จากข้างนอกไม่ได้เลย",[10,2560,2562],{"id":2561},"หน้า-ui-ของ-alloy","หน้า UI ของ Alloy",[15,2564,2565,2566,2569],{},"เปิด ",[22,2567,2568],{},"http://localhost:12345"," แล้วไปที่เมนู Graph ได้เลยครับ (หน้าแรกที่เด้งขึ้นมาจะเป็น Components) มันจะวาด pipeline ให้ดูว่า component ไหนต่อกับอันไหน",[15,2571,2572],{},[290,2573],{"alt":74,"src":2574},"/images/2026-08-03-grafana-alloy-loki/alloy-ui.png",[15,2576,2577,2578,2580,2581,2583,2584,2586],{},"นี่แหละครับคือสิ่งที่อธิบายไปเมื่อกี้ ",[22,2579,2409],{}," ยิงไปหา ",[22,2582,2422],{}," แล้วต่อไป ",[22,2585,2440],{}," ตรงตาม config ที่เขียนไว้เป๊ะ ๆ เส้นสีเขียวคือ log ถ้ามี metrics หรือ traces ด้วยก็จะเป็นคนละสี อันนี้มีประโยชน์มากตอน config เริ่มใหญ่ เพราะดูรูปทีเดียวรู้เลยว่าอะไรต่อกับอะไร",[10,2588,2590],{"id":2589},"เช็คว่า-log-เข้าจริง","เช็คว่า log เข้าจริง",[15,2592,2593],{},"ลองเขียน log เพิ่มเข้าไปในไฟล์",[69,2595,2597],{"className":71,"code":2596,"language":73,"meta":74,"style":74},"for i in 1 2 3; do\n  echo \"{\\\"level\\\":\\\"info\\\",\\\"msg\\\":\\\"handled request $i\\\"}\" >> ~/alloy/logs/app.log\ndone\necho '{\"level\":\"error\",\"msg\":\"database connection refused\"}' >> ~/alloy/logs/app.log\n",[22,2598,2599,2626,2686,2691],{"__ignoreMap":74},[78,2600,2601,2605,2608,2611,2614,2617,2620,2623],{"class":80,"line":81},[78,2602,2604],{"class":2603},"s36IK","for",[78,2606,2607],{"class":919}," i",[78,2609,2610],{"class":2603}," in",[78,2612,2613],{"class":88}," 1",[78,2615,2616],{"class":88}," 2",[78,2618,2619],{"class":88}," 3",[78,2621,2622],{"class":331},";",[78,2624,2625],{"class":2603}," do\n",[78,2627,2628,2631,2633,2636,2639,2642,2644,2647,2649,2652,2654,2657,2659,2662,2664,2666,2668,2671,2674,2676,2679,2681,2684],{"class":80,"line":98},[78,2629,2630],{"class":335},"  echo",[78,2632,403],{"class":168},[78,2634,2635],{"class":88},"{",[78,2637,2638],{"class":149},"\\\"",[78,2640,2641],{"class":88},"level",[78,2643,2638],{"class":149},[78,2645,2646],{"class":88},":",[78,2648,2638],{"class":149},[78,2650,2651],{"class":88},"info",[78,2653,2638],{"class":149},[78,2655,2656],{"class":88},",",[78,2658,2638],{"class":149},[78,2660,2661],{"class":88},"msg",[78,2663,2638],{"class":149},[78,2665,2646],{"class":88},[78,2667,2638],{"class":149},[78,2669,2670],{"class":88},"handled request ",[78,2672,2673],{"class":919},"$i",[78,2675,2638],{"class":149},[78,2677,2678],{"class":88},"}",[78,2680,473],{"class":168},[78,2682,2683],{"class":161}," >>",[78,2685,2259],{"class":88},[78,2687,2688],{"class":80,"line":348},[78,2689,2690],{"class":2603},"done\n",[78,2692,2693,2695,2697,2700,2702,2704],{"class":80,"line":355},[78,2694,2247],{"class":335},[78,2696,169],{"class":168},[78,2698,2699],{"class":88},"{\"level\":\"error\",\"msg\":\"database connection refused\"}",[78,2701,927],{"class":168},[78,2703,2683],{"class":161},[78,2705,2259],{"class":88},[15,2707,2708],{},"รอสัก 2-3 วินาทีให้ Alloy อ่านไฟล์แล้วส่งเข้า Loki ก่อนนะครับ ถ้ายิง query ทันทีจะเห็นแค่บรรทัดแรกบรรทัดเดียวแล้วนึกว่าพัง",[15,2710,2711],{},"ถามก่อนว่า Loki รู้จัก label อะไรบ้าง",[69,2713,2715],{"className":71,"code":2714,"language":73,"meta":74,"style":74},"curl -s http://localhost:3100/loki/api/v1/labels\n",[22,2716,2717],{"__ignoreMap":74},[78,2718,2719,2721,2723],{"class":80,"line":81},[78,2720,1348],{"class":84},[78,2722,1351],{"class":106},[78,2724,2725],{"class":88}," http://localhost:3100/loki/api/v1/labels\n",[69,2727,2731],{"className":2728,"code":2729,"language":2730,"meta":74,"style":74},"language-json shiki shiki-themes material-theme-lighter one-dark-pro github-dark","{ \"status\": \"success\", \"data\": [\"filename\", \"host\", \"job\", \"service_name\"] }\n","json",[22,2732,2733],{"__ignoreMap":74},[78,2734,2735,2737,2740,2744,2746,2748,2750,2753,2755,2757,2759,2762,2764,2766,2769,2771,2774,2776,2778,2780,2782,2784,2786,2788,2790,2792,2794,2796,2799,2801,2804],{"class":80,"line":81},[78,2736,2635],{"class":331},[78,2738,403],{"class":2739},"sObbn",[78,2741,2743],{"class":2742},"sVeMc","status",[78,2745,473],{"class":2739},[78,2747,2646],{"class":331},[78,2749,403],{"class":168},[78,2751,2752],{"class":88},"success",[78,2754,473],{"class":168},[78,2756,2656],{"class":331},[78,2758,403],{"class":2739},[78,2760,2761],{"class":2742},"data",[78,2763,473],{"class":2739},[78,2765,2646],{"class":331},[78,2767,2768],{"class":331}," [",[78,2770,473],{"class":168},[78,2772,2773],{"class":88},"filename",[78,2775,473],{"class":168},[78,2777,2656],{"class":331},[78,2779,403],{"class":168},[78,2781,2416],{"class":88},[78,2783,473],{"class":168},[78,2785,2656],{"class":331},[78,2787,403],{"class":168},[78,2789,2413],{"class":88},[78,2791,473],{"class":168},[78,2793,2656],{"class":331},[78,2795,403],{"class":168},[78,2797,2798],{"class":88},"service_name",[78,2800,473],{"class":168},[78,2802,2803],{"class":331},"]",[78,2805,2806],{"class":331}," }\n",[15,2808,2809,56,2811,2813,2814,2816,2817,2819,2820,2822,2823,2825],{},[22,2810,2413],{},[22,2812,2416],{}," คือที่เราใส่ไว้ใน config เอง ส่วนอีกสองตัวมาคนละที่กันครับ ",[22,2815,2773],{}," เป็นของ Alloy ที่เอา ",[22,2818,2457],{}," มาแปะให้ทุกบรรทัด ส่วน ",[22,2821,2798],{}," เป็นของ Loki ที่ map มาจาก label ",[22,2824,2413],{}," ให้อัตโนมัติตอน ingest",[15,2827,2828],{},"ทีนี้ลองดึง log ออกมาดูจริง ๆ",[69,2830,2832],{"className":71,"code":2831,"language":73,"meta":74,"style":74},"curl -sG http://localhost:3100/loki/api/v1/query_range \\\n  --data-urlencode 'query={job=\"demo-app\"}' \\\n  --data-urlencode 'limit=10'\n",[22,2833,2834,2845,2859],{"__ignoreMap":74},[78,2835,2836,2838,2840,2843],{"class":80,"line":81},[78,2837,1348],{"class":84},[78,2839,1399],{"class":106},[78,2841,2842],{"class":88}," http://localhost:3100/loki/api/v1/query_range",[78,2844,150],{"class":149},[78,2846,2847,2850,2852,2855,2857],{"class":80,"line":98},[78,2848,2849],{"class":106},"  --data-urlencode",[78,2851,169],{"class":168},[78,2853,2854],{"class":88},"query={job=\"demo-app\"}",[78,2856,927],{"class":168},[78,2858,150],{"class":149},[78,2860,2861,2863,2865,2868],{"class":80,"line":348},[78,2862,2849],{"class":106},[78,2864,169],{"class":168},[78,2866,2867],{"class":88},"limit=10",[78,2869,175],{"class":168},[15,2871,2872],{},"ที่ได้กลับมาคือ JSON ก้อนเดียวยาว ๆ หน้าตาประมาณนี้",[69,2874,2876],{"className":2728,"code":2875,"language":2730,"meta":74,"style":74},"{\"status\":\"success\",\"data\":{\"resultType\":\"streams\",\"result\":[{\"stream\":{\"detected_level\":\"error\",\"filename\":\"/var/log/demo/app.log\",\"host\":\"homelab\",\"job\":\"demo-app\",\"service_name\":\"demo-app\"},\"values\":[[\"1785760821561036812\",\"{\\\"level\\\":\\\"error\\\",\\\"msg\\\":\\\"database connection refused\\\"}\"]]},...\n",[22,2877,2878],{"__ignoreMap":74},[78,2879,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2907,2909,2913,2915,2917,2919,2922,2924,2926,2928,2931,2933,2936,2938,2942,2944,2946,2948,2952,2954,2956,2958,2961,2963,2965,2967,2969,2971,2973,2975,2978,2980,2982,2984,2986,2988,2990,2992,2995,2997,2999,3001,3003,3005,3007,3009,3012,3014,3016,3018,3020,3022,3024,3026,3028,3030,3033,3035,3038,3040,3043,3045,3048,3050,3052,3054,3056,3058,3060,3062,3064,3066,3068,3070,3072,3074,3076,3078,3080,3082,3085,3087,3089,3091,3094],{"class":80,"line":81},[78,2881,2635],{"class":331},[78,2883,473],{"class":2739},[78,2885,2743],{"class":2742},[78,2887,473],{"class":2739},[78,2889,2646],{"class":331},[78,2891,473],{"class":168},[78,2893,2752],{"class":88},[78,2895,473],{"class":168},[78,2897,2656],{"class":331},[78,2899,473],{"class":2739},[78,2901,2761],{"class":2742},[78,2903,473],{"class":2739},[78,2905,2906],{"class":331},":{",[78,2908,473],{"class":2739},[78,2910,2912],{"class":2911},"sLise","resultType",[78,2914,473],{"class":2739},[78,2916,2646],{"class":331},[78,2918,473],{"class":168},[78,2920,2921],{"class":88},"streams",[78,2923,473],{"class":168},[78,2925,2656],{"class":331},[78,2927,473],{"class":2739},[78,2929,2930],{"class":2911},"result",[78,2932,473],{"class":2739},[78,2934,2935],{"class":331},":[{",[78,2937,473],{"class":2739},[78,2939,2941],{"class":2940},"sxkdo","stream",[78,2943,473],{"class":2739},[78,2945,2906],{"class":331},[78,2947,473],{"class":2739},[78,2949,2951],{"class":2950},"s9IQt","detected_level",[78,2953,473],{"class":2739},[78,2955,2646],{"class":331},[78,2957,473],{"class":168},[78,2959,2960],{"class":88},"error",[78,2962,473],{"class":168},[78,2964,2656],{"class":331},[78,2966,473],{"class":2739},[78,2968,2773],{"class":2950},[78,2970,473],{"class":2739},[78,2972,2646],{"class":331},[78,2974,473],{"class":168},[78,2976,2977],{"class":88},"/var/log/demo/app.log",[78,2979,473],{"class":168},[78,2981,2656],{"class":331},[78,2983,473],{"class":2739},[78,2985,2416],{"class":2950},[78,2987,473],{"class":2739},[78,2989,2646],{"class":331},[78,2991,473],{"class":168},[78,2993,2994],{"class":88},"homelab",[78,2996,473],{"class":168},[78,2998,2656],{"class":331},[78,3000,473],{"class":2739},[78,3002,2413],{"class":2950},[78,3004,473],{"class":2739},[78,3006,2646],{"class":331},[78,3008,473],{"class":168},[78,3010,3011],{"class":88},"demo-app",[78,3013,473],{"class":168},[78,3015,2656],{"class":331},[78,3017,473],{"class":2739},[78,3019,2798],{"class":2950},[78,3021,473],{"class":2739},[78,3023,2646],{"class":331},[78,3025,473],{"class":168},[78,3027,3011],{"class":88},[78,3029,473],{"class":168},[78,3031,3032],{"class":331},"},",[78,3034,473],{"class":2739},[78,3036,3037],{"class":2940},"values",[78,3039,473],{"class":2739},[78,3041,3042],{"class":331},":[[",[78,3044,473],{"class":168},[78,3046,3047],{"class":88},"1785760821561036812",[78,3049,473],{"class":168},[78,3051,2656],{"class":331},[78,3053,473],{"class":168},[78,3055,2635],{"class":88},[78,3057,2638],{"class":149},[78,3059,2641],{"class":88},[78,3061,2638],{"class":149},[78,3063,2646],{"class":88},[78,3065,2638],{"class":149},[78,3067,2960],{"class":88},[78,3069,2638],{"class":149},[78,3071,2656],{"class":88},[78,3073,2638],{"class":149},[78,3075,2661],{"class":88},[78,3077,2638],{"class":149},[78,3079,2646],{"class":88},[78,3081,2638],{"class":149},[78,3083,3084],{"class":88},"database connection refused",[78,3086,2638],{"class":149},[78,3088,2678],{"class":88},[78,3090,473],{"class":168},[78,3092,3093],{"class":331},"]]},",[78,3095,3097],{"class":3096},"sg798","...\n",[15,3099,3100,3101,3103],{},"อ่านไม่ไหวครับ ต่อ ",[22,3102,51],{}," เอาเฉพาะบรรทัด log ดีกว่า",[69,3105,3107],{"className":71,"code":3106,"language":73,"meta":74,"style":74},"curl -sG http://localhost:3100/loki/api/v1/query_range \\\n  --data-urlencode 'query={job=\"demo-app\"}' \\\n  --data-urlencode 'limit=10' | jq -r '.data.result[].values[][1]'\n",[22,3108,3109,3119,3131],{"__ignoreMap":74},[78,3110,3111,3113,3115,3117],{"class":80,"line":81},[78,3112,1348],{"class":84},[78,3114,1399],{"class":106},[78,3116,2842],{"class":88},[78,3118,150],{"class":149},[78,3120,3121,3123,3125,3127,3129],{"class":80,"line":98},[78,3122,2849],{"class":106},[78,3124,169],{"class":168},[78,3126,2854],{"class":88},[78,3128,927],{"class":168},[78,3130,150],{"class":149},[78,3132,3133,3135,3137,3139,3141,3143,3145,3147,3149,3152],{"class":80,"line":348},[78,3134,2849],{"class":106},[78,3136,169],{"class":168},[78,3138,2867],{"class":88},[78,3140,927],{"class":168},[78,3142,162],{"class":161},[78,3144,1364],{"class":84},[78,3146,1367],{"class":106},[78,3148,169],{"class":168},[78,3150,3151],{"class":88},".data.result[].values[][1]",[78,3153,175],{"class":168},[69,3155,3158],{"className":3156,"code":3157,"language":181},[179],"{\"level\":\"error\",\"msg\":\"database connection refused\"}\n{\"level\":\"info\",\"msg\":\"handled request 3\"}\n{\"level\":\"info\",\"msg\":\"handled request 2\"}\n{\"level\":\"info\",\"msg\":\"handled request 1\"}\n{\"level\":\"info\",\"msg\":\"service started\"}\n",[22,3159,3157],{"__ignoreMap":74},[15,3161,3162],{},"ครบทั้ง 5 บรรทัดที่เขียนลงไฟล์ไป",[15,3164,3165,3166,3168],{},"กลับไปดูใน JSON ก้อนบนอีกทีจะเห็น ",[22,3167,2951],{}," โผล่มาด้วย อันนี้ผมไม่ได้ใส่ไว้ใน config เลยครับ เป็น Loki ที่อ่านเนื้อ log แล้วแยกให้เองว่าบรรทัดไหน error บรรทัดไหน info ทั้งที่ผมไม่ได้เขียน parser อะไรไว้สักตัว",[15,3170,3171],{},"ถ้าอยากกรองเฉพาะ error ก็ใส่ filter ต่อท้าย query ได้",[69,3173,3175],{"className":71,"code":3174,"language":73,"meta":74,"style":74},"curl -sG http://localhost:3100/loki/api/v1/query_range \\\n  --data-urlencode 'query={job=\"demo-app\"} |= \"error\"' \\\n  --data-urlencode 'limit=10' | jq -r '.data.result[].values[][1]'\n",[22,3176,3177,3187,3200],{"__ignoreMap":74},[78,3178,3179,3181,3183,3185],{"class":80,"line":81},[78,3180,1348],{"class":84},[78,3182,1399],{"class":106},[78,3184,2842],{"class":88},[78,3186,150],{"class":149},[78,3188,3189,3191,3193,3196,3198],{"class":80,"line":98},[78,3190,2849],{"class":106},[78,3192,169],{"class":168},[78,3194,3195],{"class":88},"query={job=\"demo-app\"} |= \"error\"",[78,3197,927],{"class":168},[78,3199,150],{"class":149},[78,3201,3202,3204,3206,3208,3210,3212,3214,3216,3218,3220],{"class":80,"line":348},[78,3203,2849],{"class":106},[78,3205,169],{"class":168},[78,3207,2867],{"class":88},[78,3209,927],{"class":168},[78,3211,162],{"class":161},[78,3213,1364],{"class":84},[78,3215,1367],{"class":106},[78,3217,169],{"class":168},[78,3219,3151],{"class":88},[78,3221,175],{"class":168},[69,3223,3226],{"className":3224,"code":3225,"language":181},[179],"{\"level\":\"error\",\"msg\":\"database connection refused\"}\n",[22,3227,3225],{"__ignoreMap":74},[10,3229,3231],{"id":3230},"ตรวจ-config-ก่อนรัน","ตรวจ config ก่อนรัน",[15,3233,3234,3235,3238],{},"Alloy มีคำสั่ง ",[22,3236,3237],{},"validate"," ให้ใช้ครับ ไม่ต้องรันจริงแล้วมานั่งดู log ว่าพังตรงไหน",[69,3240,3242],{"className":71,"code":3241,"language":73,"meta":74,"style":74},"docker run --rm -v ~/alloy/config.alloy:/c.alloy \\\n  grafana/alloy:latest validate /c.alloy\n",[22,3243,3244,3260],{"__ignoreMap":74},[78,3245,3246,3248,3250,3252,3255,3258],{"class":80,"line":81},[78,3247,85],{"class":84},[78,3249,103],{"class":88},[78,3251,139],{"class":106},[78,3253,3254],{"class":106}," -v",[78,3256,3257],{"class":88}," ~/alloy/config.alloy:/c.alloy",[78,3259,150],{"class":149},[78,3261,3262,3264,3267],{"class":80,"line":98},[78,3263,2524],{"class":88},[78,3265,3266],{"class":88}," validate",[78,3268,3269],{"class":88}," /c.alloy\n",[15,3271,3272,3273,3276],{},"ถ้าผ่านจะไม่มีอะไรขึ้นมาเลย ทีนี้ลองทำให้มันพังดูบ้าง ผมสร้างไฟล์ ",[22,3274,3275],{},"bad.alloy"," ที่ใส่ attribute มั่ว ๆ เข้าไปหนึ่งตัว",[69,3278,3280],{"className":71,"code":3279,"language":73,"meta":74,"style":74},"cat > ~/alloy/bad.alloy \u003C\u003C'EOF'\nloki.write \"default\" {\n  endpoint {\n    url = \"http://loki-demo:3100/loki/api/v1/push\"\n  }\n  bogus_arg = 1\n}\nEOF\n\ndocker run --rm -v ~/alloy/bad.alloy:/bad.alloy \\\n  grafana/alloy:latest validate /bad.alloy\n",[22,3281,3282,3295,3299,3303,3307,3311,3316,3320,3324,3328,3343],{"__ignoreMap":74},[78,3283,3284,3286,3288,3291,3293],{"class":80,"line":81},[78,3285,740],{"class":84},[78,3287,476],{"class":161},[78,3289,3290],{"class":88}," ~/alloy/bad.alloy",[78,3292,748],{"class":161},[78,3294,752],{"class":751},[78,3296,3297],{"class":80,"line":98},[78,3298,2355],{"class":88},[78,3300,3301],{"class":80,"line":348},[78,3302,2360],{"class":88},[78,3304,3305],{"class":80,"line":355},[78,3306,2365],{"class":88},[78,3308,3309],{"class":80,"line":385},[78,3310,2370],{"class":88},[78,3312,3313],{"class":80,"line":412},[78,3314,3315],{"class":88},"  bogus_arg = 1\n",[78,3317,3318],{"class":80,"line":417},[78,3319,2323],{"class":88},[78,3321,3322],{"class":80,"line":423},[78,3323,801],{"class":751},[78,3325,3326],{"class":80,"line":449},[78,3327,345],{"emptyLinePlaceholder":344},[78,3329,3330,3332,3334,3336,3338,3341],{"class":80,"line":462},[78,3331,85],{"class":84},[78,3333,103],{"class":88},[78,3335,139],{"class":106},[78,3337,3254],{"class":106},[78,3339,3340],{"class":88}," ~/alloy/bad.alloy:/bad.alloy",[78,3342,150],{"class":149},[78,3344,3345,3347,3349],{"class":80,"line":482},[78,3346,2524],{"class":88},[78,3348,3266],{"class":88},[78,3350,3351],{"class":88}," /bad.alloy\n",[69,3353,3356],{"className":3354,"code":3355,"language":181},[179],"Error: /bad.alloy:5:3: unrecognized attribute name \"bogus_arg\"\n\n4 |   }\n5 |   bogus_arg = 1\n  |   ^^^^^^^^^^^^^\n6 | }\nError: validation failed\n2026/08/03 13:32:02 collector server run finished with error: validation failed\n",[22,3357,3355],{"__ignoreMap":74},[15,3359,3360],{},"ชี้ให้เลยว่าบรรทัด 5 ตัวอักษรที่ 3 พร้อมขีดเส้นใต้ให้ด้วย",[15,3362,3363,3364,3367,3368,3370,3371,3374,3375,3378,3379,3381,3382,3384],{},"ระวังอย่าไปสับสนกับคำสั่ง ",[22,3365,3366],{},"fmt"," นะครับ สองตัวนี้ตรวจคนละชั้นกัน ",[22,3369,3366],{}," ดูแค่ syntax คือวงเล็บปิดครบไหม โครงสร้างถูกไหม (ผมลองเอา config ที่ลืมปิดวงเล็บไปให้ มันฟ้อง ",[22,3372,3373],{},"expected }, got EOF"," แล้ว exit 1) แต่มันไม่เช็คว่าชื่อ attribute หรือ component นั้นมีอยู่จริงหรือเปล่า ตัว ",[22,3376,3377],{},"bogus_arg"," ข้างบนผ่าน ",[22,3380,3366],{}," ฉลุยด้วย exit 0 แถมยังพ่น config ที่จัดหน้าเรียบร้อยแล้วออกมาให้อีก ต้องใช้ ",[22,3383,3237],{}," ถึงจะจับได้",[10,3386,3388],{"id":3387},"ย้ายจาก-promtail","ย้ายจาก Promtail",[15,3390,3391,3392,3395],{},"ใครที่มี config Promtail อยู่แล้วไม่ต้องนั่งแปลมือครับ Alloy มีคำสั่ง ",[22,3393,3394],{},"convert"," ให้ ผมลองเอา config สั้น ๆ แบบนี้ไปแปลงดู",[69,3397,3399],{"className":71,"code":3398,"language":73,"meta":74,"style":74},"cat > ~/alloy/promtail.yaml \u003C\u003C'EOF'\nclients:\n  - url: http://localhost:3100/loki/api/v1/push\nscrape_configs:\n  - job_name: system\n    static_configs:\n      - targets: [localhost]\n        labels:\n          job: varlogs\n          __path__: /var/log/*.log\nEOF\n\ndocker run --rm -v ~/alloy/promtail.yaml:/p.yaml \\\n  grafana/alloy:latest convert --source-format=promtail /p.yaml\n",[22,3400,3401,3414,3419,3424,3428,3433,3437,3442,3447,3452,3457,3461,3465,3480],{"__ignoreMap":74},[78,3402,3403,3405,3407,3410,3412],{"class":80,"line":81},[78,3404,740],{"class":84},[78,3406,476],{"class":161},[78,3408,3409],{"class":88}," ~/alloy/promtail.yaml",[78,3411,748],{"class":161},[78,3413,752],{"class":751},[78,3415,3416],{"class":80,"line":98},[78,3417,3418],{"class":88},"clients:\n",[78,3420,3421],{"class":80,"line":348},[78,3422,3423],{"class":88},"  - url: http://localhost:3100/loki/api/v1/push\n",[78,3425,3426],{"class":80,"line":355},[78,3427,1182],{"class":88},[78,3429,3430],{"class":80,"line":385},[78,3431,3432],{"class":88},"  - job_name: system\n",[78,3434,3435],{"class":80,"line":412},[78,3436,1197],{"class":88},[78,3438,3439],{"class":80,"line":417},[78,3440,3441],{"class":88},"      - targets: [localhost]\n",[78,3443,3444],{"class":80,"line":423},[78,3445,3446],{"class":88},"        labels:\n",[78,3448,3449],{"class":80,"line":449},[78,3450,3451],{"class":88},"          job: varlogs\n",[78,3453,3454],{"class":80,"line":462},[78,3455,3456],{"class":88},"          __path__: /var/log/*.log\n",[78,3458,3459],{"class":80,"line":482},[78,3460,801],{"class":751},[78,3462,3463],{"class":80,"line":513},[78,3464,345],{"emptyLinePlaceholder":344},[78,3466,3467,3469,3471,3473,3475,3478],{"class":80,"line":531},[78,3468,85],{"class":84},[78,3470,103],{"class":88},[78,3472,139],{"class":106},[78,3474,3254],{"class":106},[78,3476,3477],{"class":88}," ~/alloy/promtail.yaml:/p.yaml",[78,3479,150],{"class":149},[78,3481,3482,3484,3487,3490],{"class":80,"line":536},[78,3483,2524],{"class":88},[78,3485,3486],{"class":88}," convert",[78,3488,3489],{"class":106}," --source-format=promtail",[78,3491,3492],{"class":88}," /p.yaml\n",[15,3494,3495],{},"ได้ผลออกมาแบบนี้",[69,3497,3501],{"className":3498,"code":3499,"language":3500,"meta":74,"style":74},"language-hcl shiki shiki-themes material-theme-lighter one-dark-pro github-dark","loki.source.file \"system\" {\n    targets = [{\n        __address__ = \"localhost\",\n        __path__    = \"/var/log/*.log\",\n        job         = \"varlogs\",\n    }]\n    forward_to = [loki.write.default.receiver]\n\n    file_match {\n        enabled = true\n    }\n    legacy_positions_file = \"/var/log/positions.yaml\"\n}\n\nloki.write \"default\" {\n    endpoint {\n        url = \"http://localhost:3100/loki/api/v1/push\"\n    }\n    external_labels = {}\n}\n","hcl",[22,3502,3503,3530,3543,3558,3572,3589,3597,3607,3611,3618,3629,3634,3648,3652,3656,3674,3681,3695,3699,3709],{"__ignoreMap":74},[78,3504,3505,3509,3512,3515,3517,3520,3522,3525,3527],{"class":80,"line":81},[78,3506,3508],{"class":3507},"sZDfs","loki",[78,3510,3511],{"class":161},".",[78,3513,3514],{"class":919},"source",[78,3516,3511],{"class":161},[78,3518,3519],{"class":919},"file",[78,3521,403],{"class":168},[78,3523,3524],{"class":88},"system",[78,3526,473],{"class":168},[78,3528,3529],{"class":331}," {\n",[78,3531,3532,3535,3538,3540],{"class":80,"line":98},[78,3533,3534],{"class":919},"    targets",[78,3536,3537],{"class":923}," =",[78,3539,2768],{"class":3507},[78,3541,3542],{"class":331},"{\n",[78,3544,3545,3548,3550,3553,3555],{"class":80,"line":348},[78,3546,3547],{"class":3507},"        __address__ = ",[78,3549,473],{"class":168},[78,3551,3552],{"class":88},"localhost",[78,3554,473],{"class":168},[78,3556,3557],{"class":3507},",\n",[78,3559,3560,3563,3565,3568,3570],{"class":80,"line":355},[78,3561,3562],{"class":3507},"        __path__    = ",[78,3564,473],{"class":168},[78,3566,3567],{"class":88},"/var/log/*.log",[78,3569,473],{"class":168},[78,3571,3557],{"class":3507},[78,3573,3574,3577,3580,3582,3585,3587],{"class":80,"line":385},[78,3575,3576],{"class":919},"        job",[78,3578,3579],{"class":923},"         =",[78,3581,403],{"class":168},[78,3583,3584],{"class":88},"varlogs",[78,3586,473],{"class":168},[78,3588,3557],{"class":3507},[78,3590,3591,3594],{"class":80,"line":412},[78,3592,3593],{"class":331},"    }",[78,3595,3596],{"class":3507},"]\n",[78,3598,3599,3602,3604],{"class":80,"line":417},[78,3600,3601],{"class":919},"    forward_to",[78,3603,3537],{"class":923},[78,3605,3606],{"class":3507}," [loki.write.default.receiver]\n",[78,3608,3609],{"class":80,"line":423},[78,3610,345],{"emptyLinePlaceholder":344},[78,3612,3613,3616],{"class":80,"line":449},[78,3614,3615],{"class":3507},"    file_match ",[78,3617,3542],{"class":331},[78,3619,3620,3623,3625],{"class":80,"line":462},[78,3621,3622],{"class":919},"        enabled",[78,3624,3537],{"class":923},[78,3626,3628],{"class":3627},"scCEF"," true\n",[78,3630,3631],{"class":80,"line":482},[78,3632,3633],{"class":331},"    }\n",[78,3635,3636,3639,3641,3643,3646],{"class":80,"line":513},[78,3637,3638],{"class":919},"    legacy_positions_file",[78,3640,3537],{"class":923},[78,3642,403],{"class":168},[78,3644,3645],{"class":88},"/var/log/positions.yaml",[78,3647,409],{"class":168},[78,3649,3650],{"class":80,"line":531},[78,3651,2323],{"class":331},[78,3653,3654],{"class":80,"line":536},[78,3655,345],{"emptyLinePlaceholder":344},[78,3657,3658,3660,3662,3665,3667,3670,3672],{"class":80,"line":542},[78,3659,3508],{"class":3507},[78,3661,3511],{"class":161},[78,3663,3664],{"class":919},"write",[78,3666,403],{"class":168},[78,3668,3669],{"class":88},"default",[78,3671,473],{"class":168},[78,3673,3529],{"class":331},[78,3675,3676,3679],{"class":80,"line":567},[78,3677,3678],{"class":3507},"    endpoint ",[78,3680,3542],{"class":331},[78,3682,3683,3686,3688,3690,3693],{"class":80,"line":579},[78,3684,3685],{"class":919},"        url",[78,3687,3537],{"class":923},[78,3689,403],{"class":168},[78,3691,3692],{"class":88},"http://localhost:3100/loki/api/v1/push",[78,3694,409],{"class":168},[78,3696,3697],{"class":80,"line":604},[78,3698,3633],{"class":331},[78,3700,3701,3704,3706],{"class":80,"line":1248},[78,3702,3703],{"class":919},"    external_labels",[78,3705,3537],{"class":923},[78,3707,3708],{"class":331}," {}\n",[78,3710,3711],{"class":80,"line":1273},[78,3712,2323],{"class":331},[15,3714,3715,3716,3718,3719,3721,3722,3724],{},"สังเกตว่ามันเลือกใช้ ",[22,3717,2453],{}," block ในตัวของ ",[22,3720,2422],{}," แทนที่จะแยก ",[22,3723,2409],{}," ออกมาเหมือนที่ผมเขียนไว้ข้างบน ซึ่งก็ถูกทั้งคู่ครับ",[15,3726,3727],{},"ใช้ได้เลยแต่แนะนำให้อ่านทวนก่อนสักรอบ อย่าเอาไปใช้ทั้งดุ้นเลยทีเดียว",[10,3729,1669],{"id":1669},[15,3731,1672],{},[69,3733,3735],{"className":71,"code":3734,"language":73,"meta":74,"style":74},"docker rm -f alloy-demo loki-demo\ndocker network rm alloy-net\nrm -rf ~/alloy\n",[22,3736,3737,3750,3760],{"__ignoreMap":74},[78,3738,3739,3741,3743,3745,3747],{"class":80,"line":81},[78,3740,85],{"class":84},[78,3742,251],{"class":88},[78,3744,254],{"class":106},[78,3746,2482],{"class":88},[78,3748,3749],{"class":88}," loki-demo\n",[78,3751,3752,3754,3756,3758],{"class":80,"line":98},[78,3753,85],{"class":84},[78,3755,89],{"class":88},[78,3757,251],{"class":88},[78,3759,2153],{"class":88},[78,3761,3762,3764,3766],{"class":80,"line":348},[78,3763,1720],{"class":84},[78,3765,1723],{"class":106},[78,3767,3768],{"class":88}," ~/alloy\n",[10,3770,1737],{"id":1737},[15,3772,3773],{},"ที่ลองมาผมว่า Alloy เข้าใจง่ายกว่า Promtail อยู่พอสมควรครับ ตรงที่ config เป็น component ต่อกันเป็นท่อ แล้วมีหน้า UI ให้ดูว่าท่อมันต่อถูกไหม เวลาพังจะหาต้นตอง่ายกว่าเยอะ",[15,3775,3776],{},"ที่เขียนในบทความนี้เป็นแค่ log อย่างเดียวนะครับ จริง ๆ Alloy ทำ metrics กับ traces ได้ด้วยในตัวเดียวกัน ถ้ามีโอกาสจะมาเขียนต่อ",[15,3778,3779,3780,3782],{},"ส่วนใครที่ยังใช้ Promtail อยู่ ถึงตอนนี้มันยังทำงานได้ปกติก็จริง แต่ EOL ไปแล้วตั้งแต่เดือนมีนาคมครับ แปลว่าถ้ามีช่องโหว่โผล่มาก็จะไม่มีใครแก้ให้แล้ว ควรวางแผนย้ายไว้แต่เนิ่น ๆ ดีกว่า และมีคำสั่ง ",[22,3781,3394],{}," ช่วยอยู่แล้วด้วย",[10,3784,1753],{"id":1752},[1755,3786,3787,3794,3801,3808,3815],{},[1758,3788,3789],{},[27,3790,3793],{"href":3791,"rel":3792},"https://grafana.com/docs/alloy/latest/",[31],"Grafana Alloy documentation",[1758,3795,3796],{},[27,3797,3800],{"href":3798,"rel":3799},"https://grafana.com/docs/alloy/latest/set-up/install/docker/",[31],"Install Alloy on Docker",[1758,3802,3803],{},[27,3804,3807],{"href":3805,"rel":3806},"https://grafana.com/docs/alloy/latest/reference/components/loki/loki.source.file/",[31],"loki.source.file component",[1758,3809,3810],{},[27,3811,3814],{"href":3812,"rel":3813},"https://grafana.com/docs/loki/latest/send-data/promtail/",[31],"Promtail EOL notice",[1758,3816,3817],{},[27,3818,3821],{"href":3819,"rel":3820},"https://grafana.com/docs/agent/latest/",[31],"Grafana Agent EOL announcement",[1780,3823,3824],{},"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 .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 .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 .sbGrh, html code.shiki .sbGrh{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#F97583}html pre.shiki code .s2KDN, html code.shiki .s2KDN{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--shiki-dark:#9ECBFF}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 .sLHrO, html code.shiki .sLHrO{--shiki-light:#90A4AE;--shiki-default:#E06C75;--shiki-dark:#E1E4E8}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 .sLise, html code.shiki .sLise{--shiki-light:#E2931D;--shiki-default:#E06C75;--shiki-dark:#79B8FF}html pre.shiki code .sxkdo, html code.shiki .sxkdo{--shiki-light:#F76D47;--shiki-default:#E06C75;--shiki-dark:#79B8FF}html pre.shiki code .s9IQt, html code.shiki .s9IQt{--shiki-light:#E53935;--shiki-default:#E06C75;--shiki-dark:#79B8FF}html pre.shiki code .sg798, html code.shiki .sg798{--shiki-light:#90A4AE;--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 .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 .scCEF, html code.shiki .scCEF{--shiki-light:#39ADB5;--shiki-default:#D19A66;--shiki-dark:#79B8FF}",{"title":74,"searchDepth":98,"depth":98,"links":3826},[3827,3828,3829,3830,3831,3832,3833,3834,3835,3836,3837],{"id":12,"depth":98,"text":13},{"id":2096,"depth":98,"text":2097},{"id":2134,"depth":98,"text":2134},{"id":2262,"depth":98,"text":2263},{"id":2561,"depth":98,"text":2562},{"id":2589,"depth":98,"text":2590},{"id":3230,"depth":98,"text":3231},{"id":3387,"depth":98,"text":3388},{"id":1669,"depth":98,"text":1669},{"id":1737,"depth":98,"text":1737},{"id":1752,"depth":98,"text":1753},"2026-08-03T00:00:00.000Z","แนะนำ Grafana Alloy ตัวเก็บ telemetry รุ่นใหม่ของ Grafana ตั้งแต่ติดตั้งด้วย docker เขียน config ส่ง log เข้า Loki ไปจนถึงคำสั่ง validate และ convert จาก Promtail",{},"Grafana Alloy With Loki","/blogs/2026-08-03-grafana-alloy-loki",[1808],{"title":2058,"description":3839},{"loc":3842},"blogs/2026-08-03-grafana-alloy-loki",[1814],"4ri5cnRtp0DQtmGT_GJ7z6bV1eWEI5iph1oBfohJLtY",[3850,4701,5132,5325,5544,5709,6151,6266,6355,6445],{"id":3851,"title":3852,"body":3853,"date":4690,"description":4691,"draft":1801,"extension":1802,"meta":4692,"navigation":344,"next":1804,"ogTitle":4693,"path":4694,"pin":1801,"prev":1804,"recommends":4695,"seo":4696,"sitemap":4697,"stem":4698,"tags":4699,"updated":1804,"__hash__":4700},"content/blogs/2024-01-14-setup-ubuntu-server.md","ตั้งค่า Ubuntu Server",{"type":7,"value":3854,"toc":4674},[3855,3857,3860,3863,3868,3896,3903,3921,3943,3960,3964,3991,3995,4030,4034,4040,4067,4074,4079,4117,4123,4162,4165,4176,4180,4187,4228,4232,4281,4292,4375,4382,4440,4446,4457,4460,4501,4512,4564,4567,4571,4628,4631,4671],[10,3856,13],{"id":12},[15,3858,3859],{},"รวม command ที่ต้องใช้บ่อย ๆ เมื่อมีการ Setup เครื่อง Ubuntu ใหม่ (Version 20, 22, 24)",[10,3861,3862],{"id":3862},"หมวดหมู่การตั้งค่าพื้นฐาน",[3864,3865,3867],"h3",{"id":3866},"update-packages-ต่าง-ๆ-ที่ติดตั้งมาใน-os-ให้เป็น-version-ล่าสุด","Update Packages ต่าง ๆ ที่ติดตั้งมาใน OS ให้เป็น Version ล่าสุด",[69,3869,3871],{"className":71,"code":3870,"language":73,"meta":74,"style":74},"sudo apt update && sudo apt upgrade -y\n",[22,3872,3873],{"__ignoreMap":74},[78,3874,3875,3877,3880,3883,3885,3888,3890,3893],{"class":80,"line":81},[78,3876,1552],{"class":84},[78,3878,3879],{"class":88}," apt",[78,3881,3882],{"class":88}," update",[78,3884,332],{"class":331},[78,3886,3887],{"class":84}," sudo",[78,3889,3879],{"class":88},[78,3891,3892],{"class":88}," upgrade",[78,3894,3895],{"class":106}," -y\n",[3864,3897,3899,3900],{"id":3898},"แก้ไข-timezone-ให้เป็น-asiabangkok","แก้ไข timezone ให้เป็น ",[22,3901,3902],{},"Asia/Bangkok",[69,3904,3906],{"className":71,"code":3905,"language":73,"meta":74,"style":74},"sudo timedatectl set-timezone Asia/Bangkok\n",[22,3907,3908],{"__ignoreMap":74},[78,3909,3910,3912,3915,3918],{"class":80,"line":81},[78,3911,1552],{"class":84},[78,3913,3914],{"class":88}," timedatectl",[78,3916,3917],{"class":88}," set-timezone",[78,3919,3920],{"class":88}," Asia/Bangkok\n",[3864,3922,3924,3925,3928,3929,3932,3933,3932,3936,3932,3939,3942],{"id":3923},"ติดตั้ง-packages-ที่สำคัญสำหรับการพัฒนา-development-โดยภายใน-build-essential-จะมีการรวมเอา-packages-ที่สำคัญเอาไว้เช่น-gcc-c-make-lib6-dev-และอื่น-ๆ-อีกมากมาย","ติดตั้ง Packages ที่สำคัญสำหรับการพัฒนา (Development) โดยภายใน ",[22,3926,3927],{},"Build Essential"," จะมีการรวมเอา Packages ที่สำคัญเอาไว้เช่น ",[22,3930,3931],{},"gcc",", ",[22,3934,3935],{},"c++",[22,3937,3938],{},"make",[22,3940,3941],{},"lib6-dev"," และอื่น ๆ อีกมากมาย",[69,3944,3946],{"className":71,"code":3945,"language":73,"meta":74,"style":74},"sudo apt install build-essential\n",[22,3947,3948],{"__ignoreMap":74},[78,3949,3950,3952,3954,3957],{"class":80,"line":81},[78,3951,1552],{"class":84},[78,3953,3879],{"class":88},[78,3955,3956],{"class":88}," install",[78,3958,3959],{"class":88}," build-essential\n",[3864,3961,3963],{"id":3962},"แก้ไข-hostname","แก้ไข Hostname",[69,3965,3967],{"className":71,"code":3966,"language":73,"meta":74,"style":74},"sudo hostnamectl set-hostname \u003Chostname>\n",[22,3968,3969],{"__ignoreMap":74},[78,3970,3971,3973,3976,3979,3982,3985,3988],{"class":80,"line":81},[78,3972,1552],{"class":84},[78,3974,3975],{"class":88}," hostnamectl",[78,3977,3978],{"class":88}," set-hostname",[78,3980,3981],{"class":161}," \u003C",[78,3983,3984],{"class":88},"hostnam",[78,3986,3987],{"class":3507},"e",[78,3989,3990],{"class":161},">\n",[3864,3992,3994],{"id":3993},"ปิดการใช้งาน-cloud-init-network","ปิดการใช้งาน Cloud-init network",[69,3996,3998],{"className":71,"code":3997,"language":73,"meta":74,"style":74},"sudo nano  /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg\n\n# เอาไปใส่ในไฟล์\nnetwork: {config: disabled}\n",[22,3999,4000,4010,4014,4019],{"__ignoreMap":74},[78,4001,4002,4004,4007],{"class":80,"line":81},[78,4003,1552],{"class":84},[78,4005,4006],{"class":88}," nano",[78,4008,4009],{"class":88},"  /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg\n",[78,4011,4012],{"class":80,"line":98},[78,4013,345],{"emptyLinePlaceholder":344},[78,4015,4016],{"class":80,"line":348},[78,4017,4018],{"class":351},"# เอาไปใส่ในไฟล์\n",[78,4020,4021,4024,4027],{"class":80,"line":355},[78,4022,4023],{"class":84},"network:",[78,4025,4026],{"class":88}," {config:",[78,4028,4029],{"class":88}," disabled}\n",[3864,4031,4033],{"id":4032},"ขั้นตอนการติดตั้ง-docker-ใน-version-daemon","ขั้นตอนการติดตั้ง docker ใน version daemon",[15,4035,4036,4037,4039],{},"ติดตั้ง ",[22,4038,1348],{}," สำหรับการเรียกใช้งาน Script",[69,4041,4043],{"className":71,"code":4042,"language":73,"meta":74,"style":74},"sudo apt update\nsudo apt install curl -y\n",[22,4044,4045,4054],{"__ignoreMap":74},[78,4046,4047,4049,4051],{"class":80,"line":81},[78,4048,1552],{"class":84},[78,4050,3879],{"class":88},[78,4052,4053],{"class":88}," update\n",[78,4055,4056,4058,4060,4062,4065],{"class":80,"line":98},[78,4057,1552],{"class":84},[78,4059,3879],{"class":88},[78,4061,3956],{"class":88},[78,4063,4064],{"class":88}," curl",[78,4066,3895],{"class":106},[15,4068,4069,4070,4073],{},"ติดตั้งโดยใช้ script จาก ",[22,4071,4072],{},"get.docker.com"," ซึ่งจะช่วยในการติดตั้ง docker แบบง่ายมาก ๆ",[1017,4075,4076],{},[15,4077,4078],{},"script นี้จำเป็นต้องใช้ root หรือ sudo ในการทำงานนะครับ",[69,4080,4082],{"className":71,"code":4081,"language":73,"meta":74,"style":74},"cd /tmp\ncurl -fsSL https://get.docker.com -o get-docker.sh\nsudo sh get-docker.sh\n",[22,4083,4084,4092,4108],{"__ignoreMap":74},[78,4085,4086,4089],{"class":80,"line":81},[78,4087,4088],{"class":335},"cd",[78,4090,4091],{"class":88}," /tmp\n",[78,4093,4094,4096,4099,4102,4105],{"class":80,"line":98},[78,4095,1348],{"class":84},[78,4097,4098],{"class":106}," -fsSL",[78,4100,4101],{"class":88}," https://get.docker.com",[78,4103,4104],{"class":106}," -o",[78,4106,4107],{"class":88}," get-docker.sh\n",[78,4109,4110,4112,4115],{"class":80,"line":348},[78,4111,1552],{"class":84},[78,4113,4114],{"class":88}," sh",[78,4116,4107],{"class":88},[15,4118,4119,4120,4122],{},"หากต้องการรัน docker โดยไม่ต้องรัน ",[22,4121,1552],{}," นำหน้าสามารถเพิ่ม user ดังกล่าวเข้าไปใน group docker ได้ครับ",[69,4124,4126],{"className":71,"code":4125,"language":73,"meta":74,"style":74},"# run docker without sudo\nsudo gpasswd -a $USER docker\nsudo service docker restart\n",[22,4127,4128,4133,4149],{"__ignoreMap":74},[78,4129,4130],{"class":80,"line":81},[78,4131,4132],{"class":351},"# run docker without sudo\n",[78,4134,4135,4137,4140,4143,4146],{"class":80,"line":98},[78,4136,1552],{"class":84},[78,4138,4139],{"class":88}," gpasswd",[78,4141,4142],{"class":106}," -a",[78,4144,4145],{"class":919}," $USER",[78,4147,4148],{"class":88}," docker\n",[78,4150,4151,4153,4156,4159],{"class":80,"line":348},[78,4152,1552],{"class":84},[78,4154,4155],{"class":88}," service",[78,4157,4158],{"class":88}," docker",[78,4160,4161],{"class":88}," restart\n",[15,4163,4164],{},"ทดสอบเรียกใช้งาน",[69,4166,4168],{"className":71,"code":4167,"language":73,"meta":74,"style":74},"docker --version\n",[22,4169,4170],{"__ignoreMap":74},[78,4171,4172,4174],{"class":80,"line":81},[78,4173,85],{"class":84},[78,4175,2125],{"class":106},[10,4177,4179],{"id":4178},"ติดตั้ง-helm","ติดตั้ง helm",[15,4181,4182,4183],{},"Credit: ",[27,4184,4185],{"href":4185,"rel":4186},"https://helm.sh/docs/intro/install/#from-script",[31],[69,4188,4190],{"className":71,"code":4189,"language":73,"meta":74,"style":74},"cd /tmp\ncurl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3\nchmod 700 get_helm.sh\n./get_helm.sh\n",[22,4191,4192,4198,4212,4223],{"__ignoreMap":74},[78,4193,4194,4196],{"class":80,"line":81},[78,4195,4088],{"class":335},[78,4197,4091],{"class":88},[78,4199,4200,4202,4204,4206,4209],{"class":80,"line":98},[78,4201,1348],{"class":84},[78,4203,4098],{"class":106},[78,4205,4104],{"class":106},[78,4207,4208],{"class":88}," get_helm.sh",[78,4210,4211],{"class":88}," https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3\n",[78,4213,4214,4217,4220],{"class":80,"line":348},[78,4215,4216],{"class":84},"chmod",[78,4218,4219],{"class":379}," 700",[78,4221,4222],{"class":88}," get_helm.sh\n",[78,4224,4225],{"class":80,"line":355},[78,4226,4227],{"class":84},"./get_helm.sh\n",[10,4229,4231],{"id":4230},"ติดตั้ง-zsh-p10k-configure-เพื่อให้ใช้งาน-terminal-ได้ง่ายยิ่งขึ้น","ติดตั้ง ZSH + P10k Configure เพื่อให้ใช้งาน terminal ได้ง่ายยิ่งขึ้น",[69,4233,4235],{"className":71,"code":4234,"language":73,"meta":74,"style":74},"sudo apt install curl wget git zsh -y\n\nsh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\n\n",[22,4236,4237,4258,4262],{"__ignoreMap":74},[78,4238,4239,4241,4243,4245,4247,4250,4253,4256],{"class":80,"line":81},[78,4240,1552],{"class":84},[78,4242,3879],{"class":88},[78,4244,3956],{"class":88},[78,4246,4064],{"class":88},[78,4248,4249],{"class":88}," wget",[78,4251,4252],{"class":88}," git",[78,4254,4255],{"class":88}," zsh",[78,4257,3895],{"class":106},[78,4259,4260],{"class":80,"line":98},[78,4261,345],{"emptyLinePlaceholder":344},[78,4263,4264,4266,4268,4271,4273,4275,4278],{"class":80,"line":348},[78,4265,73],{"class":84},[78,4267,220],{"class":106},[78,4269,4270],{"class":168}," \"$(",[78,4272,1348],{"class":84},[78,4274,4098],{"class":106},[78,4276,4277],{"class":88}," https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh",[78,4279,4280],{"class":168},")\"\n",[15,4282,4283,4284,4287,4288,4291],{},"ติดตั้ง plugins เพิ่มเติมโดยแนะนำเป็น ",[22,4285,4286],{},"zsh-syntax-highlighting"," เพื่อช่วยในการ highlight command และ ",[22,4289,4290],{},"zsh-autosuggestions"," ซึ่งจะเป็นการนำเอา history มาแสดงให้ขณะพิมพ์ command",[69,4293,4295],{"className":71,"code":4294,"language":73,"meta":74,"style":74},"# Install plugins\ngit clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting\ngit clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions\n",[22,4296,4297,4302,4343],{"__ignoreMap":74},[78,4298,4299],{"class":80,"line":81},[78,4300,4301],{"class":351},"# Install plugins\n",[78,4303,4304,4307,4310,4313,4316,4319,4322,4325,4328,4330,4333,4335,4338,4340],{"class":80,"line":98},[78,4305,4306],{"class":84},"git",[78,4308,4309],{"class":88}," clone",[78,4311,4312],{"class":88}," https://github.com/zsh-users/zsh-syntax-highlighting.git",[78,4314,4315],{"class":331}," ${",[78,4317,4318],{"class":919},"ZSH_CUSTOM",[78,4320,4321],{"class":161},":-",[78,4323,4324],{"class":3507},"~",[78,4326,4327],{"class":161},"/",[78,4329,3511],{"class":3507},[78,4331,4332],{"class":919},"oh-my-zsh",[78,4334,4327],{"class":161},[78,4336,4337],{"class":919},"custom",[78,4339,2678],{"class":331},[78,4341,4342],{"class":88},"/plugins/zsh-syntax-highlighting\n",[78,4344,4345,4347,4349,4352,4354,4356,4358,4360,4362,4364,4366,4368,4370,4372],{"class":80,"line":348},[78,4346,4306],{"class":84},[78,4348,4309],{"class":88},[78,4350,4351],{"class":88}," https://github.com/zsh-users/zsh-autosuggestions",[78,4353,4315],{"class":331},[78,4355,4318],{"class":919},[78,4357,4321],{"class":161},[78,4359,4324],{"class":3507},[78,4361,4327],{"class":161},[78,4363,3511],{"class":3507},[78,4365,4332],{"class":919},[78,4367,4327],{"class":161},[78,4369,4337],{"class":919},[78,4371,2678],{"class":331},[78,4373,4374],{"class":88},"/plugins/zsh-autosuggestions\n",[15,4376,4377,4378,4381],{},"แก้ไข ",[22,4379,4380],{},".zshrc"," file เพื่อทำการเพิ่ม plugins เข้าไป",[69,4383,4385],{"className":71,"code":4384,"language":73,"meta":74,"style":74},"nano ~/.zshrc\n\n# Update plugins\nplugins=(\n    git\n    zsh-syntax-highlighting # \u003C- Add\n    zsh-autosuggestions     # \u003C- Add\n)\n",[22,4386,4387,4395,4399,4404,4414,4419,4427,4435],{"__ignoreMap":74},[78,4388,4389,4392],{"class":80,"line":81},[78,4390,4391],{"class":84},"nano",[78,4393,4394],{"class":88}," ~/.zshrc\n",[78,4396,4397],{"class":80,"line":98},[78,4398,345],{"emptyLinePlaceholder":344},[78,4400,4401],{"class":80,"line":348},[78,4402,4403],{"class":351},"# Update plugins\n",[78,4405,4406,4409,4411],{"class":80,"line":355},[78,4407,4408],{"class":919},"plugins",[78,4410,924],{"class":923},[78,4412,4413],{"class":331},"(\n",[78,4415,4416],{"class":80,"line":385},[78,4417,4418],{"class":88},"    git\n",[78,4420,4421,4424],{"class":80,"line":412},[78,4422,4423],{"class":88},"    zsh-syntax-highlighting",[78,4425,4426],{"class":351}," # \u003C- Add\n",[78,4428,4429,4432],{"class":80,"line":417},[78,4430,4431],{"class":88},"    zsh-autosuggestions",[78,4433,4434],{"class":351},"     # \u003C- Add\n",[78,4436,4437],{"class":80,"line":423},[78,4438,4439],{"class":331},")\n",[15,4441,4442,4443,4445],{},"จากนั้น reload ",[22,4444,4380],{}," file เพื่อทำการ reload การตั้งค่า",[69,4447,4449],{"className":71,"code":4448,"language":73,"meta":74,"style":74},"source ~/.zshrc\n",[22,4450,4451],{"__ignoreMap":74},[78,4452,4453,4455],{"class":80,"line":81},[78,4454,3514],{"class":335},[78,4456,4394],{"class":88},[15,4458,4459],{},"ลง P10K เพิ่มเพื่อทำให้ terminal ดูสวยงามมากยิ่งขึ้น",[69,4461,4463],{"className":71,"code":4462,"language":73,"meta":74,"style":74},"git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k\n\n",[22,4464,4465],{"__ignoreMap":74},[78,4466,4467,4469,4471,4474,4477,4479,4481,4483,4486,4488,4490,4492,4494,4496,4498],{"class":80,"line":81},[78,4468,4306],{"class":84},[78,4470,4309],{"class":88},[78,4472,4473],{"class":106}," --depth=1",[78,4475,4476],{"class":88}," https://github.com/romkatv/powerlevel10k.git",[78,4478,4315],{"class":331},[78,4480,4318],{"class":919},[78,4482,4321],{"class":161},[78,4484,4485],{"class":919},"$HOME",[78,4487,4327],{"class":161},[78,4489,3511],{"class":3507},[78,4491,4332],{"class":919},[78,4493,4327],{"class":161},[78,4495,4337],{"class":919},[78,4497,2678],{"class":331},[78,4499,4500],{"class":88},"/themes/powerlevel10k\n",[15,4502,4503,4504,4507,4508,4511],{},"จากนั้นแก้ไข ",[22,4505,4506],{},"ZSH_THEME"," ให้เป็น ",[22,4509,4510],{},"powerlevel10k/powerlevel10k"," เพื่อ load theme ดังกล่าวเข้าไป",[69,4513,4515],{"className":71,"code":4514,"language":73,"meta":74,"style":74},"# Update .zshrc file\nnano ~/.zshrc\n\n# Update Theme\nZSH_THEME=\"powerlevel10k/powerlevel10k\"\n\n# Reload\nsource ~/.zshrc\n",[22,4516,4517,4522,4528,4532,4537,4549,4553,4558],{"__ignoreMap":74},[78,4518,4519],{"class":80,"line":81},[78,4520,4521],{"class":351},"# Update .zshrc file\n",[78,4523,4524,4526],{"class":80,"line":98},[78,4525,4391],{"class":84},[78,4527,4394],{"class":88},[78,4529,4530],{"class":80,"line":348},[78,4531,345],{"emptyLinePlaceholder":344},[78,4533,4534],{"class":80,"line":355},[78,4535,4536],{"class":351},"# Update Theme\n",[78,4538,4539,4541,4543,4545,4547],{"class":80,"line":385},[78,4540,4506],{"class":919},[78,4542,924],{"class":923},[78,4544,473],{"class":168},[78,4546,4510],{"class":88},[78,4548,409],{"class":168},[78,4550,4551],{"class":80,"line":412},[78,4552,345],{"emptyLinePlaceholder":344},[78,4554,4555],{"class":80,"line":417},[78,4556,4557],{"class":351},"# Reload\n",[78,4559,4560,4562],{"class":80,"line":423},[78,4561,3514],{"class":335},[78,4563,4394],{"class":88},[4565,4566],"hr",{},[10,4568,4570],{"id":4569},"ติดตั้ง-nodejs-v22","ติดตั้ง NodeJS (v.22)",[69,4572,4574],{"className":71,"code":4573,"language":73,"meta":74,"style":74},"curl -sL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh\nsudo bash /tmp/nodesource_setup.sh\nsudo apt install nodejs\n\n# Verify\nnode -v\n",[22,4575,4576,4591,4600,4611,4615,4620],{"__ignoreMap":74},[78,4577,4578,4580,4583,4586,4588],{"class":80,"line":81},[78,4579,1348],{"class":84},[78,4581,4582],{"class":106}," -sL",[78,4584,4585],{"class":88}," https://deb.nodesource.com/setup_22.x",[78,4587,4104],{"class":106},[78,4589,4590],{"class":88}," /tmp/nodesource_setup.sh\n",[78,4592,4593,4595,4598],{"class":80,"line":98},[78,4594,1552],{"class":84},[78,4596,4597],{"class":88}," bash",[78,4599,4590],{"class":88},[78,4601,4602,4604,4606,4608],{"class":80,"line":348},[78,4603,1552],{"class":84},[78,4605,3879],{"class":88},[78,4607,3956],{"class":88},[78,4609,4610],{"class":88}," nodejs\n",[78,4612,4613],{"class":80,"line":355},[78,4614,345],{"emptyLinePlaceholder":344},[78,4616,4617],{"class":80,"line":385},[78,4618,4619],{"class":351},"# Verify\n",[78,4621,4622,4625],{"class":80,"line":412},[78,4623,4624],{"class":84},"node",[78,4626,4627],{"class":106}," -v\n",[15,4629,4630],{},"ติดตั้ง yarn เพิ่มเติม",[69,4632,4634],{"className":71,"code":4633,"language":73,"meta":74,"style":74},"# Install yarn\nsudo npm install -g yarn\n\n# Verify\nyarn --version\n",[22,4635,4636,4641,4656,4660,4664],{"__ignoreMap":74},[78,4637,4638],{"class":80,"line":81},[78,4639,4640],{"class":351},"# Install yarn\n",[78,4642,4643,4645,4648,4650,4653],{"class":80,"line":98},[78,4644,1552],{"class":84},[78,4646,4647],{"class":88}," npm",[78,4649,3956],{"class":88},[78,4651,4652],{"class":106}," -g",[78,4654,4655],{"class":88}," yarn\n",[78,4657,4658],{"class":80,"line":348},[78,4659,345],{"emptyLinePlaceholder":344},[78,4661,4662],{"class":80,"line":355},[78,4663,4619],{"class":351},[78,4665,4666,4669],{"class":80,"line":385},[78,4667,4668],{"class":84},"yarn",[78,4670,2125],{"class":106},[1780,4672,4673],{},"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 .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--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 .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 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 .sJZfL, html code.shiki .sJZfL{--shiki-light:#6182B8;--shiki-default:#56B6C2;--shiki-dark:#79B8FF}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 .snfNA, html code.shiki .snfNA{--shiki-light:#39ADB5;--shiki-default:#98C379;--shiki-dark:#9ECBFF}html pre.shiki code .sMTEB, html code.shiki .sMTEB{--shiki-light:#39ADB5;--shiki-default:#56B6C2;--shiki-dark:#F97583}",{"title":74,"searchDepth":98,"depth":98,"links":4675},[4676,4677,4687,4688,4689],{"id":12,"depth":98,"text":13},{"id":3862,"depth":98,"text":3862,"children":4678},[4679,4680,4682,4684,4685,4686],{"id":3866,"depth":348,"text":3867},{"id":3898,"depth":348,"text":4681},"แก้ไข timezone ให้เป็น Asia/Bangkok",{"id":3923,"depth":348,"text":4683},"ติดตั้ง Packages ที่สำคัญสำหรับการพัฒนา (Development) โดยภายใน Build Essential จะมีการรวมเอา Packages ที่สำคัญเอาไว้เช่น gcc, c++, make, lib6-dev และอื่น ๆ อีกมากมาย",{"id":3962,"depth":348,"text":3963},{"id":3993,"depth":348,"text":3994},{"id":4032,"depth":348,"text":4033},{"id":4178,"depth":98,"text":4179},{"id":4230,"depth":98,"text":4231},{"id":4569,"depth":98,"text":4570},"2024-01-14T00:00:00.000Z","โน๊ตนี้คือ commands ที่มักจะใช้บ่อย ๆ เมื่อต้อง setup ubuntu server (20.04, 22.04)",{},"Setup Ubuntu Server","/blogs/2024-01-14-setup-ubuntu-server",[2049],{"title":3852,"description":4691},{"loc":4694},"blogs/2024-01-14-setup-ubuntu-server",[1814],"5oBy7u1bHxX4cEDFPSQBKPIWtGVACmqhxjEqyYarFoc",{"id":4702,"title":4703,"body":4704,"date":5121,"description":5122,"draft":1801,"extension":1802,"meta":5123,"navigation":344,"next":1804,"ogTitle":4703,"path":5124,"pin":1801,"prev":1804,"recommends":5125,"seo":5126,"sitemap":5127,"stem":5128,"tags":5129,"updated":5130,"__hash__":5131},"content/blogs/2024-01-19-setup-uptime-kuma.md","Setup Uptime Kuma",{"type":7,"value":4705,"toc":5113},[4706,4708,4711,4721,4726,4730,4781,4785,4792,4886,4890,4979,4985,4990,4994,5071,5083,5094,5098,5110],[10,4707,13],{"id":12},[15,4709,4710],{},"โน๊ตนี้คือ วิธีการ setup uptime kuma แบบไม่ใช้ docker",[1755,4712,4713],{},[1758,4714,4715,4716],{},"สำหรับข้อมูลฉบับเต็มสามารถดูได้ที่ ",[27,4717,4720],{"href":4718,"rel":4719},"https://github.com/louislam/uptime-kuma",[31],"louislam/uptime-kuma",[1017,4722,4723],{},[15,4724,4725],{},"อัปเดต 2026-08-09 ตอนที่เขียนโน๊ตนี้ครั้งแรกยังเป็น 1.23.x อยู่ ตอนนี้ 2.x ออกมาแล้วนะ\nเลยกลับมาแก้ส่วนของการอัปเดตให้ตรงกับของจริง",[10,4727,4729],{"id":4728},"_1-basic-setup","1. Basic setup",[69,4731,4733],{"className":71,"code":4732,"language":73,"meta":74,"style":74},"sudo apt update && sudo apt upgrade -y\nsudo timedatectl set-timezone Asia/Bangkok\n\nsudo apt install curl git -y\n",[22,4734,4735,4753,4763,4767],{"__ignoreMap":74},[78,4736,4737,4739,4741,4743,4745,4747,4749,4751],{"class":80,"line":81},[78,4738,1552],{"class":84},[78,4740,3879],{"class":88},[78,4742,3882],{"class":88},[78,4744,332],{"class":331},[78,4746,3887],{"class":84},[78,4748,3879],{"class":88},[78,4750,3892],{"class":88},[78,4752,3895],{"class":106},[78,4754,4755,4757,4759,4761],{"class":80,"line":98},[78,4756,1552],{"class":84},[78,4758,3914],{"class":88},[78,4760,3917],{"class":88},[78,4762,3920],{"class":88},[78,4764,4765],{"class":80,"line":348},[78,4766,345],{"emptyLinePlaceholder":344},[78,4768,4769,4771,4773,4775,4777,4779],{"class":80,"line":355},[78,4770,1552],{"class":84},[78,4772,3879],{"class":88},[78,4774,3956],{"class":88},[78,4776,4064],{"class":88},[78,4778,4252],{"class":88},[78,4780,3895],{"class":106},[10,4782,4784],{"id":4783},"_2-install-nodejs-v22-and-pm2","2. Install nodejs (v.22) and pm2",[15,4786,4787,4788,4791],{},"Uptime Kuma 2.x บังคับ nodejs ตั้งแต่ 20.4.0 ขึ้นไป (ดูได้จาก ",[22,4789,4790],{},"engines"," ใน package.json ของ repo) ลง v.22 ไปเลยก็ได้ครับ",[69,4793,4795],{"className":71,"code":4794,"language":73,"meta":74,"style":74},"curl -sL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh\n\nsudo bash /tmp/nodesource_setup.sh\n\nsudo apt install nodejs\n\n# Verify\nnode -v\n\n# Install pm2\nsudo npm install -g pm2\n\n# Verify\npm2 --version\n",[22,4796,4797,4809,4813,4821,4825,4835,4839,4843,4849,4853,4858,4871,4875,4879],{"__ignoreMap":74},[78,4798,4799,4801,4803,4805,4807],{"class":80,"line":81},[78,4800,1348],{"class":84},[78,4802,4582],{"class":106},[78,4804,4585],{"class":88},[78,4806,4104],{"class":106},[78,4808,4590],{"class":88},[78,4810,4811],{"class":80,"line":98},[78,4812,345],{"emptyLinePlaceholder":344},[78,4814,4815,4817,4819],{"class":80,"line":348},[78,4816,1552],{"class":84},[78,4818,4597],{"class":88},[78,4820,4590],{"class":88},[78,4822,4823],{"class":80,"line":355},[78,4824,345],{"emptyLinePlaceholder":344},[78,4826,4827,4829,4831,4833],{"class":80,"line":385},[78,4828,1552],{"class":84},[78,4830,3879],{"class":88},[78,4832,3956],{"class":88},[78,4834,4610],{"class":88},[78,4836,4837],{"class":80,"line":412},[78,4838,345],{"emptyLinePlaceholder":344},[78,4840,4841],{"class":80,"line":417},[78,4842,4619],{"class":351},[78,4844,4845,4847],{"class":80,"line":423},[78,4846,4624],{"class":84},[78,4848,4627],{"class":106},[78,4850,4851],{"class":80,"line":449},[78,4852,345],{"emptyLinePlaceholder":344},[78,4854,4855],{"class":80,"line":462},[78,4856,4857],{"class":351},"# Install pm2\n",[78,4859,4860,4862,4864,4866,4868],{"class":80,"line":482},[78,4861,1552],{"class":84},[78,4863,4647],{"class":88},[78,4865,3956],{"class":88},[78,4867,4652],{"class":106},[78,4869,4870],{"class":88}," pm2\n",[78,4872,4873],{"class":80,"line":513},[78,4874,345],{"emptyLinePlaceholder":344},[78,4876,4877],{"class":80,"line":531},[78,4878,4619],{"class":351},[78,4880,4881,4884],{"class":80,"line":536},[78,4882,4883],{"class":84},"pm2",[78,4885,2125],{"class":106},[10,4887,4889],{"id":4888},"_3-install-uptime-kuma","3. Install Uptime Kuma",[69,4891,4893],{"className":71,"code":4892,"language":73,"meta":74,"style":74},"git clone https://github.com/louislam/uptime-kuma.git\n\ncd uptime-kuma\n\nnpm run setup\n\npm2 install pm2-logrotate\n\npm2 start server/server.js --name uptime-kuma\n\npm2 save && pm2 startup\n",[22,4894,4895,4904,4908,4915,4919,4929,4933,4942,4946,4960,4964],{"__ignoreMap":74},[78,4896,4897,4899,4901],{"class":80,"line":81},[78,4898,4306],{"class":84},[78,4900,4309],{"class":88},[78,4902,4903],{"class":88}," https://github.com/louislam/uptime-kuma.git\n",[78,4905,4906],{"class":80,"line":98},[78,4907,345],{"emptyLinePlaceholder":344},[78,4909,4910,4912],{"class":80,"line":348},[78,4911,4088],{"class":335},[78,4913,4914],{"class":88}," uptime-kuma\n",[78,4916,4917],{"class":80,"line":355},[78,4918,345],{"emptyLinePlaceholder":344},[78,4920,4921,4924,4926],{"class":80,"line":385},[78,4922,4923],{"class":84},"npm",[78,4925,103],{"class":88},[78,4927,4928],{"class":88}," setup\n",[78,4930,4931],{"class":80,"line":412},[78,4932,345],{"emptyLinePlaceholder":344},[78,4934,4935,4937,4939],{"class":80,"line":417},[78,4936,4883],{"class":84},[78,4938,3956],{"class":88},[78,4940,4941],{"class":88}," pm2-logrotate\n",[78,4943,4944],{"class":80,"line":423},[78,4945,345],{"emptyLinePlaceholder":344},[78,4947,4948,4950,4953,4956,4958],{"class":80,"line":449},[78,4949,4883],{"class":84},[78,4951,4952],{"class":88}," start",[78,4954,4955],{"class":88}," server/server.js",[78,4957,110],{"class":106},[78,4959,4914],{"class":88},[78,4961,4962],{"class":80,"line":462},[78,4963,345],{"emptyLinePlaceholder":344},[78,4965,4966,4968,4971,4973,4976],{"class":80,"line":482},[78,4967,4883],{"class":84},[78,4969,4970],{"class":88}," save",[78,4972,332],{"class":331},[78,4974,4975],{"class":84}," pm2",[78,4977,4978],{"class":88}," startup\n",[15,4980,4981,4984],{},[22,4982,4983],{},"npm run setup"," จะ checkout ไปที่ tag ล่าสุดให้เองครับ ไม่ต้องระบุ version เอง ตอนนี้ (2026-08-09) คือ 2.5.0",[15,4986,4987],{},[22,4988,4989],{},"http://ip:3001",[10,4991,4993],{"id":4992},"_4-update-uptime-kuma","4. Update Uptime Kuma",[69,4995,4997],{"className":71,"code":4996,"language":73,"meta":74,"style":74},"cd uptime-kuma\n\ngit fetch --all\ngit checkout 2.5.0 --force\n\nnpm ci --omit dev\nnpm run download-dist\n\npm2 restart uptime-kuma\n",[22,4998,4999,5005,5009,5019,5032,5036,5049,5058,5062],{"__ignoreMap":74},[78,5000,5001,5003],{"class":80,"line":81},[78,5002,4088],{"class":335},[78,5004,4914],{"class":88},[78,5006,5007],{"class":80,"line":98},[78,5008,345],{"emptyLinePlaceholder":344},[78,5010,5011,5013,5016],{"class":80,"line":348},[78,5012,4306],{"class":84},[78,5014,5015],{"class":88}," fetch",[78,5017,5018],{"class":106}," --all\n",[78,5020,5021,5023,5026,5029],{"class":80,"line":355},[78,5022,4306],{"class":84},[78,5024,5025],{"class":88}," checkout",[78,5027,5028],{"class":379}," 2.5.0",[78,5030,5031],{"class":106}," --force\n",[78,5033,5034],{"class":80,"line":385},[78,5035,345],{"emptyLinePlaceholder":344},[78,5037,5038,5040,5043,5046],{"class":80,"line":412},[78,5039,4923],{"class":84},[78,5041,5042],{"class":88}," ci",[78,5044,5045],{"class":106}," --omit",[78,5047,5048],{"class":88}," dev\n",[78,5050,5051,5053,5055],{"class":80,"line":417},[78,5052,4923],{"class":84},[78,5054,103],{"class":88},[78,5056,5057],{"class":88}," download-dist\n",[78,5059,5060],{"class":80,"line":423},[78,5061,345],{"emptyLinePlaceholder":344},[78,5063,5064,5066,5069],{"class":80,"line":449},[78,5065,4883],{"class":84},[78,5067,5068],{"class":88}," restart",[78,5070,4914],{"class":88},[15,5072,5073,5074,5077,5078],{},"เลข version ตรง ",[22,5075,5076],{},"git checkout"," เปลี่ยนตามอันล่าสุดได้เลย ดูได้ที่ ",[27,5079,5082],{"href":5080,"rel":5081},"https://github.com/louislam/uptime-kuma/releases",[31],"Releases",[1017,5084,5085],{},[15,5086,5087,5088,5093],{},"ถ้าอัปจาก 1.x มา 2.x อันนี้เป็น major version มี breaking changes อยู่ ทาง repo แนะนำให้สำรองข้อมูลก่อน\nแล้วอ่าน ",[27,5089,5092],{"href":5090,"rel":5091},"https://github.com/louislam/uptime-kuma/wiki/Migration-From-v1-To-v2",[31],"Migration From v1 To v2"," ให้จบก่อนนะ",[10,5095,5097],{"id":5096},"ref","Ref",[1755,5099,5100,5105],{},[1758,5101,5102],{},[27,5103,4720],{"href":4718,"rel":5104},[31],[1758,5106,5107],{},[27,5108,5082],{"href":5080,"rel":5109},[31],[1780,5111,5112],{},"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 .stlKB, html code.shiki .stlKB{--shiki-light:#39ADB5;--shiki-default:#ABB2BF;--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 .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 .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":74,"searchDepth":98,"depth":98,"links":5114},[5115,5116,5117,5118,5119,5120],{"id":12,"depth":98,"text":13},{"id":4728,"depth":98,"text":4729},{"id":4783,"depth":98,"text":4784},{"id":4888,"depth":98,"text":4889},{"id":4992,"depth":98,"text":4993},{"id":5096,"depth":98,"text":5097},"2024-01-19T00:00:00.000Z","วิธีติดตั้ง Uptime Kuma บน Ubuntu แบบไม่ใช้ docker ด้วย nodejs กับ pm2 พร้อมวิธีอัปเดตเป็น version 2.x",{},"/blogs/2024-01-19-setup-uptime-kuma",[],{"title":4703,"description":5122},{"loc":5124},"blogs/2024-01-19-setup-uptime-kuma",[1814],"2026-08-09T00:00:00.000Z","fNs_-CirZm8l5v2KDuNENJEJekQvNA8T3eMSnkyPnrc",{"id":5133,"title":5134,"body":5135,"date":5314,"description":5315,"draft":1801,"extension":1802,"meta":5316,"navigation":344,"next":1804,"ogTitle":5317,"path":5318,"pin":1801,"prev":1804,"recommends":5319,"seo":5320,"sitemap":5321,"stem":5322,"tags":5323,"updated":1804,"__hash__":5324},"content/blogs/2025-04-15-ubuntu-pro.md","วิธีการติดตั้ง Ubuntu Version Pro แบบฟรี",{"type":7,"value":5136,"toc":5309},[5137,5140,5150,5154,5219,5229,5233,5248,5254,5257,5262,5267,5277,5282,5289,5291,5294],[15,5138,5139],{},"หลายคนอาจเคยเห็นว่า Ubuntu มีเวอร์ชัน Pro ให้ใช้งาน ไม่ว่าจะในหน้าจอติดตั้งระบบ หรือเป็นหน้าต่างแจ้งเตือนเมื่อเข้าสู่ระบบปฏิบัติการแล้ว",[15,5141,5142,5143,5146,5147],{},"ทาง Ubuntu เปิดให้ใช้งานเวอร์ชัน Pro ได้ฟรี สำหรับผู้ใช้แบบส่วนตัวสูงสุด ",[22,5144,5145],{},"5 active machines"," และสำหรับสมาชิก Ubuntu community สูงสุด ",[22,5148,5149],{},"50 active machines",[10,5151,5153],{"id":5152},"ความแตกต่างหลักระหว่าง-ubuntu-ปกติ-และ-ubuntu-pro","ความแตกต่างหลักระหว่าง Ubuntu ปกติ และ Ubuntu Pro",[5155,5156,5157,5173],"table",{},[5158,5159,5160],"thead",{},[5161,5162,5163,5167,5170],"tr",{},[5164,5165,5166],"th",{},"คุณสมบัติ",[5164,5168,5169],{},"Ubuntu ปกติ",[5164,5171,5172],{},"Ubuntu Pro (ฟรี)",[5174,5175,5176,5188,5199,5208],"tbody",{},[5161,5177,5178,5182,5185],{},[5179,5180,5181],"td",{},"ระยะเวลารับอัปเดต",[5179,5183,5184],{},"5 ปี",[5179,5186,5187],{},"10 ปี",[5161,5189,5190,5193,5196],{},[5179,5191,5192],{},"Livepatch",[5179,5194,5195],{},"-",[5179,5197,5198],{},"✓",[5161,5200,5201,5204,5206],{},[5179,5202,5203],{},"การ hardening ระบบ",[5179,5205,5195],{},[5179,5207,5198],{},[5161,5209,5210,5213,5216],{},[5179,5211,5212],{},"จำนวนเครื่องที่รองรับ",[5179,5214,5215],{},"ไม่จำกัด",[5179,5217,5218],{},"5 เครื่อง (ส่วนตัว)",[1017,5220,5221],{},[15,5222,5223,5224,5228],{},"สามารถอ่านเพิ่มเติมได้ที่ ",[27,5225,5226],{"href":5226,"rel":5227},"https://ubuntu.com/pricing/pro",[31]," ตรง Ubuntu Pro for personal users",[10,5230,5232],{"id":5231},"ขั้นตอนการกดรับ-license","ขั้นตอนการกดรับ License",[5234,5235,5236],"ol",{},[1758,5237,5238,5239,5243,5244],{},"สมัคร account ที่ ",[27,5240,5241],{"href":5241,"rel":5242},"https://ubuntu.com",[31]," ให้เรียบร้อยจากนั้นไปที่หน้า ",[27,5245,5246],{"href":5246,"rel":5247},"https://ubuntu.com/pro",[31],[15,5249,5250],{},[290,5251],{"alt":5252,"src":5253},"Ubuntu Pro","/images/2025-04-15-ubuntu-pro/image-1.png",[15,5255,5256],{},"แล้วกดที่ปุ่ม \"Get Ubuntu Pro now\" ได้เลยครับ",[5234,5258,5259],{"start":98},[1758,5260,5261],{},"จากนั้นเลือกเป็น \"Myself\" ได้เลยครับ จะเป็นการกดรับ license free กด continue to card ได้เลย",[15,5263,5264],{},[290,5265],{"alt":5252,"src":5266},"/images/2025-04-15-ubuntu-pro/image-2.png",[5234,5268,5269],{"start":348},[1758,5270,5271,5272,5276],{},"เมื่อเสร็จสิ้นขั้นตอนให้ไปยัง ",[27,5273,5274],{"href":5274,"rel":5275},"https://ubuntu.com/pro/dashboard",[31]," ตรงเมนู \"Your subscriptions\" จะเจอกับ Token ซึ่งสามารถนำไปใช้งานได้เลยครับ",[15,5278,5279],{},[290,5280],{"alt":5252,"src":5281},"/images/2025-04-15-ubuntu-pro/image-3.png",[15,5283,5284,5285,5288],{},"หากเป็นการติดตั้งระบบปฏิบัติการใหม่ สามารถกรอก Token ได้ในขั้นตอนติดตั้ง แต่หากติดตั้ง Ubuntu ไปแล้ว สามารถใช้คำสั่ง ",[22,5286,5287],{},"sudo pro attach \u003CTOKEN>"," เพื่อเปิดใช้งาน License ได้เลย",[4565,5290],{},[10,5292,5293],{"id":5293},"แหล่งข้อมูลเพิ่มเติม",[1755,5295,5296,5303],{},[1758,5297,5298],{},[27,5299,5302],{"href":5300,"rel":5301},"https://ubuntu.com/legal/ubuntu-pro/personal",[31],"Ubuntu Pro for Personal Use",[1758,5304,5305],{},[27,5306,5308],{"href":5226,"rel":5307},[31],"Ubuntu Pro Pricing",{"title":74,"searchDepth":98,"depth":98,"links":5310},[5311,5312,5313],{"id":5152,"depth":98,"text":5153},{"id":5231,"depth":98,"text":5232},{"id":5293,"depth":98,"text":5293},"2025-04-15T00:00:00.000Z","วิธีขอและเปิดใช้ Ubuntu Pro ฟรีสำหรับ personal use สูงสุด 5 เครื่อง ได้อัปเดตความปลอดภัยยาว 10 ปี",{},"Ubuntu Pro Version","/blogs/2025-04-15-ubuntu-pro",[2050],{"title":5134,"description":5315},{"loc":5318},"blogs/2025-04-15-ubuntu-pro",[1814],"4URY48rjIDwOv4p9KyD2cdd-LNS--_uYloigKRGb5UY",{"id":5326,"title":5327,"body":5328,"date":5533,"description":5534,"draft":1801,"extension":1802,"meta":5535,"navigation":344,"next":1804,"ogTitle":5536,"path":5537,"pin":1801,"prev":1804,"recommends":5538,"seo":5539,"sitemap":5540,"stem":5541,"tags":5542,"updated":1804,"__hash__":5543},"content/blogs/2025-05-12-next-dns.md","ผมย้ายจาก Pihole & Adguard มาใช้ NextDNS",{"type":7,"value":5329,"toc":5520},[5330,5335,5339,5342,5345,5348,5352,5357,5367,5370,5374,5377,5381,5384,5387,5391,5394,5397,5400,5405,5408,5411,5417,5420,5423,5428,5431,5442,5445,5451,5454,5459,5463,5468,5471,5475,5478,5483,5487,5490,5495,5497,5500,5503,5510,5517],[15,5331,5332],{},[290,5333],{"alt":74,"src":5334},"/images/2025-05-12-next-dns/images-7cc8fbe7-61fd-422c-90fd-f1f87ff3201e.jpg",[10,5336,5338],{"id":5337},"nextdns-คือ","NextDNS คือ",[15,5340,5341],{},"DNS Server ที่สามารถช่วยในการป้องกัน security threads, block โฆษณา หรือ block การ tracking ของ website หรือ application ต่าง ๆ โดยที่เราสามารถตั้งค่าได้ว่าในแต่ละ device จะใช้ policy ใดในการป้องกัน",[15,5343,5344],{},"โดยวิธีการทำงานตัว NextDNS จะมี list ของ domain ที่เป็นอันตราย (สามารถใช้ Blacklist มาตรฐานทั่ว ๆ ไปของ Adguard หรือ PiHole ได้) เมื่อมีการพยายาม query ข้อมูลที่เป็นอันตรายจาก domain ที่อยู่ใน blacklist ตัว NextDNS ก็จะทำการ block คำขอดังกล่าวแทน",[15,5346,5347],{},"วิธีการใช้งานตั้งค่าจะคล้าย ๆ กับ Pihole หรือ Adguard เลยครับ แต่ต่างตรงที่เราไม่ต้อง self-hosted เอง, สามารถจัดการเองได้บน website \"my.nextdns.io\" ครับ",[10,5349,5351],{"id":5350},"pricing","Pricing",[15,5353,5354],{},[290,5355],{"alt":74,"src":5356},"/images/2025-05-12-next-dns/images-21282f92-db55-4b79-aa63-67fc9aa7303a.jpg",[15,5358,5359,5362,5363,5366],{},[1850,5360,5361],{},"ข้อมูล ณ วันที่ 12/5/68","\nจะมีให้ใช้ 4 Plan หลัก ๆ ด้วยกันโดยที่มี version free ให้ใช้ด้วยครับ สามารถตั้งค่าได้เหมือนกับ version เสียตัง แต่จะแตกต่างตรงที่หากครบ 300,000 queries ต่อเดือนแล้วหลังจากนั้น",[35,5364,5365],{},"จะไม่มีการ block query"," (จะกลายเป็นเหมือน DNS ธรรมดาทั่ว ๆ ไป) เลยไม่ต้องกังวลไปนะครับว่าหลังจากครบโควต้าฟรีแล้วจะใช้งาน DNS ไม่ได้",[15,5368,5369],{},"ส่วนตัวผมเลือกเป็น Version Pro ครับ ปีละ 590 บาท (ทดลองใช้ตัวฟรีประมาณ 1 สัปดาห์ก็ตัดสินใจจ่ายเงินเลยครับ)",[10,5371,5373],{"id":5372},"ควรเลือก-plan-ไหนดี","ควรเลือก Plan ไหนดี",[15,5375,5376],{},"สำหรับผมแล้ว 300,000 queries ต่อเดือน บอกเลยครับว่า \"ไม่พอ\" 5555 เลยตัดสินใจ subscription เป็นตัว pro แทน แต่ก็แนะนำว่าให้ลองใช้ดูก่อนก็ได้ครับ ว่าเข้ากับ lifestyle ของตัวเองรึเปล่า ถ้าถูกใจค่อยจ่ายตังก็ได้ครับ เพราะตัว free มี featureเหมือนกับตัว pro หรือตัวเสียตังเลย",[10,5378,5380],{"id":5379},"เอาเงินค่า-subscription-ไปเช่า-server-แล้ว-self-hosted-เองจะดีกว่ามั้ย","เอาเงินค่า subscription ไปเช่า server แล้ว self-hosted เองจะดีกว่ามั้ย",[15,5382,5383],{},"เครื่อง server 1 ปีในราคา 590 บาทถ้าถามว่ามีมั้ย ก็ต้องบอกว่าพอจะหาได้ครับ ผมเองก็เป็นสาย Self-hosted, Home Lab ที่ชอบการ self-hosted อะไรหลาย ๆ อย่าง แต่ผมมีความคิดนึงครับว่าจะมีอยู่ไม่กี่อย่างที่จะไม่ self-hosted เองได้แก่ Email Server, Bitwarden (password manager) และ DNS เพราะสิ่งเหล่านี้การดูแลรักษาให้ใช้งานได้และปลอดภัยถือว่าเป็น challenge มากครับ ถ้าใช้งานไม่ได้ขึ้นมางานหยาบทันที ดังนั้นยอมเสียตังหน่อยเพื่อใช้ความ stable ตรงนี้ดีกว่าครับ (อันนี้มุมมองผมนะ)",[15,5385,5386],{},"แล้วก็เคยมีประสบการณ์ที่ Internal ช้าเพราะว่า Setup Pihole & Adguard ไม่ดีทำให้จังหวะ query มันแปลก ๆ กว่าจะหาเหตุผลเจอว่าเป็นที่การตั้งค่าของตัวเอง ก็โทษ ISP อยู่หลายวัน 55555 (อันนี้ยอมรับว่าเป็น skill issue ของตัวเอง) แต่ก็อยากแชร์เป็น use-case ให้ฟังครับ",[10,5388,5390],{"id":5389},"dns-over-https","DNS over HTTPS",[15,5392,5393],{},"อันนี้เป็นอีกเหตุผลนึงที่ผมย้ายมาใช้ NextDNS เพราะว่ามี DoH ให้ใช้ อธิบายให้เห็นภาพง่าย ๆ ให้มองว่าถ้าเราใช้ DNS แบบปกติทางผู้ให้บริการ internal หรือผู้ดูแลระบบจะสามารถดู DNS query ได้ (DNS query จะไม่ได้มีการเข้ารหัสไว้) ซึ่งการมาใช้ DoH จะทำให้มี Privacy ในส่วนนี้มากยิ่งขึ้น จะเป็นการ encryption DNS query อีกทั้งยังสามารถป้องกันการโดน DNS Proxy จากผู้ดูแลได้อีกด้วย (สำหรับคนที่กังวลในเรื่อง Privacy แบบผมอันนี้คือจุดสำคัญเลยครับ)",[10,5395,5396],{"id":5396},"วิธีการใช้งาน",[15,5398,5399],{},"ต้องบอกว่าสามารถใช้งานได้ในแทบทุกอุปกรณ์เลย ส่วนตัวผมเองใช้งานบน Windows, MacOS, IPAD OS, iOS โดยจะมีการตั้งค่าหลายวิธีเลยครับ แต่แนะนำว่าให้ใช้วิธีการติดตั้ง Application ดีกว่า ตัวอย่างเช่น iOS",[15,5401,5402],{},[290,5403],{"alt":74,"src":5404},"/images/2025-05-12-next-dns/images-44fe4e0e-4f38-4ed5-abd5-c165444b4de7.jpg",[15,5406,5407],{},"ซึ่งสามารถใช้งานได้ 2 วิธีคือการลง Profile กับติดตั้ง Application บอกตรง ๆ ว่าผมไม่กล้าเลือกการติดตั้งโดยใช้ Profile ครับ เลยเลือกเป็น Download Application จาก App Store แทน",[15,5409,5410],{},"วิธีการใช้งานง่ายมากจริง ๆ อ่านตรงข้างล่างก็ทำตามได้แล้วครับ",[15,5412,5413,5414],{},"ผมเองใช้งานทั้งในรูปแบบของการติดตั้งบน end devices และติดตั้งบน router ที่บ้านเลยครับ แต่ถ้าเป็นการติดตั้งบน router ที่ไม่ได้รองรับการ setup next dns (อุุปกรณ์ที่รองรับเช่น Mikrotik, Tailscale, pfSense) จำเป็นต้องตั้งค่า DNS แบบปกติ (เอา IP มาใส่) แล้ว Update ตรง \"Linked IP\"\n",[290,5415],{"alt":74,"src":5416},"/images/2025-05-12-next-dns/images-5da350b0-f339-429e-a108-edfefb194df2.jpg",[15,5418,5419],{},"ให้ IP เป็นของบ้านเรา แล้วจะสามารถใช้ Profile นั้น ๆ ได้ จะใช้เป็นตั้งค่า DDNS หรือทำ scheduler เพื่อไป update ip ก็ได้ครับ (ผมใช้ n8n ทำ workflows ให้ loop fetch เรื่อย ๆ)",[10,5421,5422],{"id":5422},"ป้องกันอะไรได้บ้าง",[15,5424,5425],{},[290,5426],{"alt":74,"src":5427},"/images/2025-05-12-next-dns/images-0eae9706-37a8-4bc3-ab47-0e7f86206438.jpg",[15,5429,5430],{},"ภายใน Tab Security จะมี options ให้ตั้งค่าได้ครับว่า Profile นี้จะให้มีการป้องกันอะไรบ้าง เช่น",[1755,5432,5433,5436,5439],{},[1758,5434,5435],{},"Threat Intelligence Feeds: ป้องกัน domain ที่เป็นอันตรายเช่น Phishing, c2 server",[1758,5437,5438],{},"DNS Rebinding Protection: ป้องกัน DNS ประเภทที่ Mapping เป็น Private IP",[1758,5440,5441],{},"Typosquatting Protection: ป้องกัน domain ประเภทที่เจตนาเล่นกับการพิมพ์ผิด เช่น gooogle.com เป็นต้น (เว็บจริงคือ google.com)",[15,5443,5444],{},"และอื่น ๆ อีกตั้งค่าได้เยอะประมาณนึงเลยครับ",[15,5446,5447,5448],{},"เลือก Blacklists เองได้ เหมือนกับ Adguard & Pihole เลย\n",[290,5449],{"alt":74,"src":5450},"/images/2025-05-12-next-dns/images-1d87500f-04aa-4aaa-b6f8-d46c4f0b7da4.jpg",[15,5452,5453],{},"มี Parental Control ใช้สำหรับการสร้าง Profile เพื่อป้องกันเด็ก ๆ ที่บ้านดูในสิ่งที่ไม่ควรจะดูได้ สามารถ block การเข้าถึง application ต่าง ๆ ได้ ตั้งเวลาในการเข้าใช้งาน domain ต่าง ๆ ได้ครับ",[15,5455,5456],{},[290,5457],{"alt":74,"src":5458},"/images/2025-05-12-next-dns/images-890aa801-ef39-44e6-bfa4-d13f15d97025.jpg",[10,5460,5462],{"id":5461},"รองรับหลาย-profile","รองรับหลาย Profile",[15,5464,5465],{},[290,5466],{"alt":74,"src":5467},"/images/2025-05-12-next-dns/images-76b242e0-e379-4af6-ad5e-75eec5ac5c51.jpg",[15,5469,5470],{},"ในบางกรณีจะนำไปใช้ในการควบคุมการเข้าถึงของบุคคลอื่น ๆ เช่นผู้สูงอายุที่บ้าน, เด็ก ๆ ที่บ้าน ทาง NextDNS เองก็มีการให้ตั้งค่า Profile ได้เช่นเดียวกัน โดยที่แต่ละ Profile จะมี ID ของตัวเองและตั้งค่าแยกกันโดยอิสระ ในเครื่องที่ต้องการใช้ Profile อื่นก็ให้นำ ID ใน Tab Setup ไปใส่ได้เลยครับ",[10,5472,5474],{"id":5473},"ดู-query-logs-ได้","ดู query logs ได้",[15,5476,5477],{},"สามารถดู Logs การ query domain ต่าง ๆ ได้ครับ สิ่งจะเห็นทุก ๆ อุปกรณ์ที่ใช้ Profile ID นี้ แต่ตรงนี้สามารถปิดได้นะครับ (ปกติก็ผมจะปิดไว้)",[15,5479,5480],{},[290,5481],{"alt":74,"src":5482},"/images/2025-05-12-next-dns/images-aaeef8ce-564a-4afb-abb2-e9fdbbd29f9b.jpg",[10,5484,5486],{"id":5485},"server-มีที่ไหนบ้าง","Server มีที่ไหนบ้าง",[15,5488,5489],{},"มีหลาย Server มากครับโดยใกล้สุดคืออยู่ที่กรุงเทพ ซึ่ง Ping เพียงแค่ \"7ms\" น้อยมาก ๆ อันนี้เลยเป็นอีกเหตุผลที่ผมเลือกใช้",[15,5491,5492],{},[290,5493],{"alt":74,"src":5494},"/images/2025-05-12-next-dns/images-509d5eaf-58e3-46b7-a425-01b7b562443d.jpg",[10,5496,1737],{"id":1737},[15,5498,5499],{},"ผมใช้งานมาได้ประมาณ 1 เดือนแล้วครับ ตอนนี้ก็เอา Adgaurd ออกจาก Home Server แล้ว จากที่ใช้มายังไม่เจอเหตุการณ์ที่ Server Down แล้วทำให้ใช้งานไม่ได้เลยครับ ส่วนตัวให้ 9/10 หัก 1 คะแนนตรงที่ ถ้ามี Custom DNS record ให้ใช้จะดีเลยครับ แต่อันนี้ไม่มี",[15,5501,5502],{},"สนใจสมัครได้ที่",[15,5504,5505,5506],{},"(affiliate link): ",[27,5507,5508],{"href":5508,"rel":5509},"https://nextdns.io/?from=2ggah5wn",[31],[15,5511,5512,5513],{},"(link ปกติ): ",[27,5514,5515],{"href":5515,"rel":5516},"https://my.nextdns.io/",[31],[15,5518,5519],{},"กดสมัครผ่าน affiliate link เพื่อเป็นกำลังใจเล็ก ๆ น้อย ๆ ได้นะครับ ~",{"title":74,"searchDepth":98,"depth":98,"links":5521},[5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532],{"id":5337,"depth":98,"text":5338},{"id":5350,"depth":98,"text":5351},{"id":5372,"depth":98,"text":5373},{"id":5379,"depth":98,"text":5380},{"id":5389,"depth":98,"text":5390},{"id":5396,"depth":98,"text":5396},{"id":5422,"depth":98,"text":5422},{"id":5461,"depth":98,"text":5462},{"id":5473,"depth":98,"text":5474},{"id":5485,"depth":98,"text":5486},{"id":1737,"depth":98,"text":1737},"2025-05-12T00:00:00.000Z","การมี DNS ที่ช่วยในการ Block โฆษณาหรือการ Tracking ในยุคสมัยนี้ถือว่าเป็นสิ่งที่ควรจะมี",{},"NextDNS","/blogs/2025-05-12-next-dns",[],{"title":5327,"description":5534},{"loc":5537},"blogs/2025-05-12-next-dns",[1814],"AxQCd5onmd9TH4k-Y4t4pP2k8lbRUHexZW547MjeeKY",{"id":1818,"title":1819,"body":5545,"date":2043,"description":2044,"draft":1801,"extension":1802,"meta":5704,"navigation":344,"next":1804,"ogTitle":2046,"path":2047,"pin":1801,"prev":1804,"recommends":5705,"seo":5706,"sitemap":5707,"stem":2053,"tags":5708,"updated":1804,"__hash__":2055},{"type":7,"value":5546,"toc":5697},[5547,5551,5555,5557,5559,5573,5577,5583,5587,5589,5591,5605,5609,5615,5619,5623,5627,5629,5631,5645,5649,5651,5665,5667,5669,5671,5673,5675,5689,5693,5695],[15,5548,5549],{},[290,5550],{"alt":74,"src":1826},[15,5552,1829,5553,1833],{},[35,5554,1832],{},[10,5556,1837],{"id":1836},[15,5558,1840],{},[1755,5560,5561,5563,5569,5571],{},[1758,5562,1845],{},[1758,5564,1848,5565,5567],{},[1850,5566,1852],{},[290,5568],{"alt":74,"src":1855},[1758,5570,1858],{},[1758,5572,1861],{},[15,5574,1864,5575],{},[290,5576],{"alt":74,"src":1867},[15,5578,1870,5579,1875,5581,1879],{},[27,5580,1874],{"href":1873},[22,5582,1878],{},[15,5584,5585],{},[290,5586],{"alt":74,"src":1884},[10,5588,1887],{"id":1887},[15,5590,1890],{},[69,5592,5593],{"className":71,"code":1893,"language":73,"meta":74,"style":74},[22,5594,5595],{"__ignoreMap":74},[78,5596,5597,5599,5601,5603],{"class":80,"line":81},[78,5598,1552],{"class":84},[78,5600,1902],{"class":88},[78,5602,1905],{"class":88},[78,5604,1908],{"class":351},[15,5606,5607],{},[290,5608],{"alt":74,"src":1913},[15,5610,1916,5611,1920,5613,1924],{},[22,5612,1919],{},[22,5614,1923],{},[15,5616,5617],{},[290,5618],{"alt":74,"src":1929},[15,5620,1932,5621],{},[22,5622,1935],{},[15,5624,5625],{},[290,5626],{"alt":74,"src":1940},[15,5628,1943],{},[10,5630,1947],{"id":1946},[69,5632,5633],{"className":71,"code":1950,"language":73,"meta":74,"style":74},[22,5634,5635],{"__ignoreMap":74},[78,5636,5637,5639,5641,5643],{"class":80,"line":81},[78,5638,1552],{"class":84},[78,5640,1959],{"class":88},[78,5642,1962],{"class":88},[78,5644,1965],{"class":88},[15,5646,5647],{},[290,5648],{"alt":74,"src":1970},[15,5650,1973],{},[69,5652,5653],{"className":71,"code":1976,"language":73,"meta":74,"style":74},[22,5654,5655],{"__ignoreMap":74},[78,5656,5657,5659,5661,5663],{"class":80,"line":81},[78,5658,1552],{"class":84},[78,5660,1959],{"class":88},[78,5662,1987],{"class":88},[78,5664,1965],{"class":88},[10,5666,1993],{"id":1992},[15,5668,1996],{},[10,5670,1737],{"id":1737},[15,5672,2001],{},[15,5674,2004],{},[1755,5676,5677,5679,5681,5683,5685,5687],{},[1758,5678,2009],{},[1758,5680,2012],{},[1758,5682,2015],{},[1758,5684,2018],{},[1758,5686,2021],{},[1758,5688,2024],{},[15,5690,5691],{},[290,5692],{"alt":74,"src":2029},[15,5694,2032],{},[1780,5696,2035],{},{"title":74,"searchDepth":98,"depth":98,"links":5698},[5699,5700,5701,5702,5703],{"id":1836,"depth":98,"text":1837},{"id":1887,"depth":98,"text":1887},{"id":1946,"depth":98,"text":1947},{"id":1992,"depth":98,"text":1993},{"id":1737,"depth":98,"text":1737},{},[2049,2050],{"title":1819,"description":2044},{"loc":2047},[1814],{"id":5710,"title":5711,"body":5712,"date":6141,"description":6142,"draft":1801,"extension":1802,"meta":6143,"navigation":344,"next":1804,"ogTitle":6144,"path":6145,"pin":1801,"prev":1804,"recommends":1804,"seo":6146,"sitemap":6147,"stem":6148,"tags":6149,"updated":1804,"__hash__":6150},"content/blogs/2025-08-26-xponology-on-old-pc.md","ติดตั้ง Xpenology บน PC เครื่องเก่ากัน",{"type":7,"value":5713,"toc":6133},[5714,5718,5721,5724,5727,5731,5734,5748,5751,5756,5759,5767,5770,5773,5783,5788,5791,5796,5807,5810,5813,5826,5831,5850,5855,5858,5862,5865,5870,5873,5878,5881,5886,5889,5894,5897,5902,5905,5910,5913,5918,5921,5926,5929,5934,5937,5942,5945,5950,5953,5958,5963,5966,5971,5974,5979,5982,5987,5990,5994,6002,6007,6010,6015,6020,6025,6028,6033,6038,6043,6046,6051,6054,6059,6062,6067,6072,6075,6080,6083,6088,6093,6096,6101,6104,6109,6112,6117,6119],[10,5715,5717],{"id":5716},"xpenology-คืออะไร","Xpenology คืออะไร?",[15,5719,5720],{},"Xpenology เป็นส่วนของซอฟต์แวร์แบบ “bootloader” ที่ทำให้ระบบปฏิบัติการของ Synology ซึ่งเรียกว่า DSM (DiskStation Manager) สามารถรันบน hardware ทั่วไปได้ แทนที่จะต้องใช้เฉพาะเครื่อง NAS ของ Synology เท่านั้น",[15,5722,5723],{},"DSM เป็นระบบปฏิบัติการที่ Synology พัฒนาบน Linux ออกแบบมาเพื่อทำงานบนเครื่อง NAS มีฟีเจอร์ที่จำเป็นสำหรับการเก็บข้อมูล แชร์ไฟล์ steaming และอื่น ๆ อีกมากมาย",[15,5725,5726],{},"ซึ่งการติดตั้ง Xpenology พูดง่าย ๆ คือการที่จะทำให้เราสามารถใช้งาน Synology OS บน Hardware ทั่ว ๆ ไปได้นั่นเอง โดยในวันนี้ผมจะมาติดตั้งบน PC เครื่องเก่ากันนะครับ",[10,5728,5730],{"id":5729},"hardware-วันนี้","Hardware วันนี้",[15,5732,5733],{},"โดยเครื่องนี้จะเป็น",[1755,5735,5736,5739,5742,5745],{},[1758,5737,5738],{},"CPU: I3 7100",[1758,5740,5741],{},"Ram: 8GB DDR4",[1758,5743,5744],{},"HDD: 1TB 2 ลูก",[1758,5746,5747],{},"SSD: 256GB สำหรับติดตั้ง OS",[15,5749,5750],{},"โดย PC เครื่องนี้ผมซื้อมือ 2 มาจาก facebook marketplace โดยตอนซื้อมาจะมีแค่ CPU, RAM, HDD 1 ลูก โดยเงื่อนไขของการทำ DIY NAS ของผมคือต้องใส่ HDD อย่างน้อยได้ 2 ลูก เพื่อทำ RAID 1 (RAID Software ก็ได้)",[15,5752,5753],{},[290,5754],{"alt":74,"src":5755},"/images/2025-08-26-xponology-on-old-pc/images-e8de1f9e-b6b3-4964-9299-d404f3f70541.jpg",[15,5757,5758],{},"ปัญหาต่อมาคืออยากจะใช้ SSD เพื่อทำให้ OS ทำงานได้ไวขึ้น มี 2 ทางเลือกครับ (บอร์ดรุ่นเก่าไม่มีช่อง M.2 ให้เสียบ sadddd)",[1755,5760,5761,5764],{},[1758,5762,5763],{},"ซื้อ SSD แบบ SATA มาเสียบเพิ่ม เพราะช่อง SATA มี 4 ช่อง (ใช้ไปแล้ว 2)",[1758,5765,5766],{},"ซื้อตัวแปร PCI-E เป็น M.2 แล้วเสียบ SSD แบบ M.2",[15,5768,5769],{},"ตอนแรกทางเลือกแรกคือทางเลือกที่ดีสำหรับผม แต่แล้วเมื่อตรวจสอบสายไฟจาก POWER แล้วก็ทำให้น้ำตาไหล เพราะว่ามีสายไฟสำหรับ HDD แค่ 2 ช่อง (ใช้ครบแล้ว) เลยทำให้ต้องไปทางเลือกที่ 2 นั่นก็คือซื้อ PCE to M.2",[15,5771,5772],{},"สิ่งที่ลุ้นและขี้เกียจก็คือซื้อมาแล้ว Mainboard จะรองรับมั้ย ซึ่งตามหลัก Engineer ก็คือต้องเปิด Datasheet เพื่อหาข้อมูล แต่ไม่ทำ (5555) ซื้อเลย ได้ไม่ได้ค่อยว่ากันอีกที เพราะตั้งใจจะทำ DIY อยู่แล้ว เลยไปเจอใน shopee มาครับเป็นตัวนี้",[15,5774,5775,5778,5779],{},[35,5776,5777],{},"JEYI PCIe X4 to M.2 NVMe",": ",[27,5780,5781],{"href":5781,"rel":5782},"https://s.shopee.co.th/VwlrEE0hL",[31],[15,5784,5785],{},[290,5786],{"alt":74,"src":5787},"/images/2025-08-26-xponology-on-old-pc/images-1183b11d-997c-413e-aa3a-f0caf0a40d9d.jpg",[15,5789,5790],{},"เมื่อประกอบ SSD เข้าไปจะหน้าตาประมาณนี้ครับ",[15,5792,5793],{},[290,5794],{"alt":74,"src":5795},"/images/2025-08-26-xponology-on-old-pc/images-43be6af3-b251-4c21-86fd-37d63fa4d4aa.jpg",[1755,5797,5798,5801,5804],{},[1758,5799,5800],{},"โดย Adapter ตัวนี้จะเสียบลงไปที่ช่อง PCI-E ได้ทั้ง x4 x8 x16 เลย",[1758,5802,5803],{},"SSD ตัวนี้เป็น M.2 NVME Gen 4 ความเร็วก็คือวิ่งได้ที่ระดับ Gen 4 เลยครับ",[1758,5805,5806],{},"รองรับ mainboard ในหลาย ๆ รุ่นจากที่ลองหาข้อมูลดูนะครับ ตัวอย่างผมซื้อมาใช้กับ Asrock H11M-K รุ่นค่อนข้างเก่าเลย แต่ก็ยังใช้งานได้",[15,5808,5809],{},"ต่อไปมาเริ่มติดตั้ง OS กันครับ ~",[10,5811,5812],{"id":5812},"เตรียมการก่อนเริ่มติดตั้ง",[5234,5814,5815],{},[1758,5816,5817,5818,5822,5823],{},"download img จาก ",[27,5819,5820],{"href":5820,"rel":5821},"https://github.com/AuxXxilium/arc/releases/",[31]," โดยเลือกเป็น .img.zip นะครับ โดยในตัวอย่างผมติดตั้งเป็น version ",[1850,5824,5825],{},"2.6.5",[15,5827,5828],{},[290,5829],{"alt":74,"src":5830},"/images/2025-08-26-xponology-on-old-pc/images-d9342298-65c4-4b3d-925d-b18be0afcfdb.jpg",[5234,5832,5833,5844],{"start":98},[1758,5834,5835,5836,5840,5841],{},"สำหรับโปรแกรมที่ใช้การสร้าง bootloader ผมจะใช้เป็น ตัวนี้นะครับ สามารถโหลดได้จาก ",[27,5837,5838],{"href":5838,"rel":5839},"https://etcher.balena.io",[31]," ผมใช้ตัวนี้เป็นประจำ หากมีเครื่องมือตัวอื่นที่ถนัดก็สามารถใช้ได้เหมือนกัน",[290,5842],{"alt":74,"src":5843},"/images/2025-08-26-xponology-on-old-pc/images-be9017e4-edbe-41bc-8b98-67ae7ec9e1f2.jpg",[1758,5845,5846,5847],{},"ต้องมี Flashdrive ขนาด 4GB++ ด้วยนะครับ โดยเลือก image เป็น ARC ส่วนของ Target Drive ก็เลือกเป็น Flashdrive แล้วนั่งรอได้เลย ",[290,5848],{"alt":74,"src":5849},"/images/2025-08-26-xponology-on-old-pc/images-a68d5af0-d0c5-46e5-92f6-e5b6cb141946.jpg",[15,5851,5852],{},[290,5853],{"alt":74,"src":5854},"/images/2025-08-26-xponology-on-old-pc/images-dce47f43-fc8c-41e3-880b-ee970b3c54ce.jpg",[15,5856,5857],{},"เมื่อเสร็จแล้วสามารถนำไปเสียบกับ PC แล้วเลือกให้ Boot จาก Flashdrive ได้เลย (วิธีการขึ้นอยู่กับ Hardware โดยปกติก็เข้า bios แล้วปรับให้ boot จาก flashdrive ก่อนได้เลย)",[10,5859,5861],{"id":5860},"ติดตั้ง-xpenology","ติดตั้ง Xpenology",[15,5863,5864],{},"ของผมจะเป็น Mainboard ASUS เมื่อเข้ามา Bios ได้แล้วให้กด F8 แล้วเลือก Flashdrive",[15,5866,5867],{},[290,5868],{"alt":74,"src":5869},"/images/2025-08-26-xponology-on-old-pc/images-a9e1330c-551a-48b3-9be2-44cef6cfc9af.jpg",[15,5871,5872],{},"กด Arc Config Mode เพื่อเริ่มการตั้งค่าได้เลย (ผมเองก็ยังมือใหม่นะ ลองผิดลองถูกมาหลายรอบเหมือนกัน 5555)",[15,5874,5875],{},[290,5876],{"alt":74,"src":5877},"/images/2025-08-26-xponology-on-old-pc/images-684d99e1-c4dc-4992-929c-1f2c43c233d3.jpg",[15,5879,5880],{},"Choose Model",[15,5882,5883],{},[290,5884],{"alt":74,"src":5885},"/images/2025-08-26-xponology-on-old-pc/images-103a577d-9d02-497d-b32e-f0134880c992.jpg",[15,5887,5888],{},"ผมจะเลือกเป็น DS920+ (แนะนำให้เลือกเป็นตัวที่มี + จะทำอะไรได้เยอะกว่า)",[15,5890,5891],{},[290,5892],{"alt":74,"src":5893},"/images/2025-08-26-xponology-on-old-pc/images-4ec24e02-3a07-4e8d-9715-ade4c1fc5a13.jpg",[15,5895,5896],{},"ขั้นตอนนี้ผมจะเลือกเป็น No นะเพื่อดูการตั้งค่าเพิ่มเติม",[15,5898,5899],{},[290,5900],{"alt":74,"src":5901},"/images/2025-08-26-xponology-on-old-pc/images-50b1276f-aebd-4e3d-b2d8-c2e422ac2d65.jpg",[15,5903,5904],{},"เลือกเป็น \"Use random SN/Mac (Reduced DSM Features)\"",[15,5906,5907],{},[290,5908],{"alt":74,"src":5909},"/images/2025-08-26-xponology-on-old-pc/images-168b3668-464c-4bdc-b74a-55377d806109.jpg",[15,5911,5912],{},"ติดตั้ง Addons ผมจะเลือกเป็น default ไปเลยครับ",[15,5914,5915],{},[290,5916],{"alt":74,"src":5917},"/images/2025-08-26-xponology-on-old-pc/images-86186fc1-f851-4d02-b009-3ad9ca1bf2cb.jpg",[15,5919,5920],{},"ในส่วนของ Power option จะเลือกเป็น conservative ตามคำแนะนำเลย เพราะจะ balance ระหว่าง performance และการประหยัดไฟ แต่ถ้าใครอยากประหยัดไฟสุด ๆ ก็เลือกเป็น powersave ได้เหมือนกันนะ",[15,5922,5923],{},[290,5924],{"alt":74,"src":5925},"/images/2025-08-26-xponology-on-old-pc/images-7d83a70d-3bea-40d7-9c1a-1fcbc8417e19.jpg",[15,5927,5928],{},"ผมจะยังไม่ build ครับ จะไปแก้ไขการตั้งค่าเพิ่มเติมอีกนิด",[15,5930,5931],{},[290,5932],{"alt":74,"src":5933},"/images/2025-08-26-xponology-on-old-pc/images-adab944f-3d00-4f12-ab3c-cb24e08f2fee.jpg",[15,5935,5936],{},"จากนั้นผมจะ Format Disk ตัวหลักสำหรับลง OS ไป โดยเลือกในหัวข้อ \"Show Loader Options\" แล้วไปที่ \"Format Disks\"",[15,5938,5939],{},[290,5940],{"alt":74,"src":5941},"/images/2025-08-26-xponology-on-old-pc/images-9fa91ef7-7702-473e-afd2-9001bf83ea29.jpg",[15,5943,5944],{},"เลือก drive ที่ต้องการ format ในที่นี้ผมจะ format /dev/nvme0 นะครับ",[15,5946,5947],{},[290,5948],{"alt":74,"src":5949},"/images/2025-08-26-xponology-on-old-pc/images-ccd646d7-e3cb-4478-a9af-43162f3393c2.jpg",[15,5951,5952],{},"จากนั้นผมจะทำการ Clone bootloader จาก flashdrive ไปยัง SSD ที่ได้มีการ format ในขั้นตอนก่อนหน้า",[15,5954,5955],{},[290,5956],{"alt":74,"src":5957},"/images/2025-08-26-xponology-on-old-pc/images-972237ff-6eb8-4809-b141-86dd087ac4a2.jpg",[15,5959,5960],{},[290,5961],{"alt":74,"src":5962},"/images/2025-08-26-xponology-on-old-pc/images-a2e1e489-d07d-45c1-a477-aaf84a95f1cf.jpg",[15,5964,5965],{},"จากนั้นก็นั่งรอระบบทำการ Clone ให้ครับ",[15,5967,5968],{},[290,5969],{"alt":74,"src":5970},"/images/2025-08-26-xponology-on-old-pc/images-81d93c17-e52b-4e7c-ba52-f6a58d8a456e.jpg",[15,5972,5973],{},"เมื่อเสร็จแล้วก็ให้ถอด flashdrive ออกได้เลยย แล้วก็ reboot ได้เลยครับ",[15,5975,5976],{},[290,5977],{"alt":74,"src":5978},"/images/2025-08-26-xponology-on-old-pc/images-baf17eec-c164-4342-a35c-06d5f8d2fc14.jpg",[15,5980,5981],{},"ระบบจะ boot แล้วกลับมาหน้านี้ (อย่าลืมถอด flashdrive ก่อนนะ ไม่งั้นอาจจะยังเป็นการ boot จาก flashdrive อยู่)",[15,5983,5984],{},[290,5985],{"alt":74,"src":5986},"/images/2025-08-26-xponology-on-old-pc/images-55c9740c-54ab-4047-bf63-3d32566019f5.jpg",[15,5988,5989],{},"ถ้ายังใช้ไม่ได้ให้กด Arc Config Mode -> Build อีกทีดูนะครับ",[10,5991,5993],{"id":5992},"setup-synology","Setup Synology",[15,5995,5996,5997,6001],{},"เปิด Browser เข้าไปที่ ",[27,5998,5999],{"href":5999,"rel":6000},"http://finds.synology.com/",[31]," จากนั้นจะเจอกับ synology เครื่องใหม่ของเราครับ ตรง model name อาจจะยังไม่ตรงอยู่ไม่เป็นไร",[15,6003,6004],{},[290,6005],{"alt":74,"src":6006},"/images/2025-08-26-xponology-on-old-pc/images-f8a9ccd4-c797-4619-8fef-2f3ebcb2b754.jpg",[15,6008,6009],{},"จากนั้นก็ Next Next Next",[15,6011,6012],{},[290,6013],{"alt":74,"src":6014},"/images/2025-08-26-xponology-on-old-pc/images-53c51d89-810e-4606-8fb5-9a9764e17bd5.jpg",[15,6016,6017],{},[290,6018],{"alt":74,"src":6019},"/images/2025-08-26-xponology-on-old-pc/images-5d0ad6a6-f349-4b30-8e49-a72a2d74576c.jpg",[15,6021,6022],{},[290,6023],{"alt":74,"src":6024},"/images/2025-08-26-xponology-on-old-pc/images-466bd21f-e423-4a16-860c-f83d875cc79f.jpg",[15,6026,6027],{},"แล้วก็นั่งรอแบบ จ๊อง จ๊อง",[15,6029,6030],{},[290,6031],{"alt":74,"src":6032},"/images/2025-08-26-xponology-on-old-pc/images-fde692f4-1653-4fb8-a25d-45f786e58ea1.jpg",[15,6034,6035],{},[290,6036],{"alt":74,"src":6037},"/images/2025-08-26-xponology-on-old-pc/images-b0d91faa-8ab0-42b5-ba03-36dd055ec016.jpg",[15,6039,6040],{},[290,6041],{"alt":74,"src":6042},"/images/2025-08-26-xponology-on-old-pc/images-04e06b92-7e9c-498a-a82b-3d35afd09afa.jpg",[15,6044,6045],{},"ตรงนี้ก็ตั้ง user/password ได้เลย บาง step ผมจะข้าม ๆ ไปนะ ใช้ sense ได้เลยครับ",[15,6047,6048],{},[290,6049],{"alt":74,"src":6050},"/images/2025-08-26-xponology-on-old-pc/images-af6b4954-f9d3-4073-896d-35efcbf2acff.jpg",[15,6052,6053],{},"ต่อมาจะเป็นการตั้งค่า storage นะ",[15,6055,6056],{},[290,6057],{"alt":74,"src":6058},"/images/2025-08-26-xponology-on-old-pc/images-41946753-83ef-4480-b1c7-394b639a4540.jpg",[15,6060,6061],{},"โดยผมจะเลือกเป็น RAID 1 คือ 2 ลูก mirror กัน",[15,6063,6064],{},[290,6065],{"alt":74,"src":6066},"/images/2025-08-26-xponology-on-old-pc/images-a7392138-9df9-499e-8feb-3f4c0febc9f9.jpg",[15,6068,6069],{},[290,6070],{"alt":74,"src":6071},"/images/2025-08-26-xponology-on-old-pc/images-a7b843e5-fdf7-4fc6-905f-f385b3e7ca26.jpg",[15,6073,6074],{},"ขั้นตอนนี้จะเป็นการตรวจสอบสภาพของ HDD แนะนำให้ทำก่อนก็ดีครับ จะได้มั่นใจว่าข้อมูลจะไม่หาย",[15,6076,6077],{},[290,6078],{"alt":74,"src":6079},"/images/2025-08-26-xponology-on-old-pc/images-2df346bb-85da-430e-8746-fd2fe051f056.jpg",[15,6081,6082],{},"โดยความจุของ RAID 1 จะเป็นไปตามขนาดของ Disk ที่เล็กที่สุด ให้กดปุ่ม Max เพื่อตั้งค่าได้เลย",[15,6084,6085],{},[290,6086],{"alt":74,"src":6087},"/images/2025-08-26-xponology-on-old-pc/images-31528bb1-07aa-4938-919f-b54efcc45b23.jpg",[15,6089,6090],{},[290,6091],{"alt":74,"src":6092},"/images/2025-08-26-xponology-on-old-pc/images-f7f0e1e1-2a27-4835-91a5-3460b11d78fb.jpg",[15,6094,6095],{},"ตรงนี้แนะนำให้เปิด Disk Encryption ด้วยครับ",[15,6097,6098],{},[290,6099],{"alt":74,"src":6100},"/images/2025-08-26-xponology-on-old-pc/images-b5ee381d-566a-4e8c-9295-056fbd02ebe7.jpg",[15,6102,6103],{},"แล้วรอนั่งรอตัวระบบทำการตรวจสอบ Health ของ Disk ครับ",[15,6105,6106],{},[290,6107],{"alt":74,"src":6108},"/images/2025-08-26-xponology-on-old-pc/images-c1934bff-cdb7-47de-89ea-cfa4e381613b.jpg",[15,6110,6111],{},"เรียบร้อยแล้วครับ เพียงเท่านี้เราก็จะมี Synology ใช้บน Hardware ที่เราเลือกเองได้ ผิดถูกตรงไหนขออภัยด้วยครับ หวังว่าจะพอเป็นแนวทางให้ได้ หากต้องการใช้งานจริงจังแนะนำอ่าน Docs ของทาง Xpenology เพิ่มเติมจะได้ข้อมูลที่แม่นยำมากยิ่งขึ้นฮะ",[15,6113,6114],{},[290,6115],{"alt":74,"src":6116},"/images/2025-08-26-xponology-on-old-pc/images-12cdc78c-2d2e-4b00-9fac-a475d6b2404a.jpg",[10,6118,1753],{"id":1752},[1755,6120,6121,6127],{},[1758,6122,6123],{},[27,6124,6125],{"href":6125,"rel":6126},"https://github.com/AuxXxilium/arc",[31],[1758,6128,6129],{},[27,6130,6131],{"href":6131,"rel":6132},"https://xpenology.org/",[31],{"title":74,"searchDepth":98,"depth":98,"links":6134},[6135,6136,6137,6138,6139,6140],{"id":5716,"depth":98,"text":5717},{"id":5729,"depth":98,"text":5730},{"id":5812,"depth":98,"text":5812},{"id":5860,"depth":98,"text":5861},{"id":5992,"depth":98,"text":5993},{"id":1752,"depth":98,"text":1753},"2025-08-26T00:00:00.000Z","วิธีลง Xpenology เพื่อให้ Synology DSM รันบน PC เครื่องเก่า กลายเป็น NAS ไว้ใช้เองที่บ้าน",{},"How To Setup Xpenology","/blogs/2025-08-26-xponology-on-old-pc",{"title":5711,"description":6142},{"loc":6145},"blogs/2025-08-26-xponology-on-old-pc",[1814],"eUiaA4iK3ITfQ4vjCMiMaj8zw7Xwx63qfeD0sIUmigE",{"id":6152,"title":6153,"body":6154,"date":6255,"description":6256,"draft":1801,"extension":1802,"meta":6257,"navigation":344,"next":1804,"ogTitle":6258,"path":6259,"pin":1801,"prev":1804,"recommends":6260,"seo":6261,"sitemap":6262,"stem":6263,"tags":6264,"updated":1804,"__hash__":6265},"content/blogs/2025-09-19-chuwi-minibook-x.md","Chuwi Minibook X Linux แบบพกพาง่าย",{"type":7,"value":6155,"toc":6250},[6156,6159,6164,6167,6171,6174,6185,6188,6191,6194,6199,6204,6218,6223,6234,6241,6244,6247],[15,6157,6158],{},"เบื่อไหมกับการที่ต้องพก Notebook ขนาดใหญ่ไปไหนมาไหนแล้วมันไม่สะดวก หรือจะเป็นการที่อยากได้ Notebook ขนาดเล็ก ๆ เอาไว้พกพาไปใช้งาน (ขำ ๆ) วันนี้ผมเจอ Notebook รุ่นนึงมานำเสนอครับ นั่นก็คืออออ \"Chuwi minibook X\" นั่นเอง",[15,6160,6161],{},[290,6162],{"alt":74,"src":6163},"/images/2025-09-19-chuwi-minibook-x/preview.jpg",[15,6165,6166],{},"เอาจริง ๆ ผมเองก็เพิ่งเคยได้ยินชื่อแบรนนี้เมื่อไม่กี่วันก่อนมาจากใน Reddit ครับ ด้วยความที่กำลังมองหา Notebook ขนาดเล็ก เอามาลง Linux เพื่อใช้งานตอนที่ไม่ได้จริงจังมาก ก็เลยได้ยินคนในกลุ่มแนะนำรุ่นนี้มา เลยลองมาหาข้อมูลดูก็พบว่ามีขายในไทยด้วย !!",[10,6168,6170],{"id":6169},"spec","Spec",[15,6172,6173],{},"สิ่งที่ทำให้ผมตัดสินใจซื้อคือตัว CPU ครับ ส่วนตัวผมเองชอบ CPU ตระกูล N ของ Intel อยู่แล้ว เช่น N95, N100, N150 ซึ่งเป็น CPU ที่ประหยัดพลังงาน แต่ก็ยังคงประสิทธิภาพไว้อยู่ (ผมมี Mini PC ที่เป็น N100 รันอยู่ปีกว่าแล้วครับนิ่ง ๆ เลย) โดย Notebook รุ่นนี้ที่ผมได้มาเป็น Spec",[1755,6175,6176,6179,6182],{},[1758,6177,6178],{},"CPU: N150",[1758,6180,6181],{},"Ram: 16GB",[1758,6183,6184],{},"SSD: 512GB",[15,6186,6187],{},"จริง ๆ Spec ลองไปดูใน website ก็ได้นะครับ ในส่วนอื่น ๆ อีกอย่างที่ผมชอบคือขนาดเพียง 10\" นิด ๆ แต่มี Keyboard + Trackpad มาให้ด้วย รุ่นนี้เป็นจอ Touch Screen พลิกกลับไปเป็น Tablet ได้ด้วยนะะ",[10,6189,6190],{"id":6190},"รีวิวส่วนตัว",[15,6192,6193],{},"จากที่ผมลองใช้งานมานะครับ ผมขอสรุปเป็นข้อดีข้อเสียดังนี้",[15,6195,6196],{},[290,6197],{"alt":74,"src":6198},"/images/2025-09-19-chuwi-minibook-x/btop.jpg",[15,6200,6201],{},[35,6202,6203],{},"ข้อดี",[1755,6205,6206,6209,6212,6215],{},[1758,6207,6208],{},"ขนาดเล็ก พกพาง่าย",[1758,6210,6211],{},"CPU N150 ทำงานได้ค่อนข้างดี เหมาะกับงานที่ไม่ได้หนักมาก",[1758,6213,6214],{},"มี Keyboard และ Trackpad ซึ่งความรู้สึกในการใช้งานคือดีเลย ไม่ก๊องแก๊ง",[1758,6216,6217],{},"Port การเชื่อมต่อ Type-C ชาร์จได้ ต่อจอได้ (ผมลองเสียบกับจอ Dell ของผมที่เป็น Type C แบบ one-wire ใช้งานได้ปกติ)",[15,6219,6220],{},[35,6221,6222],{},"ข้อเสีย",[1755,6224,6225,6228,6231],{},[1758,6226,6227],{},"นิ้วเบียด พิมพ์ผิดบ่อย ด้วยความที่ keyboard ขนาดเล็กครับ ฝั่งซ้ายมันจะเล็ก (zone: Tab, Capslock, Shift) เลยทำให้จังหวะแรกที่พิมพ์ผมมักพิมพ์ผิดบ่อย ๆ",[1758,6229,6230],{},"เครื่องร้อนง่าย (แต่พอเปลี่ยนซิริโคลนแล้วดีขึ้นนะครับ)",[1758,6232,6233],{},"UI ตอน Boot จะบัค ถ้าเอามาลง Linux",[15,6235,6236,6237],{},"แต่โดยรวม ๆ ผมให้ 8/10 ครับ ตอนที่ตัดสินใจซื้อมาจริง ๆ แล้วก็ไม่ได้คาดหวังว่ามันจะออกมา perfect หรอกครับ แต่แค่นี้ผมก็รู้สึกไม่ผิดหวังแล้ว ราคาใน Shopee ปกติจะอยู่ที่ประมาณ 12XXX บาทครับ แต่ผมได้ช่วง Flash Sell + 9.9 มาเหลือประมาณ 89XX บาท (ถูกมากกก) ผมกดจากอันนี้ฮะ ",[27,6238,6239],{"href":6239,"rel":6240},"https://s.shopee.co.th/qaEfmoOmu",[31],[10,6242,6243],{"id":6243},"สุดท้าย",[15,6245,6246],{},"ความตั้งใจของผมคือเอาพกติดกระเป๋าเอาไว้ใช้งานตอนออกไปข้างนอก เผื่อต้อง remote ไปทำอะไรที่มันด่วน ๆ จะได้ทำได้เลย เพราะเครื่องนี้น้ำหนักประมาณ 1KG ครับ เบามาก ก่อนหน้านี้เคยลองซื้อ IPAD Mini 7 + Keyboard Case มา แต่ก็ไม่ไหวพกพาไม่สะดวกเลยครับ พอมาเจอตัวนี้ ง่ายขึ้นเยอะะะะ",[15,6248,6249],{},"ตัดสินใจลงเป็น Linux Ubuntu เอาไว้ ต้องมานั่ง Custom พอสมควรเลยเช่น Tablet mode, Touch Screen ใช้เวลาพอสมควรเลย แต่ก็สนุกดีครับ หาอะไรลองเล่นไปเรื่อยยยย ~",{"title":74,"searchDepth":98,"depth":98,"links":6251},[6252,6253,6254],{"id":6169,"depth":98,"text":6170},{"id":6190,"depth":98,"text":6190},{"id":6243,"depth":98,"text":6243},"2025-09-19T00:00:00.000Z","รีวิว Notebook พกพาขนาดเล็กกก เอามาเป็นเครื่องไว้ใช้งานสำหรับ Remote โดยการติดตั้ง Ubuntu Desktop",{},"Chuwi Miniboox X","/blogs/2025-09-19-chuwi-minibook-x",[2050,2049],{"title":6153,"description":6256},{"loc":6259},"blogs/2025-09-19-chuwi-minibook-x",[1814],"u_pP94b9jrUXYQ3SDuMCCkmg6epEOhOmgIeQo-V3mwM",{"id":6267,"title":6268,"body":6269,"date":6345,"description":6346,"draft":1801,"extension":1802,"meta":6347,"navigation":344,"next":1804,"ogTitle":6348,"path":6349,"pin":1801,"prev":1804,"recommends":1804,"seo":6350,"sitemap":6351,"stem":6352,"tags":6353,"updated":1804,"__hash__":6354},"content/blogs/2025-10-30-3d-printer-bambu-lab-a1.md","มือใหม่หัดเล่น 3D Printer (bambu lab a1) ปริ้นอะไรดี ?",{"type":7,"value":6270,"toc":6335},[6271,6275,6279,6287,6290,6294,6302,6305,6308,6316,6319,6323,6327],[10,6272,6274],{"id":6273},"แนะนำของที่ต้องมี-ด้วยการซื้อ","แนะนำของที่ต้องมี (ด้วยการซื้อ)",[10,6276,6278],{"id":6277},"_3d-printer-bambu-lab-a1","3D Printer Bambu Lab A1",[1755,6280,6281],{},[1758,6282,6283],{},[27,6284,6285],{"href":6285,"rel":6286},"https://s.shopee.co.th/6psUuYdq3G",[31],[15,6288,6289],{},"แนะนำตัวนี้เพราะว่า Plate มีขนาดถึง 26x26x26 เป็นขนาดที่พอเหมาะ ไม่เล็กและไม่ใหญ่จนเกินไป (ถ้าไปตัวเล็กกว่านี้จะพิมพ์อะไรได้น้อยกว่า)",[3864,6291,6293],{"id":6292},"เส้น-3d-esun-pla","เส้น 3D ESun PLA",[1755,6295,6296],{},[1758,6297,6298],{},[27,6299,6300],{"href":6300,"rel":6301},"https://s.shopee.co.th/2g2vwxaUCS",[31],[15,6303,6304],{},"ลองเปลี่ยนมาหลายรุ่นแล้ว สุดท้ายก็มาจบที่ตัวนี้ครับ (มี profile บน bambu lab ด้วยนะเอออ)",[3864,6306,6307],{"id":6307},"หัวพิมพ์สำรอง",[1755,6309,6310],{},[1758,6311,6312],{},[27,6313,6314],{"href":6314,"rel":6315},"https://s.shopee.co.th/gHrZLuAGC",[31],[15,6317,6318],{},"อันนี้ผมซื้อเอาไว้สำรองครับ เผื่อหัวหลักมีปัญหา เลยลองเอารุ่นทดแทนมาใช้ (ไม่ใช่ของแท้)",[10,6320,6322],{"id":6321},"ป้ายยา-model-3d-ที่เคยพิมพ์มา","ป้ายยา Model 3D ที่เคยพิมพ์มา",[3864,6324,6326],{"id":6325},"นกฮูกสำหรับวางแว่นสุด-cute","นกฮูกสำหรับวางแว่นสุด Cute",[1755,6328,6329],{},[1758,6330,6331],{},[27,6332,6333],{"href":6333,"rel":6334},"https://makerworld.com/en/models/580423-owl-glasses-holder",[31],{"title":74,"searchDepth":98,"depth":98,"links":6336},[6337,6338,6342],{"id":6273,"depth":98,"text":6274},{"id":6277,"depth":98,"text":6278,"children":6339},[6340,6341],{"id":6292,"depth":348,"text":6293},{"id":6307,"depth":348,"text":6307},{"id":6321,"depth":98,"text":6322,"children":6343},[6344],{"id":6325,"depth":348,"text":6326},"2025-10-30T00:00:00.000Z","รวม item ที่ควรมีและ model ที่เคยพิมพ์จริงจากการเล่น 3D Printer Bambu Lab A1",{},"Bambu Lab A1","/blogs/2025-10-30-3d-printer-bambu-lab-a1",{"title":6268,"description":6346},{"loc":6349},"blogs/2025-10-30-3d-printer-bambu-lab-a1",[1814],"-U1ObIEXRmu5LJReIqk17ZUkWHOQom57hwXN9p4O5XE",{"id":6356,"title":6357,"body":6358,"date":6435,"description":6436,"draft":1801,"extension":1802,"meta":6437,"navigation":344,"next":1804,"ogTitle":6438,"path":6439,"pin":1801,"prev":1804,"recommends":1804,"seo":6440,"sitemap":6441,"stem":6442,"tags":6443,"updated":5130,"__hash__":6444},"content/blogs/2025-11-09-nanokvm-kvm.md","รีวิว nanokvm - kvm ขนาดเล็กพกพาง่าย",{"type":7,"value":6359,"toc":6428},[6360,6367,6372,6376,6379,6382,6385,6388,6391,6394,6397,6402,6407,6410,6413,6418,6420],[15,6361,6362,6363],{},"แวะมาป้ายยา item สำหรับสาย homelab ที่มีชื่อว่า nanovkm usb ",[27,6364,6365],{"href":6365,"rel":6366},"https://wiki.sipeed.com/hardware/en/kvm/NanoKVM_USB/introduction.html",[31],[15,6368,6369],{},[290,6370],{"alt":74,"src":6371},"/images/2025-11-09-nanokvm-kvm/nano_kvm_01.jpg",[10,6373,6375],{"id":6374},"nanokvm-usb-คืออะไร","NanoKVM USB คืออะไร",[15,6377,6378],{},"โดยความสามารถของอุปกรณ์ตัวนี้คือการเชื่อมสามารถใช้ type C 1 เส้น ในการแสดงผลภาพและส่ง mouse keyboard ของ macbook ไปยังเครื่องปลายทางได้ครับ",[10,6380,6381],{"id":6381},"วิธีการต่อ",[15,6383,6384],{},"ฝั่ง Host: เสียบ type C 1 เส้นเข้ากับอุปกรณ์ เช่น notebook, macbook ได้เลย",[15,6386,6387],{},"ฝั่ง Target: เสียบ HDMI, USB เพื่อส่งภาพและ virtual mouse keyboard ไปยังเครื่องปลายทาง",[10,6389,6390],{"id":6390},"ราคาและการสั่งซื้อ",[15,6392,6393],{},"โดยอุปกรณ์นี้ผมได้มาในราคาประมาณ 1200 บาท สามารถสั่งเว็บตรงของทางผู้ผลิตได้เลย ใช้เวลาจัดส่งประมาณ 2-3 สัปดาห์ครับ หลังจากได้ที่มาชอบมากกก เพราะทำให้ตอนที่ต้อง connect minipc, server, raspberry pi ไม่จำเป็นต้องเสียบหน้าจออีกต่อไป สามารถใช้ macbook ควบคุมได้เลย",[15,6395,6396],{},"จริง ๆ ยังมีอีกหลายรุ่นนะครับ แต่ผมกดรุ่นนี้มาเพราะรูปทรง + features เหมาะกับการใช้งานของผมพอดี",[15,6398,6399],{},[290,6400],{"alt":74,"src":6401},"/images/2025-11-09-nanokvm-kvm/nano_kvm_02.jpg",[15,6403,6404],{},[290,6405],{"alt":74,"src":6406},"/images/2025-11-09-nanokvm-kvm/nano_kvm_03.jpg",[10,6408,6409],{"id":6409},"หน้าตาตอนใช้งาน",[15,6411,6412],{},"สามารถใช้งานผ่าน web browser ได้เลย",[15,6414,6415],{},[290,6416],{"alt":74,"src":6417},"/images/2025-11-09-nanokvm-kvm/nano_kvm_web.jpg",[10,6419,5097],{"id":5096},[1755,6421,6422],{},[1758,6423,6424],{},[27,6425,6427],{"href":6365,"rel":6426},[31],"NanoKVM USB - Sipeed Wiki",{"title":74,"searchDepth":98,"depth":98,"links":6429},[6430,6431,6432,6433,6434],{"id":6374,"depth":98,"text":6375},{"id":6381,"depth":98,"text":6381},{"id":6390,"depth":98,"text":6390},{"id":6409,"depth":98,"text":6409},{"id":5096,"depth":98,"text":5097},"2025-11-09T00:00:00.000Z","รีวิว NanoKVM USB ตัวเล็ก ๆ ที่ใช้ type C เส้นเดียวคุม minipc, server, raspberry pi ได้โดยไม่ต้องเสียบจอ",{},"NanoKVM","/blogs/2025-11-09-nanokvm-kvm",{"title":6357,"description":6436},{"loc":6439},"blogs/2025-11-09-nanokvm-kvm",[1814],"-I5iAm_Q9XVMxcXqJEaUp8kve-C_oSv0cDLP-yqPn08",{"id":2057,"title":2058,"body":6446,"date":3838,"description":3839,"draft":1801,"extension":1802,"meta":7898,"navigation":344,"next":1804,"ogTitle":3841,"path":3842,"pin":1801,"prev":1804,"recommends":7899,"seo":7900,"sitemap":7901,"stem":3846,"tags":7902,"updated":1804,"__hash__":3848},{"type":7,"value":6447,"toc":7885},[6448,6450,6454,6458,6460,6468,6470,6472,6474,6476,6492,6497,6499,6501,6553,6555,6567,6573,6575,6601,6603,6607,6699,6711,6713,6737,6739,6749,6751,6825,6831,6833,6837,6841,6849,6851,6853,6941,6943,6945,6957,7025,7039,7041,7077,7079,7281,7285,7333,7338,7340,7344,7346,7394,7399,7401,7405,7431,7435,7505,7510,7512,7526,7528,7532,7616,7618,7796,7804,7806,7808,7810,7844,7846,7848,7850,7854,7856,7883],[10,6449,13],{"id":12},[15,6451,2065,6452,2069],{},[22,6453,2068],{},[15,6455,2072,6456,2076],{},[35,6457,2075],{},[15,6459,2079],{},[15,6461,2082,6462,2085,6464,2089,6466,2093],{},[22,6463,51],{},[22,6465,2088],{},[22,6467,2092],{},[10,6469,2097],{"id":2096},[15,6471,2100],{},[15,6473,2103],{},[15,6475,2106],{},[69,6477,6478],{"className":71,"code":2109,"language":73,"meta":74,"style":74},[22,6479,6480],{"__ignoreMap":74},[78,6481,6482,6484,6486,6488,6490],{"class":80,"line":81},[78,6483,85],{"class":84},[78,6485,103],{"class":88},[78,6487,139],{"class":106},[78,6489,2122],{"class":88},[78,6491,2125],{"class":106},[69,6493,6495],{"className":6494,"code":2129,"language":181},[179],[22,6496,2129],{"__ignoreMap":74},[10,6498,2134],{"id":2134},[15,6500,2137],{},[69,6502,6503],{"className":71,"code":2140,"language":73,"meta":74,"style":74},[22,6504,6505,6515,6519,6533,6541,6549],{"__ignoreMap":74},[78,6506,6507,6509,6511,6513],{"class":80,"line":81},[78,6508,85],{"class":84},[78,6510,89],{"class":88},[78,6512,92],{"class":88},[78,6514,2153],{"class":88},[78,6516,6517],{"class":80,"line":98},[78,6518,345],{"emptyLinePlaceholder":344},[78,6520,6521,6523,6525,6527,6529,6531],{"class":80,"line":348},[78,6522,85],{"class":84},[78,6524,103],{"class":88},[78,6526,107],{"class":106},[78,6528,110],{"class":106},[78,6530,2170],{"class":88},[78,6532,150],{"class":149},[78,6534,6535,6537,6539],{"class":80,"line":355},[78,6536,2177],{"class":106},[78,6538,2180],{"class":88},[78,6540,150],{"class":149},[78,6542,6543,6545,6547],{"class":80,"line":385},[78,6544,2187],{"class":106},[78,6546,2190],{"class":88},[78,6548,150],{"class":149},[78,6550,6551],{"class":80,"line":412},[78,6552,2197],{"class":88},[15,6554,2200],{},[69,6556,6557],{"className":71,"code":2203,"language":73,"meta":74,"style":74},[22,6558,6559],{"__ignoreMap":74},[78,6560,6561,6563,6565],{"class":80,"line":81},[78,6562,1348],{"class":84},[78,6564,1351],{"class":106},[78,6566,2214],{"class":88},[15,6568,2217,6569,2221,6571,2225],{},[22,6570,2220],{},[22,6572,2224],{},[15,6574,2228],{},[69,6576,6577],{"className":71,"code":2231,"language":73,"meta":74,"style":74},[22,6578,6579,6587],{"__ignoreMap":74},[78,6580,6581,6583,6585],{"class":80,"line":81},[78,6582,322],{"class":84},[78,6584,325],{"class":106},[78,6586,2242],{"class":88},[78,6588,6589,6591,6593,6595,6597,6599],{"class":80,"line":98},[78,6590,2247],{"class":335},[78,6592,169],{"class":168},[78,6594,2252],{"class":88},[78,6596,927],{"class":168},[78,6598,476],{"class":161},[78,6600,2259],{"class":88},[10,6602,2263],{"id":2262},[15,6604,2266,6605,2270],{},[22,6606,2269],{},[69,6608,6609],{"className":71,"code":2273,"language":73,"meta":74,"style":74},[22,6610,6611,6623,6627,6631,6635,6639,6643,6647,6651,6655,6659,6663,6667,6671,6675,6679,6683,6687,6691,6695],{"__ignoreMap":74},[78,6612,6613,6615,6617,6619,6621],{"class":80,"line":81},[78,6614,740],{"class":84},[78,6616,476],{"class":161},[78,6618,2284],{"class":88},[78,6620,748],{"class":161},[78,6622,752],{"class":751},[78,6624,6625],{"class":80,"line":98},[78,6626,2293],{"class":88},[78,6628,6629],{"class":80,"line":348},[78,6630,2298],{"class":88},[78,6632,6633],{"class":80,"line":355},[78,6634,2303],{"class":88},[78,6636,6637],{"class":80,"line":385},[78,6638,2308],{"class":88},[78,6640,6641],{"class":80,"line":412},[78,6642,2313],{"class":88},[78,6644,6645],{"class":80,"line":417},[78,6646,2318],{"class":88},[78,6648,6649],{"class":80,"line":423},[78,6650,2323],{"class":88},[78,6652,6653],{"class":80,"line":449},[78,6654,345],{"emptyLinePlaceholder":344},[78,6656,6657],{"class":80,"line":462},[78,6658,2332],{"class":88},[78,6660,6661],{"class":80,"line":482},[78,6662,2337],{"class":88},[78,6664,6665],{"class":80,"line":513},[78,6666,2342],{"class":88},[78,6668,6669],{"class":80,"line":531},[78,6670,2323],{"class":88},[78,6672,6673],{"class":80,"line":536},[78,6674,345],{"emptyLinePlaceholder":344},[78,6676,6677],{"class":80,"line":542},[78,6678,2355],{"class":88},[78,6680,6681],{"class":80,"line":567},[78,6682,2360],{"class":88},[78,6684,6685],{"class":80,"line":579},[78,6686,2365],{"class":88},[78,6688,6689],{"class":80,"line":604},[78,6690,2370],{"class":88},[78,6692,6693],{"class":80,"line":1248},[78,6694,2323],{"class":88},[78,6696,6697],{"class":80,"line":1273},[78,6698,801],{"class":751},[15,6700,2381,6701,2384,6703,2388,6705,2392,6707,2396,6709,2399],{},[22,6702,44],{},[35,6704,2387],{},[22,6706,2391],{},[22,6708,2395],{},[22,6710,2068],{},[15,6712,2402],{},[1755,6714,6715,6723,6733],{},[1758,6716,6717,2410,6719,56,6721,2417],{},[22,6718,2409],{},[22,6720,2413],{},[22,6722,2416],{},[1758,6724,6725,2423,6727,2427,6729,2431,6731,2435],{},[22,6726,2422],{},[22,6728,2426],{},[22,6730,2430],{},[22,6732,2434],{},[1758,6734,6735,2441],{},[22,6736,2440],{},[15,6738,2444],{},[15,6740,2447,6741,2450,6743,2454,6745,2458,6747,2461],{},[22,6742,2422],{},[22,6744,2453],{},[22,6746,2457],{},[22,6748,2409],{},[15,6750,2464],{},[69,6752,6753],{"className":71,"code":2467,"language":73,"meta":74,"style":74},[22,6754,6755,6769,6777,6785,6793,6801,6807,6815,6821],{"__ignoreMap":74},[78,6756,6757,6759,6761,6763,6765,6767],{"class":80,"line":81},[78,6758,85],{"class":84},[78,6760,103],{"class":88},[78,6762,107],{"class":106},[78,6764,110],{"class":106},[78,6766,2482],{"class":88},[78,6768,150],{"class":149},[78,6770,6771,6773,6775],{"class":80,"line":98},[78,6772,2177],{"class":106},[78,6774,2180],{"class":88},[78,6776,150],{"class":149},[78,6778,6779,6781,6783],{"class":80,"line":348},[78,6780,2187],{"class":106},[78,6782,2499],{"class":88},[78,6784,150],{"class":149},[78,6786,6787,6789,6791],{"class":80,"line":355},[78,6788,865],{"class":106},[78,6790,2508],{"class":88},[78,6792,150],{"class":149},[78,6794,6795,6797,6799],{"class":80,"line":385},[78,6796,865],{"class":106},[78,6798,2517],{"class":88},[78,6800,150],{"class":149},[78,6802,6803,6805],{"class":80,"line":412},[78,6804,2524],{"class":88},[78,6806,150],{"class":149},[78,6808,6809,6811,6813],{"class":80,"line":417},[78,6810,2531],{"class":88},[78,6812,2534],{"class":106},[78,6814,150],{"class":149},[78,6816,6817,6819],{"class":80,"line":423},[78,6818,2541],{"class":106},[78,6820,150],{"class":149},[78,6822,6823],{"class":80,"line":449},[78,6824,2548],{"class":88},[15,6826,617,6827,2554,6829,2558],{},[22,6828,2553],{},[22,6830,2557],{},[10,6832,2562],{"id":2561},[15,6834,2565,6835,2569],{},[22,6836,2568],{},[15,6838,6839],{},[290,6840],{"alt":74,"src":2574},[15,6842,2577,6843,2580,6845,2583,6847,2586],{},[22,6844,2409],{},[22,6846,2422],{},[22,6848,2440],{},[10,6850,2590],{"id":2589},[15,6852,2593],{},[69,6854,6855],{"className":71,"code":2596,"language":73,"meta":74,"style":74},[22,6856,6857,6875,6923,6927],{"__ignoreMap":74},[78,6858,6859,6861,6863,6865,6867,6869,6871,6873],{"class":80,"line":81},[78,6860,2604],{"class":2603},[78,6862,2607],{"class":919},[78,6864,2610],{"class":2603},[78,6866,2613],{"class":88},[78,6868,2616],{"class":88},[78,6870,2619],{"class":88},[78,6872,2622],{"class":331},[78,6874,2625],{"class":2603},[78,6876,6877,6879,6881,6883,6885,6887,6889,6891,6893,6895,6897,6899,6901,6903,6905,6907,6909,6911,6913,6915,6917,6919,6921],{"class":80,"line":98},[78,6878,2630],{"class":335},[78,6880,403],{"class":168},[78,6882,2635],{"class":88},[78,6884,2638],{"class":149},[78,6886,2641],{"class":88},[78,6888,2638],{"class":149},[78,6890,2646],{"class":88},[78,6892,2638],{"class":149},[78,6894,2651],{"class":88},[78,6896,2638],{"class":149},[78,6898,2656],{"class":88},[78,6900,2638],{"class":149},[78,6902,2661],{"class":88},[78,6904,2638],{"class":149},[78,6906,2646],{"class":88},[78,6908,2638],{"class":149},[78,6910,2670],{"class":88},[78,6912,2673],{"class":919},[78,6914,2638],{"class":149},[78,6916,2678],{"class":88},[78,6918,473],{"class":168},[78,6920,2683],{"class":161},[78,6922,2259],{"class":88},[78,6924,6925],{"class":80,"line":348},[78,6926,2690],{"class":2603},[78,6928,6929,6931,6933,6935,6937,6939],{"class":80,"line":355},[78,6930,2247],{"class":335},[78,6932,169],{"class":168},[78,6934,2699],{"class":88},[78,6936,927],{"class":168},[78,6938,2683],{"class":161},[78,6940,2259],{"class":88},[15,6942,2708],{},[15,6944,2711],{},[69,6946,6947],{"className":71,"code":2714,"language":73,"meta":74,"style":74},[22,6948,6949],{"__ignoreMap":74},[78,6950,6951,6953,6955],{"class":80,"line":81},[78,6952,1348],{"class":84},[78,6954,1351],{"class":106},[78,6956,2725],{"class":88},[69,6958,6959],{"className":2728,"code":2729,"language":2730,"meta":74,"style":74},[22,6960,6961],{"__ignoreMap":74},[78,6962,6963,6965,6967,6969,6971,6973,6975,6977,6979,6981,6983,6985,6987,6989,6991,6993,6995,6997,6999,7001,7003,7005,7007,7009,7011,7013,7015,7017,7019,7021,7023],{"class":80,"line":81},[78,6964,2635],{"class":331},[78,6966,403],{"class":2739},[78,6968,2743],{"class":2742},[78,6970,473],{"class":2739},[78,6972,2646],{"class":331},[78,6974,403],{"class":168},[78,6976,2752],{"class":88},[78,6978,473],{"class":168},[78,6980,2656],{"class":331},[78,6982,403],{"class":2739},[78,6984,2761],{"class":2742},[78,6986,473],{"class":2739},[78,6988,2646],{"class":331},[78,6990,2768],{"class":331},[78,6992,473],{"class":168},[78,6994,2773],{"class":88},[78,6996,473],{"class":168},[78,6998,2656],{"class":331},[78,7000,403],{"class":168},[78,7002,2416],{"class":88},[78,7004,473],{"class":168},[78,7006,2656],{"class":331},[78,7008,403],{"class":168},[78,7010,2413],{"class":88},[78,7012,473],{"class":168},[78,7014,2656],{"class":331},[78,7016,403],{"class":168},[78,7018,2798],{"class":88},[78,7020,473],{"class":168},[78,7022,2803],{"class":331},[78,7024,2806],{"class":331},[15,7026,7027,56,7029,2813,7031,2816,7033,2819,7035,2822,7037,2825],{},[22,7028,2413],{},[22,7030,2416],{},[22,7032,2773],{},[22,7034,2457],{},[22,7036,2798],{},[22,7038,2413],{},[15,7040,2828],{},[69,7042,7043],{"className":71,"code":2831,"language":73,"meta":74,"style":74},[22,7044,7045,7055,7067],{"__ignoreMap":74},[78,7046,7047,7049,7051,7053],{"class":80,"line":81},[78,7048,1348],{"class":84},[78,7050,1399],{"class":106},[78,7052,2842],{"class":88},[78,7054,150],{"class":149},[78,7056,7057,7059,7061,7063,7065],{"class":80,"line":98},[78,7058,2849],{"class":106},[78,7060,169],{"class":168},[78,7062,2854],{"class":88},[78,7064,927],{"class":168},[78,7066,150],{"class":149},[78,7068,7069,7071,7073,7075],{"class":80,"line":348},[78,7070,2849],{"class":106},[78,7072,169],{"class":168},[78,7074,2867],{"class":88},[78,7076,175],{"class":168},[15,7078,2872],{},[69,7080,7081],{"className":2728,"code":2875,"language":2730,"meta":74,"style":74},[22,7082,7083],{"__ignoreMap":74},[78,7084,7085,7087,7089,7091,7093,7095,7097,7099,7101,7103,7105,7107,7109,7111,7113,7115,7117,7119,7121,7123,7125,7127,7129,7131,7133,7135,7137,7139,7141,7143,7145,7147,7149,7151,7153,7155,7157,7159,7161,7163,7165,7167,7169,7171,7173,7175,7177,7179,7181,7183,7185,7187,7189,7191,7193,7195,7197,7199,7201,7203,7205,7207,7209,7211,7213,7215,7217,7219,7221,7223,7225,7227,7229,7231,7233,7235,7237,7239,7241,7243,7245,7247,7249,7251,7253,7255,7257,7259,7261,7263,7265,7267,7269,7271,7273,7275,7277,7279],{"class":80,"line":81},[78,7086,2635],{"class":331},[78,7088,473],{"class":2739},[78,7090,2743],{"class":2742},[78,7092,473],{"class":2739},[78,7094,2646],{"class":331},[78,7096,473],{"class":168},[78,7098,2752],{"class":88},[78,7100,473],{"class":168},[78,7102,2656],{"class":331},[78,7104,473],{"class":2739},[78,7106,2761],{"class":2742},[78,7108,473],{"class":2739},[78,7110,2906],{"class":331},[78,7112,473],{"class":2739},[78,7114,2912],{"class":2911},[78,7116,473],{"class":2739},[78,7118,2646],{"class":331},[78,7120,473],{"class":168},[78,7122,2921],{"class":88},[78,7124,473],{"class":168},[78,7126,2656],{"class":331},[78,7128,473],{"class":2739},[78,7130,2930],{"class":2911},[78,7132,473],{"class":2739},[78,7134,2935],{"class":331},[78,7136,473],{"class":2739},[78,7138,2941],{"class":2940},[78,7140,473],{"class":2739},[78,7142,2906],{"class":331},[78,7144,473],{"class":2739},[78,7146,2951],{"class":2950},[78,7148,473],{"class":2739},[78,7150,2646],{"class":331},[78,7152,473],{"class":168},[78,7154,2960],{"class":88},[78,7156,473],{"class":168},[78,7158,2656],{"class":331},[78,7160,473],{"class":2739},[78,7162,2773],{"class":2950},[78,7164,473],{"class":2739},[78,7166,2646],{"class":331},[78,7168,473],{"class":168},[78,7170,2977],{"class":88},[78,7172,473],{"class":168},[78,7174,2656],{"class":331},[78,7176,473],{"class":2739},[78,7178,2416],{"class":2950},[78,7180,473],{"class":2739},[78,7182,2646],{"class":331},[78,7184,473],{"class":168},[78,7186,2994],{"class":88},[78,7188,473],{"class":168},[78,7190,2656],{"class":331},[78,7192,473],{"class":2739},[78,7194,2413],{"class":2950},[78,7196,473],{"class":2739},[78,7198,2646],{"class":331},[78,7200,473],{"class":168},[78,7202,3011],{"class":88},[78,7204,473],{"class":168},[78,7206,2656],{"class":331},[78,7208,473],{"class":2739},[78,7210,2798],{"class":2950},[78,7212,473],{"class":2739},[78,7214,2646],{"class":331},[78,7216,473],{"class":168},[78,7218,3011],{"class":88},[78,7220,473],{"class":168},[78,7222,3032],{"class":331},[78,7224,473],{"class":2739},[78,7226,3037],{"class":2940},[78,7228,473],{"class":2739},[78,7230,3042],{"class":331},[78,7232,473],{"class":168},[78,7234,3047],{"class":88},[78,7236,473],{"class":168},[78,7238,2656],{"class":331},[78,7240,473],{"class":168},[78,7242,2635],{"class":88},[78,7244,2638],{"class":149},[78,7246,2641],{"class":88},[78,7248,2638],{"class":149},[78,7250,2646],{"class":88},[78,7252,2638],{"class":149},[78,7254,2960],{"class":88},[78,7256,2638],{"class":149},[78,7258,2656],{"class":88},[78,7260,2638],{"class":149},[78,7262,2661],{"class":88},[78,7264,2638],{"class":149},[78,7266,2646],{"class":88},[78,7268,2638],{"class":149},[78,7270,3084],{"class":88},[78,7272,2638],{"class":149},[78,7274,2678],{"class":88},[78,7276,473],{"class":168},[78,7278,3093],{"class":331},[78,7280,3097],{"class":3096},[15,7282,3100,7283,3103],{},[22,7284,51],{},[69,7286,7287],{"className":71,"code":3106,"language":73,"meta":74,"style":74},[22,7288,7289,7299,7311],{"__ignoreMap":74},[78,7290,7291,7293,7295,7297],{"class":80,"line":81},[78,7292,1348],{"class":84},[78,7294,1399],{"class":106},[78,7296,2842],{"class":88},[78,7298,150],{"class":149},[78,7300,7301,7303,7305,7307,7309],{"class":80,"line":98},[78,7302,2849],{"class":106},[78,7304,169],{"class":168},[78,7306,2854],{"class":88},[78,7308,927],{"class":168},[78,7310,150],{"class":149},[78,7312,7313,7315,7317,7319,7321,7323,7325,7327,7329,7331],{"class":80,"line":348},[78,7314,2849],{"class":106},[78,7316,169],{"class":168},[78,7318,2867],{"class":88},[78,7320,927],{"class":168},[78,7322,162],{"class":161},[78,7324,1364],{"class":84},[78,7326,1367],{"class":106},[78,7328,169],{"class":168},[78,7330,3151],{"class":88},[78,7332,175],{"class":168},[69,7334,7336],{"className":7335,"code":3157,"language":181},[179],[22,7337,3157],{"__ignoreMap":74},[15,7339,3162],{},[15,7341,3165,7342,3168],{},[22,7343,2951],{},[15,7345,3171],{},[69,7347,7348],{"className":71,"code":3174,"language":73,"meta":74,"style":74},[22,7349,7350,7360,7372],{"__ignoreMap":74},[78,7351,7352,7354,7356,7358],{"class":80,"line":81},[78,7353,1348],{"class":84},[78,7355,1399],{"class":106},[78,7357,2842],{"class":88},[78,7359,150],{"class":149},[78,7361,7362,7364,7366,7368,7370],{"class":80,"line":98},[78,7363,2849],{"class":106},[78,7365,169],{"class":168},[78,7367,3195],{"class":88},[78,7369,927],{"class":168},[78,7371,150],{"class":149},[78,7373,7374,7376,7378,7380,7382,7384,7386,7388,7390,7392],{"class":80,"line":348},[78,7375,2849],{"class":106},[78,7377,169],{"class":168},[78,7379,2867],{"class":88},[78,7381,927],{"class":168},[78,7383,162],{"class":161},[78,7385,1364],{"class":84},[78,7387,1367],{"class":106},[78,7389,169],{"class":168},[78,7391,3151],{"class":88},[78,7393,175],{"class":168},[69,7395,7397],{"className":7396,"code":3225,"language":181},[179],[22,7398,3225],{"__ignoreMap":74},[10,7400,3231],{"id":3230},[15,7402,3234,7403,3238],{},[22,7404,3237],{},[69,7406,7407],{"className":71,"code":3241,"language":73,"meta":74,"style":74},[22,7408,7409,7423],{"__ignoreMap":74},[78,7410,7411,7413,7415,7417,7419,7421],{"class":80,"line":81},[78,7412,85],{"class":84},[78,7414,103],{"class":88},[78,7416,139],{"class":106},[78,7418,3254],{"class":106},[78,7420,3257],{"class":88},[78,7422,150],{"class":149},[78,7424,7425,7427,7429],{"class":80,"line":98},[78,7426,2524],{"class":88},[78,7428,3266],{"class":88},[78,7430,3269],{"class":88},[15,7432,3272,7433,3276],{},[22,7434,3275],{},[69,7436,7437],{"className":71,"code":3279,"language":73,"meta":74,"style":74},[22,7438,7439,7451,7455,7459,7463,7467,7471,7475,7479,7483,7497],{"__ignoreMap":74},[78,7440,7441,7443,7445,7447,7449],{"class":80,"line":81},[78,7442,740],{"class":84},[78,7444,476],{"class":161},[78,7446,3290],{"class":88},[78,7448,748],{"class":161},[78,7450,752],{"class":751},[78,7452,7453],{"class":80,"line":98},[78,7454,2355],{"class":88},[78,7456,7457],{"class":80,"line":348},[78,7458,2360],{"class":88},[78,7460,7461],{"class":80,"line":355},[78,7462,2365],{"class":88},[78,7464,7465],{"class":80,"line":385},[78,7466,2370],{"class":88},[78,7468,7469],{"class":80,"line":412},[78,7470,3315],{"class":88},[78,7472,7473],{"class":80,"line":417},[78,7474,2323],{"class":88},[78,7476,7477],{"class":80,"line":423},[78,7478,801],{"class":751},[78,7480,7481],{"class":80,"line":449},[78,7482,345],{"emptyLinePlaceholder":344},[78,7484,7485,7487,7489,7491,7493,7495],{"class":80,"line":462},[78,7486,85],{"class":84},[78,7488,103],{"class":88},[78,7490,139],{"class":106},[78,7492,3254],{"class":106},[78,7494,3340],{"class":88},[78,7496,150],{"class":149},[78,7498,7499,7501,7503],{"class":80,"line":482},[78,7500,2524],{"class":88},[78,7502,3266],{"class":88},[78,7504,3351],{"class":88},[69,7506,7508],{"className":7507,"code":3355,"language":181},[179],[22,7509,3355],{"__ignoreMap":74},[15,7511,3360],{},[15,7513,3363,7514,3367,7516,3370,7518,3374,7520,3378,7522,3381,7524,3384],{},[22,7515,3366],{},[22,7517,3366],{},[22,7519,3373],{},[22,7521,3377],{},[22,7523,3366],{},[22,7525,3237],{},[10,7527,3388],{"id":3387},[15,7529,3391,7530,3395],{},[22,7531,3394],{},[69,7533,7534],{"className":71,"code":3398,"language":73,"meta":74,"style":74},[22,7535,7536,7548,7552,7556,7560,7564,7568,7572,7576,7580,7584,7588,7592,7606],{"__ignoreMap":74},[78,7537,7538,7540,7542,7544,7546],{"class":80,"line":81},[78,7539,740],{"class":84},[78,7541,476],{"class":161},[78,7543,3409],{"class":88},[78,7545,748],{"class":161},[78,7547,752],{"class":751},[78,7549,7550],{"class":80,"line":98},[78,7551,3418],{"class":88},[78,7553,7554],{"class":80,"line":348},[78,7555,3423],{"class":88},[78,7557,7558],{"class":80,"line":355},[78,7559,1182],{"class":88},[78,7561,7562],{"class":80,"line":385},[78,7563,3432],{"class":88},[78,7565,7566],{"class":80,"line":412},[78,7567,1197],{"class":88},[78,7569,7570],{"class":80,"line":417},[78,7571,3441],{"class":88},[78,7573,7574],{"class":80,"line":423},[78,7575,3446],{"class":88},[78,7577,7578],{"class":80,"line":449},[78,7579,3451],{"class":88},[78,7581,7582],{"class":80,"line":462},[78,7583,3456],{"class":88},[78,7585,7586],{"class":80,"line":482},[78,7587,801],{"class":751},[78,7589,7590],{"class":80,"line":513},[78,7591,345],{"emptyLinePlaceholder":344},[78,7593,7594,7596,7598,7600,7602,7604],{"class":80,"line":531},[78,7595,85],{"class":84},[78,7597,103],{"class":88},[78,7599,139],{"class":106},[78,7601,3254],{"class":106},[78,7603,3477],{"class":88},[78,7605,150],{"class":149},[78,7607,7608,7610,7612,7614],{"class":80,"line":536},[78,7609,2524],{"class":88},[78,7611,3486],{"class":88},[78,7613,3489],{"class":106},[78,7615,3492],{"class":88},[15,7617,3495],{},[69,7619,7620],{"className":3498,"code":3499,"language":3500,"meta":74,"style":74},[22,7621,7622,7642,7652,7664,7676,7690,7696,7704,7708,7714,7722,7726,7738,7742,7746,7762,7768,7780,7784,7792],{"__ignoreMap":74},[78,7623,7624,7626,7628,7630,7632,7634,7636,7638,7640],{"class":80,"line":81},[78,7625,3508],{"class":3507},[78,7627,3511],{"class":161},[78,7629,3514],{"class":919},[78,7631,3511],{"class":161},[78,7633,3519],{"class":919},[78,7635,403],{"class":168},[78,7637,3524],{"class":88},[78,7639,473],{"class":168},[78,7641,3529],{"class":331},[78,7643,7644,7646,7648,7650],{"class":80,"line":98},[78,7645,3534],{"class":919},[78,7647,3537],{"class":923},[78,7649,2768],{"class":3507},[78,7651,3542],{"class":331},[78,7653,7654,7656,7658,7660,7662],{"class":80,"line":348},[78,7655,3547],{"class":3507},[78,7657,473],{"class":168},[78,7659,3552],{"class":88},[78,7661,473],{"class":168},[78,7663,3557],{"class":3507},[78,7665,7666,7668,7670,7672,7674],{"class":80,"line":355},[78,7667,3562],{"class":3507},[78,7669,473],{"class":168},[78,7671,3567],{"class":88},[78,7673,473],{"class":168},[78,7675,3557],{"class":3507},[78,7677,7678,7680,7682,7684,7686,7688],{"class":80,"line":385},[78,7679,3576],{"class":919},[78,7681,3579],{"class":923},[78,7683,403],{"class":168},[78,7685,3584],{"class":88},[78,7687,473],{"class":168},[78,7689,3557],{"class":3507},[78,7691,7692,7694],{"class":80,"line":412},[78,7693,3593],{"class":331},[78,7695,3596],{"class":3507},[78,7697,7698,7700,7702],{"class":80,"line":417},[78,7699,3601],{"class":919},[78,7701,3537],{"class":923},[78,7703,3606],{"class":3507},[78,7705,7706],{"class":80,"line":423},[78,7707,345],{"emptyLinePlaceholder":344},[78,7709,7710,7712],{"class":80,"line":449},[78,7711,3615],{"class":3507},[78,7713,3542],{"class":331},[78,7715,7716,7718,7720],{"class":80,"line":462},[78,7717,3622],{"class":919},[78,7719,3537],{"class":923},[78,7721,3628],{"class":3627},[78,7723,7724],{"class":80,"line":482},[78,7725,3633],{"class":331},[78,7727,7728,7730,7732,7734,7736],{"class":80,"line":513},[78,7729,3638],{"class":919},[78,7731,3537],{"class":923},[78,7733,403],{"class":168},[78,7735,3645],{"class":88},[78,7737,409],{"class":168},[78,7739,7740],{"class":80,"line":531},[78,7741,2323],{"class":331},[78,7743,7744],{"class":80,"line":536},[78,7745,345],{"emptyLinePlaceholder":344},[78,7747,7748,7750,7752,7754,7756,7758,7760],{"class":80,"line":542},[78,7749,3508],{"class":3507},[78,7751,3511],{"class":161},[78,7753,3664],{"class":919},[78,7755,403],{"class":168},[78,7757,3669],{"class":88},[78,7759,473],{"class":168},[78,7761,3529],{"class":331},[78,7763,7764,7766],{"class":80,"line":567},[78,7765,3678],{"class":3507},[78,7767,3542],{"class":331},[78,7769,7770,7772,7774,7776,7778],{"class":80,"line":579},[78,7771,3685],{"class":919},[78,7773,3537],{"class":923},[78,7775,403],{"class":168},[78,7777,3692],{"class":88},[78,7779,409],{"class":168},[78,7781,7782],{"class":80,"line":604},[78,7783,3633],{"class":331},[78,7785,7786,7788,7790],{"class":80,"line":1248},[78,7787,3703],{"class":919},[78,7789,3537],{"class":923},[78,7791,3708],{"class":331},[78,7793,7794],{"class":80,"line":1273},[78,7795,2323],{"class":331},[15,7797,3715,7798,3718,7800,3721,7802,3724],{},[22,7799,2453],{},[22,7801,2422],{},[22,7803,2409],{},[15,7805,3727],{},[10,7807,1669],{"id":1669},[15,7809,1672],{},[69,7811,7812],{"className":71,"code":3734,"language":73,"meta":74,"style":74},[22,7813,7814,7826,7836],{"__ignoreMap":74},[78,7815,7816,7818,7820,7822,7824],{"class":80,"line":81},[78,7817,85],{"class":84},[78,7819,251],{"class":88},[78,7821,254],{"class":106},[78,7823,2482],{"class":88},[78,7825,3749],{"class":88},[78,7827,7828,7830,7832,7834],{"class":80,"line":98},[78,7829,85],{"class":84},[78,7831,89],{"class":88},[78,7833,251],{"class":88},[78,7835,2153],{"class":88},[78,7837,7838,7840,7842],{"class":80,"line":348},[78,7839,1720],{"class":84},[78,7841,1723],{"class":106},[78,7843,3768],{"class":88},[10,7845,1737],{"id":1737},[15,7847,3773],{},[15,7849,3776],{},[15,7851,3779,7852,3782],{},[22,7853,3394],{},[10,7855,1753],{"id":1752},[1755,7857,7858,7863,7868,7873,7878],{},[1758,7859,7860],{},[27,7861,3793],{"href":3791,"rel":7862},[31],[1758,7864,7865],{},[27,7866,3800],{"href":3798,"rel":7867},[31],[1758,7869,7870],{},[27,7871,3807],{"href":3805,"rel":7872},[31],[1758,7874,7875],{},[27,7876,3814],{"href":3812,"rel":7877},[31],[1758,7879,7880],{},[27,7881,3821],{"href":3819,"rel":7882},[31],[1780,7884,3824],{},{"title":74,"searchDepth":98,"depth":98,"links":7886},[7887,7888,7889,7890,7891,7892,7893,7894,7895,7896,7897],{"id":12,"depth":98,"text":13},{"id":2096,"depth":98,"text":2097},{"id":2134,"depth":98,"text":2134},{"id":2262,"depth":98,"text":2263},{"id":2561,"depth":98,"text":2562},{"id":2589,"depth":98,"text":2590},{"id":3230,"depth":98,"text":3231},{"id":3387,"depth":98,"text":3388},{"id":1669,"depth":98,"text":1669},{"id":1737,"depth":98,"text":1737},{"id":1752,"depth":98,"text":1753},{},[1808],{"title":2058,"description":3839},{"loc":3842},[1814],1786290982404]