<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Pickyzz</title><description>Just a pieces of code</description><link>https://pickyzz.dev/</link><item><title>2025 and me</title><link>https://pickyzz.dev/blog/2025-and-me</link><guid isPermaLink="true">https://pickyzz.dev/blog/2025-and-me</guid><description>ปี 2025 สอนให้รู้ว่า &quot;การปล่อยบางอย่างไป เพื่อรักษาและต่อยอดสิ่งที่สำคัญกว่า&quot; คือกุญแจสำคัญของความสุขในปีนี้ครับ</description><pubDate>Sun, 28 Dec 2025 19:50:00 GMT</pubDate><content:encoded># ปีแห่งการเลิกฝืน และกลับมาโฟกัสสิ่งที่ทำได้ดี


ปี 2025 สำหรับผมไม่ใช่ปีแห่งการพุ่งชนทุกโอกาส แต่เป็นปีแห่งการ **&quot;คัดกรอง&quot;** ครับ หลังจากลองผิดลองถูกมาสักพัก ผมตัดสินใจหยุดทำในสิ่งที่ไม่ถนัด แม้มันจะเคยดูท้าทายและสนุกในช่วงแรก แต่ความจริงที่ได้เรียนรู้คือ เราไม่ได้มีเวลาเหลือเฟือที่จะวิ่งไล่ตามทุกอย่าง การหันกลับมาพัฒนาสิ่งที่ทำได้ดีอยู่แล้วให้ดียิ่งขึ้นไปอีก กลายเป็นบทเรียนที่สำคัญที่สุดของปีนี้


## **Career &amp; Lifestyle**


ปีนี้มีการเปลี่ยนแปลงใหญ่เรื่องงาน ผมได้รับโอกาสเริ่มงานประจำที่ใหม่ ซึ่งลงตัวกับจังหวะชีวิตพอดี และด้วยความที่อยากดูแลตัวเองให้ดีขึ้น ผมเริ่มหันมาทำ IF และคุมอาหารอย่างจริงจัง ผลที่ได้ไม่ใช่แค่ตัวเลขบนตาชั่ง แต่คือระเบียบวินัยในตัวเองที่ชัดเจนขึ้นครับ


## **Gear &amp; Setup**


ในส่วนของ Gadget และงานอดิเรก ปีนี้มีการอัปเกรดพอสมควร

- **Work &amp; Play Space:** ผมตัดสินใจแยกพื้นที่ทำงานและพื้นที่พักผ่อนออกจากกันอย่างเด็ดขาด เพื่อให้การโฟกัสในแต่ละส่วนทำได้ดีที่สุด
- **PC &amp; Monitor:** ในที่สุดก็ได้ฤกษ์เปลี่ยนจอใหม่สักที (หลังจากโดนทักมานาน!) พร้อมกับอัปเกรด PC ครั้งใหญ่สำหรับความบันเทิง ชุดนี้น่าจะอยู่กันไปยาวๆ อีกหลายปีครับ
- **Mobile:** ในที่สุดผมก็เปลี่ยนมือถือใหม่ในรอบหลายปีแล้ว แต่ก็ยังใช้ Android เหมือนเดิม เปลี่ยนเป็น S25 ตัวปกติ เพราะเนื่องจากชอบมือถือที่ไซส์ประมาณนี้มากกว่าจะไปไซส์ใหญ่ๆ เพราะชอบพกใส่กระเป๋ากางเกงได้สะดวก ๆ มากกว่า
- **Motorcycle:** สมาชิกใหม่คือ **Suzuki V-Strom 800DE** รถสาย Adventure ที่จะพาผมไปได้ไกลขึ้นกว่าเดิมในวันที่ต้องการพักผ่อน (แต่เจ้า Monkey คันเก่งก็ยังอยู่นะครับ ไม่ได้หายไปไหน)

โดยรวมแล้ว ปี 2025 สำหรับผมคือปีแห่งการ **&quot;จัดระเบียบ&quot;** และ **&quot;เตรียมความพร้อม&quot;** ครับ ทั้งการจัดวาง Mindset ใหม่ที่เลือกโฟกัสเฉพาะสิ่งที่ถนัด การดูแลสุขภาพให้แข็งแรงขึ้น ไปจนถึงการอัปเกรดเครื่องมือและพื้นที่ทำงานให้ลงตัวที่สุด การเปลี่ยนแปลงทั้งหมดนี้ไม่ได้เป็นเพียงแค่การซื้อของใหม่หรือการย้ายงาน แต่มันคือการสร้างรากฐานที่มั่นใจเพื่อให้ตัวเองก้าวไปข้างหน้าได้อย่างมั่นคง


สำหรับปี 2026 ที่กำลังจะมาถึง ผมตั้งเป้าว่าจะใช้พื้นฐานที่เตรียมไว้ในปีนี้ ออกไปหาประสบการณ์ใหม่ๆ ให้มากขึ้น โดยเฉพาะการหาเวลาพา V-Strom 800DE คู่ใจออกไปแตะขอบฟ้าในเส้นทางที่ไกลกว่าเดิม และรักษาวินัยในการใช้ชีวิตให้สมดุลแบบนี้ต่อไป ขอบคุณทุกคนที่ติดตามอ่านเรื่องราวการเดินทางของผมในปีนี้ แล้วพบกันใหม่ปีหน้า ขอให้เป็นปีที่ยอดเยี่ยมสำหรับทุกคนเช่นกันครับ


&lt;figure class=&quot;notion-video&quot;&gt;
  &lt;iframe width=&quot;100%&quot; height=&quot;480&quot; src=&quot;https://www.youtube.com/embed/L051YSpEEYU&quot;
    title=&quot;YouTube video player&quot; frameborder=&quot;0&quot;
    allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
    allowfullscreen&gt;&lt;/iframe&gt;
  
&lt;/figure&gt;</content:encoded></item><item><title>ตรวจสอบอัพเดต Notion ด้วย Cloudflare Workers</title><link>https://pickyzz.dev/blog/cloudflare-workers-notion-monitor</link><guid isPermaLink="true">https://pickyzz.dev/blog/cloudflare-workers-notion-monitor</guid><description>เปลี่ยนวิธีการ monitor Notion database เพื่อสั่ง build เว็บจาก GitHub Actions มาเป็น Cloudflare Workers เพื่อความรวดเร็วและประหยัดทรัพยากร</description><pubDate>Wed, 24 Dec 2025 10:20:00 GMT</pubDate><content:encoded>## เกริ่นก่อน


หลังจากที่ผมได้เขียนถึงการ [รื้อ Stack หลังบ้าน](/blog/web-stack-update-2024/) โดยเปลี่ยนมาใช้ Notion เป็น CMS และสั่ง Build เว็บไซต์ผ่าน GitHub Actions ก็พบว่ามันทำงานได้ดีครับ แต่ก็ยังมีจุดที่รู้สึกว่า “น่าจะดีกว่านี้ได้”


ปัญหานึงคือ **GitHub Actions** มักจะมี delay ในการทำงาน และการรัน Cron Job ทุกๆ ชั่วโมงก็อาจจะไม่ทันใจ (หรือถ้าถี่กว่านั้นก็เปลือง resource โดยใช่เหตุ) ประกอบกับช่วงหลังเห็น **Cloudflare Workers** มาแรง ด้วย Free Tier ที่ใจป้ำมาก (100,000 requests/day!) เลยเกิดไอเดียว่า ทำไมเราไม่ย้าย logic การเช็คอัพเดตมาไว้ที่ Edge ซะเลยล่ะ?


## ทำไมต้อง Cloudflare Workers?

1. **ฟรีและเยอะ**: ให้ quota มาเหลือเฟือสำหรับการเช็คอัพเดต Notion ทุก 15 นาที
2. **Cron Triggers**: ตั้งเวลาทำงานได้ง่ายๆ ผ่าน `wrangler.toml` ไม่ต้องพึ่ง external cron
3. **Edge Network**: ทำงานเร็ว ไม่ต้อง spin up container นานเหมือน CI/CD
4. **Integration**: ทำงานร่วมกับ Upstash Redis (ที่ใช้อยู่แล้ว) ได้เนียนๆ

## Architecture


คอนเซปต์ยังคงเดิม คือ “เช็คว่ามีอะไรเปลี่ยนแปลงไหม? ถ้ามี ก็สั่ง Build” แต่เปลี่ยนคนทำงานจาก GitHub Runner มาเป็น Worker ครับ


```plain text
+-------------------+       (1) Check         +------------------+
| Cloudflare Worker | ----------------------&gt; |    Notion API    |
| (Cron every 15m)  | &lt;---------------------- | (Last Edited Time)|
+-------------------+       Result            +------------------+
          |
          | (2) Compare
          v
+-------------------+       If New Update     +------------------+
|   Upstash Redis   | ----------------------&gt; |      Vercel      |
|  (Last Checked)   |      Trigger Build      |   (Deploy Hook)  |
+-------------------+                         +------------------+
```


## ลงมือทำ


### 1. Setup โปรเจ็กต์


เริ่มจากสร้าง directory ใหม่ และ init wrangler ครับ (ผมใช้ `bun` เป็นหลักนะช่วงนี้)


```bash
mkdir notion-monitor-worker
cd notion-monitor-worker
bun init
bun add -d wrangler
```


### 2. Config `wrangler.toml`


หัวใจสำคัญอยู่ที่ `[triggers]` ครับ เราตั้งให้มันทำงานทุกๆ 15 นาที


```toml
name = &quot;notion-monitor-worker&quot;
main = &quot;src/index.js&quot;
compatibility_date = &quot;2024-12-24&quot;

# Cron Triggers - every 15 minutes
[triggers]
crons = [&quot;*/15 * * * *&quot;]

# Environment variables จะถูก set ผ่าน wrangler secret
```


### 3. เขียน Code (`src/index.js`)


Logic ไม่มีอะไรซับซ้อน:
1. ดึงเวลา `last_edited_time` จาก Notion Database
2. เทียบกับค่าล่าสุดที่เก็บไว้ใน Redis
3. ถ้า **ใหม่กว่า** -&gt; ยิง Webhook ไปหา Vercel ให้ Rebuild -&gt; อัพเดต Redis
4. ถ้า **เท่าเดิม** -&gt; จบงาน แยกย้าย


```javascript
// src/index.js

const REDIS_KEY = &apos;notion:last_checked_time&apos;;

async function getRedisValue(env) {
  const url = `${env.UPSTASH_REDIS_REST_URL}/get/${REDIS_KEY}`;
  
  try {
    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${env.UPSTASH_REDIS_REST_TOKEN}`,
      },
    });

    const data = await response.json();
    if (data.result === null) return null;
    
    let result = data.result;
    if (typeof result === &apos;string&apos; &amp;&amp; result.startsWith(&apos;&quot;&apos;) &amp;&amp; result.endsWith(&apos;&quot;&apos;)) {
      result = result.slice(1, -1);
    }
    
    return result;
  } catch (error) {
    console.error(&apos;Error fetching from Redis:&apos;, error.message);
    throw new Error(&apos;Failed to connect to Redis.&apos;);
  }
}

async function setRedisValue(env, timestamp) {
  const url = `${env.UPSTASH_REDIS_REST_URL}/set/${REDIS_KEY}/${encodeURIComponent(timestamp)}`;
  
  try {
    const response = await fetch(url, {
      method: &apos;POST&apos;,
      headers: {
        Authorization: `Bearer ${env.UPSTASH_REDIS_REST_TOKEN}`,
      },
    });

    const data = await response.json();
    if (data.result !== &apos;OK&apos;) {
      throw new Error(`Redis SET failed: ${JSON.stringify(data)}`);
    }
  } catch (error) {
    console.error(&apos;Error setting Redis:&apos;, error.message);
    throw new Error(&apos;Failed to set value to Redis.&apos;);
  }
}

async function getNotionLastEdited(env) {
  const url = `https://api.notion.com/v1/databases/${env.DATABASE_ID}/query`;
  
  try {
    const response = await fetch(url, {
      method: &apos;POST&apos;,
      headers: {
        &apos;Authorization&apos;: `Bearer ${env.NOTION_KEY}`,
        &apos;Notion-Version&apos;: &apos;2022-06-28&apos;,
        &apos;Content-Type&apos;: &apos;application/json&apos;,
      },
      body: JSON.stringify({
        page_size: 1,
        sorts: [
          {
            timestamp: &apos;last_edited_time&apos;,
            direction: &apos;descending&apos;,
          },
        ],
      }),
    });

    if (!response.ok) throw new Error(`Notion API error: ${response.status}`);

    const data = await response.json();
    return data.results.length &gt; 0 ? data.results[0].last_edited_time : null;
  } catch (error) {
    console.error(&apos;Error fetching from Notion:&apos;, error.message);
    throw error;
  }
}

async function triggerVercelDeploy(env) {
  try {
    const response = await fetch(env.VERCEL_DEPLOYMENT_HOOK, {
      method: &apos;POST&apos;,
      headers: {
        &apos;Content-Type&apos;: &apos;application/json&apos;,
        &apos;User-Agent&apos;: &apos;Cloudflare-Worker-Notion-Monitor&apos;,
      },
    });

    if (!response.ok) throw new Error(`Deploy hook failed: ${response.status}`);
    return true;
  } catch (error) {
    console.error(&apos;Error triggering deploy:&apos;, error.message);
    throw error;
  }
}

async function checkAndDeploy(env) {
  const lastEdited = await getNotionLastEdited(env);
  const lastChecked = await getRedisValue(env);
  
  let needsDeploy = false;
  
  if (!lastChecked) {
    needsDeploy = true;
  } else {
    const lastEditedDate = new Date(lastEdited);
    const lastCheckedDate = new Date(lastChecked);
    if (lastEditedDate &gt; lastCheckedDate) needsDeploy = true;
  }
  
  if (needsDeploy) {
    await triggerVercelDeploy(env);
    await setRedisValue(env, lastEdited);
    return { deployed: true, lastEdited };
  }
  
  return { deployed: false, lastEdited };
}

export default {
  // Scheduled handler (Cron Trigger)
  async scheduled(event, env, ctx) {
    try {
      await checkAndDeploy(env);
    } catch (error) {
      console.error(&apos;❌ Error during scheduled check:&apos;, error);
    }
  },

  // Fetch handler (Manual Trigger via POST)
  async fetch(request, env, ctx) {
    if (request.method !== &apos;POST&apos;) {
      return new Response(JSON.stringify({ message: &apos;Use POST to trigger check&apos; }), {
        status: 200,
        headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
      });
    }

    try {
      const result = await checkAndDeploy(env);
      return new Response(JSON.stringify({ success: true, ...result }), {
        status: 200,
        headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
      });
    } catch (error) {
      return new Response(JSON.stringify({ success: false, error: error.message }), {
        status: 500,
        headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
      });
    }
  },
};
```

&gt; Code เต็มๆ ผมใส่ function ย่อยไว้จัดการ fetch/error handling ด้วยนะครับ เพื่อความสะอาด

### 4. Deploy &amp; Secrets


จัดการ set secret keys ต่างๆ ให้ครบ (ปลอดภัยกว่าใส่ใน code นะ)


```bash
bun wrangler secret put NOTION_KEY
bun wrangler secret put DATABASE_ID
bun wrangler secret put UPSTASH_REDIS_REST_URL
bun wrangler secret put UPSTASH_REDIS_REST_TOKEN
bun wrangler secret put VERCEL_DEPLOYMENT_HOOK
```


แล้วก็ Deploy โลด!


```bash
bun wrangler deploy
```


## ปัญหาที่เจอ และวิธีแก้


ระหว่างทำก็เจอปัญหาจุดนึงครับ คือค่า **Timestamp** ที่ได้จาก Upstash Redis นั้นดันมี **Quotes** แถมมาด้วย (เช่น `&quot;\&quot;2025-12-24...\&quot;&quot;`) ทำให้เวลาเอาไป `new Date()` แล้วได้ค่าเป็น `Invalid Date` ส่งผลให้ Script ของเราเข้าใจผิดว่าไม่มีการอัพเดตตลอดเวลา


**วิธีแก้:** ผมเพิ่ม logic ในการเช็คและตัด Quotes ออกทั้งตอน Get และ Set ค่าครับ:


```javascript
// ตอน Get: ตัด quotes หัว-ท้ายออก
if (typeof result === &apos;string&apos; &amp;&amp; result.startsWith(&apos;&quot;&apos;) &amp;&amp; result.endsWith(&apos;&quot;&apos;)) {
  result = result.slice(1, -1);
}

