<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Peel的部落格</title>
    <link>https://mrpeelblog.com/</link>
    <description>Recent content on Peel的部落格</description>
    <generator>Hugo -- 0.135.0</generator>
    <language>zh-tw</language>
    <lastBuildDate>Sun, 08 Jun 2025 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://mrpeelblog.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>使用 Twikoo 架設匿名留言板</title>
      <link>https://mrpeelblog.com/posts/twikoo/</link>
      <pubDate>Sun, 08 Jun 2025 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/twikoo/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>我的部落格使用 Twikoo 匿名留言板 <a href="https://twikoo.js.org">https://twikoo.js.org</a>。</p>
<p>配置 Twikoo 要做到以下三件事：</p>
<ul>
<li>
<p>設定資料庫：儲存留言紀錄，使用 MongoDB 串接 Twikoo（免費方案）</p>
</li>
<li>
<p>部屬服務：透過 Vercel 部屬（免費方案）</p>
</li>
<li>
<p>前端顯示：Hugo 主題能支援 Twikoo 留言服務（PaperMod 可以）</p>
</li>
</ul>
<h2 id="設定資料庫">設定資料庫</h2>
<p>註冊 MongoDB 服務 <a href="https://www.mongodb.com">https://www.mongodb.com</a>。</p>
<p>選擇 M0 方案，512MB 以我的部落格人數用到下輩子都沒問題～</p>
<p><img alt="MongoDB_Price" src="/posts/twikoo/assets/MongoDB_Price.png">
</p>
<p>位置選擇最近的 Hong Kong (ap-east1)。</p>
<p><img alt="MongoDB_Location" src="/posts/twikoo/assets/MongoDB_Location.png">
</p>
<p>下一步會看到資料庫 IP 位置，並建立 Database 帳號和密碼。</p>
<p><img alt="MongoDB_User" src="/posts/twikoo/assets/MongoDB_User.png">
</p>
<h3 id="設定連線權限">設定連線權限</h3>
<p>前往「Network Access」修改設定「0.0.0.0/0」所有 IP 可以訪問資料庫。</p>
<p><img alt="MongoDB_Network" src="/posts/twikoo/assets/MongoDB_Network.png">
</p>
<h3 id="複製-mongodb_uri">複製 MONGODB_URI</h3>
<p>前往「Clusters」點「Connect」設定 MongoDB Driver。</p>
<p><img alt="MongoDB_Connect" src="/posts/twikoo/assets/MongoDB_Connect.png">
</p>
<p>複製下面的連線字串 MONGODB_URI，後面部屬到 Vercel 會用到。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">mongodb</span><span class="o">+</span><span class="ss">srv</span><span class="p">:</span><span class="sr">//</span><span class="o">&lt;</span><span class="n">username</span><span class="o">&gt;</span><span class="ss">:&lt;</span><span class="n">db_password</span><span class="o">&gt;</span><span class="vi">@hugo</span><span class="o">.</span><span class="n">t3il4</span><span class="o">.</span><span class="n">mongodb</span><span class="o">.</span><span class="n">net</span><span class="o">/</span><span class="p">?</span><span class="n">retryWrites</span> <span class="o">=</span> <span class="kp">true</span><span class="o">&amp;</span><span class="n">w</span> <span class="o">=</span> <span class="n">majority</span><span class="o">&amp;</span><span class="n">appName</span> <span class="o">=</span> <span class="no">Hugo</span>
</span></span></code></pre></div><p>把 <code>&lt;username&gt;</code> 和 <code>&lt;db_password&gt;</code> 換成 Database 的帳號密碼。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># 範例</span>
</span></span><span class="line"><span class="cl"><span class="n">mongodb</span><span class="o">+</span><span class="ss">srv</span><span class="p">:</span><span class="sr">//use</span><span class="ss">rname</span><span class="p">:</span><span class="n">db_password</span><span class="vi">@hugo</span><span class="o">.</span><span class="n">t3il4</span><span class="o">.</span><span class="n">mongodb</span><span class="o">.</span><span class="n">net</span><span class="o">/</span><span class="p">?</span><span class="n">retryWrites</span> <span class="o">=</span> <span class="kp">true</span><span class="o">&amp;</span><span class="n">w</span> <span class="o">=</span> <span class="n">majority</span><span class="o">&amp;</span><span class="n">appName</span> <span class="o">=</span> <span class="no">Hugo</span>
</span></span></code></pre></div><p><img alt="MongoDB_URI" src="/posts/twikoo/assets/MongoDB_URI.png">
</p>
<h3 id="刪除資料庫預設資料">刪除資料庫預設資料</h3>
<p>點垃圾桶圖示 Drop Database。</p>
<p><img alt="MongoDB_Drop" src="/posts/twikoo/assets/MongoDB_Drop.png">
</p>
<h2 id="部屬服務">部屬服務</h2>
<p>註冊 Vercel 服務 <a href="https://vercel.com">https://vercel.com</a>。</p>
<p>連結 GitHub Repository。</p>
<p><img alt="Vercel_Link" src="/posts/twikoo/assets/Vercel_Link.png">
</p>
<p>前往 Twikoo 部屬文件 <a href="https://twikoo.js.org/backend.html">https://twikoo.js.org/backend.html</a>。</p>
<p>點「Deploy」按鈕。</p>
<p><img alt="Vercel_Deploy" src="/posts/twikoo/assets/Vercel_Deploy.png">
</p>
<h3 id="設定環境變量">設定環境變量</h3>
<p>前往「Settings」點「Environment Variables」，新增「Key」：<code>MONGODB_URI</code>、「Value」貼上 MongoDB 的 URI：<code>mongodb+srv://&lt;username&gt;:&lt;db_password&gt;@hugo.t3il4.mongodb.net/?retryWrites = true&amp;w = majority&amp;appName = Hugo</code>。</p>
<p><img alt="Vercel_API" src="/posts/twikoo/assets/Vercel_API.png">
</p>
<h3 id="設定瀏覽權限">設定瀏覽權限</h3>
<p>設定「Vercel Authentication」關閉，所有人可以瀏覽。</p>
<p><img alt="Vercel_Auth" src="/posts/twikoo/assets/Vercel_Auth.png">
</p>
<h3 id="設定位置">設定位置</h3>
<p>設定位置「Hong Kong (East) - hkg1」。</p>
<p><img alt="Vercel_Location" src="/posts/twikoo/assets/Vercel_Location.png">
</p>
<p>以上設定好重新「Redeploy」。</p>
<p><img alt="Vercel_Redeploy" src="/posts/twikoo/assets/Vercel_Redeploy.png">
</p>
<p>成功部屬。</p>
<p><img alt="Vercel_Success" src="/posts/twikoo/assets/Vercel_Success.png">
</p>
<p>前往網址也能正常瀏覽。</p>
<p><img alt="Vercel_Success1" src="/posts/twikoo/assets/Vercel_Success1.png">
</p>
<h2 id="前端顯示">前端顯示</h2>
<p>我使用的 Hugo 主題 PaperMod 支援 Twikoo，修改部落格根目錄的 <code>config.yml</code>。</p>
<p><code>version</code> 請填 Twikoo 部屬的版本。版本要填正確會透過 CDN 導入。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">comments</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">twikoo</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">1.6.39</span><span class="w">
</span></span></span></code></pre></div><p>修改 <code>layouts/_default/single.html</code>，在 <code>/footer</code> 後面加入下面這段。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">footer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  {{- if (.Param &#34;comments&#34;) }}
</span></span><span class="line"><span class="cl">  {{- partial &#34;comments.html&#34; . }}
</span></span><span class="line"><span class="cl">  {{- end }}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">article</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>修改 <code>layouts/partials/comments.html</code>，加上下面這段。</p>
<p>其中 <code>envId</code> 值填 Vercel 部屬網址、<code>region</code> 值填 Vercel 地理位置，我是選香港，所以填 <code>ap-east-1</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- Twikoo --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;pagination__title&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;pagination__title-h&#34;</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;font-size: 20px;&#34;</span><span class="p">&gt;&lt;</span><span class="nt">br</span><span class="p">&gt;</span>留言板<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">hr</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;tcomment&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://cdn.jsdelivr.net/npm/twikoo@{{ .Site.Params.twikoo.version }}/dist/twikoo.all.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">twikoo</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">            <span class="nx">envId</span><span class="o">:</span> <span class="s2">&#34;Vercel 部屬網址&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">el</span><span class="o">:</span> <span class="s2">&#34;#tcomment&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">lang</span><span class="o">:</span> <span class="s1">&#39;zh-TW&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">region</span><span class="o">:</span> <span class="s1">&#39;ap-east-1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">path</span><span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>設定成功文章下方有留言區，點右下角齒輪，設定管理介面暗號以及密碼。</p>
<p>一般讀者看不到管理介面，只有在名字欄位輸入暗號才能開啟管理介面。</p>
<p><strong>如果要用作者名字留言，必須先登入過管理介面才能留言。</strong></p>
<p>詳細參考 <a href="https://twikoo.js.org/faq.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98">Twikoo 常見問題</a>。</p>
<p><img alt="Twikoo_Blog.png" src="/posts/twikoo/assets/Twikoo_Blog.png">
</p>
<h2 id="其他設置">其他設置</h2>
<p><strong>Twikoo 頭像支援 CDN</strong>：我是使用 <code>gravatar.com</code> 顯示。</p>
<p><img alt="Twikoo_Avatar" src="/posts/twikoo/assets/Twikoo_Avatar.png">
</p>
<p><strong>留言板文字修改</strong>：修改 <code>comments.html</code>，換成修改的 JS 檔案，<code>{{ .Site.BaseURL }}</code> 指向部落格根目錄下的 static 資料夾。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">    <span class="c">&lt;!--&lt;script src=&#34;https://cdn.jsdelivr.net/npm/twikoo@{{ .Site.Params.twikoo.version }}/dist/twikoo.all.min.js&#34;&gt;&lt;/script&gt; --&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;{{ .Site.BaseURL }}ZH_TW_twikoo.all.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="參考資料">參考資料</h2>
<p><a href="https://twikoo.js.org/">Twikoo 文档</a></p>
<p><a href="https://www.sulvblog.cn/posts/blog/hugo_twikoo/">Hugo 博客添加 Twikoo 评论</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>開源 DeepSeek-R1 模型本地端離線使用教學</title>
      <link>https://mrpeelblog.com/posts/deepseek-r1/</link>
      <pubDate>Wed, 29 Jan 2025 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/deepseek-r1/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>DeepSeek 是中國深度求索（DeepSeek）公司開發的 AI 模型。其中 DeepSeek-R1 是開源模型，<a href="https://api-docs.deepseek.com/news/news250120">官方表示</a> 性能表現與 <a href="https://openai.com/o1/">OpenAI o1</a> 相當。</p>
<p>DeepSeek 官方網站：<a href="https://www.deepseek.com/">https://www.deepseek.com/</a></p>
<p>DeepSeek-R1 GitHub：<a href="https://github.com/deepseek-ai/DeepSeek-R1">https://github.com/deepseek-ai/DeepSeek-R1</a></p>
<p>本文教學如何離線使用開源的 DeepSeek-R1 模型。</p>
<p><img alt="DeepSeek-R1" src="/posts/deepseek-r1/assets/DeepSeek-R1.png">
</p>
<h2 id="安裝-ollama">安裝 Ollama</h2>
<p>Ollama 可以在本地端（Windows、macOS、Linux）運行語言模型，包括 Llama 3.3、DeepSeek-R1、Phi-4 等其他模型。本次示範 Windows 版本，其餘作業系統操作邏輯大同小異。</p>
<p>Ollama 網站：<a href="https://ollama.com/">https://ollama.com/</a></p>
<p>Ollama GitHub：<a href="https://github.com/ollama/ollama">https://github.com/ollama/ollama</a></p>
<p>進入官網後，點選「Downloads」，選擇對應的操作系統安裝。</p>
<p><img alt="Download_Ollama" src="/posts/deepseek-r1/assets/Download_Ollama.png">
</p>
<p>Ollama 安裝完成後它會自動啟動，可以在常駐列看到。瀏覽器網址輸入：<code>127.0.0.1:11434</code>，可以看到「Ollama is running」訊息。</p>
<p><img alt="Ollama_Tray" src="/posts/deepseek-r1/assets/Ollama_Tray.png">
</p>
<p>如果不使用 Ollama，常駐列右鍵圖示選擇「Quit Ollama」即可。</p>
<h2 id="下載-deepseek-模型">下載 DeepSeek 模型</h2>
<p>預設 Ollama 沒有安裝任何模型，本文示範安裝 DeepSeek-R1 模型。</p>
<p>找到 Ollama 安裝位置，通常在 <code>[使用者]\AppData\Local\Programs\Ollama</code> 底下，接著我們用 <code>cmd.exe</code> 進入 Ollama 目錄，再輸入 <code>ollama.exe</code>，會看到 Ollama 指令說明。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="n">cd</span> <span class="n">C</span><span class="p">:</span>\<span class="n">Users</span>\<span class="n">Peel</span>\<span class="n">AppData</span>\<span class="n">Local</span>\<span class="n">Programs</span>\<span class="n">Ollama</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="n">ollama</span><span class="o">.</span><span class="n">exe</span>
</span></span><span class="line"><span class="cl"><span class="n">Usage</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">ollama</span> <span class="p">[</span><span class="n">flags</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="n">ollama</span> <span class="p">[</span><span class="n">command</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Available</span> <span class="n">Commands</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">serve</span>       <span class="n">Start</span> <span class="n">ollama</span>
</span></span><span class="line"><span class="cl">  <span class="n">create</span>      <span class="n">Create</span> <span class="n">a</span> <span class="n">model</span> <span class="kn">from</span> <span class="nn">a</span> <span class="n">Modelfile</span>
</span></span><span class="line"><span class="cl">  <span class="n">show</span>        <span class="n">Show</span> <span class="n">information</span> <span class="k">for</span> <span class="n">a</span> <span class="n">model</span>
</span></span><span class="line"><span class="cl">  <span class="n">run</span>         <span class="n">Run</span> <span class="n">a</span> <span class="n">model</span>
</span></span><span class="line"><span class="cl">  <span class="n">stop</span>        <span class="n">Stop</span> <span class="n">a</span> <span class="n">running</span> <span class="n">model</span>
</span></span><span class="line"><span class="cl">  <span class="n">pull</span>        <span class="n">Pull</span> <span class="n">a</span> <span class="n">model</span> <span class="kn">from</span> <span class="nn">a</span> <span class="n">registry</span>
</span></span><span class="line"><span class="cl">  <span class="n">push</span>        <span class="n">Push</span> <span class="n">a</span> <span class="n">model</span> <span class="n">to</span> <span class="n">a</span> <span class="n">registry</span>
</span></span><span class="line"><span class="cl">  <span class="nb">list</span>        <span class="n">List</span> <span class="n">models</span>
</span></span><span class="line"><span class="cl">  <span class="n">ps</span>          <span class="n">List</span> <span class="n">running</span> <span class="n">models</span>
</span></span><span class="line"><span class="cl">  <span class="n">cp</span>          <span class="n">Copy</span> <span class="n">a</span> <span class="n">model</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span>          <span class="n">Remove</span> <span class="n">a</span> <span class="n">model</span>
</span></span><span class="line"><span class="cl">  <span class="n">help</span>        <span class="n">Help</span> <span class="n">about</span> <span class="nb">any</span> <span class="n">command</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Flags</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="o">-</span><span class="n">h</span><span class="p">,</span> <span class="o">--</span><span class="n">help</span>      <span class="n">help</span> <span class="k">for</span> <span class="n">ollama</span>
</span></span><span class="line"><span class="cl">  <span class="o">-</span><span class="n">v</span><span class="p">,</span> <span class="o">--</span><span class="n">version</span>   <span class="n">Show</span> <span class="n">version</span> <span class="n">information</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Use</span> <span class="s2">&#34;ollama [command] --help&#34;</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span> <span class="n">about</span> <span class="n">a</span> <span class="n">command</span><span class="o">.</span>
</span></span></code></pre></div><p>Ollama 官網有 DeepSeek 模型介紹頁：<a href="https://ollama.com/library/deepseek-r1">https://ollama.com/library/deepseek-r1</a>。</p>
<p><img alt="Download_DeepSeek" src="/posts/deepseek-r1/assets/Download_DeepSeek.png">
</p>
<p>選擇適合的訓練參數，右邊的指令下載 LLM 模型，本文示範使用 <code>deepsekk-r1:8b</code>，輸入 <code>ollama run deepseek-r1:8b</code>，下載 DeepSeek-R1 8b 模型。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="n">ollama</span> <span class="n">run</span> <span class="n">deepseek</span><span class="o">-</span><span class="n">r1</span><span class="p">:</span><span class="mi">8</span><span class="n">b</span>
</span></span><span class="line"><span class="cl"><span class="n">pulling</span> <span class="n">manifest</span>
</span></span><span class="line"><span class="cl"><span class="n">pulling</span> <span class="mi">6340</span><span class="n">dc3229b0</span><span class="o">...</span> <span class="mi">100</span><span class="o">%</span> <span class="err">▕████████████████████████████████████████████████████████▏</span> <span class="mf">4.9</span> <span class="n">GB</span>
</span></span><span class="line"><span class="cl"><span class="n">pulling</span> <span class="mi">369</span><span class="n">ca498f347</span><span class="o">...</span> <span class="mi">100</span><span class="o">%</span> <span class="err">▕████████████████████████████████████████████████████████▏</span> <span class="mi">387</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl"><span class="n">pulling</span> <span class="mf">6e4</span><span class="n">c38e1172f</span><span class="o">...</span> <span class="mi">100</span><span class="o">%</span> <span class="err">▕████████████████████████████████████████████████████████▏</span> <span class="mf">1.1</span> <span class="n">KB</span>
</span></span><span class="line"><span class="cl"><span class="n">pulling</span> <span class="n">f4d24e9138dd</span><span class="o">...</span> <span class="mi">100</span><span class="o">%</span> <span class="err">▕████████████████████████████████████████████████████████▏</span> <span class="mi">148</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl"><span class="n">pulling</span> <span class="mi">0</span><span class="n">cb05c6e4e02</span><span class="o">...</span> <span class="mi">100</span><span class="o">%</span> <span class="err">▕████████████████████████████████████████████████████████▏</span> <span class="mi">487</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl"><span class="n">verifying</span> <span class="n">sha256</span> <span class="n">digest</span>
</span></span><span class="line"><span class="cl"><span class="n">writing</span> <span class="n">manifest</span>
</span></span><span class="line"><span class="cl"><span class="n">success</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">Send</span> <span class="n">a</span> <span class="n">message</span> <span class="p">(</span><span class="o">/</span><span class="err">?</span> <span class="k">for</span> <span class="n">help</span><span class="p">)</span>
</span></span></code></pre></div><p>模型下載成功後，可以直接在下方輸入文字內容與 DeepSeek 對話。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">你好</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">think</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;/</span><span class="n">think</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">你好</span><span class="err">！</span><span class="n">很高兴见到你</span><span class="err">，</span><span class="n">有什么我可以帮忙的吗</span><span class="err">？</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">請問你是什麼模型</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">think</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">嗯</span><span class="err">，</span><span class="n">你好呀</span><span class="err">！</span><span class="n">我是由中国的深度求索</span><span class="err">（</span><span class="n">DeepSeek</span><span class="err">）</span><span class="n">公司独立开发的智能助手DeepSeek</span><span class="o">-</span><span class="n">R1</span><span class="o">-</span><span class="n">Lite</span><span class="o">-</span><span class="n">Preview</span><span class="err">，</span><span class="n">很高兴为您提供服务</span><span class="err">！</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;/</span><span class="n">think</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">嗯</span><span class="err">，</span><span class="n">你好呀</span><span class="err">！</span><span class="n">我是由中国的深度求索</span><span class="err">（</span><span class="n">DeepSeek</span><span class="err">）</span><span class="n">公司独立开发的智能助手DeepSeek</span><span class="o">-</span><span class="n">R1</span><span class="o">-</span><span class="n">Lite</span><span class="o">-</span><span class="n">Preview</span><span class="err">，</span><span class="n">很高兴为您提供服务</span><span class="err">！</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">Send</span> <span class="n">a</span> <span class="n">message</span> <span class="p">(</span><span class="o">/</span><span class="err">?</span> <span class="n">For</span> <span class="n">help</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="使用-open-webui-介面">使用 Open WebUI 介面</h2>
<p>雖然我們成功和 DeekSeek 對話，但這種對話方式不好用，我們可以使用類似與 Chat-GPT 對話的介面來更方便對話。</p>
<p>透過 Open WebUI 可以實現類似 Chat-GPT 介面，它是開源的項目。</p>
<p>Open WebUI 官方網站：<a href="https://openwebui.com">https://openwebui.com</a></p>
<p>Opeb WebUI GitHub：<a href="https://github.com/open-webui/open-webui">https://github.com/open-webui/open-webui</a></p>
<p>可以使用 Docker 或是 uv（Python 套件管理工具）安裝，更多詳細可以參考 <a href="https://docs.openwebui.com/">官方 Docs</a>，本文示範 <code>uv</code> 安裝。</p>
<p>使用 Powershell 輸入以下指令：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">powershell</span> <span class="o">-</span><span class="n">ExecutionPolicy</span> <span class="n">ByPass</span> <span class="o">-</span><span class="n">c</span> <span class="s2">&#34;irm https://astral.sh/uv/install.ps1 | iex&#34;</span>
</span></span></code></pre></div><p>設定環境變數和下載所需要的檔案：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="err">$</span><span class="n">env</span><span class="p">:</span><span class="n">DATA_DIR</span><span class="o">=</span><span class="s2">&#34;C:\open-webui\data&#34;</span><span class="p">;</span> <span class="n">uvx</span> <span class="o">--</span><span class="n">python</span> <span class="mf">3.11</span> <span class="nb">open</span><span class="o">-</span><span class="n">webui</span><span class="nd">@latest</span> <span class="n">serve</span>
</span></span></code></pre></div><p>中間如果跳出存取應用程式請按「允許」。</p>
<p><img alt="WebUI_Firewall" src="/posts/deepseek-r1/assets/WebUI_Firewall.png">
</p>
<p>一切就緒如下，瀏覽器輸入 <code>127.0.0.1:8080</code>，就可以使用 Open WebUI。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="err">⠦</span> <span class="n">Preparing</span> <span class="n">packages</span><span class="p">...</span> <span class="p">(</span><span class="mf">127</span><span class="p">/</span><span class="mf">299</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">INFO</span><span class="err">:</span>     <span class="n">Started</span> <span class="n">server</span> <span class="k">process</span> <span class="p">[</span><span class="mf">16896</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">INFO</span><span class="err">:</span>     <span class="n">Waiting</span> <span class="k">for</span> <span class="n">application</span> <span class="n">startup</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="n">INFO</span><span class="err">:</span>     <span class="n">Application</span> <span class="n">startup</span> <span class="n">complete</span><span class="p">.</span>
</span></span><span class="line"><span class="cl"><span class="n">INFO</span><span class="err">:</span>     <span class="n">Uvicorn</span> <span class="n">running</span> <span class="n">on</span> <span class="n">http</span><span class="err">:</span><span class="p">//</span><span class="mf">0.0</span><span class="p">.</span><span class="py">0</span><span class="p">.</span><span class="mf">0</span><span class="err">:</span><span class="mf">8080</span> <span class="p">(</span><span class="n">Press</span> <span class="n">CTRL</span><span class="p">+</span><span class="n">C</span> <span class="n">to</span> <span class="n">quit</span><span class="p">)</span>
</span></span></code></pre></div><p><img alt="Open_WebUI" src="/posts/deepseek-r1/assets/Open_WebUI.png">
</p>
<p>點選「開始使用」建立管理者帳號。</p>
<p><img alt="Create_Account" src="/posts/deepseek-r1/assets/Create_Account.png">
</p>
<p>登入熟悉的介面，此時會看到先前已經安裝 DeepSeek 模型（須確保 Ollama 已運行）。可用往常方式與 LLM 對話，DeepSeek 支援上傳檔案。</p>
<p><img alt="DeepSeek-R1" src="/posts/deepseek-r1/assets/DeepSeek-R1.png">
</p>
<p>以上是 DeepSeek-R1 搭配 Ollama 與 Open WebUI 的使用教學，其餘模型也可以透過此方式使用。</p>
<p>另外 Open WebUI 支援 OpenAI API 串接，詳情參考部落格文章「<a href="https://mrpeelblog.com/posts/openai-api/">使用 OpenAI API 與 GPT 對話</a>」。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://github.com/ollama/ollama/tree/main/docs">Ollama Docs</a></p>
<p><a href="https://docs.openwebui.com/">Open WebUI Docs</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Hugo Gallery 放大圖片和畫廊 Short Code</title>
      <link>https://mrpeelblog.com/posts/zoom-and-gallery/</link>
      <pubDate>Fri, 10 Jan 2025 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/zoom-and-gallery/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>Hugo PaperMod 主題沒有提供圖片放大和畫廊的功能，本篇介紹外掛讓文章的圖片增添效果。</p>
<h2 id="medium-zoom">Medium Zoom</h2>
<p><a href="https://github.com/francoischalifour/medium-zoom">GitHub</a> ／ <a href="https://medium-zoom.francoischalifour.com/">Website</a></p>
<p>提供圖片放大效果且安裝容易，點擊下方圖片可以看到效果：</p>
<p><img alt="202302_02_1920x1200" src="/posts/zoom-and-gallery/assets/202302_02_1920x1200.jpg">
</p>
<h3 id="安裝-medium-zoom">安裝 Medium Zoom</h3>
<p>下載 Github 提供的連結：<a href="https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js">Minified</a>，並把它放到 <code>/static</code> 資料夾裡面。</p>
<p>修改 <code>/themes/PaperMod/layouts/partials/comments.html</code>，插入以下程式碼：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;{{ .Site.BaseURL }}medium-zoom.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">images</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">&#34;.post-content img&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="nx">images</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">img</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">mediumZoom</span><span class="p">(</span><span class="nx">img</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">margin</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="cm">/* The space outside the zoomed image */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">scrollOffset</span><span class="o">:</span> <span class="mi">40</span><span class="p">,</span> <span class="cm">/* The number of pixels to scroll to close the zoom */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">container</span><span class="o">:</span> <span class="kc">null</span><span class="p">,</span> <span class="cm">/* The viewport to render the zoom in */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">template</span><span class="o">:</span> <span class="kc">null</span><span class="p">,</span> <span class="cm">/* The template element to display on zoom */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">background</span><span class="o">:</span> <span class="s2">&#34;#000&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>重新佈署就可以看到效果了。</p>
<p>以上程式碼是針對 PaperMod 的 <code>.post-content img</code> 圖片增加 zoom-in 效果，其他主題可能要另外修改。</p>
<h2 id="gallery">Gallery</h2>
<p><a href="https://github.com/mfg92/hugo-shortcode-gallery">GitHub</a> ／ <a href="https://matze.rocks/images/">Website</a></p>
<p>提供多圖片畫廊效果。</p>







	






































	
	

	
		<script src="/shortcode-gallery/jquery-3.7.1.min.js"></script>
	

	
		<script src="/shortcode-gallery/lazy/jquery.lazy.min.js"></script>
	

	<script src="/shortcode-gallery/swipebox/js/jquery.swipebox.min.js"></script>
	<link rel="stylesheet" href="/shortcode-gallery/swipebox/css/swipebox.min.css">

	<script src="/shortcode-gallery/justified_gallery/jquery.justifiedGallery.min.js"></script>
	<link rel="stylesheet" href="/shortcode-gallery/justified_gallery/justifiedGallery.min.css"/>


<style>
	

	
</style>





<div id="gallery-459bba48db659cb8ca6dfd8a96feaefc-0-wrapper" class="gallery-wrapper">
<div id="gallery-459bba48db659cb8ca6dfd8a96feaefc-0" class="justified-gallery">
	
		

			
			

			
			
				
			

			
				
			

			
			
				
					
				
			


			
			

			<div>
				
				
					
					
				

				
				
					
					
				

				<a href="/posts/zoom-and-gallery/gallery/202412_03_1080x1920.jpg"
					class="galleryImg"
					
					>
					<img
						width="338" height="600"

						
							
							style="filter: blur(25px);"
							
								src="data:image/jpeg;base64,/9j/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4&#43;JS5ESUM8SDc9PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIACAAEgMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/APTfu08FWHBB&#43;lYeta7DZW7Rqx84rkBeoHrWZ4VvjPePI1y8iup4YYw2eRihyV7FqF43OsEagYyfzo2L6n86jM0eT8w/Ok86P&#43;8Pzp2RBzHiTSo7&#43;N54piMptKoDj69qp&#43;CrNEvJN7SPsBYBxjBziprXxVpU9sAUhiOMZCjP8zVhfEOm2iboXXJH8IwTS5dblc2ljeaYhiBayEA9cf8A16Tz2/59ZPy/&#43;vXBS&#43;M5PNfbJcY3HHzGm/8ACZS/89Lj/vs1RB//2Q=="
							
							class="lazy"
							data-src="/posts/zoom-and-gallery/gallery/202412_03_1080x1920_hu10420653338116156228.jpg"
						

						
					>
				</a>
			</div>
		
	
		

			
			

			
			
				
			

			
				
			

			
			
				
					
				
			


			
			

			<div>
				
				
					
					
				

				
				
					
					
				

				<a href="/posts/zoom-and-gallery/gallery/202402_03_1080x1920.jpg"
					class="galleryImg"
					
					>
					<img
						width="338" height="600"

						
							
							style="filter: blur(25px);"
							
								src="data:image/jpeg;base64,/9j/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4&#43;JS5ESUM8SDc9PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIACAAEgMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AO4mlWCNSY4UwMKGUEH8647XNbvbe6ne32mITHYobG1fLx346jOPxqnBfXsNhFb3V00xVQCZBknjp9KfFcRXMY81c72JDYByO/f0xVtm8cM7amKvjm4CgPdfNj5sQqeaP&#43;E5m/5&#43;j/34WopfC2n&#43;a&#43;EugNxwAeP5Uz/hFrD&#43;7d/mf8KzM/YyJpvlYl5AEz87A8n8ak0&#43;VxDGx25CjawXGQOBXLw6iMZcFmBzwBx&#43;dWF1gxqsQEigKRw/r7U7nV7dHVmZsnhx9Go85v8App/31XIm/iJJ3zc/7Ro&#43;3xf35v8Avo0ri9uj/9k="
							
							class="lazy"
							data-src="/posts/zoom-and-gallery/gallery/202402_03_1080x1920_hu2189133346593202057.jpg"
						

						
					>
				</a>
			</div>
		
	
		

			
			

			
			
				
			

			
				
			

			
			
				
					
				
			


			
			

			<div>
				
				
					
					
				

				
				
					
					
				

				<a href="/posts/zoom-and-gallery/gallery/202402_02_1920x1200.jpg"
					class="galleryImg"
					
					>
					<img
						width="600" height="375"

						
							
							style="filter: blur(25px);"
							
								src="data:image/jpeg;base64,/9j/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4&#43;JS5ESUM8SDc9PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIABQAIAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AOMsjGyqMMSOuF6&#43;2KnFgsc6u6HAbB5x1q5Y2Mjgy740RTkMSAMdM5rZt9Ke8LRxywSgqTtQmQqO3TgDP869VtJanztpylpomc3exOFFuisFYYkOOqjtVIv5W8IVBwV6A5BGP5flXT3PheexjlkuZo44TyJWz8hz04H9arp4fiuYHlivImUZLMTxjvzis3KL3NqdOS2OKt5HWYfMSPQ10Nh4n1O1eO2t2hijK84hXP5kZrnIf9cKuW//AB&#43;w/wC6f5Vxo9U6eTxRqzyMDOgVWIwIlAI/KsjVPEeq3Nu0Ut0zIh2hfUY/&#43;tQf9bJ/vn&#43;tZd592X/fH8qb2FFtvU//2Q=="
							
							class="lazy"
							data-src="/posts/zoom-and-gallery/gallery/202402_02_1920x1200_hu5938730388101906157.jpg"
						

						
					>
				</a>
			</div>
		
	
		

			
			

			
			
				
			

			
				
			

			
			
				
					
				
			


			
			

			<div>
				
				
					
					
				

				
				
					
					
				

				<a href="/posts/zoom-and-gallery/gallery/202312_02_1920x1200.jpg"
					class="galleryImg"
					
					>
					<img
						width="600" height="375"

						
							
							style="filter: blur(25px);"
							
								src="data:image/jpeg;base64,/9j/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4&#43;JS5ESUM8SDc9PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIABQAIAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5&#43;gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4&#43;Tl5ufo6ery8/T19vf4&#43;fr/2gAMAwEAAhEDEQA/AFttU0jgNcJ&#43;IP8AhWjJc6Vc2rR293tcKzZAYZwOnArzQGVcYwffIqZb14jkRp/wLp&#43;lbToza92REakFvE6FtY2WnkTySAjGOpYeoPFULjXbZG3RwSO&#43;0jcUz7dDWebt5h8sf5NgVXdHYksyKPY//Wqo07KzYSmnsgiOzG0AVY80uMMqMPdQarJ2&#43;tTLWjIRXuJWLbc4A6AcVVd29asT/wCsNVXrRbGEm7n/2Q=="
							
							class="lazy"
							data-src="/posts/zoom-and-gallery/gallery/202312_02_1920x1200_hu7010916167190237500.jpg"
						

						
					>
				</a>
			</div>
		
	
</div>
</div>

<script>
	if (!("HSCGjQuery" in window)) {
		if (!window.jQuery) {
			throw new Error("jQuery is not loaded, hugo-shortcode-gallery wont work without it!");
		}
		window.HSCGjQuery = window.jQuery.noConflict(true);
	}

	
	
	
	
	
	;(function($) {

		$( document ).ready(() => {
			const gallery = $("#gallery-459bba48db659cb8ca6dfd8a96feaefc-0");
			

			
			let swipeboxInstance = null;

			
			
			gallery.on('jg.complete', () => {
				
					
					
					$(() => {
						$('.lazy').Lazy({
							visibleOnly: true,
							afterLoad: element => element.css({filter: "none", transition: "filter 1.0s ease-in-out"})
						});
					});
				

				swipeboxInstance = $('.galleryImg').swipebox(
					$.extend({},
						{  }
					)
				);
			});

			
			gallery.justifiedGallery($.extend(
				{
					rowHeight : "150",
					margins : "5",
					border : 0,
					randomize :  false ,
					waitThumbnailsLoad : false,
					lastRow : "justify",
					captions : false,
					
					
				},
				{  }
			));

			
			
		});

	
	
	
	})(window.HSCGjQuery)
</script>

<h3 id="安裝-gallery">安裝 Gallery</h3>
<p>從 GitHub <a href="https://github.com/mfg92/hugo-shortcode-gallery/releases">下載</a> 解壓縮放到 <code>/theme/</code> 並重新命名為 <code>gallery</code>。</p>
<p>修改 Hugo 目錄 <code>config.yaml</code> 的 <code>theme</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">theme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">PaperMod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">gallery</span><span class="w"> </span><span class="c">#新增 gallery 主題</span><span class="w">
</span></span></span></code></pre></div><p>直接在文章 <code>.md</code> 裡面插入以下程式碼：</p>
<p>{{&lt; gallery match=&ldquo;gallery/*&rdquo; sortOrder=&ldquo;desc&rdquo; rowHeight=&ldquo;150&rdquo; margins=&ldquo;5&rdquo; thumbnailResizeOptions=&ldquo;600x600 q90 Lanczos&rdquo; showExif=false previewType=&ldquo;blur&rdquo; embedPreview=true loadJQuery=true &gt;}}</p>
<p><code>gallery/*</code> 為畫廊的圖片資料夾位置，依照需求自行修改。</p>
<h2 id="圖片來源">圖片來源</h2>
<p>Nikon</p>
<ul>
<li><a href="https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2023/202302_02_1920x1200.jpg">https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2023/202302_02_1920x1200.jpg</a></li>
<li><a href="https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2024/202412_03_1080x1920.jpg">https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2024/202412_03_1080x1920.jpg</a></li>
<li><a href="https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2023/202312_02_1920x1200.jpg">https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2023/202312_02_1920x1200.jpg</a></li>
<li><a href="https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2024/202402_02_1920x1200.jpg">https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2024/202402_02_1920x1200.jpg</a></li>
<li><a href="https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2024/202402_03_1080x1920.jpg">https://www.nikon-image.com/enjoy/membership/img/sample/wallpaper/2024/202402_03_1080x1920.jpg</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>修改 PaperMod 文章列表排版</title>
      <link>https://mrpeelblog.com/posts/papermod-grid/</link>
      <pubDate>Sun, 22 Dec 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/papermod-grid/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>本文介紹如何修改 <a href="https://github.com/adityatelange/hugo-PaperMod">PaperMod</a> 主題呈現卡片式文章列表。</p>
<p>預設 PaperMod，僅能看到一個大縮圖。</p>
<p><img alt="old" src="/posts/papermod-grid/assets/old.png">
</p>
<p>修改 PaperMod 後，一次可以查看三篇文章標題。</p>
<p><img alt="modify" src="/posts/papermod-grid/assets/modify.png">
</p>
<h2 id="實現方法">實現方法</h2>
<p>透過 CSS 的 Grid 樣式使文章排列成表格，本篇示範 3 x 2 排列方式。</p>
<h2 id="修改首頁文章數量">修改首頁文章數量</h2>
<p>編輯 Hugo 目錄下的 <code>config.yml</code> 或 <code>config.toml</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">新版 Hugo 修改 pagerSize</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">pagination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pagerSize</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w"> </span><span class="c">#修改數量改成 6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">舊版 Hugo 修改 paginate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">paginate</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w">
</span></span></span></code></pre></div><h2 id="修改-css">修改 CSS</h2>
<p>正常來說修改 <code>.main</code> 成為 Grid 樣式就可以看到效果，但是 PaperMod 主題它的 <code>.main</code> 和瀏覽文章裡的 <code>.main</code> 效果一樣。所以首頁修改成功，但文章也會變成 Grid 排版，導致版面錯亂。</p>
<p><strong>修改思路</strong>：首頁的 <code>.main</code> 與其他頁面的 <code>.main</code> 分不同名稱。</p>
<p>修改 <code>/themes/PaperMod/layouts/_default/baseof.html</code>，加上判斷式：<code>main {{ if .IsHome }}home{{ end }}</code>。</p>
<p>如果在首頁則 <code>.main</code> 變成 <code>.main.home</code>，這樣和文章中的 <code>.main</code> 不同名稱就可以分開定義樣式。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="hl"><span class="lnt">10
</span></span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">{{- if (or (ne .Kind `page` ) (eq .Layout `archives`) (eq .Layout `search`)) -}}
</span></span></span><span class="line"><span class="cl"><span class="s">{{- print &#34;</span><span class="na">list</span><span class="err">&#34;</span> <span class="na">-</span><span class="err">}}</span>
</span></span><span class="line"><span class="cl"><span class="err">{{</span><span class="na">-</span> <span class="na">end</span> <span class="na">-</span><span class="err">}}</span>
</span></span><span class="line"><span class="cl"><span class="err">{{</span><span class="na">-</span> <span class="na">if</span> <span class="na">eq</span> <span class="na">site</span><span class="err">.</span><span class="na">Params</span><span class="err">.</span><span class="na">defaultTheme</span> <span class="err">`</span><span class="na">dark</span><span class="err">`</span> <span class="na">-</span><span class="err">}}</span>
</span></span><span class="line"><span class="cl"><span class="err">{{</span><span class="na">-</span> <span class="na">print</span> <span class="err">&#34;</span> <span class="na">dark</span><span class="err">&#34;</span> <span class="err">}}</span>
</span></span><span class="line"><span class="cl"><span class="err">{{</span><span class="na">-</span> <span class="na">end</span> <span class="na">-</span><span class="err">}}</span>
</span></span><span class="line"><span class="cl"><span class="err">&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;top&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {{- partialCached &#34;header.html&#34; . .Page -}}
</span></span><span class="line hl"><span class="cl">    <span class="p">&lt;</span><span class="nt">main</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;main {{ if .IsHome }}home{{ end }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {{- block &#34;main&#34; . }}{{ end }}
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {{ partialCached &#34;footer.html&#34; . .Layout .Kind (.Param &#34;hideFooter&#34;) (.Param &#34;ShowCodeCopyButtons&#34;) -}}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="grid-排版">Grid 排版</h3>
<p>修改 <code>/themes/PaperMod/assets/css/common/main.css</code>。</p>
<p>配合手機瀏覽，我們要定義當寬度變小時，從 3 x 2 變成 2 x 3 ，再變成 1 x 6。提升閱讀體驗。</p>
<p>使用 <code>@media screen and (max-width: 寬度)</code> 可以實現不同寬度使用不同 CSS。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">main</span><span class="p">.</span><span class="nc">home</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">display</span><span class="p">:</span> <span class="k">grid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-template-columns</span><span class="p">:</span> <span class="nf">repeat</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="kc">auto</span><span class="p">);</span> <span class="c">/* 改為三欄 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">max-width</span><span class="p">:</span> <span class="nb">calc</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">main</span><span class="o">-</span><span class="n">width</span><span class="p">)</span> <span class="o">+</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">gap</span><span class="p">)</span> <span class="o">*</span> <span class="mi">15</span><span class="p">);</span> <span class="c">/*增加原來寬度*/</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="nt">screen</span> <span class="nt">and</span> <span class="o">(</span><span class="nt">max-width</span><span class="o">:</span> <span class="nt">1200px</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nc">main</span><span class="p">.</span><span class="nc">home</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-template-columns</span><span class="p">:</span> <span class="nf">repeat</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="kc">auto</span><span class="p">);</span> <span class="c">/* 寬度小於1200改為兩欄 */</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="nt">screen</span> <span class="nt">and</span> <span class="o">(</span><span class="nt">max-width</span><span class="o">:</span> <span class="nt">900px</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nc">main</span><span class="p">.</span><span class="nc">home</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-template-columns</span><span class="p">:</span> <span class="nf">repeat</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="kc">auto</span><span class="p">);</span> <span class="c">/* 寬度小於900改為一欄 */</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> 
</span></span></code></pre></div><h3 id="自訂位置">自訂位置</h3>
<p>由於 <code>.main.home</code> 底下包含首頁標題 <code>.first-entry.home-info</code> 和頁數切換 <code>.page-footer</code>，我們要再定義它們的位置。</p>
<p>修改 <code>/themes/PaperMod/assets/css/common/post-entry.css</code>，自訂位置和動態調整。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">first-entry</span><span class="p">.</span><span class="nc">home-info</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">grid-column-start</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">grid-column-end</span><span class="p">:</span> <span class="mi">4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">page-footer</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-column-start</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">grid-column-end</span><span class="p">:</span> <span class="mi">4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">margin</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="nt">screen</span> <span class="nt">and</span> <span class="o">(</span><span class="nt">max-width</span><span class="o">:</span> <span class="nt">1200px</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nc">first-entry</span><span class="p">.</span><span class="nc">home-info</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-column-start</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">grid-column-end</span><span class="p">:</span> <span class="mi">3</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nc">page-footer</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-column-start</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">grid-column-end</span><span class="p">:</span> <span class="mi">3</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">media</span> <span class="nt">screen</span> <span class="nt">and</span> <span class="o">(</span><span class="nt">max-width</span><span class="o">:</span> <span class="nt">900px</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nc">first-entry</span><span class="p">.</span><span class="nc">home-info</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-column-start</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">grid-column-end</span><span class="p">:</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nc">page-footer</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">grid-column-start</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">grid-column-end</span><span class="p">:</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="微調樣式">微調樣式</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="hl"><span class="lnt">8
</span></span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">post-entry</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">position</span><span class="p">:</span> <span class="kc">relative</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">padding</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">gap</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">entry</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">border-radius</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">radius</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">transition</span><span class="p">:</span> <span class="k">transform</span> <span class="mf">0.1</span><span class="kt">s</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">border</span><span class="p">);</span>
</span></span><span class="line hl"><span class="cl">    <span class="k">margin</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>以上調整可以修改排版，適合文章有縮圖的部落格使用，邏輯掌握好可以實現九宮格等任何組合。</p>
<p>另外 Hugo 有一個主題本身就提供卡片式預覽，名為 <a href="https://blowfish.page/">BlowFish</a>，有興趣的朋友可以去體驗看看。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://developer.mozilla.org/zh-TW/docs/Web/CSS/CSS_grid_layout/Basic_concepts_of_grid_layout">格線佈局的基本概念</a></p>
<p><a href="https://travishorn.com/responsive-grid-in-2-minutes-with-css-grid-layout-4842a41420fe">Responsive grid in 2 minutes with CSS Grid Layout</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>解決 WireGuard 無法登入、哈希值生成</title>
      <link>https://mrpeelblog.com/posts/wireguard-hash/</link>
      <pubDate>Mon, 04 Nov 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/wireguard-hash/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>先前文章教學「<a href="https://mrpeelblog.com/posts/wireguard-with-adguard/">如何架設 WireGuard VPN 和 AdGuard 擋廣告</a>」使用 Docker Compose 部屬。然而新版本 WireGuard 已不使用 <code>PASSWORD</code>，如果部屬失敗請使用 <code>PASSWORD_HASH</code> 哈希值，提升安全性。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">wg-easy      <span class="p">|</span> Node.js v20.17.0
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span> /app/lib/Server.js:311
</span></span><span class="line hl"><span class="cl">wg-easy      <span class="p">|</span>       throw new Error<span class="o">(</span><span class="s1">&#39;DO NOT USE PASSWORD ENVIRONMENT VARIABLE.                                                                   USE PASSWORD_HASH INSTEAD.\nSee https://github.com/wg-easy/wg-easy/blob/master/H                                                                  ow_to_generate_an_bcrypt_hash.md&#39;</span><span class="o">)</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>       ^
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span> Error: DO NOT USE PASSWORD ENVIRONMENT VARIABLE. USE PASSWORD_HAS                                                                  H INSTEAD.
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span> See https://github.com/wg-easy/wg-easy/blob/master/How_to_generat                                                                  e_an_bcrypt_hash.md
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at new Server <span class="o">(</span>/app/lib/Server.js:311:13<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Object.&lt;anonymous&gt; <span class="o">(</span>/app/services/Server.js:5:18<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Module._compile <span class="o">(</span>node:internal/modules/cjs/loader:1469:14<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Module._extensions..js <span class="o">(</span>node:internal/modules/cjs/loader:1                                                                  548:10<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Module.load <span class="o">(</span>node:internal/modules/cjs/loader:1288:32<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Module._load <span class="o">(</span>node:internal/modules/cjs/loader:1104:12<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Module.require <span class="o">(</span>node:internal/modules/cjs/loader:1311:19<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at require <span class="o">(</span>node:internal/modules/helpers:179:18<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Object.&lt;anonymous&gt; <span class="o">(</span>/app/server.js:3:1<span class="o">)</span>
</span></span><span class="line"><span class="cl">wg-easy      <span class="p">|</span>     at Module._compile <span class="o">(</span>node:internal/modules/cjs/loader:1469:14<span class="o">)</span>
</span></span></code></pre></div><h2 id="產生哈希值">產生哈希值</h2>
<p>透過 wg-easy 的 <code>wgpw</code> 指令產生哈希值，以下示範登入密碼：<code>apple123</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ docker run -it ghcr.io/wg-easy/wg-easy wgpw apple123
</span></span><span class="line"><span class="cl"><span class="nv">PASSWORD_HASH</span><span class="o">=</span><span class="s1">&#39;$2a$12$3SqIVeJWRE2wnM8ryXWjsOR01YCHWD.8mb9owL46juxkTOvCnXyT6&#39;</span>
</span></span></code></pre></div><p><code>apple123</code> 的哈希值為：<code>$2a$12$3SqIVeJWRE2wnM8ryXWjsOR01YCHWD.8mb9owL46juxkTOvCnXyT6</code>。</p>
<h2 id="修改-docker-composeyml">修改 docker-compose.yml</h2>
<p>將哈希值的所有錢符號 <code>$</code> 改為 <code>$$</code>。</p>
<p>所以會變成：<code>$$2a$$12$$3SqIVeJWRE2wnM8ryXWjsOR01YCHWD.8mb9owL46juxkTOvCnXyT6</code>。</p>
<p>再把修改後的值寫入 <code>PASSWORD_HASH</code> 中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wg-easy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/wg-easy/wg-easy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">wg-easy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">LANG=cht</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WG_HOST=公網IP</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span>- <span class="l">PASSWORD_HASH=$$2a$$12$$xkGUzsl56OYJqxdhebh7jegTW753ox6MArlr13DZiSibT3fOuZrBe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PORT=51821</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WG_PORT=51820</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./wg-easy:/etc/wireguard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">51820</span><span class="p">:</span><span class="m">51820</span><span class="l">/udp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">51821</span><span class="p">:</span><span class="m">51821</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cap_add</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">NET_ADMIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SYS_MODULE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sysctls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">net.ipv4.conf.all.src_valid_mark=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">net.ipv4.ip_forward=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></div><p>重新啟動 WireGuard 輸入密碼 <code>apple123</code> 就可以順利登入。</p>
<p>如果登入出現 <code>Unauthorized</code>，表示密碼錯誤，請回頭重新檢查哈希值是否正確修改成功，另外 <code>PASSWORD_HASH=</code> 後面不要有單引號 <code>'</code>。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://github.com/wg-easy/wg-easy/blob/master/How_to_generate_an_bcrypt_hash.md">How to generate an bcrypt hash</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>設定 Hugo 排程發文</title>
      <link>https://mrpeelblog.com/posts/hugo-schedule/</link>
      <pubDate>Mon, 28 Oct 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/hugo-schedule/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>本文介紹如何設定排程發表文章，透過自動化腳本發表文章。</p>
<h2 id="原理">原理</h2>
<p>利用頁面 Front matter 的 <code>date</code> 值，把日期設 <strong>欲發表日期</strong>，透過 <code>crontab</code> 每日執行腳本發表文章。</p>
<p>排程發表的文章請將 <code>draft</code> 設 <code>false</code>。</p>
<p>Front matter 也可以使用以下參數：</p>
<ul>
<li><code>draft</code> 草稿</li>
<li><code>publishDate</code> 發佈日期</li>
<li><code>expiryDate</code> 過期日期</li>
</ul>
<h2 id="hugo-本機運行指令">Hugo 本機運行指令</h2>
<p>參數：<code>-D</code> 草稿（Draft）、<code>-F</code> 未來時間（Future）、<code>-E</code> 已過期（Expired）。</p>
<ul>
<li>僅預覽草稿</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo server -D
</span></span></code></pre></div><ul>
<li>僅預覽未來日期的文章</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo server -F
</span></span></code></pre></div><ul>
<li>預覽草稿和未來日期的文章</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo server -FD
</span></span></code></pre></div><h2 id="deploysh">deploy.sh</h2>
<p>建立部屬腳本，依自身環境修改程式碼。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">/bin/bash
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 設定環境變數，防止執行腳本無反應</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span>/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /home/hugo/blog
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 刪除 index.lock 防止 Git 卡住</span>
</span></span><span class="line"><span class="cl">rm -f .git/index.lock
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">today</span><span class="o">=</span><span class="k">$(</span>date +<span class="s2">&#34;%Y/%m/%d&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Hugo 生成網站並檢查結果</span>
</span></span><span class="line"><span class="cl">hugo --cleanDestinationDir
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> -ne <span class="m">0</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Hugo generation failed&#34;</span> &gt;&gt; /home/hugo/deploy.log
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git add -A
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;</span><span class="si">${</span><span class="nv">today</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">git branch -M main
</span></span><span class="line"><span class="cl">git remote set-url origin git@github.com:&lt;Git 帳號&gt;/&lt;Repository 名稱&gt;.git
</span></span><span class="line"><span class="cl">git push
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Deploy script finished at </span><span class="k">$(</span>date<span class="k">)</span><span class="s2">&#34;</span> &gt;&gt; /home/hugo/deploy.log
</span></span></code></pre></div><h2 id="crontab-設定">Crontab 設定</h2>
<p>輸入 <code>crontab -e</code> 編輯工作排程，在最底下新增每日固定時間執行 <code>deploy.sh</code> 發布。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="m">0</span> <span class="m">1</span> * * * /bin/bash /home/hugo/deploy.sh &gt; /home/hugo/deploy.log 2&gt;<span class="p">&amp;</span><span class="m">1</span>
</span></span></code></pre></div><h2 id="參考資料">參考資料</h2>
<p><a href="https://gohugo.io/getting-started/usage/#draft-future-and-expired-content">Draft, future, and expired content</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>使用 OpenAI API 與 GPT 對話</title>
      <link>https://mrpeelblog.com/posts/openai-api/</link>
      <pubDate>Sun, 27 Oct 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/openai-api/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>OpenAI 有提供訂閱方案增加 <a href="https://openai.com/index/hello-gpt-4o/">GPT-4o</a> 模型詢問次數，非大量使用我認為訂閱價格不划算。</p>
<p>我建議使用 OpenAI API，它也可以使用 GPT-4o 模型，並且是以量計價，相比訂閱費用價格就很彈性。</p>
<p>透過 Docker Open WebUI 我們很方便用 API 和 GPT 對話。建立 OpenAI API 請先準備：OpenAI 帳戶、信用卡、手機號碼、驗證的信箱。以下分享如何建立 API 和使用方法：</p>
<h2 id="建立-openai-api-金鑰">建立 OpenAI API 金鑰</h2>
<p>前往 <a href="https://platform.openai.com/settings/profile?tab=api-keys">OpenAI 後台</a>。點選 <code>+ Create new secret key</code>，建立金鑰。</p>
<p><img alt="Create_API" src="/posts/openai-api/assets/Create_API.png">
</p>
<p>建立成功後，會出現 API Key，API key <strong>只會顯示一次</strong>，請妥善保管金鑰。</p>
<p><img alt="API_Key" src="/posts/openai-api/assets/API_Key.png">
</p>
<h3 id="信用卡儲值至帳戶">信用卡儲值至帳戶</h3>
<p>前往 <a href="https://platform.openai.com/settings/organization/billing/overview">OpenAI 付款頁面</a> 會顯示目前餘額。</p>
<p><img alt="Billing" src="/posts/openai-api/assets/Billing.png">
</p>
<p>點選 <code>Payment methods</code> 新增新用卡。</p>
<p><img alt="Credit_Card" src="/posts/openai-api/assets/Credit_Card.png">
</p>
<p>再回到 <code>Overview</code> 點選 <code>Add to credit balance</code>，儲值金額到 OpenAI 戶頭。</p>
<p>最低儲值金額 5 美金，根據需求儲值。</p>
<p><img alt="Credit" src="/posts/openai-api/assets/Credit.png">
</p>
<h3 id="查看-api-用量">查看 API 用量</h3>
<p>前往 <a href="https://platform.openai.com/usage">OpenAI Usage</a> 可以查看使用量。</p>
<p><img alt="OpenAI_Usage" src="/posts/openai-api/assets/OpenAI_Usage.png">
</p>
<h2 id="使用-openai-api-金鑰">使用 OpenAI API 金鑰</h2>
<h3 id="部屬-open-webui">部屬 Open WebUI</h3>
<p>我使用 <a href="https://openwebui.com/">Open WebUI</a> 搭配 OpenAI Key 選擇 GPT 模型和 AI 對話。</p>
<p>Open WebUI 提供 Docker 方式建立，以下是 <code>docker-compose.yml</code> 內容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.8&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">open-webui</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/open-webui/open-webui:main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000:8080&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">OPENAI_API_KEY</span><span class="p">:</span><span class="w"> </span><span class="l">your_secret_key </span><span class="w"> </span><span class="c">#填入 OpenAI API 金鑰</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./open-webui:/app/backend/data</span><span class="w">
</span></span></span></code></pre></div><p><code>your_secret_key</code> 填入剛剛建立的 OpenAI API。</p>
<p>輸入指令 <code>docker compose up -d</code> 部屬 Open WebUI。</p>
<p>前往 <code>IP位址:3000</code>，首先建立 Open WebUI 使用者帳號登入。</p>
<p><strong>注意</strong>：Open WebUI 帳號非 OpebAI 帳戶，兩者是不同關係。</p>
<p>登入成功會看到與 ChatGPT 網頁類似的聊天介面。</p>
<p><img alt="Open_WebUI" src="/posts/openai-api/assets/Open_WebUI.png">
</p>
<p>左側可以收合側邊欄，選擇想使用的 GPT 模型就可以開始和 AI 對話。</p>
<p><img alt="Select_Models" src="/posts/openai-api/assets/Select_Models.png">
</p>
<p><img alt="Chat" src="/posts/openai-api/assets/Chat.png">
</p>
<h3 id="設定對話繁體中文">設定對話繁體中文</h3>
<p>點右上角頭像選擇設定，系統提示詞可以讓 LLM 依照你的要求回應，例如加上 <code>回應時使用**繁體中文**</code>。</p>
<p><img alt="ZH_TW" src="/posts/openai-api/assets/ZH_TW.png">
</p>
<h3 id="設定預設模型">設定預設模型</h3>
<p>在 <code>介面</code> 設定中，可以設定每次對話預設的模型。</p>
<p><img alt="Default_Model" src="/posts/openai-api/assets/Default_Model.png">
</p>
<p>以上是透過 Open WebUI 使用 OpenAI API 金鑰與 ChatGPT 對話，依照問題難度可以自由選擇使用的模型來解決。目前最新的 <a href="https://openai.com/o1/">OpenAI o1</a> 模型 Open WebUI 尚未正式支援。</p>
<h2 id="附錄">附錄</h2>
<p><a href="https://mrpeelblog.com/posts/docker/">快速上手 Docker 和 Docker Compose</a></p>
<p><a href="https://openai.com/api/pricing/">OpenAI Models Pricing</a></p>
<p><a href="https://docs.openwebui.com/">Open WebUI Doc</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Markdown 編輯器推薦：Typora &#43; 開源插件使用心得</title>
      <link>https://mrpeelblog.com/posts/typora/</link>
      <pubDate>Sun, 20 Oct 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/typora/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>Typora 是一款 <a href="https://zh.wikipedia.org/zh-tw/%E6%89%80%E8%A7%81%E5%8D%B3%E6%89%80%E5%BE%97">WYSIWYM</a> Markdown 付費編輯器，能直接編輯渲染的 <code>.md</code> 文件，也能自訂主題外觀，搭配 Typora Plugin 可以增加寫作體驗，例如：使用分頁切換瀏覽、中英文排版優化、Markdown 語法檢查。</p>
<p>平時記筆記和寫部落格文章皆是使用 Typora 撰寫，配合插件使用兩年整體體驗良好。推薦大家使用看看。</p>
<p>Typora 官方網站：<a href="https://typora.io/">https://typora.io/</a></p>
<p>Typora Plugin GitHub：<a href="https://github.com/obgnail/typora_plugin">https://github.com/obgnail/typora_plugin</a></p>
<h2 id="下載與安裝">下載與安裝</h2>
<p>前往 <a href="https://typora.io/#download">Typora 下載頁面</a>，支持 Windows、macOS、Linux 作業系統，軟體定價為 $14.99 美金，下載提供 15 天試用、購買軟體可以授權在 3 台不同的裝置使用。</p>
<p>若要在第 4 台裝置使用，可以停用前面裝置授權再增加新的裝置。詳請參考 <a href="https://store.typora.io/#faq">官方 FAQ</a>。</p>
<p><img alt="Typora_Licence" src="/posts/typora/assets/Typora_Licence.png">
</p>
<h2 id="介面佈局">介面佈局</h2>
<p>Typora 佈局左側為檔案瀏覽器 ／ 大綱，右側是主要編輯區。</p>
<p>Typora 提供兩種視窗樣式：經典、一體化。</p>
<p>我習慣使用一體化界面，版面 UI 最少，可以看得到更多內容。</p>
<p><img alt="Typora" src="/posts/typora/assets/Typora.png">
</p>
<p>快捷鍵「Ctrl＋，」開啟設定畫面。</p>
<p><img alt="Settings" src="/posts/typora/assets/Settings.png">
</p>
<h2 id="插入圖片">插入圖片</h2>
<p>可以直接貼上圖片，圖片規則依照設定可以存放在文章根目錄 ／ 子目錄、自訂目錄或是上傳圖片等。</p>
<p><img alt="Image_Rules" src="/posts/typora/assets/Image_Rules.png">
</p>
<h2 id="使用插件">使用插件</h2>
<p>Typora 基本功能已經足夠使用，為了符合特定需求，GitHub 上名為 <a href="https://github.com/obgnail/typora_plugin">Typora Plugin</a> 第三方插件提供額外功能增加使用體驗，例如：window_tab（分頁瀏覽）、md_padding（中英文排版）、markdownLint（格式檢查）。</p>
<p>安裝步驟 Typora Plugin 有 <a href="https://github.com/obgnail/typora_plugin?tab=readme-ov-file#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95%E4%B8%80%E8%87%AA%E5%8A%A8">詳細教學</a>，這邊就不贅述了。</p>
<p>Typora Plugin 提供許多功能，我這邊用到以下功能，其他功能因人而異可以自由使用。</p>
<h3 id="分頁瀏覽">分頁瀏覽</h3>
<p>會以分頁的方式開啟檔案。</p>
<p><img alt="window_tab" src="https://github.com/obgnail/typora_plugin/raw/master/assets/window_tab.gif">
 圖片來源：typora_plugin</p>
<h3 id="中英文排版">中英文排版</h3>
<p>這個功能對我來說很有用，我會希望中文和英文詞之間有空格，讓整體排版看起來更整齊。</p>
<p><img alt="md_padding" src="https://github.com/obgnail/typora_plugin/raw/master/assets/md_padding.gif">
 圖片來源：typora_plugin</p>
<h3 id="格式檢查">格式檢查</h3>
<p>透過 <a href="https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md">Markdown lint</a> 規則來檢查內容是否有違反規則。點右上角小方塊可以檢查。</p>
<h2 id="更改主題">更改主題</h2>
<p>Typora 可以切換主題，也能輕鬆安裝其他第三方主題來改變外觀。我使用 <a href="https://github.com/adesurirey/typora-notes-dark-theme">Notes-dark</a> 主題，並修改字型和一些細節。</p>
<p><img alt="Notes-dark" src="/posts/typora/assets/Notes-dark.png">
</p>
<h2 id="跨平台同步">跨平台同步</h2>
<p>Typora 沒有手機版本只能本地編輯，所以移動端要使用其他的 Markdown 編輯器和同步軟體才能實現跨平台同步。</p>
<p>Android 系統我推薦使用 <a href="https://play.google.com/store/apps/details?id=org.eu.thedoc.zettelnotes&amp;hl=en">Zettel Notes</a>、iOS 系統使用 <a href="https://apps.apple.com/tw/app/1writer-markdown-text-editor/id680469088">1Writer</a>。透過 WebDAV 方式同步文件實現同步效果。</p>
<p>可以參考部落格文章「<a href="https://mrpeelblog.com/posts/webdav-notes-system/">使用 WebDAV 同步 Markdwon 筆記</a>」。</p>
]]></content:encoded>
    </item>
    <item>
      <title>修改 Hugo 文章資料夾位置</title>
      <link>https://mrpeelblog.com/posts/hugo-content/</link>
      <pubDate>Tue, 24 Sep 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/hugo-content/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>大部分頁面和文章都存放在 Hugo 根目錄下的 <code>content</code> 資料夾中，通常發布的文章會在 <code>content/posts</code> 資料夾裡面。</p>
<p>我會將部落格的文章儲存到其他地方，每次 Hugo 部屬再去抓文章的位置。使文章頁面和 Hugo 本體分開，我可以更好的去控制文章的版本或是備份。</p>
<p>本篇文章教學根目錄下的 <code>content</code> 的路徑改為其他儲存位置。</p>
<h2 id="變更-content-目錄">變更 content 目錄</h2>
<p>修改 Hugo 根目錄下的 <code>config.yaml</code> 或 <code>config.toml</code> ，新增以下這行。</p>
<p>config.yaml：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">contentDir</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;新的位置&#34;</span><span class="w">
</span></span></span></code></pre></div><p>config.toml：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">contentDir</span> <span class="p">=</span> <span class="s2">&#34;新的位置&#34;</span>
</span></span></code></pre></div><p><code>contentDir</code> 可以將 <code>content</code> 目錄指定為其他位置。</p>
<p>使用 <code>hugo new</code> 指令時，頁面會存在 <code>新的位置/posts/title1/index.md</code> 裡面。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">hugo new posts/title1/index.md
</span></span></code></pre></div><h2 id="解決看不到文章">解決看不到文章</h2>
<p>若在 <a href="https://www.netlify.com/">Netlify</a> 或 <a href="https://pages.cloudflare.com/">Cloudflare Page</a> 部屬網站之後看不到文章，請不要使用 <code>hugo</code> 指令，直接讓他們讀取我們本地 <code>hugo</code> 產生的 <code>public</code> 資料夾可以解決。</p>
<p>因為 <code>content</code> 資料夾已經不在 <code>hugo</code> 根目錄下了，Netlify 或 Cloudflare Page 使用指令部屬會找不到 <code>content</code> 資料夾 ，導致產生出來 <code>/public</code> 的靜態網站沒有文章頁面。</p>
<p>所以為了不要讓部屬網站麻煩，建議自己先在本地端產生 <code>public</code> 目錄，再把 <code>/public</code> 上傳上去。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://gohugo.io/getting-started/configuration/">Configure Hugo</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>使用 WebDAV 同步 Markdown 筆記</title>
      <link>https://mrpeelblog.com/posts/webdav-notes-system/</link>
      <pubDate>Sun, 22 Sep 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/webdav-notes-system/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在筆記軟體盛行的時代，我們有許多記筆記的方式，大部分軟體會保存使用者的資料在伺服器上面。若是輕度使用不會有太大影響，但長期或重度的使用者會衍伸出一些問題：</p>
<ol>
<li>資料沒有隱私：無法保障資料不會被其他人查看。</li>
<li>資料儲存格式不統一：軟體公司會使用自家的格式保存檔案，有可能會遇到用其他筆記軟體無法開啟或編輯。</li>
<li>軟體營運決策：開公司會想要賺錢，隨時都有可能被養套殺，會因大量筆記搬家很累而無奈繼續使用他們的軟體。</li>
</ol>
<p>此系統優點是，同步快速穩定，圖片可以正常顯示，且圖片不是放在公開的網址上，也可以把 WebDAV 當作私人硬碟，隨時上傳任何檔案。一切的一切都由你主導控制。只需要維護系統就好。</p>
<p>本篇實現如標題所講的功能，可以在多平台查看和編輯 Markdown 筆記。主要原理是透過 WebDAV 的方式來同步，需要的軟體技術如下：</p>
<p><strong>所需軟體：</strong></p>
<ul>
<li>
<p>伺服器端：<a href="https://releases.ubuntu.com/jammy/">Ubuntu Server 22.04.5 LTS</a></p>
</li>
<li>
<p>電腦端：<a href="https://typora.io/">Typora</a>，付費軟體。簡潔介面、WYSIWYM，推薦使用</p>
</li>
<li>
<p>手機端（iOS）：<a href="https://apps.apple.com/tw/app/1writer-markdown-text-editor/id680469088">1Writer</a>，付費軟體。支援 Markdown 渲染及 WebDAV，推薦使用</p>
</li>
<li>
<p>手機端（Android）：<a href="https://play.google.com/store/apps/details?id=org.eu.thedoc.zettelnotes&amp;hl=en">Zettel Notes</a>，免費軟體。支援 Markdown 渲染及 WebDAV，推薦使用</p>
</li>
<li>
<p>個人網域 ＋ SSL 認證：iOS 必要，連線需要 https</p>
</li>
<li>
<p><a href="https://www.docker.com/">Docker</a>、<a href="https://docs.docker.com/compose/">Docker Compose</a>：安裝 WebDAV、Nginx Proxy Manager</p>
</li>
<li>
<p><a href="https://rclone.org/">Rclone</a> WebDAV：透過 Rclone 架設 WebDAV</p>
</li>
<li>
<p><a href="https://nginxproxymanager.com/">Nginx Proxy Manager</a>：實現反向代理</p>
</li>
</ul>
<h2 id="安裝-nginx-proxy-manager">安裝 Nginx Proxy Manager</h2>
<p>Nginx Proxy Manager 以下簡稱 NPM，可以更輕鬆設定反向代理，以及 SSL 認證。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">mkdir -p docker/npm <span class="c1">#在 docker 資料夾底下建立 npm 資料夾</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> docker/npm
</span></span><span class="line"><span class="cl">nano docker-compose.yml
</span></span></code></pre></div><h3 id="設定-npm-docker-composeyml">設定 NPM docker-compose.yml</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;jc21/nginx-proxy-manager:latest&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;80:80&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;81:81&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;443:443&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./letsencrypt:/etc/letsencrypt</span><span class="w">
</span></span></span></code></pre></div><h3 id="設定-port-forwarding">設定 Port Forwarding</h3>
<p>開啟 <code>80</code>、<code>81</code>、<code>443</code>。</p>
<p><img alt="Set_Port_Forwarding" src="/posts/webdav-notes-system/assets/Set_Port_Forwarding.png">
</p>
<h3 id="啟動-nginx-proxy-manger">啟動 Nginx Proxy Manger</h3>
<p>設定好 <code>docker-compose.yml</code> 和防火牆後，啟動 Docker。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">docker compose up -d
</span></span></code></pre></div><p>同目錄會產生 <code>data</code>、<code>letsencrypt</code> 資料夾不要刪掉。</p>
<p>網址輸入：<code>http://內網IP:81/</code> 進入 NPM 管理介面，輸入帳號 <code>admin@example.com</code> 和密碼 <code>changeme</code> 來修改日後登入帳密。</p>
<p><img alt="NPM_login" src="/posts/webdav-notes-system/assets/NPM_login.png">
</p>
<h2 id="架設-rclone-webdav">架設 Rclone WebDAV</h2>
<h3 id="安裝-rclone">安裝 Rclone</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo -v <span class="p">;</span> curl https://rclone.org/install.sh <span class="p">|</span> sudo bash
</span></span></code></pre></div><h3 id="調整資料夾架構">調整資料夾架構</h3>
<p>關於資料夾選用，我的建議是 Docker 相關的腳本歸在一類，與自用的存筆記資料夾分開，避免誤刪。</p>
<p>示範將筆記資料存在 <code>notes</code> 資料夾。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">home
</span></span><span class="line"><span class="cl">├── dockers
</span></span><span class="line"><span class="cl">│   ├── npm
</span></span><span class="line"><span class="cl">│   └── rclone
</span></span><span class="line"><span class="cl">└── notes
</span></span></code></pre></div><h3 id="設定-webdav-docker-composeyml">設定 WebDAV docker-compose.yml</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">cd</span> .. <span class="c1">#回到 Home 目錄</span>
</span></span><span class="line"><span class="cl">mkdir docker/rclone
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> docker/rclone
</span></span><span class="line"><span class="cl">nano docker-compose.yml
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">webdav</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">rclone/rclone:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;serve&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;webdav&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--addr&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;:7000&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--user&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;web_ac&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--pass&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;web_pwd&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--read-only=false&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/home/note/&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;7000:7000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/note:/home/note</span><span class="w">
</span></span></span></code></pre></div><p>在 <code>command</code> 中：</p>
<ul>
<li>
<p><code>:7000</code>：Port 號，如果 <code>7000</code> 使已用的話，請自行更改。</p>
<p>注意：webdav 的 <code>ports</code> 冒號兩邊都要更改成一樣。</p>
</li>
<li>
<p><code>web_ac</code>：WebDAV 登入帳號</p>
</li>
<li>
<p><code>web_pwd</code>：WebDAV 登入密碼</p>
</li>
<li>
<p><code>/home/note/</code>：筆記存放目錄，如果要修改的話 <code>volumes</code> 冒號兩邊都要修改成一樣。</p>
</li>
</ul>
<h3 id="啟動-rclone-webdav">啟動 Rclone WebDAV</h3>
<p>設定好 <code>docker-compose.yml</code> 和規劃筆記資料夾後，啟動 Docker。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">docker compose up -d
</span></span></code></pre></div><h2 id="設定-webdav-網址">設定 WebDAV 網址</h2>
<p>此步驟選用，如果要在 iOS 上使用 WebDAV，網址需要有 SSL 認證。</p>
<h3 id="網域供應商新增-dns">網域供應商新增 DNS</h3>
<p><img alt="Cloudflare" src="/posts/webdav-notes-system/assets/Cloudflare.png">
</p>
<p>確認使用者已加入 <code>sudo</code> 群組</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo usermod -aG sudo user <span class="c1"># 將 user 加入 sudo 群組</span>
</span></span><span class="line"><span class="cl">groups
</span></span></code></pre></div><h3 id="npm-新增反向代理ssl-證書">NPM 新增反向代理、SSL 證書</h3>
<p>登入 NPM，點選「ADD Proxy Host」。
注意：不要勾選 <code>Cache Assets</code> 和 <code>Block Common Exploits</code>，否則無法正常運作。</p>
<p><img alt="Edit Proxy Host" src="/posts/webdav-notes-system/assets/Edit_Proxy_Host.png">
</p>
<p>SSL 設定</p>
<p>點選 <code>Request a new SSL Certificate</code>。</p>
<p><img alt="SSL_Settings" src="/posts/webdav-notes-system/assets/SSL_Settings.png">
</p>
<p>開啟 <code>Force SSL</code>。</p>
<p><img alt="Force_SSL" src="/posts/webdav-notes-system/assets/Force_SSL.png">
</p>
<h2 id="登入-webdav">登入 WebDAV</h2>
<p>以上設定好後，接下來在各個平台來驗證看看。</p>
<h3 id="瀏覽器">瀏覽器</h3>
<p>連線 WebDAV 網址，可以帳號密碼登入就代表 WebDAV 架設成功。</p>
<p><img alt="WebDAV_Web" src="/posts/webdav-notes-system/assets/WebDAV_Web.png">
</p>
<h3 id="windwos-網路磁碟機">Windwos 網路磁碟機</h3>
<p>檔案總管右鍵，新增網路位置，輸入帳號密碼。</p>
<p><img alt="WebDAV_Windows" src="/posts/webdav-notes-system/assets/WebDAV_Windows.png">
</p>
<h3 id="typora">Typora</h3>
<p>剛安裝完 Typora 可以讓你試用 14 天，到期再選擇購買授權碼啟用。</p>
<p>開啟側邊欄（Ctrl＋／），點下方按鈕選擇開啟資料夾 。</p>
<p><img alt="Typora" src="/posts/webdav-notes-system/assets/Typora.png">
</p>
<h3 id="ios">iOS</h3>
<p>開啟 1Writer，點選 <code>新增資料夾</code>，選擇 <code>WebDAV</code>，輸入 WebDAV 網址以及帳號密碼。</p>
<p>iOS 的 WebDAV 網址要有 SSL 認證，若擔心公開至網路上，可以參考 <a href="https://mrpeelblog.com/posts/ssl-with-intranet/">這一篇文章</a> 架設內網 SSL 憑證。</p>
<p><img alt="1Writer" src="/posts/webdav-notes-system/assets/1Writer.png">
</p>
<h3 id="android">Android</h3>
<p>開啟 Zettel Notes 進入儲存庫管理介面，編輯儲存庫同步選擇 WebDAV。</p>
<p><img alt="Zettel_Notes" src="/posts/webdav-notes-system/assets/Zettel_Notes.jpg">
</p>
<h2 id="結語">結語</h2>
<p>以上是透過 WebDAV 實現多平台同步筆記內容，透過自架的方式我們可以掌握資料在手中。未來如果要換筆記軟體或儲存位置也可以無腦搬家。</p>
<p>除此之外定期備份也不能馬虎，Rclone 也提供上傳至雲端硬碟的功能。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://rclone.org/commands/rclone_serve_webdav/">rclone serve webdav</a></p>
<p><a href="https://mrpeelblog.com/posts/typora/">Markdown 編輯器推薦：Typora + 開源插件使用心得</a></p>
<p><a href="https://mrpeelblog.com/posts/docker/">快速上手 Docker 和 Docker Compose</a></p>
<p><a href="https://mrpeelblog.com/posts/ssl-with-intranet/">如何在內網服務中使用 SSL 憑證</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>快速上手 Docker 和 Docker Compose</title>
      <link>https://mrpeelblog.com/posts/docker/</link>
      <pubDate>Fri, 20 Sep 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/docker/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>什麼是 Docker？</p>
<p>Docker 可以幫助你快速部屬應用程式，不需要設定系統環境，減少開發人員與使用者部屬時間。</p>
<p>下圖是 Docker 在作業系統中的架構，透過它可以執行數個應用程式，且應用程式之間是獨立運行的。</p>
<p><img alt="Docker containerized appliction blue border 2" src="https://www.docker.com/wp-content/uploads/2021/11/docker-containerized-appliction-blue-border_2.png">
</p>
<p>圖片來源：<a href="https://www.docker.com/resources/what-container/">Docker</a></p>
<h2 id="docker-基礎名詞解釋">Docker 基礎名詞解釋</h2>
<h3 id="image">Image</h3>
<p>Image（映像檔），開發人員撰寫程式並把需要依賴的套件環境整個打包在一起，要部屬應用程式要有 image 才能進行。</p>
<h3 id="container">Container</h3>
<p>Container（容器），透過 Image 部屬出來的應用，一個 Image 可以部屬數個 Container，且這些 Container 之間互不影響，也不會影響 image 本體。</p>
<h3 id="docker-compose">Docker Compose</h3>
<p>Docker Compose 是將很多 Docker 服務和啟動參數打包在一個文件 <code>docker compose.yml</code> 中，可以在其他環境下使用。我自己是用 Docker Compose 來使用 Docker 的服務。</p>
<p>有些應用它會需要依賴許多套件，例如資料庫 MySQL、PostgreSQL。</p>
<p>一般使用 Docker 服務流程：</p>
<p>Pull Docker Image（下載 Image）&ndash;&gt; 透過指令 <code>docker run</code> 或 <code>docker compose up -d</code> &ndash;&gt; Docker Container 部屬成功</p>
<h2 id="安裝-docker-和-docker-compose">安裝 Docker 和 Docker Compose</h2>
<h3 id="安裝-docker">安裝 Docker</h3>
<p>使用指令 <code>curl</code> 從官網下載。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -fsSL https://get.docker.com/ <span class="p">|</span> sh
</span></span></code></pre></div><p>提高使用者執行權限。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo gpasswd -a username docker <span class="c1">#username 自行換成使用者帳號</span>
</span></span><span class="line"><span class="cl">newgrp docker
</span></span></code></pre></div><p>啟動 Docker 服務。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo service docker start
</span></span></code></pre></div><h3 id="安裝-docker-compose">安裝 Docker Compose</h3>
<p>使用指令 <code>curl</code> 從 <a href="https://github.com/docker/compose">Github Release</a> 下載。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo curl -SL https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
</span></span></code></pre></div><p>確認 Docker Compose 是否安裝成功。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">docker compose version
</span></span></code></pre></div><h2 id="部屬-docker-服務">部屬 Docker 服務</h2>
<p>我們以 <a href="https://hub.docker.com/_/hello-world">Hello World</a> 來示範：</p>
<h3 id="docker-run-方式部屬">Docker Run 方式部屬</h3>
<p>首先下載 Hello World Image：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ docker pull hello-world
</span></span><span class="line"><span class="cl">Using default tag: latest
</span></span><span class="line"><span class="cl">latest: Pulling from library/hello-world
</span></span><span class="line"><span class="cl">c1ec31eb5944: Pull <span class="nb">complete</span>
</span></span><span class="line"><span class="cl">Digest: sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2
</span></span><span class="line"><span class="cl">Status: Downloaded newer image <span class="k">for</span> hello-world:latest
</span></span><span class="line"><span class="cl">docker.io/library/hello-world:latest
</span></span></code></pre></div><p>接著使用 <code>docker run hello-world</code> 跑 Hello World。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ docker run hello-world
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Hello from Docker!
</span></span><span class="line"><span class="cl">This message shows that your installation appears to be working correctly.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">To generate this message, Docker took the following steps:
</span></span><span class="line"><span class="cl"> 1. The Docker client contacted the Docker daemon.
</span></span><span class="line"><span class="cl"> 2. The Docker daemon pulled the <span class="s2">&#34;hello-world&#34;</span> image from the Docker Hub.
</span></span><span class="line"><span class="cl">    <span class="o">(</span>amd64<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 3. The Docker daemon created a new container from that image which runs the
</span></span><span class="line"><span class="cl">    executable that produces the output you are currently reading.
</span></span><span class="line"><span class="cl"> 4. The Docker daemon streamed that output to the Docker client, which sent it
</span></span><span class="line"><span class="cl">    to your terminal.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">To try something more ambitious, you can run an Ubuntu container with:
</span></span><span class="line"><span class="cl"> $ docker run -it ubuntu bash
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Share images, automate workflows, and more with a free Docker ID:
</span></span><span class="line"><span class="cl"> https://hub.docker.com/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">For more examples and ideas, visit:
</span></span><span class="line"><span class="cl"> https://docs.docker.com/get-started/
</span></span></code></pre></div><p>若有輸出內容代表 Hello World 已成功啟動。</p>
<h3 id="docker-compose-方式部屬">Docker Compose 方式部屬</h3>
<p>若使用 Docker Compose 建議先建立資料夾存放 <code>docker-compose.yml</code> 文件，因為有些專案會需要實際位置存放資料。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">mkdir hello-world
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> hello-world
</span></span><span class="line"><span class="cl">nano docker-compose.yml 
</span></span></code></pre></div><p>將下列參數貼上至 <code>docker-compose.yml</code>，「CTRL + X」儲存。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hello_world</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">hello-world</span><span class="w">
</span></span></span></code></pre></div><h3 id="參數說明">參數說明</h3>
<p>services：使用的程式套件，依照想要的功能可以掛上其他套件，例如資料庫。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span></code></pre></div><p>輸入 <code>docker compose up</code> 啟動。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ docker compose up
</span></span><span class="line"><span class="cl"><span class="o">[</span>+<span class="o">]</span> Running 2/1
</span></span><span class="line"><span class="cl"> ✔ Network hello-world_default          C...                               0.1s
</span></span><span class="line"><span class="cl"> ✔ Container hello-world-hello_world-1  Created                            0.0s
</span></span><span class="line"><span class="cl">Attaching to hello_world-1
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span> Hello from Docker!
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span> This message shows that your installation appears to be working correctly.
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span> To generate this message, Docker took the following steps:
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>  1. The Docker client contacted the Docker daemon.
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>  2. The Docker daemon pulled the <span class="s2">&#34;hello-world&#34;</span> image from the Docker Hub.
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>     <span class="o">(</span>amd64<span class="o">)</span>
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>  3. The Docker daemon created a new container from that image which runs the
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>     executable that produces the output you are currently reading.
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>  4. The Docker daemon streamed that output to the Docker client, which sent it
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>     to your terminal.
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span> To try something more ambitious, you can run an Ubuntu container with:
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>  $ docker run -it ubuntu bash
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span> Share images, automate workflows, and more with a free Docker ID:
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>  https://hub.docker.com/
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span> For more examples and ideas, visit:
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>  https://docs.docker.com/get-started/
</span></span><span class="line"><span class="cl">hello_world-1  <span class="p">|</span>
</span></span><span class="line"><span class="cl">hello_world-1 exited with code <span class="m">0</span>
</span></span></code></pre></div><p>若有輸出內容代表 Hello World 已成功啟動。</p>
<p>以上是基本使用 Docker 的流程，關於 Docker compose 需要補充的地方，未來會更新一篇文章細部講解。</p>
<h2 id="docker-常用指令">Docker 常用指令</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">docker compose up -d <span class="c1">#背景啟動容器</span>
</span></span><span class="line"><span class="cl">docker compose down <span class="c1">#停止容器</span>
</span></span><span class="line"><span class="cl">docker compose pull <span class="c1">#更新 image（先 docker compose down 容器）</span>
</span></span><span class="line"><span class="cl">docker images <span class="c1">#查看已下載的映像檔</span>
</span></span><span class="line"><span class="cl">docker image prune -a <span class="c1">#刪除無用的映像檔</span>
</span></span><span class="line"><span class="cl">docker system prune -a <span class="c1">#刪除無用的映像檔與已經停止的容器</span>
</span></span></code></pre></div><h2 id="參考資料">參考資料</h2>
<p><a href="https://www.docker.com/">Docker 官網</a></p>
<p><a href="https://github.com/docker/compose">Docker Compose</a></p>
<p><a href="https://hub.docker.com/_/hello-world">Hello World Image</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>利用 YouTube API 取得訂閱頻道 RSS 網址</title>
      <link>https://mrpeelblog.com/posts/youtube-rss/</link>
      <pubDate>Wed, 18 Sep 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/youtube-rss/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>如果想要用 RSS 訂閱 YouTube 頻道動態，Google 官方有提供 RSS 訂閱網址訂閱頻道。</p>
<p>RSS 訂閱網址：<a href="https://www.youtube.com/feeds/videos.xml?channel_id=%7Bchannel_id%7D">https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}</a></p>
<p>頻道簡介可以取得 <code>channel_id</code>。</p>
<p><img alt="Channel_ID" src="/posts/youtube-rss/assets/Channel_ID.png">
</p>
<p><code>channel_id</code> 填入網址可以透過 RSS 程式訂閱頻道更新。</p>
<p><a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCpGk56cJDZcVqIxZatX7nbQ">https://www.youtube.com/feeds/videos.xml?channel_id=UCpGk56cJDZcVqIxZatX7nbQ</a></p>
<p><img alt="RSS_Sub" src="/posts/youtube-rss/assets/RSS_Sub.png">
</p>
<p>但是訂閱頻道數量多的話就要重複步驟很多次，有更快速的方法獲得所有訂閱頻道的 RSS 網址呢？</p>
<p>答案是可以，透過 Google 提供的 YouTube Data API 可以做到，以下分享 <strong>利用 YouTube API 取得訂閱頻道 RSS 網址</strong>。</p>
<h2 id="1-建立-youtube-data-api">1. 建立 YouTube Data API</h2>
<h3 id="1-新增專案">1. 新增專案</h3>
<p>首先進入 <a href="https://console.cloud.google.com/?hl=zh-tw">Google Cloud Console</a>，建立新專案。</p>
<p><img alt="Create_Project_1" src="/posts/youtube-rss/assets/Create_Project_1.png">
</p>
<p>專案名稱可以自由取名。</p>
<p><img alt="Create_Project" src="/posts/youtube-rss/assets/Create_Project.png">
</p>
<p>建立專案之後，前往 API 和服務。</p>
<p><img alt="Console_API" src="/posts/youtube-rss/assets/Console_API.png">
</p>
<h3 id="2-啟用-api">2. 啟用 API</h3>
<p>啟用 API 服務，選擇 <code>YouTube Data API v3</code>。讀取訂閱頻道需要 API 協助。</p>
<p><img alt="Enable_API" src="/posts/youtube-rss/assets/Enable_API.png">
</p>
<h3 id="3-設定憑證">3. 設定憑證</h3>
<p>選擇 API 之後，我們要設定使用 API 金鑰和 OAuth 用戶端 ID。</p>
<p>建立憑證，選擇 API 金鑰。</p>
<p><img alt="Create_API_Key" src="/posts/youtube-rss/assets/Create_API_Key.png">
</p>
<p>API 金鑰建立好，會顯示金鑰密碼，不要讓別人取得這個金鑰。</p>
<p><img alt="API_Key" src="/posts/youtube-rss/assets/API_Key.png">
</p>
<p>建立 OAuth 用戶端 ID。</p>
<p><img alt="Create_OAuth_ID" src="/posts/youtube-rss/assets/Create_OAuth_ID.png">
</p>
<p>選擇電腦版應用程式，名稱自由取名。</p>
<p><img alt="Create_OAuth_ID1" src="/posts/youtube-rss/assets/Create_OAuth_ID1.png">
</p>
<p>進入 OAuth 用戶端 ID，右方下載 JSON 檔，後面金鑰驗證會用到。</p>
<p><img alt="Download_JSON" src="/posts/youtube-rss/assets/Download_JSON.png">
</p>
<p>設定 OAuth 同意畫面，填寫基本資訊。</p>
<p><img alt="OAuth" src="/posts/youtube-rss/assets/OAuth.png">
</p>
<p>在 OAuth 同意畫面新增測試使用者，這個測試使用者是要存取 YouTube 資料的帳戶。<strong>若沒有設定會沒有權限使用 API 金鑰</strong>。</p>
<p><img alt="OAuth1" src="/posts/youtube-rss/assets/OAuth1.png">
</p>
<h2 id="2-撰寫-python-程式碼">2. 撰寫 Python 程式碼</h2>
<p>測試環境：</p>
<ul>
<li>Windows 11</li>
<li>Python 3.12.6</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">google.auth</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">google_auth_oauthlib.flow</span> <span class="kn">import</span> <span class="n">InstalledAppFlow</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 認證範圍</span>
</span></span><span class="line"><span class="cl"><span class="n">SCOPES</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;https://www.googleapis.com/auth/youtube.readonly&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 取得 API 金鑰</span>
</span></span><span class="line"><span class="cl"><span class="n">credentials</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 如果已經存在 token.json，就直接使用</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="s1">&#39;token.json&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="kn">from</span> <span class="nn">google.oauth2.credentials</span> <span class="kn">import</span> <span class="n">Credentials</span>
</span></span><span class="line"><span class="cl">    <span class="n">credentials</span> <span class="o">=</span> <span class="n">Credentials</span><span class="o">.</span><span class="n">from_authorized_user_file</span><span class="p">(</span><span class="s1">&#39;token.json&#39;</span><span class="p">,</span> <span class="n">SCOPES</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 如果沒有可用的憑證，讓用戶登入</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="ow">not</span> <span class="n">credentials</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">credentials</span><span class="o">.</span><span class="n">valid</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">credentials</span> <span class="ow">and</span> <span class="n">credentials</span><span class="o">.</span><span class="n">expired</span> <span class="ow">and</span> <span class="n">credentials</span><span class="o">.</span><span class="n">refresh_token</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">credentials</span><span class="o">.</span><span class="n">refresh</span><span class="p">(</span><span class="n">requests</span><span class="o">.</span><span class="n">Request</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">flow</span> <span class="o">=</span> <span class="n">InstalledAppFlow</span><span class="o">.</span><span class="n">from_client_secrets_file</span><span class="p">(</span><span class="s1">&#39;client_secrets.json&#39;</span><span class="p">,</span> <span class="n">SCOPES</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">credentials</span> <span class="o">=</span> <span class="n">flow</span><span class="o">.</span><span class="n">run_local_server</span><span class="p">(</span><span class="n">port</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># 儲存憑證以供下次使用</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;token.json&#39;</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">token</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">token</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">credentials</span><span class="o">.</span><span class="n">to_json</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 讀取訂閱列表</span>
</span></span><span class="line"><span class="cl"><span class="n">api_url</span> <span class="o">=</span> <span class="s1">&#39;https://www.googleapis.com/youtube/v3/subscriptions?part=snippet&amp;mine=true&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">subscriptions</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">next_page_token</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">next_page_token</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">api_url</span> <span class="o">+</span> <span class="sa">f</span><span class="s1">&#39;&amp;pageToken=</span><span class="si">{</span><span class="n">next_page_token</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s1">&#39;Authorization&#39;</span><span class="p">:</span> <span class="sa">f</span><span class="s1">&#39;Bearer </span><span class="si">{</span><span class="n">credentials</span><span class="o">.</span><span class="n">token</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">api_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s1">&#39;Authorization&#39;</span><span class="p">:</span> <span class="sa">f</span><span class="s1">&#39;Bearer </span><span class="si">{</span><span class="n">credentials</span><span class="o">.</span><span class="n">token</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;發生錯誤: </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s1">, </span><span class="si">{</span><span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">subscriptions</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;items&#39;</span><span class="p">,</span> <span class="p">[]))</span>
</span></span><span class="line"><span class="cl">    <span class="n">next_page_token</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;nextPageToken&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># 如果沒有下一頁，停止請求</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">next_page_token</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 將所有訂閱頻道的 RSS 網址寫入 RSS.txt</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;RSS.txt&#39;</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">subscriptions</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">channel_title</span> <span class="o">=</span> <span class="n">item</span><span class="p">[</span><span class="s1">&#39;snippet&#39;</span><span class="p">][</span><span class="s1">&#39;title&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">channel_id</span> <span class="o">=</span> <span class="n">item</span><span class="p">[</span><span class="s1">&#39;snippet&#39;</span><span class="p">][</span><span class="s1">&#39;resourceId&#39;</span><span class="p">][</span><span class="s1">&#39;channelId&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">rss_feed</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;https://www.youtube.com/feeds/videos.xml?channel_id=</span><span class="si">{</span><span class="n">channel_id</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;頻道名稱: </span><span class="si">{</span><span class="n">channel_title</span><span class="si">}</span><span class="s1">, RSS 訂閱網址: </span><span class="si">{</span><span class="n">rss_feed</span><span class="si">}</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;RSS 連結已寫入 RSS.txt&#34;</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="3-使用-api">3. 使用 API</h2>
<p>重新命名下載的 JSON 檔改為 <code>client_secrets.json</code>，並和程式碼放在同一個目錄。</p>
<p>安裝 Python 套件</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install requests google-auth google-auth-oauthlib
</span></span></code></pre></div><p>執行 Python 程式</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ python youtube_sub.py
</span></span><span class="line"><span class="cl">Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type<span class="o">=</span> ....
</span></span></code></pre></div><p>瀏覽器會跳出驗證網頁，選擇要讀取的 YouTube 帳戶，最後出現「這個應用程式未經 Google 驗證」，選擇「繼續」。</p>
<p><img alt="Validation" src="/posts/youtube-rss/assets/Validation.png">
</p>
<p>如果不想看到這個畫面，在 OAuth 同意畫面將應用程式發佈。</p>
<p>驗證成功後會存到 <code>token.json</code> 裡面，這是放驗證的 Token，以後執行程式就不用再重複驗證。</p>
<p>最後所有訂閱頻道的 RSS 網址就會儲存同目錄的 <code>RSS.txt</code> 中，我們可以用 RSS 軟體來批次訂閱。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://console.cloud.google.com/?hl=zh-tw">Google Cloud Console</a></p>
<p><a href="https://developers.google.com/youtube/v3/docs?hl=zh-tw">YouTube Data API Reference</a></p>
<p><a href="https://www.python.org/downloads/">Python 下載</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>使用 AdGuard 移除 LINE 廣告</title>
      <link>https://mrpeelblog.com/posts/line-adblock/</link>
      <pubDate>Mon, 09 Sep 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/line-adblock/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>上一篇文章教大家「<a href="https://mrpeelblog.com/posts/wireguard-with-adguard/">如何架設 WireGuard VPN 和 AdGuard 擋廣告</a>」，我們已經學會架設 VPN 和擋廣告。</p>
<p>今天和大家分享擋 LINE 廣告的過濾規則，當你連上 VPN 的時候，LINE App 不會出現煩人的廣告占版面。</p>
<p><strong>主頁</strong></p>
<p><img alt="LINE_Adblock_0" src="/posts/line-adblock/assets/LINE_Adblock_0.png">
</p>
<p><strong>聊天阻擋上方廣告</strong></p>
<p><img alt="LINE_Adblock_1" src="/posts/line-adblock/assets/LINE_Adblock_1.png">
</p>
<h2 id="設定">設定</h2>
<p>DNS 過濾規則參考 <a href="https://github.com/FutaGuard/LowTechFilter/blob/master/filter.txt">LowTechFilter</a> ，另外我自己有再加上其他規則。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="o">!</span> <span class="nt">Line</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="nt">白名單</span>
</span></span><span class="line"><span class="cl"><span class="o">@@||</span><span class="nt">obs</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">/</span><span class="nt">r</span>
</span></span><span class="line"><span class="cl"><span class="o">@@||</span><span class="nt">obs-tw</span><span class="p">.</span><span class="nc">line-apps</span><span class="p">.</span><span class="nc">com</span><span class="o">/</span><span class="nt">talk</span>
</span></span><span class="line"><span class="cl"><span class="o">@@||</span><span class="nt">stickershop</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="nt">UA</span> <span class="nt">偽裝</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">a</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">/</span><span class="nt">er</span><span class="o">/</span><span class="nt">la</span><span class="o">*/</span><span class="nt">v1</span><span class="o">/$</span><span class="nt">image</span><span class="o">,</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">w</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">/</span><span class="nt">la</span><span class="o">*/</span><span class="nt">api</span><span class="o">/</span><span class="nt">v</span><span class="o">*/</span><span class="nt">ads</span><span class="o">^$</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="nt">廣告媒體</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">d</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">/</span><span class="nt">lcp-prod-photo</span><span class="o">/$</span><span class="nt">image</span><span class="o">,</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">obs</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">/</span><span class="nt">0h</span><span class="o">*/</span><span class="nt">w750</span><span class="o">$</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">obs</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">/</span><span class="nt">0h</span><span class="o">*/</span><span class="nt">o1280x720</span><span class="o">$</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="nt">廣告資料</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">sch</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">/</span><span class="nt">api</span><span class="o">/</span><span class="nt">v</span><span class="o">*/</span><span class="nt">ads</span><span class="o">^$</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">buy</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">/</span><span class="nt">api</span><span class="o">/</span><span class="nt">graphql</span><span class="o">?</span><span class="nt">variables</span><span class="o">$</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="nt">Line</span> <span class="nt">新聞</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">crs-event</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">^$</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">api</span><span class="p">.</span><span class="nc">today</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">/</span><span class="nt">webapi</span><span class="o">/</span><span class="nt">TW</span><span class="o">/</span><span class="nt">recommendation</span><span class="o">/</span><span class="nt">articles</span><span class="o">/</span><span class="nt">articleend</span><span class="o">^$</span><span class="nt">empty</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="nt">額外</span> <span class="nt">LINE</span> <span class="nt">規則</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">linecrs</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">voom-assets</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">today-obs</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">voom-obs</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">today</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">jp-col-tcp</span><span class="p">.</span><span class="nc">nelo</span><span class="p">.</span><span class="nc">linecorp</span><span class="p">.</span><span class="nc">com</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">uts-front</span><span class="p">.</span><span class="nc">line-apps</span><span class="p">.</span><span class="nc">com</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">ad</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">||</span><span class="nt">a</span><span class="p">.</span><span class="nc">line</span><span class="p">.</span><span class="nc">me</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="o">(</span><span class="nt">option</span><span class="o">)</span> <span class="o">||</span><span class="nt">legy</span><span class="p">.</span><span class="nc">line-apps</span><span class="p">.</span><span class="nc">com</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="o">(</span><span class="nt">option</span><span class="o">)</span> <span class="o">||</span><span class="nt">legy</span><span class="p">.</span><span class="nc">line-apps</span><span class="p">.</span><span class="nc">com</span><span class="p">.</span><span class="nc">akadns</span><span class="p">.</span><span class="nc">net</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="o">(</span><span class="nt">option</span><span class="o">)</span> <span class="o">||</span><span class="nt">legy-jp-addr-ds</span><span class="p">.</span><span class="nc">line-apps</span><span class="p">.</span><span class="nc">com</span><span class="o">^$</span><span class="nt">important</span>
</span></span><span class="line"><span class="cl"><span class="o">!</span> <span class="o">(</span><span class="nt">option</span><span class="o">)</span> <span class="o">||</span><span class="nt">obs</span><span class="p">.</span><span class="nc">line-scdn</span><span class="p">.</span><span class="nc">net</span><span class="o">^$</span><span class="nt">important</span>
</span></span></code></pre></div><p>感謝網友回報：<code>||legy.line-apps.com^$important</code> 套用後從其他 App 分享至 LINE 會失敗（iOS）</p>
<p>將規則複製到 AdGuard 過濾器中，套用之後 10 分鐘內就可以看到效果。</p>
<p><img alt="AdGuard_Custom" src="/posts/line-adblock/assets/AdGuard_Custom.png">
</p>
<p>透過自訂過濾規則，可以將 LINE 廣告關閉、聊天頁面最上層廣告移除掉，而且不會影響貼圖商店使用，是個節省流量的好規則。如果是和 WireGuard VPN 一起架設的話需要連線 VPN 才有擋廣告的效果。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://github.com/FutaGuard/LowTechFilter">LowTechFilter Github</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>如何架設 WireGuard VPN 和 AdGuard 擋廣告</title>
      <link>https://mrpeelblog.com/posts/wireguard-with-adguard/</link>
      <pubDate>Thu, 29 Aug 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/wireguard-with-adguard/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>本篇教大家如何架設 VPN 連回內網並阻檔廣告。使用 Docker WG-easy 更容易建立 WireGuard VPN，並只要設定相同網段，就可以連上 VPN 擋廣告。</p>
<p>安裝 Docker 和 Docker Compose 請參考文章「<a href="https://mrpeelblog.com/posts/docker/">快速上手 Docker 和 Docker Compose</a>」。</p>
<h2 id="1-設定-docker">1. 設定 Docker</h2>
<p>以下是 WireGuard 和 AdGuard 的 <code>docker-compose.yml</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.8&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wg-easy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/wg-easy/wg-easy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">wg-easy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">LANG=cht</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WG_HOST=公網IP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c">#- PASSWORD=密碼 新版 Wireguard 已停用 PASSWORD，使用哈希值取代。</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PASSWORD_HASH=哈希值密碼，設定參閱下方部落格文章教學</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PORT=51821</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WG_PORT=51820</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WG_DEFAULT_DNS=172.20.0.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./wg-easy:/etc/wireguard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">51820</span><span class="p">:</span><span class="m">51820</span><span class="l">/udp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">51821</span><span class="p">:</span><span class="m">51821</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cap_add</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">NET_ADMIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SYS_MODULE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sysctls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">net.ipv4.conf.all.src_valid_mark=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">net.ipv4.ip_forward=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ipv4_address</span><span class="p">:</span><span class="w"> </span><span class="m">172.20.0.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">adguardhome</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">adguard/adguardhome</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">adguardhome</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./adguardhome/config:/opt/adguardhome/conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./adguardhome/data:/opt/adguardhome/work</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">內網IP:53:53</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">內網IP:53:53/udp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">8050</span><span class="p">:</span><span class="m">80</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">853</span><span class="p">:</span><span class="m">853</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">784</span><span class="p">:</span><span class="m">784</span><span class="l">/udp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5443</span><span class="p">:</span><span class="m">5443</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5443</span><span class="p">:</span><span class="m">5443</span><span class="l">/udp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">3000</span><span class="p">:</span><span class="m">3000</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ipv4_address</span><span class="p">:</span><span class="w"> </span><span class="m">172.20.0.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ipam</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">subnet</span><span class="p">:</span><span class="w"> </span><span class="m">172.20.0.0</span><span class="l">/16</span><span class="w">
</span></span></span></code></pre></div><p>其中 <code>WG_DEFAULT_DNS = 172.20.0.3</code>、<code>wg-easy</code>、<code>adguardhome</code> 要設成同一個網段，可以直接使用上面的值。</p>
<p><strong>注意</strong>：新版 WireGuard 已不使用 <code>PASSWORD</code>，因此如果 Docker 部屬失敗請使用 <code>PASSWORD_HASH</code> 哈希值。</p>
<p>詳細參閱部落格文章：<a href="https://mrpeelblog.com/posts/wireguard-hash/">解決 WireGuard 無法登入、哈希值生成</a>。</p>
<h2 id="2-開啟-port-端口">2. 開啟 Port 端口</h2>
<ul>
<li>
<p>UDP Port <code>51820</code>，這是 WireGuard 連線的 VPN 埠</p>
</li>
<li>
<p>TCP Port <code>51821</code>，這是透過登入 WireGuard 後來的網頁，未來已連上的話可以把它關閉提高安全性</p>
</li>
</ul>
<p><img alt="Forward_Rules" src="/posts/wireguard-with-adguard/assets/Forward_Rules.png">
</p>
<h2 id="3-設定-wireguard">3. 設定 WireGuard</h2>
<p>一切就緒之後，連線 <code>伺服器IP:51281</code>，登入設定的密碼，新建 VPN 通道。</p>
<p><img alt="WireGuard_WebUI" src="/posts/wireguard-with-adguard/assets/WireGuard_WebUI.png">
</p>
<p>若是手機的話可以下載 WireGuard APP，然後掃描二維碼，可以建立 VPN 連線。</p>
<p>而 PC 的話可以去 <a href="https://www.wireguard.com/install/">WireGuard 官網</a> 下載電腦版軟體。</p>
<p>下載設定檔就可以匯入 WireGuard 進行 VPN 連線。</p>
<h2 id="4-設定-adguard-home">4. 設定 AdGuard Home</h2>
<p>網址輸入 <code>伺服器IP:3000</code>，進行 AdGuard 初始設定，基本上沒特別設定下一步就可以。</p>
<p>設定完成後就可以進入 <code>伺服器IP:8050</code>，進入 AdGuard 管理介面後台。</p>
<p><img alt="AdGuard_Home_WebUI" src="/posts/wireguard-with-adguard/assets/AdGuard_Home_WebUI.png">
</p>
<p>此時當你連上 WireGuard VPN 時，擋廣告的功能也會啟用。</p>
<p>可以新增黑名單來阻擋廣告，以下是我正在用的 DNS 封鎖名單：</p>
<p><img alt="AdGuard_DNS" src="/posts/wireguard-with-adguard/assets/AdGuard_DNS.png">
</p>
<h2 id="結語">結語</h2>
<p>我的手機使用這個方式已有一段時間，最有感的部分是它可以減少載入網頁時間。並且我可以透過 VPN 直接使用內網的服務，即使出門在外也可以安全連回來使用。強烈推薦自架伺服器使用 VPN 來保護伺服器安全。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://mrpeelblog.com/posts/wireguard-hash/">解決 WireGuard 無法登入、哈希值生成</a></p>
<p><a href="https://mrpeelblog.com/posts/docker/">快速上手 Docker 和 Docker Compose</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>關閉 Cloudflare-static-email-decode.min.js</title>
      <link>https://mrpeelblog.com/posts/disable-cloudflare-static-email-decode/</link>
      <pubDate>Tue, 27 Aug 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/disable-cloudflare-static-email-decode/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>最近網站剛建立好，來跑一下 Google 自家的 PageSpeed Insights <a href="https://pagespeed.web.dev/">網頁效能測試</a>。</p>
<p>跑出來的結果很滿意，但是看到效能分數 99 還是很好奇是什麼東西影響的，畢竟網站沒放什麼東西。</p>
<p><img alt="PageSpeed" src="/posts/disable-cloudflare-static-email-decode/assets/PageSpeed.png">
</p>
<p>診斷結果是 <code>cloudflare-static/email-decode.min.js</code> 影響。</p>
<p><img alt="Result" src="/posts/disable-cloudflare-static-email-decode/assets/Result.png">
</p>
<p>這個東西是 Cloudflare Scrape Shield 的 <code>電子郵件地址掩蔽</code>，本身網站沒什麼流量，我就把它關閉吧。</p>
<p><img alt="Scrape_Shield" src="/posts/disable-cloudflare-static-email-decode/assets/Scrape_Shield.png">
</p>
<p>改完之後全部項目都滿分了，看了很開心。</p>
<p><img alt="Result_2nd" src="/posts/disable-cloudflare-static-email-decode/assets/Result_2nd.png">
</p>
<h2 id="結語">結語</h2>
<p>本站使用 Cloudflare Page 部屬，體感速度比 Netlify 快很多而且也是免費。</p>
<p>未來會再使用 Google Analytics 來分析流量，但對前期架設的人來說完全沒必要，現在要做的是努力寫文章，提升 SEO。</p>
]]></content:encoded>
    </item>
    <item>
      <title>解決 VMware NAT Service CPU 使用率高</title>
      <link>https://mrpeelblog.com/posts/fix-vmware-nat-high-cpu/</link>
      <pubDate>Mon, 19 Aug 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/fix-vmware-nat-high-cpu/</guid>
      <description></description>
      <content:encoded><![CDATA[<p>前陣子發現 VMware NAT Service 的 CPU 使用率居然使用 20% 左右，即使不開 VMware Player 也是這樣。</p>
<p><img alt="VMware_NAT_Service_CPU_High" src="/posts/fix-vmware-nat-high-cpu/assets/VMware_NAT_Service_CPU_High.png">
</p>
<p>後來官方在 17.5.2 修復這個問題，真是可喜可賀，各位趕快去更新吧～</p>
<p><a href="https://docs.vmware.com/en/VMware-Workstation-Pro/17.5.2/rn/vmware-workstation-1752-pro-release-notes/index.html">https://docs.vmware.com/en/VMware-Workstation-Pro/17.5.2/rn/vmware-workstation-1752-pro-release-notes/index.html</a></p>
<p><img alt="1752_Fix_Bug" src="/posts/fix-vmware-nat-high-cpu/assets/1752_Fix_Bug.png">
</p>
]]></content:encoded>
    </item>
    <item>
      <title>如何在內網服務中使用 SSL 憑證</title>
      <link>https://mrpeelblog.com/posts/ssl-with-intranet/</link>
      <pubDate>Sun, 18 Aug 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/ssl-with-intranet/</guid>
      <description></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>在設定 Docker 服務時，可能需要為網站配置 SSL 憑證，才能正常使用。例如 Vaultwarden（開源的密碼管理），它會要求只能用 Https 才能登入。</p>
<p>即使服務沒有公開到網路上，在某些情況下也需要使用 SSL 憑證，所以才有這一篇教學，而且可以使用 <code>XXX.mrpeelblog.com</code> 來存取內部服務，就算其他人知道這個網址也沒辦法連線，因為他的 DNS 是指向內部 IP。</p>
<p>以下是設定的步驟，讓你在伺服器上啟用內網 SSL 憑證，同時不影響外網 SSL 憑證。</p>
<p>事前配置：</p>
<ul>
<li><a href="https://mrpeelblog.com/posts/docker/">安裝 Docker Compose</a></li>
<li>安裝 Nginx Proxy Manager</li>
<li>個人網域</li>
</ul>
<h2 id="1-開啟-80443-端口">1. 開啟 80、443 端口</h2>
<p>前往路由器設定，確保 <code>80</code> 和 <code>443</code> 端口已經開啟，以便讓 SSL 請求能夠通過。</p>
<p><img alt="Port_Forwarding" src="/posts/ssl-with-intranet/assets/Port_Forwarding.png">
</p>
<h2 id="2-設定-dns">2. 設定 DNS</h2>
<p>進入 Cloudflare，新增 A 記錄，並將 SSL 網址與公網 IP 設定連結起來。記得將 Proxy 功能關閉。如有多個域名，請重複此步驟。</p>
<p><img alt="Cloudflare_DNS" src="/posts/ssl-with-intranet/assets/Cloudflare_DNS.png">
</p>
<p><strong>注意</strong>：公網 IP 設定是暫時的，是為了取得憑證用的。</p>
<h2 id="3-設定-nginx-proxy-manager">3. 設定 Nginx Proxy Manager</h2>
<p>打開 Nginx Proxy Manager，進入 SSL Certificates，並新增 Let’s Encrypt Certificate。在 <code>Domain Names</code> 欄位填寫您想要設定的 SSL 網址。如果有多個域名，請重複此步驟。</p>
<p>點選 Test Server Reachabilty 確保連線沒問題後，勾選同意條款新增憑證。</p>
<p><img alt="NPM_SSL" src="/posts/ssl-with-intranet/assets/NPM_SSL.png">
</p>
<p>回到 Dash Board，新增 Proxy Host，<code>Domain Names</code> 填要設定 SSL 的網址；<code>Forward Hostname / IP</code> 填內網 IP；<code>Forward Port</code> 填服務的 Port。</p>
<p><img alt="NPM_Host" src="/posts/ssl-with-intranet/assets/NPM_Host.png">
</p>
<p>SSL 部分就選擇剛剛新增的 SSL 網址，打勾 Force SSL、HTTP/2 Support 新增憑證網址。</p>
<p><img alt="NPM_Host_SSL" src="/posts/ssl-with-intranet/assets/NPM_Host_SSL.png">
</p>
<h2 id="4-再次修改-dns-設定">4. 再次修改 DNS 設定</h2>
<p>回到 Cloudflare，將 SSL 網址的公網 IP 改回內網 IP，以及關閉路由器的 <code>80</code>、<code>443</code> Port，即完成內網服務使用 SSL 憑證、不暴露公網且不影響現有公網 SSL 憑證。</p>
<p><img alt="Cloudflare_DNS_Intranet" src="/posts/ssl-with-intranet/assets/Cloudflare_DNS_Intranet.png">
</p>
<h2 id="5-證書過期重新延長">5. 證書過期重新延長</h2>
<p>SSL 證書啟用時間 90 天，因為我們外網連不上所以無法自動延長，快要到期會寄信到 NPM 註冊的信箱，此時我們要手動延長時間。</p>
<p><img alt="Encrypt" src="/posts/ssl-with-intranet/assets/Encrypt.png">
</p>
<p>Port Forwarding 打開 <code>80</code> 和 <code>443</code>，Cloudflare DNS IP 改成公網 IP。前往 NPM SSL 設定，對要延長的網域先 <code>Test Server Reachability</code>。</p>
<p><img alt="Test" src="/posts/ssl-with-intranet/assets/Test.png">
</p>
<p>測試成功，再點 <code>Renew Now</code>。</p>
<p><img alt="Success" src="/posts/ssl-with-intranet/assets/Success.png">
</p>
<h2 id="結語">結語</h2>
<p>透過以上設定，可以讓一些需要 SSL 憑證服務可以正常使用。以及如果想要使用 PWA APP 的話，也要開啟 SSL 才能安裝。</p>
<h2 id="參考資料">參考資料</h2>
<p><a href="https://mrpeelblog.com/posts/docker/">快速上手 Docker 和 Docker Compose</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>解決 Hugo 無法安裝其他主題</title>
      <link>https://mrpeelblog.com/posts/fix-hugo-install-theme/</link>
      <pubDate>Sat, 17 Aug 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/fix-hugo-install-theme/</guid>
      <description></description>
      <content:encoded><![CDATA[<p>安裝主題後預覽網站出現類似錯誤訊息：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ hugo server
</span></span><span class="line"><span class="cl">Start building sites …
</span></span><span class="line"><span class="cl">hugo v0.92.2+extended linux/amd64 <span class="nv">BuildDate</span><span class="o">=</span>2023-01-31T11:11:57Z <span class="nv">VendorInfo</span><span class="o">=</span>ubuntu:0.92.2-1ubuntu0.1
</span></span><span class="line"><span class="cl">ERROR 2024/08/17 18:34:40 render of <span class="s2">&#34;page&#34;</span> failed: <span class="s2">&#34;/home/peel/files/blog/themes/typo/layouts/_default/baseof.html:2:18&#34;</span>: execute of template failed: template: _default/single.html:2:18: executing <span class="s2">&#34;_default/single.html&#34;</span> at &lt;site&gt;: can<span class="s1">&#39;t evaluate field LanguageCode in type *langs.Language
</span></span></span><span class="line"><span class="cl"><span class="s1">ERROR 2024/08/17 18:34:40 render of &#34;taxonomy&#34; failed: &#34;/home/peel/files/blog/themes/typo/layouts/_default/baseof.html:2:18&#34;: execute of template failed: template: _default/list.html:2:18: executing &#34;_default/list.html&#34; at &lt;site&gt;: can&#39;</span>t evaluate field LanguageCode in <span class="nb">type</span> *langs.Language
</span></span><span class="line"><span class="cl">ERROR 2024/08/17 18:34:40 render of <span class="s2">&#34;page&#34;</span> failed: <span class="s2">&#34;/home/peel/files/blog/themes/typo/layouts/_default/baseof.html:2:18&#34;</span>: execute of template failed: template: _default/single.html:2:18: executing <span class="s2">&#34;_default/single.html&#34;</span> at &lt;site&gt;: can<span class="s1">&#39;t evaluate field LanguageCode in type *langs.Language
</span></span></span><span class="line"><span class="cl"><span class="s1">ERROR 2024/08/17 18:34:40 render of &#34;section&#34; failed: &#34;/home/peel/files/blog/themes/typo/layouts/_default/baseof.html:2:18&#34;: execute of template failed: template: _default/list.html:2:18: executing &#34;_default/list.html&#34; at &lt;site&gt;: can&#39;</span>t evaluate field LanguageCode in <span class="nb">type</span> *langs.Language
</span></span><span class="line"><span class="cl">Error: Error building site: failed to render pages: render of <span class="s2">&#34;home&#34;</span> failed: <span class="s2">&#34;/home/peel/files/blog/themes/typo/layouts/_default/baseof.html:2:18&#34;</span>: execute of template failed: template: _default/home.html:2:18: executing <span class="s2">&#34;_default/home.html&#34;</span> at &lt;site&gt;: can<span class="err">&#39;</span>t evaluate field LanguageCode in <span class="nb">type</span> *langs.Language
</span></span><span class="line"><span class="cl">Built in <span class="m">62</span> ms
</span></span></code></pre></div><p>通常是 Hugo 版本太舊，如果透過 <code>apt</code> 更新，它的版本可能不是最新的。</p>
<p><code>hugo is already the newest version (0.92.2-1ubuntu0.1).</code> 最新版本 <code>0.92.2</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo apt update
</span></span><span class="line"><span class="cl">$ sudo apt install hugo
</span></span><span class="line"><span class="cl">Reading package lists... Done
</span></span><span class="line"><span class="cl">Building dependency tree... Done
</span></span><span class="line"><span class="cl">Reading state information... Done
</span></span><span class="line"><span class="cl">hugo is already the newest version <span class="o">(</span>0.92.2-1ubuntu0.1<span class="o">)</span>.
</span></span></code></pre></div><p>前往 <a href="https://github.com/gohugoio/hugo">Hugo GitHub</a> 確認版本，此時 Hugo 版本已經到 <code>0.132.2</code>。</p>
<p><img alt="Hugo_Github_Release" src="/posts/fix-hugo-install-theme/assets/Hugo_Github_Release.png">
</p>
<p>從 <a href="https://github.com/gohugoio/hugo/releases">GitHub Releases</a> 下載最新的版本，如果是 Ubuntu 系統的話，請下載類似檔案名：<code>hugo_0.132.2_Linux-64bit.tar.gz</code>。</p>
<p>解壓縮並輸入指令替換原本的舊版本：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tar -zxvf hugo_extended_0.xx.x_Linux-64bit.tar.gz
</span></span><span class="line"><span class="cl">sudo mv hugo /usr/local/bin/
</span></span></code></pre></div><p>輸入 <code>hugo version</code> 檢查版本，確認更新成功。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ hugo version
</span></span><span class="line"><span class="cl">hugo v0.132.2-3fd26c70dff5934ec1802b9563530130ed1bca75 linux/amd64 <span class="nv">BuildDate</span><span class="o">=</span>2024-08-14T16:17:21Z <span class="nv">VendorInfo</span><span class="o">=</span>gohugoio
</span></span></code></pre></div><p>最後輸入 <code>hugo server</code> 就不會報錯了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ hugo server
</span></span><span class="line"><span class="cl">Watching <span class="k">for</span> changes in /home/peel/files/blog/<span class="o">{</span>archetypes,content,data,layouts,static,themes<span class="o">}</span>
</span></span><span class="line"><span class="cl">Watching <span class="k">for</span> config changes in /home/peel/files/blog/config.toml, /home/peel/files/blog/themes/typo/hugo.toml
</span></span><span class="line"><span class="cl">Start building sites …
</span></span><span class="line"><span class="cl">hugo v0.132.2-3fd26c70dff5934ec1802b9563530130ed1bca75 linux/amd64 <span class="nv">BuildDate</span><span class="o">=</span>2024-08-14T16:17:21Z <span class="nv">VendorInfo</span><span class="o">=</span>gohugoio
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                   <span class="p">|</span> EN
</span></span><span class="line"><span class="cl">-------------------+-----
</span></span><span class="line"><span class="cl">  Pages            <span class="p">|</span> <span class="m">12</span>
</span></span><span class="line"><span class="cl">  Paginator pages  <span class="p">|</span>  <span class="m">0</span>
</span></span><span class="line"><span class="cl">  Non-page files   <span class="p">|</span>  <span class="m">0</span>
</span></span><span class="line"><span class="cl">  Static files     <span class="p">|</span> <span class="m">12</span>
</span></span><span class="line"><span class="cl">  Processed images <span class="p">|</span>  <span class="m">0</span>
</span></span><span class="line"><span class="cl">  Aliases          <span class="p">|</span>  <span class="m">4</span>
</span></span><span class="line"><span class="cl">  Cleaned          <span class="p">|</span>  <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Built in <span class="m">88</span> ms
</span></span><span class="line"><span class="cl">Environment: <span class="s2">&#34;development&#34;</span>
</span></span><span class="line"><span class="cl">Serving pages from disk
</span></span><span class="line"><span class="cl">Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
</span></span><span class="line"><span class="cl">Web Server is available at http://localhost:1313/ <span class="o">(</span><span class="nb">bind</span> address 127.0.0.1<span class="o">)</span>
</span></span><span class="line"><span class="cl">Press Ctrl+C to stop
</span></span></code></pre></div><h2 id="參考資料">參考資料</h2>
<p><a href="https://gohugo.io/">Hugo 官網</a></p>
<p><a href="https://github.com/gohugoio/hugo/releases">Github Hugo Releases</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>分析 X6S 和 X5R 電容對 Nvidia GPU 影響</title>
      <link>https://mrpeelblog.com/posts/nvidia-gpu-capacitor/</link>
      <pubDate>Sun, 11 Aug 2024 00:00:00 +0800</pubDate>
      <guid>https://mrpeelblog.com/posts/nvidia-gpu-capacitor/</guid>
      <description></description>
      <content:encoded><![CDATA[<p>開發 GPU 小板或使用到 GPU 對於電容選擇必須要多注意。在 PCI-E 的 AC Capacitor 建議直接使用 X6S 的電容，雖然 NV Spec 說 X5R 可以，但當 GPU 在高負載下運作，它的溫度會很高，若使用 X5R 的容值多少會偏，造成 MODS 測試可能 fail 或是其他問題，因此建議直接使用 X6S 最好。</p>
<p>以下列出 X6S 與 X5R 電容差異：</p>
<ul>
<li>X5R：溫度範圍為 -55°C 到 +85°C。在這個範圍內，電容值的變化範圍不會超過 ±15%</li>
<li>X6S：溫度範圍為 -55°C 到 +105°C。同樣在這個範圍內，電容值的變化範圍不會超過 ±22%，且價格比 X5R 貴</li>
</ul>
<p>那 Decoupling 電容就更不用說了，NV Spec 要求就是要使用 X6S 的電容，甚至容值與數量要和公版一致，位置依照公版擺放，因為那些電容是 NV 算好通過 Power Integrity (PI) Simulation 條件，除非有高度限制或其他要求才需要微調位置。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
