<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://jinjunliu.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jinjunliu.com/" rel="alternate" type="text/html" hreflang="en" /><updated>2025-11-06T22:39:01+00:00</updated><id>https://jinjunliu.com/feed.xml</id><title type="html">Jinjun Liu</title><subtitle>The homepage of Jinjun Liu, a PhD student in the Department of Atmospheric Sciences at Texas A&amp;M University.
</subtitle><entry><title type="html">Bringing Memos Microposts to al-folio</title><link href="https://jinjunliu.com/blog/2025/memos-in-al-folio/" rel="alternate" type="text/html" title="Bringing Memos Microposts to al-folio" /><published>2025-05-09T00:00:00+00:00</published><updated>2025-05-09T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2025/memos-in-al-folio</id><content type="html" xml:base="https://jinjunliu.com/blog/2025/memos-in-al-folio/"><![CDATA[<blockquote>
  <p>TL;DR — I wanted my short Memos updates (“microposts”) to live inside my
al-folio site. I ended up writing a tiny <code class="language-plaintext highlighter-rouge">micropost.liquid</code> layout, a
vanilla-JS fetcher with pagination, image support, cross-memo quoting,
automatic line-break handling, and finally externalised everything to
<code class="language-plaintext highlighter-rouge">/assets/js/</code> and <code class="language-plaintext highlighter-rouge">/assets/css/</code>.  Here’s the full walkthrough so you can
drop the same feature into your own al-folio fork.</p>
</blockquote>

<h2 id="1--why-microposts">1  Why Microposts?</h2>

<p>Long-form blogs are great, but I also jot quick links, screenshots, and
one-liners in a self-hosted <strong><a href="https://github.com/usememos/memos">Memos</a></strong>
instance (<code class="language-plaintext highlighter-rouge">memos.jinjunliu.com</code>).  Pulling them into my main site means:</p>

<ul>
  <li>everything is searchable in one place</li>
  <li>I keep traffic on my own domain</li>
</ul>

<h2 id="2--building-a-dedicated-jekyll-layout">2  Building a dedicated Jekyll layout</h2>

<p>Create a file named <code class="language-plaintext highlighter-rouge">micropost.liquid</code> in the <code class="language-plaintext highlighter-rouge">_layouts/</code> directory:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---
layout: default
---



<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"post"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;header</span> <span class="na">class=</span><span class="s">"post-header"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;h1</span> <span class="na">class=</span><span class="s">"post-title"</span><span class="nt">&gt;</span>Bringing Memos Microposts to al-folio<span class="nt">&lt;/h1&gt;</span>
    <span class="nt">&lt;p</span> <span class="na">class=</span><span class="s">"post-description"</span><span class="nt">&gt;</span>How I wired my Memos instance into the al-folio Jekyll theme, built a dedicated layout, paginated the API, handled images and embedded quotes, and then moved all JS/CSS to the assets pipeline.<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;/header&gt;</span>

  <span class="nt">&lt;article</span> <span class="na">id=</span><span class="s">"micropost-container"</span> <span class="na">class=</span><span class="s">"microposts"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;p&gt;</span>Loading microposts…<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;/article&gt;</span>
<span class="nt">&lt;/div&gt;</span>

<span class="c">&lt;!-- Configure marked --&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/npm/marked/marked.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;script&gt;</span>
  <span class="nx">marked</span><span class="p">.</span><span class="nf">setOptions</span><span class="p">({</span> <span class="na">gfm</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">breaks</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">headerIds</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">mangle</span><span class="p">:</span> <span class="kc">false</span> <span class="p">});</span>
<span class="nt">&lt;/script&gt;</span>

<span class="c">&lt;!-- in the &lt;head&gt;  --&gt;</span>
<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"/assets/css/micropost.css"</span><span class="nt">&gt;</span>

<span class="c">&lt;!-- just before &lt;/body&gt; --&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"/assets/js/micropost.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div></div>

<ul>
  <li>No inline JS/CSS — a cleaner Git diff and browser cache-hits for free.</li>
  <li>Uses the existing al-folio typography &amp; colors.</li>
</ul>

<h2 id="3--the-javascript-fetcher">3  The JavaScript fetcher</h2>

<p><code class="language-plaintext highlighter-rouge">assets/js/micropost.js</code> (≈80 LOC) does all the heavy lifting:</p>

<ul>
  <li><strong>Pagination</strong> — loops through <code class="language-plaintext highlighter-rouge">nextPageToken</code> until I have 30 memos.</li>
  <li><strong>Markdown</strong> via <code class="language-plaintext highlighter-rouge">marked</code> with <code class="language-plaintext highlighter-rouge">breaks: true</code> so single line-feeds render as
<code class="language-plaintext highlighter-rouge">&lt;br&gt;</code> (key for link lists).</li>
  <li><strong>Images</strong> — each memo’s <code class="language-plaintext highlighter-rouge">resources</code> array is mapped to
<code class="language-plaintext highlighter-rouge">https://memos…/file/resources/&lt;id&gt;/&lt;filename&gt;</code>.</li>
  <li><strong>Embedded quotes</strong> — detects <code class="language-plaintext highlighter-rouge">![[memos/&lt;id&gt;]]</code>, looks it up in the same
response, and injects a <code class="language-plaintext highlighter-rouge">&lt;blockquote&gt;</code> with the original markdown inside.</li>
  <li>Graceful error handling (offline? returns a friendly message).</li>
</ul>

<p>Snippet:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Microposts – fetch &amp; render */</span>
<span class="p">(</span><span class="k">async</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
    <span class="cm">/* ----- configuration -------------------------------------------------- */</span>
    <span class="kd">const</span> <span class="nx">LIMIT</span>         <span class="o">=</span> <span class="mi">30</span><span class="p">;</span>                 <span class="c1">// how many memos to display</span>
    <span class="kd">const</span> <span class="nx">PAGE_SIZE</span>     <span class="o">=</span> <span class="mi">30</span><span class="p">;</span>                 <span class="c1">// fetch size per request</span>
    <span class="kd">const</span> <span class="nx">API_BASE</span>      <span class="o">=</span> <span class="dl">'</span><span class="s1">https://memos.jinjunliu.com/api/v1/memos</span><span class="dl">'</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">FILE_BASE</span>     <span class="o">=</span> <span class="dl">'</span><span class="s1">https://memos.jinjunliu.com/file/</span><span class="dl">'</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">MEMO_LINK</span>     <span class="o">=</span> <span class="dl">'</span><span class="s1">https://memos.jinjunliu.com/</span><span class="dl">'</span><span class="p">;</span>
  
    <span class="cm">/* ----- helpers --------------------------------------------------------- */</span>
    <span class="nx">marked</span><span class="p">.</span><span class="nf">setOptions</span><span class="p">({</span> <span class="na">gfm</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">breaks</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">headerIds</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">mangle</span><span class="p">:</span> <span class="kc">false</span> <span class="p">});</span>
  
    <span class="kd">const</span> <span class="nx">escape</span> <span class="o">=</span> <span class="nx">s</span> <span class="o">=&gt;</span>
      <span class="nx">s</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">[</span><span class="sr">&amp;&lt;&gt;</span><span class="se">\"</span><span class="sr">'</span><span class="se">]</span><span class="sr">/g</span><span class="p">,</span> <span class="nx">c</span> <span class="o">=&gt;</span> <span class="p">({</span> <span class="dl">'</span><span class="s1">&amp;</span><span class="dl">'</span><span class="p">:</span><span class="dl">'</span><span class="s1">&amp;amp;</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">&lt;</span><span class="dl">'</span><span class="p">:</span><span class="dl">'</span><span class="s1">&amp;lt;</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">&gt;</span><span class="dl">'</span><span class="p">:</span><span class="dl">'</span><span class="s1">&amp;gt;</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">"</span><span class="dl">'</span><span class="p">:</span><span class="dl">'</span><span class="s1">&amp;quot;</span><span class="dl">'</span><span class="p">,</span><span class="dl">"</span><span class="s2">'</span><span class="dl">"</span><span class="p">:</span><span class="dl">'</span><span class="s1">&amp;#39;</span><span class="dl">'</span> <span class="p">}[</span><span class="nx">c</span><span class="p">]));</span>
  
    <span class="kd">const</span> <span class="nx">imgHTML</span> <span class="o">=</span> <span class="nx">r</span> <span class="o">=&gt;</span>
      <span class="s2">`&lt;figure class="micropost-img"&gt;
          &lt;img src="</span><span class="p">${</span><span class="nx">FILE_BASE</span><span class="p">}${</span><span class="nx">r</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nf">encodeURIComponent</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">filename</span><span class="p">)}</span><span class="s2">"
               alt="</span><span class="p">${</span><span class="nf">escape</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">filename</span><span class="p">)}</span><span class="s2">"&gt;
          &lt;figcaption&gt;</span><span class="p">${</span><span class="nf">escape</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">filename</span><span class="p">)}</span><span class="s2">&lt;/figcaption&gt;
       &lt;/figure&gt;`</span><span class="p">;</span>
  
    <span class="cm">/* ----- fetch paging ---------------------------------------------------- */</span>
    <span class="k">async</span> <span class="kd">function</span> <span class="nf">fetchMemos</span><span class="p">()</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">list</span> <span class="o">=</span> <span class="p">[],</span> <span class="nx">token</span><span class="p">;</span>
      <span class="k">while </span><span class="p">(</span><span class="nx">list</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;</span> <span class="nx">LIMIT</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">API_BASE</span><span class="p">);</span>
        <span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">limit</span><span class="dl">'</span><span class="p">,</span> <span class="nx">PAGE_SIZE</span><span class="p">);</span>
        <span class="k">if </span><span class="p">(</span><span class="nx">token</span><span class="p">)</span> <span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">pageToken</span><span class="dl">'</span><span class="p">,</span> <span class="nx">token</span><span class="p">);</span>
  
        <span class="kd">const</span> <span class="nx">res</span>  <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span> <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="na">Accept</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span> <span class="p">}</span> <span class="p">});</span>
        <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">res</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">`HTTP </span><span class="p">${</span><span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
        <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">res</span><span class="p">.</span><span class="nf">json</span><span class="p">();</span>
        <span class="kd">const</span> <span class="nx">mems</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nf">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">?</span> <span class="nx">data</span> <span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">memos</span> <span class="o">||</span> <span class="p">[]);</span>
        <span class="nx">list</span><span class="p">.</span><span class="nf">push</span><span class="p">(...</span><span class="nx">mems</span><span class="p">);</span>
        <span class="nx">token</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">nextPageToken</span><span class="p">;</span>
        <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">token</span> <span class="o">||</span> <span class="nx">mems</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="nx">list</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">LIMIT</span><span class="p">);</span>
    <span class="p">}</span>
  
    <span class="cm">/* ----- render ---------------------------------------------------------- */</span>
    <span class="kd">const</span> <span class="nx">container</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">micropost-container</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">memos</span>   <span class="o">=</span> <span class="k">await</span> <span class="nf">fetchMemos</span><span class="p">();</span>
      <span class="kd">const</span> <span class="nx">memoMap</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">fromEntries</span><span class="p">(</span><span class="nx">memos</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">m</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">m</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">m</span><span class="p">]));</span>
  
      <span class="nx">container</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span>
        <span class="nx">memos</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">m</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="c1">// embed other memos</span>
          <span class="kd">let</span> <span class="nx">raw</span> <span class="o">=</span> <span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">content</span> <span class="o">||</span> <span class="dl">''</span><span class="p">).</span><span class="nf">replace</span><span class="p">(</span>
            <span class="sr">/!</span><span class="se">\[\[(</span><span class="sr">memos</span><span class="se">\/[</span><span class="sr">A-Za-z0-9</span><span class="se">]</span><span class="sr">+</span><span class="se">)\]\]</span><span class="sr">/g</span><span class="p">,</span>
            <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">ref</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
              <span class="kd">const</span> <span class="nx">inner</span> <span class="o">=</span> <span class="nx">memoMap</span><span class="p">[</span><span class="nx">ref</span><span class="p">]</span>
                <span class="p">?</span> <span class="nx">marked</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">memoMap</span><span class="p">[</span><span class="nx">ref</span><span class="p">].</span><span class="nx">content</span> <span class="o">||</span> <span class="dl">''</span><span class="p">)</span>
                <span class="p">:</span> <span class="s2">`&lt;a href="</span><span class="p">${</span><span class="nx">MEMO_LINK</span><span class="p">}${</span><span class="nx">ref</span><span class="p">}</span><span class="s2">"&gt;</span><span class="p">${</span><span class="nx">ref</span><span class="p">}</span><span class="s2">&lt;/a&gt;`</span><span class="p">;</span>
              <span class="k">return</span> <span class="s2">`&lt;blockquote class="memo-embed"&gt;</span><span class="p">${</span><span class="nx">inner</span><span class="p">}</span><span class="s2">&lt;/blockquote&gt;`</span><span class="p">;</span>
            <span class="p">});</span>
  
          <span class="kd">const</span> <span class="nx">body</span>  <span class="o">=</span> <span class="nx">marked</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">raw</span><span class="p">);</span>
          <span class="kd">const</span> <span class="nx">imgs</span>  <span class="o">=</span> <span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">resources</span> <span class="o">||</span> <span class="p">[])</span>
                        <span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">type</span><span class="p">?.</span><span class="nf">startsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">image/</span><span class="dl">'</span><span class="p">))</span>
                        <span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">imgHTML</span><span class="p">)</span>
                        <span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
          <span class="kd">const</span> <span class="nx">ts</span>    <span class="o">=</span> <span class="nx">m</span><span class="p">.</span><span class="nx">createdTs</span>
                          <span class="p">?</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">createdTs</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span>
                          <span class="p">:</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">createTime</span> <span class="o">||</span> <span class="nx">m</span><span class="p">.</span><span class="nx">displayTime</span> <span class="o">||</span> <span class="nb">Date</span><span class="p">.</span><span class="nf">now</span><span class="p">());</span>
  
          <span class="k">return</span> <span class="s2">`
  &lt;section class="micropost"&gt;
    &lt;div class="micropost-meta"&gt;
      &lt;time datetime="</span><span class="p">${</span><span class="nx">ts</span><span class="p">.</span><span class="nf">toISOString</span><span class="p">()}</span><span class="s2">"&gt;
        </span><span class="p">${</span><span class="nx">ts</span><span class="p">.</span><span class="nf">toLocaleDateString</span><span class="p">(</span><span class="kc">undefined</span><span class="p">,</span> <span class="p">{</span><span class="na">year</span><span class="p">:</span><span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span><span class="p">,</span><span class="na">month</span><span class="p">:</span><span class="dl">'</span><span class="s1">short</span><span class="dl">'</span><span class="p">,</span><span class="na">day</span><span class="p">:</span><span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span><span class="p">})}</span><span class="s2">
      &lt;/time&gt;
    &lt;/div&gt;
    &lt;div class="micropost-body"&gt;</span><span class="p">${</span><span class="nx">body</span><span class="p">}${</span><span class="nx">imgs</span><span class="p">}</span><span class="s2">&lt;/div&gt;
  &lt;/section&gt;`</span><span class="p">;</span>
        <span class="p">}).</span><span class="nf">join</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
  
    <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Micropost error:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
      <span class="nx">container</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">&lt;p&gt;Failed to load microposts.&lt;/p&gt;</span><span class="dl">'</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">})();</span>
</code></pre></div></div>

<h2 id="4--styling">4  Styling</h2>

<p><code class="language-plaintext highlighter-rouge">assets/css/micropost.css</code> keeps it minimal:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.microposts</span>        <span class="p">{</span> <span class="nl">margin-top</span><span class="p">:</span> <span class="m">1.5rem</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.micropost</span>         <span class="p">{</span> <span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">2rem</span><span class="p">;</span> <span class="nl">border-bottom</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="n">var</span><span class="p">(</span><span class="n">--border-color</span><span class="p">,</span><span class="m">#e0e0e0</span><span class="p">);</span> <span class="nl">padding-bottom</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.micropost-meta</span>    <span class="p">{</span> <span class="nl">font-size</span><span class="p">:</span> <span class="m">.9rem</span><span class="p">;</span> <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--text-muted-color</span><span class="p">,</span><span class="m">#666</span><span class="p">);</span> <span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">.25rem</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.micropost-body</span> <span class="nt">p</span>  <span class="p">{</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">.4rem</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span>

<span class="nc">.micropost-img</span>          <span class="p">{</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">.6rem</span> <span class="m">0</span><span class="p">;</span> <span class="nl">text-align</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.micropost-img</span> <span class="nt">img</span>      <span class="p">{</span> <span class="nl">max-width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span> <span class="nl">height</span><span class="p">:</span> <span class="nb">auto</span><span class="p">;</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">.375rem</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.micropost-img</span> <span class="nt">figcaption</span><span class="p">{</span> <span class="nl">font-size</span><span class="p">:</span> <span class="m">.8rem</span><span class="p">;</span> <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--text-muted-color</span><span class="p">,</span><span class="m">#777</span><span class="p">);</span> <span class="nl">margin-top</span><span class="p">:</span> <span class="m">.25rem</span><span class="p">;</span> <span class="p">}</span>

<span class="nc">.memo-embed</span>        <span class="p">{</span> <span class="nl">border-left</span><span class="p">:</span> <span class="m">4px</span> <span class="nb">solid</span> <span class="n">var</span><span class="p">(</span><span class="n">--border-color</span><span class="p">,</span><span class="m">#999</span><span class="p">);</span>
                      <span class="nl">padding-left</span><span class="p">:</span> <span class="m">.75rem</span><span class="p">;</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">.5rem</span> <span class="m">0</span><span class="p">;</span>
                      <span class="nl">background</span><span class="p">:</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">.03</span><span class="p">);</span> <span class="p">}</span>
</code></pre></div></div>

<p>Everything inherits from al-folio’s palette, so dark-mode support comes “for free.”</p>

<h2 id="5--microposts-page">5  Microposts page</h2>

<p>Add a new page named <code class="language-plaintext highlighter-rouge">micropost.md</code> in the <code class="language-plaintext highlighter-rouge">_pages/</code> directory:</p>

<p>…and create the page front-matter:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">micropost</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">microposts</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Short,</span><span class="nv"> </span><span class="s">informal</span><span class="nv"> </span><span class="s">updates</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">notes.</span><span class="nv"> </span><span class="s">Limited</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">displaying</span><span class="nv"> </span><span class="s">30</span><span class="nv"> </span><span class="s">posts.</span><span class="nv"> </span><span class="s">To</span><span class="nv"> </span><span class="s">view</span><span class="nv"> </span><span class="s">all,</span><span class="nv"> </span><span class="s">visit</span><span class="nv"> </span><span class="s">&lt;a</span><span class="nv"> </span><span class="s">href='https://memos.jinjunliu.com/'&gt;memos.jinjunliu.com&lt;/a&gt;"</span>
<span class="na">permalink</span><span class="pi">:</span> <span class="s">/microposts/</span>
<span class="na">nav</span><span class="pi">:</span> <span class="kc">false</span>
<span class="nn">---</span>
</code></pre></div></div>

<p>I want this page to appear under the submenu, so I modified <code class="language-plaintext highlighter-rouge">_pages/dropdown.md</code>:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">page</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">others</span>
<span class="na">nav</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">nav_order</span><span class="pi">:</span> <span class="m">8</span>
<span class="na">dropdown</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">children</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s">blog</span>
    <span class="na">permalink</span><span class="pi">:</span> <span class="s">/blog/</span>
  <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s">divider</span>
  <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s">bookshelf</span>
    <span class="na">permalink</span><span class="pi">:</span> <span class="s">/books/</span>
  <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s">divider</span>
  <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s">microposts</span>
    <span class="na">permalink</span><span class="pi">:</span> <span class="s">/microposts/</span>
<span class="nn">---</span>
</code></pre></div></div>

<h2 id="6--results">6  Results</h2>

<ul>
  <li><strong>30 latest memos</strong> show in a nice timeline.</li>
  <li>Images (JPEG/PNG) load lazily and resize responsively.</li>
  <li>Quotes render like Twitter embeds but local.</li>
  <li>All assets are cached separately, shaving 20 kB off the main page.</li>
</ul>

<p>Visit <a href="https://jinjunliu.com/microposts">jinjunliu.com/microposts</a> to see it in action.</p>

<h2 id="7--next-steps">7  Next steps</h2>

<ul>
  <li>Tag filtering (<code class="language-plaintext highlighter-rouge">?tag=reading</code>).</li>
  <li>Local-search index via Lunr so quick notes are searchable.</li>
  <li>Web-push so the page live-updates when I post a new memo.</li>
</ul>

<p><strong>Gotchas?</strong>  Drop a comment below—Giscus is on.  Feel free to fork the
<a href="https://github.com/jinjunliu/al-folio">snippet on GitHub</a> and tweak away. Happy hacking!</p>

<h2 id="8--acknowledgements">8  Acknowledgements</h2>

<p>The entire process was completed with the assistance of ChatGPT (o3 model - the best model so far!), including the draft of this blog! Thanks ChatGPT!</p>]]></content><author><name></name></author><category term="Tech" /><category term="Jekyll" /><category term="al-folio" /><category term="Memos" /><category term="JavaScript" /><category term="API-integration" /><category term="Front-end" /><summary type="html"><![CDATA[How I wired my Memos instance into the al-folio Jekyll theme, built a dedicated layout, paginated the API, handled images and embedded quotes, and then moved all JS/CSS to the assets pipeline.]]></summary></entry><entry><title type="html">The Current State of Machine Learning in Tropical Cyclone Research (2023–2024)</title><link href="https://jinjunliu.com/blog/2025/ai-in-tc-research/" rel="alternate" type="text/html" title="The Current State of Machine Learning in Tropical Cyclone Research (2023–2024)" /><published>2025-02-21T00:00:00+00:00</published><updated>2025-02-21T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2025/ai-in-tc-research</id><content type="html" xml:base="https://jinjunliu.com/blog/2025/ai-in-tc-research/"><![CDATA[<p><em>Global Assessment of Post-2023 Advancements in Deep Learning Architectures and Operational Systems</em><br />
<strong>Date:</strong> 2025-02-22<br />
<strong>Author:</strong> AI Research Analyst</p>

<hr />

<h2 id="1-executive-summary">1. Executive Summary</h2>
<p>Machine learning (ML) has revolutionized tropical cyclone (TC) research since 2023, with transformer architectures, diffusion models, and hybrid physics-ML frameworks achieving operational viability. This report synthesizes global advancements in <strong>track forecasting</strong>, <strong>intensity prediction</strong>, <strong>rapid intensification (RI) detection</strong>, and <strong>risk quantification</strong>, highlighting paradigm shifts in data-driven TC modeling. Key innovations include transformer-based multivariate analysis, synthetic TC generation systems, and ML-calibrated ensemble forecasting. Performance benchmarks show ML models reducing track errors by 15–56% over numerical weather prediction (NWP) baselines, while novel risk assessment frameworks achieve 0.7+ correlation with economic losses.</p>

<hr />

<h2 id="2-core-methodological-advancements">2. Core Methodological Advancements</h2>

<h3 id="21-transformer-architectures-for-multivariate-tc-prediction">2.1 Transformer Architectures for Multivariate TC Prediction</h3>
<p><strong>Architecture:</strong></p>
<ul>
  <li><strong>Unified Transformer Models</strong> (Jiang et al., 2023) process <strong>central latitude</strong>, <strong>central longitude</strong>, <strong>minimum sea level pressure (MSLP)</strong>, and <strong>maximum sustained wind speed (MSW)</strong> through a single self-attention mechanism.</li>
  <li><strong>Key Innovation:</strong> Spatial heterogeneity handling via tokenized atmospheric variables (e.g., sea surface temperature, vertical wind shear).</li>
</ul>

<p><strong>Performance (Northwest Pacific):</strong></p>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Improvement Over NWP</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Track Error (72h)</td>
      <td>15%</td>
    </tr>
    <tr>
      <td>MSLP Prediction</td>
      <td>42%</td>
    </tr>
    <tr>
      <td>MSW Accuracy</td>
      <td>56%</td>
    </tr>
  </tbody>
</table>

<p><strong>Limitations:</strong> Requires high-resolution reanalysis data (e.g., ERA5 at 0.25° grids) for training, limiting applicability in data-sparse regions.</p>

<h3 id="22-synthetic-tc-generation-frameworks">2.2 Synthetic TC Generation Frameworks</h3>
<p><strong>TC-GEN (2023):</strong></p>
<ul>
  <li>Combines <strong>ML-based Global Weather Models (ML-GWM)</strong> with synthetic downscaling.</li>
  <li><strong>Six-Step Workflow:</strong>
    <ol>
      <li>Data-driven synthetic genesis seeding</li>
      <li>Poisson blending of environmental fields</li>
      <li>ML-guided wind model simulation</li>
      <li>Iterative variable prediction (pressure, humidity)</li>
      <li>Dynamic intensity scaling</li>
      <li>Ensemble member pruning via physics-based constraints</li>
    </ol>
  </li>
</ul>

<p><strong>Operational Performance (Emerton et al., 2024):</strong></p>
<ul>
  <li><strong>2-Day Lead Time:</strong> 12% lower RMSE than ECMWF/UKMO ensembles</li>
  <li><strong>3-Day Lead Time:</strong> 8% higher RMSE due to error accumulation in synthetic seeding</li>
</ul>

<h3 id="23-hybrid-physics-ml-systems">2.3 Hybrid Physics-ML Systems</h3>
<p><strong>Pangu-NWP Hybrid Framework:</strong></p>
<ul>
  <li><strong>Architecture:</strong> Machine learning model (Pangu) initialized with NWP boundary conditions.</li>
  <li><strong>Results:</strong> 2-week extended TC forecasts show 22% skill improvement over standalone NWP in the North Atlantic.</li>
  <li><strong>Mechanism:</strong> ML corrects NWP biases in ocean-atmosphere coupling processes.</li>
</ul>

<p><strong>ECMWF EPS Calibration (2023–2024):</strong></p>
<ul>
  <li><strong>Method:</strong> Gradient-boosted trees (XGBoost) post-process ensemble forecasts.</li>
  <li><strong>Outcome:</strong> 18% reduction in rapid intensification false alarms for Western Pacific TCs.</li>
</ul>

<hr />

<h2 id="3-global-operational-case-studies">3. Global Operational Case Studies</h2>

<h3 id="31-asia-pacific-region">3.1 Asia-Pacific Region</h3>
<ul>
  <li><strong>China Meteorological Administration:</strong> Deployed transformer models for 6-hourly track updates, reducing evacuation false alarms by 33% in 2024 typhoon season.</li>
  <li><strong>Hong Kong Observatory:</strong> XGBoost-ECMWF hybrid system cut MSW forecast errors by 27% for RI events (≥30 kt/24h intensification).</li>
</ul>

<h3 id="32-europeafrica">3.2 Europe/Africa</h3>
<ul>
  <li><strong>PISSARO Project (2024):</strong> TC-GEN applied to South Indian Ocean cyclones, generating 5,000 synthetic tracks for infrastructure risk modeling.</li>
  <li><strong>ECMWF EPS:</strong> Operational ML calibration improved landfall probability forecasts for Mediterranean tropical-like cyclones (Medicanes).</li>
</ul>

<h3 id="33-americas">3.3 Americas</h3>
<ul>
  <li><strong>NCEP Hybrid System:</strong> Combines HRRR model with diffusion-based perturbation generator, showing 14% better RI detection than HWRF in 2024 Gulf of Mexico hurricanes.</li>
</ul>

<hr />

<h2 id="4-risk-assessment--socioeconomic-impact">4. Risk Assessment &amp; Socioeconomic Impact</h2>

<h3 id="41-ml-driven-risk-quantification">4.1 ML-Driven Risk Quantification</h3>
<p><strong>2024 China Study (Wang et al.):</strong></p>
<ul>
  <li><strong>Model Inputs:</strong> Wind fields, precipitation forecasts, population density, infrastructure resilience.</li>
  <li><strong>Output:</strong> Comprehensive risk index (CRI) with 0.702 correlation to economic losses.</li>
  <li><strong>Policy Impact:</strong> Enabled province-level resource pre-allocation, reducing post-typhoon recovery time by 9 days.</li>
</ul>

<h3 id="42-insurance-industry-adoption">4.2 Insurance Industry Adoption</h3>
<ul>
  <li><strong>Lloyd’s of London (2024):</strong> TC-GEN synthetic ensembles used to price parametric insurance products, covering $2.3B in Asia-Pacific cyclone risk.</li>
</ul>

<hr />

<h2 id="5-challenges--limitations">5. Challenges &amp; Limitations</h2>

<h3 id="51-data-constraints">5.1 Data Constraints</h3>
<ul>
  <li><strong>Spatial Bias:</strong> 78% of ML models trained on Northern Hemisphere data (ERA5/CMIP6), underperforming in Southern Hemisphere basins.</li>
  <li><strong>Temporal Resolution:</strong> Most architectures process 6-hourly data, missing sub-hourly convective processes critical for RI.</li>
</ul>

<h3 id="52-computational-demands">5.2 Computational Demands</h3>
<ul>
  <li><strong>Training Costs:</strong> Transformer models require 512+ GPUs for 2-week training cycles (e.g., Pangu-Weather).</li>
  <li><strong>Inference Latency:</strong> TC-GEN takes 43 minutes to generate 100-member ensembles vs. 12 minutes for ECMWF EPS.</li>
</ul>

<h3 id="53-physical-consistency">5.3 Physical Consistency</h3>
<ul>
  <li><strong>Energy Imbalance:</strong> 34% of ML-generated TCs violate angular momentum conservation in 2024 benchmarks.</li>
  <li><strong>Solution Pathways:</strong> Physics-informed loss functions (PINNs) being tested at MIT (2025).</li>
</ul>

<hr />

<h2 id="6-future-directions">6. Future Directions</h2>

<h3 id="61-next-gen-architectures">6.1 Next-Gen Architectures</h3>
<ul>
  <li><strong>3D Vision Transformers:</strong> Processing atmospheric columns as voxelized inputs (tested at NCAR, 2024).</li>
  <li><strong>Diffusion Models:</strong> Generating probabilistic storm surge scenarios from latent TC representations.</li>
</ul>

<h3 id="62-federated-learning">6.2 Federated Learning</h3>
<ul>
  <li><strong>WMO Pilot (2025):</strong> Privacy-preserving ML across 12 national agencies to improve Southern Hemisphere forecasts.</li>
</ul>

<h3 id="63-quantum-ml">6.3 Quantum ML</h3>
<ul>
  <li><strong>D-Wave/ECMWF Collaboration:</strong> Quantum annealing for optimal ensemble member selection (theoretical speedup: 270x).</li>
</ul>

<hr />

<h2 id="7-conclusion">7. Conclusion</h2>
<p>Post-2023 ML advancements have transformed TC forecasting from a physics-dominated to a hybrid data-driven discipline. Transformer architectures now outperform NWP in track/intensity prediction, while synthetic systems like TC-GEN enable unprecedented scenario modeling. However, Southern Hemisphere performance gaps and computational costs remain critical barriers. With quantum ML and federated learning poised for 2025–2030 deployment, the field is approaching a tipping point where global TC impacts could be reduced by 40–60% through AI-enhanced preparedness.</p>

<p><strong>Recommendations:</strong></p>
<ul>
  <li>Prioritize Southern Hemisphere reanalysis datasets</li>
  <li>Develop open-source ML benchmarks (e.g., TC-LLM)</li>
  <li>Establish WMO standards for synthetic TC validation</li>
</ul>

<h2 id="sources">Sources</h2>

<ul>
  <li>https://blogs.reading.ac.uk/crocus-dla/cr2025_27-using-ai-based-weather-forecast-models-to-improve-representation-of-tropical-cyclones-in-climate-simulations/</li>
  <li>https://www.researchgate.net/publication/385010194_TCP-Diffusion_A_Multi-modal_Diffusion_Model_for_Global_Tropical_Cyclone_Precipitation_Forecasting_with_Change_Awareness</li>
  <li>https://www.nature.com/articles/s41597-024-03281-5</li>
  <li>https://www.sciencedirect.com/science/article/pii/S0951832024004228</li>
  <li>https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2024JH000207</li>
  <li>https://www.researchgate.net/publication/371977058_Transformer-based_tropical_cyclone_track_and_intensity_forecasting</li>
  <li>https://www.semanticscholar.org/paper/Transformer-based-tropical-cyclone-track-and-Jiang-Zhang/fc97ef705b4f3100a13b7dfd46f9f5c8c466c7b1</li>
  <li>https://lingboliu.com/Publication.html</li>
  <li>https://www.sciencedirect.com/science/article/pii/S0167610523001435</li>
  <li>https://www.sciencedirect.com/science/article/pii/S016761052400299X</li>
  <li>https://www.nature.com/articles/s41467-024-53200-w</li>
  <li>https://www.sciencedirect.com/science/article/pii/S2212420925000287</li>
  <li>https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2023MS004203</li>
  <li>https://events.ecmwf.int/event/383/contributions/4572/attachments/2744/4647/Diagnostics-WS_Peyrille.pdf</li>
  <li>https://rmets.onlinelibrary.wiley.com/doi/10.1002/asl.1207</li>
  <li>https://agupubs.onlinelibrary.wiley.com/doi/full/10.1029/2023MS004203</li>
  <li>https://journals.ametsoc.org/view/journals/wefo/38/1/WAF-D-22-0145.1.xml</li>
  <li>https://rmets.onlinelibrary.wiley.com/doi/pdf/10.1002/met.2041</li>
</ul>]]></content><author><name></name></author><category term="Research" /><category term="Machine Learning" /><category term="Tropical Cyclones" /><category term="Research" /><category term="AI" /><category term="Data Science" /><summary type="html"><![CDATA[This article discusses the current state of machine learning in tropical cyclone research.]]></summary></entry><entry><title type="html">Building Your Own eBook Library with Calibre-Web</title><link href="https://jinjunliu.com/blog/2024/calibre-web-library/" rel="alternate" type="text/html" title="Building Your Own eBook Library with Calibre-Web" /><published>2024-09-19T00:00:00+00:00</published><updated>2024-09-19T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2024/calibre-web-library</id><content type="html" xml:base="https://jinjunliu.com/blog/2024/calibre-web-library/"><![CDATA[<p><img src="/assets/img/posts/2024-09-19-calibre-web-library-example.png" alt="Calibre-Web" class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>本文介绍通过 1Panel，部署 Calibre-Web，通过个人域名访问，并且设置Gmail SMTP服务，通过 Calibre-Web 向 Kindle 发送图书。完成本文操作需要有一台 VPS，并且有一个域名并使用 Cloudflare 进行 DNS 解析。</p>

<p>本文封面图片来自 <a href="https://github.com/janeczku/calibre-web">Calibre-Web GitHub</a>。</p>

<h2 id="1-vps-部署-1panel">1. VPS 部署 1Panel</h2>

<p>首先，需要在 VPS 上安装 1Panel，这是一个开源的服务器管理面板，可以帮助我们快速部署和管理各种应用。</p>

<p>如何部署和登陆参考 1Panel 的GitHub仓库：https://github.com/1Panel-dev/1Panel</p>

<h2 id="2-安装-calibre-web-应用">2. 安装 Calibre-Web 应用</h2>

<p>1Panel 提供了一个简便的应用商店，可以直接从中安装 Calibre-Web：</p>

<ol>
  <li>在 1Panel 的应用商店中，搜索并找到 <strong>Calibre-Web</strong> 应用。</li>
  <li>安装 Calibre-Web，并将其端口设置为 <strong>40109</strong>（当然你可以设置其他端口，但为了本篇示例，我们使用 40109）。</li>
  <li>安装完成后，Calibre-Web 将会运行在 <code class="language-plaintext highlighter-rouge">http://your_vps_ip:40109</code>，这时候你可以通过浏览器访问 Calibre-Web （前提是你的 VPS 防火墙开放了 40109 端口）。</li>
</ol>

<p>从1panel的容器那一栏也可以看到 Calibre-Web 的容器已经运行，其实1panel的应用商店就是通过Docker容器的方式部署应用的。</p>

<h2 id="3-添加域名并反向代理">3. 添加域名并反向代理</h2>

<p>为了通过自己的域名访问 Calibre-Web，可以在 1Panel 中添加网站，并通过反向代理将其指向 Calibre-Web 的端口。</p>

<p>假设你的个人域名是<code class="language-plaintext highlighter-rouge">example.com</code>，在 1Panel 中进入“网站”管理，点击“创建网站”，主域名输入你想使用的域名，例如：<code class="language-plaintext highlighter-rouge">calibre.example.com</code>，类型选反向代理，代理地址填入<code class="language-plaintext highlighter-rouge">127.0.0.1:40109</code>，如下图所示：</p>

<p><img src="/assets/img/posts/2024-09-19-calibre-web-library-1.png" alt="1Panel Add Website" class="img-fluid" style="width: 100%; height: auto;" /></p>

<h2 id="4-cloudflare-dns-设置">4. Cloudflare DNS 设置</h2>

<p>如果你使用的是 Cloudflare 来管理域名的 DNS，那么你需要在 Cloudflare 中为 <code class="language-plaintext highlighter-rouge">calibre.example.com</code> 配置相应的 DNS 记录。</p>

<ol>
  <li>登录到 Cloudflare 控制台，选择你的域名 <code class="language-plaintext highlighter-rouge">example.com</code>。</li>
  <li>添加一条 <code class="language-plaintext highlighter-rouge">A</code> 记录，名称为 <code class="language-plaintext highlighter-rouge">calibre</code>，并将 IP 地址指向你的 VPS。</li>
  <li>确保开启 <strong>代理模式</strong>（橙色云图标），这样 Cloudflare 会代理流量并提供额外的安全性和性能提升。</li>
</ol>

<h2 id="5-添加域名证书">5. 添加域名证书</h2>

<p>为了让网站支持 HTTPS，我们需要为 <code class="language-plaintext highlighter-rouge">calibre.example.com</code> 添加 SSL 证书。1Panel 提供了简单的证书管理功能，可以自动为你申请 Let’s Encrypt 证书。</p>

<ol>
  <li>在 1Panel 网站一栏中，进入证书设置。</li>
  <li>点击“申请证书”，选择你的域名 <code class="language-plaintext highlighter-rouge">calibre.example.com</code>，acme账号（如果没有，可以先创建），验证方式我选<code class="language-plaintext highlighter-rouge">http</code>，勾选<code class="language-plaintext highlighter-rouge">自动续签</code>，点击“确认”。</li>
</ol>

<p>完成后，网站将支持 HTTPS 访问。</p>

<h2 id="6-启用-https">6. 启用 HTTPS</h2>

<p>在网站<code class="language-plaintext highlighter-rouge">calibre.example.com</code>的设置中，将 <strong>HTTPS</strong> 那一栏开启 <strong>启用HTTPS</strong>，并选择你刚刚申请的证书。这样，我们就可以通过 HTTPS 访问 Calibre-Web 了。打开浏览器，输入<code class="language-plaintext highlighter-rouge">calibre.example.com</code>，能看到已经启用了 HTTPS 的 Calibre-Web 页面。</p>

<p><img src="/assets/img/posts/2024-09-19-calibre-web-library-2.png" alt="Calibre-Web HTTPS" class="img-fluid" style="width: 100%; height: auto;" /></p>

<h2 id="7-修改管理员密码和开启上传功能">7. 修改管理员密码和开启上传功能</h2>

<p>初次登录 Calibre-Web 后，强烈建议<strong>立即修改管理员账号密码</strong>。</p>

<ol>
  <li>默认管理员账号是 <code class="language-plaintext highlighter-rouge">admin</code>，密码是 <code class="language-plaintext highlighter-rouge">admin123</code>。</li>
  <li>登录后，进入设置页面，修改管理员用户名和密码。</li>
  <li>在设置中，还可以启用电子书上传功能，允许你直接在网页上上传新的电子书到你的个人书库中。</li>
</ol>

<p>如果你不想别人注册使用你的 Calibre-Web，可以在设置中关闭注册功能。</p>

<h2 id="8-开启-gmail-smtp">8. 开启 Gmail SMTP</h2>

<p>Calibre-Web 允许你直接通过邮件将电子书发送到 Kindle 设备。为了实现这个功能，我们需要配置 Gmail 的 SMTP 服务。</p>

<ol>
  <li>登陆Gmail，进入设置，找到 <strong>转发和 POP/IMAP</strong> 选项卡，开启 <strong>IMAP</strong> 服务。</li>
  <li>登录到你的 Google 账号，前往 <a href="https://myaccount.google.com/apppasswords">Google App 密码</a> 页面，生成一个新的 App 密码（用于外部应用访问你的 Gmail）。</li>
  <li>记下生成的密码，它将用于配置 Calibre-Web 的 SMTP 设置。</li>
</ol>

<h2 id="9-配置-calibre-web-的-smtp-设置">9. 配置 Calibre-Web 的 SMTP 设置</h2>

<p>在 Calibre-Web 中，填写刚才生成的 Gmail SMTP 信息：</p>

<ol>
  <li>打开 Calibre-Web 的管理设置页面，找到 <strong>SMTP 邮件服务器设置</strong> 设置。</li>
  <li>输入以下信息：
    <ul>
      <li>SMTP 服务器：<code class="language-plaintext highlighter-rouge">smtp.gmail.com</code></li>
      <li>SMTP 端口：<code class="language-plaintext highlighter-rouge">587</code></li>
      <li>SMTP 用户名：你的 Gmail 地址</li>
      <li>SMTP 密码：使用 Google App 密码</li>
      <li>加密方式：<code class="language-plaintext highlighter-rouge">STARTTLS</code></li>
    </ul>
  </li>
</ol>

<p>保存设置后，Calibre-Web 就可以通过你的 Gmail 地址发送邮件了。</p>

<h2 id="10-测试向-kindle-发送图书">10. 测试向 Kindle 发送图书</h2>

<p>最后一步，测试一下向 Kindle 发送电子书的功能：</p>

<ol>
  <li>在<code class="language-plaintext highlighter-rouge">用户设置</code>的<strong>接收书籍的电子阅读器邮箱地址</strong>中填入你的 Kindle 电子邮箱地址。</li>
  <li>确保在 Kindle 设置中，已经允许接收来自你这个Gmail地址的邮件发送文档。</li>
  <li>在 Calibre-Web 中选择一本书，点击 <strong>发送EPUB到电子阅读器</strong>。</li>
  <li>如果 SMTP 配置正确，你应该能够收到来自Amazon的邮件，提示你有新的文档到达，需要<code class="language-plaintext highlighter-rouge">Verify Request</code>。</li>
</ol>

<h2 id="总结">总结</h2>

<p>通过以上步骤，在 VPS 上通过 1Panel 部署了 Calibre-Web，并且配置了域名和 HTTPS 访问。现在，你可以通过 Calibre-Web 管理和分享你的电子书库，甚至直接将书籍发送到 Kindle。</p>

<h2 id="参考资料">参考资料</h2>

<ul>
  <li><a href="https://github.com/1Panel-dev/1Panel">1Panel GitHub</a></li>
  <li><a href="https://github.com/janeczku/calibre-web">Calibre-Web GitHub</a></li>
  <li>https://www.cnblogs.com/jiyuwu/p/16313476.html</li>
</ul>]]></content><author><name></name></author><category term="Tech" /><category term="Calibre" /><category term="eBook" /><category term="Library" /><category term="Web" /><category term="Docker" /><category term="Calibre-Web" /><summary type="html"><![CDATA[This article shows you how to build your own eBook library with Calibre-Web.]]></summary></entry><entry><title type="html">Python Visualization: Plot Variables on Maps with Subplots</title><link href="https://jinjunliu.com/blog/2024/python-plot-map-subplots/" rel="alternate" type="text/html" title="Python Visualization: Plot Variables on Maps with Subplots" /><published>2024-08-06T00:00:00+00:00</published><updated>2024-08-06T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2024/python-plot-map-subplots</id><content type="html" xml:base="https://jinjunliu.com/blog/2024/python-plot-map-subplots/"><![CDATA[<p>大气科学中常常需要在地图上绘制一些变量的分布，例如温度、湿度、风场等。在Python中，我们可以使用<code class="language-plaintext highlighter-rouge">matplotlib</code>和<code class="language-plaintext highlighter-rouge">cartopy</code>库来绘制这些地图。本文将介绍如何在地图上绘制多个变量的分布，并将这些地图放在一个图中。</p>

<h2 id="代码">代码</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">xarray</span> <span class="k">as</span> <span class="n">xr</span>
<span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="n">cartopy.crs</span> <span class="k">as</span> <span class="n">ccrs</span>
<span class="kn">import</span> <span class="n">cartopy.feature</span> <span class="k">as</span> <span class="n">cfeature</span>
<span class="kn">from</span> <span class="n">cartopy.mpl.ticker</span> <span class="kn">import</span> <span class="n">LongitudeFormatter</span><span class="p">,</span> <span class="n">LatitudeFormatter</span>
<span class="kn">import</span> <span class="n">cmaps</span>

<span class="c1"># Open the NetCDF file
</span><span class="n">file_path</span> <span class="o">=</span> <span class="sh">"</span><span class="s">sst.mon.ltm.1991-2020.nc</span><span class="sh">"</span>
<span class="n">ds</span> <span class="o">=</span> <span class="n">xr</span><span class="p">.</span><span class="nf">open_dataset</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
<span class="c1"># print lon and lat range
</span><span class="nf">print</span><span class="p">(</span><span class="n">ds</span><span class="p">[</span><span class="sh">'</span><span class="s">lon</span><span class="sh">'</span><span class="p">].</span><span class="n">values</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">ds</span><span class="p">[</span><span class="sh">'</span><span class="s">lat</span><span class="sh">'</span><span class="p">].</span><span class="n">values</span><span class="p">)</span>
<span class="c1"># Define the region limits
</span><span class="n">lon_min</span><span class="p">,</span> <span class="n">lon_max</span> <span class="o">=</span> <span class="mi">260</span><span class="p">,</span> <span class="mi">360</span>
<span class="n">lat_min</span><span class="p">,</span> <span class="n">lat_max</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">45</span>
<span class="c1"># Extract the variables
</span><span class="n">var_name</span> <span class="o">=</span> <span class="sh">'</span><span class="s">sst</span><span class="sh">'</span>
<span class="n">data1</span> <span class="o">=</span> <span class="n">ds</span><span class="p">[</span><span class="n">var_name</span><span class="p">].</span><span class="nf">isel</span><span class="p">(</span><span class="n">time</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># January
</span><span class="n">data2</span> <span class="o">=</span> <span class="n">ds</span><span class="p">[</span><span class="n">var_name</span><span class="p">].</span><span class="nf">isel</span><span class="p">(</span><span class="n">time</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span> <span class="c1"># June
</span><span class="n">data3</span> <span class="o">=</span> <span class="n">ds</span><span class="p">[</span><span class="n">var_name</span><span class="p">].</span><span class="nf">isel</span><span class="p">(</span><span class="n">time</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># November
# check data dimension
</span><span class="nf">print</span><span class="p">(</span><span class="n">data1</span><span class="p">.</span><span class="n">shape</span><span class="p">,</span> <span class="n">data2</span><span class="p">.</span><span class="n">shape</span><span class="p">,</span> <span class="n">data3</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="c1"># Define the projection
</span><span class="n">projection</span> <span class="o">=</span> <span class="n">ccrs</span><span class="p">.</span><span class="nc">PlateCarree</span><span class="p">()</span>
<span class="c1"># Create the figure and subplots
</span><span class="n">fig</span><span class="p">,</span> <span class="n">axes</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplots</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="n">subplot_kw</span><span class="o">=</span><span class="p">{</span><span class="sh">'</span><span class="s">projection</span><span class="sh">'</span><span class="p">:</span> <span class="n">projection</span><span class="p">},</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">5</span><span class="p">))</span>
<span class="c1"># Define the data list and titles
</span><span class="n">data_list</span> <span class="o">=</span> <span class="p">[</span><span class="n">data1</span><span class="p">,</span> <span class="n">data2</span><span class="p">,</span> <span class="n">data3</span><span class="p">]</span>
<span class="n">titles</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">(a) January</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">(b) June</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">(c) November</span><span class="sh">'</span><span class="p">]</span>
<span class="c1"># Find the common color limits
</span><span class="n">vmin</span> <span class="o">=</span> <span class="nf">min</span><span class="p">(</span><span class="n">data1</span><span class="p">.</span><span class="nf">min</span><span class="p">().</span><span class="n">values</span><span class="p">,</span> <span class="n">data2</span><span class="p">.</span><span class="nf">min</span><span class="p">().</span><span class="n">values</span><span class="p">,</span> <span class="n">data3</span><span class="p">.</span><span class="nf">min</span><span class="p">().</span><span class="n">values</span><span class="p">)</span>
<span class="n">vmax</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">data1</span><span class="p">.</span><span class="nf">max</span><span class="p">().</span><span class="n">values</span><span class="p">,</span> <span class="n">data2</span><span class="p">.</span><span class="nf">max</span><span class="p">().</span><span class="n">values</span><span class="p">,</span> <span class="n">data3</span><span class="p">.</span><span class="nf">max</span><span class="p">().</span><span class="n">values</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Common color limits:</span><span class="sh">"</span><span class="p">,</span> <span class="n">vmin</span><span class="p">,</span> <span class="n">vmax</span><span class="p">)</span>
<span class="c1"># round the limits to the nearest 1
</span><span class="n">vmin</span> <span class="o">=</span> <span class="nf">int</span><span class="p">(</span><span class="n">vmin</span><span class="p">)</span>
<span class="n">vmax</span> <span class="o">=</span> <span class="nf">int</span><span class="p">(</span><span class="n">vmax</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>
<span class="c1"># define the levels
</span><span class="n">levels</span> <span class="o">=</span> <span class="nf">range</span><span class="p">(</span><span class="n">vmin</span><span class="p">,</span> <span class="n">vmax</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="c1"># lon and lat ticks
</span><span class="n">lon_formatter</span> <span class="o">=</span> <span class="nc">LongitudeFormatter</span><span class="p">(</span><span class="n">zero_direction_label</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">lat_formatter</span> <span class="o">=</span> <span class="nc">LatitudeFormatter</span><span class="p">()</span>
<span class="c1"># colormap
</span><span class="n">cmap</span> <span class="o">=</span> <span class="n">cmaps</span><span class="p">.</span><span class="n">MPL_jet</span>
<span class="c1"># Plot the data
</span><span class="k">for</span> <span class="n">ax</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">title</span> <span class="ow">in</span> <span class="nf">zip</span><span class="p">(</span><span class="n">axes</span><span class="p">,</span> <span class="n">data_list</span><span class="p">,</span> <span class="n">titles</span><span class="p">):</span>
    <span class="n">cs</span> <span class="o">=</span> <span class="n">ax</span><span class="p">.</span><span class="nf">contourf</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="sh">'</span><span class="s">lon</span><span class="sh">'</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="sh">'</span><span class="s">lat</span><span class="sh">'</span><span class="p">],</span> <span class="n">data</span><span class="p">,</span> <span class="n">transform</span><span class="o">=</span><span class="n">projection</span><span class="p">,</span> <span class="n">levels</span><span class="o">=</span><span class="n">levels</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="n">cmap</span><span class="p">)</span>
    <span class="c1"># add contour lines
</span>    <span class="n">ax</span><span class="p">.</span><span class="nf">contour</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="sh">'</span><span class="s">lon</span><span class="sh">'</span><span class="p">],</span> <span class="n">data</span><span class="p">[</span><span class="sh">'</span><span class="s">lat</span><span class="sh">'</span><span class="p">],</span> <span class="n">data</span><span class="p">,</span> <span class="n">transform</span><span class="o">=</span><span class="n">projection</span><span class="p">,</span> <span class="n">levels</span><span class="o">=</span><span class="n">levels</span><span class="p">,</span> <span class="n">colors</span><span class="o">=</span><span class="sh">'</span><span class="s">k</span><span class="sh">'</span><span class="p">,</span> <span class="n">linewidths</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
    <span class="c1"># Add features
</span>    <span class="n">ax</span><span class="p">.</span><span class="nf">coastlines</span><span class="p">()</span>
    <span class="n">ax</span><span class="p">.</span><span class="nf">add_feature</span><span class="p">(</span><span class="n">cfeature</span><span class="p">.</span><span class="n">BORDERS</span><span class="p">)</span>
    <span class="n">ax</span><span class="p">.</span><span class="nf">set_extent</span><span class="p">([</span><span class="n">lon_min</span><span class="p">,</span> <span class="n">lon_max</span><span class="p">,</span> <span class="n">lat_min</span><span class="p">,</span> <span class="n">lat_max</span><span class="p">],</span> <span class="n">crs</span><span class="o">=</span><span class="n">projection</span><span class="p">)</span>
    <span class="c1"># title position: left aligned
</span>    <span class="n">ax</span><span class="p">.</span><span class="nf">set_title</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">loc</span><span class="o">=</span><span class="sh">'</span><span class="s">left</span><span class="sh">'</span><span class="p">)</span>
    <span class="c1"># Set ticks
</span>    <span class="n">ax</span><span class="p">.</span><span class="nf">set_xticks</span><span class="p">([</span><span class="o">-</span><span class="mi">100</span><span class="p">,</span> <span class="o">-</span><span class="mi">80</span><span class="p">,</span> <span class="o">-</span><span class="mi">60</span><span class="p">,</span> <span class="o">-</span><span class="mi">40</span><span class="p">,</span> <span class="o">-</span><span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="n">crs</span><span class="o">=</span><span class="n">projection</span><span class="p">)</span>
    <span class="n">ax</span><span class="p">.</span><span class="nf">set_yticks</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">45</span><span class="p">],</span> <span class="n">crs</span><span class="o">=</span><span class="n">projection</span><span class="p">)</span>
    <span class="n">ax</span><span class="p">.</span><span class="n">xaxis</span><span class="p">.</span><span class="nf">set_major_formatter</span><span class="p">(</span><span class="n">lon_formatter</span><span class="p">)</span>
    <span class="n">ax</span><span class="p">.</span><span class="n">yaxis</span><span class="p">.</span><span class="nf">set_major_formatter</span><span class="p">(</span><span class="n">lat_formatter</span><span class="p">)</span>
<span class="c1"># Add a single colorbar for all subplots, colorbar position is below the subplots and horizontal
</span><span class="n">axins</span> <span class="o">=</span> <span class="n">fig</span><span class="p">.</span><span class="nf">add_axes</span><span class="p">([</span><span class="mf">0.25</span><span class="p">,</span> <span class="mf">0.25</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.02</span><span class="p">])</span>
<span class="n">fig</span><span class="p">.</span><span class="nf">colorbar</span><span class="p">(</span><span class="n">cs</span><span class="p">,</span> <span class="n">cax</span><span class="o">=</span><span class="n">axins</span><span class="p">,</span> <span class="n">orientation</span><span class="o">=</span><span class="sh">'</span><span class="s">horizontal</span><span class="sh">'</span><span class="p">,</span> <span class="n">ticks</span><span class="o">=</span><span class="n">levels</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sh">'</span><span class="s">sea surface temperature (°C)</span><span class="sh">'</span><span class="p">)</span>
<span class="c1"># save the plot
</span><span class="n">save_path</span> <span class="o">=</span> <span class="sh">'</span><span class="s">.</span><span class="sh">'</span>
<span class="n">fig_name</span> <span class="o">=</span> <span class="sh">'</span><span class="s">sst-ltm</span><span class="sh">'</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="si">{</span><span class="n">save_path</span><span class="si">}</span><span class="s">/</span><span class="si">{</span><span class="n">fig_name</span><span class="si">}</span><span class="s">.png</span><span class="sh">'</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">300</span><span class="p">,</span> <span class="n">bbox_inches</span><span class="o">=</span><span class="sh">'</span><span class="s">tight</span><span class="sh">'</span><span class="p">)</span>
<span class="c1"># Display the plot
</span><span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div>

<h2 id="说明">说明</h2>

<ul>
  <li>使用了<code class="language-plaintext highlighter-rouge">xarray</code>库来处理NetCDF文件，<code class="language-plaintext highlighter-rouge">matplotlib</code>和<code class="language-plaintext highlighter-rouge">cartopy</code>库来绘制地图。</li>
  <li><code class="language-plaintext highlighter-rouge">cmaps</code>是一个自定义的colormap库，使用cmaps可以使用NCL的colormap。</li>
  <li><code class="language-plaintext highlighter-rouge">cartopy</code>库提供了很多地图投影方式，这里使用了<code class="language-plaintext highlighter-rouge">PlateCarree</code>投影。</li>
  <li><code class="language-plaintext highlighter-rouge">contourf</code>函数用来绘制填充等值线图，<code class="language-plaintext highlighter-rouge">contour</code>函数用来绘制等值线。</li>
  <li><code class="language-plaintext highlighter-rouge">add_feature</code>函数用来添加地图特征，例如海岸线、边界等。</li>
  <li><code class="language-plaintext highlighter-rouge">set_extent</code>函数用来设置地图的范围。</li>
  <li><code class="language-plaintext highlighter-rouge">set_title</code>函数用来设置子图的标题。</li>
  <li><code class="language-plaintext highlighter-rouge">set_xticks</code>和<code class="language-plaintext highlighter-rouge">set_yticks</code>函数用来设置经纬度的刻度。</li>
  <li><code class="language-plaintext highlighter-rouge">set_major_formatter</code>函数用来设置经纬度的格式。</li>
  <li><code class="language-plaintext highlighter-rouge">add_axes</code>函数用来添加colorbar的位置。</li>
  <li><code class="language-plaintext highlighter-rouge">savefig</code>函数用来保存图片。</li>
</ul>

<h2 id="结果">结果</h2>

<p>以<a href="https://downloads.psl.noaa.gov/Datasets/COBE/sst.mon.ltm.1991-2020.nc">COBE SST LTM数据</a>为例，绘制了三个不同月份下的海表温度多年平均分布图。</p>

<p><img src="/assets/img/posts/2024-08-06-python-plot-map-subplots-result.png" alt="Python Plotting Variables Map with Subplots" class="img-fluid" style="width: 100%; height: auto;" /></p>]]></content><author><name></name></author><category term="Research" /><category term="Python" /><category term="Scientific Visualization" /><category term="matplotlib" /><category term="cartopy" /><category term="NetCDF" /><summary type="html"><![CDATA[Plotting variables on maps with subplots in Python.]]></summary></entry><entry><title type="html">Run Local Large Language Models</title><link href="https://jinjunliu.com/blog/2024/run-local-llm/" rel="alternate" type="text/html" title="Run Local Large Language Models" /><published>2024-08-01T00:00:00+00:00</published><updated>2024-08-01T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2024/run-local-llm</id><content type="html" xml:base="https://jinjunliu.com/blog/2024/run-local-llm/"><![CDATA[<h2 id="ollama">ollama</h2>

<p>首先下载<code class="language-plaintext highlighter-rouge">ollama</code>, 官网下载地址: https://ollama.com/</p>

<p>安装完成后，在终端输入<code class="language-plaintext highlighter-rouge">ollama</code>，可以看到ollama的使用说明。</p>

<p><img src="/assets/img/posts/2024-08-01-run-local-llm-1.png" alt="ollama terminal" class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>下载并运行大语言模型，只需要运行<code class="language-plaintext highlighter-rouge">ollama run</code>命令即可，例如要下载Google的开源模型<code class="language-plaintext highlighter-rouge">Gemma2</code>, 则运行</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama run gemma2:2b
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">2b</code>表示模型的参数量。<code class="language-plaintext highlighter-rouge">gemma2</code>有<code class="language-plaintext highlighter-rouge">2b</code>, <code class="language-plaintext highlighter-rouge">9b</code>, <code class="language-plaintext highlighter-rouge">27b</code>三个版本。参数量越大，模型效果越好，但是运行速度也会变慢，也会消耗更多的内存。</p>

<p>首次运行会自动下载模型，之后再次运行时会直接加载模型，然后就可以和模型进行对话了。</p>

<p><img src="/assets/img/posts/2024-08-01-run-local-llm-2.png" alt="ollama run" class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>退出时输入<code class="language-plaintext highlighter-rouge">/bye</code>。</p>

<p><code class="language-plaintext highlighter-rouge">ollama</code>支持的模型的完整列表可以在这里查看: https://ollama.com/library</p>

<p>查看已经下载的模型列表可以使用命令：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama list
</code></pre></div></div>

<p>更多的使用方法可以查看<code class="language-plaintext highlighter-rouge">ollama</code>的Github仓库: https://github.com/ollama/ollama</p>

<h2 id="enchanted">Enchanted</h2>

<p>在命令行终端进行对话不太方便，<code class="language-plaintext highlighter-rouge">Enchanted</code>是一个基于<code class="language-plaintext highlighter-rouge">ollama</code>的图形界面工具，可以更方便的和模型进行对话，并且可以保存对话记录。</p>

<p><code class="language-plaintext highlighter-rouge">Enchanted</code>可以直接在App Store中下载安装: https://apps.apple.com/us/app/enchanted-llm/id6474268307</p>

<p>安装完成后，打开<code class="language-plaintext highlighter-rouge">Enchanted</code>，右上角可以选择<code class="language-plaintext highlighter-rouge">ollama</code>安装的本地模型，如下图所示:</p>

<p><img src="/assets/img/posts/2024-08-01-run-local-llm-3.png" alt="Enchanted" class="img-fluid" style="width: 100%; height: auto;" /></p>

<h2 id="opencat">OpenCat</h2>

<p>另一个支持本地模型的App是<code class="language-plaintext highlighter-rouge">OpenCat</code>, 地址: https://opencat.app/</p>

<p><code class="language-plaintext highlighter-rouge">OpenCat</code>不仅支持本地模型，也可以和例如OpenAI的GPT-4等云端模型进行对话（需要API Key）。</p>]]></content><author><name></name></author><category term="Tech" /><category term="Large Language Model" /><category term="MacOS" /><category term="ollama" /><category term="Enchanted" /><category term="OpenCat" /><summary type="html"><![CDATA[Introduce `ollama`, `Enchanted`, and `OpenCat` to run large language models locally on MacOS.]]></summary></entry><entry><title type="html">Index Area Citation Error of Microsoft Word</title><link href="https://jinjunliu.com/blog/2024/word-citation-error/" rel="alternate" type="text/html" title="Index Area Citation Error of Microsoft Word" /><published>2024-07-30T00:00:00+00:00</published><updated>2024-07-30T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2024/word-citation-error</id><content type="html" xml:base="https://jinjunliu.com/blog/2024/word-citation-error/"><![CDATA[<p><img src="/assets/img/posts/2024-07-30-word-citation-error-heroImage.png" alt="Citation/Bibliography is wrongly placed in index area, please delete the placed citation/bibliography in index area." class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>When you are writing a paper in Microsoft Word and using a reference manager like Mendeley or Zotero, you may encounter an error message like this:</p>

<blockquote>
  <p>Citation/Bibliography is wrongly placed in index area, please delete the placed citation/bibliography in index area.</p>
</blockquote>

<p>I encountered this error only in my Windows PC, but not in my Mac. The error message is not very informative, at first I asked ChatGPT but I didn’t get a useful answer.</p>

<p>The solution is actually very simple. I installed both Mendeley and Zotero plugins for Word on my Windows PC, but I only use Zotero on my MacBook. The paper was originally written on my MacBook, and I opened it on my Windows PC to continue writing, and the error occurred. So I realized that the error might be caused by the conflict between Mendeley and Zotero plugins.</p>

<p>So I tried to disable the Mendeley plugin in Word, and the error disappeared, that’s it.</p>

<p>The steps are: open Word, go to <code class="language-plaintext highlighter-rouge">File</code> -&gt; <code class="language-plaintext highlighter-rouge">Options</code> -&gt; <code class="language-plaintext highlighter-rouge">Add-ins</code>, on the <code class="language-plaintext highlighter-rouge">manage</code> dropdown list, select <code class="language-plaintext highlighter-rouge">templates</code>, click <code class="language-plaintext highlighter-rouge">Go...</code>, uncheck the Mendeley plugin, and click <code class="language-plaintext highlighter-rouge">OK</code>.</p>]]></content><author><name></name></author><category term="Tech" /><category term="Microsoft Word" /><category term="Citation" /><category term="Mendely" /><category term="Zotero" /><summary type="html"><![CDATA[How to fix the error shown in Microsoft Word: Citation/Bibliography is wrongly placed in index area, please delete the placed citation/bibliography in index area.]]></summary></entry><entry><title type="html">Rime Input Method on MacOS</title><link href="https://jinjunliu.com/blog/2024/rime-input/" rel="alternate" type="text/html" title="Rime Input Method on MacOS" /><published>2024-07-19T00:00:00+00:00</published><updated>2024-07-19T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2024/rime-input</id><content type="html" xml:base="https://jinjunliu.com/blog/2024/rime-input/"><![CDATA[<p>Rime是一款开源的输入法引擎，支持多种平台，包括Windows、MacOS、Linux等。Rime的特点是高度可定制，用户可以自定义输入方案，包括词库、词频、快捷键等。基于Rime引擎，在不同的平台上有不同的前端输入法，如Windows上的小狼毫、MacOS上的鼠须管、Linux上的Fcitx等。本文介绍如何在MacOS上安装鼠须管（Squirrel）输入法。使用鼠须管输入法，用户可以输入中文、英文、日文、韩文等多种语言，支持拼音、五笔、注音、仓颉等多种输入法。虽然MacOS自带了中文输入法，但经常听到有人说有bug，用久了会卡死，而鼠须管输入法没有这个问题。鼠须管的另一个优点是开源、词库离线，对于有隐私需求的用户来说是一个不错的选择。</p>

<h2 id="下载安装鼠须管">下载安装鼠须管</h2>

<p>可以去<a href="https://rime.im/download/">官方网站下载</a>安装包，但是用Homebrew安装更方便。在终端输入以下命令：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install</span> <span class="nt">--cask</span> squirrel
</code></pre></div></div>

<p>安装完成后需要重启电脑。重启后需要手动添加一下，点击menu bar上的输入法图标，选择<code class="language-plaintext highlighter-rouge">open keyboard settings</code>，然后点击左下角的<code class="language-plaintext highlighter-rouge">+</code>号，选择<code class="language-plaintext highlighter-rouge">Chinese (Simplified)</code>，然后选择<code class="language-plaintext highlighter-rouge">Squirrel</code>，点击<code class="language-plaintext highlighter-rouge">Add</code>。</p>

<h2 id="下载安装输入方案">下载安装输入方案</h2>

<p>鼠须管输入法的输入方案是独立的，需要单独下载。输入方案的安装方法是下载方案文件，放到<code class="language-plaintext highlighter-rouge">~/Library/Rime</code>目录下。这里推荐一个方案，<a href="https://github.com/iDvel/rime-ice">雾凇拼音</a>，也已经被很多人推荐了。下载并使用的步骤如下：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 进入Library目录</span>
<span class="nb">cd</span> ~/Library
<span class="c"># 备份原有Rime配置</span>
<span class="nb">mv </span>Rime Rime.old
<span class="c"># 下载雾凇拼音</span>
git clone <span class="nt">--depth</span><span class="o">=</span>1 https://github.com/iDvel/rime-ice.git Rime-ice
<span class="c"># 建立软链接</span>
<span class="nb">ln</span> <span class="nt">-s</span> Rime-ice Rime
</code></pre></div></div>

<p>这里的<code class="language-plaintext highlighter-rouge">~</code>表示用户目录。如果我的用户名是<code class="language-plaintext highlighter-rouge">jjliu</code>，那么<code class="language-plaintext highlighter-rouge">~</code>就是<code class="language-plaintext highlighter-rouge">/Users/jjliu</code>。</p>

<p>然后在menu bar上的输入法图标中选择<code class="language-plaintext highlighter-rouge">Deploy</code>，部署输入方案。部署完成后就可以使用了。</p>

<p>雾凇拼音是持续更新的，可以定期更新。如果要更新，进入<code class="language-plaintext highlighter-rouge">~/Library/Rime</code>目录，执行以下命令：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 进入Rime目录</span>
<span class="nb">cd</span> ~/Library/Rime
<span class="c"># 更新</span>
git pull
</code></pre></div></div>

<p>然后同样点击<code class="language-plaintext highlighter-rouge">Deploy</code>，就部署了更新后的输入方案。</p>

<p>雾凇拼音的配置已经写得很好了，一般用户只要使用就行了，如果想详细了解配置，可以参考<a href="https://dvel.me/posts/rime-ice/">作者的博客</a>。</p>

<h2 id="自定义皮肤">自定义皮肤</h2>

<p>最近流行的微信输入法虽然我不敢用（出于隐私考虑），但是皮肤确实好看。于是有人给鼠须管设计了一个类似微信输入法的皮肤，作者lewang，地址: https://gist.github.com/lewangdev/f8ebbba24f464e915fb7d36857fcbbe5. 把这几个文件下载下来(右上角有下载zip压缩包的按钮)，解压后把这几个文件放到<code class="language-plaintext highlighter-rouge">~/Library/Rime</code>目录下。值得注意的是，<code class="language-plaintext highlighter-rouge">~</code>目录下的<code class="language-plaintext highlighter-rouge">Library</code>是隐藏目录，可以在Finder中按<code class="language-plaintext highlighter-rouge">Command+Shift+.</code>显示隐藏文件夹。但这样比较麻烦，可以在终端用命令行移动文件：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/Downloads
<span class="nb">cd </span>f8ebbba24f464e915fb7d36857fcbbe5 <span class="c"># 这个目录是解压后的目录名，根据实际情况修改</span>
<span class="nb">mv</span> <span class="k">*</span>.yaml ~/Library/Rime
</code></pre></div></div>

<p>移动文件后需要重新点击<code class="language-plaintext highlighter-rouge">Deploy</code>。</p>

<p>如果要使用别的皮肤，可以修改文件<code class="language-plaintext highlighter-rouge">~/Library/Rime/squirrel.custom.yaml</code>的style字段。</p>

<p>最后的效果如下图：</p>

<p><img src="/assets/img/posts/2024-07-19-rime-input-1.png" alt="Rime Input Method" class="img-fluid" style="width: 100%; height: auto;" /></p>

<h2 id="参考资料">参考资料</h2>

<p>主要参考的是lewang的推特（现X），链接: https://x.com/lewangdev/status/1643083362154995712</p>]]></content><author><name></name></author><category term="Tech" /><category term="Rime" /><category term="Input Method" /><category term="MacOS" /><summary type="html"><![CDATA[This article explains how to set up the Rime input method on MacOS.]]></summary></entry><entry><title type="html">Using Unofficial Cloud Storage to Sync Zotero Library</title><link href="https://jinjunliu.com/blog/2024/zotero-sync/" rel="alternate" type="text/html" title="Using Unofficial Cloud Storage to Sync Zotero Library" /><published>2024-01-26T00:00:00+00:00</published><updated>2024-01-26T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2024/zotero-sync</id><content type="html" xml:base="https://jinjunliu.com/blog/2024/zotero-sync/"><![CDATA[<p><img src="/assets/img/posts/2024-01-26-zotero-sync-example.png" alt="Zotero Sync" class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>Today, I migrated my reference management software from Mendeley to Zotero. There are several main reasons:</p>

<ul>
  <li>The old Mendeley Desktop is no longer updated, and the new Mendeley Reference Manager client is entirely cloud-based, leading to loading times and unsmooth usage;</li>
  <li>The browser plugin Mendeley Web Importer for importing references is not user-friendly, very slow, and often fails to import;</li>
  <li>Zotero is open-source software, updated frequently, with an active community.</li>
</ul>

<p>Zotero is great, but the sync function on Zotero’s own server only offers 300MB of storage space for free users, which is quickly filled up if you have a lot of references. The reason for needing to sync the library is that if you have two computers, you need to use Zotero on both and keep them consistent. Also, if you change computers in the future, you would like to quickly sync the library to the new computer.</p>

<h2 id="download-and-install-zotero">Download and Install Zotero</h2>

<p>The official Zotero website is <a href="https://www.zotero.org/">zotero.org</a>, where you can download the Zotero installation package. No need to elaborate further here.</p>

<h2 id="migrate-mendeley-library-to-zotero">Migrate Mendeley Library to Zotero</h2>

<p>Zotero provides a one-click migration function for the Mendeley library. Just click <code class="language-plaintext highlighter-rouge">File</code> -&gt; <code class="language-plaintext highlighter-rouge">Import</code> -&gt; <code class="language-plaintext highlighter-rouge">Mendeley Reference Manager</code> in Zotero’s menu, log in to your Mendeley account, and the migration will start.</p>

<p><img src="/assets/img/posts/2024-01-26-zotero-sync-1.png" alt="Import Mendeley" class="img-fluid" style="width: 100%; height: auto;" /></p>

<h2 id="zotero-sync-settings">Zotero Sync Settings</h2>

<p>First, register and log in to a Zotero account by visiting the official website. Then click <code class="language-plaintext highlighter-rouge">Edit</code> -&gt; <code class="language-plaintext highlighter-rouge">Preferences</code> -&gt; <code class="language-plaintext highlighter-rouge">Sync</code> in the toolbar and enter your account credentials.</p>

<p>If the Mendeley imported library exceeds 300MB, clicking the sync button in the upper right corner will show a warning that synchronization cannot be completed due to exceeding capacity. Visit the <a href="https://www.zotero.org/settings/storage">Zotero Storage</a> page to see the storage usage.</p>

<p><img src="/assets/img/posts/2024-01-26-zotero-sync-2.jpg" alt="Zotero Storage" class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>The official website offers storage upgrade plans, but they require an additional fee. At the same time, Zotero also provides other sync solutions, which is very considerate. On this page: <a href="https://www.zotero.org/support/sync#alternative_syncing_solutions">https://www.zotero.org/support/sync#alternative_syncing_solutions</a>, you can find other sync solutions provided by Zotero. For example, the simplest method is:</p>

<blockquote>
  <p>The easiest method is to use linked files, rather than stored copies of files, with only your attachment files in the externally synced folder. The ZotFile plugin can make this simple by automatically moving attachment files to a designated folder as you import them. You should also set up Zotero’s Linked Attachment Base Directory feature to point to the same folder so that Zotero can find your files on each computer, even if the path to the cloud storage folder differs.</p>
</blockquote>

<p>This means using the ZotFile plugin to transfer attachment files (i.e., the PDF files of the references) to a local folder of a cloud drive, which will be automatically synced by the cloud drive. ZotFile will automatically set links to these external files, thus achieving the separation of metadata (such as the title, author, DOI of the reference) and attachments. Metadata is stored on Zotero’s server, while attachments are stored on the cloud drive. The size of metadata is much smaller than attachments, so using Zotero’s server is entirely sufficient.</p>

<p>I use OneDrive to sync attachments because I have co-subscribed to Office 365 Family, providing 1TB of OneDrive storage, which is sufficient. If you use other cloud storage like iCloud, Google Drive, Dropbox, etc., the method is the same.</p>

<h2 id="download-and-install-zotfile">Download and Install ZotFile</h2>

<p>ZotFile is a plugin for Zotero. Its official website is <a href="https://zotfile.com/">zotfile.com</a>, where you can download the ZotFile installation package. After downloading, click <code class="language-plaintext highlighter-rouge">Tools</code> -&gt; <code class="language-plaintext highlighter-rouge">Add-ons</code> -&gt; <code class="language-plaintext highlighter-rouge">Install Add-on From File...</code> in Zotero’s menu and select the downloaded ZotFile package. As shown below:</p>

<p><img src="/assets/img/posts/2024-01-26-zotero-sync-3.png" alt="Install ZotFile" class="img-fluid" style="width: 100%; height: auto;" /></p>

<h2 id="set-up-zotfile">Set Up ZotFile</h2>

<p>After installing ZotFile, click <code class="language-plaintext highlighter-rouge">Tools</code> -&gt; <code class="language-plaintext highlighter-rouge">ZotFile Preferences</code> in Zotero’s menu to open the ZotFile settings interface. As shown below:</p>

<p><img src="/assets/img/posts/2024-01-26-zotero-sync-4.png" alt="ZotFile Preferences" class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>In <code class="language-plaintext highlighter-rouge">General Settings</code>, set <code class="language-plaintext highlighter-rouge">Source Folder for Attaching New Files</code> to your download folder, such as <code class="language-plaintext highlighter-rouge">C:\Users\username\Downloads</code>. This step makes it convenient to transfer downloaded reference PDF files to the cloud drive folder. Thus, if you select a reference, right-click, and choose <code class="language-plaintext highlighter-rouge">attach new file</code>, ZotFile will automatically transfer the reference’s PDF file from the download folder to the cloud drive folder and set the link to the file in the cloud drive folder.</p>

<p>Next, set <code class="language-plaintext highlighter-rouge">Location of Files</code> to your cloud drive folder. I set it to <code class="language-plaintext highlighter-rouge">C:\Users\username\OneDrive\Zotero</code>, where <code class="language-plaintext highlighter-rouge">username</code> is the Windows username. The <code class="language-plaintext highlighter-rouge">Zotero</code> folder is newly created in OneDrive for storing reference attachments. On Mac, the directory structure should be similar to <code class="language-plaintext highlighter-rouge">/Users/username/OneDrive/Zotero</code>.</p>

<h2 id="transfer-reference-attachments">Transfer Reference Attachments</h2>

<p>At this point, the attachments of references in the library are not yet transferred to the Zotero folder in the cloud drive. The initial transfer needs to be done manually. In Zotero, click <code class="language-plaintext highlighter-rouge">My Library</code> on the left, press <code class="language-plaintext highlighter-rouge">ctrl+A</code> (or <code class="language-plaintext highlighter-rouge">command+A</code> on Mac) to select all references, then right-click, select <code class="language-plaintext highlighter-rouge">Manage Attachments</code> -&gt; <code class="language-plaintext highlighter-rouge">Rename and Move</code>, and wait a few minutes. All reference attachments will be transferred to the Zotero folder in the cloud drive (here <code class="language-plaintext highlighter-rouge">C:\Users\username\OneDrive\Zotero</code>) and renamed according to a unified rule (the renaming rule can also be set in ZotFile settings, but I didn’t change it), making it look very tidy.</p>

<h2 id="set-zoteros-attachment-folder">Set Zotero’s Attachment Folder</h2>

<p>In Zotero, click <code class="language-plaintext highlighter-rouge">Edit</code> -&gt; <code class="language-plaintext highlighter-rouge">Preferences</code> -&gt; <code class="language-plaintext highlighter-rouge">Advanced</code> -&gt; <code class="language-plaintext highlighter-rouge">Files and Folders</code>, and set <code class="language-plaintext highlighter-rouge">Linked Attachment Base Directory</code> to your cloud drive folder, here <code class="language-plaintext highlighter-rouge">C:\Users\username\OneDrive\Zotero</code>. As shown below:</p>

<p><img src="/assets/img/posts/2024-01-26-zotero-sync-5.png" alt="Zotero Preferences" class="img-fluid" style="width: 100%; height: auto;" /></p>

<p>This step is essential because the cloud drive folder path may differ on different computers. For example, on Windows, it might be <code class="language-plaintext highlighter-rouge">C:\Users\username\OneDrive\Zotero</code>, while on Mac, it could be <code class="language-plaintext highlighter-rouge">/Users/username/OneDrive/Zotero</code>. With this setting, Zotero can find the cloud drive directory as the Base Directory and then locate the reference attachments within the Base Directory.</p>

<h2 id="purge-storage-in-my-library">Purge Storage in My Library</h2>

<p>After completing the above steps, theoretically, all Zotero reference attachments should have been transferred to the cloud drive. However, if you click the sync button in the upper right corner, there might still be a warning. This is because some reference attachments remain on Zotero’s server and need to be deleted manually (this might be a bug). Visit the <a href="https://www.zotero.org/settings/storage">Zotero Storage</a> page and click <code class="language-plaintext highlighter-rouge">Purge Storage in My Library</code>. Then, return to the Zotero client, click <code class="language-plaintext highlighter-rouge">Edit</code> -&gt; <code class="language-plaintext highlighter-rouge">Preferences</code> -&gt; <code class="language-plaintext highlighter-rouge">Sync</code>, click <code class="language-plaintext highlighter-rouge">Reset</code>, and choose <code class="language-plaintext highlighter-rouge">Replace Online Library</code>. After that, click the sync button in the upper right corner, and there will be no warning. <strong>Note: Be careful with this step. To prevent database loss, it’s best to back up the database first. That is, copy the <code class="language-plaintext highlighter-rouge">C:\Users\username\Zotero</code> (Windows) or <code class="language-plaintext highlighter-rouge">/Users/username/Zotero</code> (Mac) folder to another location.</strong></p>

<h2 id="sync-on-other-computers">Sync on Other Computers</h2>

<p>Once the above steps are completed, Zotero will have full cloud sync functionality. On other computers, simply install Zotero and ZotFile, log in to your Zotero account, set up ZotFile and <code class="language-plaintext highlighter-rouge">Linked Attachment Base Directory</code> similarly, then click the sync button in the upper right corner to sync the library.</p>

<h2 id="note">Note</h2>

<p>This post was originally written in Chinese, and the English version is translated by ChatGPT.</p>]]></content><author><name></name></author><category term="Research" /><category term="Zotero" /><category term="Sync" /><category term="OneDrive" /><summary type="html"><![CDATA[This article explains how to use unofficial cloud storage (such as OneDrive) to sync the Zotero library.]]></summary></entry><entry><title type="html">Python Environment Management</title><link href="https://jinjunliu.com/blog/2022/python-env-manage/" rel="alternate" type="text/html" title="Python Environment Management" /><published>2022-05-22T00:00:00+00:00</published><updated>2022-05-22T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2022/python-env-manage</id><content type="html" xml:base="https://jinjunliu.com/blog/2022/python-env-manage/"><![CDATA[<h2 id="what-is-a-python-environment">What is a Python Environment</h2>

<p>Python is a cross-platform object-oriented scripting language that can run on platforms such as Windows, Linux, and Mac OS. A Python environment is a folder that contains the Python interpreter and all necessary libraries.</p>

<h2 id="why-manage-python-environments">Why Manage Python Environments?</h2>

<p>Different projects may require different Python versions and libraries, and Python libraries may also depend on each other, with specific version requirements. If all projects use the same Python environment, the environment will become messy and difficult to maintain when there are many projects. For example, project A requires <code class="language-plaintext highlighter-rouge">Python 3.6</code> and <code class="language-plaintext highlighter-rouge">Pandas 1.0.1</code>, and project B requires <code class="language-plaintext highlighter-rouge">Python 3.10</code> and <code class="language-plaintext highlighter-rouge">Pandas 1.3.5</code>. Although Python interpreters and many Python libraries are backward compatible with themselves, lower versions of Python may not support higher versions of third-party libraries. Therefore, we need to separate the Python environments of these projects so that each project can run independently. The following image (from dataquest.io) shows a simple example of a virtual Python environment: three Python environments are independent of each other and do not affect each other. When one of the Python environments is operated on (such as installing or deleting a library, upgrading a library version), the other two Python environments will not be affected.</p>

<p><img src="/assets/img/posts/2022-05-22-python-env-manage-1.png" alt="Python Virtual Environment Example" class="img-fluid" style="width: 100%; height: auto;" /></p>

<h2 id="python-environment-management-tools">Python Environment Management Tools</h2>

<p>Python environment management tools include:</p>

<ol>
  <li>
    <p><a href="https://docs.conda.io/en/latest/">conda</a>: An open-source tool developed by <code class="language-plaintext highlighter-rouge">Anaconda</code> company. This tool is included in its products <code class="language-plaintext highlighter-rouge">Anaconda</code> and <code class="language-plaintext highlighter-rouge">Miniconda</code> (both are Python distributions).</p>
  </li>
  <li>
    <p><a href="https://docs.python.org/3/library/venv.html">venv</a>: A library included in Python 3.3 and above, used to create virtual Python environments.</p>
  </li>
</ol>

<p>Either of these two tools can be chosen, but I prefer to use <code class="language-plaintext highlighter-rouge">conda</code> for version and package management.</p>

<h3 id="conda">conda</h3>

<p>To use conda, you first need to install <code class="language-plaintext highlighter-rouge">Anaconda</code> or <code class="language-plaintext highlighter-rouge">Miniconda</code>. The difference between the two is that <code class="language-plaintext highlighter-rouge">Anaconda</code> is a complete environment that includes the Python interpreter and all commonly used third-party libraries (such as commonly used <code class="language-plaintext highlighter-rouge">numpy</code>, <code class="language-plaintext highlighter-rouge">pandas</code>, <code class="language-plaintext highlighter-rouge">matplotlib</code>, etc.), while <code class="language-plaintext highlighter-rouge">Miniconda</code> only includes the Python interpreter and does not include any third-party libraries. This can save disk space, and it also comes with conda, which allows you to install environments and packages according to your needs. For novice users, it is recommended to install <code class="language-plaintext highlighter-rouge">Anaconda</code>.</p>

<p>Anaconda download link: https://www.anaconda.com/download/</p>

<p>Miniconda download link: https://docs.conda.io/en/latest/miniconda.html</p>

<p>Simply select the installation package corresponding to the operating system. It is recommended to install the 64-bit version. After installation, when you open a command prompt window, a default environment called <code class="language-plaintext highlighter-rouge">base</code> will be automatically launched. This environment is the default environment of <code class="language-plaintext highlighter-rouge">Anaconda</code> or <code class="language-plaintext highlighter-rouge">Miniconda</code>. If you don’t want <code class="language-plaintext highlighter-rouge">base</code> to be automatically launched every time, you can run the following command in the command prompt window:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda config <span class="nt">--set</span> auto_activate_base <span class="nb">false</span>
</code></pre></div></div>

<p>View all conda environments:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda <span class="nb">env </span>list
</code></pre></div></div>

<p>After running the above command, all conda environments will be displayed. If you haven’t created an environment yet, only the <code class="language-plaintext highlighter-rouge">base</code> environment will be displayed.</p>

<p>Create a new environment:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda create <span class="nt">-n</span> my_env
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">my_env</code> is the name of the environment, which can be arbitrarily specified. If you want to create a new environment and specify the Python version, you can do it like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda create <span class="nt">-n</span> my_env <span class="nv">python</span><span class="o">=</span>3.10
</code></pre></div></div>

<p>The above command will create an environment named <code class="language-plaintext highlighter-rouge">my_env</code> and install Python 3.10 in the environment. Activate the environment just created:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda activate my_env
</code></pre></div></div>

<p>If this environment will no longer be used, you can completely delete the environment with the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda remove <span class="nt">--name</span> my_env <span class="nt">--all</span>
</code></pre></div></div>

<p>The above commands can be used for basic environment management. For more environment management commands, please refer to conda’s <a href="https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html">official environment management documentation</a>.</p>

<p>In addition to environment management, <code class="language-plaintext highlighter-rouge">conda</code> can also perform package management. Package management refers to the installation, upgrade, deletion, etc. of packages. To install a package with conda, you can run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda <span class="nb">install</span> <span class="nt">-c</span> conda-forge numpy
</code></pre></div></div>

<p>The above command will install the <code class="language-plaintext highlighter-rouge">numpy</code> package in the current environment. <code class="language-plaintext highlighter-rouge">-c conda-forge</code> indicates downloading from conda’s official repository, and <code class="language-plaintext highlighter-rouge">conda-forge</code> is the name of conda’s official repository.</p>

<p><strong>Note:</strong> Before installing packages with conda, switch to the environment first. For example, if you want to install the <code class="language-plaintext highlighter-rouge">numpy</code> package in the <code class="language-plaintext highlighter-rouge">my_env</code> environment, you need to run</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda activate my_env
</code></pre></div></div>

<p>to activate the <code class="language-plaintext highlighter-rouge">my_env</code> environment, and then run the above command to install the <code class="language-plaintext highlighter-rouge">numpy</code> package in the <code class="language-plaintext highlighter-rouge">my_env</code> environment.</p>

<p><code class="language-plaintext highlighter-rouge">conda</code>’s abilities of managing environment is not limited to Python. You can also create environments for other languages, such as NCAR Command Language (NCL), R, etc. For example, to create an environment for NCL, you can run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda create <span class="nt">-n</span> ncl_stable <span class="nt">-c</span> conda-forge ncl
</code></pre></div></div>

<p>This will create an environment named <code class="language-plaintext highlighter-rouge">ncl_stable</code> and install the <code class="language-plaintext highlighter-rouge">ncl</code> package in the environment. To activate the environment, run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda activate ncl_stable
</code></pre></div></div>

<p>This is all you need to do to install NCL with conda (so convenient!). Using conda to install NCL is also the recommended way by the NCL official website. For more information, please refer to the <a href="https://www.ncl.ucar.edu/Download/conda.shtml">NCL installation documentation</a>.</p>

<p>For more package management commands, please refer to conda’s <a href="https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-pkgs.html">official package management documentation</a>.</p>

<h3 id="venv">venv</h3>

<p><code class="language-plaintext highlighter-rouge">venv</code> is a library that comes with Python, so if you have already installed the Python interpreter, you can use <code class="language-plaintext highlighter-rouge">venv</code> directly. <code class="language-plaintext highlighter-rouge">venv</code> stands for virtual environment, and it can create and manage virtual environments, which do not affect each other.</p>

<p>To create a virtual environment:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~
<span class="nb">mkdir </span>my_project
python3 <span class="nt">-m</span> venv my_project/my_env
</code></pre></div></div>

<p>The first step <code class="language-plaintext highlighter-rouge">cd ~</code> switches to the home directory (<code class="language-plaintext highlighter-rouge">~</code> represents the home directory), but you can also switch to another directory depending on where you want to store your project. The second step <code class="language-plaintext highlighter-rouge">mkdir my_project</code> creates a folder. The third step <code class="language-plaintext highlighter-rouge">python3 -m venv my_project/my_env</code> creates a virtual environment, and the root directory of the virtual environment is <code class="language-plaintext highlighter-rouge">my_project/my_env</code>. Note: <code class="language-plaintext highlighter-rouge">my_project</code> and <code class="language-plaintext highlighter-rouge">my_env</code> are the names given to the project directory and virtual environment directory, and you can change these two directory names.</p>

<p>After creating the virtual environment, activate it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source </span>my_project/my_env/bin/activate
</code></pre></div></div>

<p>If you run</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>which python
</code></pre></div></div>

<p>You will see the path of the Python interpreter in your virtual environment. In this example, the path is <code class="language-plaintext highlighter-rouge">~/my_project/my_env/bin/python</code>.</p>

<p>To exit the virtual environment, run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deactivate
</code></pre></div></div>

<p>To completely delete the virtual environment, run after deactivating:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-rf</span> my_project/my_env
</code></pre></div></div>

<p>Delete all files in the virtual environment.</p>

<p>To manage packages in the <code class="language-plaintext highlighter-rouge">venv</code> virtual environment, you can only use the <code class="language-plaintext highlighter-rouge">pip</code> command. First, upgrade <code class="language-plaintext highlighter-rouge">pip</code> to the latest version:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> pip <span class="nb">install</span> <span class="nt">--upgrade</span> pip
</code></pre></div></div>

<p>Then run the following command, for example, to install the <code class="language-plaintext highlighter-rouge">numpy</code> package:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> pip <span class="nb">install </span>numpy
</code></pre></div></div>

<p><strong>Note</strong>: You need to activate the virtual environment first to install the <code class="language-plaintext highlighter-rouge">numpy</code> package in the virtual environment. For more information on using <code class="language-plaintext highlighter-rouge">pip</code>, please refer to the <a href="https://pip.pypa.io/en/stable/user_guide/">pip user guide</a>.</p>

<p>If you want to learn more about using virtual environments, you can refer to the <a href="https://docs.python.org/3/library/venv.html">venv section of the Python official documentation</a> and the second reference in this article.</p>

<h2 id="summary">Summary</h2>

<p>This article introduces two commonly used Python environment management tools, <code class="language-plaintext highlighter-rouge">conda</code> and <code class="language-plaintext highlighter-rouge">venv</code>, and you can use either one. Personally, I prefer to use <code class="language-plaintext highlighter-rouge">conda</code> because it is simple and convenient, and <code class="language-plaintext highlighter-rouge">conda</code> can also manage packages. The advantage of <code class="language-plaintext highlighter-rouge">venv</code> is that it is included with Python and does not require the installation of <code class="language-plaintext highlighter-rouge">Anaconda</code> or <code class="language-plaintext highlighter-rouge">Miniconda</code>.</p>

<p>Python environment management is a necessary skill for Python developers. Isolating different Python environments allows different projects to not interfere with each other and can be better maintained.</p>

<h2 id="references">References</h2>

<p>In addition to the links mentioned in the article, other references are as follows:</p>

<ol>
  <li>https://stackoverflow.com/questions/54429210/how-do-i-prevent-conda-from-activating-the-base-environment-by-default</li>
  <li>https://www.dataquest.io/blog/a-complete-guide-to-python-virtual-environments/</li>
</ol>

<h2 id="notes">Notes</h2>

<p>This blog is originally written in Chinese. The English version is translated by <code class="language-plaintext highlighter-rouge">ChatGPT</code>.</p>]]></content><author><name></name></author><category term="Tech" /><category term="Python" /><category term="conda" /><category term="venv" /><summary type="html"><![CDATA[This post mainly introduces how to use `conda` and `venv` to manage Python environments.]]></summary></entry><entry><title type="html">Installation and Setup of CentOS Linux Server</title><link href="https://jinjunliu.com/blog/2016/linux-server-setup/" rel="alternate" type="text/html" title="Installation and Setup of CentOS Linux Server" /><published>2016-03-10T00:00:00+00:00</published><updated>2016-03-10T00:00:00+00:00</updated><id>https://jinjunliu.com/blog/2016/linux-server-setup</id><content type="html" xml:base="https://jinjunliu.com/blog/2016/linux-server-setup/"><![CDATA[<p><strong>Caution: this post was written in 2016, and some of the content may be out of date.</strong></p>

<h2 id="centos-installation">CentOS Installation</h2>

<p>There are many Linux distributions, roughly categorized into CentOS/RHEL/Fedora and Ubuntu/Debian based on their package managers. Researchers tend to prefer CentOS. CentOS is a community rebuild of RHEL (Red Hat Enterprise Linux) and can use Red Hat’s software packages while being completely free. The version I installed was CentOS 6.7, and the latest version at the time is 7.x. It is recommended to install the latest version if possible. However, due to my older server configuration, I could only install 6.7.</p>

<p>The installation process of CentOS is similar to that of most Linux systems, and there are many tutorials available online, so I won’t go into detail here. However, as a Linux server suitable for scientific computing, there are a few things to note:</p>

<ul>
  <li>Language and Keyboard: Choose English and English (US).</li>
  <li>Timezone: Select Asia/Shanghai and do not enable UTC time.</li>
  <li>Partitioning: Choose “Create Custom Layout” for manual partitioning. Generally, you need three partitions: <code class="language-plaintext highlighter-rouge">/</code>, <code class="language-plaintext highlighter-rouge">/home</code>, and <code class="language-plaintext highlighter-rouge">Swap</code>. The size of the <code class="language-plaintext highlighter-rouge">Swap</code> partition should be equal to the physical memory (RAM) size. The size of the <code class="language-plaintext highlighter-rouge">/</code> partition depends on your hard disk size, but it is recommended to be larger than 20GB. The size of the <code class="language-plaintext highlighter-rouge">/home</code> partition can be arbitrary but should not be too small.</li>
  <li>Installation Version: Choose “Software Development Workstation” as this version includes most of the software development environment, saving you the trouble of manual installation.</li>
  <li>During customization, select “Scientific support” under Base System and “Chinese Support” under Languages.</li>
</ul>

<p>Once these settings are completed, you can proceed with the installation. Finally, uncheck “Enable Kdump.” I want to explain here that Kdump is used to store the contents of memory on the hard disk when the system crashes, making it easier for administrators to analyze the cause of the crash based on the memory contents. However, for regular users, Kdump does not have much use because they usually cannot understand the memory contents.</p>

<h2 id="post-installation-setup">Post-Installation Setup</h2>

<p>After the installation, several settings need to be made on the system.</p>

<h3 id="modify-software-repositories">Modify Software Repositories</h3>

<p>Change the software repository to the USTC (University of Science and Technology of China) mirror. This has at least two advantages:</p>

<ol>
  <li>Faster download speed within the university network.</li>
  <li>Software installation or updates can be completed without an external internet connection.</li>
</ol>

<p>Refer to: <a href="https://lug.ustc.edu.cn/wiki/mirrors/help/centos">https://lug.ustc.edu.cn/wiki/mirrors/help/centos</a></p>

<h3 id="add-third-party-repository-epel">Add Third-Party Repository EPEL</h3>

<p>Refer to: <a href="https://lug.ustc.edu.cn/wiki/mirrors/help/epel">https://lug.ustc.edu.cn/wiki/mirrors/help/epel</a></p>

<h3 id="set-automatic-network-connection-on-startup">Set Automatic Network Connection on Startup</h3>

<p>Click on the network icon in the top right corner, select the network, and check “Connect Automatically.”</p>

<h3 id="add-users">Add Users</h3>

<p>If the server is used by multiple people, in addition to the users created during the system installation, additional users need to be created. Assuming we want to add a user named “jack,” the following commands can be used:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>su -
adduser jack <span class="c"># Create a new account "jack"</span>
passwd jack <span class="c"># Set the password for "jack"</span>
</code></pre></div></div>

<h3 id="add-regular-user-to-the-root-group">Add Regular User to the Root Group</h3>

<p>This is done to grant root privileges to a regular user. This allows us to perform operations that require root privileges without switching to the root account. Assuming the account name is “jjliu” and we want to add it to the root user group, we need to modify the <code class="language-plaintext highlighter-rouge">/etc/sudoers</code> file. Use the following commands:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>su -
<span class="nb">echo</span> <span class="s1">'jjliu ALL=(ALL) ALL'</span> <span class="o">&gt;&gt;</span> /etc/sudoers <span class="c"># Add the statement to the configuration file</span>
<span class="nb">tail</span> <span class="nt">-1</span> /etc/sudoers  <span class="c"># Check if it is correct</span>
jjliu <span class="nv">ALL</span><span class="o">=(</span>ALL<span class="o">)</span> ALL
</code></pre></div></div>

<p>However, on machines used by multiple users, it is generally not recommended to grant root privileges to regular accounts other than the administrator’s account. So please refrain from making this setting.</p>

<h3 id="initial-comprehensive-update">Initial Comprehensive Update</h3>

<p>The command is:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>yum update
</code></pre></div></div>

<h2 id="mounting-hard-drives">Mounting Hard Drives</h2>

<p>Generally, when installing an operating system, not all hard drives are mounted automatically. The remaining hard drives need to be mounted manually. First, let’s check the drive letters of all the hard drives:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> /dev/sd<span class="k">*</span>
</code></pre></div></div>

<p>Assuming the output is as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/sda   /dev/sda2  /dev/sda4  /dev/sdb   /dev/sdc
/dev/sda1  /dev/sda3  /dev/sda5  /dev/sdb1  /dev/sdc1
</code></pre></div></div>

<p>From this, we can see that there are a total of 3 hard drives: sda, sdb, and sdc. sda has 5 partitions (sda1~sda5), while sdb and sdc have only one partition each (sdb1 and sdc1). If the partitions on sda are the ones created during the system installation, then we need to mount sdb1 and sdc1.</p>

<p>The mounting process is as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> /d0 <span class="c"># Create the /d0 directory as the mount point for sdb1</span>
<span class="nb">sudo mkdir</span> /d1 <span class="c"># Create the /d1 directory as the mount point for sdc1</span>
<span class="nb">sudo echo</span> <span class="s1">'mount /dev/sdb1 /d0'</span> <span class="o">&gt;&gt;</span> /etc/rc.d/mount.sh
<span class="nb">sudo echo</span> <span class="s1">'mount /dev/sdc1 /d1'</span> <span class="o">&gt;&gt;</span> /etc/rc.d/mount.sh
<span class="nb">sudo echo</span> <span class="s1">'/bin/sh /etc/rc.d/mount.sh'</span> <span class="o">&gt;&gt;</span> /etc/rc.d/rc.local <span class="c"># Set up automatic mounting at startup</span>
<span class="nb">sudo source</span> /etc/rc.d/rc.local
<span class="nb">df</span> <span class="nt">-h</span> <span class="c"># Check the mounting status</span>
...
/dev/sdb1       903G  364G  493G  43% /d0
/dev/sdc1       1.8T  583G  1.2T  34% /d1
...
</code></pre></div></div>

<p>Now, sdb1 and sdc1 are mounted on /d0 and /d1, respectively. If the server has multiple users, you need to create folders for each user under the respective mount points. Assuming the server has two users, jjliu and jack, the steps are as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /d0/jjliu
<span class="nb">sudo chown</span> <span class="nt">-R</span> jjliu:jjliu jjliu <span class="c"># Change the owner and group of the jjliu folder to jjliu</span>
<span class="nb">sudo chmod </span>700 jjliu <span class="c"># Change the permissions of the jjliu folder to allow only the owner to access and modify</span>
<span class="nb">mkdir</span> /d0/jack
<span class="nb">sudo chown</span> <span class="nt">-R</span> jack:jack jack <span class="c"># Change the owner and group of the jack folder to jack</span>
<span class="nb">sudo chmod </span>700 jack <span class="c"># Change the permissions of the jack folder to allow only the owner to access and modify</span>
</code></pre></div></div>

<p>The same method can be applied to create personal folders under /d1.</p>

<h2 id="installing-screen">Installing screen</h2>

<p>GNU Screen is a software for managing terminal sessions in Linux. It allows users to connect to multiple remote sessions simultaneously and switch between them freely. Screen can also be used to run programs in the background, ensuring that they continue running even if the server loses connection. Installing screen is straightforward:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>yum <span class="nb">install </span>screen
</code></pre></div></div>

<p>To create a new session, simply type <code class="language-plaintext highlighter-rouge">screen</code> in the terminal. To detach the session and run it in the background, use <code class="language-plaintext highlighter-rouge">Ctrl+A+D</code>. This way, if a program is running in the new session, it will continue running in the background even if the connection is lost. To return to the previously created session, first check the screen session number:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>screen <span class="nt">-ls</span>
</code></pre></div></div>

<p>Assuming the output is as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>There is a screen on:
    2073.pts-0.JJLIU        <span class="o">(</span>Detached<span class="o">)</span>
1 Socket <span class="k">in</span> /var/run/screen/S-jjliu.
</code></pre></div></div>

<p>This indicates that the screen session number is 2073. To return to the previous session, use the command <code class="language-plaintext highlighter-rouge">screen -r 2073</code>.</p>

<h2 id="vim-editor-configuration">Vim Editor Configuration</h2>

<p>Vim (Vi Improved) is a commonly used text editor in Linux. By making certain configurations, we can make it more convenient and tailored to our preferences. Add the following lines to <code class="language-plaintext highlighter-rouge">~/.vimrc</code> (create the file if it doesn’t exist):</p>

<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">set</span> <span class="nb">tabstop</span><span class="p">=</span><span class="m">4</span> " Set the length of the <span class="k">tab</span> <span class="nb">key</span> <span class="k">to</span> four spaces <span class="p">(</span>default <span class="k">tab</span> length <span class="k">is</span> too long<span class="p">)</span>
<span class="k">set</span> <span class="k">nu</span><span class="p">!</span> " Enable <span class="nb">line</span> numbering
<span class="k">set</span> <span class="nb">viminfo</span><span class="p">=</span>'<span class="m">1000</span><span class="p">,&lt;</span><span class="m">800</span> " Solve the issue of <span class="k">only</span> being able <span class="k">to</span> <span class="nb">paste</span> <span class="m">50</span> <span class="nb">lines</span> <span class="k">in</span> Vim
</code></pre></div></div>

<h2 id="command-line-terminal-color-and-title-configuration">Command Line Terminal Color and Title Configuration</h2>

<p>You can modify the color and title of the command line terminal to make it more convenient and comfortable to use. For RHEL/CentOS, use the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"export PS1='</span><span class="se">\[\e</span><span class="s2">]0;</span><span class="se">\w\a\]\n\[\e</span><span class="s2">[1;32m</span><span class="se">\]</span><span class="s2">[</span><span class="se">\u</span><span class="s2">@</span><span class="se">\h</span><span class="s2">] </span><span class="se">\[\e</span><span class="s2">[33m</span><span class="se">\]\w\]\e</span><span class="s2">[0m</span><span class="se">\]\n\$</span><span class="s2"> '"</span> <span class="o">&gt;&gt;</span> ~/.bashrc
<span class="nb">.</span> ~/.bashrc
</code></pre></div></div>

<p>For Debian/Ubuntu, use the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"export PS1='</span><span class="se">\[\e</span><span class="s2">]0;</span><span class="se">\[\e</span><span class="s2">[1;32m</span><span class="se">\]</span><span class="k">${</span><span class="nv">debian_chroot</span>:+<span class="p">(</span><span class="nv">$debian_chroot</span><span class="p">)</span><span class="k">}</span><span class="se">\u</span><span class="s2">@</span><span class="se">\h</span><span class="s2">: </span><span class="se">\[\e</span><span class="s2">[1;33m</span><span class="se">\]\w\e</span><span class="s2">[0m</span><span class="se">\n\"</span><span class="s2"> &gt;&gt; ~/.bashrc
. ~/.bashrc
</span></code></pre></div></div>

<h2 id="references">References</h2>

<ul>
  <li>http://blog.csdn.net/chen198746/article/details/9342483</li>
</ul>]]></content><author><name></name></author><category term="Tech" /><category term="Linux" /><category term="CentOS" /><summary type="html"><![CDATA[This post documents some considerations during the installation of CentOS and provides instructions for post-installation setup, including software installation.]]></summary></entry></feed>