// ตอน Set: ใช้ encodeURIComponent แทนการใส่ quotes เอง
const url = `${env.UPSTASH_REDIS_REST_URL}/set/${REDIS_KEY}/${encodeURIComponent(timestamp)}`;
```


## ผลลัพธ์


หลังจาก Deploy เสร็จ ผมลองยิง request เข้าไปเทสดู ผลลัพธ์ออกมาสวยงาม:


```json
{
  &quot;success&quot;: true,
  &quot;deployed&quot;: false,
  &quot;lastEdited&quot;: &quot;2025-12-17T06:40:00.000Z&quot;
}
```


และเมื่อดู Logs ผ่าน `wrangler tail` ก็เห็นการทำงานชัดเจน (อันนี้ตัวอย่างตอนไม่มีอัพเดต)


```bash
🔍 Checking for Notion updates...
Notion last edited: 2025-12-17T06:40:00.000Z
Last checked (Redis): 2025-12-17T06:40:00.000Z
⏸️ No updates detected.
```


## สรุป


การย้ายมาใช้ **Cloudflare Workers** ช่วยให้:
- **ประหยัด**: ไม่เปลือง Github Actions runner minutes
- **Real-time ขึ้น**: เช็คได้ถี่ขึ้น (ทุก 15 นาที) โดยไม่ต้องเกรงใจ quota
- **Scalable**: ถ้าวันหน้าอยากเพิ่ม logic อะไรก็ใส่ใน Worker ได้เลย


ใครที่ทำ Static Site แล้วอยากได้ความ Dynamic แบบไม่ต้องรัน Server ตลอดเวลา ท่านี้แนะนำเลยครับ!</content:encoded></item><item><title>Uptime 32 years of me.</title><link>https://pickyzz.dev/blog/uptime-32-years-mr</link><guid isPermaLink="true">https://pickyzz.dev/blog/uptime-32-years-mr</guid><description>32 ปีแล้วก็ยังโสดอยู่ โสดมาจะครบ 10 ปีแล้ว แม่ถามทุกครั้งว่าให้หาแฟนได้แล้ว</description><pubDate>Tue, 30 Sep 2025 13:00:00 GMT</pubDate><content:encoded>สวัสดีครับ ปึหนึ่งผ่านไปไวเหลือเกิน ผมเองก็ไม่ได้เขียนบล็อกเลย มีความคิดจะเขียน มีไอเดียอยู่เยอะมาก แต่ไม่ค่อยมีเวลา


เผลอแป๊บเดียววันเกิดปีที่ 32 ก็วนมาถึงแล้ว เริ่มรู้สึกว่าตัวเองอยู่กึ่งกลางระหว่างความเป็นเด็ก และความเป็นผู้ใหญ่


ที่ผ่านมาก็ใช้ชีวิตเหมือนเดิม ยังโสดอยู่ _โสดมาจะครบ 10 ปีแล้ว_ แม่ถามทุกครั้งว่าให้หาแฟน (แต่แม่พูดว่า”เมีย”) ได้แล้ว จริงๆก็อยากมีนะ แต่ว่ารู้สึกว่าตัวเองไม่ใช่แฟนที่ดีเท่าไร ไม่อยากเอาตัวเองไปเป็นปัญหาใครเขา แถมวันๆทำงานรีโมทอยู่ที่บ้าน ไม่ค่อยได้เจอใคร ก็แอบคิดว่าชาตินี้จะมีกับเขาไหมเหมือนกัน ฮ่าๆ


## อัพเดทชีวิต

- เริ่มขยับมาทำงาน routine มากขึ้น ขอบคุณอาจารย์แม็กกี้ที่ดึงเข้ามาทำตรงนี้ ผมรู้สึกเป็นหนี้อาจารย์แม็กกี้หลายอย่างมากๆ
- เริ่มใช้ ai ในการทำงานมากขึ้น จากเป็นคนที่แอนตี้ แต่ที่สุดก็ต้องยอมรับว่าช่วยงานเราได้เยอะ แต่ยังไม่ถึงขนาดไว้ใจให้มันทำทั้งหมด ยังคงจับมือทำอยู่ สั่งทีละนิดว่าอยากได้อะไร ตรงไหน
- มีงาน มีเงินเดือนกับเขาแล้ว จากเดิมที่กินเงินแบบรายจ๊อบ ก็รู้สึกแปลกใหม่ดีเหมือนกัน
- ยอมเปลี่ยนมือถือแล้ว หลังจากที่ลากเครื่องเก่ามาหลายปี
- อัพเกรดคอมใหม่ยกเครื่อง แยก workspace และ playspace ออกจากกันให้ชัดเจนขึ้น
- Router หลักในบ้านหมดประกันแล้ว จับลง openwrt ใช้งานฟินๆ กว่าจะนิ่งก็พังคามือไปหลายรอบ
- มอเตอร์ไซค์คันเดิมใกล้จะครบปิดยอดแล้ว อีกหนึ่งปีนิดๆ ส่งเกิน ส่งตรงทุกเดือน
- เริ่มเรียน Unreal Engine 5 แล้ว ยิ่งเรียนก็ยิ่งสนุก

## ความปราถนา

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

ช่วงนี้ยังไม่ได้มีความอยากอะไรมากมาย ชีวิตก็เรื่อยๆ มีความสุขดีครับ ยังคงพัฒนาตัวเองเท่าที่ทำไหว เว็บนี้เองก็เป็นเวอร์ชั่นที่อยู่กับผมมาเกิน 2 ปี แต่ก็แอบเปลี่ยนหลังบ้านไปเป็น server side บ้างแล้ว


สุดท้ายนี้ขอบคุณ เพื่อนๆ พี่ ๆ น้อง ๆ ที่ทักมาอวยพรกันหลังไมค์จากหลาย ๆ ช่องทาง ขอให้พรนั้นกลับไปสู่ผู้อวยพรทุกท่านอย่างทวีคูณเลยครับ


แล้วพบกันใหม่โพสต์หน้า สวัสดีครับ</content:encoded></item><item><title>2024 and me</title><link>https://pickyzz.dev/blog/2024-and-me</link><guid isPermaLink="true">https://pickyzz.dev/blog/2024-and-me</guid><description>สำหรับชีวิตของผมในปี 2024 นี้ เกิดหลายอย่างขึ้นพอสมควร</description><pubDate>Tue, 31 Dec 2024 10:00:00 GMT</pubDate><content:encoded>สำหรับชีวิตของผมในปี 2024 นี้ เกิดหลายอย่างขึ้นพอสมควร


## ชีวิตใน 2024

- ทำงานโค้ดน้อยลง แต่ก็ยังฝึกฝนอยู่เรื่อยๆ เพื่อไม่ให้ลืม
- มีโอกาสเข้าไปช่วยงานการเมือง (และออกมาแล้ว)
- มีโอกาสได้พากย์งานบ้าง ถึงจะเป็นงานเล็กๆ แต่ผมรู้สึกขอบคุณเสมอที่ได้รับโอกาส และเห็นถึงความพยายาม
- ยังสนุกกับการขับมอเตอร์ไซค์คันน้อยๆของตัวเองเหมือนเดิม
- อายุมาพอที่จะเป็นกรดไหลย้อนเมื่อกินตอนดึก
- กินมื้อดึกน้อยลงแล้ว
- มีงาน = มีเงิน
- ยังไม่หยุดพัฒนาตัวเอง ถึงจะต้องใช้เวลามากกว่าคนอื่นหน่อย แต่ไม่เป็นไรเรามีเวลาเยอะ

## สิ่งที่อยากขอในปี 2025

- ขอให้มีงานแบบนี้ไปเรื่อยๆ
- อยากรีบปิดยอดรถคันเล็ก
- อยากออกทัวริ่งเพิ่มอีกคันเอาไว้ขับไปทำงาน - เที่ยวได้ไกลขึ้น
- ขอให้สุขภาพแข็งแรงกว่านี้
- ขอให้ตัวเองเป็นคนที่รับฟังคำแนะนำของคนอื่น
- อยากเป็นคนที่พัฒนาตัวเองได้ดีขึ้น

สุดท้ายนี้ขอให้ทุกท่านที่อ่านมีความสุขมากๆในปี 2025 นี้ครับ ❤️</content:encoded></item><item><title>ทำ Down Detector แจ้งเตือนเน็ตล่ม</title><link>https://pickyzz.dev/blog/home-network-down-detector-with-uptime-kuma</link><guid isPermaLink="true">https://pickyzz.dev/blog/home-network-down-detector-with-uptime-kuma</guid><description>ในกรณีที่เกิดปัญหากับเน็ตเวิร์คที่บ้าน อุปกรณ์ในวง Network ที่เป็นตัวส่ง Ping จะไม่สามารถส่งข้อมูลออกไปได้ เมื่อ Healthchecks ไม่ได้รับ Ping ภายในระยะเวลาที่กำหนด จะส่งแจ้งเตือนผ่านไปยัง service ต่างๆที่เรากำหนดไว้</description><pubDate>Thu, 03 Oct 2024 09:30:00 GMT</pubDate><content:encoded>ช่วงนี้ระบบ Internet ที่บ้านผมเริ่มมีปัญหาบ่อยมากขึ้น รวมถึงระบบไฟฟ้าที่เริ่มมีปัญหาบ่อยขึ้น หลังจากที่มีการเดินระบบสายไฟใหม่ในบริเวณหมู่บ้าน ไฟตกบ่อยขึ้น ส่งผลต่อระบบเน็ตเวิร์คในบ้าน และหลายครั้งที่เกิดปัญหาขึ้นในขณะตัวผมเองไม่ได้อยู่ที่บ้าน ทำให้ผมต้องมองหาวิธีว่าที่จะสามารถแจ้งเตือนเมื่อเกิดปัญหาขึ้น จนมาพบกับ [healthchecks.io](http://healthchecks.io/) 


## สิ่งที่ต้องใช้

- Account เว็บ [healthchecks.io](https://healthchecks.io/) เพื่อใช้เป็นรับการ ping เพื่อเช็คว่า network ปกติ
- อุปกรณ์ในการส่ง Ping ไปยัง [healthchecks.io](http://healthchecks.io/) (ผมใช้ Uptime Kuma ที่รันใน Armbian)

## หลักการ

- สร้าง Checking service บน [healthchecks.io](http://healthchecks.io/) เพื่อใช้เป็นตัวรับการ Ping จากอุปกรณ์ใน Home Network
- ส่ง Ping ไปยัง Healthchecks ที่สร้างไว้ โดยให้ interval อยู่ในช่วงที่กำหนดบน healthchecks

Checking service ของ Healthchecks จะทำการตรวจสอบข้อมูลการ Ping ตามกรอบเวลา ในกรณีที่เกิดปัญหากับเน็ตเวิร์คที่บ้าน อุปกรณ์ในวง Network ที่เป็นตัวส่ง Ping จะไม่สามารถส่งข้อมูลออกไปได้


เมื่อ Healthchecks ไม่ได้รับ Ping ภายในระยะเวลาที่กำหนด จะส่งแจ้งเตือนผ่านไปยัง service ต่างๆที่เรากำหนดไว้รองรับการแจ้งเตือนเยอะมากๆ


![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-1.png)


## ตั้งค่า Healthchecks

- สร้าง account ทำการล็อกอินเข้าระบบ **คลิก Add Check**

![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-2.png)

- ตั้งค่า ชื่อ slug แท็ก ต่างๆ ส่วนสำคัญ คือ Period และ Grace Time
    - Period คือ กรอบเวลาที่รับ Ping จากอุปกรณ์ ตรงนี้ผมตั้งไว้ 5 นาที (อุปกรณ์ของผมมีการ auto reboot ใช้เวลาประมาณ 3 นาที เลยทำการตั้งเวลาเผื่อไว้)
    - Grace Time ตามที่ผมเข้าใจคือ ช่วงเวลาก่อนที่จะส่งแจ้งเตือน หากไม่ได้รับการ Ping (ตรงนี้ผมตั้งไว้ 3 นาที)

การตั้งกรอบเวลาทั้งสอง ไม่มีกำหนดตายตัว สามารถปรับเปลี่ยนตามบริบทของแต่ละท่านได้เลย


![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-3.png)

- คลิก Save จะปรากฎ checker ที่เราสร้างอยู่ในลิสต์เรียบร้อย

![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-2.png)


โดย ping url คือส่วนที่เราต้องการ เพื่อนำไปป้อนให้อุปกรณ์เพื่อทำการ Ping มาที่นี่ เพื่อเป็นการยืนยันว่า Home network ของเรายังออนไลน์อยู่


## ตั้งค่าการแจ้งเตือน

- ไปที่ Intergrations ที่เมนูด้านบน

![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-4.png)

- เลือกช่องทางการแจ้งเตือน และทำการ Add Intergration ได้เลย ส่วนวิธีการตั้งค่าของแต่ละแพล็ตฟอร์มก็จะต่างกันออกไป

![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-1.png)


ทดลองตั้งค่าการแจ้งเตือนผ่าน Discord


![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-5.png)


สามารถ Test การแจ้งเตือนได้ว่าเราทำการตั้งค่าถูกต้องไหม (อันนี้ดีมาก)

- เมื่อตั้งค่า Intergration เรียบร้อย กลับมาที่หน้า Checks จะพบไอคอนของแพล็ตฟอร์มที่เราตั้งค่าการแจ้งเตือนเพิ่มขึ้นมา

![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-6.png)


จะสังเกตว่า email จะเป็น default อยู่แล้ว ซึ่งเราสามารถ เปิด-ปิด การแจ้งเตือนได้ทั้งหมด โดยคลิกที่ไอคอนได้เลย ไม่ต้องลบ intergration แต่อย่างใด


### ตัวอย่างการใช้ Uptime Kuma ในการ Ping ไปยัง Healthchecks


ในกรณีของผม ผมมีบอร์ด Armbian อยู่ที่บ้าน ใช้ทำหน้าที่เป็น DNS Filter กรองโฆษณาหรือเว็บ spam ต่างๆ อยู่แต่เดิม และลง uptime kuma ไว้เพื่อเป็นตัว Monitor อุปกรณ์ smart device ต่างๆ ให้คอยแจ้งเตือนเมื่อผ่านแพล็ตฟอร์มต่างๆ เมื่ออุปกรณ์ตัวไหนออฟไลน์


อธิบายสั้นๆ คือ _Uptime Kuma ทำหน้าที่เหมือน Healchecks เลย แต่เงื่อนไขการ Alive checking ต่างกัน โดย Healthchecks จะรับการ Ping จากอุปกรณ์เพื่อตรวจว่ายังทำงาน แต่ Uptime Kuma จะทำการ Ping ไปยังปลายทางเพื่อดู Reponse แทน_


### วิธีการ

- ล็อกอินเข้า Dashboard ของ Uptime Kuma
- ทำการ Add new monitor
- เลือก Monitor type เป็น HTTP(S) และ ในส่วน URL ให้ใส ping url ที่เราได้จาก [healthchecks.io](http://healthchecks.io/)
- Heartbeat Interval และ Heartbeat retry ให้กำหนดระยะเวลาไม่เกินจากที่เราตั้งค่าไว้ที่ healthchecks

![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-7.png)


สังเกตว่า Notifications ผมจะทำการปิดไว้ เพราะ ในกรณีที่ Network ล่ม ตัวอุปกรณ์ภายในบ้านก็ไม่สามารถส่งข้อมูลออกไปได้อยู่แล้ว ซึ่งเราจะให้ healthchecks เป็นฝ่ายแจ้งเตือน เมื่อไม่ได้รับการ ping จาก uptime kuma แทน และเมื่อมีการแจ้งเตือนว่า Network ในบ้านล่ม ก็จะทราบได้ทันทีว่า อุปกรณ์ในบ้านตัวอื่นๆ ก็ล่มไปทั้งหมดนั่นเอง

- ทำการ Save ข้อมูล ตรวจสอบความถูกต้องที่ dashboard ของ uptime kuma

![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-8.png)


ไม่มีอะไรผิดพลาด

- ล็อกอินกลับไปที่ healthchecks &gt; Check กด 3 จุด ท้าย checker ที่สร้างไว้เพื่อดู detail

บริเวณขวามือ ส่วนของ Event พบการ ping มาจาก uptime kuma แบบนี้ถือว่าถูกต้อง


![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-9.png)


### อัพเดท


ณ วันที่ 8/10/2024 ขณะที่ผมอยู่นอกบ้าน มีแจ้งเตือนผ่าน Discord ว่าเน็ตเวิร์คที่บ้านล่ม


![image.png](/images/blog/home-network-down-detector-with-uptime-kuma-content-10.png)


ทดลองผ่านสถานการณ์จริงแล้ว ถือว่าสอบผ่าน


## สรุป


จากการปล่อยให้ระบบที่ตั้งไว้ทำงานผ่านไประยะหนึ่ง จนถึงปัจจุบันที่เขียนบทความนี้ ยังไม่พบปัญหาอะไร รวมถึงระบบอินเตอร์เน็ตที่บ้านยังไม่ล่ม จึงยังไม่ได้ทดสอบบนสถานการณ์จริง


ถ้าไม่ต้องการติดตั้ง uptime kuma สามารถใช้วิธีการอื่นๆ เพื่อ ping ไปยัง healthchecks แทนได้ทั้งหมด Router บางรุ่นที่สามารถ interval ping ก็สามารถทำได้

- _สามารถใช้ cron ในการจัดการ ping แทนก็ได้_ แต่ที่ผมเลือก uptime kuma เพราะมันมี dashboard ให้ดูผ่าน browser ได้เลย ไม่ต้อง ssh เข้าตัว armbian ให้ยุ่งยากเพียงเพื่อจะดู log file ไม่กี่ตัว
- _สามารถใช้วิธี ติดตั้ง uptime kuma จาก hosting นอก home network แล้ว monitor กลับมาตรวจสอบอุปกรณ์ใน home network_ ก็สามารถทำได้เช่นกัน แต่ผมไม่ได้ใช้วิธีนี้ ส่วนตัวได้ลองเปรียบเทียบกันพบว่า ต้อง setup และจัดการ port กันวุ่นวายพอสมควร

สุดท้ายนี้ หวังว่าจะเป็นไอเดียใหม่ๆ ให้ผู้อ่านไปทดลองและต่อยอดเพิ่มเติมได้บ้างนะครับ</content:encoded></item><item><title>Uptime 31 years.</title><link>https://pickyzz.dev/blog/uptime-31-years</link><guid isPermaLink="true">https://pickyzz.dev/blog/uptime-31-years</guid><description>มีเพื่อนๆ พี่ๆ น้องๆ เขียนอวยพรมาพอสมควร อาจจะไม่เยอะมาก แต่ผมก็รู้สึกประทับใจเสมอ ขอบคุณที่ยังนึกถึงกัน</description><pubDate>Mon, 30 Sep 2024 10:28:00 GMT</pubDate><content:encoded>สวัสดีครับ 🙏🏻 เผลอแป๊บเดียวเวลาก็วนมาครบอีกปีแล้ว เวลาช่างเป็นสิ่งที่ผ่านไปไวและช้าในขณะเดียวกัน


ปีนี้ผมครบ 31 แล้ว ยังรู้สึกว่ายังไม่ได้ประสบความสำเร็จอะไรเป็นชิ้นเป็นอันเลย แต่รู้สึกว่าชีวิตเป็นรูปเป็นร่างขึ้นกว่าเดิม รู้สึกว่ามีอะไรที่ยังอยากทำอยู่หลายอย่าง ยังคงอยากลองทำอะไรใหม่ๆเหมือนเดิม ได้ออกไปทำอะไรข้างนอกบ้านมากขึ้น มีคนมาชวนไปช่วยงานหลังบ้านที่เกี่ยวข้องกับสังคม ได้พูดบ้าง ได้รับฟังมากขึ้น ถือเป็นโอกาสได้เปิดมุมมองใหม่ๆ


ช่วงนี้ผมอยู่กับปัจจุบันมากขึ้น พยายามไม่คิดถึงสิ่งที่ยังไม่เกิดมากจนเกินไป เพราะคิดไปก็ปวดหัวเปล่า แต่ไม่ใช่ว่าจะไม่วางแผนในการทำอะไรในชีวิต ยังคงเป็นคนที่วางแผนการใช้ชีวิตอยู่เสมอ แต่พยายามปรับลดความตึงลงมา เพื่อจะได้ไม่ทำให้ตัวเองกังวลมากจนเกินไป พยายามอยู่ในกรอบที่สบายๆมากขึ้น ทำให้สุขภาพจิตค่อนข้างดีขึ้นมากครับ


วันเกิดปีนี้มีเรื่องให้จดจำ ก่อนเข้าวันเกิด ทีมฟุตบอลทีมรักของผมแพ้ย่อยยับรับวันเกิด ดูจบก็เที่ยงคืนพอดี บ่นยับเลยครับ แต่ถามว่าจะเลิกเชียร์ไหมก็คงจะยังเชียร์ต่อไป ชีวิตมันก็คงจะแบบนี้ล่ะมั้ง ไม่มีอะไรได้ดั่งใจเสมอไป


มีเพื่อนๆ พี่ๆ น้องๆ เขียนอวยพรมาพอสมควร อาจจะไม่เยอะมาก แต่ผมก็รู้สึกประทับใจเสมอ ขอบคุณที่ยังนึกถึงกัน ผมจะพยายามทำตัวให้น่ารักขึ้นนะครับ อะไรที่ไม่ดีจะพยายามปรับปรุงตัวเอง ทั้งในด้านการใช้ชีวิต และการทำงาน


**สุดท้ายนี้ให้พรใดที่ท่านให้มา ขอให้กลับคืนสู่ท่านเพิ่มขึ้นเป็นทวีคูณ** 


ขอบคุณครับ ❤️</content:encoded></item><item><title>รื้อ Stack หลังบ้าน ver.2024</title><link>https://pickyzz.dev/blog/web-stack-update-2024</link><guid isPermaLink="true">https://pickyzz.dev/blog/web-stack-update-2024</guid><description>วันเวลาผ่านไปหนึ่งปี กอปรกับเป็นคนขี้เบื่อ จึงเริ่มคิดถึงการใช้ระบบจัดการเนื้อหา เพราะสะดวกกับการเขียนมากกว่าการมานั่งเขียนไฟล์ markdown เอง ทำให้คิดถึงการใช้ Notion ขึ้นมา</description><pubDate>Sun, 07 Apr 2024 06:20:00 GMT</pubDate><content:encoded>## เกริ่นก่อน


สวัสดีครับผู้อ่านทุกท่าน หลังจากที่หายหน้าหายตาไปนาน วันนี้ผมกลับมาพร้อมกับการรื้อเว็บนี้อีกครั้งครับ เหตุเกิดจากช่วงนี้มีงานเว็บเข้ามา เป็นงานช่วงเช้า ซึ่งเปลี่ยนพฤๅติกรรมของผมจากคนนอนดึก ให้ต้องปรับเวลานอน เพื่อที่จะตื่นเช้ามาเตรียมตัวเผื่อมีอะไรให้ทำ ส่งผลให้ช่วงบ่ายผมพอจะมีเวลาว่างอยู่บ้าง กอปรกับความฟุ้งซ่านจากในอดีตที่เคยใช้ Notion ทำเป็นตัวจัดการเนื้อหา (Content management system : CMS)


ในตอนนั้นยังไม่มี Official API ผมเลยอาศัยโปรเจ็กต์ที่มีคนเขียนอยู่แล้วมาใช้งาน ตอนนั้นเป็น Notion + NextJs ซึ่งก็ดูใช้ได้ดีไม่ติดขัดอะไรในช่วงแรง 


แต่เมื่อเวลาผ่านไปสักระยะ ทาง Notion ได้ปล่อย Official API ออกมา และจำกัดการเข้าถึงของ API ตัวเดิม ส่งผลให้เว็บของผมได้รับผลกระทบไปด้วย เนื่องจากตัวเว็บไซต์ของผมทำงานในเชิง Server Side คือ เมื่อมีการเรียกหน้าเว็บไซต์จะทำการดึงข้อมูลเนื้อหาจาก Notion ผ่าน API มาทำการแสดงผลเป็นรายครั้ง


ซึ่งปัญหาก็จะเกิดขึ้นตรงนี้ เพราะเมื่อ API เก่าถูกจำกัดการใช้งาน ซึ่งข้อจำกัดคือมีการหน่วงเวลาในการเรียกข้อมูลแต่ละครั้ง ทำให้การเปิดหน้าเว็บไซต์แต่ละครั้ง ข้อมูลมาไม่ครบ บางครั้งรูปไม่ขึ้นบ้าง รวมถึงปัญหาเชิงเทคนิคอื่นๆ


ปัญหาที่เกิดขึ้นทำให้ผมตัดสินใจย้ายไปใช้ tech stack ตัวอื่นๆ ก่อนจะกลับมาเป็นเว็บโฉมปัจจุบันนี้ และกลับมาใช้วิธีการ build แบบ static site และเขียนเนื้อหาเป็นไฟล์ markdown แทน แต่ใช้วิธีแยกเนื้อหา กับ เฟรมเวิร์ค ออกเป็นคนละส่วนแทน ([รายละเอียดส่วนดังกล่าว](/blog/deploy-with-modules/))


วันเวลาผ่านไปหนึ่งปี กอปรกับเป็นคนขี้เบื่อ จึงเริ่มคิดถึงการใช้ระบบจัดการเนื้อหา เพราะสะดวกกับการเขียนมากกว่าการมานั่งเขียนไฟล์ markdown เอง ทำให้คิดถึงการใช้ Notion ขึ้นมา และจำได้ว่าสามารถ export เนื้อหาออกไปเป็นไฟล์ markdown ได้ จึงลองหาข้อมูลว่ามีใครทำวิธีคล้ายๆกันนี้บ้าง เพื่อจะมาเป็นแนวทาง


## เข้าสู่เนื้อหาหลัก


หาข้อมูลและวิธีอยู่หลายเดือน จนบังเอิญไปเจอกับโปรเจ็กต์ตัวหนึ่งที่ถูกเขียนไว้ [[Link]](https://github.com/jsonMartin/AstroNot) ซึ่งตรงกับสิ่งที่กำลังมองหาทุกอย่าง ไม่ว่าจะเป็น

- ใช้ Notion Offical API
- ดึงข้อมูลจาก Notion ทั้งหมดมาแปลงเป็นไฟล์ Markdown แบบ Offline ได้
- สามารถเอาไปใช้กับโปรเจ็กต์ที่ถูกเขียนอยู่แล้วได้

### ของดีย่อมมีข้อจำกัด


จากข้อจำกัดในการทำเว็บไซต์แบบ Server-side ผมเคยพบเจอปัญหาในการ Fetch ข้อมูลมาแสดงผลแล้วมาบ้าง ไม่มาบ้าง ที่เป็นข้อจำกัดเดิม จนทำให้ผมเปลี่ยนวิธีมาใช้แบบ โหลดข้อมูลที่ต้องใช้ทุกอย่างมา Build หน้าเว็บทั้งหมดเป็นรายครั้ง เพื่อแก้ปัญหาจุดเดิม


ข้อจำกัดของวิธีนี้ คือ ข้อมูลที่แสดงผลบนหน้าเว็บฯ จะไม่ถูกอัพเดทตามข้อมูลหลังบ้าน (บน Notion) จนกว่าจะมีการสั่ง Build หน้าเว็บใหม่ในครั้งต่อไป


_**กล่าวโดยสรุปคือ**_ เราต้องทำการสั่ง Build เว็บทั้งหมดใหม่ทุกครั้งที่มีการเพิ่ม ลบ หรือแก้ไขบทความ 


ซึ่งทางผู้พัฒนา api ได้แนะนำวิธีการว่าให้ใช้เว็บฟรีในการสั่งคำสั่ง build เมื่อมีการอัพเดตเนื้อหาบน Notion แต่ผมรู้สึกว่า เว็บเหล่านั้นจำนวนครั้งที่ให้ใช้ฟรีต่อเดือนค่อนข้างน้อยไปหน่อย แต่ไม่เป็นอะไร ขอให้ api ตัวนี้ใช้ได้จริงก่อน อย่างอื่นเดี๋ยวเขียนเพิ่มทีหลังได้ (เราก็ไปทางเก่งซะแล้ว ฮ่าๆ)


## เริ่มงาน


เพื่อป้องกันความสับสน ผมจะค่อยๆเรียงลำดับสิ่งที่จำเป็นต้องทำทีละขั้นตอนตามลำดับการทำงานเวลาเรียกใช้งาน โดยเริ่มจาก


### 1. ติดตั้ง Dependencies ที่จำเป็น


ทางผู้พัฒนาได้แนะนำให้ติดตั้ง Dependencies เพิ่มหลายตัว แต่เมื่อพิจารณาถึงส่วนที่จำเป็นจริงๆ มีที่ต้องใช้เพียง


```json
&quot;@notionhq/client&quot;
&quot;dotenv&quot;
&quot;image-type&quot;
&quot;notion-to-md&quot;
&quot;sharp&quot;
&quot;tinycolor2&quot;
```


สามารถติดตั้งทั้งหมดได้เลย


```json
bun installl -D @notionhq/client dotenv image-type notion-to-md sharp tinycolor2
```


จากนั้น ผมจะยังไม่เข้าไปยุ่งในส่วนของ script บนไฟล์ package.json เพราะยังไม่มีส่วนที่ต้องใช้งาน (เขียนไปก็ยังรันไม่ได้อยู่ดี)


### 2. เพิ่มไฟล์ที่จำเป็น


จากต้นฉบับ [https://github.com/jsonMartin/AstroNot](https://github.com/jsonMartin/AstroNot) ไฟล์ที่ผมพิจารณาว่าจำเป็นต่อการใช้งานกับเว็บตัวเอง จะเหลือเพียงเท่านี้

- `src/helpers/delay.mjs`
- `src/helpers/images.mjs` (ไฟล์นี้ผมทำการแก้ไขเพียง path ในปลายทางเท่านั้น)

ซึ่งในต้นฉบับจะเป็น


```typescript
switch (ext) {
    case &quot;.webp&quot;:
      return await import(`../images/posts/${name}.webp`);
    case &quot;.jpg&quot;:
      return await import(`../images/posts/${name}.jpg`);
    case &quot;.png&quot;:
      return await import(`../images/posts/${name}.png`);
    case &quot;.svg&quot;:
      return await import(`../images/posts/${name}.svg`);
    case &quot;.gif&quot;:
      return await import(`../images/posts/${name}.gif`);
    case &quot;.avif&quot;:
      return await import(`../images/posts/${name}.avif`);
    case &quot;.jpeg&quot;:
      return await import(`../images/posts/${name}.jpeg`);
    case &quot;.bmp&quot;:
      return await import(`../images/posts/${name}.bmp`);
    default:
      return await import(`../images/posts/${name}.jpg`);
}
```


ทำการแก้ไข path ปลายทางเป็น


```typescript
switch (ext) {
    case &quot;.webp&quot;:
      return await import(`../assets/images/blog/${name}.webp`);
    case &quot;.jpg&quot;:
      return await import(`../assets/images/blog/${name}.jpg`);
    case &quot;.png&quot;:
      return await import(`../assets/images/blog/${name}.png`);
    case &quot;.svg&quot;:
      return await import(`../assets/images/blog/${name}.svg`);
    case &quot;.gif&quot;:
      return await import(`../assets/images/blog/${name}.gif`);
    case &quot;.avif&quot;:
      return await import(`../assets/images/blog/${name}.avif`);
    case &quot;.jpeg&quot;:
      return await import(`../assets/images/blog/${name}.jpeg`);
    case &quot;.bmp&quot;:
      return await import(`../assets/images/blog/${name}.bmp`);
    default:
      return await import(`../assets/images/blog/${name}.jpg`);
}
```


สาเหตุก็เพราะ ผมเปลี่ยนมาใช้การเก็บไฟล์รูปในโฟลเดอร์ assets ตามที่ Astro แนะนำ

- `src/helpers/sanitize.mjs`
- `src/helpers/throttle.ts`
- `src/components/Image.astro`

---


จากนั้นทำการสร้างไฟล์  `src/libs/notion.js` ซึ่ง path ตามต้นฉบับไม่ใช่ที่นี่ (File structure ก็ไม่ใช่แบบนี้ ผมก็ไม่เข้าใจตัวเองเหมือนกันว่ามันทำไมไปเก็บ libs ไว้ใน src ฮ่าๆ)

&gt; ไฟล์ต้นฉบับชื่อ `astronot.js`

โดยไฟล์ `notion.js` นี้จะเป็นไฟล์หลักที่ถูกเรียกใช้งานเพื่อดึงข้อมูลจาก Notion มาแปลงเป็นไฟล์เนื้อหาโพสต์ในเว็บบล็อก ผมได้ทำการแก้ไขหลายจุดเพื่อให้เป็นไปตาม frontmatter ของเว็บนี้


```typescript
//const POSTS_PATH = `src/pages/posts`; // path เดิม
const POSTS_PATH = `src/content/blog`; // path ใหม่
```


```typescript
//return `&lt;Image src=&quot;/images/posts/${fileName}&quot; /&gt;`; // path เดิม
return `&lt;Image src=&quot;@assets/images/blog/${fileName}&quot; /&gt;`; // path ใหม่
```


และในส่วน Create Page นั้นผมได้ปรับเปลี่ยนเกือบทั้งหมด เพื่อให้เป็นไปตาม fronmatter ของเว็บ


```typescript
// Create Pages
const pages = results.map(page =&gt; {
  const { properties, cover, created_time, last_edited_time, archived } = page;
  const title = properties.title.title[0].plain_text;
  const slug = properties?.slug?.rich_text[0]?.plain_text || sanitizeUrl(title);

  console.info(&quot;Notion Page:&quot;, page);

  return {
    id: page.id,
    title,
    type: page.object,
    cover: cover?.external?.url || cover?.file?.url,
    tags: properties.tags.multi_select,
    created_time,
    last_edited_time,
    featured: properties?.featured?.select?.name,
    archived,
    status: properties?.status?.select?.name,
    publish_date: properties?.publish_date?.date?.start,
    modified_date: properties?.modified_date?.date?.start,
    description: properties?.description?.rich_text[0]?.plain_text,
    slug,
  };
});

