<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title><![CDATA[Eric Guo's blog.cloud-mes.com]]></title>
  <link href="https://blog.cloud-mes.com/atom.xml" rel="self"/>
  <link href="https://blog.cloud-mes.com"/>
  <updated>2026-08-19T01:08:41+08:00</updated>
  <id>https://blog.cloud-mes.com</id>
  <author>
    <name><![CDATA[Eric Guo]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
    <entry>
      




<title type="html"><![CDATA[Upgrade PostgreSQL 16.x to 17.x on Rocky Linux 9.2]]></title>
<link href="https://blog.cloud-mes.com/2026/07/12/upgrade-postgresql-16-dot-x-to-17-dot-x-on-rocky-linux-9-dot-2/"/>
<updated>2026-07-12T16:54:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-07-12-upgrade-postgresql-16-dot-x-to-17-dot-x-on-rocky-linux-9-dot-2.markdown</id>
<category term="pgvector" /><category term="pgxn" /><category term="PostgreSQL" /><category term="Rocky" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-3:56">This guide upgrades a dedicated Rocky Linux 9.2 server from PostgreSQL 16.x to
17.x in place with <code data-sourcepos="2:20-2:31">pg_upgrade</code>. PostgreSQL 17 is installed alongside PostgreSQL
16, and the old data directory is retained for rollback.</p>
<p data-sourcepos="5:1-7:45">The commands use <code data-sourcepos="5:18-5:25">--copy</code>, not <code data-sourcepos="5:32-5:39">--link</code>. Copy mode requires enough free disk
space for another copy of the cluster, but PostgreSQL 16 remains usable until
PostgreSQL 17 receives new production writes.</p>
<p data-sourcepos="9:1-10:11">The paths below match installations made with the official PostgreSQL Yum
repository:</p>
<figure class='code'><figcaption>Set the PostgreSQL upgrade paths<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">export</span><span class="w"> </span><span class="nv">OLD_BIN</span><span class="o">=</span>/usr/pgsql-16/bin
</div><div class='line'><span class="nb">export</span><span class="w"> </span><span class="nv">NEW_BIN</span><span class="o">=</span>/usr/pgsql-17/bin
</div><div class='line'><span class="nb">export</span><span class="w"> </span><span class="nv">OLD_DATA</span><span class="o">=</span>/var/lib/pgsql/16/data
</div><div class='line'><span class="nb">export</span><span class="w"> </span><span class="nv">NEW_DATA</span><span class="o">=</span>/var/lib/pgsql/17/data
</div><div class='line'><span class="nb">export</span><span class="w"> </span><span class="nv">UPGRADE_DIR</span><span class="o">=</span>/var/lib/pgsql/upgrade-16-to-17
</div></pre></td></table></div></figure>
<p data-sourcepos="20:1-20:56">Adjust them if your current server uses different paths.</p>
<h2 data-sourcepos="22:1-22:41">1. Inventory the PostgreSQL 16 cluster</h2>
<p data-sourcepos="24:1-24:47">Confirm the running version and data directory:</p>
<figure class='code'><figcaption>Check the PostgreSQL 16 version and data directory<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-XAtc<span class="w"> </span><span class="s2">&quot;SELECT version();&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-XAtc<span class="w"> </span><span class="s2">&quot;SHOW data_directory;&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="31:1-32:26">Record the cluster encoding, locale, and checksum setting. The new cluster must
be initialized compatibly.</p>
<figure class='code'><figcaption>Check encoding, locale, and data checksums<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-X<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;</span>
</div><div class='line'><span class="s2">SELECT current_setting(&#39;server_encoding&#39;) AS encoding,</span>
</div><div class='line'><span class="s2">       current_setting(&#39;lc_collate&#39;) AS lc_collate,</span>
</div><div class='line'><span class="s2">       current_setting(&#39;lc_ctype&#39;) AS lc_ctype;</span>
</div><div class='line'><span class="s2">&quot;</span>
</div><div class='line'> </div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/pg_controldata&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-E<span class="w"> </span><span class="s2">&quot;Database cluster state|Data page checksum version&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="44:1-45:8">Check tablespaces because their data also needs space during a copy-mode
upgrade:</p>
<figure class='code'><figcaption>List PostgreSQL tablespaces<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-X<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;</span>
</div><div class='line'><span class="s2">SELECT spcname, pg_tablespace_location(oid)</span>
</div><div class='line'><span class="s2">FROM pg_tablespace</span>
</div><div class='line'><span class="s2">ORDER BY spcname;</span>
</div><div class='line'><span class="s2">&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="55:1-55:46">List extensions in every connectable database:</p>
<figure class='code'><figcaption>List extensions in every PostgreSQL database<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-iu<span class="w"> </span>postgres<span class="w"> </span>bash<span class="w"> </span><span class="s">&lt;&lt;&#39;EOF&#39;</span>
</div><div class='line'><span class="s">while IFS= read -r database; do</span>
</div><div class='line'><span class="s">  echo &quot;=== Database: $database ===&quot;</span>
</div><div class='line'><span class="s">  /usr/pgsql-16/bin/psql -X -d &quot;$database&quot; -c \</span>
</div><div class='line'><span class="s">    &quot;SELECT extname, extversion FROM pg_extension ORDER BY extname;&quot;</span>
</div><div class='line'><span class="s">done &lt; &lt;(</span>
</div><div class='line'><span class="s">  /usr/pgsql-16/bin/psql -XAtc \</span>
</div><div class='line'><span class="s">    &quot;SELECT datname FROM pg_database WHERE datallowconn AND NOT datistemplate&quot;</span>
</div><div class='line'><span class="s">)</span>
</div><div class='line'><span class="s">EOF</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="70:1-72:69">Install a PostgreSQL 17-compatible build of every extension containing native
code before running <code data-sourcepos="71:21-71:32">pg_upgrade</code>. Do not manually create those extensions in the
empty PostgreSQL 17 cluster; <code data-sourcepos="72:30-72:41">pg_upgrade</code> migrates their definitions.</p>
<h2 data-sourcepos="74:1-74:54">2. Update PostgreSQL 16 to its latest minor release</h2>
<p data-sourcepos="76:1-76:75">First update the existing 16.x packages. Plan a short outage for this step.</p>
<figure class='code'><figcaption>Update PostgreSQL 16 packages<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>stop<span class="w"> </span>postgresql-16
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>upgrade<span class="w"> </span>-y<span class="w"> </span><span class="s1">&#39;postgresql16*&#39;</span><span class="w"> </span><span class="s1">&#39;pgvector_16*&#39;</span>
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>start<span class="w"> </span>postgresql-16
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>status<span class="w"> </span>postgresql-16<span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="85:1-85:32">Verify that the server is ready:</p>
<figure class='code'><figcaption>Verify the updated PostgreSQL 16 server<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-XAtc<span class="w"> </span><span class="s2">&quot;SELECT version();&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/pg_isready&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="92:1-93:61">If pgvector was installed from PGXN rather than an RPM, omit <code data-sourcepos="92:62-92:75">pgvector_16*</code> and
update it using the same installation method originally used.</p>
<h2 data-sourcepos="95:1-95:18">3. Take backups</h2>
<p data-sourcepos="97:1-98:10">Stop here if there is no tested backup. Create a logical backup of all databases
and roles:</p>
<figure class='code'><figcaption>Back up all PostgreSQL 16 databases and roles<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>install<span class="w"> </span>-d<span class="w"> </span>-m<span class="w"> </span><span class="m">700</span><span class="w"> </span>/var/backups/postgresql
</div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/pg_dumpall&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="p">|</span><span class="w"> </span>gzip<span class="w"> </span>&gt;<span class="w"> </span><span class="s2">&quot;/var/backups/postgresql/pgsql16-before-pg17-</span><span class="k">$(</span>date<span class="w"> </span>+%Y%m%d-%H%M%S<span class="k">)</span><span class="s2">.sql.gz&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="106:1-106:32">Back up the configuration files:</p>
<figure class='code'><figcaption>Back up the PostgreSQL 16 configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>install<span class="w"> </span>-d<span class="w"> </span>-m<span class="w"> </span><span class="m">700</span><span class="w"> </span>/root/postgresql16-config-backup
</div><div class='line'>sudo<span class="w"> </span>cp<span class="w"> </span>-a<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/postgresql.conf&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/postgresql.auto.conf&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/pg_hba.conf&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/pg_ident.conf&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>/root/postgresql16-config-backup
</div></pre></td></table></div></figure>
<p data-sourcepos="118:1-118:66">Check disk space for the data directory and every user tablespace:</p>
<figure class='code'><figcaption>Check data size and available disk space<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>du<span class="w"> </span>-sh<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>df<span class="w"> </span>-h<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="125:1-126:26">A filesystem or VM snapshot taken while PostgreSQL is stopped provides an
additional rollback layer.</p>
<h2 data-sourcepos="128:1-128:42">4. Install PostgreSQL 17 and extensions</h2>
<p data-sourcepos="130:1-130:46">Install PostgreSQL 17 alongside PostgreSQL 16:</p>
<figure class='code'><figcaption>Install PostgreSQL 17 packages<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>postgresql17-server<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>postgresql17-contrib<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>postgresql17-devel
</div></pre></td></table></div></figure>
<p data-sourcepos="139:1-140:22">If pgvector was installed from the PostgreSQL Yum repository, install its
PostgreSQL 17 package:</p>
<figure class='code'><figcaption>Install pgvector for PostgreSQL 17<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>pgvector_17
</div></pre></td></table></div></figure>
<p data-sourcepos="146:1-148:26">Prefer the RPM package when available and do not install pgvector with both RPM
and PGXN. If an extension is available only through PGXN, select the PostgreSQL
17 <code data-sourcepos="148:4-148:14">pg_config</code> explicitly:</p>
<figure class='code'><figcaption>Install a PGXN extension for PostgreSQL 17<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>pgxnclient<span class="w"> </span>install<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--pg_config<span class="w"> </span>/usr/pgsql-17/bin/pg_config<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>vector
</div></pre></td></table></div></figure>
<p data-sourcepos="156:1-156:41">Confirm that both versions are installed:</p>
<figure class='code'><figcaption>Check the installed PostgreSQL binaries<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/postgres&quot;</span><span class="w"> </span>--version
</div><div class='line'><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/postgres&quot;</span><span class="w"> </span>--version
</div><div class='line'><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/pg_upgrade&quot;</span><span class="w"> </span>--version
</div></pre></td></table></div></figure>
<h2 data-sourcepos="164:1-164:47">5. Initialize an empty PostgreSQL 17 cluster</h2>
<p data-sourcepos="166:1-168:14">Initialize the new cluster with the encoding and locale recorded earlier. Based
on the original Rocky Linux installation, these are likely <code data-sourcepos="167:60-167:65">UTF8</code> and
<code data-sourcepos="168:1-168:13">en_US.UTF-8</code>:</p>
<figure class='code'><figcaption>Initialize the PostgreSQL 17 cluster<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>install<span class="w"> </span>-d<span class="w"> </span>-o<span class="w"> </span>postgres<span class="w"> </span>-g<span class="w"> </span>postgres<span class="w"> </span>-m<span class="w"> </span><span class="m">700</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/initdb&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--pgdata<span class="o">=</span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--encoding<span class="o">=</span>UTF8<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--locale<span class="o">=</span>en_US.UTF-8
</div></pre></td></table></div></figure>
<p data-sourcepos="178:1-179:57">If <code data-sourcepos="178:4-178:19">pg_controldata</code> reported checksum version <code data-sourcepos="178:47-178:49">1</code>, remove the newly initialized
empty directory and initialize it with checksums instead:</p>
<figure class='code'><figcaption>Initialize PostgreSQL 17 with data checksums<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>rm<span class="w"> </span>-rf<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>install<span class="w"> </span>-d<span class="w"> </span>-o<span class="w"> </span>postgres<span class="w"> </span>-g<span class="w"> </span>postgres<span class="w"> </span>-m<span class="w"> </span><span class="m">700</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/initdb&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--pgdata<span class="o">=</span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--encoding<span class="o">=</span>UTF8<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--locale<span class="o">=</span>en_US.UTF-8<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--data-checksums
</div></pre></td></table></div></figure>
<p data-sourcepos="191:1-192:40">Only remove <code data-sourcepos="191:13-191:22">NEW_DATA</code> at this point, while it is a newly initialized empty
cluster. Do not start PostgreSQL 17 yet.</p>
<h2 data-sourcepos="194:1-194:45">6. Stop database clients and PostgreSQL 16</h2>
<p data-sourcepos="196:1-197:33">Stop applications, scheduled jobs, backup jobs, monitoring, and connection
pools. Review remaining sessions:</p>
<figure class='code'><figcaption>Check active PostgreSQL client connections<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-X<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;</span>
</div><div class='line'><span class="s2">SELECT pid, usename, datname, application_name, client_addr, state</span>
</div><div class='line'><span class="s2">FROM pg_stat_activity</span>
</div><div class='line'><span class="s2">WHERE pid &lt;&gt; pg_backend_pid()</span>
</div><div class='line'><span class="s2">ORDER BY datname, usename;</span>
</div><div class='line'><span class="s2">&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="208:1-208:28">Stop both database services:</p>
<figure class='code'><figcaption>Stop PostgreSQL 16 and PostgreSQL 17<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>stop<span class="w"> </span>postgresql-16
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>stop<span class="w"> </span>postgresql-17<span class="w"> </span><span class="m">2</span>&gt;/dev/null<span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nb">true</span>
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>is-active<span class="w"> </span>postgresql-16
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>is-active<span class="w"> </span>postgresql-17
</div></pre></td></table></div></figure>
<p data-sourcepos="217:1-217:39">Both services should report <code data-sourcepos="217:29-217:38">inactive</code>.</p>
<h2 data-sourcepos="219:1-219:33">7. Run the compatibility check</h2>
<p data-sourcepos="221:1-222:35">Create a working directory owned by <code data-sourcepos="221:37-221:46">postgres</code>, then run the PostgreSQL 17
<code data-sourcepos="222:1-222:12">pg_upgrade</code> binary with <code data-sourcepos="222:26-222:34">--check</code>:</p>
<figure class='code'><figcaption>Check PostgreSQL 16 and 17 cluster compatibility<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>install<span class="w"> </span>-d<span class="w"> </span>-o<span class="w"> </span>postgres<span class="w"> </span>-g<span class="w"> </span>postgres<span class="w"> </span>-m<span class="w"> </span><span class="m">700</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$UPGRADE_DIR</span><span class="s2">&quot;</span>
</div><div class='line'> </div><div class='line'>sudo<span class="w"> </span>-iu<span class="w"> </span>postgres<span class="w"> </span>bash<span class="w"> </span><span class="s">&lt;&lt;&#39;EOF&#39;</span>
</div><div class='line'><span class="s">cd /var/lib/pgsql/upgrade-16-to-17</span>
</div><div class='line'><span class="s">/usr/pgsql-17/bin/pg_upgrade \</span>
</div><div class='line'><span class="s">  --check \</span>
</div><div class='line'><span class="s">  --old-bindir=/usr/pgsql-16/bin \</span>
</div><div class='line'><span class="s">  --new-bindir=/usr/pgsql-17/bin \</span>
</div><div class='line'><span class="s">  --old-datadir=/var/lib/pgsql/16/data \</span>
</div><div class='line'><span class="s">  --new-datadir=/var/lib/pgsql/17/data \</span>
</div><div class='line'><span class="s">  --username=postgres</span>
</div><div class='line'><span class="s">EOF</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="238:1-238:43">Do not continue unless the final result is:</p>
<figure class='code'><figcaption>Expected pg_upgrade compatibility result<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>Clusters are compatible
</div></pre></td></table></div></figure>
<p data-sourcepos="244:1-245:73">For example, an error about not loading <code data-sourcepos="244:41-244:56">$libdir/vector</code> means the PostgreSQL 17
pgvector library is missing. Install <code data-sourcepos="245:38-245:50">pgvector_17</code>, then rerun the check.</p>
<h2 data-sourcepos="247:1-247:38">8. Run the PostgreSQL major upgrade</h2>
<p data-sourcepos="249:1-249:34">Run the real upgrade in copy mode:</p>
<figure class='code'><figcaption>Upgrade PostgreSQL 16 to PostgreSQL 17<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-iu<span class="w"> </span>postgres<span class="w"> </span>bash<span class="w"> </span><span class="s">&lt;&lt;&#39;EOF&#39;</span>
</div><div class='line'><span class="s">cd /var/lib/pgsql/upgrade-16-to-17</span>
</div><div class='line'><span class="s">/usr/pgsql-17/bin/pg_upgrade \</span>
</div><div class='line'><span class="s">  --old-bindir=/usr/pgsql-16/bin \</span>
</div><div class='line'><span class="s">  --new-bindir=/usr/pgsql-17/bin \</span>
</div><div class='line'><span class="s">  --old-datadir=/var/lib/pgsql/16/data \</span>
</div><div class='line'><span class="s">  --new-datadir=/var/lib/pgsql/17/data \</span>
</div><div class='line'><span class="s">  --username=postgres \</span>
</div><div class='line'><span class="s">  --jobs=&quot;$(nproc)&quot; \</span>
</div><div class='line'><span class="s">  --copy</span>
</div><div class='line'><span class="s">EOF</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="265:1-267:65">Do not use <code data-sourcepos="265:12-265:22">--no-sync</code> on a production server. Read the complete output and run
any required extension-update or index-rebuild scripts exactly as printed. Keep
the generated old-cluster deletion script, but do not run it yet.</p>
<h2 data-sourcepos="269:1-269:27">9. Migrate configuration</h2>
<p data-sourcepos="271:1-272:79">Do not overwrite PostgreSQL 17's entire <code data-sourcepos="271:41-271:57">postgresql.conf</code> with the PostgreSQL 16
file. Compare the files and manually reapply custom settings that remain valid:</p>
<figure class='code'><figcaption>Compare PostgreSQL 16 and 17 configuration files<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>diff<span class="w"> </span>-u<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">/postgresql.conf&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/postgresql.conf&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="p">|</span><span class="w"> </span>less
</div></pre></td></table></div></figure>
<p data-sourcepos="281:1-283:68">Review settings such as <code data-sourcepos="281:25-281:42">listen_addresses</code>, <code data-sourcepos="281:45-281:50">port</code>, memory limits, WAL settings,
logging, SSL, and <code data-sourcepos="282:19-282:44">shared_preload_libraries</code>. Any library named in
<code data-sourcepos="283:1-283:26">shared_preload_libraries</code> must also be installed for PostgreSQL 17.</p>
<p data-sourcepos="285:1-285:68">The client authentication files can normally be copied after review:</p>
<figure class='code'><figcaption>Copy PostgreSQL client authentication configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>cp<span class="w"> </span>-a<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/pg_hba.conf&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">/pg_hba.conf&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>cp<span class="w"> </span>-a<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/pg_ident.conf&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">/pg_ident.conf&quot;</span>
</div><div class='line'>sudo<span class="w"> </span>chown<span class="w"> </span>postgres:postgres<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">/pg_hba.conf&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">/pg_ident.conf&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="295:1-296:9">Review <code data-sourcepos="295:8-295:29">postgresql.auto.conf</code> separately and reapply only valid PostgreSQL 17
settings:</p>
<figure class='code'><figcaption>Review automatically configured PostgreSQL settings<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>cat<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$OLD_DATA</span><span class="s2">/postgresql.auto.conf&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="302:1-302:59">Check key configuration values before starting the service:</p>
<figure class='code'><figcaption>Validate the PostgreSQL 17 configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/postgres&quot;</span><span class="w"> </span>-D<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">&quot;</span><span class="w"> </span>-C<span class="w"> </span>data_directory
</div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/postgres&quot;</span><span class="w"> </span>-D<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_DATA</span><span class="s2">&quot;</span><span class="w"> </span>-C<span class="w"> </span>listen_addresses
</div></pre></td></table></div></figure>
<h2 data-sourcepos="309:1-309:37">10. Start and verify PostgreSQL 17</h2>
<p data-sourcepos="311:1-311:51">Switch the enabled service and start PostgreSQL 17:</p>
<figure class='code'><figcaption>Enable and start PostgreSQL 17<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>disable<span class="w"> </span>postgresql-16
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>postgresql-17
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>start<span class="w"> </span>postgresql-17
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>status<span class="w"> </span>postgresql-17<span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="320:1-320:65">Verify the server identity, data directory, encoding, and locale:</p>
<figure class='code'><figcaption>Verify the upgraded PostgreSQL 17 cluster<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-X<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;</span>
</div><div class='line'><span class="s2">SELECT version();</span>
</div><div class='line'><span class="s2">SHOW data_directory;</span>
</div><div class='line'><span class="s2">SHOW server_encoding;</span>
</div><div class='line'><span class="s2">SHOW lc_collate;</span>
</div><div class='line'><span class="s2">SHOW lc_ctype;</span>
</div><div class='line'><span class="s2">&quot;</span>
</div><div class='line'> </div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/pg_isready&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="333:1-333:37">Check that all databases are present:</p>
<figure class='code'><figcaption>List upgraded PostgreSQL databases and sizes<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-X<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;</span>
</div><div class='line'><span class="s2">SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size</span>
</div><div class='line'><span class="s2">FROM pg_database</span>
</div><div class='line'><span class="s2">WHERE datallowconn</span>
</div><div class='line'><span class="s2">ORDER BY datname;</span>
</div><div class='line'><span class="s2">&quot;</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="344:1-344:24">11. Update extensions</h2>
<p data-sourcepos="346:1-347:29">First run any extension-update script produced by <code data-sourcepos="346:51-346:62">pg_upgrade</code>. Then list the
installed extension versions:</p>
<figure class='code'><figcaption>Check extensions after the PostgreSQL upgrade<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-iu<span class="w"> </span>postgres<span class="w"> </span>bash<span class="w"> </span><span class="s">&lt;&lt;&#39;EOF&#39;</span>
</div><div class='line'><span class="s">while IFS= read -r database; do</span>
</div><div class='line'><span class="s">  echo &quot;=== Database: $database ===&quot;</span>
</div><div class='line'><span class="s">  /usr/pgsql-17/bin/psql -X -d &quot;$database&quot; -c \</span>
</div><div class='line'><span class="s">    &quot;SELECT extname, extversion FROM pg_extension ORDER BY extname;&quot;</span>
</div><div class='line'><span class="s">done &lt; &lt;(</span>
</div><div class='line'><span class="s">  /usr/pgsql-17/bin/psql -XAtc \</span>
</div><div class='line'><span class="s">    &quot;SELECT datname FROM pg_database WHERE datallowconn AND NOT datistemplate&quot;</span>
</div><div class='line'><span class="s">)</span>
</div><div class='line'><span class="s">EOF</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="362:1-362:46">Update pgvector in each database that uses it:</p>
<figure class='code'><figcaption>Update and verify the pgvector extension<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--dbname<span class="o">=</span>your_database<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--command<span class="o">=</span><span class="s2">&quot;ALTER EXTENSION vector UPDATE;&quot;</span>
</div><div class='line'> </div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--dbname<span class="o">=</span>your_database<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--command<span class="o">=</span><span class="s2">&quot;SELECT extversion FROM pg_extension WHERE extname = &#39;vector&#39;;&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="373:1-373:54">Update other extensions only where they are installed:</p>
<figure class='code'><figcaption>Update PostgreSQL contrib extensions<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  psql'><pre><div class='line'><span></span><span class="k">ALTER</span><span class="w"> </span><span class="k">EXTENSION</span><span class="w"> </span><span class="n">pg_trgm</span><span class="w"> </span><span class="k">UPDATE</span><span class="p">;</span>
</div><div class='line'><span class="go">ALTER EXTENSION btree_gist UPDATE;</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="380:1-380:38">12. Regenerate optimizer statistics</h2>
<p data-sourcepos="382:1-383:46"><code data-sourcepos="382:1-382:12">pg_upgrade</code> does not transfer optimizer statistics. Regenerate them in stages
before returning the server to normal traffic:</p>
<figure class='code'><figcaption>Regenerate PostgreSQL optimizer statistics in stages<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/vacuumdb&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--all<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--analyze-in-stages<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--jobs<span class="o">=</span><span class="s2">&quot;</span><span class="k">$(</span>nproc<span class="k">)</span><span class="s2">&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="392:1-392:55">After normal operation resumes, run a complete analyze:</p>
<figure class='code'><figcaption>Run a complete PostgreSQL analyze<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/vacuumdb&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--all<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--analyze-only<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--jobs<span class="o">=</span><span class="s2">&quot;</span><span class="k">$(</span>nproc<span class="k">)</span><span class="s2">&quot;</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="401:1-401:45">13. Validate before deleting PostgreSQL 16</h2>
<p data-sourcepos="403:1-403:23">Review the service log:</p>
<figure class='code'><figcaption>Check recent PostgreSQL 17 service logs<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>journalctl<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-u<span class="w"> </span>postgresql-17<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--since<span class="w"> </span><span class="s2">&quot;1 hour ago&quot;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="412:1-412:53">Check for invalid indexes in each important database:</p>
<figure class='code'><figcaption>Find invalid PostgreSQL indexes<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>postgres<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$NEW_BIN</span><span class="s2">/psql&quot;</span><span class="w"> </span>-X<span class="w"> </span>-d<span class="w"> </span>your_database<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;</span>
</div><div class='line'><span class="s2">SELECT n.nspname AS schema_name, c.relname AS index_name</span>
</div><div class='line'><span class="s2">FROM pg_index i</span>
</div><div class='line'><span class="s2">JOIN pg_class c ON c.oid = i.indexrelid</span>
</div><div class='line'><span class="s2">JOIN pg_namespace n ON n.oid = c.relnamespace</span>
</div><div class='line'><span class="s2">WHERE NOT i.indisvalid</span>
</div><div class='line'><span class="s2">ORDER BY 1, 2;</span>
</div><div class='line'><span class="s2">&quot;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="425:1-428:56">Validate database and table counts, important row counts, application login,
reads and writes, scheduled jobs, backups, monitoring, connection pools, and
pgvector searches and indexes. Keep the PostgreSQL 16 packages and
<code data-sourcepos="428:1-428:24">/var/lib/pgsql/16/data</code> for an agreed retention period.</p>
<h2 data-sourcepos="430:1-430:37">Roll back before production writes</h2>
<p data-sourcepos="432:1-433:70">Because the upgrade used <code data-sourcepos="432:26-432:33">--copy</code>, PostgreSQL 16 remains unchanged. Before
PostgreSQL 17 receives production writes, rollback is straightforward:</p>
<figure class='code'><figcaption>Roll back to PostgreSQL 16 before new writes<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>stop<span class="w"> </span>postgresql-17
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>disable<span class="w"> </span>postgresql-17
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>postgresql-16
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>start<span class="w"> </span>postgresql-16
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>status<span class="w"> </span>postgresql-16<span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="443:1-445:49">After clients write to PostgreSQL 17, restarting PostgreSQL 16 would discard
those new writes. Stop all clients and plan how to export or reconcile the
PostgreSQL 17 changes before attempting rollback.</p>
<h2 data-sourcepos="447:1-447:16">Final cleanup</h2>
<p data-sourcepos="449:1-450:34">Only after PostgreSQL 17 has passed validation and fresh backups are working,
disable PostgreSQL 16 permanently:</p>
<figure class='code'><figcaption>Disable the old PostgreSQL 16 service<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>disable<span class="w"> </span>postgresql-16
</div></pre></td></table></div></figure>
<p data-sourcepos="456:1-458:8">After the retention period, remove the old packages. Review the DNF transaction
before accepting it so that libraries required by other software are not
removed:</p>
<figure class='code'><figcaption>Remove the old PostgreSQL 16 packages<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>remove<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>postgresql16-server<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>postgresql16-contrib<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>postgresql16-devel
</div></pre></td></table></div></figure>
<p data-sourcepos="467:1-469:56">Finally, delete the old cluster only when you are certain it is no longer needed.
Use the deletion script generated by <code data-sourcepos="468:38-468:49">pg_upgrade</code>, or archive the old data
directory according to your backup and retention policy.</p>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Installing Snell Server on Rocky Linux 9 with Network Bandwidth Limiting]]></title>
<link href="https://blog.cloud-mes.com/2026/07/10/installing-snell-server-on-rocky-linux-9-with-network-bandwidth-limiting/"/>
<updated>2026-07-10T14:27:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-07-10-installing-snell-server-on-rocky-linux-9-with-network-bandwidth-limiting.markdown</id>
<category term="Surge" /><category term="Snell" /><category term="Rocky" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:243">My previous <a data-sourcepos="1:13-1:84" href="https://blog.cloud-mes.com/2020/03/18/install-snell-server-in-centos-7/">Snell installation note</a> was written for CentOS 7 in 2020. Several years later, CentOS 7 is obsolete, predictable network interface names are common, and Snell has moved to version 5.</p>
<p data-sourcepos="3:1-3:204">This guide installs Snell Server on Rocky Linux 9, manages it with systemd, opens the required firewall ports, and optionally limits the server’s outbound network throughput using Linux Traffic Control.</p>
<p data-sourcepos="5:1-5:272">Snell is a lightweight encrypted proxy protocol developed for Surge. The official server is distributed as a single binary with no external runtime dependencies other than glibc. Snell v5 also adds a QUIC proxy mode, which requires the server’s UDP port to be reachable.</p>
<h2 data-sourcepos="7:1-7:55">1. Update Rocky Linux and install the required tools</h2>
<p data-sourcepos="9:1-9:53">Log in as <code data-sourcepos="9:11-9:16">root</code>, or prefix the commands with <code data-sourcepos="9:47-9:52">sudo</code>.</p>
<figure class='code'><figcaption>Update Rocky Linux and install prerequisites<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>dnf<span class="w"> </span>update<span class="w"> </span>-y
</div><div class='line'>dnf<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>wget<span class="w"> </span>unzip<span class="w"> </span>firewalld<span class="w"> </span>iproute-tc
</div></pre></td></table></div></figure>
<p data-sourcepos="16:1-16:85">The <code data-sourcepos="16:5-16:16">iproute-tc</code> package provides the <code data-sourcepos="16:39-16:42">tc</code> command used later for bandwidth limiting.</p>
<p data-sourcepos="18:1-18:30">Enable and start the firewall:</p>
<figure class='code'><figcaption>Enable and start firewalld<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>--now<span class="w"> </span>firewalld
</div><div class='line'>systemctl<span class="w"> </span>status<span class="w"> </span>firewalld
</div></pre></td></table></div></figure>
<p data-sourcepos="25:1-25:196">Rocky Linux 9 uses <code data-sourcepos="25:20-25:30">firewalld</code> for common firewall management. Its runtime and permanent configurations are separate, so permanent rules must be explicitly added when they should survive a reboot.</p>
<h2 data-sourcepos="27:1-27:38">2. Create a dedicated Snell account</h2>
<p data-sourcepos="29:1-29:49">Snell does not need an interactive login account:</p>
<figure class='code'><figcaption>Create the non-login Snell service account<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>useradd<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--system<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--home-dir<span class="w"> </span>/opt/snell<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--create-home<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>--shell<span class="w"> </span>/sbin/nologin<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>snell
</div></pre></td></table></div></figure>
<p data-sourcepos="40:1-40:33">Create the application directory:</p>
<figure class='code'><figcaption>Create and secure the Snell application directory<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>mkdir<span class="w"> </span>-p<span class="w"> </span>/opt/snell
</div><div class='line'>chown<span class="w"> </span>snell:snell<span class="w"> </span>/opt/snell
</div><div class='line'>chmod<span class="w"> </span><span class="m">750</span><span class="w"> </span>/opt/snell
</div></pre></td></table></div></figure>
<h2 data-sourcepos="48:1-48:27">3. Download Snell Server</h2>
<p data-sourcepos="50:1-50:74">At the time of writing, the latest official release is Snell Server 5.0.1.</p>
<p data-sourcepos="52:1-52:25">For a typical x86-64 VPS:</p>
<figure class='code'><figcaption>Download Snell Server<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">cd</span><span class="w"> </span>/tmp
</div><div class='line'> </div><div class='line'>wget<span class="w"> </span>https://dl.nssurge.com/snell/snell-server-v5.0.1-linux-amd64.zip
</div><div class='line'> </div><div class='line'>unzip<span class="w"> </span>snell-server-v5.0.1-linux-amd64.zip
</div><div class='line'> </div><div class='line'>install<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-o<span class="w"> </span>snell<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-g<span class="w"> </span>snell<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-m<span class="w"> </span><span class="m">750</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>snell-server<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>/opt/snell/snell-server
</div><div class='line'> </div><div class='line'>rm<span class="w"> </span>-f<span class="w"> </span>snell-server<span class="w"> </span>snell-server-v5.0.1-linux-amd64.zip
</div></pre></td></table></div></figure>
<p data-sourcepos="67:1-67:62">For an ARM64 server, use the official AArch64 package instead:</p>
<figure class='code'><figcaption>Official Snell Server download for ARM64<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>https://dl.nssurge.com/snell/snell-server-v5.0.1-linux-aarch64.zip
</div></pre></td></table></div></figure>
<p data-sourcepos="73:1-73:86">The official Snell page currently provides builds for AMD64, i386, AArch64, and ARMv7.</p>
<p data-sourcepos="75:1-75:29">Confirm that the binary runs:</p>
<figure class='code'><figcaption>Confirm that the binary runs<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>/opt/snell/snell-server<span class="w"> </span>--help
</div></pre></td></table></div></figure>
<h2 data-sourcepos="81:1-81:38">4. Generate the Snell configuration</h2>
<p data-sourcepos="83:1-83:58">Run the built-in configuration wizard as the <code data-sourcepos="83:46-83:52">snell</code> user:</p>
<figure class='code'><figcaption>Run the Snell configuration wizard<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">cd</span><span class="w"> </span>/opt/snell
</div><div class='line'>sudo<span class="w"> </span>-u<span class="w"> </span>snell<span class="w"> </span>./snell-server
</div></pre></td></table></div></figure>
<p data-sourcepos="90:1-90:35">The wizard asks for values such as:</p>
<ul data-sourcepos="92:1-95:14">
<li data-sourcepos="92:1-92:19">Listening address</li>
<li data-sourcepos="93:1-93:16">Listening port</li>
<li data-sourcepos="94:1-94:24">Pre-shared key, or PSK</li>
<li data-sourcepos="95:1-95:14">IPv6 support</li>
</ul>
<p data-sourcepos="97:1-97:33">For example, select port <code data-sourcepos="97:26-97:32">11666</code>.</p>
<p data-sourcepos="99:1-99:53">After the wizard finishes, verify the generated file:</p>
<figure class='code'><figcaption>Inspect the generated Snell configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>cat<span class="w"> </span>/opt/snell/snell-server.conf
</div></pre></td></table></div></figure>
<p data-sourcepos="105:1-105:34">A typical configuration resembles:</p>
<figure class='code'><figcaption>Example Snell Server configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  ini'><pre><div class='line'><span></span><span class="k">[snell-server]</span>
</div><div class='line'><span class="na">listen</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">0.0.0.0:11666</span>
</div><div class='line'><span class="na">psk</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">YOUR_RANDOM_SECRET</span>
</div><div class='line'><span class="na">ipv6</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">false</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="114:1-114:54">Protect the configuration because it contains the PSK:</p>
<figure class='code'><figcaption>Restrict access to the Snell configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>chown<span class="w"> </span>snell:snell<span class="w"> </span>/opt/snell/snell-server.conf
</div><div class='line'>chmod<span class="w"> </span><span class="m">600</span><span class="w"> </span>/opt/snell/snell-server.conf
</div></pre></td></table></div></figure>
<p data-sourcepos="121:1-121:74">Do not publish the real PSK in screenshots, shell history, or a blog post.</p>
<h2 data-sourcepos="123:1-123:32">5. Create the systemd service</h2>
<p data-sourcepos="125:1-125:43">Create <code data-sourcepos="125:8-125:42">/etc/systemd/system/snell.service</code>:</p>
<figure class='code'><figcaption>Create the Snell systemd service file<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div><div data-line='18' class='line-number'></div><div data-line='19' class='line-number'></div><div data-line='20' class='line-number'></div><div data-line='21' class='line-number'></div><div data-line='22' class='line-number'></div><div data-line='23' class='line-number'></div><div data-line='24' class='line-number'></div><div data-line='25' class='line-number'></div><div data-line='26' class='line-number'></div><div data-line='27' class='line-number'></div><div data-line='28' class='line-number'></div><div data-line='29' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>cat<span class="w"> </span>&gt;/etc/systemd/system/snell.service<span class="w"> </span><span class="s">&lt;&lt;&#39;EOF&#39;</span>
</div><div class='line'><span class="s">[Unit]</span>
</div><div class='line'><span class="s">Description=Snell Proxy Service</span>
</div><div class='line'><span class="s">Documentation=https://kb.nssurge.com/surge-knowledge-base/release-notes/snell</span>
</div><div class='line'><span class="s">After=network-online.target</span>
</div><div class='line'><span class="s">Wants=network-online.target</span>
</div><div class='line'> </div><div class='line'><span class="s">[Service]</span>
</div><div class='line'><span class="s">Type=simple</span>
</div><div class='line'><span class="s">User=snell</span>
</div><div class='line'><span class="s">Group=snell</span>
</div><div class='line'><span class="s">WorkingDirectory=/opt/snell</span>
</div><div class='line'> </div><div class='line'><span class="s">ExecStart=/opt/snell/snell-server -c /opt/snell/snell-server.conf</span>
</div><div class='line'> </div><div class='line'><span class="s">Restart=on-failure</span>
</div><div class='line'><span class="s">RestartSec=5s</span>
</div><div class='line'> </div><div class='line'><span class="s">LimitNOFILE=32768</span>
</div><div class='line'> </div><div class='line'><span class="s">NoNewPrivileges=true</span>
</div><div class='line'><span class="s">PrivateTmp=true</span>
</div><div class='line'><span class="s">ProtectHome=true</span>
</div><div class='line'><span class="s">ProtectSystem=strict</span>
</div><div class='line'><span class="s">ReadWritePaths=/opt/snell</span>
</div><div class='line'> </div><div class='line'><span class="s">[Install]</span>
</div><div class='line'><span class="s">WantedBy=multi-user.target</span>
</div><div class='line'><span class="s">EOF</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="153:1-153:31">Reload systemd and start Snell:</p>
<figure class='code'><figcaption>Reload systemd and start Snell<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>daemon-reload
</div><div class='line'>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>--now<span class="w"> </span>snell
</div></pre></td></table></div></figure>
<p data-sourcepos="160:1-160:17">Check its status:</p>
<figure class='code'><figcaption>Check Snell service status<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>status<span class="w"> </span>snell<span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="166:1-166:17">View recent logs:</p>
<figure class='code'><figcaption>View recent Snell service logs<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>journalctl<span class="w"> </span>-u<span class="w"> </span>snell<span class="w"> </span>-n<span class="w"> </span><span class="m">100</span><span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="172:1-172:29">Follow the logs in real time:</p>
<figure class='code'><figcaption>Follow Snell service logs<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>journalctl<span class="w"> </span>-u<span class="w"> </span>snell<span class="w"> </span>-f
</div></pre></td></table></div></figure>
<p data-sourcepos="178:1-178:44">Confirm that the selected port is listening:</p>
<figure class='code'><figcaption>Confirm that the Snell port is listening<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>ss<span class="w"> </span>-lntup<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="m">11666</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="184:1-184:38">6. Open the Snell port in firewalld</h2>
<p data-sourcepos="186:1-186:140">Snell accepts its primary connection over TCP. Snell v5’s QUIC proxy mode additionally uses UDP, so open the same port for both protocols.</p>
<figure class='code'><figcaption>Open the Snell TCP and UDP ports<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>firewall-cmd<span class="w"> </span>--permanent<span class="w"> </span>--zone<span class="o">=</span>public<span class="w"> </span>--add-port<span class="o">=</span><span class="m">11666</span>/tcp
</div><div class='line'>firewall-cmd<span class="w"> </span>--permanent<span class="w"> </span>--zone<span class="o">=</span>public<span class="w"> </span>--add-port<span class="o">=</span><span class="m">11666</span>/udp
</div><div class='line'>firewall-cmd<span class="w"> </span>--reload
</div></pre></td></table></div></figure>
<p data-sourcepos="194:1-194:17">Verify the rules:</p>
<figure class='code'><figcaption>List the configured firewalld ports<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>firewall-cmd<span class="w"> </span>--zone<span class="o">=</span>public<span class="w"> </span>--list-ports
</div></pre></td></table></div></figure>
<p data-sourcepos="200:1-200:31">Expected output should include:</p>
<figure class='code'><figcaption>Expected firewalld port listing<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>11666/tcp 11666/udp
</div></pre></td></table></div></figure>
<p data-sourcepos="206:1-206:117">If the server uses a cloud-provider firewall or security group, the same TCP and UDP ports must also be opened there.</p>
<h2 data-sourcepos="208:1-208:21">7. Configure Surge</h2>
<p data-sourcepos="210:1-210:39">Add a proxy entry to the Surge profile:</p>
<figure class='code'><figcaption>Configure Surge<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  ini'><pre><div class='line'><span></span><span class="k">[Proxy]</span>
</div><div class='line'><span class="na">My-Snell-Server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">snell, YOUR_SERVER_IP, 11666, psk=YOUR_RANDOM_SECRET, version=5</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="217:1-217:8">Replace:</p>
<ul data-sourcepos="219:1-220:61">
<li data-sourcepos="219:1-219:50"><code data-sourcepos="219:3-219:18">YOUR_SERVER_IP</code> with the VPS public IP address.</li>
<li data-sourcepos="220:1-220:61"><code data-sourcepos="220:3-220:22">YOUR_RANDOM_SECRET</code> with the PSK from <code data-sourcepos="220:42-220:60">snell-server.conf</code>.</li>
</ul>
<p data-sourcepos="222:1-222:106">The Snell v5 server remains backward compatible with v4 clients. However, QUIC proxy mode is a v5 feature.</p>
<h2 data-sourcepos="224:1-224:39">8. Limit outbound network throughput</h2>
<p data-sourcepos="226:1-226:153">Some VPS providers charge for excess bandwidth or impose fair-use limits. Linux Traffic Control can apply a maximum outbound rate to a network interface.</p>
<p data-sourcepos="228:1-228:183">Linux <code data-sourcepos="228:7-228:10">tc</code> manages queueing disciplines that schedule packets as they leave an interface. The Token Bucket Filter, or TBF, is suitable for applying a simple maximum transmission rate.</p>
<h3 data-sourcepos="230:1-230:37">Find the public network interface</h3>
<p data-sourcepos="232:1-232:99">Do not assume that the interface is named <code data-sourcepos="232:43-232:48">eth0</code>. Rocky Linux systems frequently use names such as:</p>
<ul data-sourcepos="234:1-237:10">
<li data-sourcepos="234:1-234:8"><code data-sourcepos="234:3-234:8">ens3</code></li>
<li data-sourcepos="235:1-235:9"><code data-sourcepos="235:3-235:9">ens18</code></li>
<li data-sourcepos="236:1-236:10"><code data-sourcepos="236:3-236:10">enp1s0</code></li>
<li data-sourcepos="237:1-237:10"><code data-sourcepos="237:3-237:10">enp0s3</code></li>
</ul>
<p data-sourcepos="239:1-239:45">Find the interface used by the default route:</p>
<figure class='code'><figcaption>Show the default-route network interface<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>ip<span class="w"> </span>route<span class="w"> </span>show<span class="w"> </span>default
</div></pre></td></table></div></figure>
<p data-sourcepos="245:1-245:12">For example:</p>
<figure class='code'><figcaption>Example default-route output<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>default via 192.0.2.1 dev eth0 proto static metric 100
</div></pre></td></table></div></figure>
<p data-sourcepos="251:1-251:41">In this example, the interface is <code data-sourcepos="251:35-251:40">eth0</code>.</p>
<p data-sourcepos="253:1-253:33">You can extract it directly with:</p>
<figure class='code'><figcaption>Extract the default-route interface name<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>ip<span class="w"> </span>route<span class="w"> </span>show<span class="w"> </span>default<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">&#39;{print $5; exit}&#39;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="259:1-259:102">Rocky Linux recommends modern tools such as <code data-sourcepos="259:45-259:48">ip</code> and <code data-sourcepos="259:54-259:60">nmcli</code> for network inspection and configuration.</p>
<h3 data-sourcepos="261:1-261:26">Test the rule manually</h3>
<p data-sourcepos="263:1-263:83">The following command limits outbound traffic on <code data-sourcepos="263:50-263:55">eth0</code> to approximately 36 Mbit/s:</p>
<figure class='code'><figcaption>Apply a 36 Mbit/s outbound TBF limit<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>tc<span class="w"> </span>qdisc<span class="w"> </span>replace<span class="w"> </span>dev<span class="w"> </span>eth0<span class="w"> </span>root<span class="w"> </span>tbf<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>rate<span class="w"> </span>36mbit<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>burst<span class="w"> </span>128kbit<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>latency<span class="w"> </span>200ms
</div></pre></td></table></div></figure>
<p data-sourcepos="272:1-272:37">Check the active queueing discipline:</p>
<figure class='code'><figcaption>Inspect the active TBF queueing discipline<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>tc<span class="w"> </span>-s<span class="w"> </span>qdisc<span class="w"> </span>show<span class="w"> </span>dev<span class="w"> </span>eth0
</div></pre></td></table></div></figure>
<p data-sourcepos="278:1-278:16">Remove the rule:</p>
<figure class='code'><figcaption>Remove the temporary TBF limit<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>tc<span class="w"> </span>qdisc<span class="w"> </span>del<span class="w"> </span>dev<span class="w"> </span>eth0<span class="w"> </span>root
</div></pre></td></table></div></figure>
<p data-sourcepos="284:1-284:174">The limit applies to traffic transmitted through <code data-sourcepos="284:50-284:55">eth0</code>. It therefore affects Snell, SSH, package downloads, web servers, and any other outbound service using that interface.</p>
<p data-sourcepos="286:1-286:183">It does not directly limit inbound traffic. TCP downloads may nevertheless slow down indirectly because acknowledgements and response traffic leave through the rate-limited interface.</p>
<h3 data-sourcepos="288:1-288:34">Persist the limit with systemd</h3>
<p data-sourcepos="290:1-290:46">Create <code data-sourcepos="290:8-290:45">/etc/systemd/system/netlimit.service</code>:</p>
<figure class='code'><figcaption>Create the netlimit systemd service file<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div><div data-line='18' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>cat<span class="w"> </span>&gt;/etc/systemd/system/netlimit.service<span class="w"> </span><span class="s">&lt;&lt;&#39;EOF&#39;</span>
</div><div class='line'><span class="s">[Unit]</span>
</div><div class='line'><span class="s">Description=Limit outbound network throughput</span>
</div><div class='line'><span class="s">After=network-online.target</span>
</div><div class='line'><span class="s">Wants=network-online.target</span>
</div><div class='line'><span class="s">Before=snell.service</span>
</div><div class='line'> </div><div class='line'><span class="s">[Service]</span>
</div><div class='line'><span class="s">Type=oneshot</span>
</div><div class='line'> </div><div class='line'><span class="s">ExecStart=/usr/sbin/tc qdisc replace dev eth0 root tbf rate 36mbit burst 128kbit latency 200ms</span>
</div><div class='line'><span class="s">ExecStop=-/usr/sbin/tc qdisc del dev eth0 root</span>
</div><div class='line'> </div><div class='line'><span class="s">RemainAfterExit=yes</span>
</div><div class='line'> </div><div class='line'><span class="s">[Install]</span>
</div><div class='line'><span class="s">WantedBy=multi-user.target</span>
</div><div class='line'><span class="s">EOF</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="309:1-309:70">Replace <code data-sourcepos="309:9-309:14">eth0</code> with the actual public network interface found earlier.</p>
<p data-sourcepos="311:1-311:223">The use of <code data-sourcepos="311:12-311:20">replace</code>, rather than <code data-sourcepos="311:35-311:39">add</code>, makes the service more tolerant of an existing root queueing discipline. The leading <code data-sourcepos="311:127-311:129">-</code> in <code data-sourcepos="311:134-311:143">ExecStop</code> tells systemd not to treat a missing queueing discipline as a fatal stop error.</p>
<p data-sourcepos="313:1-313:29">Enable and start the service:</p>
<figure class='code'><figcaption>Reload systemd and start netlimit<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>daemon-reload
</div><div class='line'>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>--now<span class="w"> </span>netlimit
</div></pre></td></table></div></figure>
<p data-sourcepos="320:1-320:17">Check its status:</p>
<figure class='code'><figcaption>Check netlimit service status<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>status<span class="w"> </span>netlimit<span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="326:1-326:23">Verify the active rate:</p>
<figure class='code'><figcaption>Verify the configured TBF rate<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>tc<span class="w"> </span>-s<span class="w"> </span>qdisc<span class="w"> </span>show<span class="w"> </span>dev<span class="w"> </span>eth0
</div></pre></td></table></div></figure>
<p data-sourcepos="332:1-332:15">Example output:</p>
<figure class='code'><figcaption>Example TBF queueing-discipline output<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>qdisc tbf 8001: root refcnt 2 rate 36Mbit burst 128Kb lat 200ms
</div></pre></td></table></div></figure>
<p data-sourcepos="338:1-338:53">Restarting the service reapplies the configured rate:</p>
<figure class='code'><figcaption>Restart netlimit to reapply the rate<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>restart<span class="w"> </span>netlimit
</div></pre></td></table></div></figure>
<p data-sourcepos="344:1-344:30">To disable bandwidth limiting:</p>
<figure class='code'><figcaption>Disable the persistent bandwidth limit<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>disable<span class="w"> </span>--now<span class="w"> </span>netlimit
</div></pre></td></table></div></figure>
<p data-sourcepos="350:1-350:39">Then confirm that the TBF rule is gone:</p>
<figure class='code'><figcaption>Confirm that the TBF rule is removed<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>tc<span class="w"> </span>qdisc<span class="w"> </span>show<span class="w"> </span>dev<span class="w"> </span>eth0
</div></pre></td></table></div></figure>
<h2 data-sourcepos="356:1-356:33">9. Testing the bandwidth limit</h2>
<p data-sourcepos="358:1-358:127">The most reliable test is to transfer a sufficiently large file from the Snell server or run a speed test from a remote client.</p>
<p data-sourcepos="360:1-360:29">Remember the unit conversion:</p>
<figure class='code'><figcaption>Convert the configured Mbit/s rate to MB/s<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>36 Mbit/s ÷ 8 = approximately 4.5 MB/s
</div></pre></td></table></div></figure>
<p data-sourcepos="366:1-366:121">Protocol overhead means that the observed application-level transfer speed will normally be slightly lower than 4.5 MB/s.</p>
<p data-sourcepos="368:1-368:45">You can watch the TBF counters while testing:</p>
<figure class='code'><figcaption>Watch TBF counters while testing<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>watch<span class="w"> </span>-n<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="s1">&#39;tc -s qdisc show dev eth0&#39;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="374:1-374:17">Pay attention to:</p>
<ul data-sourcepos="376:1-380:9">
<li data-sourcepos="376:1-376:12">Sent bytes</li>
<li data-sourcepos="377:1-377:14">Sent packets</li>
<li data-sourcepos="378:1-378:17">Dropped packets</li>
<li data-sourcepos="379:1-379:12">Overlimits</li>
<li data-sourcepos="380:1-380:9">Backlog</li>
</ul>
<p data-sourcepos="382:1-382:250">An increasing <code data-sourcepos="382:15-382:26">overlimits</code> counter is normal: it shows that TBF is delaying packets to enforce the configured rate. A large number of dropped packets or a continuously growing backlog may indicate that the <code data-sourcepos="382:207-382:213">burst</code> or <code data-sourcepos="382:218-382:226">latency</code> values need adjustment.</p>
<h2 data-sourcepos="384:1-384:27">10. Maintenance commands</h2>
<p data-sourcepos="386:1-386:14">Restart Snell:</p>
<figure class='code'><figcaption>Restart the Snell service<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>restart<span class="w"> </span>snell
</div></pre></td></table></div></figure>
<p data-sourcepos="392:1-392:12">Check Snell:</p>
<figure class='code'><figcaption>Check Snell service status during maintenance<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>status<span class="w"> </span>snell<span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="398:1-398:10">View logs:</p>
<figure class='code'><figcaption>View Snell logs since today<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>journalctl<span class="w"> </span>-u<span class="w"> </span>snell<span class="w"> </span>--since<span class="w"> </span>today
</div></pre></td></table></div></figure>
<p data-sourcepos="404:1-404:25">Review the configuration:</p>
<figure class='code'><figcaption>Inspect the Snell configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>-u<span class="w"> </span>snell<span class="w"> </span>cat<span class="w"> </span>/opt/snell/snell-server.conf
</div></pre></td></table></div></figure>
<p data-sourcepos="410:1-410:19">Check the firewall:</p>
<figure class='code'><figcaption>List the configured firewall ports<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>firewall-cmd<span class="w"> </span>--zone<span class="o">=</span>public<span class="w"> </span>--list-ports
</div></pre></td></table></div></figure>
<p data-sourcepos="416:1-416:25">Check bandwidth limiting:</p>
<figure class='code'><figcaption>Inspect the active bandwidth-limit rule<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>tc<span class="w"> </span>-s<span class="w"> </span>qdisc<span class="w"> </span>show<span class="w"> </span>dev<span class="w"> </span>eth0
</div></pre></td></table></div></figure>
<p data-sourcepos="422:1-422:35">Check both services after a reboot:</p>
<figure class='code'><figcaption>Check Snell and netlimit after reboot<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>is-active<span class="w"> </span>snell<span class="w"> </span>netlimit
</div><div class='line'>systemctl<span class="w"> </span>is-enabled<span class="w"> </span>snell<span class="w"> </span>netlimit
</div></pre></td></table></div></figure>
<h2 data-sourcepos="429:1-429:29">11. Upgrading Snell Server</h2>
<p data-sourcepos="431:1-431:124">Download and extract the newer binary into a temporary directory, then stop the service and replace the existing executable:</p>
<figure class='code'><figcaption>Upgrading Snell Server<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>systemctl<span class="w"> </span>stop<span class="w"> </span>snell
</div><div class='line'> </div><div class='line'>install<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-o<span class="w"> </span>snell<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-g<span class="w"> </span>snell<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-m<span class="w"> </span><span class="m">750</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>/path/to/new/snell-server<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>/opt/snell/snell-server
</div><div class='line'> </div><div class='line'>systemctl<span class="w"> </span>start<span class="w"> </span>snell
</div><div class='line'>systemctl<span class="w"> </span>status<span class="w"> </span>snell<span class="w"> </span>--no-pager
</div></pre></td></table></div></figure>
<p data-sourcepos="445:1-445:110">The configuration file can normally remain in place, but release notes should be reviewed before each upgrade.</p>
<h2 data-sourcepos="447:1-447:13">Conclusion</h2>
<p data-sourcepos="449:1-449:83">Compared with the old CentOS 7 setup, the Rocky Linux 9 version is mostly familiar:</p>
<ul data-sourcepos="451:1-454:59">
<li data-sourcepos="451:1-451:48">Snell still runs as a small standalone binary.</li>
<li data-sourcepos="452:1-452:39">systemd manages startup and recovery.</li>
<li data-sourcepos="453:1-453:53"><code data-sourcepos="453:3-453:13">firewalld</code> exposes the selected TCP and UDP ports.</li>
<li data-sourcepos="454:1-454:59"><code data-sourcepos="454:3-454:6">tc</code> and TBF provide a simple outbound bandwidth ceiling.</li>
</ul>
<p data-sourcepos="456:1-456:188">The main detail to watch is the network interface name. Copying <code data-sourcepos="456:65-456:70">eth0</code> blindly may cause <code data-sourcepos="456:90-456:107">netlimit.service</code> to fail on servers whose public interface is named <code data-sourcepos="456:160-456:165">ens3</code>, <code data-sourcepos="456:168-456:174">ens18</code>, or <code data-sourcepos="456:180-456:187">enp1s0</code>.</p>
<p data-sourcepos="458:1-458:227">Finally, remember that the simple TBF rule limits the entire network interface. It is suitable for a dedicated Snell VPS. On a shared server, more advanced <code data-sourcepos="458:157-458:160">tc</code> classes and filters would be required to limit only Snell traffic.</p>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Fixing bun run dev Failure in Opencode Desktop]]></title>
<link href="https://blog.cloud-mes.com/2026/06/10/fix-opencode-desktop-bun-run-dev-fails/"/>
<updated>2026-06-10T19:35:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-06-10-fix-opencode-desktop-bun-run-dev-fails.markdown</id>
<category term="Electron" /><category term="Bun" /><category term="opencode" /><category term="troubleshooting" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:264">Today I encountered a classic &quot;works on my machine... wait, no it doesn't&quot; moment while setting up the Opencode Desktop project. Running <code data-sourcepos="1:138-1:150">bun run dev</code> failed with a cryptic <code data-sourcepos="1:174-1:200">Error: Electron uninstall</code> message. Here's the full story of how I diagnosed and fixed it.</p>
<!-- more -->
<h2 data-sourcepos="5:1-5:14">The Symptom</h2>
<p data-sourcepos="7:1-7:144">Running <code data-sourcepos="7:9-7:21">bun run dev</code> in the <code data-sourcepos="7:30-7:47">packages/desktop</code> directory would build successfully through Vite and electron-vite, then crash at the final step:</p>
<figure class='code'><figcaption>The Symptom<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>error during start dev server and electron app:
</div><div class='line'>Error: Electron uninstall
</div><div class='line'>    at getElectronPath (file:///.../electron-vite/dist/chunks/lib-q6ns0vZr.js:155:19)
</div><div class='line'>    at startElectron (file:///.../electron-vite/dist/chunks/lib-q6ns0vZr.js:222:26)
</div><div class='line'>    ...
</div><div class='line'>error: script &quot;dev&quot; exited with code 1
</div></pre></td></table></div></figure>
<p data-sourcepos="18:1-18:57">The build completed, but Electron itself couldn't launch.</p>
<h2 data-sourcepos="20:1-20:22">Root Cause Analysis</h2>
<h3 data-sourcepos="22:1-22:39">Step 1: Check Electron Installation</h3>
<p data-sourcepos="24:1-24:61">First, I checked if the <code data-sourcepos="24:25-24:34">electron</code> npm package was installed:</p>
<figure class='code'><figcaption>Inspect the Electron package contents<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>$<span class="w"> </span>ls<span class="w"> </span>node_modules/electron
</div><div class='line'>abi_version<span class="w">  </span>checksums.json<span class="w">  </span>cli.js<span class="w">  </span>dist<span class="w">  </span>index.js<span class="w">  </span>install.js<span class="w">  </span>...
</div></pre></td></table></div></figure>
<p data-sourcepos="31:1-31:100">Looks present. But <code data-sourcepos="31:20-31:42">node_modules/electron</code> was actually a <strong data-sourcepos="31:59-31:69">symlink</strong> to Bun's global package store:</p>
<figure class='code'><figcaption>Check the Electron package symlink<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>$<span class="w"> </span>readlink<span class="w"> </span>node_modules/electron
</div><div class='line'>../../../node_modules/.bun/electron@42.3.3+759ce506b1ed1a42/node_modules/electron
</div></pre></td></table></div></figure>
<h3 data-sourcepos="38:1-38:35">Step 2: Check the Actual Binary</h3>
<p data-sourcepos="40:1-40:114">The <code data-sourcepos="40:5-40:14">electron</code> npm package is just a downloader wrapper. The actual Chromium binary lives in <code data-sourcepos="40:94-40:100">dist/</code>. Let's check:</p>
<figure class='code'><figcaption>Step 2: Check the Actual Binary<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>$<span class="w"> </span>ls<span class="w"> </span>-la<span class="w"> </span>node_modules/electron/dist
</div><div class='line'>total<span class="w"> </span><span class="m">39040</span>
</div><div class='line'>-rw-r--r--<span class="w"> </span><span class="m">1</span><span class="w"> </span>staff<span class="w"> </span><span class="m">19325554</span><span class="w"> </span>Jun<span class="w"> </span><span class="m">10</span><span class="w"> </span><span class="m">19</span>:23<span class="w"> </span>LICENSES.chromium.html
</div></pre></td></table></div></figure>
<p data-sourcepos="48:1-48:107"><strong data-sourcepos="48:1-48:24">Only a LICENSE file!</strong> The actual <code data-sourcepos="48:37-48:50">Electron.app</code> bundle was missing. The binary had never been extracted.</p>
<h3 data-sourcepos="50:1-50:40">Step 3: Why Didn't Postinstall Work?</h3>
<p data-sourcepos="52:1-52:99">Electron's <code data-sourcepos="52:12-52:25">package.json</code> includes a <code data-sourcepos="52:38-52:50">postinstall</code> script that runs <code data-sourcepos="52:69-52:85">node install.js</code>. This script:</p>
<ol data-sourcepos="53:1-55:32">
<li data-sourcepos="53:1-53:48">Checks if <code data-sourcepos="53:14-53:27">dist/version</code> and <code data-sourcepos="53:33-53:42">path.txt</code> exist</li>
<li data-sourcepos="54:1-54:66">If not, downloads the platform-specific zip via <code data-sourcepos="54:52-54:66">@electron/get</code></li>
<li data-sourcepos="55:1-55:32">Extracts it via <code data-sourcepos="55:20-55:32">extract-zip</code></li>
</ol>
<p data-sourcepos="57:1-57:35">The zip was already cached locally:</p>
<figure class='code'><figcaption>Confirm the Electron archive is cached<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>$<span class="w"> </span>ls<span class="w"> </span>~/Library/Caches/electron/
</div><div class='line'>electron-v42.3.3-darwin-arm64.zip<span class="w">  </span><span class="c1"># ~120MB, present</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="64:1-64:98">So download worked. But extraction didn't. I wrote a test script to verify <code data-sourcepos="64:76-64:88">extract-zip</code> behavior:</p>
<figure class='code'><figcaption>Reproduce extract-zip asynchronous extraction<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  javascript'><pre><div class='line'><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">extract</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;extract-zip&#39;</span><span class="p">);</span>
</div><div class='line'><span class="nx">extract</span><span class="p">(</span><span class="s1">&#39;/path/to/electron-v42.3.3-darwin-arm64.zip&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</div><div class='line'><span class="w">  </span><span class="nx">dir</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;/path/to/dist&#39;</span>
</div><div class='line'><span class="p">}).</span><span class="nx">then</span><span class="p">(()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;done&#39;</span><span class="p">));</span>
</div><div class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;after call&#39;</span><span class="p">);</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="74:1-74:7">Output:</p>
<figure class='code'><figcaption>Output from the extract-zip test<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>after call
</div><div class='line'># Process exits immediately with code 0, never prints &quot;done&quot;
</div></pre></td></table></div></figure>
<p data-sourcepos="80:1-80:290"><strong data-sourcepos="80:1-80:95"><code data-sourcepos="80:3-80:15">extract-zip</code> v2.0.1 was returning a promise but resolving instantly without doing the work</strong>, causing the install script to exit cleanly before extraction completed. This appears to be a compatibility quirk with Node.js v24 and the specific <code data-sourcepos="80:245-80:257">extract-zip</code> version used by Electron 42.3.3.</p>
<h3 data-sourcepos="82:1-82:40">Step 4: The Smoking Gun — path.txt</h3>
<p data-sourcepos="84:1-84:224">Even if extraction had worked, I later discovered another issue. The <code data-sourcepos="84:70-84:81">install.js</code> script creates <code data-sourcepos="84:98-84:107">path.txt</code> containing the relative path to the Electron executable. When I manually created this file, I initially used <code data-sourcepos="84:218-84:223">echo</code>:</p>
<figure class='code'><figcaption>Incorrectly write Electron path.txt with a newline<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;Electron.app/Contents/MacOS/Electron&quot;</span><span class="w"> </span>&gt;<span class="w"> </span>path.txt
</div></pre></td></table></div></figure>
<p data-sourcepos="90:1-90:81">This added a trailing newline (<code data-sourcepos="90:32-90:37">0x0a</code>), which caused a hilarious secondary error:</p>
<figure class='code'><figcaption>Electron launch failure caused by the trailing newline<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>Error: spawn /path/to/Electron.app/Contents/MacOS/Electron\n ENOENT
</div></pre></td></table></div></figure>
<p data-sourcepos="96:1-96:97">Note the <code data-sourcepos="96:10-96:13">\n</code> in the path! Always use <code data-sourcepos="96:39-96:46">printf</code> without a newline for machine-readable path files:</p>
<figure class='code'><figcaption>Write Electron path.txt without a trailing newline<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">printf</span><span class="w"> </span><span class="s1">&#39;Electron.app/Contents/MacOS/Electron&#39;</span><span class="w"> </span>&gt;<span class="w"> </span>path.txt
</div></pre></td></table></div></figure>
<h2 data-sourcepos="102:1-102:10">The Fix</h2>
<p data-sourcepos="104:1-104:99">Since the automated install script couldn't extract the archive, I performed a manual surgical fix:</p>
<figure class='code'><figcaption>Manually repair the Bun-installed Electron package<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="c1"># 1. Clean the incomplete dist directory</span>
</div><div class='line'>rm<span class="w"> </span>-rf<span class="w"> </span>node_modules/.bun/electron@42.3.3+759ce506b1ed1a42/node_modules/electron/dist/*
</div><div class='line'> </div><div class='line'><span class="c1"># 2. Extract the cached zip using system unzip (reliable)</span>
</div><div class='line'>unzip<span class="w"> </span>-q<span class="w"> </span>~/Library/Caches/electron/0398eab6ff3b.../electron-v42.3.3-darwin-arm64.zip<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>-d<span class="w"> </span>node_modules/.bun/electron@42.3.3+759ce506b1ed1a42/node_modules/electron/dist
</div><div class='line'> </div><div class='line'><span class="c1"># 3. Create path.txt WITHOUT trailing newline</span>
</div><div class='line'><span class="nb">printf</span><span class="w"> </span><span class="s1">&#39;Electron.app/Contents/MacOS/Electron&#39;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>&gt;<span class="w"> </span>node_modules/.bun/electron@42.3.3+759ce506b1ed1a42/node_modules/electron/path.txt
</div></pre></td></table></div></figure>
<p data-sourcepos="117:1-117:13">Verification:</p>
<figure class='code'><figcaption>Verify that Electron resolves after the repair<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>$<span class="w"> </span>node<span class="w"> </span>-e<span class="w"> </span><span class="s2">&quot;console.log(require(&#39;electron&#39;))&quot;</span>
</div><div class='line'>/Users/guochunzhong/git/oss/opencode/node_modules/.bun/electron@42.3.3+.../dist/Electron.app/Contents/MacOS/Electron
</div></pre></td></table></div></figure>
<p data-sourcepos="124:1-124:129">Then <code data-sourcepos="124:6-124:18">bun run dev</code> worked perfectly — the Electron app launched, connected to the Vite dev server, and the sidecar initialized.</p>
<h2 data-sourcepos="126:1-126:18">Lessons Learned</h2>
<ol data-sourcepos="128:1-134:140">
<li data-sourcepos="128:1-128:191">
<p data-sourcepos="128:4-128:191"><strong data-sourcepos="128:4-128:40">Symlinks obscure the real problem</strong>: When using Bun or pnpm, packages live in a content-addressable store. The <code data-sourcepos="128:117-128:139">node_modules/electron</code> directory you see isn't where the actual files are.</p>
</li>
<li data-sourcepos="130:1-130:204">
<p data-sourcepos="130:4-130:204"><strong data-sourcepos="130:4-130:57">Don't trust postinstall in modern package managers</strong>: Bun's installation strategy sometimes doesn't play well with native binary postinstall scripts, especially ones using older extraction libraries.</p>
</li>
<li data-sourcepos="132:1-132:155">
<p data-sourcepos="132:4-132:155"><strong data-sourcepos="132:4-132:44"><code data-sourcepos="132:6-132:11">echo</code> is dangerous for file contents</strong>: That trailing newline cost me an extra debugging cycle. Use <code data-sourcepos="132:107-132:114">printf</code> or <code data-sourcepos="132:119-132:127">echo -n</code> when writing exact strings.</p>
</li>
<li data-sourcepos="134:1-135:0">
<p data-sourcepos="134:4-134:140"><strong data-sourcepos="134:4-134:27">Cache is your friend</strong>: The 120MB zip was already in <code data-sourcepos="134:59-134:86">~/Library/Caches/electron/</code>. No need to re-download — just extract it properly.</p>
</li>
</ol>
<h2 data-sourcepos="136:1-136:12">Reference</h2>
<ul data-sourcepos="138:1-141:99">
<li data-sourcepos="138:1-138:90">Electron package store path: <code data-sourcepos="138:32-138:90">node_modules/.bun/electron@42.3.3+*/node_modules/electron</code></li>
<li data-sourcepos="139:1-139:54">Electron cache: <code data-sourcepos="139:19-139:46">~/Library/Caches/electron/</code> (macOS)</li>
<li data-sourcepos="140:1-140:108">Expected <code data-sourcepos="140:12-140:17">dist</code> contents after extraction: <code data-sourcepos="140:46-140:60">Electron.app/</code>, <code data-sourcepos="140:63-140:71">LICENSE</code>, <code data-sourcepos="140:74-140:97">LICENSES.chromium.html</code>, <code data-sourcepos="140:100-140:108">version</code></li>
<li data-sourcepos="141:1-141:99">The <code data-sourcepos="141:7-141:16">path.txt</code> format: plain relative path, no newline, pointing to the executable inside <code data-sourcepos="141:93-141:99">dist/</code></li>
</ul>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[My 2026 Monthly Subscription Review]]></title>
<link href="https://blog.cloud-mes.com/2026/06/09/my-2026-monthly-subscription-review/"/>
<updated>2026-06-09T15:33:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-06-09-my-2026-monthly-subscription-review.markdown</id>
<category term="Subscription" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:345">Like the previous years <a data-sourcepos="1:25-1:80" href="https://blog.cloud-mes.com/2022/02/21/my-monthly-subscription-review-list/">2022</a> / <a data-sourcepos="1:84-1:144" href="https://blog.cloud-mes.com/2023/07/06/my-2023-monthly-subscription-review-list/">2023</a> / <a data-sourcepos="1:148-1:208" href="https://blog.cloud-mes.com/2024/07/15/my-2024-monthly-subscription-review-list/">2024</a> and <a data-sourcepos="1:214-1:274" href="https://blog.cloud-mes.com/2025/10/15/my-2025-monthly-subscription-review-list/">2025</a>, here is my 2026 subscription list. The first number is RMB per month.</p>
<p data-sourcepos="3:1-3:7">My pay:</p>
<ol data-sourcepos="5:1-21:52">
<li data-sourcepos="5:1-5:17">(68) iCloud 2T</li>
<li data-sourcepos="6:1-6:31">(17) Apple Music family plan</li>
<li data-sourcepos="7:1-7:38">(24) Dragonruby Pro (annual 42 USD)</li>
<li data-sourcepos="8:1-8:47">(33) Bandwagon host (monthly 33 USD, shared)</li>
<li data-sourcepos="9:1-9:34">(6) Adblock Pro (annual 70 RMB)</li>
<li data-sourcepos="10:1-10:41">(6.5) This blog domain (annual 11 USD)</li>
<li data-sourcepos="11:1-11:37">(1.5) 香哈菜谱 (annual 18 RMB)</li>
<li data-sourcepos="12:1-12:55">(49) AWS hosting (3 years 206 USD, monthly 1.35 USD)</li>
<li data-sourcepos="13:1-13:34">(16.8) Meituan biking (monthly)</li>
<li data-sourcepos="14:1-14:46">(110) Cursor.sh AI editor. (yearly 192 USD)</li>
<li data-sourcepos="15:1-15:43">(8.2) Ivory for Mastodon (yearly 98 RMB)</li>
<li data-sourcepos="16:1-16:48">(8) IndieWeb.Social Backer. (monthly 1.5 SGD)</li>
<li data-sourcepos="17:1-17:49">(31) Sublime Text and Merge (3 years, 152 AUD)</li>
<li data-sourcepos="18:1-18:30">(26) Surge (yearly, 46 USD)</li>
<li data-sourcepos="19:1-19:34">(156) ChatGPT (monthly, 22 USD)</li>
<li data-sourcepos="20:1-20:46">(17.9) Kapeli Dash.app (yearly, 215.33 RMB)</li>
<li data-sourcepos="21:1-21:52">(199) Kimi Allegretto for code (monthly, 199 RMB)</li>
</ol>
<p data-sourcepos="23:1-23:7">TH pay:</p>
<ol data-sourcepos="25:1-27:63">
<li data-sourcepos="25:1-25:44">(TH) OpenCode Go and Zen (depend on bill)</li>
<li data-sourcepos="26:1-26:45">(TH) Gemini &amp; Nano Banana (depend on bill)</li>
<li data-sourcepos="27:1-27:63">(52.25) Google Workspace Business Starter (yearly 92.65 USD)</li>
</ol>
<p data-sourcepos="29:1-29:113">So totally 777.9 RMB per month to pay. In the previous 2025 year it was 773 RMB, so almost same compared to 2025.</p>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Build x86 electron on arm64 Mac]]></title>
<link href="https://blog.cloud-mes.com/2026/05/10/build-x86-electron-on-arm64-mac/"/>
<updated>2026-05-10T10:32:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-05-10-build-x86-electron-on-arm64-mac.markdown</id>
<category term="Electron" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:139">When building a Windows Electron app on Apple Silicon macOS, <code data-sourcepos="1:62-1:85">electron-builder --win</code> defaults to the <strong data-sourcepos="1:103-1:123">host architecture</strong> — so running:</p>
<figure class='code'><figcaption>Electron-builder default architecture on Apple Silicon<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">cd</span><span class="w"> </span>packages/desktop<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>bun<span class="w"> </span>run<span class="w"> </span>package:win
</div></pre></td></table></div></figure>
<p data-sourcepos="7:1-7:64">produces <code data-sourcepos="7:10-7:16">arm64</code> Windows binaries instead of the expected <code data-sourcepos="7:59-7:63">x64</code>.</p>
<h2 data-sourcepos="9:1-9:10">The Fix</h2>
<p data-sourcepos="11:1-11:77">Force x64 explicitly with the <code data-sourcepos="11:31-11:37">--x64</code> flag when invoking the package command:</p>
<figure class='code'><figcaption>Build the Windows package explicitly for x64<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">cd</span><span class="w"> </span>/path/to/packages/desktop
</div><div class='line'>bun<span class="w"> </span>run<span class="w"> </span>build
</div><div class='line'>bun<span class="w"> </span>run<span class="w"> </span>package:win<span class="w"> </span>--<span class="w"> </span>--x64<span class="w"> </span>--publish<span class="w"> </span>never
</div></pre></td></table></div></figure>
<p data-sourcepos="19:1-19:36">Or call <code data-sourcepos="19:9-19:26">electron-builder</code> directly:</p>
<figure class='code'><figcaption>Build the x64 Windows package with electron-builder<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>npx<span class="w"> </span>electron-builder<span class="w"> </span>--win<span class="w"> </span>--x64<span class="w"> </span>--publish<span class="w"> </span>never<span class="w"> </span>--config<span class="w"> </span>electron-builder.config.ts
</div></pre></td></table></div></figure>
<p data-sourcepos="25:1-25:46">This produces the standard NSIS x64 artifacts:</p>
<figure class='code'><figcaption>Expected x64 NSIS package artifacts<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>dist/SigmaAgents-win-x64-&lt;version&gt;.exe
</div><div class='line'>dist/SigmaAgents-win-x64-&lt;version&gt;.exe.blockmap
</div><div class='line'>dist/latest.yml
</div></pre></td></table></div></figure>
<p data-sourcepos="33:1-33:96">To pin a specific version (e.g. <code data-sourcepos="33:33-33:41">1.14.42</code>), set the <code data-sourcepos="33:53-33:70">OPENCODE_VERSION</code> env var before the build:</p>
<figure class='code'><figcaption>Build a specific x64 package version<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nv">OPENCODE_VERSION</span><span class="o">=</span><span class="m">1</span>.14.42<span class="w"> </span>bun<span class="w"> </span>./scripts/prepare.ts
</div><div class='line'>bun<span class="w"> </span>run<span class="w"> </span>build
</div><div class='line'>bun<span class="w"> </span>run<span class="w"> </span>package:win<span class="w"> </span>--<span class="w"> </span>--x64<span class="w"> </span>--publish<span class="w"> </span>never
</div></pre></td></table></div></figure>
<h2 data-sourcepos="41:1-41:36">Follow-up: Native Module Mismatch</h2>
<p data-sourcepos="43:1-43:66">After building with <code data-sourcepos="43:21-43:27">--x64</code>, the <code data-sourcepos="43:34-43:39">.exe</code> may launch but crash with:</p>
<figure class='code'><figcaption>Follow-up: Native Module Mismatch<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>Error: Cannot find module &#39;./windowsTerminal&#39;
</div></pre></td></table></div></figure>
<p data-sourcepos="49:1-49:308"><strong data-sourcepos="49:1-49:14">Root cause</strong>: <code data-sourcepos="49:17-49:31">bun run build</code> resolves native modules (e.g. <code data-sourcepos="49:63-49:80">@lydell/node-pty</code>) based on the <strong data-sourcepos="49:96-49:103">host</strong> platform and architecture — <code data-sourcepos="49:135-49:148">darwin-arm64</code> — rather than the target <code data-sourcepos="49:177-49:187">win32-x64</code>. The bundle ends up importing the macOS native binding, which fails when the Windows <code data-sourcepos="49:274-49:279">.exe</code> tries to load it at runtime.</p>
<h3 data-sourcepos="51:1-51:11">The Fix</h3>
<p data-sourcepos="53:1-53:119">Set <code data-sourcepos="53:5-53:40">RUST_TARGET=x86_64-pc-windows-msvc</code> so the build step selects the correct native bindings for the target platform:</p>
<figure class='code'><figcaption>Set the Rust target for Windows x64 bindings<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">cd</span><span class="w"> </span>/path/to/packages/desktop
</div><div class='line'><span class="nv">RUST_TARGET</span><span class="o">=</span>x86_64-pc-windows-msvc<span class="w"> </span>bun<span class="w"> </span>run<span class="w"> </span>build
</div><div class='line'><span class="nv">RUST_TARGET</span><span class="o">=</span>x86_64-pc-windows-msvc<span class="w"> </span>bun<span class="w"> </span>run<span class="w"> </span>package:win<span class="w"> </span>--<span class="w"> </span>--x64<span class="w"> </span>--publish<span class="w"> </span>never
</div></pre></td></table></div></figure>
<h3 data-sourcepos="61:1-61:10">Verify</h3>
<p data-sourcepos="63:1-63:78">After the build, confirm the main bundle imports the correct platform binding:</p>
<figure class='code'><figcaption>Check the bundled node-pty platform binding<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>rg<span class="w"> </span><span class="s2">&quot;node-pty&quot;</span><span class="w"> </span>out/main/index.js
</div><div class='line'><span class="c1"># should show: @lydell/node-pty-win32-x64</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="70:1-70:67">And check the packaged <code data-sourcepos="70:24-70:33">app.asar</code> includes the right native module:</p>
<figure class='code'><figcaption>Check the packaged node-pty native module<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>npx<span class="w"> </span>asar<span class="w"> </span>list<span class="w"> </span>dist/win-unpacked/resources/app.asar<span class="w"> </span><span class="p">|</span><span class="w"> </span>rg<span class="w"> </span><span class="s2">&quot;windowsTerminal&quot;</span>
</div><div class='line'><span class="c1"># should exist under: node_modules/@lydell/node-pty-win32-x64/lib/</span>
</div></pre></td></table></div></figure>
<h3 data-sourcepos="77:1-77:37">TL;DR for cross-building on macOS</h3>
<p data-sourcepos="79:1-79:62">Both env vars are needed together for a working Windows build:</p>
<figure class='code'><figcaption>Cross-building Windows x64 on macOS<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nv">RUST_TARGET</span><span class="o">=</span>x86_64-pc-windows-msvc<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>bun<span class="w"> </span>run<span class="w"> </span>build<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="nv">RUST_TARGET</span><span class="o">=</span>x86_64-pc-windows-msvc<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>bun<span class="w"> </span>run<span class="w"> </span>package:win<span class="w"> </span>--<span class="w"> </span>--x64<span class="w"> </span>--publish<span class="w"> </span>never
</div></pre></td></table></div></figure>
<p data-sourcepos="88:1-88:119">Without <code data-sourcepos="88:9-88:21">RUST_TARGET</code>, you get a working <code data-sourcepos="88:42-88:47">.exe</code> installer but a broken main process that can't find its native modules.</p>
<p data-sourcepos="90:1-90:18">For Windows ARM64:</p>
<figure class='code'><figcaption>Cross-building Windows arm64 on macOS<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nv">RUST_TARGET</span><span class="o">=</span>aarch64-pc-windows-msvc<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>bun<span class="w"> </span>run<span class="w"> </span>build<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="nv">RUST_TARGET</span><span class="o">=</span>aarch64-pc-windows-msvc<span class="w"> </span><span class="se">\</span>
</div><div class='line'><span class="w">  </span>bun<span class="w"> </span>run<span class="w"> </span>package:win<span class="w"> </span>--<span class="w"> </span>--arm64<span class="w"> </span>--publish<span class="w"> </span>never
</div></pre></td></table></div></figure>
<h2 data-sourcepos="99:1-99:23">Caveat: Code Signing</h2>
<p data-sourcepos="101:1-101:387">The <code data-sourcepos="101:5-101:10">.exe</code> built on macOS will <strong data-sourcepos="101:32-101:38">not</strong> be Windows-code-signed. Electron-builder's signing routines typically gate on <code data-sourcepos="101:118-101:147">process.platform === &quot;win32&quot;</code> (and often <code data-sourcepos="101:160-101:186">GITHUB_ACTIONS === &quot;true&quot;</code>), so they intentionally skip signing on macOS. If you need a signed Windows release artifact, you must either build and sign on a Windows machine or add a macOS-compatible signing path to your config.</p>
<h2 data-sourcepos="103:1-103:12">CLI build</h2>
<figure class='code'><figcaption>Build the linux x64 cli<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>bun<span class="w"> </span>run<span class="w"> </span>build:node<span class="w"> </span>--target<span class="o">=</span>linux-x64
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[How to fix Bun v1.3.12 based opencode got Killed: 9 after bun turbo build]]></title>
<link href="https://blog.cloud-mes.com/2026/04/12/how-to-fix-bun-v1-dot-3-12-based-opencode-got-killed-9-after-bun-turbo-build/"/>
<updated>2026-04-12T14:50:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-04-12-how-to-fix-bun-v1-dot-3-12-based-opencode-got-killed-9-after-bun-turbo-build.markdown</id>
<category term="Bun" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:33">New bun 1.3.12 release but I got:</p>
<figure class='code'><figcaption>New bun 1.3.12 release but I got:<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>$ ./opencode --version
</div><div class='line'>Killed: 9
</div></pre></td></table></div></figure>
<p data-sourcepos="8:1-8:61">In fact, the binary was fine. The <strong data-sourcepos="8:35-8:52">code signature</strong> was not.</p>
<h2 data-sourcepos="10:1-10:12">Diagnosis</h2>
<p data-sourcepos="12:1-12:56">A quick check with <code data-sourcepos="12:20-12:29">codesign</code> revealed the real culprit:</p>
<figure class='code'><figcaption>Check the binary code signature<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>$ codesign -vv ./opencode
</div><div class='line'>./opencode: invalid or unsupported format for signature
</div><div class='line'>In architecture: arm64
</div></pre></td></table></div></figure>
<p data-sourcepos="20:1-20:241">On Apple Silicon, macOS enforces code signing strictly. A malformed or corrupt <code data-sourcepos="20:80-20:98">LC_CODE_SIGNATURE</code> load command will cause the kernel to terminate the process immediately with <code data-sourcepos="20:177-20:185">SIGKILL</code> (<code data-sourcepos="20:188-20:198">Killed: 9</code>)—before a single line of your code runs.</p>
<p data-sourcepos="22:1-22:63">You can confirm the signature load command exists with <code data-sourcepos="22:56-22:62">otool</code>:</p>
<figure class='code'><figcaption>Confirm the code-signature load command<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>$<span class="w"> </span>otool<span class="w"> </span>-l<span class="w"> </span>./opencode<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>LC_CODE_SIGNATURE
</div><div class='line'><span class="w">      </span>cmd<span class="w"> </span>LC_CODE_SIGNATURE
</div></pre></td></table></div></figure>
<h2 data-sourcepos="29:1-29:10">The Fix</h2>
<p data-sourcepos="31:1-31:73">Remove the broken signature and replace it with a local ad-hoc signature:</p>
<figure class='code'><figcaption>Replace the broken code signature<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>codesign<span class="w"> </span>--remove-signature<span class="w"> </span>./opencode
</div><div class='line'>codesign<span class="w"> </span>--sign<span class="w"> </span>-<span class="w"> </span>--force<span class="w"> </span>./opencode
</div></pre></td></table></div></figure>
<p data-sourcepos="38:1-38:12">Then verify:</p>
<figure class='code'><figcaption>Verify the repaired code signature<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>$ codesign -vv ./opencode
</div><div class='line'>./opencode: valid on disk
</div><div class='line'>./opencode: satisfies its Designated Requirement
</div><div class='line'> </div><div class='line'>$ ./opencode --version
</div><div class='line'>0.0.0-eric_dev-202604120639
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Some tricky command not in the official openclaw document]]></title>
<link href="https://blog.cloud-mes.com/2026/02/17/some-tricky-command-not-in-the-official-openclaw-document/"/>
<updated>2026-02-17T16:26:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-02-17-some-tricky-command-not-in-the-official-openclaw-document.markdown</id>
<category term="OpenClaw" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:80">Make pnpm install success on the MacOS machine already install libvips via brew.</p>
<figure class='code'><figcaption>Useful OpenClaw commands absent from the official documentation<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nv">SHARP_IGNORE_GLOBAL_LIBVIPS</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>pnpm<span class="w"> </span>install
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Quickly fix turbo version not match package.json + bun.lock]]></title>
<link href="https://blog.cloud-mes.com/2026/01/28/quickly-fix-turbo-version-not-match-package-dot-json-plus-bun-dot-lock/"/>
<updated>2026-01-28T06:54:22+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-01-27-quickly-fix-turbo-version-not-match-package-dot-json-plus-bun-dot-lock.markdown</id>
<category term="Bun" />

      <content type="html"><![CDATA[<figure class='code'><figcaption>Align the Turbo version in package.json and bun.lock<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>rm<span class="w"> </span>-rf<span class="w"> </span>node_modules/turbo-darwin-arm64<span class="w"> </span>node_modules/turbo<span class="w"> </span>node_modules/.bin/turbo
</div><div class='line'>bun<span class="w"> </span>install<span class="w"> </span>--force
</div><div class='line'>./node_modules/.bin/turbo<span class="w"> </span>--version
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Using git blame.ignoreRevsFile options to ignore your auto format coding commits]]></title>
<link href="https://blog.cloud-mes.com/2026/01/11/using-git-blame-dot-ignorerevsfile-options-to-ignore-your-auto-format-coding-commits/"/>
<updated>2026-01-11T13:44:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2026/2026-01-11-using-git-blame-dot-ignorerevsfile-options-to-ignore-your-auto-format-coding-commits.markdown</id>
<category term="Git" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:148">After running any auto-format on a repository, <code data-sourcepos="1:48-1:58">git blame</code> becomes almost useless because every line is “last touched” by the formatting commit.</p>
<p data-sourcepos="3:1-3:73">Git has a built-in solution: tell <code data-sourcepos="3:35-3:45">git blame</code> to ignore specific commits.</p>
<h2 data-sourcepos="5:1-5:48">Create <code data-sourcepos="5:11-5:34">.git-blame-ignore-revs</code> in root repo.</h2>
<figure class='code'><figcaption>.git-blame-ignore-revs<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span># Formatting / reindent / whitespace-only commits
</div><div class='line'>1a2b3c4d
</div><div class='line'>abcdef01
</div></pre></td></table></div></figure>
<h2 data-sourcepos="13:1-13:33">Enable it in your repo via git</h2>
<figure class='code'><figcaption>Enable it in your repo via git<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>git<span class="w"> </span>config<span class="w"> </span>--local<span class="w"> </span>blame.ignoreRevsFile<span class="w"> </span>.git-blame-ignore-revs
</div></pre></td></table></div></figure>
<p data-sourcepos="19:1-19:99">Now when you run <code data-sourcepos="19:18-19:28">git blame</code>, those commits will be skipped and you can see the real history again.</p>
<h2 data-sourcepos="21:1-21:8">Notes</h2>
<ul data-sourcepos="23:1-24:127">
<li data-sourcepos="23:1-23:111">Commit <code data-sourcepos="23:10-23:33">.git-blame-ignore-revs</code> into your repo so GitHub (and other developers) can use the same ignore list.</li>
<li data-sourcepos="24:1-24:127">GitHub also reads <code data-sourcepos="24:21-24:44">.git-blame-ignore-revs</code> automatically in the web UI, so you’ll get the same “clean blame” there too.</li>
</ul>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Deploy Next.js 15.5 via capistrano into self-hosting Rocky Linux 9]]></title>
<link href="https://blog.cloud-mes.com/2025/12/03/deploy-next-dot-js-15-dot-5-via-capistrano-into-self-hosting-rocky-linux-9/"/>
<updated>2025-12-03T11:20:34+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-12-03-deploy-next-dot-js-15-dot-5-via-capistrano-into-self-hosting-rocky-linux-9.markdown</id>
<category term="capistrano" /><category term="NextJS" /><category term="Rocky" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:341">It's very similar post like <a data-sourcepos="1:29-1:121" href="https://blog.cloud-mes.com/2023/06/26/deploy-next-dot-js-app-to-a-dedicateed-rocky-linux-v8/">deploy to Rocky Linux 8</a>, but this time we using <a data-sourcepos="1:147-1:230" href="https://github.com/Eric-Guo/webapp-conversation/tree/sso_login">a opencode project</a>, this Next.JS enable you add a login via sso to an existing dify app, which is must need for a enterprise env.</p>
<h3 data-sourcepos="4:1-4:21">Create a new user</h3>
<figure class='code'><figcaption>Create a new user<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>adduser<span class="w"> </span>sql_chat
</div><div class='line'>gpasswd<span class="w"> </span>-a<span class="w"> </span>sql_chat<span class="w"> </span>wheel
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/etc/sudoers.d
</div><div class='line'><span class="c1"># you can later change more limit like &quot;sql_chat ALL=(ALL) NOPASSWD:/usr/bin/systemctl&quot;</span>
</div><div class='line'><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;sql_chat ALL=(ALL) NOPASSWD:ALL&quot;</span><span class="w"> </span>&gt;<span class="w"> </span><span class="m">97</span>-sql_chat-user
</div><div class='line'>sudo<span class="w"> </span>su<span class="w"> </span>-<span class="w"> </span>sql_chat
</div><div class='line'>mkdir<span class="w"> </span>.ssh
</div><div class='line'>chmod<span class="w"> </span><span class="m">700</span><span class="w"> </span>.ssh
</div><div class='line'>vi<span class="w"> </span>.ssh/authorized_keys<span class="w"> </span><span class="c1"># and paste your public key</span>
</div><div class='line'>chmod<span class="w"> </span><span class="m">600</span><span class="w"> </span>.ssh/authorized_keys
</div></pre></td></table></div></figure>
<p data-sourcepos="19:1-19:58">and make sure you can login via <code data-sourcepos="19:33-19:58">ssh sql_chat@your_server</code></p>
<h3 data-sourcepos="21:1-21:22">Install Node.js 22</h3>
<figure class='code'><figcaption>Install Node.js 22<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>curl<span class="w"> </span>-sL<span class="w"> </span>https://rpm.nodesource.com/setup_22.x<span class="w"> </span>-o<span class="w"> </span>nodesource_setup.sh
</div><div class='line'>sudo<span class="w"> </span>bash<span class="w"> </span>nodesource_setup.sh
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>nodejs
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>install<span class="w"> </span>gcc-c++<span class="w"> </span>make
</div><div class='line'>sudo<span class="w"> </span>npm<span class="w"> </span>install<span class="w"> </span>-g<span class="w"> </span>npm
</div></pre></td></table></div></figure>
<h3 data-sourcepos="31:1-31:41">Fix permissions for the deploy folder</h3>
<figure class='code'><figcaption>Fix permissions for the deploy folder<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>mkdir<span class="w"> </span>/var/www
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/var/www
</div><div class='line'>sudo<span class="w"> </span>mkdir<span class="w"> </span>sql_chat
</div><div class='line'>sudo<span class="w"> </span>chown<span class="w"> </span>sql_chat:sql_chat<span class="w"> </span>sql_chat
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[召唤神龙！7 个 AI 编程智能体的许愿力测评]]></title>
<link href="https://blog.cloud-mes.com/2025/11/09/a-review-of-the-wish-granting-power-of-7-AI-coding-agents/"/>
<updated>2025-11-09T13:36:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-11-09-a-review-of-the-wish-granting-power-of-7-AI-coding-agents.markdown</id>


      <content type="html"><![CDATA[<blockquote data-sourcepos="1:1-1:130">
<p data-sourcepos="1:3-1:130">I planned a book, 7 Coding Agents in 7 Weeks. In the end, I only had two days—and this Chinese article was all I could manage.</p>
</blockquote>
<img class="" src="https://blog.cloud-mes.com/images/2025/7-coding-agent-in-7-weeks.jpg" title="苹果的组织图" >
<h1 data-sourcepos="5:1-5:81">Summon Shenron! A Review of the Wish-Granting Power of 7 AI Programming Agents.</h1>
<h2 data-sourcepos="7:1-7:24">测评原因及声明</h2>
<blockquote data-sourcepos="9:1-9:197">
<p data-sourcepos="9:3-9:197">声明：以下测评内容没有经过任何 AI 的润色或者重写，所以你发现了 AI 味道，那一定是我被影响了，欢迎指出拯救我的 AI 味，如同我的老登味一样</p>
</blockquote>
<p data-sourcepos="11:1-11:330">时光飞快，日月如梭，转眼到了中年，到了看到任何科技都是异端的年纪，但是谁会拒绝拥有一个言出法随，言听计从的智能体呢？如果一个不行，那就再来一个，于是，一个接着一个，我一不小心拥有了 7 个，那么就不妨来个个人风格横向测评吧！</p>
<p data-sourcepos="13:1-13:75">本次测评纯属我个人行为，无任何一拍即合的商业合作。</p>
<p data-sourcepos="15:1-15:144">本次测评使用的方法是不严谨的，仅供我个人记录回忆以及和朋友们的交流使用，您可以看作这是一次娱乐。</p>
<h2 data-sourcepos="17:1-17:15">测评方式</h2>
<p data-sourcepos="19:1-19:506">由于我们是测试比较 AI coding 智能体，而不是 LLM 大模型，所以为了尽量避免苹果和梨放在一起比较的尴尬，我还是控制了一下测试变量，如果硬要说我这个测试方式还是拿苹果和梨放在一起比较，那您一定是对的，只要定语加的足够多，我相信您的结论。但我这篇文章是没有定语的，测评的是全球范围的，不限制商业或者开源，但是受到一些合理工程成本限制的 AI Agent 们的能力。</p>
<p data-sourcepos="21:1-21:501">测试方式是日常开发使用场景，介绍一下背景，我是 15 年的 Rails 程序员，15 年的.Net 程序员，毕业在外企工作了 7 年，互联网公司 5 年，建筑设计公司 6 年。日常使用场景在被测试的 coding agent 上使用的方式都完全相同，coding agent 能接触到的工具就是他们自己的工具，使用的模型总是使用 coding agent 推荐的。如果默认的模型做出的结果不好，我会选择更加强力的模型再试一次。</p>
<p data-sourcepos="23:1-23:271">所有测试都是在本机上测试，环境是 MacOS 15.7.2，homebrew 安装的开发工具集，命令行工具也是系统自带，如果 coding agent 有内置的工具，比如 kimi-cli 就会自动安装 ripgrep，那么 kimi-cli 就用的是它自带的工具。</p>
<p data-sourcepos="25:1-25:195">所有 coding agent 的配置选项都是默认，如果 coding agent 有能力搜索 web，那它就可以搜索 web，如果 coding agent 有做 RAG，那就允许，不会做任何的限制。</p>
<p data-sourcepos="27:1-27:105">所有 coding agent 的测试都只考虑结果是否正确，不会特别考察 agent 的运行速度。</p>
<p data-sourcepos="29:1-29:514">另外关于测试题目，因为都是我的日常开发场景，所以必然有难有容易，也很难特别考虑区分度，我也不会在测评最后打分，因为我不想排序，作为成年人，我可能会排出掉 2～3 个 coding agent 后，余下的我都要。这个策略是现实中非常合理的，如果你日常大量使用 coding agent 的话，应该和我一样感到一个 coding agent 是肯定不够用的，但是如果直接买 200 美刀的 ultra 版本，显然也非常吃亏。</p>
<p data-sourcepos="31:1-31:292">目前我的常驻付费 coding agent 分别是 cursor 和 openai codex，opencode 也是我日常天天使用的，opencode 的付费是按照 token 用量，对于按照 token 用量的，我一般会一直保留使用，但是对于订阅的，我还是会评估，并进行成本优化。</p>
<h2 data-sourcepos="33:1-33:36">没有纳入测试对象及原因</h2>
<ul data-sourcepos="35:1-51:212">
<li data-sourcepos="35:1-35:81"><a data-sourcepos="35:3-35:35" href="https://windsurf.com/">windsurf</a>，只有 GUI，没有会员，不测试了。</li>
<li data-sourcepos="36:1-36:89"><a data-sourcepos="36:3-36:56" href="https://www.augmentcode.com/product/CLI">augmentcode</a>，没有会员，不测试了。</li>
<li data-sourcepos="37:1-37:85"><a data-sourcepos="37:3-37:44" href="https://github.com/Aider-AI/aider">aider</a>，2025 年 8 月后就没发新版本。</li>
<li data-sourcepos="38:1-38:175"><a data-sourcepos="38:3-38:41" href="https://github.com/cline/cline">cline</a>，被 roo cline fork 了，感觉社区治理有问题，另外只做 VS Code 插件，但是我不喜欢微软，也没装 VScode。</li>
<li data-sourcepos="39:1-39:127"><a data-sourcepos="39:3-39:53" href="https://github.com/RooCodeInc/Roo-Code">roo cline</a>，分叉 cline，感觉不尊重开源，另外也只有 VSCode 插件。</li>
<li data-sourcepos="40:1-40:126"><a data-sourcepos="40:3-40:46" href="https://github.com/Kilo-Org/kilocode">kilo</a>，只有 VSCode 插件的不测评。不过 MiniMax M2 目前免费，好评！</li>
<li data-sourcepos="41:1-41:80"><a data-sourcepos="41:3-41:50" href="https://github.com/QwenLM/qwen-code">qwen-code</a>，没时间，下次一定！</li>
<li data-sourcepos="42:1-42:208"><a data-sourcepos="42:3-42:41" href="https://github.com/block/goose">goose</a>，Rust 写的，又不是 OpenAI，应该赶不上 AI coding agent 的快速演进，忽略，另外名字起的也差，呆头鹅…，币圈的品味一言难尽。</li>
<li data-sourcepos="43:1-43:137"><a data-sourcepos="43:3-43:50" href="https://www.warp.dev/blog/how-warp-works">warp</a>，Rust 写的，UI 看起来品味真好，但是 20 美刀的订阅，下次一定！</li>
<li data-sourcepos="44:1-44:178"><a data-sourcepos="44:3-44:32" href="https://cli.iflow.cn/">IFLOW</a>，官网紫色，直接就想 pass 了，本着认真负责的精神，又看了文档站，全站没提到收费，怀疑有坑，坚定 pass。</li>
<li data-sourcepos="45:1-45:271"><a data-sourcepos="45:3-45:50" href="https://github.com/antinomyhq/forge">forgecode</a>，Rust 写的，发版本 5 天前，在一个快速发展的领域，真的不建议用 Rust 写，不过一个月 20 美刀 500 次的用量给的倒是非常慷慨，考虑撸一下羊毛？（算了，我是好人）</li>
<li data-sourcepos="46:1-46:141"><a data-sourcepos="46:3-46:50" href="https://github.com/plandex-ai/plandex">plandex</a>，官网不接受新用户？看一眼最后递交，上个月，可能不行了吧。🙏</li>
<li data-sourcepos="47:1-47:91"><a data-sourcepos="47:3-47:49" href="https://github.com/charmbracelet/crush">crush</a>，Go 写的，又开源，没啥黑点，</li>
<li data-sourcepos="48:1-48:181"><a data-sourcepos="48:3-48:46" href="https://openhands.dev/features">open hands</a>，Basecamp 风格的官网挺喜欢的，但是不知道他们要买啥，可能是团队应用？40 美刀起始直接劝退了我。</li>
<li data-sourcepos="49:1-49:359"><a data-sourcepos="49:3-49:49" href="https://aws.amazon.com/q/developer/">amazon Q</a>，官网介绍为啥要介绍 Java 的升级？显然不是面向我们非 Java 程序员的，定价很特殊，19 美刀一个月，4,000 LOC 的配额，我看了一眼我才用了 10 天的 cursor dashboard，代码改动 28678 accept，按照 AWS 的价格，24000x0.003=72 USD，简直是贵到飞起！</li>
<li data-sourcepos="50:1-50:351"><a data-sourcepos="50:3-50:59" href="https://www.npmjs.com/package/@sourcegraph/amp">Amp CLI</a>，这不是<a data-sourcepos="50:72-50:138" href="https://juejin.cn/post/6844903432084537357">王垠喷过的公司</a>么？要不留给他喷？可惜他 blog 都不维护了。。。除开这个，收费<a data-sourcepos="50:226-50:297" href="https://ampcode.com/manual#pricing">按照 token 收费其实很合理</a>，网站做的也很好，下次一定测评一下。</li>
<li data-sourcepos="51:1-52:0"><a data-sourcepos="51:3-51:37" href="https://factory.ai/pricing">droid</a>，很不错，20 美刀的订阅+token 后付费也很合理，还可以 share 给 50 个用户，考虑<a data-sourcepos="51:143-51:209" href="https://factory.ai/news/terminal-bench">OpenAI Codex 到期后转</a>。</li>
</ul>
<h2 data-sourcepos="53:1-53:15">测评对象</h2>
<ol data-sourcepos="55:1-61:52">
<li data-sourcepos="55:1-55:53"><a data-sourcepos="55:4-55:53" href="https://cursor.com/docs/cli/overview">Cursor CLI</a></li>
<li data-sourcepos="56:1-56:39"><a data-sourcepos="56:4-56:39" href="https://opencode.ai/docs">OpenCode</a></li>
<li data-sourcepos="57:1-57:44"><a data-sourcepos="57:4-57:44" href="https://openai.com/codex/">OpenAI codex</a></li>
<li data-sourcepos="58:1-58:63"><a data-sourcepos="58:4-58:63" href="https://github.com/google-gemini/gemini-cli">Google gemini</a></li>
<li data-sourcepos="59:1-59:61"><a data-sourcepos="59:4-59:61" href="https://code.claude.com/docs/zh-CN/overview">Claude Code</a></li>
<li data-sourcepos="60:1-60:53"><a data-sourcepos="60:4-60:53" href="https://github.com/MoonshotAI/kimi-cli">kimi-cli</a></li>
<li data-sourcepos="61:1-62:0"><a data-sourcepos="61:4-61:52" href="https://www.codebuddy.ai/cli">Tencent CodeBuddy</a></li>
</ol>
<h2 data-sourcepos="63:1-63:65">题目一：Tell me where is opencode store the credentials?</h2>
<ol data-sourcepos="65:1-65:9">
<li data-sourcepos="65:1-65:9">Cursor</li>
</ol>
<p data-sourcepos="67:1-67:232">模型 auto，3 次 greps, 2 次 files，准确指出代码行：<code data-sourcepos="67:66-67:108">packages/opencode/src/auth/index.ts:35-38</code>，实测<code data-sourcepos="67:118-67:152">~/.local/share/opencode/auth.json</code>找到配置 key，但是提到的 MacOS 路径不存在，基本回答正确。</p>
<ol data-sourcepos="69:1-69:11" start="2">
<li data-sourcepos="69:1-69:11">OpenCode</li>
</ol>
<p data-sourcepos="71:1-71:265">模型 big-pickle（免费），1 次 grep，2 次 files，回答“Opencode stores credentials in ~/.local/share/opencode/auth.json (or the equivalent XDG data directory on your system). The file is created with 0o600 permissions for security.”，完全准确。</p>
<ol data-sourcepos="73:1-73:15" start="3">
<li data-sourcepos="73:1-73:15">OpenAI codex</li>
</ol>
<p data-sourcepos="75:1-75:296">模型 gpt-5-codex high，直接读 AGENTS.md 后，回答“OpenCode never stores credentials in the repo—secrets are pulled from environment variables that sst.config.ts consumes, and .env* (or any other credential-bearing files) are kept out of version control entirely.”，回答错误。</p>
<p data-sourcepos="77:1-77:198">由于是 OpenAI 的最强模型，我不太相信这个结果，又做了一次实验，相同。猜测是 AGENTS.md 中的 Security &amp; Configuration Tips，提到了 sst.config.ts，干扰了祂。</p>
<p data-sourcepos="79:1-79:6">···</p>
<h2 data-sourcepos="80:1-80:32">Security &amp; Configuration Tips</h2>
<p data-sourcepos="81:1-82:6">Secrets load via environment variables consumed inside <code data-sourcepos="81:56-81:70">sst.config.ts</code>, so never commit <code data-sourcepos="81:89-81:95">.env*</code>, credentials, or generated <code data-sourcepos="81:124-81:130">logs/</code>. Execute binaries through <code data-sourcepos="81:158-81:171">bin/opencode</code> or the install script so patched dependencies in <code data-sourcepos="81:222-81:231">/patches</code> remain applied. When adding fixtures or docs, redact user-specific paths (prefer <code data-sourcepos="81:314-81:325">.opencode/</code> references) and strip identifiers from captured sessions before committing.
···</p>
<p data-sourcepos="84:1-84:162">删除上述 AGENTS.md 文件内容后，经过 3 次 serach，2 次 List，1 次 Read 后，得到正确答案。最终回答准确，但是应该无法得分。</p>
<ol data-sourcepos="86:1-86:16" start="4">
<li data-sourcepos="86:1-86:16">Google gemini</li>
</ol>
<p data-sourcepos="88:1-88:338">模型 gemini-2.5-pro，触发了 Codebase Investigator Agent 后，由子 agent 找到具体位置，但是主 Agent 仍然提到了 ~/Library/Application Support/opencode/auth.json，这个Cursor一样犯的错误，估计是XDG Base Directory Specification 这个关键词导致的干扰，但是回答应该还是认为基本准确。</p>
<ol data-sourcepos="90:1-90:14" start="5">
<li data-sourcepos="90:1-90:14">Claude Code</li>
</ol>
<p data-sourcepos="92:1-92:222">模型 minimax-m2，触发了 5 次 Search，5 次 Read 后，回答非常准确且详尽，仍然提到了不存在的 MacOS 路径，甚至有点用力过猛的给出了 project level 的位置。很卖力，能得分。</p>
<ol data-sourcepos="94:1-94:11" start="6">
<li data-sourcepos="94:1-94:11">kimi-cli</li>
</ol>
<p data-sourcepos="96:1-96:235">模型 kimi-for-coding，1 次 Grep，4 次 ReadFile，4 次 Bash 调用后，甚至直接在屏幕上打出了我的 OpenCoce Zen token，不仅准确，而且无所顾及，得分，以后做啥黑客的工作还得是国产模型！</p>
<ol data-sourcepos="98:1-98:20" start="7">
<li data-sourcepos="98:1-98:20">Tencent CodeBuddy</li>
</ol>
<p data-sourcepos="100:1-100:87">模型 auto，2 次 Search，4 次 Read，给出正确答案，中规中矩，得分。</p>
<h3 data-sourcepos="102:1-102:71">题目一结论：除了 codex 无法得分，其他全部晋级。</h3>
<h2 data-sourcepos="104:1-104:122">题目二：<a data-sourcepos="104:16-104:60" href="https://example.thape.com.cn/nc/supplier/edit">https://example.thape.com.cn/nc/supplier/edit</a> 调用这个接口的时候，增加传一个字段 nature_id</h2>
<ol data-sourcepos="106:1-106:9">
<li data-sourcepos="106:1-106:9">Cursor</li>
</ol>
<p data-sourcepos="108:1-108:120">模型 auto，一路中文推理后，修改正确，甚至还告诉我 nature_id（10 表示单位，20 表示个人）</p>
<ol data-sourcepos="110:1-110:11" start="2">
<li data-sourcepos="110:1-110:11">OpenCode</li>
</ol>
<p data-sourcepos="112:1-112:137">模型 big-pickle（免费），修改正确，但是会去掉几行无伤大雅的代码，可能这个模型还是有自己的品味。</p>
<ol data-sourcepos="114:1-114:15" start="3">
<li data-sourcepos="114:1-114:15">OpenAI codex</li>
</ol>
<p data-sourcepos="116:1-116:73">模型 gpt-5-codex high，7 次 Search，3 次 Read 后，修改正确。</p>
<ol data-sourcepos="118:1-118:16" start="4">
<li data-sourcepos="118:1-118:16">Google gemini</li>
</ol>
<p data-sourcepos="120:1-120:155">模型 gemini-2.5-pro，修改正确，但是修改甚至会跑 test，但是由于这个项目 test 是挂的，所以……，无论如何还是正确。</p>
<ol data-sourcepos="122:1-122:14" start="5">
<li data-sourcepos="122:1-122:14">Claude Code</li>
</ol>
<p data-sourcepos="124:1-124:67">模型 minimax-m2，7 次 Search，4 次 Read 后，修改正确。</p>
<ol data-sourcepos="126:1-126:11" start="6">
<li data-sourcepos="126:1-126:11">kimi-cli</li>
</ol>
<p data-sourcepos="128:1-128:141">模型 kimi-for-coding，修改正确，还告诉我 nature_id（10 表示单位，20 表示个人），甚至我都没开 thinking 模式。</p>
<ol data-sourcepos="130:1-130:20" start="7">
<li data-sourcepos="130:1-130:20">Tencent CodeBuddy</li>
</ol>
<p data-sourcepos="132:1-132:40">模型 auto (GLM 4.6) ，修改正确。</p>
<h3 data-sourcepos="134:1-134:126">题目二结论：：看来这个问题太简单了，白试了，还有就是以后用 Gemini 还是得修一下测试。</h3>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Migrate from yarn to pnpm package manager in the Rails way]]></title>
<link href="https://blog.cloud-mes.com/2025/10/31/migrate-from-yarn-to-pnpm-package-manager-in-the-rails-way/"/>
<updated>2025-10-31T14:40:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-10-31-migrate-from-yarn-to-pnpm-package-manager-in-the-rails-way.markdown</id>
<category term="Rails" /><category term="Node.js" /><category term="pnpm" /><category term="Yarn" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:151">After evaluating package managers for our Rails project, I decide migrated from Yarn to pnpm for stricter, faster, and more disk‑efficient installs.</p>
<p data-sourcepos="3:1-3:81">This guide is organized into four parts so you can adopt pnpm in the right order.</p>
<h2 data-sourcepos="5:1-5:11">Why pnpm</h2>
<ul data-sourcepos="7:1-10:72">
<li data-sourcepos="7:1-7:103"><strong data-sourcepos="7:3-7:21">Disk efficiency</strong>: A content‑addressable store and hardlinks dramatically reduce duplicate files.</li>
<li data-sourcepos="8:1-8:62"><strong data-sourcepos="8:3-8:11">Speed</strong>: Faster cold and warm installs with solid caching.</li>
<li data-sourcepos="9:1-9:82"><strong data-sourcepos="9:3-9:16">Strictness</strong>: Fails on missing/phantom deps and ensures reproducible installs.</li>
<li data-sourcepos="10:1-10:72"><strong data-sourcepos="10:3-10:16">Workspaces</strong>: First‑class monorepo support without heavy hoisting.</li>
</ul>
<p data-sourcepos="12:1-12:113">And the key reason: <code data-sourcepos="12:21-12:26">yarn</code> is not doing any release in past two years and META just lay-off huge people recently.</p>
<h2 data-sourcepos="14:1-14:35">How to do import in Node.js side</h2>
<ol data-sourcepos="16:1-16:34">
<li data-sourcepos="16:1-17:0">Install pnpm (prefer Corepack):</li>
</ol>
<figure class='code'><figcaption>Enable Corepack and install pnpm<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="c1"># If Node comes with Corepack (Node 16.13+)</span>
</div><div class='line'>corepack<span class="w"> </span><span class="nb">enable</span>
</div><div class='line'>corepack<span class="w"> </span>prepare<span class="w"> </span>pnpm@latest<span class="w"> </span>--activate
</div><div class='line'>pnpm<span class="w"> </span>--version<span class="w"> </span><span class="c1"># should avtivate now</span>
</div><div class='line'> </div><div class='line'><span class="c1"># Otherwise fall back to npm with global</span>
</div><div class='line'>npm<span class="w"> </span>i<span class="w"> </span>-g<span class="w"> </span>pnpm@latest
</div></pre></td></table></div></figure>
<ol data-sourcepos="27:1-27:44" start="2">
<li data-sourcepos="27:1-28:0">Import your existing Yarn lockfile first:</li>
</ol>
<figure class='code'><figcaption>Import the Yarn lockfile into pnpm<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="c1"># Generates pnpm-lock.yaml from yarn.lock</span>
</div><div class='line'>pnpm<span class="w"> </span>import
</div><div class='line'>git<span class="w"> </span>add<span class="w"> </span>pnpm-lock.yaml
</div></pre></td></table></div></figure>
<ol data-sourcepos="35:1-35:60" start="3">
<li data-sourcepos="35:1-36:0">Remove Yarn artifacts and switch to pnpm in package.json:</li>
</ol>
<figure class='code'><figcaption>Remove Yarn artifacts<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>git<span class="w"> </span>rm<span class="w"> </span>yarn.lock
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>Declare the pnpm package manager version<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  json'><pre><div class='line'><span></span><span class="p">{</span>
</div><div class='line'><span class="w">  </span><span class="nt">&quot;packageManager&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;pnpm@10&quot;</span>
</div><div class='line'><span class="p">}</span>
</div></pre></td></table></div></figure>
<ol data-sourcepos="47:1-47:39" start="4">
<li data-sourcepos="47:1-48:0">Clean and install deterministically:</li>
</ol>
<figure class='code'><figcaption>Perform a clean pnpm install<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>rm<span class="w"> </span>-rf<span class="w"> </span>node_modules<span class="w"> </span>.yarn<span class="w"> </span>.yarn-cache
</div><div class='line'>pnpm<span class="w"> </span>install<span class="w"> </span>--frozen-lockfile
</div><div class='line'>pnpm<span class="w"> </span>approve-builds
</div></pre></td></table></div></figure>
<ol data-sourcepos="55:1-55:66" start="5">
<li data-sourcepos="55:1-56:0">(Optional) Workspaces: add <code data-sourcepos="55:31-55:51">pnpm-workspace.yaml</code> in a monorepo:</li>
</ol>
<figure class='code'><figcaption>Define the pnpm workspace<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  yaml'><pre><div class='line'><span></span><span class="c1"># Only enable if you meeting build problem as pnpm by default using isolated</span>
</div><div class='line'><span class="c1"># https://pnpm.io/settings#nodelinker</span>
</div><div class='line'><span class="c1"># nodeLinker: hoisted</span>
</div><div class='line'><span class="nt">packages</span><span class="p">:</span>
</div><div class='line'><span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&#39;.&#39;</span>
</div><div class='line'><span class="w">  </span><span class="c1"># - &#39;packages/**&#39;</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="66:1-66:36">How to change Rails to using pnpm</h2>
<ol data-sourcepos="68:1-68:54">
<li data-sourcepos="68:1-69:0">Remove Yarn‑specific deploy helpers (if present):</li>
</ol>
<figure class='code'><figcaption>Gemfile<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  diff'><pre><div class='line'><span></span><span class="gd">- gem &#39;capistrano-yarn&#39;</span>
</div><div class='line'><span class="gi">+ gem &#39;capistrano-pnpm&#39;</span>
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>Capfile<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  diff'><pre><div class='line'><span></span><span class="gd">- require &#39;capistrano/yarn&#39;</span>
</div><div class='line'><span class="gi">+ require &#39;capistrano/pnpm&#39;</span>
</div></pre></td></table></div></figure>
<ol data-sourcepos="80:1-80:70" start="2">
<li data-sourcepos="80:1-81:0">Add a <code data-sourcepos="80:10-80:19">bin/pnpm</code> wrapper so Rails tasks can call pnpm consistently:</li>
</ol>
<figure class='code'><figcaption>Add a bin/pnpm wrapper for Rails tasks<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="ch">#!/usr/bin/env ruby</span>
</div><div class='line'><span class="no">APP_ROOT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="s1">&#39;..&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">__dir__</span><span class="p">)</span>
</div><div class='line'><span class="no">Dir</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="no">APP_ROOT</span><span class="p">)</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">  </span><span class="n">pnpm</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">ENV</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="s1">&#39;PATH&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="no">File</span><span class="o">::</span><span class="no">PATH_SEPARATOR</span><span class="p">)</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">reject</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">|</span><span class="n">dir</span><span class="o">|</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="n">dir</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">__dir__</span><span class="w"> </span><span class="p">}</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">product</span><span class="p">(</span><span class="sx">%w[pnpm pnpm.cmd pnpm.ps1]</span><span class="p">)</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">map</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">|</span><span class="n">dir</span><span class="p">,</span><span class="w"> </span><span class="n">file</span><span class="o">|</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="n">file</span><span class="p">,</span><span class="w"> </span><span class="n">dir</span><span class="p">)</span><span class="w"> </span><span class="p">}</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">find</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">|</span><span class="n">file</span><span class="o">|</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">executable?</span><span class="p">(</span><span class="n">file</span><span class="p">)</span><span class="w"> </span><span class="p">}</span>
</div><div class='line'> </div><div class='line'><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="n">pnpm</span>
</div><div class='line'><span class="w">    </span><span class="nb">exec</span><span class="w"> </span><span class="n">pnpm</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="no">ARGV</span>
</div><div class='line'><span class="w">  </span><span class="k">else</span>
</div><div class='line'><span class="w">    </span><span class="vg">$stderr</span><span class="o">.</span><span class="n">puts</span><span class="w"> </span><span class="s1">&#39;pnpm executable was not detected in the system.&#39;</span>
</div><div class='line'><span class="w">    </span><span class="vg">$stderr</span><span class="o">.</span><span class="n">puts</span><span class="w"> </span><span class="s1">&#39;Install pnpm via https://pnpm.io/installation&#39;</span>
</div><div class='line'><span class="w">    </span><span class="nb">exit</span><span class="w"> </span><span class="mi">1</span>
</div><div class='line'><span class="w">  </span><span class="k">end</span>
</div><div class='line'><span class="k">end</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="101:1-101:40">Make it executable: <code data-sourcepos="101:21-101:39">chmod +x bin/pnpm</code>.</p>
<ol data-sourcepos="103:1-103:85" start="3">
<li data-sourcepos="103:1-104:0">Keep backward compatibility by proxying <code data-sourcepos="103:44-103:53">bin/yarn</code> to pnpm (update existing file):</li>
</ol>
<figure class='code'><figcaption>Proxy bin/yarn to pnpm for compatibility<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div><div data-line='18' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="ch">#!/usr/bin/env ruby</span>
</div><div class='line'><span class="no">APP_ROOT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="s1">&#39;..&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">__dir__</span><span class="p">)</span>
</div><div class='line'><span class="no">Dir</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="no">APP_ROOT</span><span class="p">)</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">  </span><span class="n">pnpm</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">ENV</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="s1">&#39;PATH&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="no">File</span><span class="o">::</span><span class="no">PATH_SEPARATOR</span><span class="p">)</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">reject</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">|</span><span class="n">dir</span><span class="o">|</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="n">dir</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">__dir__</span><span class="w"> </span><span class="p">}</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">product</span><span class="p">(</span><span class="sx">%w[pnpm pnpm.cmd pnpm.ps1]</span><span class="p">)</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">map</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">|</span><span class="n">dir</span><span class="p">,</span><span class="w"> </span><span class="n">file</span><span class="o">|</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="n">file</span><span class="p">,</span><span class="w"> </span><span class="n">dir</span><span class="p">)</span><span class="w"> </span><span class="p">}</span>
</div><div class='line'><span class="w">               </span><span class="o">.</span><span class="n">find</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">|</span><span class="n">file</span><span class="o">|</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">executable?</span><span class="p">(</span><span class="n">file</span><span class="p">)</span><span class="w"> </span><span class="p">}</span>
</div><div class='line'> </div><div class='line'><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="n">pnpm</span>
</div><div class='line'><span class="w">    </span><span class="nb">warn</span><span class="w"> </span><span class="s1">&#39;Deprecated: bin/yarn now proxies to pnpm. Use bin/pnpm.&#39;</span><span class="w"> </span><span class="k">unless</span><span class="w"> </span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PNPM_PROXY_SILENT&#39;</span><span class="o">]</span>
</div><div class='line'><span class="w">    </span><span class="nb">exec</span><span class="w"> </span><span class="n">pnpm</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="no">ARGV</span>
</div><div class='line'><span class="w">  </span><span class="k">else</span>
</div><div class='line'><span class="w">    </span><span class="vg">$stderr</span><span class="o">.</span><span class="n">puts</span><span class="w"> </span><span class="s1">&#39;pnpm executable was not detected in the system.&#39;</span>
</div><div class='line'><span class="w">    </span><span class="vg">$stderr</span><span class="o">.</span><span class="n">puts</span><span class="w"> </span><span class="s1">&#39;Install pnpm via https://pnpm.io/installation&#39;</span>
</div><div class='line'><span class="w">    </span><span class="nb">exit</span><span class="w"> </span><span class="mi">1</span>
</div><div class='line'><span class="w">  </span><span class="k">end</span>
</div><div class='line'><span class="k">end</span>
</div></pre></td></table></div></figure>
<ol data-sourcepos="125:1-125:32" start="4">
<li data-sourcepos="125:1-126:0">Call pnpm from Rails scripts:</li>
</ol>
<figure class='code'><figcaption>Call pnpm from Rails setup scripts<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="c1"># bin/setup (snippet)</span>
</div><div class='line'><span class="nb">system</span><span class="o">!</span><span class="w"> </span><span class="s1">&#39;bin/pnpm&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;install&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;--frozen-lockfile&#39;</span>
</div><div class='line'> </div><div class='line'><span class="c1"># bin/update (snippet)</span>
</div><div class='line'><span class="c1"># system(&#39;bin/pnpm&#39;, &#39;install&#39;, &#39;--frozen-lockfile&#39;)</span>
</div></pre></td></table></div></figure>
<ol data-sourcepos="134:1-134:51" start="5">
<li data-sourcepos="134:1-135:0">Webpacker binaries (if you still use webpacker):</li>
</ol>
<figure class='code'><figcaption>Set the Webpacker node_modules binary path<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="c1"># Add to both bin/webpack and bin/webpack-dev-server</span>
</div><div class='line'><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;WEBPACKER_NODE_MODULES_BIN_PATH&#39;</span><span class="o">]</span><span class="w"> </span><span class="o">||=</span><span class="w"> </span><span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="s1">&#39;../node_modules/.bin&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">__dir__</span><span class="p">)</span>
</div></pre></td></table></div></figure>
<ol data-sourcepos="141:1-141:39" start="6">
<li data-sourcepos="141:1-141:39">Capistrano: use pnpm during deploys.</li>
</ol>
<p data-sourcepos="143:1-143:40">Create <code data-sourcepos="143:8-143:39">lib/capistrano/tasks/pnpm.rake</code>:</p>
<figure class='code'><figcaption>Add the Capistrano pnpm install task<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="c1"># frozen_string_literal: true</span>
</div><div class='line'> </div><div class='line'><span class="n">namespace</span><span class="w"> </span><span class="ss">:pnpm</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">  </span><span class="n">desc</span><span class="w"> </span><span class="s1">&#39;Install JavaScript dependencies using pnpm&#39;</span>
</div><div class='line'><span class="w">  </span><span class="n">task</span><span class="w"> </span><span class="ss">:install</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">    </span><span class="n">on</span><span class="w"> </span><span class="n">roles</span><span class="p">(</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:pnpm_roles</span><span class="p">,</span><span class="w"> </span><span class="ss">:web</span><span class="p">))</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">      </span><span class="n">within</span><span class="w"> </span><span class="n">release_path</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">        </span><span class="n">with</span><span class="w"> </span><span class="n">fetch</span><span class="p">(</span><span class="ss">:pnpm_env</span><span class="p">,</span><span class="w"> </span><span class="p">{})</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">          </span><span class="n">flags</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Array</span><span class="p">(</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:pnpm_flags</span><span class="p">,</span><span class="w"> </span><span class="sx">%w[--frozen-lockfile]</span><span class="p">))</span>
</div><div class='line'><span class="w">          </span><span class="n">execute</span><span class="w"> </span><span class="ss">:pnpm</span><span class="p">,</span><span class="w"> </span><span class="ss">:install</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="n">flags</span>
</div><div class='line'><span class="w">        </span><span class="k">end</span>
</div><div class='line'><span class="w">      </span><span class="k">end</span>
</div><div class='line'><span class="w">    </span><span class="k">end</span>
</div><div class='line'><span class="w">  </span><span class="k">end</span>
</div><div class='line'><span class="k">end</span>
</div><div class='line'> </div><div class='line'><span class="n">before</span><span class="w"> </span><span class="s1">&#39;deploy:updated&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;pnpm:install&#39;</span>
</div></pre></td></table></div></figure>
<p data-sourcepos="163:1-163:26">And in <code data-sourcepos="163:8-163:25">config/deploy.rb</code>:</p>
<figure class='code'><figcaption>Configure Capistrano to run pnpm<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="c1"># Ensure node_modules is NOT a linked dir with pnpm</span>
</div><div class='line'><span class="n">append</span><span class="w"> </span><span class="ss">:linked_dirs</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;log&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;tmp/pids&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;tmp/cache&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;tmp/sockets&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;public/system&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;storage&#39;</span>
</div><div class='line'> </div><div class='line'><span class="c1"># Important: pass strict install flags to pnpm</span>
</div><div class='line'><span class="n">set</span><span class="w"> </span><span class="ss">:pnpm_flags</span><span class="p">,</span><span class="w"> </span><span class="sx">%w(--frozen-lockfile)</span>
</div></pre></td></table></div></figure>
<ol data-sourcepos="172:1-172:71" start="7">
<li data-sourcepos="172:1-173:0">(Optional, webpacker only) Replace the yarn check with a pnpm check:</li>
</ol>
<figure class='code'><figcaption>Replace Webpacker Yarn check with a pnpm check<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="c1"># lib/tasks/pnpm.rake</span>
</div><div class='line'><span class="n">namespace</span><span class="w"> </span><span class="ss">:webpacker</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">  </span><span class="k">begin</span>
</div><div class='line'><span class="w">    </span><span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s1">&#39;webpacker:check_yarn&#39;</span><span class="o">].</span><span class="n">clear</span>
</div><div class='line'><span class="w">  </span><span class="k">rescue</span><span class="w"> </span><span class="no">RuntimeError</span><span class="p">,</span><span class="w"> </span><span class="no">NameError</span>
</div><div class='line'><span class="w">  </span><span class="k">end</span>
</div><div class='line'> </div><div class='line'><span class="w">  </span><span class="n">desc</span><span class="w"> </span><span class="s1">&#39;Verifies if pnpm is installed (replaces webpacker yarn check)&#39;</span>
</div><div class='line'><span class="w">  </span><span class="n">task</span><span class="w"> </span><span class="ss">:check_yarn</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">    </span><span class="n">pnpm_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sb">`pnpm --version`</span><span class="o">.</span><span class="n">strip</span>
</div><div class='line'><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="n">pnpm_version</span><span class="o">.</span><span class="n">empty?</span>
</div><div class='line'><span class="w">      </span><span class="vg">$stderr</span><span class="o">.</span><span class="n">puts</span><span class="w"> </span><span class="s1">&#39;pnpm not installed. Please install pnpm to compile webpacker assets.&#39;</span>
</div><div class='line'><span class="w">      </span><span class="nb">exit!</span>
</div><div class='line'><span class="w">    </span><span class="k">end</span>
</div><div class='line'><span class="w">  </span><span class="k">end</span>
</div><div class='line'><span class="k">end</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="192:1-192:33">How to change CI/CD (optional)</h2>
<p data-sourcepos="194:1-194:69">Keep this for last. After local migration is green, update pipelines.</p>
<h3 data-sourcepos="196:1-196:18">GitHub Actions</h3>
<figure class='code'><figcaption>GitHub Actions<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div></pre></td><td class='main  yaml'><pre><div class='line'><span></span><span class="nt">jobs</span><span class="p">:</span>
</div><div class='line'><span class="w">  </span><span class="nt">build</span><span class="p">:</span>
</div><div class='line'><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ubuntu-latest</span>
</div><div class='line'><span class="w">    </span><span class="nt">steps</span><span class="p">:</span>
</div><div class='line'><span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">actions/checkout@v4</span>
</div><div class='line'><span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">actions/setup-node@v4</span>
</div><div class='line'><span class="w">        </span><span class="nt">with</span><span class="p">:</span>
</div><div class='line'><span class="w">          </span><span class="nt">node-version</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;lts/*&#39;</span>
</div><div class='line'><span class="w">          </span><span class="nt">cache</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;pnpm&#39;</span>
</div><div class='line'><span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">corepack enable &amp;&amp; corepack prepare pnpm@latest --activate</span>
</div><div class='line'><span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">pnpm install --frozen-lockfile</span>
</div><div class='line'><span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">bin/rails assets:precompile</span>
</div></pre></td></table></div></figure>
<h3 data-sourcepos="213:1-213:13">GitLab CI</h3>
<figure class='code'><figcaption>GitLab CI<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div><div data-line='18' class='line-number'></div><div data-line='19' class='line-number'></div></pre></td><td class='main  yaml'><pre><div class='line'><span></span><span class="nt">cache</span><span class="p">:</span>
</div><div class='line'><span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">key</span><span class="p">:</span>
</div><div class='line'><span class="w">      </span><span class="nt">files</span><span class="p">:</span>
</div><div class='line'><span class="w">        </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">pnpm-lock.yaml</span>
</div><div class='line'><span class="w">    </span><span class="nt">paths</span><span class="p">:</span>
</div><div class='line'><span class="w">      </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.pnpm-store/</span>
</div><div class='line'> </div><div class='line'><span class="nt">before_script</span><span class="p">:</span>
</div><div class='line'><span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="p p-Indicator">|</span>
</div><div class='line'><span class="w">    </span><span class="no">if command -v corepack &gt;/dev/null; then</span>
</div><div class='line'><span class="w">      </span><span class="no">corepack enable</span>
</div><div class='line'><span class="w">      </span><span class="no">corepack prepare pnpm@latest --activate</span>
</div><div class='line'><span class="w">    </span><span class="no">else</span>
</div><div class='line'><span class="w">      </span><span class="no">npm i -g pnpm@latest</span>
</div><div class='line'><span class="w">    </span><span class="no">fi</span>
</div><div class='line'> </div><div class='line'><span class="nt">install</span><span class="p">:</span>
</div><div class='line'><span class="w">  </span><span class="nt">script</span><span class="p">:</span>
</div><div class='line'><span class="w">    </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">pnpm install --frozen-lockfile --store-dir .pnpm-store</span>
</div></pre></td></table></div></figure>
<h3 data-sourcepos="235:1-235:12">CircleCI</h3>
<figure class='code'><figcaption>CircleCI<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  yaml'><pre><div class='line'><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">node/install-packages</span><span class="p">:</span>
</div><div class='line'><span class="w">    </span><span class="nt">pkg-manager</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">pnpm</span>
</div><div class='line'><span class="w">    </span><span class="nt">cache-key</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">pnpm-lock.yaml</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="243:1-243:13">Conclusion</h2>
<p data-sourcepos="245:1-245:45">Enjoy faster, stricter, and much leaner pnpm!</p>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[My 2025 Monthly Subscription Review List]]></title>
<link href="https://blog.cloud-mes.com/2025/10/15/my-2025-monthly-subscription-review-list/"/>
<updated>2025-10-15T16:20:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-10-15-my-2025-monthly-subscription-review-list.markdown</id>
<category term="Subscription" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:282">Like the previous years <a data-sourcepos="1:25-1:80" href="https://blog.cloud-mes.com/2022/02/21/my-monthly-subscription-review-list/">2022</a> / <a data-sourcepos="1:84-1:144" href="https://blog.cloud-mes.com/2023/07/06/my-2023-monthly-subscription-review-list/">2023</a> / <a data-sourcepos="1:148-1:208" href="https://blog.cloud-mes.com/2024/07/15/my-2024-monthly-subscription-review-list/">2024</a>, here is my current subscription list. The first number is RMB per month.</p>
<ol data-sourcepos="3:1-22:43">
<li data-sourcepos="3:1-3:17">(68) iCloud 2T</li>
<li data-sourcepos="4:1-4:31">(17) Apple Music family plan</li>
<li data-sourcepos="5:1-5:38">(26) Dragonruby Pro (annual 42 USD)</li>
<li data-sourcepos="6:1-6:47">(30) Bandwagon host (monthly 33 USD, shared)</li>
<li data-sourcepos="7:1-7:34">(6) Adblock Pro (annual 70 RMB)</li>
<li data-sourcepos="8:1-8:36">(6.5) Blog domain (annual 11 USD)</li>
<li data-sourcepos="9:1-9:37">(1.5) 香哈菜谱 (annual 18 RMB)</li>
<li data-sourcepos="10:1-10:55">(49) AWS hosting (3 years 206 USD, monthly 1.35 USD)</li>
<li data-sourcepos="11:1-11:34">(16.8) Meituan biking (monthly)</li>
<li data-sourcepos="12:1-12:57">(47) Google Workspace Business Starter (yearly 80 USD)</li>
<li data-sourcepos="13:1-13:46">(115) Cursor.sh AI editor. (yearly 192 USD)</li>
<li data-sourcepos="14:1-14:43">(8.2) Ivory for Mastodon (yearly 98 RMB)</li>
<li data-sourcepos="15:1-15:48">(8) IndieWeb.Social Backer. (monthly 1.5 SGD)</li>
<li data-sourcepos="16:1-16:49">(31) Sublime Text and Merge (3 years, 152 AUD)</li>
<li data-sourcepos="17:1-17:30">(21) Surge (yearly, 46 USD)</li>
<li data-sourcepos="18:1-18:35">(87) RORvsWild (monthly, 10 EUR)</li>
<li data-sourcepos="19:1-19:34">(156) ChatGPT (monthly, 22 USD)</li>
<li data-sourcepos="20:1-20:46">(17.9) Kapeli Dash.app (yearly, 215.33 RMB)</li>
<li data-sourcepos="21:1-21:47">(49) Kimi Andante for code (monthly, 49 RMB)</li>
<li data-sourcepos="22:1-22:43">(12.6) OpenCode zen (yearly, 151.82 RMB)</li>
</ol>
<p data-sourcepos="24:1-24:105">So totally 773 RMB per month to pay. In the previous year it was 480 RMB, so 61% higher compared to 2024.</p>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Install Oracle instant client 23.3 - the first macOS ARM64 driver and ruby-oci8 gem]]></title>
<link href="https://blog.cloud-mes.com/2025/09/02/install-oracle-instant-client-23-dot-3-the-first-macos-arm64-driver-and-ruby-oci8-gem/"/>
<updated>2025-09-02T14:36:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-09-02-install-oracle-instant-client-23-dot-3-the-first-macos-arm64-driver-and-ruby-oci8-gem.markdown</id>
<category term="Oracle" /><category term="Rails" /><category term="Ruby" />

      <content type="html"><![CDATA[<h1 data-sourcepos="1:1-1:14">Prerequisite</h1>
<ul data-sourcepos="3:1-3:90">
<li data-sourcepos="3:1-4:0">Command line tools for Xcode or Xcode (by executing <code data-sourcepos="3:55-3:78">xcode-select --install</code>) or [Xcode]</li>
</ul>
<h1 data-sourcepos="5:1-5:41">Download Oracle Instant Client Packages</h1>
<p data-sourcepos="8:1-8:118">Go <a data-sourcepos="8:4-8:104" href="https://www.oracle.com/database/technologies/instant-client/macos-arm64-downloads.html">oracle site</a> and download:</p>
<ul data-sourcepos="10:1-12:52">
<li data-sourcepos="10:1-10:54">instantclient-basiclite-macos.arm64-23.3.0.23.09.dmg</li>
<li data-sourcepos="11:1-11:48">instantclient-sdk-macos.arm64-23.3.0.23.09.dmg</li>
<li data-sourcepos="12:1-13:0">instantclient-sqlplus-macos.arm64-23.3.0.23.09.dmg</li>
</ul>
<h1 data-sourcepos="14:1-14:38">Mount DMG package and prepare folder</h1>
<figure class='code'><figcaption>install basiclite<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>hdiutil<span class="w"> </span>mount<span class="w"> </span>~/Downloads/instantclient-basiclite-macos.arm64-23.3.0.23.09.dmg
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/Volumes/instantclient-basiclite-macos.arm64-23.3.0.23.09
</div><div class='line'>sh<span class="w"> </span>./install_ic.sh
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>install sdk<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>hdiutil<span class="w"> </span>mount<span class="w"> </span>~/Downloads/instantclient-sdk-macos.arm64-23.3.0.23.09.dmg
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/Volumes/instantclient-sdk-macos.arm64-23.3.0.23.09
</div><div class='line'>sh<span class="w"> </span>./install_ic.sh
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>install sqlplus<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>hdiutil<span class="w"> </span>mount<span class="w"> </span>~/Downloads/instantclient-sqlplus-macos.arm64-23.3.0.23.09.dmg
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/Volumes/instantclient-sqlplus-macos.arm64-23.3.0.23.09
</div><div class='line'>sh<span class="w"> </span>./install_ic.sh
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>move to opt folder<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>mv<span class="w"> </span>~/Downloads/instantclient_23_3<span class="w"> </span>/opt
</div></pre></td></table></div></figure>
<h1 data-sourcepos="38:1-38:31">Install Oracle Instant Client</h1>
<figure class='code'><figcaption>install ruby-oci8<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">cd</span><span class="w"> </span>/usr/local/bin
</div><div class='line'>sudo<span class="w"> </span>ln<span class="w"> </span>-s<span class="w"> </span>/opt/instantclient_23_3/sqlplus<span class="w"> </span>sqlplus
</div><div class='line'><span class="nb">export</span><span class="w"> </span><span class="nv">OCI_DIR</span><span class="o">=</span>/opt/instantclient_23_3
</div><div class='line'>gem<span class="w"> </span>install<span class="w"> </span>ruby-oci8
</div></pre></td></table></div></figure>
<h1 data-sourcepos="47:1-47:18">Put tnsnames.ora</h1>
<figure class='code'><figcaption>setting TNS_ADMIN<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span><span class="nb">export</span><span class="w"> </span><span class="nv">TNS_ADMIN</span><span class="o">=</span>/opt/instantclient_23_3/network/admin
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Expose local server to the internet without ngrok]]></title>
<link href="https://blog.cloud-mes.com/2025/08/18/expose-local-server-to-the-internet-without-ngrok/"/>
<updated>2025-08-18T21:08:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-08-18-expose-local-server-to-the-internet-without-ngrok.markdown</id>
<category term="Rails" /><category term="Linux" />

      <content type="html"><![CDATA[<h3 data-sourcepos="1:1-1:12">Option 1</h3>
<figure class='code'><figcaption>config/environments/development.rb<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">configure</span><span class="w"> </span><span class="k">do</span>
</div><div class='line'><span class="w">  </span><span class="n">config</span><span class="o">.</span><span class="n">hosts</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="s2">&quot;.example.com&quot;</span>
</div><div class='line'><span class="k">end</span>
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>config/puma.rb<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  ruby'><pre><div class='line'><span></span><span class="n">ssl_bind</span><span class="w"> </span><span class="s2">&quot;0.0.0.0&quot;</span><span class="err">，</span><span class="s2">&quot;8443&quot;</span><span class="err">，</span><span class="p">{</span>
</div><div class='line'><span class="w">  </span><span class="c1"># Run &quot;certbot certificates&quot; to get these</span>
</div><div class='line'><span class="w">  </span><span class="n">key</span><span class="err">：</span><span class="w"> </span><span class="s2">&quot;server.key&quot;</span><span class="o">.</span>
</div><div class='line'><span class="w">  </span><span class="n">cert</span><span class="err">：</span><span class="s2">&quot;server.crt&quot;</span>
</div><div class='line'><span class="p">}</span>
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>expose SSH port：<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>ssh<span class="w"> </span>-R<span class="w"> </span><span class="m">8443</span>:localhost:8443<span class="w"> </span>-N<span class="w"> </span>user@example.com
</div></pre></td></table></div></figure>
<h3 data-sourcepos="21:1-21:12">Option 2</h3>
<figure class='code'><figcaption>Option 2<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>ssh<span class="w"> </span>-C2qTnN<span class="w"> </span>-R<span class="w"> </span><span class="m">3003</span>:localhost:3003<span class="w"> </span>user@ssss.com
</div><div class='line'><span class="c1"># Then, configure an Nginx proxy with a 3003 domain name for external access</span>
</div></pre></td></table></div></figure>
<h3 data-sourcepos="28:1-28:12">Option 3</h3>
<p data-sourcepos="30:1-30:43">Open cursor, goto <em data-sourcepos="30:19-30:24">port</em> tab to create one.</p>
<p data-sourcepos="32:1-32:117">If meet error goto '/Applications/Cursor.app/Contents/Resources/app/bin' and rename <code data-sourcepos="32:85-32:99">cursor-tunnel</code> to <code data-sourcepos="32:104-32:116">code-tunnel</code>.</p>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Sponge command cheetsheet]]></title>
<link href="https://blog.cloud-mes.com/2025/07/27/sponge-command-cheetsheet/"/>
<updated>2025-07-27T00:34:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-07-27-sponge-command-cheetsheet.markdown</id>


      <content type="html"><![CDATA[<figure class='code'><figcaption>Sponge command cheetsheet<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>go<span class="w"> </span>build<span class="w"> </span>-o<span class="w"> </span><span class="k">$(</span>which<span class="w"> </span>sponge<span class="k">)</span><span class="w"> </span>cmd/sponge/main.go
</div><div class='line'>go<span class="w"> </span>build<span class="w"> </span>-o<span class="w"> </span><span class="k">$(</span>which<span class="w"> </span>protoc-gen-go-gin<span class="k">)</span><span class="w"> </span>cmd/protoc-gen-go-gin/main.go
</div><div class='line'>go<span class="w"> </span>build<span class="w"> </span>-o<span class="w"> </span><span class="k">$(</span>which<span class="w"> </span>protoc-gen-go-rpc-tmpl<span class="k">)</span><span class="w"> </span>cmd/protoc-gen-go-rpc-tmpl/main.go
</div><div class='line'>go<span class="w"> </span>build<span class="w"> </span>-o<span class="w"> </span><span class="k">$(</span>which<span class="w"> </span>protoc-gen-json-field<span class="k">)</span><span class="w"> </span>cmd/protoc-gen-json-field/main.go
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Install a new Rails app in Rocky Linux 9.6 at Aliyun]]></title>
<link href="https://blog.cloud-mes.com/2025/07/12/install-a-new-rails-app-in-rocky-linux-9-dot-6-at-aliyun/"/>
<updated>2025-07-12T14:58:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-07-12-install-a-new-rails-app-in-rocky-linux-9-dot-6-at-aliyun.markdown</id>
<category term="Rails" /><category term="Rocky" />

      <content type="html"><![CDATA[<h2 data-sourcepos="1:1-1:18">Disable SELinux</h2>
<figure class='code'><figcaption>Disable SELinux<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>sudo vi /etc/selinux/config
</div><div class='line'>grubby --update-kernel ALL --args selinux=0
</div></pre></td></table></div></figure>
<h2 data-sourcepos="8:1-8:24">Install htop and atop</h2>
<figure class='code'><figcaption>Install htop and atop<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>update
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>epel-release
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>htop
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>atop
</div></pre></td></table></div></figure>
<h2 data-sourcepos="17:1-17:16">Install nginx</h2>
<figure class='code'><figcaption>Install nginx<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>nginx
</div></pre></td></table></div></figure>
<h2 data-sourcepos="23:1-23:22">Install node.js v22</h2>
<p data-sourcepos="25:1-25:129">Using <a data-sourcepos="25:7-25:129" href="https://github.com/nodesource/distributions?tab=readme-ov-file#using-rpm-based-systems-nodejs-22">nodesource distribution</a></p>
<figure class='code'><figcaption>Install node.js v22<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>curl<span class="w"> </span>-fsSL<span class="w"> </span>https://rpm.nodesource.com/setup_22.x<span class="w"> </span>-o<span class="w"> </span>nodesource_setup.sh
</div><div class='line'>sudo<span class="w"> </span>bash<span class="w"> </span>nodesource_setup.sh
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>nodejs
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>groupinstall<span class="w"> </span><span class="s1">&#39;Development Tools&#39;</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="34:1-34:15">Install yarn</h2>
<figure class='code'><figcaption>Install yarn<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>curl<span class="w"> </span>-sL<span class="w"> </span>https://dl.yarnpkg.com/rpm/yarn.repo<span class="w"> </span><span class="p">|</span><span class="w"> </span>sudo<span class="w"> </span>tee<span class="w"> </span>/etc/yum.repos.d/yarn.repo
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>install<span class="w"> </span>yarn
</div></pre></td></table></div></figure>
<h2 data-sourcepos="41:1-41:31">Install rbenv and ruby-build</h2>
<figure class='code'><figcaption>Install rbenv and ruby-build<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>whoami<span class="w"> </span><span class="c1"># should run as a ecs-user</span>
</div><div class='line'>git<span class="w"> </span>clone<span class="w"> </span>https://git.thape.com.cn/rails/rbenv.git<span class="w"> </span>.rbenv
</div><div class='line'><span class="nb">echo</span><span class="w"> </span><span class="s1">&#39;export PATH=&quot;$HOME/.rbenv/bin:$PATH&quot;&#39;</span><span class="w"> </span>&gt;&gt;<span class="w"> </span>~/.bash_profile
</div><div class='line'>~/.rbenv/bin/rbenv<span class="w"> </span>init<span class="w"> </span><span class="c1"># also edit ~/.bash_profile</span>
</div><div class='line'><span class="c1"># As an rbenv plugin</span>
</div><div class='line'>mkdir<span class="w"> </span>-p<span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>rbenv<span class="w"> </span>root<span class="k">)</span><span class="s2">&quot;</span>/plugins
</div><div class='line'>git<span class="w"> </span>clone<span class="w"> </span>https://git.thape.com.cn/rails/ruby-build.git<span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>rbenv<span class="w"> </span>root<span class="k">)</span><span class="s2">&quot;</span>/plugins/ruby-build
</div><div class='line'>git<span class="w"> </span>clone<span class="w"> </span>https://git.thape.com.cn/rails/rbenv-china-mirror.git<span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>rbenv<span class="w"> </span>root<span class="k">)</span><span class="s2">&quot;</span>/plugins/rbenv-china-mirror
</div></pre></td></table></div></figure>
<h2 data-sourcepos="54:1-54:21">Install Ruby 3.2.8</h2>
<p data-sourcepos="56:1-56:34">Ruby 3.2.8 need Rust to build JIT.</p>
<figure class='code'><figcaption>Install Rust and Ruby build dependencies<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>config-manager<span class="w"> </span>--enable<span class="w"> </span>crb
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>libffi-devel<span class="w"> </span>libyaml-devel<span class="w"> </span>readline-devel
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>zlib-devel<span class="w"> </span>openssl-devel<span class="w"> </span>gdbm-devel<span class="w"> </span>ncurses-devel<span class="w"> </span>bzip2-devel
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>rust<span class="w"> </span><span class="c1"># version 1.84.1</span>
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>Install and configure Ruby 3.2.8<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>rbenv<span class="w"> </span>install<span class="w"> </span>-l
</div><div class='line'>rbenv<span class="w"> </span>install<span class="w"> </span><span class="m">3</span>.2.8
</div><div class='line'>rbenv<span class="w"> </span>global<span class="w"> </span><span class="m">3</span>.2.8
</div><div class='line'>rbenv<span class="w"> </span>shell<span class="w"> </span><span class="m">3</span>.2.8
</div><div class='line'><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;gem: --no-document&quot;</span><span class="w"> </span>&gt;<span class="w"> </span>~/.gemrc
</div><div class='line'>gem<span class="w"> </span>update<span class="w"> </span>--system
</div></pre></td></table></div></figure>
<h2 data-sourcepos="74:1-74:39">Prepare the capistrano deploy folder</h2>
<figure class='code'><figcaption>Prepare the capistrano deploy folder<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>whoami<span class="w"> </span><span class="c1"># should run as a ecs-user</span>
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/var/www
</div><div class='line'>sudo<span class="w"> </span>mkdir<span class="w"> </span>harman
</div><div class='line'>sudo<span class="w"> </span>chown<span class="w"> </span>ecs-user:ecs-user<span class="w"> </span>harman
</div></pre></td></table></div></figure>
<h2 data-sourcepos="83:1-83:39">Add Credentials from the source site</h2>
<figure class='code'><figcaption>Add Credentials from the source site<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>vi<span class="w"> </span>.netrc
</div><div class='line'>machine<span class="w"> </span>cnb.cool<span class="w"> </span>login<span class="w"> </span>cnb<span class="w"> </span>password<span class="w"> </span>asdasdas
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Running IRuby Notebook in Cursor]]></title>
<link href="https://blog.cloud-mes.com/2025/05/25/running-iruby-and-notebook-in-cursor/"/>
<updated>2025-05-25T10:02:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-05-25-running-iruby-and-notebook-in-cursor.markdown</id>
<category term="Jupyter" /><category term="Ruby" />

      <content type="html"><![CDATA[<figure class='code'><figcaption>Running IRuby Notebook in Cursor<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>brew<span class="w"> </span>install<span class="w"> </span>ruby
</div><div class='line'>brew<span class="w"> </span>install<span class="w"> </span>python3
</div><div class='line'>brew<span class="w"> </span>install<span class="w"> </span>zeromq<span class="w"> </span><span class="c1"># require by ipykernel</span>
</div><div class='line'>/opt/homebrew/bin/python3<span class="w"> </span>-m<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>ipykernel<span class="w"> </span>-U<span class="w"> </span>--user<span class="w"> </span>--force-reinstall<span class="w"> </span>--break-system-packages
</div><div class='line'>gem<span class="w"> </span>install<span class="w"> </span>iruby
</div><div class='line'>gem<span class="w"> </span>install<span class="w"> </span>rubygems-requirements-system
</div><div class='line'>iruby<span class="w"> </span>register<span class="w"> </span>--force
</div></pre></td></table></div></figure>
<p data-sourcepos="11:1-11:102">Also install <a data-sourcepos="11:14-11:91" href="https://marketplace.cursorapi.com/items?itemName=ms-toolsai.jupyter">Jupyter</a> in cursor.</p>
<p data-sourcepos="13:1-13:72">To make <a data-sourcepos="13:9-13:63" href="https://github.com/lmstudio-ai/mlx-engine">mlx-engine</a> running:</p>
<figure class='code'><figcaption>To make mlx-engine running:<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>python3 -m pip install sentencepiece -U --user --force-reinstall --break-system-packages
</div><div class='line'>python3 -m pip install outlines -U --user --force-reinstall --break-system-packages
</div><div class='line'>python3 -m pip install outlines==1.1.1 --user --force-reinstall --ignore-requires-python --break-system-packages
</div><div class='line'>python3 -m pip install datasets==3.6.0 --user --force-reinstall --ignore-requires-python --break-system-packages
</div><div class='line'>python3 -m pip install mlx_vlm -U --user --force-reinstall --break-system-packages
</div><div class='line'>python3 -m pip install mlx-lm -U --user --force-reinstall --break-system-packages
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Install Open Project v16 in a Rocky Linux 9]]></title>
<link href="https://blog.cloud-mes.com/2025/04/21/install-open-project-v16-in-a-rocky-linux-9/"/>
<updated>2025-04-21T10:04:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-04-21-install-open-project-v16-in-a-rocky-linux-9.markdown</id>
<category term="OpenProject" /><category term="Rails" /><category term="Rocky" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:459">I already install <a data-sourcepos="1:19-1:134" href="https://blog.cloud-mes.com/2023/09/05/install-open-project-v13-in-a-new-rocky-linux-9-dot-2/">an open project instance one and half year ago</a>, but it's retired to sync with the production server OS version, which is Rocky Linux 8.10. After Ruby 3.4 released, I found the nokogiri v1.18 version and grpc v1.71 version both need a new <a data-sourcepos="1:327-1:458" href="https://forums.rockylinux.org/t/upgrade-glibc-2-28-to-2-29/7558/2">GLIBC_2.29 version which is unavailable in Rocky Linux 8 series</a>.</p>
<p data-sourcepos="3:1-3:78">So I decide install a new Open Project instance server for the new production.</p>
<h2 data-sourcepos="5:1-5:18">Disable SELinux</h2>
<figure class='code'><figcaption>Disable SELinux<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>vi /etc/selinux/config
</div><div class='line'>grubby --update-kernel ALL --args selinux=0
</div></pre></td></table></div></figure>
<h2 data-sourcepos="12:1-12:24">Install htop and atop</h2>
<figure class='code'><figcaption>Install htop and atop<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>update
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>epel-release
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>htop
</div><div class='line'>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>atop
</div></pre></td></table></div></figure>
<h2 data-sourcepos="21:1-21:16">Install nginx</h2>
<figure class='code'><figcaption>Install nginx<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>nginx
</div></pre></td></table></div></figure>
<h2 data-sourcepos="27:1-27:22">Install node.js v22</h2>
<p data-sourcepos="29:1-29:129">Using <a data-sourcepos="29:7-29:129" href="https://github.com/nodesource/distributions?tab=readme-ov-file#using-rpm-based-systems-nodejs-22">nodesource distribution</a></p>
<figure class='code'><figcaption>Install node.js v22<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>curl<span class="w"> </span>-fsSL<span class="w"> </span>https://rpm.nodesource.com/setup_22.x<span class="w"> </span>-o<span class="w"> </span>nodesource_setup.sh
</div><div class='line'>sudo<span class="w"> </span>bash<span class="w"> </span>nodesource_setup.sh
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>nodejs
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>groupinstall<span class="w"> </span><span class="s1">&#39;Development Tools&#39;</span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="38:1-38:15">Install yarn</h2>
<figure class='code'><figcaption>Install yarn<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>curl<span class="w"> </span>-sL<span class="w"> </span>https://dl.yarnpkg.com/rpm/yarn.repo<span class="w"> </span><span class="p">|</span><span class="w"> </span>sudo<span class="w"> </span>tee<span class="w"> </span>/etc/yum.repos.d/yarn.repo
</div><div class='line'>sudo<span class="w"> </span>yum<span class="w"> </span>install<span class="w"> </span>yarn
</div></pre></td></table></div></figure>
<h2 data-sourcepos="45:1-45:31">Install postgresql 16 client</h2>
<p data-sourcepos="47:1-47:122">Following <a data-sourcepos="47:11-47:122" href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-rocky-linux-9">DO manual</a></p>
<figure class='code'><figcaption>Install postgresql 16 client<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>dnf module list postgresql
</div><div class='line'>sudo dnf module enable postgresql:16
</div><div class='line'>sudo dnf install postgresql-devel glibc-all-langpacks
</div><div class='line'>sudo dnf install postgresql-contrib # pg_trgm btree_gist require by open project
</div><div class='line'>sudo dnf install mysql-devel # if need to link to mysql server
</div></pre></td></table></div></figure>
<h2 data-sourcepos="57:1-57:34">Setup open_project user account</h2>
<figure class='code'><figcaption>Setup open_project user account<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>adduser<span class="w"> </span>open_project
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/etc/sudoers.d
</div><div class='line'><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;open_project ALL=(ALL) NOPASSWD:ALL&quot;</span><span class="w"> </span>&gt;<span class="w"> </span><span class="m">30</span>-open_project-user
</div><div class='line'>sudo<span class="w"> </span>su<span class="w"> </span>-<span class="w"> </span>open_project
</div><div class='line'>mkdir<span class="w"> </span>.ssh
</div><div class='line'>chmod<span class="w"> </span><span class="m">700</span><span class="w"> </span>.ssh
</div><div class='line'>vi<span class="w"> </span>.ssh/authorized_keys<span class="w"> </span><span class="c1"># and paste your public key</span>
</div><div class='line'>chmod<span class="w"> </span><span class="m">600</span><span class="w"> </span>.ssh/authorized_keys
</div></pre></td></table></div></figure>
<h2 data-sourcepos="70:1-70:31">Install rbenv and ruby-build</h2>
<figure class='code'><figcaption>Install rbenv and ruby-build<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>whoami<span class="w"> </span><span class="c1"># should run as a open_project</span>
</div><div class='line'>git<span class="w"> </span>clone<span class="w"> </span>https://git.thape.com.cn/rails/rbenv.git<span class="w"> </span>.rbenv
</div><div class='line'><span class="nb">echo</span><span class="w"> </span><span class="s1">&#39;export PATH=&quot;$HOME/.rbenv/bin:$PATH&quot;&#39;</span><span class="w"> </span>&gt;&gt;<span class="w"> </span>~/.bash_profile
</div><div class='line'>~/.rbenv/bin/rbenv<span class="w"> </span>init<span class="w"> </span><span class="c1"># also edit ~/.bash_profile</span>
</div><div class='line'><span class="c1"># As an rbenv plugin</span>
</div><div class='line'>mkdir<span class="w"> </span>-p<span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>rbenv<span class="w"> </span>root<span class="k">)</span><span class="s2">&quot;</span>/plugins
</div><div class='line'>git<span class="w"> </span>clone<span class="w"> </span>https://git.thape.com.cn/rails/ruby-build.git<span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>rbenv<span class="w"> </span>root<span class="k">)</span><span class="s2">&quot;</span>/plugins/ruby-build
</div><div class='line'>git<span class="w"> </span>clone<span class="w"> </span>https://git.thape.com.cn/rails/rbenv-china-mirror.git<span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>rbenv<span class="w"> </span>root<span class="k">)</span><span class="s2">&quot;</span>/plugins/rbenv-china-mirror
</div></pre></td></table></div></figure>
<h2 data-sourcepos="83:1-83:21">Install Ruby 3.3.8</h2>
<p data-sourcepos="85:1-85:34">Ruby 3.3.8 need Rust to build JIT.</p>
<figure class='code'><figcaption>Install Rust and Ruby build dependencies<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>dnf<span class="w"> </span>config-manager<span class="w"> </span>--enable<span class="w"> </span>crb
</div><div class='line'>dnf<span class="w"> </span>install<span class="w"> </span>libyaml-devel
</div><div class='line'>yum<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>rust<span class="w"> </span><span class="c1"># version 1.79.0</span>
</div><div class='line'>dnf<span class="w"> </span>install<span class="w"> </span>clang-devel<span class="w"> </span><span class="c1"># for some gem like autocorrect-rb</span>
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>Install and configure Ruby 3.3.8<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>rbenv<span class="w"> </span>install<span class="w"> </span>-l
</div><div class='line'>rbenv<span class="w"> </span>install<span class="w"> </span><span class="m">3</span>.3.8
</div><div class='line'>rbenv<span class="w"> </span>global<span class="w"> </span><span class="m">3</span>.3.8
</div><div class='line'>rbenv<span class="w"> </span>shell<span class="w"> </span><span class="m">3</span>.3.8
</div><div class='line'><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;gem: --no-document&quot;</span><span class="w"> </span>&gt;<span class="w"> </span>~/.gemrc
</div><div class='line'>gem<span class="w"> </span>update<span class="w"> </span>--system
</div></pre></td></table></div></figure>
<h2 data-sourcepos="103:1-103:39">Prepare the capistrano deploy folder</h2>
<figure class='code'><figcaption>Prepare the capistrano deploy folder<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>whoami<span class="w"> </span><span class="c1"># should run as a open_project</span>
</div><div class='line'><span class="nb">cd</span><span class="w"> </span>/var/www
</div><div class='line'>sudo<span class="w"> </span>mkdir<span class="w"> </span>open_project
</div><div class='line'>sudo<span class="w"> </span>chown<span class="w"> </span>open_project:open_project<span class="w"> </span>open_project/
</div></pre></td></table></div></figure>
<h2 data-sourcepos="112:1-112:27">Using mirror when deploy</h2>
<p data-sourcepos="114:1-114:36">Run in the release rails root folder</p>
<figure class='code'><figcaption>Using mirror when deploy<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>bundle<span class="w"> </span>config<span class="w"> </span>mirror.https://rubygems.org<span class="w"> </span>https://gems.ruby-china.com
</div></pre></td></table></div></figure>
<h2 data-sourcepos="120:1-120:36">Setting the open project settings</h2>
<figure class='code'><figcaption>/etc/environment<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div><div data-line='18' class='line-number'></div><div data-line='19' class='line-number'></div><div data-line='20' class='line-number'></div><div data-line='21' class='line-number'></div><div data-line='22' class='line-number'></div><div data-line='23' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>OPENPROJECT_EDITION=bim
</div><div class='line'>OPENPROJECT_APP__TITLE=天华项目全生命周期管理
</div><div class='line'>OPENPROJECT_APP__SHORT__TITLE=PLM
</div><div class='line'>OPENPROJECT_HOST__NAME=plm-staging.thape.com.cn
</div><div class='line'>OPENPROJECT_EMAIL__DELIVERY__METHOD=&quot;smtp&quot;
</div><div class='line'>OPENPROJECT_SMTP__ADDRESS=&quot;smtp.thape.com.cn&quot;
</div><div class='line'>OPENPROJECT_SMTP__PORT=&quot;25&quot;
</div><div class='line'>OPENPROJECT_SMTP__DOMAIN=&quot;thape.com.cn&quot;
</div><div class='line'>OPENPROJECT_SMTP__AUTHENTICATION=&quot;login&quot;
</div><div class='line'>OPENPROJECT_SMTP__USER__NAME=&quot;plm&quot;
</div><div class='line'>OPENPROJECT_SMTP__PASSWORD=&quot;&quot;
</div><div class='line'>OPENPROJECT_SMTP__ENABLE__STARTTLS__AUTO=&quot;true&quot;
</div><div class='line'>OPENPROJECT_SMTP__OPENSSL__VERIFY__MODE=&quot;none&quot;
</div><div class='line'>OPENPROJECT_ENTERPRISE__TRIAL__CREATION__HOST=&quot;https://www.google-analytics.com&quot;
</div><div class='line'>GRUF_OP_SERVER=&quot;172.17.1.1:10009&quot;
</div><div class='line'>WX_TEMPLATE_ID=&quot;&quot;
</div><div class='line'>WX_WORK_PACKAGE_DETAIL=&quot;https://plm.thape.com.cn/work_packages/:id&quot;
</div><div class='line'>MP_QRCODE_ABS_PATH=&quot;/var/www/open_project/shared/public/static/mp_qrcode.jpg&quot;
</div><div class='line'>LOGO_ABS_PATH=&quot;/var/www/open_project/shared/public/static/logo_plm.png&quot;
</div><div class='line'>CSP_FRAME_SRC=&quot;https://ith-workspace.thape.com.cn&quot;
</div><div class='line'>CSP_CONNECT_SRC=&quot;https://analytics.thape.com.cn&quot;
</div><div class='line'>WECHAT_AUTH_JWT_SECERT=&quot;&quot;
</div><div class='line'>WECHAT_AUTH_ITH_URL=&quot;/ith/wechat/ppm/login&quot;
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>/etc/systemd/system/puma_plm.service<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div><div data-line='18' class='line-number'></div><div data-line='19' class='line-number'></div><div data-line='20' class='line-number'></div><div data-line='21' class='line-number'></div><div data-line='22' class='line-number'></div><div data-line='23' class='line-number'></div><div data-line='24' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>[Unit]
</div><div class='line'>Description=Puma HTTP Server for open_project (staging)
</div><div class='line'> </div><div class='line'>After=syslog.target network.target
</div><div class='line'> </div><div class='line'>[Service]
</div><div class='line'>Type=simple
</div><div class='line'>WatchdogSec=10
</div><div class='line'>User=open_project
</div><div class='line'>EnvironmentFile=/etc/environment
</div><div class='line'>WorkingDirectory=/var/www/open_project/current
</div><div class='line'>ExecStart=/home/open_project/.rbenv/bin/rbenv exec bundle exec puma -e production
</div><div class='line'>ExecReload=/bin/kill -SIGUSR1 $MAINPID
</div><div class='line'> </div><div class='line'># if we crash, restart
</div><div class='line'>RestartSec=10
</div><div class='line'>Restart=on-failure
</div><div class='line'> </div><div class='line'>StandardOutput=append:/var/www/open_project/shared/log/puma.log
</div><div class='line'>StandardError=append:/var/www/open_project/shared/log/puma.log
</div><div class='line'> </div><div class='line'>SyslogIdentifier=puma_plm
</div><div class='line'>[Install]
</div><div class='line'>WantedBy=multi-user.target
</div></pre></td></table></div></figure>
<figure class='code'><figcaption>Setting the open project settings<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>daemon-reload
</div><div class='line'>bundle<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>rake<span class="w"> </span>openproject:plugins:register_frontend
</div><div class='line'>bundle<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>rake<span class="w"> </span>i18n:js:export
</div><div class='line'>bundle<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>rake<span class="w"> </span>db:seed
</div><div class='line'>sudo<span class="w"> </span>journalctl<span class="w"> </span>-u<span class="w"> </span>puma_plm<span class="w"> </span><span class="c1"># check system log and fix errors</span>
</div><div class='line'>sudo<span class="w"> </span>systemctl<span class="w"> </span>start<span class="w"> </span>puma_plm
</div></pre></td></table></div></figure>
<h2 data-sourcepos="179:1-179:22">Upload custom fonts</h2>
<figure class='code'><figcaption>Upload custom fonts<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span>/var/www/open_project/shared/public
</div><div class='line'>gzip -9r op_public_files.zip fonts/ static/ WW_verify_*.txt
</div></pre></td></table></div></figure>
<h2 data-sourcepos="186:1-186:12">Change IP</h2>
<p data-sourcepos="188:1-188:98"><a data-sourcepos="188:1-188:97" href="https://docs.rockylinux.org/guides/network/basic_network_configuration/">Rocky 8 network change</a>:</p>
<figure class='code'><figcaption>Edit the Rocky Linux 8 network configuration<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>vi<span class="w"> </span>/etc/sysconfig/network-scripts/ifcfg-ens192
</div></pre></td></table></div></figure>
<p data-sourcepos="194:1-194:23">Rocky 9 network change:</p>
<figure class='code'><figcaption>Reload the Rocky Linux 9 NetworkManager connection<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>vi<span class="w"> </span>/etc/NetworkManager/system-connections/ens192.nmconnection
</div><div class='line'>nmcli<span class="w"> </span>connection<span class="w"> </span>reload<span class="w"> </span>/etc/NetworkManager/system-connections/ens192.nmconnection
</div><div class='line'>nmcli<span class="w"> </span>connection<span class="w"> </span>up<span class="w"> </span>/etc/NetworkManager/system-connections/ens192.nmconnection<span class="w"> </span>
</div></pre></td></table></div></figure>
<h2 data-sourcepos="202:1-202:25">Install the dependency</h2>
<figure class='code'><figcaption>Install the dependency<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>yum<span class="w"> </span>install<span class="w"> </span>ImageMagick
</div></pre></td></table></div></figure>
<h2 data-sourcepos="208:1-208:20">Open the firewall</h2>
<figure class='code'><figcaption>Open the firewall<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>sudo<span class="w"> </span>firewall-cmd<span class="w"> </span>--add-service<span class="o">=</span>http<span class="w"> </span>--permanent
</div><div class='line'>sudo<span class="w"> </span>firewall-cmd<span class="w"> </span>--add-service<span class="o">=</span>https<span class="w"> </span>--permanent
</div><div class='line'>sudo<span class="w"> </span>firewall-cmd<span class="w"> </span>--reload
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
    <entry>
      




<title type="html"><![CDATA[Migrate DB from redis 7.4 to 7.2 and skip the RDB version 12 error]]></title>
<link href="https://blog.cloud-mes.com/2025/04/17/migrate-db-from-redis-7-dot-4-to-7-dot-2-and-skip-the-rdb-version-12-error/"/>
<updated>2025-04-17T11:30:00+08:00</updated>
<id>https://blog.cloud-mes.comrepo://posts.collection/_posts/2025/2025-04-17-migrate-db-from-redis-7-dot-4-to-7-dot-2-and-skip-the-rdb-version-12-error.markdown</id>
<category term="Redis" />

      <content type="html"><![CDATA[<p data-sourcepos="1:1-1:125">After I copy redis <code data-sourcepos="1:20-1:29">dump.rdb</code> from redis 7.4 to my Macbook homebrew redis 7.2 I got Can't handle RDB format version 12 error:</p>
<figure class='code'><figcaption>Redis RDB version 12 error after downgrading to Redis 7.2<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div><div data-line='11' class='line-number'></div><div data-line='12' class='line-number'></div><div data-line='13' class='line-number'></div><div data-line='14' class='line-number'></div><div data-line='15' class='line-number'></div><div data-line='16' class='line-number'></div><div data-line='17' class='line-number'></div><div data-line='18' class='line-number'></div><div data-line='19' class='line-number'></div><div data-line='20' class='line-number'></div><div data-line='21' class='line-number'></div><div data-line='22' class='line-number'></div></pre></td><td class='main  text'><pre><div class='line'><span></span># /opt/homebrew/opt/redis/bin/redis-server /opt/homebrew/etc/redis.conf
</div><div class='line'>20066:C 17 Apr 2025 10:39:34.935 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
</div><div class='line'>20066:C 17 Apr 2025 10:39:34.935 * Redis version=7.2.7, bits=64, commit=00000000, modified=0, pid=20066, just started
</div><div class='line'>20066:C 17 Apr 2025 10:39:34.935 * Configuration loaded
</div><div class='line'>20066:M 17 Apr 2025 10:39:34.935 * Increased maximum number of open files to 10032 (it was originally set to 256).
</div><div class='line'>20066:M 17 Apr 2025 10:39:34.935 * monotonic clock: POSIX clock_gettime
</div><div class='line'> </div><div class='line'> </div><div class='line'>Redis 7.2.7 (00000000/0) 64 bit
</div><div class='line'> </div><div class='line'>Running in standalone mode
</div><div class='line'>Port: 6379
</div><div class='line'>PID: 20066
</div><div class='line'> </div><div class='line'> </div><div class='line'>      https://redis.io
</div><div class='line'> </div><div class='line'> </div><div class='line'>20066:M 17 Apr 2025 10:39:34.936 # WARNING: The TCP backlog setting of 511 cannot be enforced because kern.ipc.somaxconn is set to the lower value of 128.
</div><div class='line'>20066:M 17 Apr 2025 10:39:34.936 * Server initialized
</div><div class='line'>20066:M 17 Apr 2025 10:39:34.936 # Can&#39;t handle RDB format version 12
</div><div class='line'>20066:M 17 Apr 2025 10:39:34.936 # Fatal error loading the DB, check server logs. Exiting.
</div></pre></td></table></div></figure>
<p data-sourcepos="21:1-21:142">Two option available, upgrade redis version via <a data-sourcepos="21:49-21:142" href="https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/homebrew/">redis.io</a></p>
<h3 data-sourcepos="23:1-23:22">Using high version</h3>
<figure class='code'><figcaption>Using high version<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>brew<span class="w"> </span>tap<span class="w"> </span>redis/redis
</div><div class='line'>brew<span class="w"> </span>install<span class="w"> </span>--cask<span class="w"> </span>redis
</div></pre></td></table></div></figure>
<p data-sourcepos="30:1-30:116">The solution is <em data-sourcepos="30:17-30:22">ugly</em> as redis will give you rc version of redis and it depend on the LLVM@18, which takes 1.7GB...</p>
<h3 data-sourcepos="33:1-33:18">Stay redis 7.2</h3>
<figure class='code'><figcaption>Downgrade Homebrew Redis to version 7.2<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div><div data-line='3' class='line-number'></div><div data-line='4' class='line-number'></div><div data-line='5' class='line-number'></div><div data-line='6' class='line-number'></div><div data-line='7' class='line-number'></div><div data-line='8' class='line-number'></div><div data-line='9' class='line-number'></div><div data-line='10' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>redis-cli<span class="w"> </span>shutdown
</div><div class='line'>brew<span class="w"> </span>uninstall<span class="w"> </span>redis
</div><div class='line'>brew<span class="w"> </span>untap<span class="w"> </span>redis/redis
</div><div class='line'><span class="c1"># make sure your DB no need!</span>
</div><div class='line'>rm<span class="w"> </span>/opt/homebrew/var/db/redis/dump.rdb
</div><div class='line'>rm<span class="w"> </span>/opt/homebrew/etc/redis-sentinel.conf
</div><div class='line'>rm<span class="w"> </span>/opt/homebrew/etc/redis.conf
</div><div class='line'>rm<span class="w"> </span>/opt/homebrew/etc/redis.conf.default
</div><div class='line'>brew<span class="w"> </span>install<span class="w"> </span>redis
</div><div class='line'>brew<span class="w"> </span>services<span class="w"> </span>start<span class="w"> </span>redis
</div></pre></td></table></div></figure>
<p data-sourcepos="48:1-48:80">Using <a data-sourcepos="48:7-48:56" href="https://github.com/delano/redis-dump">redis-dump</a> to dump and restore DB.</p>
<figure class='code'><figcaption>Export and restore Redis data with redis-dump<button type='button' class='copy-code' aria-label='Copy code'>Copy</button></figcaption><div class='highlight'><table><td class='line-numbers' aria-hidden='true'><pre><div data-line='1' class='line-number'></div><div data-line='2' class='line-number'></div></pre></td><td class='main  bash'><pre><div class='line'><span></span>redis-dump<span class="w"> </span>-u<span class="w"> </span><span class="m">127</span>.0.0.1:6379<span class="w"> </span>-d<span class="w"> </span><span class="m">0</span><span class="w"> </span>&gt;<span class="w"> </span>db_db0.json
</div><div class='line'>cat<span class="w"> </span>db_db0.json<span class="w"> </span><span class="p">|</span><span class="w"> </span>redis-load<span class="w"> </span>-d<span class="w"> </span><span class="m">0</span>
</div></pre></td></table></div></figure>
]]></content>
    </entry>
  
</feed>