for (let page of pages) {
  console.info(
    &quot;Fetching from Notion &amp; Converting to Markdown: &quot;,
    `${page.title} [${page.id}]`
  );
  const mdblocks = await n2m.pageToMarkdown(page.id);
  const { parent: mdString } = n2m.toMarkdownString(mdblocks);

  const estimatedReadingTime = readingTime(mdString || &quot;&quot;).text;

  // Download Cover Image
  const coverFileName = page.cover
    ? await downloadImage(page.cover, { isCover: true })
    : &quot;&quot;;
  if (coverFileName) console.info(&quot;Cover image downloaded:&quot;, coverFileName);

  // Generate page contents (frontmatter, MDX imports, + converted Notion markdown)
  const pageContents = `---
title: &quot;${page.title}&quot;
slug: &quot;${page.slug}&quot;
ogImage: ${coverFileName}
featured: ${page.featured === &quot;featured&quot; ? true : false}
tags: ${JSON.stringify(page.tags.map(tag =&gt; tag.name))}
draft: ${page.status === &quot;draft&quot; ? true : false}
pubDatetime: ${page.publish_date === undefined ? page.created_time : page.publish_date}
modDatetime: ${page.modified_date === undefined ? page.publish_date : page.modified_date}
description: &quot;${page.description === &quot;undefined&quot; ? &quot;&quot; : page.description}&quot;
readingTime: &quot;${estimatedReadingTime}&quot;
---
import Image from &apos;../../components/Image.astro&apos;;

${mdString}
`;

  if (mdString)
    fs.writeFileSync(
      `${process.cwd()}/${POSTS_PATH}/${page.slug}.mdx`,
      pageContents
    );
  else console.log(`No content for page ${page.id}`);

  console.debug(`Sleeping for ${THROTTLE_DURATION} ms...\n`);
  await delay(THROTTLE_DURATION); // Need to throttle requests to avoid rate limiting
}
```


### 3. เพิ่ม Script ใน package.json


หลังจากสร้างไฟล์ที่จำเป็นครบแล้ว คราวนี้เรากลับมาที่ไฟล์ `package.json` เพื่อทำการเพิ่มสคริปต์ จะได้ง่ายต่อการสั่งงาน โดยคำสั่งที่ต้องเพิ่ม คือ


```typescript
&quot;scripts&quot;: {
	//...other...
	//&quot;sync&quot;: &quot;astro sync&quot;, คำสั่งเดิมลบออกได้เลย แทบจะไม่ได้ใช้
	&quot;sync&quot;: &quot;rimraf src/content/blog/_ &amp;&amp; node src/libs/notion.js&quot;,
	&quot;sync:published&quot;: &quot;rimraf src/content/blog/_ &amp;&amp; node src//libs/notion.js --published&quot;,
	&quot;generate&quot;: &quot;rimraf dist/*_ &amp;&amp; ([ -d &apos;dist&apos; ] || mkdir dist) &amp;&amp; ([ -d &apos;dist/images&apos; ] || mkdir dist/images) &amp;&amp; ([ -d &apos;src/content/blog&apos; ] || mkdir src/content/blog) &amp;&amp; ([ -d &apos;src/assets/images&apos; ] || mkdir src/assets/images) &amp;&amp; ([ -d &apos;src/assets/images/blog&apos; ] || mkdir src/assets/images/blog) &amp;&amp; rimraf src/content/blog/_ &amp;&amp; node src/libs/notion.js --published &amp;&amp; astro build &amp;&amp; jampack ./dist&quot;,
},
```


### 4. สร้าง Notion Database ที่จะเก็บโพสต์


ทำการสมัครสมาชิก และล็อกอิน Notion ให้เรียบร้อย จากนั้นจะสามารถคัดลอก Template นี้ไปใช้ได้


Template url : [[Notion]](/b60241fb283943c29acd6bc6c91acc77?v=f688e711757a47339b30e33f1fbf8d7e)

&gt; ไม่ควรเปลี่ยนชื่อหัวตาราง เพราะเมื่อรันคำสั่งจะทำให้เรียกข้อมูลไม่ได้

หรือจะเปลี่ยนก็ได้ แต่ไปแก้ไขไฟล์ notion.js ให้ตรงกันด้วย เวลาเรียกข้อมูลจะได้ไม่มีปัญหาทีหลัง


### 5. สร้าง Integration บน Notion เพื่อดึงข้อมูล

- เข้าไปที่ [https://www.notion.so/my-integrations](https://www.notion.so/my-integrations)
- เลือก New Integraion

![Untitled.png](/images/blog/web-stack-update-2024-content-1.png)

- กด Submit และ บันทึก Srecet code เพื่อใช้ในขั้นตอนต่อไป

![Untitled.png](/images/blog/web-stack-update-2024-content-2.png)

- จากนั้นกลับมาที่ database ของเรา สังเกตปุ่ม … มุมบนขวา กด Connect to และเพิ่ม Integration ที่เราสร้างขึ้นเข้าไป

![Untitled.png](/images/blog/web-stack-update-2024-content-3.png)


### 6. สร้างไฟล์ environment


ทำการสร้างไฟล์ .env.local (ใช้ dev บน local) โดยมี parameter ดังนี้


```typescript
NOTION_KEY=&apos;secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&apos;
DATABASE_ID=&apos;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&apos;
```


โดย `NOTION_KEY` คือ secret ที่เราได้จากการสร้าง integration เมื่อสักครู่


`DATABASE_ID` สามารถหาได้โดย copy ลิงค์หน้า notion database ของเรา โดย จะอยู่ในรูปแบบ


```typescript
www.notion.so/pickyzz/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX?v=zzzzzzzzzzzzz
```


จากนั้นนำไปวางในไฟล์ .env และ save ให้เรียบร้อย


### 7. ทดสอบ sync ข้อมูล


เมื่อได้ส่วนที่จำเป็นครบหมดแล้ว ก็มาถึงช่วงเวลาที่สำคัญคือการทดสอบว่าทั้งหมดที่ทำมา มันใช้งานได้จริงหรือไม่ โดย


```typescript
bun run sync
```


ถ้า log ออกมาประมาณนี้ ถือว่าผ่านแล้ว


![Untitled.png](/images/blog/web-stack-update-2024-content-4.png)


ตรวจสอบ path ของไฟล์ markdown และ ไฟล์ภาพ ว่าถูกต้องตามตำแหน่งที่ควรจะเป็นหรือไม่


![Untitled.png](/images/blog/web-stack-update-2024-content-5.png)


ครบถ้วนสมบูรณ์ดี จากนั้นทดลองเปิด local dev ดูว่าหน้าเว็บขึ้นตามปกติไหม


![Untitled.png](/images/blog/web-stack-update-2024-content-6.png)


ถือว่าผ่าน (จริงๆผมทดสอบโดยการ run test แต่กลัวจะไม่เห็นภาพ)


### 8. ทำการย้ายเนื้อหาทั้งหมดไปยัง Notion


เป็นขั้นตอนที่กินเวลายาวนานที่สุดแล้ว แต่ข้อดีของ Notion คือ เราสามารถ copy ไฟล์ภาพจากในเครื่อง แล้วไปวางได้เลย ตัวแอพจะอัพโหลดให้ทันที

&gt; แต่มีข้อควรระวังเรื่องการ copy จากหน้าเว็บ เพราะจะเป็นการวาง url ของภาพแทน ถ้าต้นทางลบรูปภาพข้อมูลภาพในโพสต์หายไปด้วย และรวมถึงเวลารันคำสั่ง syn จะโหลดเป็นนามสกุล .undefied ทำให้ไม่สามารถ build ได้

## สรุป

- เปลี่ยนขั้นตอนหลังบ้านเวลาเขียนโพสต์ใหม่จากการเขียนไฟล์ดิบ ไปเป็นการเขียนบน Notion
- ยังสามารถทำงานกับไฟล์ static markdown (เขียนได้ทั้งแบบไฟล์ดิบ และบน Notion แต่ผม ignore ไฟล์บนเครื่องไว้ เพราะปรับไปใช้บน Notion ทั้งหมด)
- ระบบหน้าบ้านทุกอย่าง หน้าเว็บทุกอย่างยังอยู่เหมือนเดิม
&gt; ในส่วนของการแก้ไข ci/cd เพิ่มเติม ผมขออนุญาติยกยอดไปไว้โพสต์ต่อไป เนื่องจากโพสต์นี้ก็ยาวพอสมควรแล้ว

ขอขอบคุณผู้อ่านที่อ่านจนถึงตรงนี้ครับ 🙏🏻


## References

- [Astronot](https://github.com/jsonMartin/AstroNot)</content:encoded></item><item><title>(รีวิว) จำกัดการชาร์จบน Macbook ด้วย Aldente</title><link>https://pickyzz.dev/blog/aldente-pro-mac-app-review</link><guid isPermaLink="true">https://pickyzz.dev/blog/aldente-pro-mac-app-review</guid><description>ในตอนนั้นคิดว่า เมื่อเขามีมาให้แล้ว ทำไมเราต้องมาเสียเงินซื้่อโปรแกรมที่ลักษณะการทำงานเหมือนๆกันอีก</description><pubDate>Wed, 07 Feb 2024 07:38:00 GMT</pubDate><content:encoded>## **เกริ่นนำ**


สวัสดีคุณผู้อ่านทุกท่านครับ โพสต์นี้ผมมาว่าด้วยเรื่องของแบตเตอรี่เจ้า Macbook air M1 อายุปีครึ่งของผม ซึ่งตลอดการใช้งานที่ผ่านมา การใช้แบตเตอรี่ก็เป็นไปด้วยความหนักหน่วงจน life cycle วิ่งไปแล้ว 75 cycles ซึ่งเมื่อลองเปรียบเทียบกับการใช้งานแล้วก็ถือว่าปกติตามแบบที่ควรจะเป็น


บอกตามตรงว่าตั้งแต่ได้เครื่องมา ก็ชาร์จและใช้งานแบบปกติทั่วไป เต็มก็ถอด ปรับพฤติกรรมการใช้โดยเข้าใจว่าเป็นการถนอมตัวแบต ด้วยความที่เพิ่งเปลี่ยนมาใช้ MacOs ครั้งแรก ยังไม่ชำนิชำนาญอะไรมากนัก จัดมาโค้ดงานอย่างเดียว โดยที่ไม่ทราบว่าตัว **MacOs เองมีฟังก์ชั่นถนอมแบตเตอรี่ในตัว ซึ่งจะปรับลดระดับการชาร์จลงมาอยู่ที่ 80% และตัดการชาร์จที่จุดนั้น**


![01.png](/images/blog/aldente-pro-mac-app-review-content-1.png)


ตั้งค่าได้ที่ Setting &gt; Battery &gt; Option


ซึ่งในตอนนั้นที่ผมหาข้อมูลเรื่องฟังก์ชันการใช้งานตรงนี้อยู่ ผมก็ได้พบว่ามีโปรแกรมที่มีลักษณะการทำงานแบบนี้อยู่ รวมถึง Aldente ด้วย แต่ในตอนนั้นคิดว่า เมื่อเขามีมาให้แล้ว ทำไมเราต้องมาเสียเงินซื้่อโปรแกรมที่ลักษณะการทำงานเหมือนๆกันอีก ก็เลยยังไม่ได้สนใจอะไรมากนัก


## **จุดสังเกตระบบตัดการชาร์จใน MacOs**


อันนี้เป็นข้อเสียที่ผมพบเจอกับตัวเองซึ่งบางข้อก็ไม่ถึงขั้นว่าเป็นข้อเสีย เรียกว่าเป็นจุดที่จุกจิกกวนใจน่าจะถูกต้องกว่า

1. ระบบต้องใช้เวลาในการเรียนรู้ช่วงเวลาในการชาร์จ

    จากข้างบนที่ผมได้เขียนไปว่า ผมแทบจะไม่รู้เลยว่า MacOs มีระบบถนอมแบต ก็ต้องบอกว่าหลักๆเป็นเพราะจุดนี้เลยครับ เพราะเริ่มแรกที่ผมได้เจ้าเครื่องนี้มาทำงาน ผมอยู่ในช่วงต่อเติมบ้านพอดี ทำให้ต้องย้ายสถานที่นั่งทำงาน ประชุมงานไปจุดต่างๆ ทำให้มีการเสียบที่ชาร์จ เข้า-ออก อยู่บ่อยๆ ทำให้ระบบไม่สามารถจดจำ Loop การชาร์จของผมได้


    แต่หลังจากที่บ้านเริ่มเรียบร้อย ผมกลับมานั่งทำงานอยู่เป็นที่เป็นทางมากขึ้น ผมลองเสียบชาร์จทิ้งไว้ตลอดเวลา (ถอดที่ชาร์จเมื่อนอน) ก็พบว่าประมาณ 1 สัปดาห์ก็เริ่มตัดการชาร์จให้แล้ว

2. เมื่อถอดที่ชาร์จแล้วเสียบใหม่ ระบบจะชาร์จจนเต็มเสมอ

    ข้อนี้ ผมเจอตอนที่เริ่มใช้ mac ทำเกี่ยวกับเสียง ที่บ้านผมระบบไฟจะมี noise เป็นช่วงๆในช่วงกลางวันทำให้เมื่อเสียบที่ชาร์จไว้ ตอนอัดเสียงจะเกิดเสียงจี่เข้าไปในไมค์ในบางครั้ง ทำให้ผมต้องถอดสายชาร์จตอนอัดเสียงเป็นช่วงๆ แต่ว่าไม่ได้นานนัก เมื่ออัดเสียงเสร็จก็เสียบชาร์จต่อ แต่ระบบจะชาร์จขึ้นไปจนเต็มเสมอ


เมื่อชาร์จจาก 80% ขึ้นไปจนเต็มบ่อยๆทำให้ cycle วิ่งขึ้นไว (อันนี้ผมไม่ซีเรียส) และเสียบชาร์จใช้งานต่อไปต้องรออีกสักพักใหญ่ๆ (ส่วนใหญ่จะข้ามวัน) ถึงจะกลับมาหยุดชาร์จแล้วปล่อยแบตกลับมาที่ 80% ตามเดิม ซึ่งส่วนตัวนั้นจุกจิกกับตรงนี้มาก


## **เริ่มใช้ Aldente Pro**


หลังจากที่ผ่านไป 1 ปีนิดๆ จนมาตรวจสอบแบตเตอรี่พบว่า Cycle ก็วิ่งไปตามปกติแต่ที่น่าสังเกตคือ Battery Health นั้นต่ำกว่าปกติมากเมื่อเปรียบเทียบกับผู้ใช้งานคนอื่นๆในระยะเวลาใกล้เคียงกัน กอปรกับระยะหลังไม่ค่อยได้พกเครื่องออกไปทำงานข้างนอกแล้ว ส่วนใหญ่ทำอยู่บ้านและเสียบที่ชาร์จตลอดเวลา เลยตัดสินใจมาดูโปรแกรมที่ทำงานในลักษณะนี้อีกครั้ง ซึ่งมีหลายตัวมาก มีทั้งฟรีและเสียเงินซื้อ


ผู้พัฒนาแบ่ง License เป็น 3 ระดับ คือ ตัวฟรี, License Pro แบบรายปี, License Pro แบบซื้อขาด โดย

- รายปีอยู่ที่ปีละประมาณ 400 บาท
- ซื้อขาดอยู่ที่ 840 บาท

โดยที่ส่วนใหญ่จะเป็นฟังก์ชันพื้นฐานจะเป็นการตัดการชาร์จเหมือนๆกัน แต่มีสิ่งที่ทำให้ผมยอมเสียเงินซื้อขาด Aldente Pro คือ


### _**จุดที่น่าสนใจ**_


![02.png](/images/blog/aldente-pro-mac-app-review-content-2.png)


หน้าตาโปรแกรม เพื่อใช้อ้างอิงง่ายๆ

1. ตั้งได้เองว่าจะให้ตัดการชาร์จที่ระดับใด

    จากรูปอ้างอิงเราสามารถตั้งระดับที่ต้องการให้ตัดการชาร์จได้ง่ายๆ ด้วยการลากปรับระดับได้เลย โดยที่จะมีตัวเลขบอกทางด้านซ้ายบน

2. เลือกได้ว่าจะให้โปรแกรมอ่านค่าแบตเตอรรี่จาก Software หรือ Hardware

![03.png](/images/blog/aldente-pro-mac-app-review-content-3.png)

&gt; จากรูป คลิกที่รูปเฟือง (setting) &gt; Charge

สิ่งที่แตกต่างกันระหว่างการอ้างอิงค่าจาก Software กับ Hardware ตามที่ผู้พัฒนาได้เขียนไว้คือ ทั้งสองจะมีค่าที่แตกต่างกันเล็กน้อย โดยความแตกต่างจะชัดเจนมากขึ้นตามอายุการใช้งานของแบตเตอรี่


แต่ว่าจุดนี้ผมก็ใช้ทั้ง Coconut Battery ในการวัดเทียบกับ ในตัว MacOs ค่าที่ได้ก็ไม่เท่ากัน ผมก็ยังสับสนว่าจะเชื่อใครดี

1. สามารถสั่งให้ชาร์จจนเต็มได้เป็นรายครั้งได้

    จากรูปอ้างอิง สังเกตที่คำว่า top up เมื่อคลิก จะเป็นการสั่งให้ชาร์จจนเต็ม 100% เผื่อใครต้องพกเครื่องออกไปข้างนอกแล้วกลัวว่าแบตที่ทำการจำกัดการชาร์จไว้จะไม่เพียงพอต่อการใช้งาน

    &gt; สามารถคลิกขวาที่ไอคอนโปรแกรมเป็นการเปิดฟังก์ชันนี้ได้เช่นกัน
2. สามารถ Calibrate Battery ได้ในคลิกเดียว

![04.png](/images/blog/aldente-pro-mac-app-review-content-4.png)

&gt; จากรูป คลิกที่รูปเฟือง (setting) &gt; Features

โดยที่ผู้พัฒนาได้เขียนบอกขั้นตอนการ Calibrate ไว้ชัดเจน เมื่อเริ่มขั้นตอนอยู่สามารถคลิปดูที่ไอคอนแอพได้ด้วยว่าอยู่ในขั้นตอนใด (เพิ่ง Calibrate ไปหนึ่งวันก่อนเขียนโพสต์ ลืมเก็บรูปไว้ ขอโทษครับ 🙏🏻)

1. ตั้ง Schedule ให้ทำฟังก์ชันต่างๆได้ล่วงหน้า

![05.png](/images/blog/aldente-pro-mac-app-review-content-5.png)


จากรูป คลิกที่รูปเฟือง (setting) &gt; Schedule


ซึ่งเราสามารถตั้งได้ทุกอย่างว่าจะให้ทำอะไรในช่วงเวลาไหน จะทำซ้ำหรือไหม ซึ่งการทำซ้ำมีตั้งแต่ รายวัน ทุกวันจันทร์-ศุกร์ รายสัปดาห์ รายปักษ์(สองสัปดาห์) รายเดือน เหมาะกับคนที่ทำงานเป็น routine หรือคนที่ขี้เกียจแบบผมก็ตั้งให้ทำการ Calibrate ทุกเดือนไปเลยอัตโนมัติ

&gt; เพิ่มเติมในส่วนของ Start task at next Opportunity คือเมื่อเปิดไว้แล้วถึงเวลาที่ต้องทำ Schedule นั้น แต่เราไม่ได้เปิดเครื่อง หรือ sleep อยู่ ตัวโปรแกรมจะจำค่าไว้ แล้วไปเริ่ม task นั้นเมื่อเราเปิดเครื่องครั้งต่อไป ในรูปผมลืมเปิดครับ ฮ่าๆๆ
1. สิ่งที่เรียกว่า Sailing Mode

![d0353b83-bfc2-43b1-a476-637a8fbb8255.png](/images/blog/aldente-pro-mac-app-review-content-6.png)

&gt; จากรูป คลิกที่รูปเฟือง (setting) &gt; Features

อธิบายสิ่งนี้ง่ายๆคือ เมื่อทำการชาร์จจนถึง Limit ที่ตั้งเอาไว้ ตัวโปรมแกรมจะทำการตัดการชาร์จ และใช้ไฟจากสายชาร์จต่อไป ถึงแม้ว่าจะไม่มีการใช้ไฟจากแบตเตอรี่แล้ว แต่ประจุไฟก็ยังมีการคายตัวเองตามธรรมชาติ และสิ่งนี้มีไว้เพื่อป้องกันการชาร์จรัวๆเมื่อแบตเตอรี่ลดลง 1% ผมตั้งไว้ 5% ซึ่งค่าตรงนี้อยู่ที่คนชอบว่าจะ 5% หรือ 10% โดยที่เมื่อแบตเตอรี่เต็ม 70% จะตัดการชาร์จและใช้ไฟเลี้ยงโดยตรง และจะชาร์จใหม่เมื่อลดลงเหลือ 65%


ข้อดีคือ ถ้าคุณใช้แบตเตอรี่ในช่วงที่ถอดสายแล้วเสียบใหม่ตอนที่แบตเตอรี่อยู่ในช่วงระหว่าง Sailing Mode นั้น Macbook จะไม่ชาร์จไฟ แต่จะใช้ไฟตรงแทน ทำมาเพื่อแก้จุดด้อยของระบบที่ถอดแล้วเสียบใหม่ก็ชาร์จนเต็มบ่อยๆ


ในจุดนี้เองแอพฟรีตัวอื่นๆ รวมถึง Aldente ที่เป็นตัวฟรีเองก็ไม่มีตรงจุดนี้ แต่ก็มีวิธีพลิกแพลงที่จะทำให้ความสามารถคล้ายๆกันอยู่ แต่ต้องมานั่งกดเองทุกครั้ง ก็ดูจุกจิกไป เลยแก้ปัญหาด้วยการอุดหนุนตัวเต็มไปเลยทีเดียว


## **สรุป**


หลังจากใช้งาน Aldente Pro มาได้สักพักใหญ่ รู้สึกว่าไม่ต้องมานั่งผวาทุกครั้งที่ตรวจสุขภาพแบตเตอรี่ ซื้อตัวเต็มไปเลยครั้งเดียวจบๆ และในอนาคตคิดว่าเปลี่ยนเครื่องผมก็คงจะติดตั้งลงบนเครื่องใหม่ไปเลยตั้งแต่วันแรก ระบบ Schedule ค่อนข้างถุูกใจเพราะตั้งค่าครั้งเดียวแล้วลืมๆไปเลย ปล่อยให้ทำงานไปเลย รวมถึงตัวไอคอนเองที่มีสถานะบอกค่อนข้างครบถ้วน ใช้งานแทนไอคอนแบตที่มากับระบบได้เลย


สุดท้ายนี้ อยากจะฝากไว้ว่า ถ้ารักซอฟต์แวร์ไหน ใช้ตัวไหนอยู่ อยากให้หันมาอุดหนุนนักพัฒนากันอย่างถูกลิขสิทธิ์ ใช้ของแท้กันเยอะๆนะครับ ขอบคุณผู้อ่านทุกท่าน ครั้งหน้าถ้ามีโปรแกรมไหนดีๆ ผมจะจับมารีวิวแน่นอนครับ


## **Referrence**

- [**apphousekitchen**](https://apphousekitchen.com/pricing/)
- [**Aldente repository on github**](https://github.com/AppHouseKitchen/AlDente-Charge-Limiter)</content:encoded></item><item><title>ปัญหาของเซิร์ฟเวอร์ Palworld (Dedicated)</title><link>https://pickyzz.dev/blog/palworld-dedicated-server-issue</link><guid isPermaLink="true">https://pickyzz.dev/blog/palworld-dedicated-server-issue</guid><description>ปัจจุบันปัญหาใหญ่ตรงนี้ก็ยังไม่ได้รับการแก้ไข จนกระทั้งมีข่าวล่าสุดว่า Official Server นั้นค่าใช้จ่ายพุ่งสูงมากถึง 70 ล้านเยน</description><pubDate>Sat, 03 Feb 2024 06:39:00 GMT</pubDate><content:encoded>## **TL;DR (โม้ก่อน.. ข้ามได้)**


เรื่องมีอยู่ว่าผมมี Dedicated Server ที่ว่างเว้นอยู่หลังจากไม่ได้ทำ FiveM, RedM, etc. เลยขอเอามารันเซิร์ฟเวอร์เกม Palworld เล่นกับเพื่อน เพราะเห็นว่าเป็นเกมที่กระแสมาแรง และอยากจะเล่นกันเฉพาะกับกลุ่มเพื่อน ไม่อยากจะเข้าไป Official Server เพราะเคยเจอประสบการณ์ไม่ดีจากบางเกม (ไม่ขอยุ่งเรื่องดราม่ากับเกมข้างเคียงนะครับ ผมก็เป็นแฟนบอยของอีกเกมด้วยเหมือนกัน)


เกมบน Steam นั้นทำ Dedicated Server ไม่ยากครับ ทำได้หลายวิธี SteamCmd (ได้ทั้ง Win, Linux), WindowsGSM, LinuxGSM ในกรณีนี้ผมเปิดผ่าน SteamCmd เพราะว่าง่ายที่สุดแล้ว พิมพ์ command ไม่กี่ตัวก็เปิดได้เลย ขี้เกียจจนเขียน bat ให้เช็คอัพเดทแล้วเปิดไปเลยทีเดียว


ในครั้งแรกที่เปิดกับเพื่อน ก็ไม่ได้คิดว่าตัวเกมจะมีปัญหาอะไร ถึงแม้ว่าช่วงแรกไปไล่อ่านรีวิวเกมมีการพูดถึงประวัติของ Developer ว่าขยันออกเกมเป็น Early Access แล้วดองงานทิ้งไว้ก็ตาม

&gt; เอาง่ายๆคือรันเซิร์ฟเสร็จแล้วก็เล่นเลยอะครับ ไม่ได้มานั่งมอนิเตอร์อะไรเลย (อย่าหาทำ เป็นนิสัยในการ develop ที่ไม่ดีเลย ฮ่าๆๆ)

เปิดไว้แบบนั้นจนเวลาผ่านไปเกือบ 2 วัน (ใช่ครับอ่านไม่ผิด) ในเกมเริ่มมีอาการกระตุกจนเริ่มสังเกตได้ ก็เลยคิดว่าจะ restart สักหน่อย ก่อนทำการ restart โดยปกติผมจะทำการเช็คดูว่า ก่อนปิดมันกิน resource เครื่องอยู่ที่ประมาณไหน เพื่อเป็นตัวเปรียบเทียบไปในตัว

&gt; ผลก็คือ Process ของ PalServer ซดแรมไป 30GB จาก 32GB

ช็อคเลยทีนี้ ทำไมมันกินเยอะขนาดนี้เนี่ย!? เก็บความตกใจไว้ก่อน ทำการแบ็คอัพเซฟเกมไว้ก่อนเผื่อมีปัญหาจะได้ restore กลับมาได้ จากนั้นเปิดต่อโดยที่รอบนี้มอนิเตอร์เครื่องเซิร์ฟเวอร์ไปด้วย


## **สิ่งที่สังเกตได้ (ก่อน v.1.3.0)**


แจ้งก่อนว่าเซิร์ฟเวอร์ของผมรันอยู่บน **Windows server 2019** เป็น Dedicate แยกต่างหาก **ไม่ได้เป็น Share Server** ที่ซอยปล่อยเช่ากัน (ได้มาเพราะไปไถหัวหน้ามาครับ อิอิ) แรมที่มีคือ **32GB**

- เมื่อรัน server ของ Palword ใหม่ๆแรมจะไต่ขึ้นไปอยู่ที่ประมาณ 7-8 GB
- จากนั้นจะมีการใช้แรมเพิ่มขึ้นไปเรื่อยๆไม่หยุด (Memory Leaked)
&gt; ตอนนั้นมี Developer ทำการ patch แก้ไขโค้ดเพื่อลดการกินแรมลง ใช้ได้ผลพอสมควร ก่อนที่ตัวเกมหลักจะปล่อยอัพเดท v.1.3.0 ตามมาภายหลัง
- ทดลอง Restart ช่วงที่ไม่มีคนเล่นเลย (เพื่อนนอนกันหมด) พบว่า อัตราการบริโภคจะช้า หรือ คงที่
- ทุกครั้งที่มี Raid Event จะมีการกินแรมที่เพิ่มขึ้น (ในฝั่ง support discord คุยกันว่าปิดไปก่อน) **ตอนแรกเปิดไว้ เพราะจะได้มีอะไรทำ แต่มอนฯก็เกิดแล้วบั๊กในกำแพง ไม่ยอมวิ่งมาที่บ้าน เลยปิดดีกว่า
- หากมีการเปิดแผนที่ และ ลง Dungeon การใช้แรมก็จะเพิ่มสูงอย่างรวดเร็วจนสังเกตได้ (ข้อนี้ คุยกับคนที่เปิด Dedicated ท่านอื่นๆใน official discord ก็พบตรงกัน)
- หากมีการ config ให้ pal ที่บ้านทำงานตลอดเวลาก็กินแรมมากขึ้น แต่ช้ากว่าข้ออื่นๆทั้งหมด (อันนี้ผมเปิดไว้ เพราะถ้าปิดก็ไม่ต่างจากเปิด multi player ปกติสิ)
&gt; โดยส่วนตัวไม่พบปัญหากินแรมจนเกิดการ crash ตามที่พบในรีพอร์ท หลายๆคนเจอปัญหานี้ ฝั่ง Linux เองก็เช่นกัน เท่าที่สอบถามมา ส่วนใหญ่ที่พบจะเป็น Share host หรือไม่ก็รันผ่าน docker ที่จะพบอาการกินแรมจน out of Memory จนพังไปทั้ง instance ซึ่งน่ากลัวมาก ถ้าหากลากกันพังไปทั้งเครื่องที่มีหลาย instance รันอยู่พร้อมกัน

## **Update v.1.4.0**

- อาการ Memory Leaked ได้รับการแก้ไขแต่ยังไม่ทั้งหมด (เมื่อมีการเปิด Raid, ลง Dungeon ก็ยังเป็นอยู่ เพียงแต่ใช้เวลานานมากขึ้นกว่าแรมจะหมดถัง)
- หากเซิร์ฟเวอร์อยู่ในสถานะคงที่ เริ่มสังเกตว่ามีการคืนแรมกลับเล็กน้อยในบางช่วง (ในกรณีที่ไม่มีคนวิ่งเปิด map, ลง dungeon)
&gt; ผมขอพูดถึงในฝั่ง Server อย่างเดียวก่อน เพราะฝั่งผู้เล่นมีการแก้บั๊กไปพอสมควรแล้ว เรื่อง Pal ชาร์จทะลุกำแพงก็ไม่เจอแล้ว ก่อนหน้านี้ลงดันบอส ชาร์จพุ่งเข้ากำแพง งงกันทั้งปาร์ตี้เลยทีเดียว

อย่างไรก็ตาม ปัจจุบันปัญหาใหญ่ตรงนี้ก็ยังไม่ได้รับการแก้ไข จนกระทั้งมีข่าวล่าสุดว่า Official Server นั้นค่าใช้จ่ายพุ่งสูงมากถึง 70 ล้านเยน (16.72 ล้านบาท ค่าเงิน 1 JPY/ 0.24 THB ณ วันเขียน) ไปแล้ว ซึ่งโดยส่วนตัวมองว่า ปัญหาอาจจะเกิดจากเรื่อง Memory Leaked นี่ด้วย ผมเลยเอามาเขียนโพสต์นี้


![01.png](/images/blog/palworld-dedicated-server-issue-content-1.png)


**Update 4/02/2024** ผมไปเจอข้อมูลเพิ่มเติมว่า ในบรรดา Server จำนวน 1000 กว่า server นั้น จัดการผ่าน K8s (Kubernetes) **โดยชายเพียงคนเดียว** 😱 ซึ่งก็เป็นไปตามที่คาดการไว้ในเรื่องของการ auto scaling ทำให้ค่าใช้จ่ายพอกพูนไปเรื่อยๆ


![02.png](/images/blog/palworld-dedicated-server-issue-content-2.png)

&gt; ส่วนตัวคาดว่าการที่ได้รับคำสั่งมาว่า server ต้องห้ามล่ม เลยทำให้เลือกวางระบบอยู่บน Cloud server และใช้งานแบบ Auto Scaling และด้วยความที่ไม่ได้มีการ optimizing ที่ดีพอ ทำให้ Ram Leaked เมื่อ Ram usage ทะลุเพดานระบบ cloud จะทำการขยาย Memory base ขึ้นไปเรื่อยๆ เพื่อเลี้ยงไม่ให้ระบบล่ม และเมื่อขาดการมอนิเตอร์ (แบบผมนีี่ล่ะ ฮ่าๆๆ) ก็ทำให้เมื่อบิลค่า cloud server ออกมาตอนสิ้นเดือนเป็นแบบที่เห็น

ขอบคุณทุกท่านที่สละเวลาอันมีค่าอ่านโพสต์นี้จนจบนะครับ หากมีอัพเดทผมจะทำการอัพเดทในโพสต์นี้ต่อไปครับ 🙏🏻


## **Reference**

- [**gamesradar**](https://www.gamesradar.com/palworld-dev-ceo-jokes-server-fees-could-bankrupt-the-studio-after-it-spends-dollar478000-to-never-let-the-service-go-down-no-matter-what/?fbclid=IwAR2_xfT0hb66sShm2KabL7hZfYfVYrtz4AZ3we6JBMv8M-qk6aCxKU0bPa0)</content:encoded></item><item><title>2023 in review</title><link>https://pickyzz.dev/blog/2023-review</link><guid isPermaLink="true">https://pickyzz.dev/blog/2023-review</guid><description>บล็อกก็ไม่ค่อยได้เขียน รีวิวชีวิตในปีนี้หน่อยก็ยังดี</description><pubDate>Sun, 31 Dec 2023 05:52:00 GMT</pubDate><content:encoded>และแล้วก็วนจนครบอีกปี เผลอแป๊บเดียวปี 2023 ก็จะหมดปีอีกแล้ว (ยังไม่ทันได้ทำอะไรเลยจริงๆ)


### _**สำหรับปีนี้ของผม**_

- ซื้อมอเตอร์ไซค์ 1 คัน เป็น Honda Monkey125 เพราะความชอบล้วนๆ
- เป็นปีที่ productive เสมอตัวกับปีที่แล้ว
- งาน coding น้อยลง (รับน้อยลงส่วนหนึ่ง ด้วยอะไรหลายๆอย่าง)
- เรียนรู้ Tech Stack ใหม่ๆน้อยลง แต่โฟกัสตัวที่เขียนอยู่แล้วให้ลึกขึ้น
- ย้ายโดเมนเว็บจาก Google(ปิดให้บริการจดโดเมน) ไปที่ Cloudflare ให้เป็นผืนเดียวทั้งระบบ และราคาถูกลง
- ลงคอร์สเรียนพากย์เสียง “คอร์สแวววับฟ์แจ่มจรัสกรรรรรร” โดย อากุ้ง [**กุ้งนักพากย์วัยสะรื่น**](https://www.facebook.com/profile.php?id=100093662121825)(เขียนลงเฟสบุ๊ค แต่ยังไม่ได้เขียนลงบล็อกเลยครับ จะเขียนแยกอีกทีแน่นอน) สนุกและได้ประสบการณ์อะไรดีๆ เพิ่มความชอบในงานสายนี้ ทำให้ตัวเองพยายามฝึกและจริงจังมากขึ้นกว่าปีที่แล้ว
- ที่บ้านตัดสินใจถางที่นาที่มีอยู่หลายไร่ ทำนาไว้กินเองในครัวเรือน ผลผลิตดี กินทั้งปีก็ไม่หมด เลยแบ่งขายในหมู่บ้าน
- ซื้อโรงสีข้าวขนาดเล็กไว้ใช้ในเครือญาติ (ร่วมหุ้นกันซื้อ) จะได้ไม่ต้องไปถูกโรงสีเอาเปรียบ
- เริ่มใช้ชีวิตหน้าคอมน้อยลง พยายามออกไปข้างนอกให้มากขึ้น ชีวิตเริ่มกลับมาสมดุล
- ออกกำลังกายอยู่ช่วงหนึง ด้วยการตื่นเช้าไปวิ่ง แต่กลับไปลูปนอนดึกเพราะงานโค้ดเข้ามาเลยต้องทำงานดึก
- ได้รู้จักผู้คนเยอะขึ้น ทั้งจากงานโค้ด และจากการฝึกพากย์
- แมวโตแล้ว แมวดื้อเงียบ
- โสดเป็นปีที่ 6 หรือ 7 แล้วไม่แน่ใจ (ขี้เกียจนับแล้ว)

### _**สิ่งที่คาดหวังในปี 2024**_

- รักษาสมดุลชีวิต สุขภาพ จิตใจ
- อยากลงงานพากย์จริงๆสักชิ้น
- หาความรู้ในเรื่องที่ทำอยู่ให้มากๆยิ่งขึ้นไป ไม่หยุดพัฒนาตัวเอง แต่ก็ไม่แข่งกับใคร แข่งกับตัวเองเมื่อวานก็เหนื่อยแล้ว
- มีกัลยาณมิตรที่ดีเหมือนทุกวันนี้
&gt; สุดท้ายนี้ ขอให้ปีที่กำลังจะผ่านไป เอาเรื่องที่ไม่ดีออกไปด้วย และปีที่กำลังจะเข้ามา นำสิ่งดีๆเข้ามาด้วย ทั้งตัวเองและผู้อ่านทุกท่านครับ สวัสดีปีใหม่ครับ 🙏🏻

&lt;figure class=&quot;notion-video&quot;&gt;
  &lt;iframe width=&quot;100%&quot; height=&quot;480&quot; src=&quot;https://www.youtube.com/embed/KOPA1FTudL0&quot;
    title=&quot;YouTube video player&quot; frameborder=&quot;0&quot;
    allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
    allowfullscreen&gt;&lt;/iframe&gt;
  
&lt;/figure&gt;</content:encoded></item><item><title>ปัญหาระหว่าง Bun กับ Sharp</title><link>https://pickyzz.dev/blog/fix-sharp-issue-with-bun</link><guid isPermaLink="true">https://pickyzz.dev/blog/fix-sharp-issue-with-bun</guid><description>อัพเดทล่าสุด bun v1.0.6 เหมือนว่าจะแก้ไขปัญหานี้แล้ว แต่มีข้อแม้นิดหน่อย</description><pubDate>Fri, 13 Oct 2023 23:50:00 GMT</pubDate><content:encoded>## **อัพเดทล่าสุด 20/01/2024**


ล่าสุดอัพเดท Bun มาเป็นเวอร์ชั่น 1.0.24 ก็พบว่าแก้ปัญหาการติดตั้ง dep ย่อยแล้ว ไม่ต้องพึ่งพา trustedDependencies แล้ว (กราบ~)


![01.png](/images/blog/fix-sharp-issue-with-bun-content-1.png)


![02.png](/images/blog/fix-sharp-issue-with-bun-content-2.png)


ลบออกแล้ว ci ก็ยังไม่พัง เย้~


---


จาก [**ความเดิมตอนที่แล้ว**](https://pickyzz.dev/blog/bun-and-sharp-issue)


ล่าสุด Bun ออกอัพเดท v1.0.6 ซึ่งเหมือนว่าจะแก้ไขปัญหาที่ไม่ยอมติดตั้ง sub dependencies หลายๆตัวแล้ว


โดยวิธีแก้ไขปัญหาก็ใช้วิธีเดิมที่ทางคอมมูนิตี้ได้เคยออกมาแจ้งก่อนหน้านี้ คือ ให้ทำการเพิ่ม trustedDependencies เข้าไป ยกตัวอย่างผมใช้ sharp ที่ไม่ยอมติดตั้งให้


```bash
&quot;trustedDependencies&quot;: [
    &quot;sharp&quot;
  ],
```

&gt; ที่ผ่านมาในเวอร์ชันก่อนๆ ได้เคยลองวิธีนี้แล้ว แต่ก็เหมือนเดิม

โดยสามารถเพิ่มเข้าไปในไฟล์ package.json ได้เลย โดยที่ผมไม่ได้เพิ่ม sharp เข้าไปในส่วน dependencies แต่อย่างใด


จากนั้นทำการลบโฟลเดอร์ node_modules และ ไฟล์ bun.lockb ออก แล้วทำการ install packages ทั้งหมดใหม่เลย


ผลคือ


```bash
bun install v1.0.6 (969da088)
....
 + react@18.2.0
 + react-dom@18.2.0
 + reading-time@1.5.0
 + rehype-external-links@3.0.0
 + remark-collapse@0.1.2
 + remark-toc@9.0.0
 + satori@0.10.9
 + tailwindcss@3.3.3
 + ts-node@10.9.1
 + typescript@5.2.2
sharp: Using cached /Users/pickyzz/.npm/_libvips/libvips-8.14.5-darwin-arm64v8.tar.br
sharp: Integrity check passed for darwin-arm64v8
```

&gt; ผ่านแล้วโว้ย!!!!

จากนั้นผมได้ลองสั่ง build ดู จากเดิมที่เคยติดปัญหาในช่วงที่ jampack ทำการ optimize ไฟล์ output มาตลอด (ที่จากเดิมแก้ปัญหาด้วยการ cd เข้าไป install sharp เอง)


พบว่า build จนจบขั้นตอนได้ และมาถึงส่วนที่ลุ้นที่สุดคือ แก้ ci แล้ว github action จะรันผ่านไหม ก็จัดการแก้จากเดิม


```bash
- name: &quot;Install dependencies&quot;
-        run: bun i --no-save &amp;&amp; cd ./node_modules/sharp &amp;&amp; bun i --no-save
+        run: bun i --no-save
```


ก็ผ่านเช่นเดียวกัน


![03.png](/images/blog/fix-sharp-issue-with-bun-content-3.png)


## **สรุป**


~~ปัญหาน่าจะได้รับการแก้ไขแล้ว แต่ส่วนตัวมองว่าเป็นการแก้ไขแบบแก้ขัดให้ใช้งานได้ในระดับหนึ่งก่อน ซึ่งเมื่อเปรียบเทียบกับ runtime ตัวอื่นๆ ที่ไม่ต้องแก้ไขปัญหาด้วยการเพิ่ม trustedDependencies ในไฟล์ package.json เลย (ในอนาคตอาจจะได้รับการแก้ไขตรงนี้ตามมา) แต่ก็ถือว่าคอมมูนิตี้ของ bun นั้นเติบโตได้เร็วอยู่ไม่น้อย~~


ในอัพเดทล่าสุดพบว่าปัญหานี้ได้รับการแก้ไขแล้ว และผมเองยังไม่พบปัญหาใดๆเพิ่มเติม จากการที่ผมใช้งานแค่ส่วนของ runtime เพื่อการ build เว็บอย่างเดียว


หวังว่าจะเป็นประโยชน์กับผู้อ่านนะครับ</content:encoded></item><item><title>uptime 30 years of me</title><link>https://pickyzz.dev/blog/up-time-30-years</link><guid isPermaLink="true">https://pickyzz.dev/blog/up-time-30-years</guid><description>ผ่านไปแล้วอีก 1 ปี ใช้ชีวิตก้าวเข้าสู่เลข 3 ครั้งแรก รู้สึกแปลกๆใจนิดหน่อย</description><pubDate>Sat, 30 Sep 2023 10:14:00 GMT</pubDate><content:encoded>ผ่านไปแล้วอีก 1 ปี ใช้ชีวิตก้าวเข้าสู่เลข 3 ครั้งแรก รู้สึกแปลกๆใจนิดหน่อย ตอนสมัยเด็กๆแอบคิดว่าคนที่อายุ 30 นี่ดูอายุเยอะ มีความเป็นผู้ใหญ่มาก (จริงๆตอนนั้นก็คิดว่าแค่ 25+ ก็ดูเป็นผู้ใหญ่แล้ว) แต่พอมาถึงคราวตัวเองกลับไม่รู้สึกว่าเป็นแบบนั้น ยังรู้สึกว่ายังอยากลองทำอะไรอีกหลายๆอย่างอยู่เลย


## **สิ่งที่ทำแล้วในช่วง 1 ปีนี้**

- ออกมอเตอร์ไซค์ใหม่เล็กๆ ไม่ชอบรถใหญ่
- ยังคงรับงานเว็บในฐานะฟรีแลนซ์
- เริ่มสนุกกับงานใช้เสียง ส่งแคสต์งานทางเสียงเยอะขึ้น ซ้อมเยอะขึ้น จริงจังมากขึ้น (ถึงจะยังไม่ติดสักงานก็ตาม)
- ลงคอร์สเรียนพากย์
- ขับมอเตอร์ไซค์ออกไปเที่ยวบ้าง ไม่ไกลมาก นานๆครั้ง ให้ในหัวไม่ต้องคิดอะไรมากมาย
- เรียนรู้ tech stack ใหม่ๆ มากขึ้น รวมถึงแนวการคิดที่เป็นระบบระเบียบมากขึ้น
- ไปดูหนังในโรงหนังบ่อยขึ้น (จากปกติถ้าไม่มีคนไปด้วยจะไม่ค่อยไป)
- จะใช้จ่ายอะไรก็รอบคอบมากขึ้น
- กลายเป็นคนที่ดูแลเรื่องบัญชีของบ้าน (จริงๆก็ดูแลมาสักพักแล้ว)
- แมวตัวอ้วนขึ้น เพราะวันๆกิน แล้วก็นอน
- ตื่นไปวิ่งตอนเช้า (ทำได้เดือนเดียวฝนก็ตกตอนเช้ารัวๆ)
- เปลี่ยนเวลานอนให้เป็นเหมือนชาวบ้าน (อันนี้ได้บ้าง ไม่ได้บ้าง)

## **สิ่งที่วางแผนไว้**

- หางานที่นอกเหนือจากงานโค้ดให้ได้อีกทาง อยากทำหลายๆอย่าง
- เอารถยนต์ไปเปลี่ยนฟิล์มกรองแสงสักที
- พยายามพัฒนาสกิลตัวเอง แก้นิสัยบางอย่างที่ดูเหมือนคน introvert ซึ่งขัดกับงานที่ทำ
- หมดฤดูฝนจะไปวิ่งให้เป็นกิจวัตร
- ขับมอเตอร์ไซค์ไปเที่ยวคนเดียวให้บ่อยขึ้น (แต่ก็อยากมีงานมากขึ้นไปพร้อมๆกันนะ เอ๊ะยังไง ?)
- จดจำคนให้เก่งขึ้น
- ถ้างานต้องเข้า กทม. แบบเลี่ยงไม่ได้ก็จะพยายามหางานที่สามารถจัดการวางแผนล่วงหน้าให้ได้ (จะได้ไม่กระทบทางบ้าน)
- ดู Attack On Titan ตอนจบ (ทั้งซับและพากย์ไทย)

ช่วงหลังมานี้เหมือนที่บ้านจะไม่ถามหาแฟนแล้ว (น่าจะตัดใจไปแล้ว ฮ่าๆ) ส่วนตัวก็ชินแล้ว ไม่ได้คิดว่าจะต้องหาให้ได้หรืออะไรยังไง ปล่อยให้มันเป็นเรื่องของจังหวะของชีวิตไป พยายามโฟกัสกับการใช้ชีวิตและงานมากกว่า ชีวิตก็มีความสุขดี อย่าไปคิดอะไรที่เป็นทุกข์มาก เพราะที่ผ่านมาช่วงที่ใช้ชีวิตอยู่กับการคิดลบ ไม่ได้ช่วยให้เราดีขึ้นเลย หาอะไรที่เป็นการพัฒนาตัวเองไปเรื่อยๆดีกว่า เอาตัวเองไปอยู่ในที่ที่คนเห็นความสามารถ และพัฒนาตัวเอง ฟังคนที่เก่งกว่า จุดไหนแย่ก็ปรับปรุง จุดไหนดีก็พัฒนาต่อไป อย่าหยุดอยู่ที่เดิม


เอาล่ะ เขียนเท่านี้พอดีกว่า ยิ่งเขียนยิ่งเหมือนชวนขายตรง

&gt; แปะเพลงเฉยๆ ติดตาม SiM มานาน (แอบแปลกใจเพลงนี้ไม่มีพาร์ทเร็กเก้ที่เป็นกิมมิควง)

&lt;figure class=&quot;notion-video&quot;&gt;
  &lt;iframe width=&quot;100%&quot; height=&quot;480&quot; src=&quot;https://www.youtube.com/embed/IPX-L2F78fU&quot;
    title=&quot;YouTube video player&quot; frameborder=&quot;0&quot;
    allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
    allowfullscreen&gt;&lt;/iframe&gt;
  
&lt;/figure&gt;</content:encoded></item><item><title>ปัญหาที่เจอเมื่อใช้ Bun ทำงาน</title><link>https://pickyzz.dev/blog/bun-and-sharp-issue</link><guid isPermaLink="true">https://pickyzz.dev/blog/bun-and-sharp-issue</guid><description>ผมขอข้ามเรื่องเชิงเทคนิคของ Bun เพราะน่าจะมีหลายๆท่านที่เชี่ยวชาญกว่าผมได้เขียนถึงเยอะแล้ว</description><pubDate>Thu, 28 Sep 2023 06:46:00 GMT</pubDate><content:encoded>## **บ่นก่อน**


เมื่อไม่นานมานี้ Bun เครื่องมือทำมาหากินคู่แข่ง npm, pnpm ได้ออกเวอร์ชั่น 1.0 สักที หลังจากที่อยู่ในสถานะ beta มานานพอสมควร แอบสารภาพว่าในช่วง v.0.8 เป็นต้อนมา ผมได้เอามาใช้ในการ build เว็บนี้ด้วย แต่ว่าก็ไม่ได้ใช้งานได้อย่างราบรื่นนัก แต่เดี๋ยวเอาไว้กล่าวถึงอีกครั้ง ช่วงแรกขออวยก่อน


ผมขอข้ามเรื่องเชิงเทคนิคของ Bun เพราะน่าจะมีหลายๆท่านที่เชี่ยวชาญกว่าผมได้เขียนถึงเยอะแล้ว ผมจะขอพูดถึงในมุมของผู้ใช้งานทั่วไป ที่เอามาใช้ run install กับ build ไม่ได้หวือหวามากมายอะไร


## **เร็วจริงๆ**


ต้องบอกว่าครั้งแรกที่ได้ยินชื่อ Bun ในหน้าเว็บมีการเคลมตัวเองไว้ว่า **เร็วกว่า javascrip runtime ตัวอื่นๆ** แถมยังมีการเอากราฟผลการทดสอบมาแปะไว้พร้อม (อะไรจะขนาดนั้น)


พอได้เอามาทดลองใช้งานก็พบว่า มันก็เร็วจริงๆ ทั้งในแง่ของการ install dependencies ไปจนถึงการสั่ง build หน้าเว็บ โดยผมเปรียบเทียบกับการใช้งาน pnpm ในงานเดียวกันเปรียบเทียบกัน ก็รู้สึกได้ว่าเร็วกว่านิดหน่อย


## **เรื่องจุกจิกที่เจอ**


นอกเหนือจากความเร็วที่น่าประทับใจแล้ว ก็ไม่ได้มีจุดอื่นที่แตกต่างจนต้องตื่นเต้นอะไรมากมายนัก แต่ปัญหาเดิมๆตั้งแต่ตอนยังไม่ release 1.0 ก็ยังไม่ได้ถูกแก้และมีคนเจอปัญหาเหล่านี้อยู่เรื่อยๆ


### _**มีปัญหากับ sub-dependency**_


Bun มีปัญหากับ sub-dependency ที่ถูกเรียกใช้จาก package บางตัว ซึ่งผมมีการใช้ Jampack โดยตัวมันจะเรียกใช้ Sharp เป็น sub-dependency ในทำการ process ไฟล์รูปภาพ โดยเว็บเฟรมเวิร์คบางตัวก็มีการเรียกใช้งานตัวนี้ตั้งแต่แรก


โดยปัญหาก็คือ ในขั้นตอนการ run install นั้น บรรดา sub-dependency ทั้งหลายจะไม่ถูกติดตั้งทันที เมื่อเปรียบเทียบกับการ run install จาก runtime ตัวอื่นๆ

&gt; ทางแก้แบบกำปั้นทุบดิน คือ ต้องสั่ง install มันเอง

เช่น ผมใช้ jampack (ที่มี sharp เป็น sub-dependency อีกที) หลังจาก bun install ผมต้อง cd เข้าไปในโฟลเดอร์ของ sharp แล้วไปรัน bun install อีกครั้ง


```bash
cd ./node_modules/sharp &amp;&amp; bun install
```

&gt; สามารถเอาไปเขียนไว้ postinstall ที่ไฟล์ package.json ก็ได้

```bash
&quot;postinstall&quot;: &quot;cd ./node_modules/sharp &amp;&amp; bun install&quot;
```


แต่ผมไม่ได้ทำแบบนั้น เพราะมีการใช้งาน renovate bot ในการอัพเดท node package ต่างๆอยู่ด้วย แล้วการที่เขียน postinstall ไปแบบนั้น ทำให้เกิดปัญหา บอทจะมองว่า artifact error ที่ตอนแรกผมก็คิดว่าด้วยความที่ Bun ค่อนข้างใหม่อยู่ เลยทำให้ renovate อ่าน lockfile ไม่ได้ แต่ปัญหาไม่ได้อยู่ที่ตรงนั้น เพราะเมื่อลอง bun install —yarn เพื่อให้ export yarn.lock ออกมาด้วย ก็พบว่ายังเป็นปัญหาอยู่ เลยได้ใช้เวลาแก้ปัญหาจุดนี้อยู่หนึ่งวัน จนได้ข้อสรุปออกมาตามที่ได้เขียนไป

&gt; สรุป ทำบน Local dev ไม่มีปัญหา สบายๆเลย แค่มันตีกับ renovate ล้วนๆ

สุดท้ายผมแก้ด้วยการเขียน github action ตอน build ให้ทำการ install ก่อนแล้ว cd เข้าไปติดตั้ง sharp ตามหลังเองต่างหาก โดยแยกออกมาเป็น script ใหม่แทน ไม่เอาเข้าไปอยู่ใน postinstall แล้ว เป็นอันจบ build &amp; deploy ผ่าน


### **เล่าเสริมนิดหน่อย**


แต่เดิมตอนที่ bun ยังไม่ได้ release 1.0.0 ออกมา ผมใช้วิธีการลักไก่ ใช้ pnpm ในการ run install package ก่อน แล้วใช้ bun run build ตามทีหลัง ก็พบว่าใช้วิธีนี้ก็ได้ แต่ตอนเขียน ci/cd ก็ไม่รู้ว่าต้องมาเสียเวลาโหลด runtime สองตัวเพื่ออะไร งานก็ไม่ได้ใหญ่อะไรมาก


### _**lock file ใหม่ เนื้อเดิม ทุกครั้งที่ install**_


อันนี้ไม่รู้ว่าเป็นเพราะอะไร เหมือนจะเคยเป็นมาก่อนในเวอร์ชันก่อนตัวเต็ม และได้ถูกแก้ไขไปแล้ว แต่ก็กลับมาเป็นอีกเรื่อยๆ เดี๋ยวเป็น เดี๋ยวหาย

&gt; ไม่ได้กวนใจอะไรมากนัก แค่พอทำงานกับ git แล้วมันกวนใจทุกที

## **สรุป**

- ในแง่ของความเร็ว Bun ทำได้ตามที่กล่าวอ้างจริงๆ คือ เร็วมาก
- ยังไม่เหมาะกับการเอามาใช้ทำงานจริง
- คอมมูนิตี้โตเรื่อยๆ มีการถาม-ตอบ แก้ไข เปิด pull-request กันตลอด อนาคตท่าทางสดใส</content:encoded></item><item><title>AI ในทัศนะของข้าพเจ้า</title><link>https://pickyzz.dev/blog/ai-in-my-opinion</link><guid isPermaLink="true">https://pickyzz.dev/blog/ai-in-my-opinion</guid><description>ในมุมของของผม ai มีความจำเป็น แต่หาใช่การนำมาแทนมนุษย์ทั้งหมด</description><pubDate>Fri, 04 Aug 2023 12:52:00 GMT</pubDate><content:encoded>สวัสดีครับ หลังจากหายไปนาน เนื่องจากติดภารกิจหลายอย่างที่นอกจากงานโค้ดแล้ว ผมได้ขยับไปเริ่มทำอย่างอื่นอย่างจริงจังมากขึ้นด้วย อันที่จริงแล้วผมอาจจะเหนื่อยจากงานเดิม กอปรกับการทำงานฝั่งนี้อย่างเดียวมันค่อนข้างจะใช้ความคิดมากไป จนอาจจะทำให้แรงจูงใจต่องานของผมมันเริ่มลดลง แนวทางแก้ไขที่หลายๆคนรอบตัวแนะนำ คือ การขยับไปทำอย่างอื่นบ้าง ผมจึงหายหน้าหายตาไปพักใหญ่


## **โดยสรุป**

&gt; ถ้า ai ถูกพัฒนาโดยมีจุดมุ่งหมายเพื่อเป็นเครื่องอยู่ในการอำนวยความสะดวกในการทำงาน เป็นเรื่องที่ดีและน่าสนับสนุน หากแต่ในปัจจุบันเกิดปัญหาบานปลายจนเกินกว่าเป็นการนำไปใช้ในงานที่เป็นความคิดสร้างสรรค์ หรืองานที่ต้องใช้เอกลักษณ์เฉพาะตัว รวมถึงการนำไปใช้ในเชิงส่งผลเสียต่อผู้อื่น ก่อให้เกิดปัญหา จนอาจเรียกได้ว่าเป็นเครื่องมือสร้างอาชญากรรมไปแล้ว ซึ่งเป็นเรื่องที่ผมรับไม่ได้และไม่ควรเกิดขึ้น

## **มุมมองต่อ ai**


การเข้ามาของ ai ในระยะหลังมีการพัฒนาการที่รวดเร็ว จากเดิมที่มีเป้าหมายการพัฒนาที่เน้นการเป็น “เครื่องมือ” อำนวยความสะดวกเพียงอย่างเดียว จนกลายเป็นการใช้ในการสร้างผลงานขึ้นมาใหม่ ซึ่งถ้ามองอย่างฉาบฉวยไม่ได้คิดอะไรมาก ก็เป็นเรื่องที่รับได้ เป็นความก้าวล้ำที่ ai มีการพัฒนาที่เห็นเป็นรูปธรรมจริง ซึ่งเป็นเรื่องที่ค่อนข้างจะขึ้นอยู่กับการกำหนดจุดประสงค์ในการใช้งานของผู้ใช้แล้ว ว่าจะนำไปใช้อย่างไร


## **ปัญหาของการใช้งาน ai**


อย่างที่กล่าวไปแล้วว่า ai มีความยืดหยุ่นในการใช้งานเป็นอย่างมาก ส่วนสิ่งที่เป็นปัญหา คือ การนำไปใช้ผิดวัตถุประสงค์ซึ่งก่อให้เกิดปัญหาที่กระทบต่อบุคคลอื่น


ตัวอย่างเช่น

- **การนำไปใช้สังเคราะห์เสียง หรือ ลักษณะเฉพาะอื่นๆ** โดยที่ใช้พื้นฐานมาจากบุคคลที่มีตัวตนจริง ซึ่งในปัจจุบันสามารถทำได้แล้ว โดยมีข่าว ซึ่งเกิดปัญหาขึ้นเมื่อไม่นานมานี้ คือ การนำเสียงของบุคคลอื่นมาใช้กระทำสิ่งอื่นโดยที่เจ้าตัวไม่ได้เป็นผู้กระทำ และยังไม่ได้รับการยินยอมจากเจ้าของเสียง [**(อ้างอิง)**](https://www.dualshockers.com/persona-5-voice-actor-ai-cover-song/?utm_campaign=trueanthem&amp;utm_medium=social&amp;utm_source=facebook&amp;fbclid=IwAR2_7qooy3DzQDdKEzYxApRQGmBymhGD928VU7ARu_denLlT3ysMOzvbRUI_aem_AakNDt7eDVBnp21K77agYC0gj4lva_6bWS8LDnDjDXkKk8C35JCEXWXt-K7PmqsMywA)
- **การนำไปจำลองลักษณะทางกายภาพ** ซึ่งมีพื้นฐานมาจากลักษณะทางกายภาพของบุคคลอื่น ไม่ว่าจะเป็นสื่อสิ่งพิมพ์ สื่อมัลติมีเดีย ซึ่งปัจจุบันมี ai หลายตัวที่ทำได้แล้ว
- **การถูกขโมยทรัพย์สินทางปัญญา** ไปใช้เป็นต้นแบบในการวิเคราะห์ เพื่อสร้างฐานข้อมูลของ ai โดยที่เจ้าของไม่ได้ยินยอม ซึ่งเรื่องนี้เองก็เกิดขึ้นตั้งแต่ ai เริ่มถูกเปิดใช้งานเป็นวงกว้างในระยะแรก ปัจจุบันเองก็ยังไม่ได้รับการแก้ไขอย่างเป็นรูปธรรม

ปัญหาที่ได้ยกตัวอย่างมานั้น ผมมองว่าส่งผลต่อบุคคลที่เป็นต้นแบบโดยตรง ซึ่งเราไม่สามารถรับรู้ได้เลยว่าฐานข้อมูลขนาดมหาศาลที่เกิดจากการเรียนรู้ของ ai นั้นมาจากข้อมูลของใครบ้าง ซึ่งบริษัทเกือบทั้งหมดไม่ได้มีการเปิดข้อมูลส่วนนี้ หากแต่ในทางกลับกัน การนำข้อมูลของเราเข้าสู่ระบบการเรียนรู้ของ ai ก็ไม่พบเห็นการขอความยินยอมจากผู้ใช้ในหลายๆที่เช่นเดียวกัน


**หากจะจี้ให้ถูกจุด เกาให้ถูกที่คัน** ก็ต้องกล่าวว่าสิ่งที่ผมมองว่าเป็นปัญหาของการใช้งาน ไม่ได้เกิดจากตัว ai เสียทีเดียว แต่เกิดจากการที่ผู้ใช้งาน รวมถึงผู้สร้าง ai บางส่วน มองข้ามเรื่องความเป็นส่วนตัวของผู้อื่น การขอความยินยอมจากบุคคลต้นแบบเสียมากกว่า


และหากจะกล่าวว่าในสังคมที่ให้ความสำคัญกับเรื่องสิทธิส่วนบุคคลจะไม่เกิดปัญหาเหล่านี้ ก็ไม่ถูกเสียทีเดียว จากข่าวที่เกิดขึ้นในระยะหลัง ปัญหาเหล่านี้ล้วนเกิดในสังคมประเทศที่มีความก้าวหน้าทางสิทธิพลเมืองเสียส่วนใหญ่เสียด้วยซ้ำ


## **มุมมองการแก้ไขปัญหา**


ปัญหาที่เกิดขึ้น ส่วนใหญ่เกิดจากความขาดการใส่ใจเรื่องของความเป็นส่วนตัว ไม่ว่าจะเป็นทางฝั่งผู้สร้าง ai หรือจากฝั่งผู้ใช้เองก็ตาม ซึ่งการแก้ไขก็ต้องแก้ไขที่สาเหตุเหล่านี้ ไม่ว่าจะทางตรงหรือทางอ้อม


ตัวอย่างเช่น

- การมีเงื่อนไขต่อผู้สร้าง ai หรือ การให้บริการขององค์กร ที่รัดกุมมากขึ้น
- ผู้สร้าง ai ควรมีการเปิดเผยต่อผู้ใช้งาน ว่าข้อมูลส่วนใดจะถูกเก็บไปวิเคราะห์บ้าง การขอความยินยอมให้ผู้ใช้สามารถเข้าถึง รวมถึงการลบข้อมูลส่วนของตนเองในภายหลังได้
- เพิ่มความเป็นธรรมแก่ผู้ที่ถูกนำลักษณะจำเพาะ ผลงาน ลักษณะทางกายภาพใดๆ ไปใช้งานโดยไม่ได้รับความยินยอม

ซึ่งทั้งหมดที่ผมยกตัวอย่างมานั้น อาจจะไม่ได้ครอบคลุมในทุกประเด็น แต่ก็น่าจะเพียงพอในการชี้ให้เห็นว่า การสร้างสรรค์ปัญญาประดิษฐ์ขึ้นมาเป็นเรื่องที่ดีและน่าสนับสนุน แต่การนำไปใช้ในวงกว้างนั้น ควรมีการสร้างข้อตกลง และกฎที่ชัดเจน เพื่อป้องปัญหาที่เกิดขึ้นจากการใช้งาน เพราะถึงแม้ ai จะวิเคราะห์ทุกอย่างโดยอ้างอิงจากหลักเหตุผล แต่เราไม่สามารถรับรู้ได้เลยว่า ai จะตกไปอยู่ในมือของผู้ใช้ที่มีพื้นฐานความคิด ความรับผิดชอบต่อสังคมส่วนรวมแบบใด</content:encoded></item><item><title>แยก Repositoroy เก็บ Content</title><link>https://pickyzz.dev/blog/deploy-with-modules</link><guid isPermaLink="true">https://pickyzz.dev/blog/deploy-with-modules</guid><description>อยากแยกส่วนเนื้อหาออกไปไว้ที่ private repository แต่ก็อยากให้ action ทำงานตอน push เนื้อหา ทำยังไงดี ?</description><pubDate>Mon, 27 Feb 2023 22:00:00 GMT</pubDate><content:encoded>สวัสดีผู้อ่านทุกท่านครับ สำหรับเนื้อหานี้เกิดจากความอยากรู้อยากลองของผมล้วนๆเลย อยากจะแยกส่วนของเนื้อหาของเว็บออกไปเก็บไว้เป็น private repository แต่ก็อยากให้ทุกครั้งที่อัพเดทเนื้อหาใหม่ขึ้นไป ก็อยากให้ trigger ไปยัง action บน repository เพื่อทำการ build &amp; deploy ด้วย

&gt; ฟังดูวุ่นวายจัง จะเริ่มยังไงดี ?

หลังจากนอนคิดสัก 3 ชั่วโมงก็พบว่า… หลับครับ แต่ก่อนหลับก็ได้คำตอบว่าสิ่งที่ควรจะทำมีประมาณนี้


![01.jpg](/images/blog/deploy-with-modules-content-1.jpg)


จากภาพ เมื่อมี commit ใหม่บน content repository จะมีการ Trigger ไปยัง action บน main repository ให้ทำการ sync &amp; pull ทั้งจาก content และ media เกิดเป็น commit ใหม่บน repository ซึ่งจะทำให้ build &amp; deploy action ทำงาน


## **แยก repository ส่วนของเนื้อหา ออกจาก framework**


ผมทำการสร้าง repository ขึ้นมาใหม่เป็น private โดยแยกทั้งสองออกจากกัน repository แรกเอาไว้เก็บเนื้อหาบล็อกทั้งหมด และอีกอันเอาไว้เก็บไฟล์รูป โดยที่ทั้งสอง repo นั้นยังคง files structure ไว้เช่นเดียวกับตอนก่อนแยกไฟล์


จากนั้นลบโฟลเดอร์และไฟล์ทั้งหมดออกจาก repository หลักและทำการ commit ขึ้นไปก่อน เนื่องจากเราจะใช้ git submodule เพิ่มเข้ามาใหม่ (ตอนแรกผมไม่ได้ทำก่อนแล้วมัน conflict กันตอนเปิด pull request)


จากนั้น เปิด terminal บน directory ที่ต้องการ clone repository จากนั้นก็ทำการ clone ผ่านคำสั่ง


```bash
git submodule add -f [url] [folderName]
```


**หมายเหตุ** [url] กับ [folderName] ให้เปลี่ยนโดยไม่ต้องใส่ [] นะครับ และระวังการใช้ -f (force) ด้วยนะครับ อาจจะส่งผลในกรณีที่ใน directory มีไฟล์อื่นๆอยู่แล้ว


หากใครต้องการให้ submodule อัพเดททุกครั้งที่มีการ git pull อย่าลืมใส่คำสั่ง (ใส่แค่ครั้งแรกที่ทำ submodul ก็พอ)


```bash
git submodule update --init --recursive
```


ในส่วนของไฟล์รูปผมก็ทำเหมือนกัน ต่างกันเพียง directory เท่านั้น


จะเห็นว่าที่ repo หลักจะมีไฟล์ .gitmodule เพิ่มขึ้นมาพร้อมกับโค้ดประมาณนี้


```bash
[submodule &quot;src/content/blog&quot;]
	path = src/content/blog
	url = https://github.com/pickyzz/blog-posts
[submodule &quot;public/assets/images&quot;]
	path = public/assets/images
	url = https://github.com/pickyzz/blog-images
```

&gt; สังเกตว่า url จะเป็นคนละชื่อกับ path และ submodule เนื่องจากการใส่พารามิเตอร์ในส่วน [folderName] นั่นเอง

**หมายเหตุ** จะมีจุดให้ปวดหัวนิดหน่อยตอน push ผ่าน submodule ซึ่งผมเองก็เจอปัญหาตรงนี้นิดหน่อย ซึ่งการแก้ก็จะต่างกันออกไป ตรงนี้ไม่สามารถรับประกันได้ว่าจะราบรื่นไปตลอดนะครับ


## **เพิ่มขั้นตอนบนไฟล์ github action**


เมื่อเรามี submodule แล้วเราจำเป็นต้องเพิ่มขั้นตอนระหว่างการ build ด้วย เพราะ action จะไม่โหลดโมดูลให้อัตโนมัติ


โดยทำการเพิ่ม parameter เข้าไปที่ขั้นตอน checkout ดังนี้


```bash
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v3
        name: &quot;Checkout Source Code&quot;
        with:
          submodules: true
          token: ${{ secrets.GH_TOKEN }}

      - [other Step]...
```


อ้างอิงจาก [**actions/checkout**](https://github.com/actions/checkout#checkout-multiple-repos-private)


**เพิ่มเติม** ในการทดสอบครั้งแรกพบว่า action หา private repository ไม่เจอ จึงหาข้อมูลเพิ่มเติมพบว่าใน github issue แนะนำให้เพิ่ม contents: read ในส่วนของ permissions ตามโค้ดด้านบน และ`อย่าลืมตั้งค่า private repository เพื่อ allow access ด้วย`


![02.png](/images/blog/deploy-with-modules-content-2.png)


![03.png](/images/blog/deploy-with-modules-content-3.png)


## **สร้าง github action ให้อัพเดท submodule อัตโนมัติ**


ผมจะยังคงให้ build action ทำงานทุกครั้งที่มีการ commit ใหม่ แต่ว่าผมจะคั่นกลางด้วยการอัพเดท submodule และ push commit ขึ้นไปใหม่โดยอัตโนมัติ จากเดิมที่เราต้องมาทำเองในโฟลเดอร์หลักของเว็บ เราก็ให้ action จัดการตรงนี้แทนเสียเลย


โดยผมจะแยก action ออกเป็นคนละไฟล์กับ build &amp; deploy action และให้ทำงานจากการ trigger จาก action ใน content repository โดยจะเขียน action ยิงมาจาก content repo เพิ่มภายหลังเพื่อไม่ให้สับสนขั้นตอน


สาเหตุที่แยกออกมานั้น เพราะ build &amp; deploy จะทำงานเมื่อมี commit ใหม่ ถ้าไปเช็คตรงนั้นมันจะเป็นการวนซ้ำ action เดิมสองรอบ ถึงจะไม่มี commit ใหม่แล้วแต่มันก็กินเวลา เขียนแยกไว้น่าจะดีกว่า


```bash
name: Submodule update check

on:
  workflow_dispatch:

jobs:
  update:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v3
        with:
          submodules: true
          token: ${{ secrets.GH_TOKEN }}

      - name: Pull &amp; update submodules recursively
        run: |
          git submodule update --init --recursive
          git submodule update --recursive --remote

      - name: Commit
        run: |
          git config --global user.name &apos;Git bot&apos;
          git config --global user.email &apos;bot@noreply.github.com&apos;
          git add --all
          git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}
          git commit -am &quot;Auto updated submodule references&quot; &amp;&amp; git push || echo &quot;No changes to commit&quot;
```


อ้างอิงจาก [**ที่พึ่ง dev ยามยาก**](https://stackoverflow.com/questions/64407333/using-github-actions-to-automatically-update-the-repos-submodules)


## **ทำ Trigger action ข้าม repository**


จากนั้นก็จัดการเขียน workflow โดยให้ทำงานทุกครั้งที่มี commit ใหม่บน content repository ให้ทำการสะกิดข้าม repo ไปยัง build action ตามที่คิดไว้ (repo เก็บรูปไม่ต้องทำ เพราะปกติจะอัพรูปก็ต้องทำพร้อมกับการเขียนเนื้อหา หรือไม่ก็อัพเดท ui อยู่แล้ว)


```bash
name: trigger build action
on:
  push:

jobs:
  build:
    runs-on: self-hosted
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          path: main

      - uses: convictional/trigger-workflow-and-wait@v1.6.5
        with:
          owner: pickyzz
          repo: blog-rebuild
          github_token: ${{ secrets.GH_TOKEN }}
          workflow_file_name: submodule.yml
```


**หมายเหตุ** เนื่องจากส่วนนี้ผมได้ทำการ selfhost action runner ต่างหากเอง ทำให้ผมใช้ `runs-on: self-hosted` หากต้องการใช้ runner ของทาง github สามารถใช้ `runs-on: ubuntu-latest` ได้ตามปกติ แต่จะมีการจำกัดการใช้งานบน private repository


## **สรุป**


workflow ทั้งหมดนั้นสามารถทำงานได้ปกติ จะมีติดขัดช่วงการ push commit ในครั้งแรกๆที่ทำ submodule เล็กน้อย ซึ่งการแก้ไขก็คือให้ทำการลบไฟล์บน directory นั้นแล้ว commit ขึ้น repository ไปก่อน 1 ครั้งตามที่ได้เขียนไว้


ขอบคุณทุกท่านที่เข้ามาอ่าน แล้วพบกันใหม่ในครั้งต่อไปครับ


## **references**

- [**futurestud.io**](https://futurestud.io/tutorials/github-actions-clone-another-repository)
- [**actions/checkout/issues/268**](https://github.com/actions/checkout/issues/268)
- [**taniarascia.com**](https://www.taniarascia.com/git-submodules-private-content/)
- [**convictional/trigger-workflow-and-wait**](https://github.com/convictional/trigger-workflow-and-wait)
- [**medium.com/@milwojarski**](https://medium.com/@milwojarski/release-the-limits-of-github-actions-with-self-hosted-runners-9ecdb6419934)
- [**Configuring the self-hosted runner application as a service**](https://docs.github.com/en/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service)</content:encoded></item><item><title>Remote Desktop ด้วย Moonlight&amp;Sunshine</title><link>https://pickyzz.dev/blog/remote_desktop_via_moonlight</link><guid isPermaLink="true">https://pickyzz.dev/blog/remote_desktop_via_moonlight</guid><description>เรื่องนี้เกิดจากปัญหาการจัดห้องที่ไม่ลงตัวของผมเองและด้วยพื้นที่ในห้องนอนที่แบ่งไว้นั่งทำงานด้วยส่วนหนึ่งจึงเป็นพื้นที่จำกัดสักหน่อย</description><pubDate>Wed, 18 Jan 2023 16:50:00 GMT</pubDate><content:encoded>## **ที่มาที่ไป**


เรื่องนี้เกิดจากปัญหาการจัดห้องที่ไม่ลงตัวของผมเอง และด้วยพื้นที่ในห้องนอนที่แบ่งไว้นั่งทำงานด้วยส่วนหนึ่งจึงเป็นพื้นที่จำกัดสักหน่อย บวกกับอาการขี้เกียจหันตัวสลับจอไปมา ในหลายๆครั้งที่ต้องเปิดทั้งพีซีและแม็ค ก็เลยมองหา Remote Desktop มาลองใช้หลายๆตัวก็พบว่าไม่ค่อยตอบโจทย์การใช้งานสักเท่าไรนัก


## **ปัญหาหลักที่พบใน Remote Desktop**

- การทำงานค่อนข้างมีอาการหน่วง
- บริโภคทรัพยากรในเครื่องเวลาใช้งาน ทั้ง Host และ Client

จากนั้นก็นึกขึ้นได้ว่าเคยเห็นการทำ Remoteplay ภายในบ้าน ที่เอาภาพจากเครื่องคอนโซลยิงออกมาเล่นผ่านอุปกรณ์ต่างๆ


## **เอามาทำกับ PC ได้ไหม ?**


ตั้งคำถามไว้แล้วก็ต้องหาคำตอบ ซึ่งผมได้ไปเจอเข้ากับ Repository หนึ่งชื่อว่า **Moonlight** [**[Link]**](https://github.com/moonlight-stream/moonlight-qt)อ่าน Readme ดูแล้วน่าสนใจ และน่าจะตอบโจทย์ในสิ่งที่ผมกำลังอยากทำพอดี


## **แนวคิดโดยสรุป**

- PC เป็นเครื่องที่ไว้สำหรับรับการเชื่อมต่อ (Server) (ต้องติดตั้งโปรแกรมสำหรับ Host)
- Macbook เป็นรีโมทเชื่อมต่อไปควบคุม PC (ต้องทำการติดตั้ง Moonlight)

## **แล้วเจ้า Moolight คืออะไร**


ทางผู้พัฒนาอธิบายไว้ว่าเป็นการทำงานด้วยหลักการเดียวกับเครื่อง Nvidia Shield คือ**ทำหน้าที่เป็นเครื่องลูก (Client)** เชื่อมต่อไปที่เครื่องที่ทำหน้าที่เป็นเซิร์ฟเวอร์อีกที ซึ่งในกรณีของผมเป็นเครื่อง Macbook


สามารถ Download เวอร์ชันล่าสุดจากผู้พัฒนาได้ที่ [**Link**](https://github.com/moonlight-stream/moonlight-qt/releases)


เมื่อติดตั้งเสร็จแล้ว เราพักเครื่อง Client ไว้ก่อน แล้วเราข้ามไปฝั่งเครื่อง Server กันบ้าง


## **เตรียมเครื่อง Server ที่จะ Remote**


ทางผู้พัฒนาเขาได้ให้ทางเลือกไว้ 2 ทาง คือ

- **ทางที่ง่ายที่สุดคือ ทำการติดตั้ง Nvidia Geforce Experience และเปิดฟังก์ชัน Gamestream**

![gfe-gamestream-enable-small.png](/images/blog/remote_desktop_via_moonlight-content-1.png)


**หมายเหตุ** เมื่อปลายปี 2022 ที่ผ่านมาได้มีประกาศการหยุดสนับสนุนฟังก์ชั่น Gamestream จากทาง Nvidia ออกมาว่าจะหยุดซัพพอร์ทในช่วงเดือนกุมภาพันธ์ 2023 ทำให้ผมตัดสินใจย้ายไปใช้ Sunshine แทน

- **ทางที่ Advance ขึ้นมานิดหน่อย ติดตั้ง Sunshine**

    สามารถ Download เวอร์ชันล่าสุดได้ที่ [**Link**](https://github.com/LizardByte/Sunshine/releases)


    **หมายเหตุ** ในกรณีที่เล่นเกมแล้วเครื่องมองไม่เห็น Controller แนะนำให้ติดตั้ง [**ViGEmBus**](https://github.com/ViGEm/ViGEmBus/releases/latest)

    - เมื่อติดตั้งโปรแกรมเสร็จแล้วให้เปิด browser แล้วไปที่ url

    ```bash
    localhost:47990
    ```


    ในครั้งแรก sunhine จะให้เราตั้ง username และ password เพื่อไว้ login ไว้เพื่อความปลอดภัย เสร็จแล้วหน้าเว็บจะรีเฟรช 1 ครั้ง ให้เราล็อกอิน


    **ในเวอร์ชั่น 0.17 จะไม่ detect หน้า desktop ให้เรา** แก้ไขได้โดยไปที่แถบ Application จากนั้นให้เพิ่มแอพใหม่ ตั้งชื่อว่า Desktop โดยไม่ต้องใส่รายละเอียดใดๆ


    ![01.png](/images/blog/remote_desktop_via_moonlight-content-2.png)


    จบสิ้นการเตรียมเครื่องฝั่ง Server แล้ว


    ## **ทำการเชื่อมต่อจาก Client**

    - ทำการเปิดโปรแกรม Moonlight ขึ้นมาหากเครื่องอยู่ในวง network เดียวกันตัว moonlight จะเจอเองโดยอัตโนมัติครับ แต่ถ้าไม่เจอสามารถ add ได้เองโดยกดปุ่มเครื่องหมาย + มุมบนขวาแล้วใส่ ip เครื่อง server ที่ต้องการ

        ![02.png](/images/blog/remote_desktop_via_moonlight-content-3.png)

    - คลิกที่เครื่องที่ต้องการเชื่อมต่อ จะมีหน้าต่างเด้งรหัส PIN ขึ้นมาให้เราไปเปิด browser บนเครื่อง server แล้วไปที่ localhost:47990 จากนั้นไปที่เมนู PIN แล้วกรอกตัวเลขชุดเดียวกันลงไป

        ![03.png](/images/blog/remote_desktop_via_moonlight-content-4.png)


        ![04.png](/images/blog/remote_desktop_via_moonlight-content-5.png)


    กลับมาที่เครื่อง Client ของเราอีกทีจะพบว่าไอค่อนที่ตอนแรกติดล็อคอยู่ได้ถูกปลดล็อคแล้ว และเมื่อคลิกเข้าไปจะพบ Desktop ที่เราเพิ่มไว้อยู่ด้วย (หากลืมเพิ่มไว้ในขั้นตอนแรก ให้มาเพิ่มทีหลังได้เหมือนกัน)


    ![05.png](/images/blog/remote_desktop_via_moonlight-content-6.png)


    **หมายเหตุ** หากเชื่อมต่อแล้วหน้าจอโปรแกรมดำสนิทให้ลองขยับเมาส์นิดหน่อยภาพจะกลับมาเหมือนเดิม


    ## **ทดสอบใช้จริง**


    ผมได้ทดลองใช้ในหลายๆรูปแบบทั้งการทำงานบางส่วน และการเล่นเกม พบว่าแทบจะไม่มีดีเลย์ อาจจะมีแต่ไม่ส่งผลต่อความรู้สึกจนหงุดหงิดอะไร ทั้งนี้ในการตั้งค่าความละเอียดของภาพเราสามารถตั้งที่ตัว Moonlight ได้เลย


    โดยส่วนตัวมองว่าตอบโจทย์เลย สามารถเปิดคอมทิ้งไว้โดยที่ไม่ต้องล็อกอินก่อน เพราะตัว server สามารถยิงภาพได้ตั้งแต่ logscreen เลย เราสามารถรีโมทไปล็อกอินจากเครื่องลูกก็ได้


    ส่วนที่น่าหงุดหงิดหน่อยจะเป็นในส่วนของ**ช่วงการอัพเดทเวอร์ชันฝั่งเครื่อง server แล้วส่งผลให้เสียงหายในเครื่องลูก ส่วนตัวผมแก้โดย เวลาอัพเดทผมจะลบตัวโปรแกรมฝั่งเซิร์ฟเวอร์ออกจนเกลี้ยง ไม่เก็บพวกไฟล์ตั้งค่าไว้เลย ก่อนทำการติดตั้งเวอร์ชันใหม่ ซึ่งแก้ปัญหานี้ได้พอสมควร**


    **หมายเหตุ** ในกรณีที่เน็ตเวิร์คที่บ้านมีอุปกรณ์ที่ใช้ร่วมกันหลายอุปกรณ์ (บ้านผมมีกล่อง internetTV อยู่ 2 ตัว) ควรมีการแยกวง network ใหม่เพื่อทำ Streaming เนื่องจากใช้แบนวิดธ์ค่อนข้างสูง


    จบแล้วครับ จริงๆวิธีนี้ผมใช้มาสักระยะแล้วตั้งแต่ช่วงปลายปีที่แล้วแต่ว่าไม่ได้เขียนให้เสร็จสักที ติดอะไรหลายๆอย่างเลยผลัดไปก่อน ตอนนี้ว่างแล้วก็เลยได้เขียนให้เสร็จสักที ซึ่งจริงๆผมทดสอบด้วยการต่อผ่าน internet ไว้ด้วย แต่ขอติดไว้ก่อนนะครับ เอาไว้จะมาเขียนวิธีการแยกต่างหาก สุดท้ายนี้ขอขอบคุณผู้อ่านทุกท่านครับ


    ## **Reference**

    - [**Moonlight repository**](https://github.com/moonlight-stream/moonlight-qt)
    - [**Moonlight Docs**](https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide)
    - [**Sunshine Repository**](https://github.com/LizardByte/Sunshine)
    - [**Sunshine Docs**](https://docs.lizardbyte.dev/projects/sunshine/en/latest/troubleshooting/windows.html)</content:encoded></item><item><title>2022 in review</title><link>https://pickyzz.dev/blog/2022-in-review</link><guid isPermaLink="true">https://pickyzz.dev/blog/2022-in-review</guid><description>เป็นปีที่ไม่ค่อยมี Productivity เท่าไร รวมถึงไม่ค่อยมีงานเป็นชิ้นเป็นอันเช่นกัน ส่วนใหญ่จะใช้เวลาไปกับการเรียนรู้สิ่งแปลกใหม่ พัฒนาสิ่งเก่าที่มีอยู่ในตัวเองเพิ่มขึ้นเสียมากกว่า</description><pubDate>Sat, 31 Dec 2022 23:45:00 GMT</pubDate><content:encoded>หากคุณกำลังอ่านบทความนี้อยู่ นั่นหมายความว่าปี 2022 ได้ผ่านพ้นไปเป็นที่เรียบร้อยแล้ว และเป็นธรรมเนียมปฏิบัติของตัวผมเองที่พยายามที่จะรวบรวมสิ่งที่ผมได้เจอได้ทำ หรือเกิดขึ้นในปี 2022 เอาไว้เป็นที่ระลึกเผื่อวันข้างหน้าได้กลับมาอ่านอีกครั้ง จะได้เกิดความฉุกคิดได้ว่าในแต่ละปีเรามีอะไรคืบหน้าไปมากน้อยแค่ไหนแล้ว


ปีนี้สำหรับผมแล้วมองว่าเป็นปีที่ Productivity น้อยปีหนึ่งเลยทีเดียว ส่วนใหญ่จะเป็นปีที่พยายามเรียนรู้ในสิ่งใหม่ๆ ลองสิ่งใหม่ๆเสียมากกว่า และเป็นปีที่ส่วนใหญ่ใช้เวลาไปกับการต่อเติมจัดการเรื่องบ้านกับครอบครัวมากกว่า

- เป็นปีที่เปลี่ยนสายงาน
- เป็นปีที่ต่อเติมบ้านเพิ่มหลายจุด
- เป็นปีที่ปรับเปลี่ยนอุปกรณ์ทำงานใหม่หลายอย่าง
- เป็นปีที่ได้เริ่มลองเรียนรู้ในสิ่งใหม่ๆ เช่น การพากย์เสียง
- เป็นปีที่ได้เพื่อนใหม่
- เป็นปีที่คุณพ่อเกษียณอายุราชการ
- เป็นปีสุดท้ายก่อนอายุขึ้นเลข 3 แล้ว แต่ก็ยังหาแฟนเป็นตัวเป็นตนไม่ได้ (😭)
- เป็นปีที่เขียนโค้ดได้น้อยลง แต่ยังคงเรียนรู้ Tech stack ใหม่ๆเพิ่มอยู่เรื่อยๆ ลองเอาอะไรมา build เล่นอยู่เรื่อยๆ
- เป็นปีที่ออกกำลังกายน้อยมาก ปีหน้าคาดว่าจะเอาใหม่ เพราะ Garmin ในมือบอกว่าเอ็งออกไปวิ่งสักทีสิไอมนุษย์

อยากให้ปีหน้า…

- ขยันมากขึ้นกว่านี้
- พยายามหางานใหม่เป็นหลักให้ได้สักที
- พยายามไปต่อในสิ่งที่ตัวเองอยากทำอยากไป ในขณะที่ยังมีแรง มีไฟในการทำอยู่
- พยายามจัดตารางวิ่งให้เป็นกิจลักษณะ และทำตามตารางวิ่งของตัวเองให้ได้
- อะไรที่ดีอยู่แล้วในปีนี้ ปีหน้าก็พยายามรักษามาตรฐาน หรือ ทำให้ดียิ่งขึ้นกว่าเดิม
- เรียนรู้สิ่งใหม่ พัฒนาสิ่งเก่า

สุดท้ายนี้ขอสวัสดีปีใหม่ 2023 แก่ผู้อ่านทุกท่าน ขอให้ทุกท่านมีสุขภาพกายที่ดี สุขภาพจิตที่ดี ไม่เจ็บป่วยทั้งกายใจ ใช้เงินอย่างรอบคอบ และไม่ยิงปืนขึ้นฟ้าในงานเทศกาล..


&lt;figure class=&quot;notion-video&quot;&gt;
  &lt;iframe width=&quot;100%&quot; height=&quot;480&quot; src=&quot;https://www.youtube.com/embed/QVKi3PqrZm0&quot;
    title=&quot;YouTube video player&quot; frameborder=&quot;0&quot;
    allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
    allowfullscreen&gt;&lt;/iframe&gt;
  
&lt;/figure&gt;</content:encoded></item><item><title>Uptime 29 years of me</title><link>https://pickyzz.dev/blog/uptime-29-years-since-1993</link><guid isPermaLink="true">https://pickyzz.dev/blog/uptime-29-years-since-1993</guid><description>สิ่งที่ฉันกำลังทำในวัย 29 ปี บันทึกไว้เผื่อในอนาคตกลับมาอ่านแล้วจะได้ทบทวนได้ว่าทำอะไรสำเร็จ หรือล้มเลิกอะไรไปแล้วบ้าง เผื่อว่านึกย้อนกลับมาอ่านแล้วจะมีหลายๆความรู้สึกเกิดขึ้นกับตัวเองในอนาคตได้บ้าง</description><pubDate>Fri, 30 Sep 2022 06:37:00 GMT</pubDate><content:encoded>สิ่งที่ฉันกำลังทำในวัย 29 ปี บันทึกไว้เผื่อในอนาคตกลับมาอ่านแล้วจะได้ทบทวนได้ว่าทำอะไรสำเร็จ หรือล้มเลิกอะไรไปแล้วบ้าง เผื่อว่านึกย้อนกลับมาอ่านแล้วจะมีหลายๆความรู้สึกเกิดขึ้นกับตัวเองในอนาคตได้บ้าง


## **สิ่งที่ทำอยู่ในวัย 29**

- รับงาน front-end แบบฟรีแลนซ์ จะตบบ่า หรือจ่ายเงินก็ทำ เพราะอยากทบทวนความรู้ตัวเอง และเรียนรู้สิ่งใหม่ๆ
- Project RedM กับทีม ซึ่งอยู่ในช่วงเริ่มต้น หลังจากที่เคยทำ FiveM แล้วแนวทางของตลาดมันเปลี่ยนแปลงไปมากจากแนวทางของพวกเรา
- ลงเรียนคอร์สสอนการพากย์เสียงกับ [**พี่เอ-อภินันท์**](https://www.facebook.com/GoodVoice/) อันนี้เป็นการเติมเต็มตัวเองในวัยเด็กจนโตที่ดูหนังก็ชอบดูพากย์ไทย และด้วยการที่อยู่ต่างจังหวัด การไปดูหนังที่โรงหนังก็จะมีตัวเลือกที่เป็นพากย์ไทยตายตัว ก็เลยดูและซึมซับ และสังเกตการพากย์ที่ดีขึ้นในทุกๆวัน แล้วก็นึกไปถึงช่วง ม.ต้น ที่ทำแฟลชอนิเมชั่นแข่งกับทางโรงเรียน แล้วได้ลองพากย์กันสนุกๆ ว่างพอดี แล้วพี่เอ ที่ได้ยินเสียงพี่แกตั้งแต่สมัยพากย์หนุ่มจืด เรื่องคนเล็กหมัดเทวดา เปิดคอร์สพอดี ก็เลยลงเรียน ซึ่งเริ่มคอรส์จริงวันที่ 9 ตุลาคม
- ย้ายที่อยู่ครบ 5 ปีแล้ว ไม่ว่าจะด้วยเหตุผลอะไร แต่ก็นั่นล่ะ ย้ายมาอยู่ที่ใหม่ครบ 5 ปีแล้ว
- และโสดมานาน 5 ปีแล้วเช่นกัน
- เปลี่ยนผ่านจาก Next.Js มาใช้ Astro แล้วก็เลิก Notion API แล้ว มันลำบากในการเขียนบล็อกเกินไป เปลี่ยนกลับมาเขียน Markdown แทน แต่เป็น .mdx แทน พอมันรวมกับ Astro ที่เขียนอะไรก็ได้ทั้ง vue, react, preact คราวนี้เว็บผมนี่กลายร่างเป็นสุกี้หม้อยำรวมไปแล้ว
- พยายามเอาใบ Certificate เพิ่มจาก Freecodcamp
- พยายามใช้ Javascript ให้น้อยลง เปลี่ยนมาใช้ Typescript ในการ init งานให้มากขึ้น
- เลี้ยงบักทอง ที่กลายเป็นแมววัยรุ่นที่วุ่นวายบ้านแตก
- อยู่ในช่วงขนของย้ายบ้านจากที่เก่า เพราะคุณพ่อเกษียณอายุราชการแล้ว

## **สิ่งที่ไม่ได้ทำต่อแล้ว**

- เลิกเล่นดนตรีมาประมาณ 5 ปีแล้ว ใช้ชีวิตเป็นผู้ฟังที่ดีแทน ฟังทุกแนว และยังหาเพลงแปลกๆทั้งเก่าและใหม่ฟังอยู่เรื่อยๆ
- งานสายกราฟฟิคที่เคยทำ ก็หยุดทำสายนี้มาประมาณ 3 ปีกว่าแล้ว หลังจากเบนเข็มมาทางโค้ดจริงจัง เพราะได้อาจารย์ที่ดีเลยไปได้ไวมาก
- ออกจากโลกของปีศาจได้แล้ว กลับมาเป็นตัวของตัวเองได้เต็มตัวแล้ว

ที่คิดออกในชีวิตตอนนี้ก็จะมีประมาณนี้ ยังเป็นอายุ 29 ที่เน้นความสนุกของตัวเองอยู่ แต่ก็คิดถึงเรื่องเงินและผลตอบแทนบ้าง ทุ่มแรงกาย แรงใจ แรงสมองให้ทีม WinDev เท่าที่ทุ่มได้ ต้องขอบคุณทีมนี้จริงๆที่ช่วยดึงศักยภาพในทางสายนี้ของตัวเองออกมา คิดไม่ออกเหมือนกันว่า ถ้าไม่มาเจอกัน ฉันในวันนี้จะกำลังทำอะไรอยู่ หลายๆอย่างก็ลุกขึ้นมาทำตามฝันเพื่อเติมเต็มตัวเอง ยังอยากเป็นฟรีแลนซ์อยู่เพราะยังอยากแบ่งเวลาไปทำสิ่งที่อยากทำเพื่อตอบสนองตัวเองอยู่บ้าง ไม่ทะเยอทะยานอยากได้อยากมีเหมือนใครเขา การไม่มีแฟน การใช้ชีวิตคนเดียวก็ไม่แย่ สนุกดี อยากทำอะไรก็ทำ ที่บ้านก็เข้าใจเราที่เป็นเรามากขึ้น (มั้ง ฮ่าๆ) ฉันในอนาคตกลับมาอ่านโพสต์นี้จะมีความคิดยังไงกันนะ ตอนนั้นจะทำงานอะไรอยู่นะ อยากรู้จัง


สุดท้ายนี้ก็ขอให้เป็นวัย 29 ที่ดีนะ..


&lt;figure class=&quot;notion-video&quot;&gt;
  &lt;iframe width=&quot;100%&quot; height=&quot;480&quot; src=&quot;https://www.youtube.com/embed/C7idsBxrG8I&quot;
    title=&quot;YouTube video player&quot; frameborder=&quot;0&quot;
    allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
    allowfullscreen&gt;&lt;/iframe&gt;
  
&lt;/figure&gt;</content:encoded></item><item><title>ทำ email routing ฟรีด้วย cloudflare</title><link>https://pickyzz.dev/blog/email-routing-gmail-cloudflare</link><guid isPermaLink="true">https://pickyzz.dev/blog/email-routing-gmail-cloudflare</guid><description>ช่วงหลังผมเปลี่ยนมาใช้ dns ของ cloudflare แทน เพื่อที่จะให้บริการ caching กับเรื่องการรักษาความปลอดภัยของเจ้านี้ แล้วก็เห็นว่าทาง Cloudflare เองมีบริการ email routing ให้ใช้ฟรีด้วย มีหรือเราจะพลาด</description><pubDate>Wed, 21 Sep 2022 17:06:00 GMT</pubDate><content:encoded>ปกติผมจะใช้บริการจดโดเมนกับ google แล้วเขาจะมีบริการ workspace แต่ก็ต้องเสียเงินเพิ่มก็เลยไม่ได้ใช้งานตรงนี้ แต่ช่วงหลังผมเปลี่ยนมาใช้ dns ของ cloudflare แทน เพื่อที่จะให้บริการ caching กับเรื่องการรักษาความปลอดภัยของเจ้านี้ แล้วก็เห็นว่าทาง Cloudflare เองมีบริการ email routing ให้ใช้ฟรีด้วย ตามธรรมเนียมของฟรี มีหรือเราจะพลาด


## **สิ่งที่ต้องมี**

- Domain name ที่อยู่บน NAMESERVER ของ Cloudflare
- email address ที่จะใช้ในการ foward mail ไป (ผมใช้ของ gmail)

## **ขั้นตอน**


การ setup ก็ไม่ยากอะไร (ซึ่งผมลืมเก็บภาพไว้ เพราะไม่คิดว่าจะมีเรื่องอะไรให้เขียนถึง)

1. เข้าไปที่ Dashboard ของ Cloudfalre และไปยัง Domain ที่ต้องการ จากนั้นเมนูทางซ้ายมือคลิกที่ Email

    ![01.png](/images/blog/email-routing-gmail-cloudflare-content-1.png)

2. กรอกชื่อ email ที่ต้องการ

โดยที่จะเป็น @domain ที่เราจดไว้เลย โดยที่ cloudflare จะทำหน้าที่เป็นตัวกลางในการ foward เมล์ที่ถูกส่งเข้ามาที่ address ของ cloudflare นี้ ไปยังปลายทางคือ address ของเรากับผู้ให้บริการต่างๆ เช่น gmail นั่นเอง ซึ่งเราต้องกรอกที่อยู่ email ที่ต้องการจะให้ foward ลงไปด้วย

1. Cloudflare จะทำการเพิ่ม setting เพิ่มลงใน dns

โดยที่จะถามและทำการเพิ่มโดยอัตโนมัติ ไม่ต้องทำอะไรทั้งนั้นกดผ่านได้เลย

1. ทำการยืนยันการ routing จาก cloudflare ในกล่องอีเมล์ _**ห้ามลืม**_

จากนั้นก็ทดลองส่ง email เข้าไปยังที่อยู่ที่เราสร้าง เพื่อทดสอบว่าทำงานได้ถูกต้องไหม แค่นี้ครับ ง่ายๆเลย


..เรื่องเหมือนจะจบแค่นั้น แต่แล้วก็พบปัญหาเข้าจนได้


![02.png](/images/blog/email-routing-gmail-cloudflare-content-2.png)


error ปรากฎ Foward ไม่ไป แล้วเหมือนว่าจะพยายาม redo task อยู่เรื่อยๆด้วย ซึ่งจะทำซ้ำกี่ครั้งก็เหมือนเดิม ก็เลยไปลองค้นดูว่ามีใครเจอปัญหานี้ไหม แล้วเขาแก้ไขกันยังไง


จากการค้นข้อมูล คาดว่าปัญหามันเกิดจากตัว email routing นี่มีปัญหากับ gmail และอีกประเด็นคือ email ที่ผมใช้งานนั้นมันเป็นลักษณะ [**xxxxx.xxxx@gmail.com**](mailto:xxxxx.xxxx@gmail.com) ซึ่งเหมือนมีคำอธิบายไว้ว่ารูปแบบพวกนี้มีปัญหากับการ routing นะ


**แล้วทางแก้ล่ะ ?**

- ไปที่หน้า DNS บน Dashbaord ของ Cloudflare

จะพบว่ามีค่า dns setting ที่เป็น TXT เพิ่มขึ้นมาใหม่ ให้เรา edit มันจากเดิมใหม่ ให้เป็น


```bash
v=spf1 a mx include:_spf.google.com include:_spf.mx.cloudflare.net  ~all
```


จากนั้นทำการสร้าง field TXT เพิ่มอีก 1 ช่อง และใส่ค่าเป็น


```bash
v=DMARC1; p=none; rua=mailto:เมล์ที่ตั้งใหม่@example.com; aspf=r;
```


จากนั้นก็บันทึกค่าและทดลองส่งเมล์ไปยัง email ที่สร้างใหม่ดูอีกครั้ง


![03.png](/images/blog/email-routing-gmail-cloudflare-content-3.png)


คราวนี้สามผ่านแบบไม่มีอะไรมาหยุดยั้งแล้ว สุดท้ายนี้หวังว่าจะเป็นประโยชน์ต่อทุกคนนะครับ


## **Reference**

- [**jay.gooby.org**](https://jay.gooby.org/2022/05/06/use-a-basic-gmail-account-to-send-mail-as-with-a-domain-that-uses-cloudflare-email-routing)</content:encoded></item><item><title>รีวิว KBDFans 67lite R2 หลังใช้มาแล้ว 1 ปี</title><link>https://pickyzz.dev/blog/kbdfans-67lite-r2-after-years-use</link><guid isPermaLink="true">https://pickyzz.dev/blog/kbdfans-67lite-r2-after-years-use</guid><description>จะพูดว่าเป็น gasket ก็พูดได้ไม่เต็มปากครับ มันจะเป็นลักษณะแผ่นซิลิโคนทั้งแผ่นทำหน้าที่เป็นทั้ง gasket และเป็นทั้งแผ่นคั่นกลางระหว่าง plate และ pcb ไปพร้อมกัน</description><pubDate>Sun, 11 Sep 2022 12:42:00 GMT</pubDate><content:encoded>## **บ่นก่อน**


ช่วงกลางปีที่แล้ว (2021) หลังจากที่ใช้งาน leopold มาได้พักใหญ่ๆ กิเลสในใจมันก็ยังไม่หมดไปในตอนนั้น กอปรกับในตอนนั้นมีคีย์บอร์ดรุ่น entry level เปิด Group buy รอบที่สองพอดี ตอนแรกผมก็หาอยู่ว่าตัวไหนดี อยากได้เป็นแนวๆ gasket mount ไม่มีน็อตยึดช่วงกลาง pcb กับเคสส่วนล่าง เพราะจากที่พิมพ์บน leopold หรือก่อนหน้านั้นเป็น Poker2 รู้สึกไม่พอใจสัมผัสในการพิมพ์ช่วงที่เป็นจุดยึดน็อต มันรู้สึกว่ามันมีแรงสะท้อนกลับที่มากกว่าจุดอื่น ทำให้ใช้งานนานๆมันล้านิ้ว มีอาการปวดข้อนิ้วมือ อาจจะฟังดูโอเวอร์นะครับ แต่ก็โอเวอร์จริงๆ (ฮ่าๆ) ไม่ใช่ครับ มันเป็นแบบนั้นจริงๆ


ครั้งแรกที่เจอตอนช่วงมีนาคม 2021 ตอนนั้นเป็นช่วงที่ R1 (Round 1) ในกลุ่มเขาเริ่มได้รับของกันแล้ว ก็เลยมีคนมาอวดในกลุ่มกันอยู่บ้าง ผมไปเห็นพอดีจึงจัดการหาข้อมูลว่ารุ่นนี้ดีไหม งานประกอบเป็นอย่างไร mounting แบบไหน ราคาเอื้อมถึงหรือไม่ ก็พบว่าเป็นแบบ silicon pad mounting ? คือจะพูดว่าเป็น gasket ก็พูดได้ไม่เต็มปากครับ มันจะเป็นลักษณะแผ่นซิลิโคนทั้งแผ่นทำหน้าที่เป็นทั้ง gasket และเป็นทั้งแผ่นคั่นกลางระหว่าง plate และ pcb ไปพร้อมกัน


![01.jpg](/images/blog/kbdfans-67lite-r2-after-years-use-content-1.jpg)


เจ้า R2 ของผมนั้นทางผู้ผลิตได้เปลี่ยน Stabilizer จาก R1 ที่เป็นของ Cherry มาเป็นของทาง KBDFans เอง ซึ่งวัสดุเป็น Poly Carbonate จากการใช้พบว่าใช้ดีพอสมควร แต่ถ้าจะให้สุดทางก็ควรเปลี่ยนเป็นของดีๆไปเลย แต่ผมไม่อยากรื้อบ่อยๆ ตั้งแต่ build มาก็รื้อมาแก้ทำ plumbing mod ไปหนึ่งครั้ง เนื่องจากมันมีจุดติที่น่าสนใจอยู่ซึ่งนั่นก็คือ มันกิน lube มาก ใช้ไปไม่นานก็มีเสียง rattle รบกวน เลยเป็นที่มาของการรื้อครั้งนั้น


![02.jpg](/images/blog/kbdfans-67lite-r2-after-years-use-content-2.jpg)


สวิทช์ที่ผมเลือกใช้ครั้งนี้จะเป็น Gateron Milky Yellow ราคาย่อมเยาว์นั่นเอง ด้วยความที่อยากประกอบให้คงคอนเซปต์ว่าเป็น entry level มากที่สุด (ความจริงคืองบหมดตอนโดนชาร์จภาษี ฮ่าๆ) สั่งโดยให้ร้านทำการ Lube ให้แต่ก็สั่งเซ็ต Lube มาพร้อมกัน เผื่อรื้อมาทาใหม่เองแล้วก็มาเพื่อทำการ Lubing Stabilizer ด้วย


![03.jpg](/images/blog/kbdfans-67lite-r2-after-years-use-content-3.jpg)


Key Caps ก็ใช้ตัวปกติทั่วไป พอดีผมอยากลองเปลี่ยน Profile ดู ก็เลยสั่งมาลองเปลี่ยนให้เจ้า Lepold ก่อน ซึ่งมันก็ชินมือไปแล้ว ก็เลยย้ายลงมาใช้ไปด้วย สีก็ดูไม่ขัดหูขัดตามากนัก

&gt; พูดเรื่องงานประกอบมาพอสมควรต่อไปข้อพูดเรื่องความรู้สึกในการใช้งานบ้าง

## **จุดที่ชอบ**

- เสียงตอนพิมพ์ค่อนข้างดี (ไม่มีคลิปทดสอบให้ฟังนะครับ ขออภัยในจุดนี้)
- สัมผัสการพิมพ์ที่กำลังดี ไม่นุ่มไม่เด้งจนเกินไป อันนี้อยู่ที่ความชอบล้วนๆ แต่ ณ entry level ผมว่าตัวนี้ในตอนนั้นที่ระดับราคานี้ออกมาไม่กี่รุ่น ถือว่าติดอันดับได้ไม่ยาก ส่วนปัจจุบันนี้ระดับราคานี้มีให้เลือกเยอะไปหมดแล้ว ถือเป็นโชคของผู้บริโภคจริงๆ
- ใช้สาย usb-c แล้ว
- มี blocker คั่นแยกระหว่างปุ่ม arrow keys และ Fn ฝั่งซ้าย
- รองรับ VIA &amp; QMK อันนี้บอร์ดคัสต้อมแทบทั้งตลาดรองรับเกือบทั้งนั้น สะดวกดีครับ ในการ remap ปุ่มต่างๆ แล้ว save ในตัวเลย ไม่ต้องลงโปรแกรมวุ่นวาย
- มีเคสกระเป๋าตรงรุ่นมาให้พร้อมในเซ็ตเลย ซื้อแยกทีหลังก็เกือบพัน (ไม่รวมส่ง/ภาษี)

## **จุดที่ไม่ค่อยประทับใจ**

- เคสชิ้นล่าง บริเวณจุดขันยึด ไม่มีชิ้นส่วนโลหะเหมือนชิ้นบน ทำให้เวลาประกอบต้องดูดีๆและใจเย็นๆ เพราะผมพลาดมาแล้ว ทั้งๆที่ค่อยๆขันยึดแล้วแต่ก็… แกร๊บ

![04.jpg](/images/blog/kbdfans-67lite-r2-after-years-use-content-4.jpg)

&gt; จุดนี้ต้องบอกว่าไม่ใช่ความผิดพลาดของผู้ผลิตนะ เขาไม่รับผิดชอบให้นะครับ แต่ว่าเรื่องนี้ผมเองก็ได้ feedback ไปให้ support ของเขาตั้งแต่แรกๆแล้ว โปรดักส์รุ่นหลังๆน่าจะแก้ไขส่วนนี้แล้ว
- Stabilizer เดิมๆค่อนข้างกิน Lube มาก!!!! แต่ด้วยความที่ผมไม่ยากสั่งรุ่นท็อปที่ช่วงนั้นของขาดตลาด ก็เลยแก้ด้วยการทำ Plumber mod ไปเรียบร้อย หายชะงักงัน
- มีรุ่นไร้สาย แต่ไม่รองรับ VIA/QMK เพราะใช้ pcb คนละรุ่นกับมีสาย

## **ความรู้สึกโดยรวม**


ส่วนตัวค่อนข้างมีความสุขในการพิมพ์งานมากขึ้น เพราะกระเป๋าเบาไปเลยช่วงนั้น ถือว่าเป็นตัวที่ใช้งานประจำวันไปเลย เพราะผมไม่ค่อยซื้ออุปกรณ์มาเก็บสะสม ส่วนใหญ่จะเน้นใช้งาน ส่วนของสะสมนั้นดูของท่านอื่นๆ เป็นอาหารตา อาหารใจก็เพียงพอแล้ว เพราะว่าสู้ราคาไม่ไหว แถมความอดทนในการรอ Shipping ที่นานแรมปีอีกต่างหาก ตอนสั่ง R2 นี้ผมโดนเลื่อนไป 1 ครั้ง เพราะว่าคนสั่งเยอะ ผู้ผลิตบอกว่าผลิตและ QC สินค้าไม่ทัน แต่ทั้งหมดก็ใช้เวลาประมาณ 4 เดือนเท่านั้น ถือว่าเร็วถ้าเทียบกับเจ้าอื่น


จากภาพด้านบน ที่เคสชิ้นล่างผมมีปัญหา จนมาถึงช่วงปลายปี 2021 ก็เปิด R3 ผมก็เลยถือโอกาสสั่งเฉพาะเคสมาเปลี่ยน คราวนี้ออกสีใหม่มาเพียบเลย แถมเป็นการขายที่เป็น in-stock แล้ว ปิดรอบสั่งแล้วจัดส่งทันทีเลย ผมก็เลยป้ายยารุ่นน้องให้สั่งพร้อมกัน หารค่าส่งและภาษี กระจายความเจ็บกันถ้วนหน้า


![05.jpg](/images/blog/kbdfans-67lite-r2-after-years-use-content-5.jpg)

&gt; รอบ R3 ของมาไว ไวจนน่าอิจฉา

![06.jpg](/images/blog/kbdfans-67lite-r2-after-years-use-content-6.jpg)

&gt; อันนี้ของรุ่นน้อง รอบนี้มีการ์ดเขียนคำยินดีมาให้อย่างน่าอิจฉา รอบผมไม่มีอะไรพวกนี้เลย ผมสั่งมาลงที่บ้านผมและทำการแพ็คและส่งต่อรุ่นน้องอีกที ของผมเองมีแค่เคส

![07.jpg](/images/blog/kbdfans-67lite-r2-after-years-use-content-7.jpg)

&gt; รอบนี้ผมเปลี่ยนเป็นสีม่วง ช่วงนั้นทานอสกำลังฮิต และก็เปลี่ยน Keycaps เป็นตัวติดบอร์ด Leopold รีวิว ส่วนพี่สิงนั้นนอนกล่องไปยาวๆ

สุดท้ายนี้ต้องขอขอบคุณทุกท่านที่อ่านมาจนถึงตรงนี้จริงๆครับ


&lt;figure class=&quot;notion-video&quot;&gt;
  &lt;iframe width=&quot;100%&quot; height=&quot;480&quot; src=&quot;https://www.youtube.com/embed/eE4pFH826Rk&quot;
    title=&quot;YouTube video player&quot; frameborder=&quot;0&quot;
    allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
    allowfullscreen&gt;&lt;/iframe&gt;
  
&lt;/figure&gt;</content:encoded></item></channel></rss>