<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>日常</title><link>https://obsp.de/zh/categories/%E6%97%A5%E5%B8%B8/</link><description>Observer's Space</description><generator>Hugo</generator><language>zh-cn</language><atom:link href="https://obsp.de/zh/categories/%E6%97%A5%E5%B8%B8/" rel="self" type="application/rss+xml"/><item><title>主域名切换至 obsp.dev</title><link>https://obsp.de/zh/posts/domain-switch-to-obsp-dev/</link><guid isPermaLink="true">https://obsp.de/zh/posts/domain-switch-to-obsp-dev/</guid><pubDate>Tue, 15 Sep 2026 02:30:00 +0200</pubDate><description>&lt;h2 id="一全新的主域名"&gt;一、全新的主域名&lt;/h2&gt;
&lt;p&gt;本站的主域名将从 &lt;code&gt;obsp.de&lt;/code&gt; 切换为 &lt;code&gt;obsp.dev&lt;/code&gt;。&lt;del&gt;站长希望走向更国际化的视野，而不是只专注于欧洲，因此作出了这一调整。&lt;/del&gt; (Na ah they just like the vibe)&lt;/p&gt;
&lt;h2 id="二变更范围"&gt;二、变更范围&lt;/h2&gt;
&lt;p&gt;本次变更只影响网站本身（&lt;code&gt;@&lt;/code&gt; 和 &lt;code&gt;www&lt;/code&gt;）。&lt;strong&gt;其余所有服务仍将保留在 &lt;code&gt;obsp.de&lt;/code&gt; 域名下，无需你做任何改动。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现阶段，&lt;code&gt;obsp.dev&lt;/code&gt; 的 &lt;code&gt;www&lt;/code&gt; 与 &lt;code&gt;@&lt;/code&gt; 会重定向到 &lt;code&gt;obsp.de&lt;/code&gt;。2–3 周之后，重定向方向将反过来。届时 RSS 条目可能会失效，在此提前致歉。&lt;/p&gt;
&lt;h2 id="三身份与-wkd"&gt;三、身份与 WKD&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;root@obsp.dev&lt;/code&gt; 如今是站长的另一个身份。OpenPGP 密钥指纹保持不变：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;9967 35BE 4961 80AF B582 ECAB EE31 BB28 ECF6 B268&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;同时，该密钥现已支持通过 WKD 发现，直接运行 &lt;code&gt;gpg --locate-key root@obsp.dev&lt;/code&gt; 即可获取。&lt;/p&gt;
&lt;h2 id="四dnssec"&gt;四、DNSSEC&lt;/h2&gt;
&lt;p&gt;由于该域名仍较新，DNSSEC 暂时尚未启用。预计一个月内将正式上线，届时会采用算法 13（&lt;code&gt;ECDSA-P256-SHA256&lt;/code&gt;）或算法 15（&lt;code&gt;Ed25519&lt;/code&gt;）中的一种。&lt;/p&gt;</description></item><item><title>GoToSocial 搭建与备份心得</title><link>https://obsp.de/zh/posts/gts/</link><guid isPermaLink="true">https://obsp.de/zh/posts/gts/</guid><pubDate>Sat, 27 Jun 2026 03:05:15 +0200</pubDate><description>&lt;h2 id="gts-的安装"&gt;GTS 的安装&lt;/h2&gt;
&lt;p&gt;因为开到了甲骨文，所以决定搭建一个自己的 Fediverse 实例，思来想去选了 &lt;a href="https://gotosocial.org"&gt;GoToSocial&lt;/a&gt;, 比较轻量级，也可以使用各种兼容 Mastodon API 的客户端，美中不足就是比较 bareminimum，像表情回应和引用之类的功能是没有的。&lt;/p&gt;
&lt;p&gt;GTS 官方的 &lt;code&gt;docker-compose.yaml&lt;/code&gt; 长这样：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/example/docker-compose/docker-compose.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gotosocial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/superseriousbusiness/gotosocial:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gotosocial&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;gotosocial&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Change this to your actual host value.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_DB_TYPE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sqlite&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Path in the GtS Docker container where&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# the sqlite.db file will be stored.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_DB_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/gotosocial/storage/sqlite.db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Change this to true if you&amp;#39;re not running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# GoToSocial behind a reverse proxy.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_LETSENCRYPT_ENABLED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set your email address here if you&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# want to receive letsencrypt notices.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_LETSENCRYPT_EMAIL_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Path in the GtS Docker container where the&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Wazero compilation cache will be stored.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_WAZERO_COMPILATION_CACHE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/gotosocial/.cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## For reverse proxy setups:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_TRUSTED_PROXIES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;172.18.0.1/16&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## Set the timezone of your server:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#TZ: UTC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;443:8080&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## For letsencrypt:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#- &amp;#34;80:80&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## For reverse proxy setups:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#- &amp;#34;127.0.0.1:8080:8080&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Your data volume, for your&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# sqlite.db file and media files.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;~/gotosocial/data:/gotosocial/storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# OPTIONAL: To mount volume for the WAZERO&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# compilation cache, for speedier restart&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# times, uncomment the below line:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#- ~/gotosocial/.cache:/gotosocial/.cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;always&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gotosocial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ipam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;subnet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;172.18.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;172.18.0.1&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;我自己用的时候，因为机器上已经有运行着的 Caddy，就把关于反向代理的部分都注释掉了。&lt;/p&gt;
&lt;p&gt;然后在 environment 底下加了几行（GTS 有个单独的 config 文件用来调整环境变量，但是你不想挂载出来的话可以直接在 &lt;code&gt;compose.yml&lt;/code&gt; 里面写，只要把变量名全部大写， &lt;code&gt;-&lt;/code&gt; 换成 &lt;code&gt;_&lt;/code&gt;，在前面加上 &lt;code&gt;GTS_&lt;/code&gt; 就好了，参考&lt;a href="https://docs.gotosocial.org/en/v0.20.3/configuration/#environment-variables"&gt;此处&lt;/a&gt;）：&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_ACCOUNTS_ALLOW_CUSTOM_CSS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 允许自定义 CSS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Europe/Berlin&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_INSTANCE_LANGUAGES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;zh,en,nl,de,fr,ja&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;在 volumes 下面挂载了自定义字体：&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/home/ubuntu/gotosocial/fonts/GeistPixel-Square.woff2:/gotosocial/web/assets/fonts/GeistPixel-Square.woff2:ro&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;这样做之后，就可以在 CSS 中调用啦。&lt;/p&gt;
&lt;h2 id="css-美化"&gt;CSS 美化&lt;/h2&gt;
&lt;p&gt;GTS 用户资料主题的来源有两个，一个是管理员上传主题作为备选项，另一个是用户自行编辑 CSS。这两个效果会叠加。&lt;/p&gt;
&lt;p&gt;全站的 CSS 也是可以自定义的，我用了 &lt;a href="https://catppuccin.com/palette/"&gt;Catppuccin Frappé&lt;/a&gt; 和 &lt;a href="https://vercel.com/font?type=pixel"&gt;Geist Pixel Square&lt;/a&gt;, &lt;a href="https://github.com/TakWolf/fusion-pixel-font"&gt;Fusion Pixel&lt;/a&gt; 的组合。&lt;/p&gt;
&lt;h2 id="数据库和媒体的备份"&gt;数据库和媒体的备份&lt;/h2&gt;
&lt;p&gt;说实话我对自建实例这件事一直很犹豫，因为我对自己的备份没什么信心，如果数据库损坏，签署信息的密钥会丢失，想要从这个域名重新加入联邦会非常非常麻烦。&lt;/p&gt;
&lt;p&gt;GTS 文档里面有关于&lt;a href="https://docs.gotosocial.org/en/v0.20.3/admin/backup_and_restore/"&gt;备份和恢复&lt;/a&gt;的详细教程，其中推荐的数据库备份法是 &lt;a href="https://torsion.org/borgmatic/"&gt;Borgmatic&lt;/a&gt;。&lt;del&gt;不过我比较懒，在写这篇之前没有看这部分所以我没用&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;备份媒体和数据库我用的是 S3 桶，通过 rclone 挂载。&lt;/p&gt;
&lt;p&gt;当然可以把 docker 映射到本机的文件夹里的内容一股脑全部上传，不过这样不太优雅，因为媒体文件包含远端实例的缓存，这些东西吃掉的空间比较多，但并没有备份的必要，只需要保存本实例的媒体即可。&lt;/p&gt;
&lt;p&gt;GTS CLI tools 有 &lt;code&gt;gotosocial admin media list-attachments&lt;/code&gt; 和 &lt;code&gt;gotosocial admin media list-emojis&lt;/code&gt;, 只要在运行的时候加上 &lt;code&gt;--local-only&lt;/code&gt; 的 flag 即可只列出本地媒体文件。&lt;/p&gt;
&lt;p&gt;输出类似于这样：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;/gotosocial/062G5WYKY35KKD12EMSM3F8PJ8/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg
/gotosocial/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH7TDVANYKWVE8VVKFPJTJ.gif
/gotosocial/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH58A357CV5K7R7TJMSH6S.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01CDR64G398ADCHXK08WWTHEZ5.gif&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;利用这个功能，我们可以在定时备份的时候对媒体文件根据本地账号进行筛选。&lt;/p&gt;
&lt;p&gt;以 rclone 备份 docker 容器映射到宿主机的文件夹为例：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/local/bin/rclone-backup-optimized.sh&lt;/code&gt;&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;set -uo pipefail&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;SRC=&amp;#34;/home/ubuntu/gotosocial/data&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;MEDIA_DST=&amp;#34;remote:bucket/media&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DB_DST=&amp;#34;remote:bucket/db&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;CONF=&amp;#34;/home/ubuntu/.config/rclone/rclone.conf&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;COMPOSE_FILE=&amp;#34;/home/ubuntu/gotosocial/docker-compose.yaml&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;SERVICE=&amp;#34;gotosocial&amp;#34; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 容器名称&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;GTS_BIN=&amp;#34;/gotosocial/gotosocial&amp;#34; &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DB_FILE=&amp;#34;/home/ubuntu/gotosocial/data/sqlite.db&amp;#34; &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# SQLite (vacuum into snapshot)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DB_OK=1; DB_OUT=&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;SNAPDIR=$(mktemp -d); SNAP=&amp;#34;$SNAPDIR/sqlite.db&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;if DB_OUT=$(sqlite3 &amp;#34;$DB_FILE&amp;#34; &amp;#34;PRAGMA busy_timeout=10000; VACUUM INTO &amp;#39;$SNAP&amp;#39;&amp;#34; 2&amp;gt;&amp;amp;1); then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CHK=$(sqlite3 &amp;#34;$SNAP&amp;#34; &amp;#34;PRAGMA integrity_check&amp;#34; 2&amp;gt;&amp;amp;1)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;if [ &amp;#34;$CHK&amp;#34; = &amp;#34;ok&amp;#34; ]; then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;if DB_OUT=$(rclone copy &amp;#34;$SNAP&amp;#34; &amp;#34;$DB_DST/&amp;#34; --config &amp;#34;$CONF&amp;#34; 2&amp;gt;&amp;amp;1); then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;DB_OK=0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;DB_OUT=&amp;#34;Snapshot integrity failed: $CHK&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;rm -rf &amp;#34;$SNAPDIR&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# list local account id &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;get_ids() {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sudo docker exec &amp;#34;$SERVICE&amp;#34; \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$GTS_BIN&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;admin media &amp;#34;$1&amp;#34; --local-only 2&amp;gt;/dev/null \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;| grep -oE &amp;#39;/[0-9A-HJKMNP-TV-Z]{26}/(attachment|emoji)/&amp;#39; \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;| grep -oE &amp;#39;[0-9A-HJKMNP-TV-Z]{26}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;IDS=$( { get_ids list-attachments; get_ids list-emojis; } | sort -u )&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# sync media&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;FILTER=$(mktemp)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;while IFS= read -r id; do printf &amp;#39;+ /%s/**\n&amp;#39; &amp;#34;$id&amp;#34;; done &amp;lt;&amp;lt;&amp;lt; &amp;#34;$IDS&amp;#34; &amp;gt;&amp;gt; &amp;#34;$FILTER&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;printf -- &amp;#39;- **\n&amp;#39; &amp;gt;&amp;gt; &amp;#34;$FILTER&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;MEDIA_OUT=$(rclone sync &amp;#34;$SRC&amp;#34; &amp;#34;$MEDIA_DST&amp;#34; --filter-from &amp;#34;$FILTER&amp;#34; --config &amp;#34;$CONF&amp;#34; 2&amp;gt;&amp;amp;1)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;MEDIA_CODE=$?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;rm -f &amp;#34;$FILTER&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;每次备份完后，也可以用 Discord Webhook 发送提醒，防止备份失败自己不知道。&lt;/p&gt;
&lt;p&gt;所以我在脚本中加上了：&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;WEBHOOK=&amp;#34;https://discord.com/api/webhooks/.../...&amp;#34; &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;notify() { &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# $1=title $2=color $3=description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local payload&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;payload=$(jq -n --arg t &amp;#34;$1&amp;#34; --arg d &amp;#34;$3&amp;#34; --arg h &amp;#34;$(hostname)&amp;#34; --argjson c &amp;#34;$2&amp;#34; \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{embeds:[{title:$t, description:$d, color:$c, footer:{text:$h}, timestamp:(now|todate)}]}&amp;#39;&lt;/span&gt;&lt;span class="l"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;curl -sf -H &amp;#34;Content-Type: application/json&amp;#34; -d &amp;#34;$payload&amp;#34; &amp;#34;$WEBHOOK&amp;#34; &amp;gt;/dev/null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;NUM=$(printf &amp;#39;%s\n&amp;#39; &amp;#34;$IDS&amp;#34; | wc -l | tr -d &amp;#39; &amp;#39;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$DB_OK&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="l"&gt;eq 0 ] &amp;amp;&amp;amp; DB_LINE=&amp;#34;Database: success&amp;#34; || DB_LINE=&amp;#34;Database: failed ($(printf &amp;#39;%s&amp;#39; &amp;#34;$DB_OUT&amp;#34; | tail -c 300))&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$MEDIA_CODE&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="l"&gt;eq 0 ] &amp;amp;&amp;amp; MEDIA_LINE=&amp;#34;Media: success (${NUM} local account directory/ies)&amp;#34; || MEDIA_LINE=&amp;#34;Media: failed (exit $MEDIA_CODE)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;if [ &amp;#34;$DB_OK&amp;#34; -eq 0 ] &amp;amp;&amp;amp; [ &amp;#34;$MEDIA_CODE&amp;#34; -eq 0 ]; then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TITLE=&amp;#34;GtS backup success&amp;#34;; COLOR=3066993&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TITLE=&amp;#34;GtS backup failed&amp;#34;; COLOR=15158332&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;TAIL=$(printf &amp;#39;%s&amp;#39; &amp;#34;$MEDIA_OUT&amp;#34; | tail -c 1000)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DESC=$(printf &amp;#39;%s\n%s\n```\n%s\n```&amp;#39; &amp;#34;$DB_LINE&amp;#34; &amp;#34;$MEDIA_LINE&amp;#34; &amp;#34;${TAIL:-（Media no output）}&amp;#34;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;notify &amp;#34;$TITLE&amp;#34; &amp;#34;$COLOR&amp;#34; &amp;#34;$DESC&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="好看好用的前端-phanpy"&gt;好看好用的前端 Phanpy&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://phanpy.social"&gt;Phanpy&lt;/a&gt; 的安装很懒人，因为它是纯静态的，可以直接下载 release 之后用 web server 反代，不过官方并不推荐这么做，推荐的做法是 custom build。&lt;/p&gt;
&lt;p&gt;克隆仓库之后类似这样：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PHANPY_DEFAULT_INSTANCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;social.obsp.de &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;PHANPY_CLIENT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Observer&amp;#39;s Space Social&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	 &lt;span class="nv"&gt;PHANPY_WEBSITE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://phanpy.obsp.de&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;PHANPY_PRIVACY_POLICY_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://social.obsp.de/about &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; npm run build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;dist&lt;/code&gt; 文件夹里就会有我们想要的东西。&lt;/p&gt;
&lt;p&gt;目前 Phanpy 还不支持实例白名单，所以我&lt;a href="https://github.com/devkamiki/phanpy-obsp/commit/5a982630644943785727d856e31839d97b77e8c2"&gt;稍微修改了一下&lt;/a&gt; &lt;code&gt;src/pages/login.jsx&lt;/code&gt;，只能从一个特定实例登录。&lt;/p&gt;</description></item><item><title>给自己做一个 Garmin 表盘</title><link>https://obsp.de/zh/posts/creating-your-own-garmin-watch-face/</link><guid isPermaLink="true">https://obsp.de/zh/posts/creating-your-own-garmin-watch-face/</guid><pubDate>Sun, 19 Apr 2026 21:52:58 +0200</pubDate><description>&lt;p&gt;&lt;img src="https://github.com/devkamiki/New3BingfaWatchFace/blob/main/Preview.png?raw=true" alt="曹氏兵法"&gt;&lt;/p&gt;
&lt;p&gt;这个&lt;a href="https://github.com/devkamiki/New3BingfaWatchFace"&gt;天意表盘&lt;/a&gt;其实已经做好很久了，但一直没时间去优化，所以也很久没有开始写笔记。现在还存在的问题主要是（1） stroke 的图层在渲染的时候是没问题的，但是加载到我的手表上时候，秒针的图层会叠在中心圆点上，导致效果不美观；（2）只兼容支持彩显的手表。&lt;/p&gt;
&lt;p&gt;一开始做的时候傻傻地手搓了半天指针式，效果总是不好，直到我发现 SDK 文件夹的 &lt;code&gt;sample/Analog&lt;/code&gt; 里有指针式的模板，只要在此基础上稍作修改即可……&lt;/p&gt;
&lt;p&gt;再加上 Garmin 官方的 &lt;a href="https://developer.garmin.com/connect-iq/monkey-c/"&gt;Monkey C 教程&lt;/a&gt; 和 &lt;a href="https://developer.garmin.com/connect-iq/api-docs/"&gt;API Docs&lt;/a&gt;, 似乎没有什么需要特别说的。&lt;/p&gt;
&lt;p&gt;值得注意的是，表盘坐标轴的原点是左上角，x 轴的方向是从左到右递增，y 轴的方向是从上到下递增。在给手表加 Hashmark 和数字的时候，用作坐标的不是该元素的中心点而是最低点，所以我写了这么丑陋的东西出来：&lt;/p&gt;
&lt;figure class="code-block" data-language="C&amp;#43;&amp;#43;"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;C&amp;#43;&amp;#43;&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 C&amp;#43;&amp;#43; 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;angleDeg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angleDeg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;180.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rad&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;有朋友可能注意到了，我用了霞鹜文楷和 JetBrains Mono 作为中文和英数的字体。这部分我参考了这篇文章：&lt;a href="https://krasimirtsonev.com/blog/article/how-to-create-garmin-fenix-8-watch-face"&gt;How I wrote a watch face for my Garmin Fēnix 8+&lt;/a&gt;, FNT 格式的字体基本上就是把要的字全部摘出来转化为 PNG 格式，然后以 FNT 文件指示每个字分别在图片的什么地方，方便调用。在将 TTF 转化为 FNT 的时候需要用到 &lt;a href="https://angelcode.com/products/bmfont/"&gt;BMFont&lt;/a&gt;。BMFont 的教程网络上也有很多，此处就不再赘述了。有一点需要注意，Latin + Latin Supplement 的部分不需要想太多，直接全部勾上就可以；中文字符则比较麻烦，通常的做法是先创建一个文本文件，输入所有你需要的字符（比如我的是骄败哀胜兵必），以 &lt;code&gt;UTF-8 with BOM&lt;/code&gt; 的格式保存后导入到 BMFont 中。在很多教程中建议的是 &lt;code&gt;UTF-8&lt;/code&gt;，但我测试之后发现会乱码，如果你遇到差不多的问题，就在这两个格式之间来回切换，看哪个可用。&lt;/p&gt;</description></item><item><title>使用 GnuPG 在 Keyoxide 上验证自己的身份</title><link>https://obsp.de/zh/posts/using-gpg-to-verify-yourself-on-keyoxide/</link><guid isPermaLink="true">https://obsp.de/zh/posts/using-gpg-to-verify-yourself-on-keyoxide/</guid><pubDate>Sat, 26 Jul 2025 20:57:44 +0800</pubDate><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;Keyoxide 类似于 Keybase.io，用于验证在线账户的身份同一性。&lt;/p&gt;
&lt;p&gt;例如，在 Keyoxide.org 上搜索 &lt;a href="mailto:contact@forgejo.org"&gt;contact@forgejo.org&lt;/a&gt;，你会看见 Forgejo 拥有 Mastodon 实例账户 @forgejo@floss.social, 域名 forgejo.org 和 Forgejo 实例账户 @forgejo@codeberg.org .&lt;/p&gt;
&lt;p&gt;你也可以通过 OpenPGP 公钥指纹进行查询。例如，在 Keyoxide.org 上搜索我的指纹（可以在“联系”页面找到），你就能看到 Yuki 声明其拥有的 ActivityPub, Matrix, Forgejo 等账号，还有 obsp.de 这一域名。&lt;/p&gt;
&lt;p&gt;每次进行查询时都会进行一次实时验证，验证成功的条目旁边会显示绿色的对钩。这保证了信息的及时更新。&lt;/p&gt;
&lt;p&gt;目前 Keyoxide 采用两种身份验证系统，一种是 Ariadne Signature Profile，另一种则是已被广泛采用的 OpenPGP.&lt;/p&gt;
&lt;p&gt;Keyoxide 是去中心化的，这不仅仅在于每个人都可以运行自己的 Keyoxide 实例，更在于 OpenPGP 采用了联邦式的信息系统。当你需要获取公钥时，你可以从大型的密钥服务器如 keys.openpgp.org 获取某电子邮件地址所对应的公钥，也可以和你的联系人进行点对点的公钥交换，还可以使用 WKD 获取公钥。&lt;/p&gt;
&lt;p&gt;那么什么是 WKD 呢？WKD 的全称是 Web Key Directory，网络密钥目录，顾名思义，它是一种通过 HTTPS 请求网站的某一目录来获取密钥的方法。假如 Alice 的邮件地址是 &lt;a href="mailto:alice@example.org"&gt;alice@example.org&lt;/a&gt;, Bob 想要获取她的公钥，而他既不能在大型密钥服务器上搜索到有效的结果（因为 Alice 没有公开发布），直接向 Alice 询问又会浪费一定的时间等待她的回复。如果 example.org 具有对 WKD 的支持，那么 Bob 可以运行 &lt;code&gt;gpg --locate-key alice@example.org&lt;/code&gt; 来获取她的公钥。这条命令实际上执行了以下的动作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 &amp;ldquo;alice&amp;rdquo; 这一字符串执行 WKD 哈希，得到哈希字符串 &amp;ldquo;kei1q4tipxxu1yj79k9kfukdhfy631xe&amp;rdquo;
&lt;ul&gt;
&lt;li&gt;你可以在终端运行 &lt;code&gt;gpg-wks-client --print-wkd-hash alice@example.org&lt;/code&gt; 验证&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将哈希字符串、域名 example.org 与 WKD 的通用格式结合，获得 WKD URL
&lt;ul&gt;
&lt;li&gt;它应该形如 &lt;code&gt;https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/kei1q4tipxxu1yj79k9kfukdhfy631xe?l=alice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;或者 &lt;code&gt;https://example.org/.well-known/openpgpkey/example.org/hu/kei1q4tipxxu1yj79k9kfukdhfy631xe?l=alice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第一种，也就是 subdomain method URL，相比起第二种 direct method URL 更受 GnuPG 青睐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;curl 这一 WKD URL 的内容
&lt;ul&gt;
&lt;li&gt;它没有经过 ASCII armor，而是 binary，所以是不可读的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpg --import&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你可能会发现，不同于在密钥服务器上进行的 &lt;code&gt;--search-key&lt;/code&gt; 动作， GnuPG 不会提示你是否选择导入。这是因为发布在 WKD 上的密钥通常被认为是有效而可信的。如果 Alice 是域名 example.org 的所有者，只有她拥有对这一域名下目录的控制权，那么 WKD 发现的密钥可认为是 Alice 亲自发布的。如果 Alice只是 example.org 的用户，那么至少这一密钥是 Alice 的管理员发布的。&lt;/p&gt;
&lt;p&gt;目前支持 WKD 的邮件提供商有 systemli.org, posteo.de, Proton, Mailfence 和 ForwardEmail 等，由于我用的 disroot.org 并没有 WKD 支持，所以你在 Keyoxide 上搜到的公钥会显示是 HKP 服务器的，而 Forgejo 为 forgejo.org 提供了 WKD 支持，Keyoxide 会显示这个公钥来自 WKD，你可以点击 WKD 链接进行查看。&lt;/p&gt;
&lt;p&gt;如果你使用域名邮箱，你可以自己托管 WKD，如果你使用支持 WKD 的提供商的话，可以把子域名 openpgpkey CNAME 指向提供商的域名。如果提供商不支持的话你也可以直接 CNAME 到 wkd.keys.openpgp.org，这是 openpgp.org 提供的 &lt;a href="https://keys.openpgp.org/about/usage#wkd-as-a-service"&gt;WKD as a service&lt;/a&gt; 服务。&lt;/p&gt;
&lt;h2 id="创建-notation"&gt;创建 Notation&lt;/h2&gt;
&lt;p&gt;假设：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你拥有一个域名 doma.in&lt;/li&gt;
&lt;li&gt;你已经为你的电子邮件地址 &lt;a href="mailto:you@doma.in"&gt;you@doma.in&lt;/a&gt; 创建了一个公钥&lt;/li&gt;
&lt;li&gt;你希望证明自己对这个域名的所有权&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么你需要做的事情很简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在公钥上创建一个注记：“我拥有 doma.in”&lt;/li&gt;
&lt;li&gt;为域名添加一条 TXT 记录：“公钥指纹xxx说得对”（这一条记录可以选择性进行哈希）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;聪明的你肯定已经发现了，这两条记录不是等价的。如果仅凭公钥上的注记，我们可以找到 doma.in，而通过 doma.in 上 TXT 记录里的指纹，我们是找不到公钥的。并且由于注记前后的指纹保持不变，你根本不知道公钥指纹xxx说了什么。我们不能也不需要从 doma.in 反推到 &lt;a href="mailto:you@doma.in"&gt;you@doma.in&lt;/a&gt;，这也就是为什么 TXT 记录可以用哈希值代替（至于为什么有人想要哈希，因为他们可能把公钥发布到了某个密钥服务器，在服务器上可以按指纹搜索反推到公钥）。&lt;/p&gt;
&lt;p&gt;公钥上的注记在 Keyoxide 被称为 &lt;em&gt;Identity Claim&lt;/em&gt;，这很好理解，它们是对身份所有权的声明。在声明所有权的同时，它会指示在该身份的哪里可以找到证明。
被声明所有权的物品返回的记录被称为 &lt;em&gt;Identity Proof&lt;/em&gt;，可以在诸如域名的 TXT 记录，git 实例的仓库或联邦宇宙账户的嘟文等出现。这些地方一定是只有账户所有者才能编辑的，比如你不能 claim 某 GitHub 账户的 &lt;em&gt;proof&lt;/em&gt; 在一个不属于你 &lt;em&gt;claim&lt;/em&gt; 的 username 下的 gist 里，道理很简单。&lt;/p&gt;
&lt;p&gt;我们运行以下命令创建注记：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpg --edit-key FINGERPRINT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;list &lt;span class="c1"&gt;#列出这一指纹下的所有uid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uid N &lt;span class="c1"&gt;#选择你要编辑的uid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notation &lt;span class="c1"&gt;#添加新的注记&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;proof@ariadne.id&lt;span class="o"&gt;=&lt;/span&gt;dns:doma.in?type&lt;span class="o"&gt;=&lt;/span&gt;TXT &lt;span class="c1"&gt;#声明你拥有 doma.in，证据在它的 TXT 记录里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;不同的在线身份拥有不同的 &lt;em&gt;claim&lt;/em&gt;，你可以&lt;a href="https://docs.keyoxide.org/service-providers/"&gt;自行查看支持的 &lt;em&gt;claim&lt;/em&gt;/&lt;em&gt;proof&lt;/em&gt; 类型&lt;/a&gt;。总的来说 &lt;em&gt;claim&lt;/em&gt; 都是 &lt;code&gt;proof@ariadne.id=CLAIM&lt;/code&gt; 的格式。&lt;/p&gt;
&lt;h2 id="创建-proof"&gt;创建 Proof&lt;/h2&gt;
&lt;p&gt;我们给 doma.in 加上 &lt;em&gt;proof&lt;/em&gt;，创建一条 TXT 记录，格式是：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openpgp4fpr: FINGERPRINT &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;TTL 多少都可以。&lt;/p&gt;
&lt;p&gt;噢对，你可以用 &lt;code&gt;argon2&lt;/code&gt; 或 &lt;code&gt;bcrypt&lt;/code&gt; 进行哈希，保证一定程度的匿名性。Keyoxide 的文档里贴心地准备了一个&lt;a href="https://docs.keyoxide.org/wiki/identity-proof-formats/#Hashed_URI"&gt;哈希小工具&lt;/a&gt;, 直接把 &lt;code&gt;openpgp4fpr: FINGERPRINT&lt;/code&gt;这一串输进去，然后把输出粘贴到 TXT 记录里。&lt;/p&gt;
&lt;p&gt;现在，把你的密钥上传到 WKD 或 HKP 吧！不出意外，TTL 之内就能在 Keyoxide 搜索邮箱地址或者公钥指纹发现你声明自己拥有 doma.in 并显示验证成功了。&lt;/p&gt;
&lt;h2 id="杂项"&gt;杂项&lt;/h2&gt;
&lt;p&gt;如果你不小心把 &lt;em&gt;claim&lt;/em&gt; 写错的话（经常发生），重新进入 GnuPG 编辑身份的 notation 部分输入 &lt;code&gt;-你写错的notation&lt;/code&gt; 可以删除这条 notation，如果你忘了自己写了哪些 notation，可以编辑身份的时候输入 &lt;code&gt;showpref&lt;/code&gt; 查看。&lt;/p&gt;
&lt;p&gt;我在大部分 &lt;em&gt;proof&lt;/em&gt; 里使用了 &lt;code&gt;argon2&lt;/code&gt; 哈希，不过 Matrix 用的是明文。用哈希值总是会显示验证失败，换了 &lt;code&gt;bcrypt&lt;/code&gt; 也不行，不知道问题具体出在哪里，过段时间看看能不能复现。（更新：解决了，需要用 &lt;code&gt;/plain&lt;/code&gt; 命令确保 Matrix 不会渲染字符串）不过影响不大，这个哈希没什么意义，除非你真的不希望只知道 &lt;em&gt;proof&lt;/em&gt; 所在地的人发现你的 &lt;em&gt;claim&lt;/em&gt; 所在地，并且你不会从其他渠道泄露这个联系…… 这个概率我觉得还挺小的。如果真是这种情况，最好不要 &lt;em&gt;claim&lt;/em&gt;，会破坏你的匿名性。&lt;/p&gt;
&lt;p&gt;Ariadne Signature Profile 的使用以及自己托管 Keyoxide 实例我还没试过，以后可能会再写一篇教程。Keyoxide 的官方文档强烈建议阅读，条理清晰，简单易懂。&lt;/p&gt;</description></item><item><title>本站注册服务现已开启邮件通知</title><link>https://obsp.de/zh/posts/remove-smtp-restriction/</link><guid isPermaLink="true">https://obsp.de/zh/posts/remove-smtp-restriction/</guid><pubDate>Sat, 19 Jul 2025 23:51:31 +0800</pubDate><description>&lt;p&gt;Vikunja 重新开放注册，Vaultwarden, Wallos &amp;amp; Vikunja 的注册现在需要邮件验证。系统通知（不包括 Wallos 的自定义订阅到期通知）均会以 &lt;code&gt;@noreply.obsp.de&lt;/code&gt; 邮件地址发送。&lt;/p&gt;
&lt;p&gt;除了 Vaultwarden 外的服务均可使用注册时的电子邮件重设密码。&lt;/p&gt;
&lt;p&gt;Enjoy~&lt;/p&gt;</description></item><item><title>本站推出新的剪贴板服务 PrivateBin</title><link>https://obsp.de/zh/posts/new-service-private-bin/</link><guid isPermaLink="true">https://obsp.de/zh/posts/new-service-private-bin/</guid><pubDate>Sat, 19 Jul 2025 13:30:29 +0000</pubDate><description>&lt;p&gt;与 Microbin 比起来更加安全，所有内容在浏览器进行加密和解密，服务器对其一无所知。&lt;/p&gt;
&lt;p&gt;服务地址：https://pb.obsp.de&lt;/p&gt;
&lt;p&gt;由于服务器资源限制，本实例禁用上传文件功能。&lt;/p&gt;</description></item><item><title>电子邮件的“自动发现”</title><link>https://obsp.de/zh/posts/email-autodiscovery/</link><guid isPermaLink="true">https://obsp.de/zh/posts/email-autodiscovery/</guid><pubDate>Fri, 18 Jul 2025 20:26:39 +0000</pubDate><description>&lt;p&gt;传统电子邮件客户端采用的自动发现通常通过查询域名 DNS 中特定的 SRV 记录来完成，这些 SRV 记录的标准格式如下：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_submission._tcp SRV 0 1 587 mail.example.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_submissions._tcp SRV 0 1 465 mail.example.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_imap._tcp SRV 0 1 143 imap.example.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_imaps._tcp SRV 0 1 993 imap.example.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_pop3._tcp SRV 0 1 110 pop3.example.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_pop3s._tcp SRV 0 1 995 pop3.example.org.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;其中 submission 和 submissions 对应的是 SMTP 服务器的 STARTTLS 和 SSL/TLS 链接，imap 和 imaps 对应 IMAP 服务器的非 TLS 连接和 SSL/TLS 连接，pop3 和 pop3s 对应 POP3 服务器的非 TLS 连接和 SSL/TLS 连接。&lt;/p&gt;
&lt;p&gt;可以通过设置 SRV 记录的域名为 &lt;code&gt;.&lt;/code&gt; 来明确表示该域名不支持某一特定协议的连接，并且可以通过设置 SRV 记录的优先级来向客户端传递自动配置时优先使用的协议。&lt;/p&gt;
&lt;p&gt;Thunderbird 采用的 autoconfig 则是通过查询 &lt;code&gt;https://autoconfig.example.org/mail/config-v1.1.xml?emailaddress=xxx@example.org&lt;/code&gt; ，返回配置的 XML 文件实现的。例如，在浏览器中访问 &lt;a href="https://autoconfig.disroot.org/mail/config-v1.1.xml?emailaddress=example@disroot.org"&gt;https://autoconfig.disroot.org/mail/config-v1.1.xml?emailaddress=example@disroot.org&lt;/a&gt;, 会返回如下记录：&lt;/p&gt;
&lt;figure class="code-block" data-language="xml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;XML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 xml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!--
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt; autoconfig for Thunderbird as explained here:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt; https://developer.mozilla.org/en/Thunderbird/Autoconfiguration#Configuration_server_at_ISP
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt; looking in path: http://autoconfig.disroot.org/mail/config-v1.1.xml 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;--&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;clientConfig&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1.1&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;emailProvider&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;disroot.org&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;domain&amp;gt;&lt;/span&gt;disroot.org&lt;span class="nt"&gt;&amp;lt;/domain&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;displayName&amp;gt;&lt;/span&gt;disroot.org&lt;span class="nt"&gt;&amp;lt;/displayName&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;displayShortName&amp;gt;&lt;/span&gt;disroot.org&lt;span class="nt"&gt;&amp;lt;/displayShortName&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;incomingServer&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;imap&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;hostname&amp;gt;&lt;/span&gt;disroot.org&lt;span class="nt"&gt;&amp;lt;/hostname&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;993&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;socketType&amp;gt;&lt;/span&gt;SSL&lt;span class="nt"&gt;&amp;lt;/socketType&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;authentication&amp;gt;&lt;/span&gt;password-cleartext&lt;span class="nt"&gt;&amp;lt;/authentication&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;username&amp;gt;&lt;/span&gt;%EMAILLOCALPART%&lt;span class="nt"&gt;&amp;lt;/username&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/incomingServer&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;outgoingServer&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;smtp&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;hostname&amp;gt;&lt;/span&gt;disroot.org&lt;span class="nt"&gt;&amp;lt;/hostname&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;465&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;socketType&amp;gt;&lt;/span&gt;TLS&lt;span class="nt"&gt;&amp;lt;/socketType&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;authentication&amp;gt;&lt;/span&gt;password-cleartext&lt;span class="nt"&gt;&amp;lt;/authentication&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;username&amp;gt;&lt;/span&gt;%EMAILLOCALPART%&lt;span class="nt"&gt;&amp;lt;/username&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/outgoingServer&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/emailProvider&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/clientConfig&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;如果你曾经使用 &lt;a href="https://migadu.com"&gt;Migadu&lt;/a&gt; 提供的邮件托管服务，那么你可能会遵照 Migadu 的指引为你的域名添加如下一条 DNS 记录：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;Host(name)&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Value (Destination)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;CNAME&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;autoconfig&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;.example.org&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;autoconfig.migadu.com.&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这条 DNS 记录是为了让你在使用自定义域名的时候让 Thunderbird 能够正确请求到位于 Migadu 网站上的 XML 记录。&lt;/p&gt;
&lt;p&gt;Thunderbird 并不仅仅依靠 autoconfig 确定邮件服务器采用的配置。如果 autoconfig 发现失败，Thunderbird 会尝试 &lt;code&gt;https://www.example.org/mozilla.xml&lt;/code&gt; 以及传统的 SRV 记录。Mozilla 的数据库中包含了大部分常用邮件域名的服务器配置，如果上述方法均失败，Thunderbird 会尝试从 Mozilla 服务器寻找配置。如果依然失败，Thunderbird 会开始启发式推测配置，依次尝试 imap.域名、pop.域名、pop3.域名、smtp.域名和 mail.域名，并为每个地址测试常见的 2-3 个端口。同时检查 SSL 是否可用，以及服务器在 CAPABILITIES 中声明的认证算法等信息。&lt;/p&gt;
&lt;p&gt;新版的 Microsoft Outlook 能够使用特殊的 SRV 记录发现配置，格式为：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_autodiscover._tcp SRV 0 1 443 autodiscover.example.org&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;虽然采用了 SRV 记录的格式，但 autodiscover 本质上是通过 http 请求完成的，因此大部分服务商会设置该记录的端口号为 443，这样 Outlook 就会直接请求 &lt;code&gt;https://autodiscover.example.org/autodiscover/autodiscover.xml&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;你也可以通过 CNAME 记录来实现这一点，例如 &lt;a href="https://namecrane.com"&gt;NameCrane&lt;/a&gt; 会建议用户添加这样一条 DNS 记录：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;Name/Host&lt;/th&gt;
					&lt;th&gt;Value&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;CNAME&lt;/td&gt;
					&lt;td&gt;autodiscover&lt;/td&gt;
					&lt;td&gt;eu1.workspace.org.&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这样一来，Outlook 在请求 &lt;code&gt;https://autodiscover.example.org/autodiscover/autodiscover.xml&lt;/code&gt; 时就会被重定向到 &lt;code&gt;https://eu1.workspace.org/autodiscover/autodiscover.xml&lt;/code&gt;，不过需要注意的是，你会发现，在 curl 这一链接（需要使用用户名和密码登录）后并不会出现 XML 文本，而是出现这样的页面：&lt;/p&gt;
&lt;figure class="code-block" data-language="html"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;HTML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 html 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Z-Push ActiveSync&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;font&lt;/span&gt; &lt;span class="na"&gt;face&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;verdana&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Z-Push - Open Source ActiveSync&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;GET not supported&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; More information about Z-Push can be found at:&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://z-push.org/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Z-Push homepage&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://z-push.org/download/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Z-Push download page&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://github.com/Z-Hub/Z-Push/issues&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Z-Push Bugtracker&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://github.com/Z-Hub/Z-Push/wiki&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Z-Push Wiki&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; and &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://github.com/Z-Hub/Z-Push/wiki/Roadmap&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Roadmap&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; All modifications to this sourcecode must be published and returned to the community.&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Please see &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://www.gnu.org/licenses/agpl-3.0.html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;AGPLv3 License&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; for details.&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;/font face=&amp;#34;verdana&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;这是因为 NameCrane 的服务器采用了 ActiveSync 协议的开源实现 Z-Push。ActiveSync 不支持采用 HTTP GET 方式直接获取记录，而是需要通过兼容的客户端获取。&lt;/p&gt;
&lt;p&gt;这意味着 NameCrane 提供的 autodiscover 只有支持 ActiveSync 的客户端（例如 Outlook 和 emClient）才能用，而不包括普通的 IMAP/SMTP 等协议的自动发现。我在 Claws Mail 无法发现域名邮箱的配置，在 Thunderbird 上的发现我认为是通过启发式推测实现的，因为 Thunderbird 发现的邮件服务器配置是 &lt;code&gt;mail.example.org&lt;/code&gt; 而不是 &lt;code&gt;eu1.workspace.org&lt;/code&gt;. 为了保证能在 Thunderbird 上自动发现，NameCrane 会指导用户将 mail.example.org CNAME 指向 &lt;code&gt;eu1.workspace.org&lt;/code&gt; 或 &lt;code&gt;us1.workspace.org&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>docker 部署 Ghost 博客，并自定义推送邮件服务（以 Resend 为例）</title><link>https://obsp.de/zh/posts/ghost-docker-smtp/</link><guid isPermaLink="true">https://obsp.de/zh/posts/ghost-docker-smtp/</guid><pubDate>Sun, 29 Jun 2025 13:03:23 +0000</pubDate><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;Ghost 是一个美观、易用、可以自托管的开源博客平台。&lt;/p&gt;
&lt;p&gt;与静态站点生成器（Hugo, Hexo, Jekyll, Astro, Nuxt 等）不同，Ghost 更像 WordPress 和 Typecho，需要独立的数据库，并且具有更为丰富的功能。Ghost 提供了一个管理后台，不仅可以实时保存博客文稿的写作进度，随时进行创作、编辑、发表，还能进行用户权限设置，为一个站点建立多个管理员及创作者用户。Ghost 集成了文章数据分析和触达读者的推送邮件服务，不需要单独设置定时任务或邮件内容。对于需要提供增值服务的博客，Ghost 内置的订阅功能可以识别用户的订阅状态，提供 &amp;ldquo;Subscriber Only&amp;rdquo; 的博文内容等，为专业的博客维护者提供了便利。著名的 Linux 入门博客 &lt;a href="https://itsfoss.com"&gt;It&amp;rsquo;s FOSS&lt;/a&gt; 就是一个 Ghost 实例，如果你曾经订阅 It&amp;rsquo;s FOSS 的新闻邮件，或是注册账户在上面进行评论，那么你可能会注意到， It&amp;rsquo;s FOSS 提供了 $24 / 年的会员订阅，在登录时，会员不会在网站上看到广告。&lt;/p&gt;
&lt;p&gt;由于 Ghost 的 docker 镜像是由社区开发者维护的，没有官方文档指导，我在安装的过程中遇到了一些波折，尤其是设置推送邮件服务。&lt;/p&gt;
&lt;h2 id="安装和设置"&gt;安装和设置&lt;/h2&gt;
&lt;p&gt;我采用的是 &lt;a href="https://hub.docker.com/_/ghost/"&gt;docker 镜像&lt;/a&gt;维护者在文档中建议的 compose.yml，在此基础上进行了小小的改动：&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghost:5-alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;2368&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;2368&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 改成你喜欢的端口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# see https://ghost.org/docs/config/#configuration-options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ChangeItToASecretPassword&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# this url value is just an example, and is likely wrong for your environment!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://example.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# contrary to the default mentioned in the linked documentation, this image defaults to NODE_ENV=production (so development mode needs to be explicitly specified if desired)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#NODE_ENV: development&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./ghost-content:/var/lib/ghost/content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 这里我改了映射路径，方便打包备份，&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 如果你更喜欢 ghost:/var/lib/ghost/content 也可以自己改&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./config/config.production.json:/var/lib/ghost/config.production.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 目的是配置邮件推送&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mariadb:10.6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ChangeItToASecretPassword &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 和上面那个 database__connection__password 要相同哦&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./db:/var/lib/mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;db:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Ghost 自托管版默认通过 Mailgun 发送订阅邮件，可以直接在后台设置域名和 Mailgun API，这是唯一支持的服务提供商。当你使用 docker compose 安装并且没有指定 Mailgun 配置的时候，会尝试从本机以 &lt;code&gt;noreply@example.org&lt;/code&gt; 的地址向注册用户和订阅者发送邮件。当然，如果服务器的 25 端口关闭，或域名没有配置合适的 spf dkim dmarc 记录，这封邮件很可能不会进入读者的收件箱。因此，我们需要编辑 docker 容器内的 &lt;code&gt;/var/lib/ghost/config.production.json&lt;/code&gt;，让它使用合适的 SMTP 服务进行邮件推送。接下来我们会以 Resend 为例，进行配置。&lt;/p&gt;
&lt;p&gt;先登录 Resend 后台，添加域名，这里我们使用 &lt;code&gt;example.org&lt;/code&gt; .&lt;/p&gt;
&lt;p&gt;添加 DNS 记录，看起来应该像是这样：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Content&lt;/th&gt;
					&lt;th&gt;TTL&lt;/th&gt;
					&lt;th&gt;Priority&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;MX&lt;/td&gt;
					&lt;td&gt;send&lt;/td&gt;
					&lt;td&gt;feedback-smtp.eu-west-1.amazonses.com&lt;/td&gt;
					&lt;td&gt;Auto&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TXT&lt;/td&gt;
					&lt;td&gt;send&lt;/td&gt;
					&lt;td&gt;v=spf1 include:amazonses.com ~all&lt;/td&gt;
					&lt;td&gt;Auto&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TXT&lt;/td&gt;
					&lt;td&gt;resend._domainkey&lt;/td&gt;
					&lt;td&gt;p=一长串字符&lt;/td&gt;
					&lt;td&gt;Auto&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TXT&lt;/td&gt;
					&lt;td&gt;_dmarc&lt;/td&gt;
					&lt;td&gt;v=DMARC1; p=none;&lt;/td&gt;
					&lt;td&gt;Auto&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;当所有 DNS 记录验证完毕后，就可以选择 API Keys，获取 API 密钥了。你可以为每个用途单独创建一个 API 密钥，我们将刚刚创建的密钥命名为 Ghost，方便管理。&lt;/p&gt;
&lt;p&gt;还记得刚刚我们映射出了配置文件吗？找到它，并且调整为如下的样子：&lt;/p&gt;
&lt;figure class="code-block" data-language="json"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;JSON&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 json 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://localhost:2368&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2368&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;mail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;transport&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SMTP&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;options&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Resend&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;smtp.resend.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;secureConnection&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;auth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;resend&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pass&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;YourApiKey&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;logging&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;content/logs/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;level&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;rotation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;count&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;period&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1d&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;transports&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;stdout&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;process&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;systemd&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;paths&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;contentPath&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/var/lib/ghost/content&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;这样就大功告成了！所有的订阅邮件将通过 Resend 的 SMTP 服务器传送。由于你授权了该服务器使用 &lt;code&gt;@example.org&lt;/code&gt;，产生的邮件将不会因为域名的 spf dkim dmarc 记录进入垃圾箱。&lt;/p&gt;
&lt;h2 id="后记"&gt;后记&lt;/h2&gt;
&lt;p&gt;Ghost 很美好，很便捷，也很惊艳——但是我最后还是选择了 Hexo，为什么呢？&lt;/p&gt;
&lt;p&gt;我想，这大概和我的不安全感有关——免费 PaaS 和数据库不是很多，并且每次重启都会清空数据。虽然我买了服务器，自己搭建过 Typecho，设置了定时备份，但我还是害怕有一天会因为自己鲁莽的行为丢失博客的配置。我也害怕自己会有一天因为交不起几欧一个月的服务器费用，被迫将站点关停。而能免费托管静态站点的平台很多，Netlify, Statichost.eu, Codeberg Pages, SourceHut Pages&amp;hellip; 数据的存储也无须过于担心，使用 GitHub 或 GitLab 仓库存储所有的站点内容，搬家也很轻松。&lt;/p&gt;
&lt;p&gt;更重要的是，我更欣赏静态站点生成器的轻量和自由——可以接入 Giscus 等外部评论区，不强制读者在站点上注册才能进行评论；可以直接撰写 Markdown 格式的博文，导入和导出更加方便。阻碍我转向 Ghost 的一大原因正是我无法通过后台进行 Markdown 文章的导入，官方只提供了对 WordPress，Medium 等平台的支持，我试图通过魔改 Jekyll 迁移插件实现支持 Hexo 的效果未果。由于实在太麻烦，在经历了五秒的思想斗争后，我放弃了逐项导入所有博文的想法，决定 &lt;code&gt;docker compose down&lt;/code&gt; 逃之夭夭。&lt;/p&gt;
&lt;p&gt;还有，Ghost 的核心功能——会员制度，对我来说确实没什么用。我不关心博文的变现和推广，目标只有一个：当潜在读者使用搜索引擎敲下问题时，他们可能在这里找到答案。&lt;/p&gt;</description></item><item><title>在局域网设备上搭建 OpenCloud.eu 实例</title><link>https://obsp.de/zh/posts/setting-up-opencloud-eu-at-local-network/</link><guid isPermaLink="true">https://obsp.de/zh/posts/setting-up-opencloud-eu-at-local-network/</guid><pubDate>Thu, 26 Jun 2025 15:00:15 +0000</pubDate><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://opencloud.eu"&gt;OpenCloud&lt;/a&gt; 是自建云存储解决方案中的新秀，类似于 NextCloud 和 ownCloud，但更加轻量化、现代化，强调安全、简洁、数字主权与团队合作，由 mailbox.org 的母公司 Heinlein Support 旗下团队研发。&lt;/p&gt;
&lt;p&gt;由于 OpenCloud 是 2025 年 1 月新推出的产品，因此应用市场中可选的插件暂时很少。&lt;/p&gt;
&lt;p&gt;今天我们介绍如何通过 docker compose 在局域网设备上搭建一个 OpenCloud 实例，并通过 Traefik 反代实现局域网域名访问。&lt;/p&gt;
&lt;p&gt;在进行安装前，请确保 OpenCloud 将会运行的机器上安装着任意 Linux 发行版或 MacOS 或 WSL，已经安装 git 和 docker compose，并且至少有 1 GHz 单核 CPU 和 512 MB 内存。具体的配置可以参考&lt;a href="https://docs.opencloud.eu/docs/admin/getting-started/requirements"&gt;官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="安装步骤"&gt;安装步骤&lt;/h2&gt;
&lt;p&gt;OpenCloud 大大简化了局域网中 Collabora Office 实例与云盘连接的配置。由于 Collabora Office 需要 https 连接，那么你需要自行进行局域网域名的自签名 SSL 证书设置。当然，你也可以关闭强制 https，但 Collabora Office 依然强制要求每一段连接中的协议一致，也就是说，必须全程使用 http 或 https。 如果你曾经在局域网中安装 NextCloud 和 Collabora Office 并且只使用 &lt;code&gt;&amp;lt;ip&amp;gt;:&amp;lt;port&amp;gt;&lt;/code&gt; 连接，那么你就会发现，当使用 Cloudflare Tunnel 的时候，由于浏览器客户端到 CF 边缘服务器是 https，而容器内部是 http，你将无法编辑 NextCloud 中的文档。OpenCloud 的懒人版配置文件自动为 Collabora 和 OpenCloud 分配了局域网域名并且生成了证书，你只需在访问的客户端中为这些域名指定 ip 地址并手动信任证书即可。&lt;/p&gt;
&lt;p&gt;在安装之前，确保你的机器上没有运行 Apache, Nginx 或 Caddy 等网络服务器监听 80 和 443 端口。然而，这并不意味着你需要停止正在运行的网络服务器——例如，如果你使用 Caddy 和 Tailscale 进行局域网域名访问，并且希望继续将 OpenCloud 实例通过现有的网络服务器反代，那么你需要注释掉配置文档中关于 Traefik 的部分，并修改默认配置中的域名，最后需要生成自签名证书。由于我没有进行尝试，无法提供准确的配置文件参考，未来可能会补上。&lt;/p&gt;
&lt;p&gt;克隆项目仓库（国内的机器请自备镜像）：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/opencloud-eu/opencloud.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;进入 compose 目录：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; opencloud/deployments/examples/opencloud_full&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Docker，启动！&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose up -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;接下来我们需要在客户端机器的 &lt;code&gt;/etc/hosts&lt;/code&gt; 文件中添加：&lt;/p&gt;
&lt;figure class="code-block" data-language="plaintext"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;PLAINTEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 plaintext 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.128.28 cloud.opencloud.test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ip 地址改成 OpenCloud 宿主机的局域网 ip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.128.28 collabora.opencloud.test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 如果是本机就改成 127.0.0.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.128.28 wopiserver.opencloud.test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 读到这里你是不是想问
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 那手机 (Android, iOS) 怎么办
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 呃……我也不知道&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;在浏览器访问 &lt;a href="https://collabora.opencloud.test"&gt;https://collabora.opencloud.test&lt;/a&gt;, 会提示有安全风险，因为证书是自签名的。选择接受风险并继续即可。如果能看到 OK 字样，说明 Collabora Office 服务器正常运行。&lt;/p&gt;
&lt;p&gt;此时可以访问 &lt;a href="https://cloud.opencloud.test"&gt;https://cloud.opencloud.test&lt;/a&gt;, 接受证书后使用用户名和密码均为 admin 登录，即可享受 OpenCloud 啦！&lt;/p&gt;
&lt;p&gt;Btw，插件市场没有日历和任务应用，我去看了下官方文档，OpenCloud 的 CalDAV 和 CardDAV 功能是通过 Radicale 实现的，在配置文档中取消注释这一部分即可。如果你看过 Observer&amp;rsquo;s Space 之前的详细版 &lt;a href="https://obsp.de/p/2025-03-05-caldav%E5%8F%8Acarddav%E6%9C%8D%E5%8A%A1%E5%99%A8radicale_v3%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97&amp;#43;caddy_v2%E4%B8%80%E8%A1%8C%E5%8F%8D%E4%BB%A3/"&gt;Radicale 设置教程&lt;/a&gt;, 就不需要依赖自动配置了，可以自己动手丰衣足食！&lt;/p&gt;
&lt;p&gt;OpenCloud 的文件浏览界面如图，我挺喜欢这个 UI 的：
&lt;img src="https://i.111666.best/image/PG3DnY9ql8nqgk421Xz1sc.png" alt="OpenCloud Interface"&gt;&lt;/p&gt;</description></item><item><title>在 EndeavourOS 上使用 KDE Connect</title><link>https://obsp.de/zh/posts/using-kdeconnect-on-endeavouros/</link><guid isPermaLink="true">https://obsp.de/zh/posts/using-kdeconnect-on-endeavouros/</guid><pubDate>Fri, 20 Jun 2025 12:22:38 +0000</pubDate><description>&lt;p&gt;如果你使用默认设置安装 EndeavourOS，那么在使用 KDE Connect 时就会发现无法寻找到局域网中的其它设备。&lt;/p&gt;
&lt;p&gt;不必担心，这是默认的防火墙设置导致的。&lt;/p&gt;
&lt;p&gt;右键点击任务栏的防火墙图标，选择“编辑防火墙设置”，如图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.111666.best/image/xVhNMQ7AqDwe1hgRrfaepE.png" alt="Select “Edit Firewall Settings”"&gt;&lt;/p&gt;
&lt;p&gt;在当前网络使用的策略组中勾选 &amp;ldquo;KDE Connect&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.111666.best/image/dKhpt8aSfwJdst1T7IXMz6.png" alt="Allowing KDE Connect in Group Public or whichever you are using"&gt;&lt;/p&gt;
&lt;p&gt;返回 KDE Connect 设置，刷新设备列表，此时你应该能够看到局域网中的设备。&lt;/p&gt;</description></item><item><title>重要迁移通知</title><link>https://obsp.de/zh/posts/important-migration/</link><guid isPermaLink="true">https://obsp.de/zh/posts/important-migration/</guid><pubDate>Sun, 08 Jun 2025 17:16:38 +0000</pubDate><description>&lt;p&gt;经过3个月的测试期，本站正式由 Typecho 搬迁到 Hexo。&lt;/p&gt;
&lt;p&gt;原 Hexo 镜像站域名 &lt;code&gt;hexo.obsp.de&lt;/code&gt; 会重定向到 &lt;code&gt;obsp.de&lt;/code&gt;，在6个月至1年（视访问量而定）后停用。&lt;/p&gt;
&lt;p&gt;由于使用 Giscus 作为评论区，杜绝了游客匿名评论，本站有望大幅减少爬虫机器人在评论区出没的情况。&lt;/p&gt;
&lt;p&gt;由于 Typecho 的永久链接目录中包含 &lt;code&gt;index.php&lt;/code&gt;，站长暂时没有找到让链接维持原样的方法。之后站长会尽可能地更新发布在各大平台上的文章链接，确保能够正确跳转。&lt;/p&gt;</description></item><item><title>在 Virtual Box 中安装 Windows 11 虚拟机</title><link>https://obsp.de/zh/posts/using-win-11-in-virtual-box/</link><guid isPermaLink="true">https://obsp.de/zh/posts/using-win-11-in-virtual-box/</guid><pubDate>Thu, 15 May 2025 15:55:20 +0000</pubDate><description>&lt;p&gt;d大家好，今天分享一下在 Arch Linux (EndeavourOS) 上使用 Virtual Box 启动 Windows 11 家庭版虚拟机的过程。&lt;/p&gt;
&lt;h1 id="下载-iso"&gt;下载 iso&lt;/h1&gt;
&lt;p&gt;我们可以在&lt;a href="https://www.microsoft.com/en-us/software-download/windows11"&gt;这里&lt;/a&gt;的页面下拉，找到 &amp;ldquo;Download Windows 11 Disk Image (ISO) for x64 devices&amp;rdquo;, 按照指引操作即可。&lt;/p&gt;
&lt;h1 id="配置虚拟机"&gt;配置虚拟机&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;打开 Virtual Box，选择 “新建”&lt;/li&gt;
&lt;li&gt;输入虚拟机名称&lt;/li&gt;
&lt;li&gt;在“虚拟光盘”处选择之前下载的 ISO 文件&lt;/li&gt;
&lt;li&gt;“类型”选择“Microsoft Windows”&lt;/li&gt;
&lt;li&gt;“版本”选择“Windows 11（64-bit）”&lt;/li&gt;
&lt;li&gt;勾选“跳过自动安装”
&lt;ul&gt;
&lt;li&gt;接下来我们需要进行一些手动操作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;分配CPU、RAM和硬盘，满足&lt;a href="https://aka.ms/WindowsSysReq"&gt;这里的要求&lt;/a&gt;即可&lt;/li&gt;
&lt;li&gt;确认创建虚拟机&lt;/li&gt;
&lt;li&gt;在虚拟机的“设置-主板”中，取消勾选 EFI 和 Secure Boot
&lt;ul&gt;
&lt;li&gt;这是因为当启用 Secure Boot 和 EFI 的时候，无法从光驱启动，会造成启动失败&lt;/li&gt;
&lt;li&gt;但是 Windows 11 安装程序的系统监测程序会检查 UEFI 和 Secure Boot 的可用性，如果无法通过检查，安装程序就会终止&lt;/li&gt;
&lt;li&gt;我们稍后再来解决这个问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在虚拟机的“设置-处理器”中，勾选“启用PAE/NX”&lt;/li&gt;
&lt;li&gt;启动！&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="开始安装"&gt;开始安装&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;选择键盘配置和地区&lt;/li&gt;
&lt;li&gt;勾选“在这台电脑上安装 Windows 11”&lt;/li&gt;
&lt;li&gt;此时不要选择版本或输入产品密钥&lt;/li&gt;
&lt;li&gt;使用虚拟机软键盘键入“Shift+F10”&lt;/li&gt;
&lt;li&gt;执行命令 &lt;code&gt;regedit&lt;/code&gt; 打开注册表&lt;/li&gt;
&lt;li&gt;导航到目录：HKEY_LOCAL_MACHINE -&amp;gt; SYSTEM -&amp;gt; Setup&lt;/li&gt;
&lt;li&gt;在 Setup 下新建一项，命名为 LabConfig&lt;/li&gt;
&lt;li&gt;添加四个 DWORD 值，名称分别为 &lt;code&gt;BypassTPMCheck&lt;/code&gt;， &lt;code&gt;BypassRAMCheck&lt;/code&gt; ， &lt;code&gt;BypassCPUCheck&lt;/code&gt; 和 &lt;code&gt;BypassSecureBootCheck&lt;/code&gt;，均赋值为1，用来绕过监测程序对 TPM 2.0，处理器、内存和 Secure Boot 的检查&lt;/li&gt;
&lt;li&gt;退出注册表和命令行&lt;/li&gt;
&lt;li&gt;输入 Windows 11 产品密钥，或选择版本&lt;/li&gt;
&lt;li&gt;划分硬盘区域&lt;/li&gt;
&lt;li&gt;按照指引完成 Windows 11 安装&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="完成安装"&gt;完成安装&lt;/h1&gt;
&lt;p&gt;在基本设置完成和第一次启动后，关闭电源，将启动顺序调整为硬盘优先。&lt;/p&gt;</description></item><item><title>moto g53刷入Evolution X</title><link>https://obsp.de/zh/posts/moto-g53-penang-flashing-evolutionx/</link><guid isPermaLink="true">https://obsp.de/zh/posts/moto-g53-penang-flashing-evolutionx/</guid><pubDate>Tue, 06 May 2025 17:47:44 +0000</pubDate><description>&lt;h1 id="前言"&gt;前言&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;解锁有风险，刷机需谨慎！学习不认真，变砖两行泪！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id="找-rom"&gt;找 ROM&lt;/h1&gt;
&lt;p&gt;moto 自带的系统不太好用，所以就要找个合适的 rom。不幸的是，moto g53 这个机型，代号penang，即使全球发售，在各大类原生OS（LineageOS，CalyXOS，DivestOS，crDroid等）的官网上也都找不到rom，更不要说吃上国内的Flyme和澎湃修改版了。&lt;/p&gt;
&lt;p&gt;经过在互联网上坚持不懈地沙里淘金，我终于在 xda 找到了&lt;a href="https://xdaforums.com/t/shared-custom-rom-for-moto-g53-penang.4681392/"&gt;一个帖子&lt;/a&gt;，楼主搜罗了一些 penang可以用的 rom，其中包括 EvolutionX，Matrixx，LineageOS 和 project2by2，只有 project2by2是来自官方的。LineageOS 的刷机包是个半成品，不能用本文所说方法刷入；Matrixx的包有问题，recovery 刷入之后会无限重启。最后我就决定先刷下载得最快（hhh）的Evolution X，再刷2by2（2by2基于LineageOS）。&lt;/p&gt;
&lt;p&gt;Evolution X 这个系统的主要特点是“让所有手机都能获得 pixel 一样的体验”，尽可能在其他厂商的产品上还原 PixelOS 的功能。在提供各种个性化选项的同时，还提供对谷歌应用伪装成 Pixel 8 Pro 和对谷歌相册伪装成 Pixel XL（解锁无限云端备份相片）的功能。但是由于没吃过猪肉，我也不知道到底多像 Pixel，再加上我本身是一个 degoogler，深入地使用g系软件只是为了狠狠地批判它，所以我对这个系统的评价难免有失偏颇。如果你是一个重视隐私/有 FOSS 情结的人，不要刷这个 rom，买个 pixel 刷 GrapheneOS 或者 LineageOS。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s21.ax1x.com/2024/11/19/pAWZvng.png" alt=" “Heading: About Evolution X, text: Evolution X aims to provide users with a Pixel-like feel at first glance, with additional features at their disposal. Above a button: Get Android 14 for your device now. Button: Download.”"&gt;&lt;/p&gt;
&lt;h1 id="解-bootloader"&gt;解 bootloader&lt;/h1&gt;
&lt;h3 id="前置条件"&gt;前置条件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;moto 手机解 bl 的方式比较直观，不需要过小米高考或者降级参加深度测试什么的，不过解bl的前提是在开发者选项里打开 oem unlock，这个选项对于某些新设备而言，需要等待一周左右才能开启。&lt;/li&gt;
&lt;li&gt;找一条可以用于 USB 调试的数据线。&lt;/li&gt;
&lt;li&gt;检查你的 PC 有没有 adb 和 fastboot 这两个命令。如果没有，从&lt;a href="https://developer.android.com/studio"&gt;Google Android开发者&lt;/a&gt;下载Android Studio Ladybug，这一步需要魔法。&lt;/li&gt;
&lt;li&gt;在你放 Android Studio 的那个文件夹里找到有 adb 和 fastboot 这两个文件的目录。不管在哪里，运行的时候记得在这两个文件存在的文件夹里运行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="开始解锁"&gt;开始解锁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在设置里找到“关于本机”，连续点击版本号 7 次，进入开发者模式&lt;/li&gt;
&lt;li&gt;进入之后，打开“ USB 调试”&lt;/li&gt;
&lt;li&gt;移除所有 Google 账号&lt;/li&gt;
&lt;li&gt;关机，然后用 USB 数据线将设备连接到计算机&lt;/li&gt;
&lt;li&gt;运行命令 &lt;code&gt;adb -d reboot bootloader&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;验证设备可以被计算机识别，运行 &lt;code&gt;fastboot devices&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;进入&lt;a href="https://en-us.support.motorola.com/app/standalone/bootloader/unlock-your-device-a"&gt;这个网址&lt;/a&gt;，拉到最下面，选择 &amp;ldquo;Proceed Anyway&amp;rdquo;&lt;/li&gt;
&lt;li&gt;运行命令 &lt;code&gt;fastboot oem get_unlock_data&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将获得的输出（一般为五行以 (bootloader) 开头的字符串）拼接成连续的一串字符串&lt;/li&gt;
&lt;li&gt;粘贴到网页中 &amp;ldquo;Paste the Device ID string into the field below to verify that your device is unlockable:&amp;rdquo; 下的空白处&lt;/li&gt;
&lt;li&gt;根据指引完成确认和解锁&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="下载所需镜像"&gt;下载所需镜像&lt;/h1&gt;
&lt;p&gt;EvolutionX 的社区版 Penang 刷机包在 &lt;a href="https://github.com/evox-penang/android_device_motorola_penang/releases"&gt;GitHub&lt;/a&gt; 可以下载，将 &lt;code&gt;boot.img&lt;/code&gt;, &lt;code&gt;dtbo.img&lt;/code&gt; , &lt;code&gt;evolution_penang-ota-uq1a.240105.004-01232132-COMMUNITY.zip&lt;/code&gt; 和 &lt;code&gt;vendor_boot.img&lt;/code&gt; 都下载下来。&lt;/p&gt;
&lt;h1 id="开始刷机"&gt;开始刷机&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;关机状态下，长按音量下键和电源键，进入 bl&lt;/li&gt;
&lt;li&gt;运行 &lt;code&gt;fastboot flash dtbo dtbo.img&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;运行 &lt;code&gt;fastboot flash vendor_boot vendor_boot.img&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;运行 &lt;code&gt;fastboot flash boot boot.img&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在设备上选择 &amp;ldquo;Recovery&amp;rdquo;&lt;/li&gt;
&lt;li&gt;进入 Recovery 后，选择 &amp;ldquo;Apply Update&amp;rdquo;&lt;/li&gt;
&lt;li&gt;选择 &amp;ldquo;Apply from ADB&amp;rdquo;&lt;/li&gt;
&lt;li&gt;返回计算机，运行 &lt;code&gt;adb -d sideload evolution_penang-ota-uq1a.240105.004-01232132-COMMUNITY.zip&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;侧载过程中可能会出现计算机上的进度条卡在 47% 的情况，不用着急，如果手机提示已经完成侧载，可以重启，那么就选择 &amp;ldquo;Advanced&amp;rdquo;，然后选择 &amp;ldquo;Reboot to Recovery&amp;rdquo;&lt;/li&gt;
&lt;li&gt;进入 Recovery 后，选择 &amp;ldquo;Factory Reset&amp;rdquo;，然后选择 &amp;ldquo;Format data / factory reset&amp;rdquo;，确认&lt;/li&gt;
&lt;li&gt;再次用上面的方法进行侧载，&amp;ldquo;Apply Update&amp;rdquo; -&amp;gt; &amp;ldquo;Apply from ADB&amp;rdquo; -&amp;gt; 运行 &lt;code&gt;adb -d sideload evolution_penang-ota-uq1a.240105.004-01232132-COMMUNITY.zip&lt;/code&gt;，继续在卡 47% 后查看手机提示&lt;/li&gt;
&lt;li&gt;选择 &amp;ldquo;Reboot system now&amp;rdquo;&lt;/li&gt;
&lt;li&gt;如果你能够看见 EvolutionX 的 Logo，恭喜你，大功告成！&lt;/li&gt;
&lt;li&gt;Enjoy~&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>EndeavourOS 日用初探以及 Nvidia GPU 驱动安装</title><link>https://obsp.de/zh/posts/endeavouros-problems/</link><guid isPermaLink="true">https://obsp.de/zh/posts/endeavouros-problems/</guid><pubDate>Mon, 21 Apr 2025 16:22:03 +0000</pubDate><description>&lt;h1 id="前言"&gt;前言&lt;/h1&gt;
&lt;p&gt;一个月前在我的不懈折腾下终于装上了 N 卡驱动开始快乐工作的 Manjaro，还没来得及承担打游戏和跑 LLM 的光荣任务就莫名其妙地壮烈牺牲了，具体体现在系统内选择关机之后有时无法正常进入关机状态，会冒出一堆内含关键词 nvidia 的报错并陷入循环，且风扇开始疯狂地呼呼作响。时间一长，渐渐地连开机都出了问题，经常会启动失败。于是一个念头逐渐在我脑海中成型：要不重装一下吧……&lt;/p&gt;
&lt;p&gt;Manjaro 作为 Arch 衍生物中的日用之王，带来的负面体验很少， N 卡的问题算是其中之一，不过这不是 Manjaro 的问题。&lt;/p&gt;
&lt;p&gt;热爱折腾的我，这次将目光转向了好评如潮的新秀 EndeavourOS。&lt;/p&gt;
&lt;h1 id="缺点"&gt;缺点&lt;/h1&gt;
&lt;p&gt;先说缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EndeavourOS 对于有魔法需求的国内用户不是很友好，当使用 Clash Verge Rev 之类的工具时，大部分的应用都会绕过代理，只能通过开 TUN 模式解决。&lt;/li&gt;
&lt;li&gt;遇到一些不知道是（EndeavourOS 的问题）还是（KDE 的问题）还是（Wayland 的问题）的问题，
&lt;ul&gt;
&lt;li&gt;某网盘软件 AppImage 版本上传和下载的按钮没有反应，而 Manjaro + GNOME + X11 不会，由于无法控制变量，此处暂时没有定论&lt;/li&gt;
&lt;li&gt;Minecraft Launcher 每次开机后只能成功启动一次，此后再启动就会提示错误，必须重启计算机，我没有在任何其他发行版上用过 Minecraft Launcher，此处暂时没有定论&lt;/li&gt;
&lt;li&gt;kmail 会开启后闪退&lt;/li&gt;
&lt;li&gt;KDE Connect 一直找不到我的移动设备，排查确定两台设备接入了同一局域网而且能互相通信，不太清楚问题所在&lt;/li&gt;
&lt;li&gt;CUPS 不能像在 Manjaro 那样自动发现局域网的打印机，不过输入 &lt;code&gt;socket://hostname:9100&lt;/code&gt; 就好了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="优点n-卡用户请看"&gt;优点，N 卡用户请看&lt;/h1&gt;
&lt;p&gt;剩下的都是优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你用的是新的 Nvidia 显卡，那么 EndeavourOS 绝对是 Arch 衍生物中不得不品的一环
&lt;ul&gt;
&lt;li&gt;EndeavourOS ISO 镜像的 boot 引导中可以选择普通还是 N 卡设备，一般来说在这一步选择 N 卡设备，boot 进去后 install，后续就不需要再考虑驱动的问题了&lt;/li&gt;
&lt;li&gt;但是我在这一步没能 boot 成功，报错的内容忘记记下来了（……）最后选择的是普通版&lt;/li&gt;
&lt;li&gt;如果是这样的话也没关系，EndeavourOS 的开发者们维护的 AUR 仓库 &lt;code&gt;endeavouros&lt;/code&gt; 中有一个非常便捷的命令——&lt;code&gt;nvidia-inst&lt;/code&gt;，功能和使用指南可以参考&lt;a href="https://discovery.endeavouros.com/nvidia/new-nvidia-driver-installer-nvidia-inst/2022/03/"&gt;官方文章&lt;/a&gt;和&lt;a href="https://forum.endeavouros.com/t/what-exactly-does-nvidia-inst-do/54997/2"&gt;社区问答&lt;/a&gt;，使用它安装 Nvidia 驱动仅需 2 步：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;yay -S nvidia-inst&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-inst&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果你使用的是其他 Arch 衍生物发行版，只需按照上文中的社区问答添加 endeavouros 仓库和镜像即可&lt;/li&gt;
&lt;li&gt;如果你用的是不再受 Nvidia 官方 Arch 仓库驱动支持的旧版显卡，在命令中加入 &lt;code&gt;--nouveau&lt;/code&gt; 或 &lt;code&gt;-n&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果你需要 Bumbleweed，在命令中加入 &lt;code&gt;-b&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EndeavourOS 自带镜像选择器 &lt;code&gt;reflector&lt;/code&gt; 的 GUI，名叫 Reflector Simple，直接勾选国家或地区即可将该地区的推荐镜像源添加到镜像列表，也可以在 GUI 上选择按照速率/延迟等指标排序镜像&lt;/li&gt;
&lt;li&gt;开发者的审美不错，
&lt;ul&gt;
&lt;li&gt;默认的黑暗紫色主题 KDE 和 Xfce 桌面都很漂亮&lt;/li&gt;
&lt;li&gt;预装的应用不多也不少，不像 Manjaro 那样大而全（自带相当于 pacman+AUR+flatpak GUI 的应用商店这种&lt;del&gt;邪恶的&lt;/del&gt;东西 www），也不像原味 Arch 一样只有骨架，可以说是简约主义和日用功能的完美平衡&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="后记"&gt;后记&lt;/h1&gt;
&lt;p&gt;如果你和我一样，对技术一窍不通，只因 AUR 的魔力使用 Arch 的话，或者你希望在 Linux 上充分发挥 Nvidia GPU 的性能的话，EndeavourOS 非常值得一试。&lt;/p&gt;
&lt;p&gt;最后还是要说一些和 EndeavourOS 或者我的 PC 硬件没有太大关系的碎碎念，本应另开一文，但是以下经验也算是日用体验的一部分吧！&lt;/p&gt;
&lt;h2 id="rime"&gt;Rime&lt;/h2&gt;
&lt;p&gt;Rime 在 KDE + X11 下的配置应该是这样的，在设置-虚拟键盘中选择 fcitx5，输入法中选择中州韵，在 &lt;code&gt;/etc/environment&lt;/code&gt; 键入：&lt;/p&gt;
&lt;figure class="code-block" data-language="shell"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;SHELL&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 shell 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;XMODIFIERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@im&lt;span class="o"&gt;=&lt;/span&gt;fcitx 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;INPUT_METHOD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fcitx 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SDL_IM_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fcitx 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GLFW_IM_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fcitx 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;QT_IM_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fcitx 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GTK_IM_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fcitx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;那这个时候就有小伙伴发现，如果使用的是 Wayland，fcitx 5 启动时会提示你删除 &lt;code&gt;QT_IM_MODULE&lt;/code&gt; 和 &lt;code&gt;GTK_IM_MODULE&lt;/code&gt; 这两行。&lt;/p&gt;
&lt;p&gt;删除之后没有问题的话可以直接跳过这一部分，不过我在日用的时候发现，如果去掉这两行，时不时会产生丢字母的问题。具体表现为尝试输入拼音 &lt;code&gt;ceshi&lt;/code&gt; ，只有 &lt;code&gt;cshi&lt;/code&gt; 被保留在输入法候选框中，而 &lt;code&gt;e&lt;/code&gt; 作为单独的字符掉落下来，键入到文本框中。这个问题说大不大说小不小，打一个完整的句子时非常烦人。即使在虚拟键盘下启用 fcitx 5 Wayland 启动器，也解决不了这个问题。&lt;/p&gt;
&lt;p&gt;有一天我灵机一动，加上了 &lt;code&gt;QT_IM_MODULE&lt;/code&gt; 和 &lt;code&gt;GTK_IM_MODULE&lt;/code&gt; 这两行，然后这个问题就再也没有出现过……&lt;/p&gt;
&lt;h2 id="onlyoffice"&gt;OnlyOffice&lt;/h2&gt;
&lt;p&gt;这个问题是 N 卡和窗口系统的相性导致的。&lt;/p&gt;
&lt;p&gt;问题描述：在使用 X11 窗口系统的时候，安装 N 卡驱动之后，OnlyOffice 的显示比例会变得特别大，参照&lt;a href="https://github.com/ONLYOFFICE/DesktopEditors/issues/324"&gt;这个 Issue&lt;/a&gt;。有时候随着 OnlyOffice 的版本更新，这个问题会自行消失，不过我最近还是遇到了。&lt;/p&gt;
&lt;p&gt;换用 Wayland 就好了。&lt;/p&gt;
&lt;h2 id="2025-6-9-更新双显卡配置steam-游戏启动选项"&gt;2025-6-9 更新：双显卡配置，Steam 游戏启动选项&lt;/h2&gt;
&lt;p&gt;先贴我的系统信息，一个英伟达独显，一个 AMD Radeon 集显：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;Operating System: EndeavourOS 
KDE Plasma Version: 6.3.5
KDE Frameworks Version: 6.14.0
Qt Version: 6.9.1
Kernel Version: 6.14.10-arch1-1 (64-bit)
Graphics Platform: Wayland
Processors: 16 × AMD Ryzen 7 7735H with Radeon Graphics
Memory: 38.4 GiB of RAM
Graphics Processor 1: NVIDIA GeForce RTX 4060 Laptop GPU
Graphics Processor 2: AMD Radeon 680M
Manufacturer: ASUSTeK COMPUTER INC.
Product Name: ASUS TUF Gaming A15 FA507NV_FA507NV
System Version: 1.0&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;我在运行 &lt;code&gt;nvidia-inst&lt;/code&gt; 后又运行了 &lt;code&gt;paru -S mesa lib32-mesa xf86-video-amdgpu vulkan-radeon lib32-vulkan-radeon libva-mesa-driver lib32-libva-mesa-driver mesa-vdpau lib32-mesa-vdpau&lt;/code&gt; ，然后就可以愉快食用了（如果不装集显的驱动就会两个卡槽都用 N 卡的 OpenGL，显示在系统信息里的会是两个英伟达 GPU）。&lt;/p&gt;
&lt;p&gt;为什么突然现在更新呢，因为我把集显的事彻底忘在了脑后，我天真地以为只要假装集显不存在就好了。直到用 Steam 和 Proton 启动 2077 的时候一直报错（如图所示），但是正确安装驱动的情况下应该是不会有这个 OpenGL 的问题的，再加上 ProtonDB 上评级为 Native 和 Platinum 的红弦俱乐部和逆转裁判123在启动时黑屏，我不由得开始怀疑是不是自己的显卡驱动真的有问题。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.111666.best/image/yHlbHLCCrPaB3Jv5yTi1BI.png" alt="cyberpunk2077报错"&gt;&lt;/p&gt;
&lt;p&gt;在这个报错之前我完全没怀疑是显卡的问题，试了很多种启动参数都没能玩上游戏，但是这个关键的错误启发了我：2077 可能在尝试使用我的集成显卡……&lt;/p&gt;
&lt;p&gt;于是，我在启动参数中强制游戏使用独显：&lt;code&gt;_NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia %command%&lt;/code&gt;，问题迎刃而解。&lt;/p&gt;
&lt;p&gt;最后我就去把集显驱动装上了……就是这样……&lt;/p&gt;
&lt;p&gt;参考文献：&lt;a href="https://wiki.archlinuxcn.org/wiki/PRIME#%E9%85%8D%E7%BD%AE%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E4%BD%BF%E7%94%A8_GPU_%E6%B8%B2%E6%9F%93"&gt;PRIME&lt;/a&gt;&lt;/p&gt;</description></item><item><title>docker 自建 Ntfy 实例以及提醒功能（接入 Uptime Kuma，Wallos 等）实战</title><link>https://obsp.de/zh/posts/ntfy-selfhosting/</link><guid isPermaLink="true">https://obsp.de/zh/posts/ntfy-selfhosting/</guid><pubDate>Thu, 10 Apr 2025 12:27:54 +0000</pubDate><description>&lt;h1 id="前言"&gt;前言&lt;/h1&gt;
&lt;p&gt;大家好，两个星期没更新了，&lt;del&gt;想必根本没有人想我罢&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;今天我们来搭建一个 Ntfy 实例，照例还是使用非常方便的 docker compose。&lt;/p&gt;
&lt;p&gt;Ntfy 的工作原理非常简单，以官方实例 &lt;code&gt;ntfy.sh&lt;/code&gt; 为例，这个实例是没有密码保护的，当用户在订阅任意的主题（通常是 &lt;code&gt;https://ntfy.sh/themename&lt;/code&gt; 的格式）时，任何发送到这个主题的通知信息都会被用户获取。因此，一些使用 Ntfy 官方实例进行通知推送的软件（如 Fluffychat）会自动生成一串随机字符，防止其他用户不慎和你订阅了同一个主题而获取你的通知信息。然而，使用诸如官方实例或是 &lt;a href="https://push.tchncs.de"&gt;tchncs.de 的 ntfy 实例&lt;/a&gt;这样的实例终究会因为主题的开放而存在一定的隐私或安全风险，更多的小伙伴还是希望能够用密码保护通知内容。那么今天我们就来一起看看如何搭建和管理自己的 Ntfy 实例吧！&lt;/p&gt;
&lt;h1 id="部署过程"&gt;部署过程&lt;/h1&gt;
&lt;p&gt;Ntfy 支持多种部署方式，并且有详尽的&lt;a href="https://docs.ntfy.sh/install/"&gt;官方文档&lt;/a&gt;，感兴趣的读者可以自行查阅。&lt;/p&gt;
&lt;p&gt;直接上 &lt;code&gt;compose.yml&lt;/code&gt; ：&lt;/p&gt;
&lt;figure class="code-block" data-language="YAML"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 YAML 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-YAML" data-lang="YAML"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ntfy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;binwiederhier/ntfy &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ntfy &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NTFY_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://ntfy.example.org &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NTFY_CACHE_FILE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/lib/ntfy/cache.db &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NTFY_AUTH_FILE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/lib/ntfy/auth.db &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NTFY_AUTH_DEFAULT_ACCESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deny-all &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NTFY_BEHIND_PROXY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NTFY_ATTACHMENT_CACHE_DIR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/lib/ntfy/attachments &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NTFY_ENABLE_LOGIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./:/var/lib/ntfy &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;serve&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;这样子，一个具有访问控制功能的 Ntfy 实例就建成了。&lt;/p&gt;
&lt;p&gt;请配置反向代理（Nginx, Caddy, Apache 等），将 &lt;code&gt;https://ntfy.example.org&lt;/code&gt; 反代到 &lt;code&gt;localhost:8080&lt;/code&gt;。如果一切顺利，访问 &lt;code&gt;https://ntfy.example.org&lt;/code&gt; 即可看到 Ntfy 网页客户端的界面！&lt;/p&gt;
&lt;p&gt;此时，默认对所有主题的控制策略都是 &lt;code&gt;deny&lt;/code&gt; ，我们需要创建用户，并且为它们分配可以读或写某些主题的权限。&lt;/p&gt;
&lt;p&gt;当没有进行容器化安装时，用户的创建和管理都是通过 &lt;code&gt;ntfy&lt;/code&gt; 命令来完成，而可执行的 &lt;code&gt;ntfy&lt;/code&gt; 位于 docker 容器内部，此时需要指明 &lt;code&gt;ntfy&lt;/code&gt; 所在的容器名称。由于我们在 &lt;code&gt;compose.yml&lt;/code&gt; 中进行了命名：&lt;code&gt;container_name: ntfy&lt;/code&gt; ，故该容器的名称应为 &lt;code&gt;ntfy&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;创建用户的命令如下：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 创建管理员，可以访问所有的主题。adminname改成你想要的管理员用户名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; -it ntfy ntfy user add --role&lt;span class="o"&gt;=&lt;/span&gt;admin adminname
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 创建普通用户，默认无法访问任何主题。username改成你想要的用户名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; -it ntfy ntfy user add --role&lt;span class="o"&gt;=&lt;/span&gt;user username
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 修改用户的访问权限，格式如下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; -it ntfy ntfy access &amp;lt;username&amp;gt; &amp;lt;theme&amp;gt; &amp;lt;read-write/read-only/write-only/deny&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;比如说，Alice 希望自己能够获取所有主题的通知，那么就执行 &lt;code&gt;docker exec -it ntfy ntfy user add --role=admin alice&lt;/code&gt; ，根据提示输入密码。&lt;/p&gt;
&lt;p&gt;Alice 运行着一个 Uptime Kuma 实例，她希望在服务不可用的时候收到 Uptime Kuma 向主题 &lt;code&gt;uptime&lt;/code&gt; 的推送通知，但是又不希望 Uptime Kuma 获取到其他主题的推送通知，于是她为此创建了一个普通用户：&lt;code&gt;docker exec -it ntfy ntfy user add --role=user uptimekuma&lt;/code&gt; , 根据提示输入密码，然后给予对应的权限： &lt;code&gt;docker exec -it ntfy ntfy access uptimekuma uptime read-write&lt;/code&gt; 。完成之后，在 Uptime Kuma 中填写用户名 &lt;code&gt;uptimekuma&lt;/code&gt; 、密码、实例网址和主题名称 &lt;code&gt;https://ntfy.example.org/uptime&lt;/code&gt; 即可。&lt;/p&gt;
&lt;h1 id="实用案例"&gt;实用案例&lt;/h1&gt;
&lt;p&gt;能够使用 Ntfy 进行推送通知的应用还有很多，如哪吒探针和 Wallos 等。我们也可以通过编写自动化脚本，在任务触发或状态改变的时候订阅了某个主题的客户端发送消息。&lt;/p&gt;
&lt;p&gt;例如，前段时间我让 Claude 写了个脚本，当有进程对 CPU 的占用超过10%的时候，就把进程的名称以及占用情况输出到一个文件中，并且将进程停止，报告到我的 ntfy 实例的一个特定主题。&lt;/p&gt;
&lt;p&gt;脚本内容如下：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 设置变量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;cpu_limit.log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NTFY_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://ntfy.example.io/xxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NTFY_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NTFY_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CPU_THRESHOLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 创建日志文件或清空现有文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: 开始监控 CPU 使用率...&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 持续监控进程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; true&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 获取 CPU 使用率超过阈值的进程列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;processes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;ps aux &lt;span class="p"&gt;|&lt;/span&gt; awk -v &lt;span class="nv"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$CPU_THRESHOLD&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$3 &amp;gt; threshold {print $2, $3, $11}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$processes&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: 发现 CPU 使用率超过 &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CPU_THRESHOLD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% 的进程:&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 遍历每个超过阈值的进程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$processes&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; pid cpu_usage process_name&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; PID: &lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;, 名称: &lt;/span&gt;&lt;span class="nv"&gt;$process_name&lt;/span&gt;&lt;span class="s2"&gt;, CPU使用率: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;cpu_usage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 停止进程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nv"&gt;$pid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;kill_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$kill_status&lt;/span&gt; -eq &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; 已停止进程 &lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="nv"&gt;$process_name&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 发送通知到 ntfy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -u &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$NTFY_USER&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$NTFY_PASS&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -H &lt;span class="s2"&gt;&amp;#34;Title: CPU 限制触发&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s2"&gt;&amp;#34;进程 &lt;/span&gt;&lt;span class="nv"&gt;$process_name&lt;/span&gt;&lt;span class="s2"&gt; (PID: &lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;) 的 CPU 使用率达到 &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;cpu_usage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%，已被停止。&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$NTFY_URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; 无法停止进程 &lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="nv"&gt;$process_name&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 暂停 5 秒后再次检查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;亲测有效~&lt;/p&gt;</description></item><item><title>自建MinIO配合Cloudflare Tunnel穿透排障</title><link>https://obsp.de/zh/posts/minio-cloudflare-tunnel-troubleshooting/</link><guid isPermaLink="true">https://obsp.de/zh/posts/minio-cloudflare-tunnel-troubleshooting/</guid><pubDate>Mon, 07 Apr 2025 16:12:40 +0000</pubDate><description>&lt;p&gt;大家好，今天分享一下使用家里云自建 MinIO 配合 CF Tunnel 内网穿透时遇到的问题以及解决方案。&lt;/p&gt;
&lt;p&gt;主要的问题有三个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何使用 docker compose 的方式搭建 MinIO ？&lt;/li&gt;
&lt;li&gt;第一次登录的时候，为何显示 &lt;code&gt;Invalid credentials&lt;/code&gt; ？&lt;/li&gt;
&lt;li&gt;为什么无法在 rclone 中使用端点域名连接，只能采用 &lt;code&gt;ip:port&lt;/code&gt; 连接？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="使用-docker-compose-搭建-minio"&gt;使用 docker compose 搭建 MinIO&lt;/h2&gt;
&lt;p&gt;对于一般的用户，我们的 &lt;code&gt;compose.yml&lt;/code&gt; 应该是像这样的：&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;minio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;minio/minio&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;server --console-address &amp;#34;:9001&amp;#34; --address &amp;#34;:9000&amp;#34; /data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_ROOT_USER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myrootuser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_ROOT_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myrootpassword&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# MINIO_SERVER_URL: &amp;#34;https://minio.yourdomain.com&amp;#34; &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 这个变量值的指定与否，会影响你是否能成功登录。我们先不指定&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;minio-data:/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;9000:9000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# API 端口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;9001:9001&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 控制台端口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;curl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-f&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://localhost:9000/minio/health/live&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;20s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;minio-data:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;然后运行 &lt;code&gt;docker compose up -d&lt;/code&gt; 即可。国内的主机记得提前设置好速度快的 docker 镜像源。&lt;/p&gt;
&lt;h2 id="内网穿透和首次登录出错"&gt;内网穿透和首次登录出错&lt;/h2&gt;
&lt;p&gt;我们在 CF 准备好两个子域名，一个是 &lt;code&gt;minio.yourdomain.com&lt;/code&gt; ，另一个是 &lt;code&gt;console.yourdomain.com&lt;/code&gt; 。顾名思义，前者是用于 API 端点的，而后者是用于控制台的。&lt;/p&gt;
&lt;p&gt;进入 Zero Trust 的 Network -&amp;gt; Tunnels，在这里你可以看到已经配置好内网穿透的家里云主机（内网穿透的教程看我之前的文章）。将 &lt;code&gt;minio.yourdomain.com&lt;/code&gt; 指向 &lt;code&gt;http://localhost:9000&lt;/code&gt; , &lt;code&gt;console.yourdomain.com&lt;/code&gt; 指向 &lt;code&gt;http://localhost:9001&lt;/code&gt;，不出意外的话，打开 &lt;code&gt;https://console.yourdomain.com&lt;/code&gt; 即可成功用 &lt;code&gt;compose.yml&lt;/code&gt; 中设置的 root 用户名和密码登录控制台创建存储桶了。&lt;/p&gt;
&lt;p&gt;但是有一些朋友可能会发现，即使自己使用了完全正确的 root 用户名和密码，MinIO 控制台依然提示 &lt;code&gt;Invalid credentials&lt;/code&gt; 。这是为什么呢？&lt;/p&gt;
&lt;p&gt;就我个人的经验而言，这是因为在 &lt;code&gt;compose.yml&lt;/code&gt; 中配置了 &lt;code&gt;MINIO_SERVER_URL: &amp;quot;https://minio.yourdomain.com&amp;quot;&lt;/code&gt; 这一变量，而又没有为 MinIO 容器配置 SSL 证书，导致访问者到 Cloudflare 再到控制台这一段使用的是 https 协议，MinIO 内部（控制台到端点）使用的却是 http 协议。由于 MinIO 预期的是全程 https，所以连接会被拒绝。&lt;/p&gt;
&lt;p&gt;解决的方法也很简单，首先生成一个自签名证书，假设放在 &lt;code&gt;./minio/certs&lt;/code&gt; ：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl req -x509 -nodes -days &lt;span class="m"&gt;365&lt;/span&gt; -newkey rsa:2048 -keyout ./certs/private.key -out ./certs/public.crt -subj &lt;span class="s2"&gt;&amp;#34;/CN=minio.yourdomain.com&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;然后修改 &lt;code&gt;compose.yml&lt;/code&gt;，让证书进入容器：&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;minio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;minio/minio&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;server --certs-dir /certs /data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_ROOT_USER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myrootuser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_ROOT_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myrootpassword&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://minio.yourdomain.com&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;minio-data:/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./minio/certs:/certs &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 挂载证书目录&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;9000:9000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# API 端口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;9001:9001&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 控制台端口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;curl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-f&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://localhost:9000/minio/health/live&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;20s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;minio-data:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;这样应该就没问题了。&lt;/p&gt;
&lt;p&gt;不过，这个问题可能的成因不止一个（比如&lt;a href="https://stackoverflow.com/questions/72291231/minio-docker-ssl-login-connection-refused"&gt;这个&lt;/a&gt;），以上只是其中之一。&lt;/p&gt;
&lt;h2 id="反代端点加-rclone-无法战胜"&gt;反代端点加 rclone 无法战胜？&lt;/h2&gt;
&lt;p&gt;提要：是的，确实无法战胜！&lt;/p&gt;
&lt;p&gt;我在使用具有公网 ip 的服务器自建 MinIO 并且用 Caddy 反代端点的时候，会出现莫名其妙的 rclone 连接问题，具体表现为：使用同样的一对 Access Key 和 Secret Key，正确进行了 rclone 配置，唯一的变量是连接的端点填写的是 &lt;code&gt;http://ip:port&lt;/code&gt; 或 &lt;code&gt;https://minio.yourdomain.com&lt;/code&gt; 。使用前者可以成功连接，而后者不可以。&lt;/p&gt;
&lt;p&gt;rclone 配置如下：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;rclone v1.69.1

- os/version: arch 25.0.0 (64 bit)

- os/kernel: 6.12.19-1-MANJARO (x86_64)

- os/type: linux

- os/arch: amd64

- go/version: go1.24.0

- go/linking: static

- go/tags: none&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;.config/rclone/rclone.conf&lt;/code&gt;：&lt;/p&gt;
&lt;figure class="code-block" data-language="sh"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;SH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 sh 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;minio&lt;span class="o"&gt;]&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; s3 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; Minio 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;access_key_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; xxx 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;secret_access_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; xxxx 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; us-east-1 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; https://minio.example.dev 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;disable_http2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;force_path_style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;v4_auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;no_check_certificate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;no_system_metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;“无法连接” 在我这里的表现是这样的：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;yuki@manjaro ~&lt;span class="o"&gt;]&lt;/span&gt;$ rclone ls minio: -vv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025/04/02 22:11:34 DEBUG : rclone: Version &lt;span class="s2"&gt;&amp;#34;v1.69.1&amp;#34;&lt;/span&gt; starting with parameters &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rclone&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ls&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;minio:&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-vv&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025/04/02 22:11:34 DEBUG : Creating backend with remote &lt;span class="s2"&gt;&amp;#34;minio:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025/04/02 22:11:34 DEBUG : Using config file from &lt;span class="s2"&gt;&amp;#34;/home/yuki/.config/rclone/rclone.conf&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025/04/02 22:11:36 DEBUG : &lt;span class="m"&gt;5&lt;/span&gt; go routines active
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025/04/02 22:11:36 NOTICE: Failed to ls: operation error S3: ListBuckets, https response error StatusCode: 403, RequestID: 183285C7252CB364, HostID: dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;起初，我看了一个类似的 GitHub Issue（对不起时间有点久找不到了，找到补上），是使用 Nginx 进行反代的，然后通过加特定的 headers 解决了问题。所以我想肯定是我的 Caddy 功夫还不过关，写的反代有问题导致的。但是在使用 CF 穿透的时候又一次遇到了这个问题，而这一次没有任何 web server 安装在家里云小主机上。&lt;/p&gt;
&lt;p&gt;我对此百思不得其解，在电脑前折腾了整整一天一夜，终于……放弃了……&lt;/p&gt;
&lt;p&gt;当我坐在马桶前沉思的时候，突然灵光乍现：有没有可能，这不是 MinIO 的问题，不是 Caddy 的问题，不是 CF 的问题，而是 rclone 的问题？&lt;/p&gt;
&lt;p&gt;于是我立马使用 MinIO 官方的命令行工具 &lt;code&gt;mc&lt;/code&gt; 使用一模一样的 Access Key 和 Secret Key 对，并且用 &lt;code&gt;https://域名&lt;/code&gt; 作为端点。令我吃惊的是，这一次我可以成功进行 &lt;code&gt;ls&lt;/code&gt; 操作了：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;yuki@manjaro ~&lt;span class="o"&gt;]&lt;/span&gt;$ mc ls minio 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;2025-04-02 15:42:43 CST&lt;span class="o"&gt;]&lt;/span&gt; 0B test/ 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;yuki@manjaro ~&lt;span class="o"&gt;]&lt;/span&gt;$ mc ls minio/test 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;2025-04-02 16:40:17 CST&lt;span class="o"&gt;]&lt;/span&gt; 33KiB STANDARD &amp;lt;picturename&amp;gt;.jpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;完全正确，没有出现 403 错误。&lt;/p&gt;
&lt;p&gt;看来，我的 https 设置并没有问题，问题的确是出在 rclone 上。&lt;/p&gt;
&lt;p&gt;在 Deepseek 和 Claude 的帮助下，我对比了两个工具的请求头（完整输出太长，只截取重要部分），已经把 troubleshooting 步骤&lt;a href="https://forum.rclone.org/t/signature-doesnt-match-for-minio-behind-cloudflare-tunnel/50691"&gt;发在 rclone forum&lt;/a&gt;：&lt;/p&gt;
&lt;p&gt;运行 &lt;code&gt;mc --debug ls one/test&lt;/code&gt; 得到的 headers：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Amz-Date: 20250402T142607Z 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Authorization: AWS4-HMAC-SHA256 &lt;span class="nv"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;accesskey&amp;gt;/20250402/us-east-1/s3/aws4_request, &lt;span class="nv"&gt;SignedHeaders&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host&lt;span class="p"&gt;;&lt;/span&gt;x-amz-content-sha256&lt;span class="p"&gt;;&lt;/span&gt;x-amz-date, &lt;span class="nv"&gt;Signature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxx &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;运行 &lt;code&gt;rclone -vv --dump headers --dump auth ls minio:&lt;/code&gt; 得到的：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Amz-Date: 20250402T142710Z 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Authorization: AWS4-HMAC-SHA256 &lt;span class="nv"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;accesskey&amp;gt;/20250402/us-east-1/s3/aws4_request, &lt;span class="nv"&gt;SignedHeaders&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;accept-encoding&lt;span class="p"&gt;;&lt;/span&gt;amz-sdk-invocation-id&lt;span class="p"&gt;;&lt;/span&gt;amz-sdk-request&lt;span class="p"&gt;;&lt;/span&gt;host&lt;span class="p"&gt;;&lt;/span&gt;x-amz-content-sha256&lt;span class="p"&gt;;&lt;/span&gt;x-amz-date, &lt;span class="nv"&gt;Signature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxx &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;可以看到 &lt;code&gt;Authorization&lt;/code&gt; 稍有不同，这是因为 rclone 默认为所有 S3 兼容存储添加了以下 headers，而这并不是 MinIO 期望的: &lt;code&gt;accept-encoding;amz-sdk-invocation-id;amz-sdk-request;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://forum.rclone.org/t/signaturedoesnotmatch/13428/3"&gt;类似的问题发生在 Exoscale 对象存储上&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rclone uses the official AWS SDK so I&amp;rsquo;d guess that this is a bug in exoscale.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;问题的原因找到了，解决的方法嘛，那就是……还没有。rclone 暂时没有提供在配置文件里修改请求头的方法（也可能有，但是我没有成功），所以只能先用其它工具了。&lt;/p&gt;</description></item><item><title>使用docker自建e2ee开源笔记软件Notesnook服务器</title><link>https://obsp.de/zh/posts/selfhosting-notesnook-with-docker/</link><guid isPermaLink="true">https://obsp.de/zh/posts/selfhosting-notesnook-with-docker/</guid><pubDate>Thu, 03 Apr 2025 11:57:01 +0000</pubDate><description>&lt;p&gt;2025.04.11 更新：邮件发送地址可以通过在 &lt;code&gt;.env&lt;/code&gt; 中指定 &lt;code&gt;NOTESNOOK_SENDER_EMAIL&lt;/code&gt; 的值来确定，这样就解决了会使用 SMTP 登录名作为发件人地址的问题&lt;/p&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;笔记软件的类型非常多，划分的标准也不尽相同。以笔记的主要存储位置为标准，既有注重云端的 Notion, OneNote 和 FlowUs 等，也有本地优先的 Obsidian, Logseq 和思源笔记等；以编辑器类型为标准，既有专注于纯文本 / 富文本 / Markdown 编辑的，也有八面玲珑，兼顾日历、任务、书签管理功能的；以加密程度为标准，既有注重隐私安全的，也有注重便捷分享的。当一个笔记软件找到自己的定位，获得了一些目标人群的认可时，也往往会顾此失彼，让另一些潜在客户悄悄流失。&lt;/p&gt;
&lt;p&gt;例如，Obsidian 以强大的文本编辑能力、本地优先，无须网络或登录账户的快速便捷性和直接使用 .md 文档作为存储格式的开放性受到了用户的交口称赞，与此同时也因为同步的不便遭到了诟病；强调隐私保护的Standard Notes，因简洁的界面、开放源代码和经第三方审计的安全性在私人笔记市场受到欢迎，但与昂贵的订阅价格无法匹配的基础功能也令用户怨声载道。&lt;/p&gt;
&lt;p&gt;不论如何，这些赫赫有名的笔记软件都拥有自己的独门绝技。在这个硬件产品逐渐同质化的时代，软件产品的差异化，能让市场更加百花齐放，满足不同客户的需求。&lt;/p&gt;
&lt;p&gt;今天我们尝试部署的笔记软件 Notesnook 就是一个很好的例子。Notesnook 是由巴基斯坦的一个 3 人小公司 Streetwriters 开发的，在 2021 年发布了第一个版本，并在 2022 年以 GPL-3.0 许可证发布了服务端与客户端的源代码。同样以“端到端加密”的特性作为主打卖点，与拥有 8 年历史的 Standard Notes 相比，Notesnook 是一个年轻的竞争者。与 Standard Notes 较为稳健、保守的风格不同，Notesnook 广泛地听取用户意见，积极地引入各种新功能，而它比 Standard Notes 低许多的高级版定价以及相对慷慨的免费层服务吸引了不少 Standard Notes 的忠实用户。不少用户表示，“阻碍我转向 Notesnook 的唯一障碍就是不能自托管”。随着自托管进入 Alpha 阶段和服务端 Docker 镜像的发布，“不能自托管”的时代也已经结束了。尽管官方的完整版自托管文档尚未发布，但是已经有不少用户使用了 GitHub 仓库中的 Building From Source 或 &lt;code&gt;docker compose&lt;/code&gt; 成功进行了部署。&lt;/p&gt;
&lt;p&gt;我们使用 &lt;code&gt;docker compose&lt;/code&gt; 进行简便部署，并且用 Caddy 进行反代。&lt;/p&gt;
&lt;h2 id="前置条件"&gt;前置条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;docker 和 docker compose&lt;/li&gt;
&lt;li&gt;4 个域名或者子域名，下面用 &lt;code&gt;notes.example.io, mono.example.io, events.example.io, auth.example.io&lt;/code&gt; 指代&lt;/li&gt;
&lt;li&gt;Caddy V2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="设置-composeyml-与-env"&gt;设置 compose.yml 与 .env&lt;/h2&gt;
&lt;p&gt;官方提供了一个开箱即用的 &lt;code&gt;docker-compose.yml&lt;/code&gt; ：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wget https://raw.githubusercontent.com/streetwriters/notesnook-sync-server/master/docker-compose.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;大部分内容我们都是不需要修改的：&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 yaml 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;x-server-discovery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;&amp;amp;server-discovery&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NOTESNOOK_SERVER_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5264&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NOTESNOOK_SERVER_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;notesnook-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;IDENTITY_SERVER_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8264&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;IDENTITY_SERVER_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;identity-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SSE_SERVER_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7264&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SSE_SERVER_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sse-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SELF_HOSTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;IDENTITY_SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${AUTH_SERVER_PUBLIC_URL}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NOTESNOOK_APP_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${NOTESNOOK_APP_PUBLIC_URL}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;x-env-files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;&amp;amp;env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;.env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vandot/alpine-bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/bin/bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- -&lt;span class="l"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # List of required environment variables
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; required_vars=(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;INSTANCE_NAME&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;NOTESNOOK_API_SECRET&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;DISABLE_SIGNUPS&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;SMTP_USERNAME&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;SMTP_PASSWORD&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;SMTP_HOST&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;SMTP_PORT&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;AUTH_SERVER_PUBLIC_URL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;NOTESNOOK_APP_PUBLIC_URL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;MONOGRAPH_PUBLIC_URL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;ATTACHMENTS_SERVER_PUBLIC_URL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Check each required environment variable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; for var in &amp;#34;$${required_vars[@]}&amp;#34;; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; if [ -z &amp;#34;$${!var}&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Error: Required environment variable $$var is not set.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; exit 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;All required environment variables are set.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Ensure the validate service runs first&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;no&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;notesnook-db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mongo:7.0.12&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;notesnook-db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dbdata:/data/db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dbdata:/data/configdb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;replSet rs0 --bind_ip_all&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;service_completed_successfully&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;echo &amp;#39;db.runCommand(&amp;#34;ping&amp;#34;).ok&amp;#39; | mongosh mongodb://localhost:27017 --quiet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;40s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;start_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;60s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# the notesnook sync server requires transactions which only work&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# with a MongoDB replica set.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# This job just runs `rs.initiate()` on our mongodb instance&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# upgrading it to a replica set. This is only required once but we running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# it multiple times is no issue.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;initiate-rs0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mongo:7.0.12&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook-db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/bin/sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- -&lt;span class="l"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mongosh mongodb://notesnook-db:27017 &amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; rs.initiate();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; rs.status();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; EOF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;notesnook-s3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;minio/minio:RELEASE.2024-07-29T22-14-52Z&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;9000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;s3data:/data/s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_BROWSER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;service_completed_successfully&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;server /data/s3 --console-address :9090&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;timeout 5s bash -c &amp;#39;:&amp;gt; /dev/tcp/127.0.0.1/9000&amp;#39; || exit 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;40s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;start_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;60s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# There&amp;#39;s no way to specify a default bucket in Minio so we have to&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# set it up ourselves.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;setup-s3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;minio/mc:RELEASE.2024-07-26T13-08-44Z&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook-s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/bin/bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- -&lt;span class="l"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; until mc alias set minio http://notesnook-s3:9000 ${MINIO_ROOT_USER:-minioadmin} ${MINIO_ROOT_PASSWORD:-minioadmin}; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sleep 1;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; done;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mc mb minio/attachments -p&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;identity-server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;streetwriters/identity:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;8264&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8264&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook-db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wget --tries=1 -nv -q http://localhost:8264/health -O- || exit 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;40s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;start_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;60s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*server-discovery&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MONGODB_CONNECTION_STRING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mongodb://notesnook-db:27017/identity?replSet=rs0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MONGODB_DATABASE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;identity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;notesnook-server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;streetwriters/notesnook-sync:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;5264&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;5264&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook-s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;setup-s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;identity-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wget --tries=1 -nv -q http://localhost:5264/health -O- || exit 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;40s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;start_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;60s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*server-discovery&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MONGODB_CONNECTION_STRING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mongodb://notesnook-db:27017/?replSet=rs0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MONGODB_DATABASE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;S3_INTERNAL_SERVICE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http://notesnook-s3:9000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;S3_INTERNAL_BUCKET_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;attachments&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;S3_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${MINIO_ROOT_USER:-minioadmin}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;S3_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${MINIO_ROOT_PASSWORD:-minioadmin}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;S3_SERVICE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${ATTACHMENTS_SERVER_PUBLIC_URL}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;S3_REGION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;S3_BUCKET_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;attachments&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sse-server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;streetwriters/sse:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;7264&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;7264&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;identity-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wget --tries=1 -nv -q http://localhost:7264/health -O- || exit 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;40s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;start_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;60s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*server-discovery&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;monograph-server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;streetwriters/monograph:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;6264&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*env-files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook-server&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;notesnook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wget --tries=1 -nv -q http://localhost:3000/api/health -O- || exit 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;40s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;start_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;60s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*server-discovery&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;API_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://notesnook-server:5264&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PUBLIC_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${MONOGRAPH_PUBLIC_URL}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;autoheal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;willfarrell/autoheal:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTOHEAL_INTERVAL=60&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTOHEAL_START_PERIOD=300&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;AUTOHEAL_DEFAULT_STOP_TIMEOUT=10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;service_completed_successfully&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;notesnook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dbdata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;s3data:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;在这个文件中，我们可以看到，Notesnook 服务器的必需组件包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vandot&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;li&gt;S3 存储，这里采用了自建 MinIO 实例的方式（应该和 Ente 一样，不是必须使用 MinIO，可以接入外部 S3，不过我没试过，这里先不演示）&lt;/li&gt;
&lt;li&gt;身份验证服务器&lt;/li&gt;
&lt;li&gt;同步服务器&lt;/li&gt;
&lt;li&gt;SSE，用于提醒事件&lt;/li&gt;
&lt;li&gt;Monograph，用于分享笔记的公开链接（可以加密）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中需要映射到的宿主机端口是 5264（同步服务器），6264（Monograph），7264（SSE）和 8264（身份服务器）。如果这些端口被占用，你可以选择映射到其他的端口。&lt;/p&gt;
&lt;p&gt;我们需要进行个性化设置的是 &lt;code&gt;.env&lt;/code&gt; 文件，官方同样提供了一个示例：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;INSTANCE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;self-hosted-notesnook-instance 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 改成你的实例名称&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NOTESNOOK_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 随机生成一串 &amp;gt;32 位的长字符串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DISABLE_SIGNUPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这个变量目前（2025年4月3日，v1.0-beta.1）还不能起效，&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 如果你需要禁用注册功能，请在自己注册完之后添加以下变量：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# DISABLE_ACCOUNT_CREATION=1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SMTP 配置，用于接收 2FA 代码和重设密码，可以使用你自己的邮件提供商的 SMTP 配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SMTP_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 大部分情况下，是你的邮箱用户名，例如 user@example.org &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 当 SMTP 用户名不是发件人地址的时候，加入下面的一行：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# NOTESNOOK_SENDER_EMAIL=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SMTP_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SMTP 密码，根据你的邮件提供商的指引设置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SMTP_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 例如 smtp.gmail.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SMTP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SMTP 端口，例如 465&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TWILIO_ACCOUNT_SID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不需要设置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不需要设置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TWILIO_SERVICE_SID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不需要设置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NOTESNOOK_CORS_ORIGINS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不需要设置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NOTESNOOK_APP_PUBLIC_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://app.notesnook.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 换成你自己的 APP 域名，如 https://notes.example.io&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;MONOGRAPH_PUBLIC_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:6264
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 换成你的 Monograph 域名，如 https://mono.example.io&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AUTH_SERVER_PUBLIC_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8264
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 换成你的身份服务器域名 https://auth.example.io&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ATTACHMENTS_SERVER_PUBLIC_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:9000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# MinIO 地址和端口，可以不用域名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;MINIO_ROOT_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不需要设置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;MINIO_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不需要设置&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;设置完直接启动即可：&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="反向代理配置"&gt;反向代理配置&lt;/h2&gt;
&lt;p&gt;Nginx 和 Certbot 的配置参考这里：https://github.com/streetwriters/notesnook-sync-server/issues/20#issuecomment-2603896363&lt;/p&gt;
&lt;figure class="code-block" data-language="nginx"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;NGINX&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 nginx 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;auth.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;auth.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/auth.domain.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/auth.domain.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/options-ssl-nginx.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_dhparam&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/ssl-dhparams.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8264&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Notes Server - With WebSocket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;notes.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;notes.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/notes.domain.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/notes.domain.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/options-ssl-nginx.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_dhparam&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/ssl-dhparams.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:5264&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;upgrade&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_read_timeout&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_send_timeout&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Events Server - With WebSocket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;events.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;events.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/events.domain.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/events.domain.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/options-ssl-nginx.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_dhparam&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/ssl-dhparams.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:7264&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;upgrade&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_read_timeout&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_send_timeout&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Monograph Server - With Cache
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;mono.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;mono.domain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/mono.domain.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/mono.domain.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/options-ssl-nginx.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ssl_dhparam&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/ssl-dhparams.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:6264&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_cache_use_stale&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt; &lt;span class="s"&gt;timeout&lt;/span&gt; &lt;span class="s"&gt;http_500&lt;/span&gt; &lt;span class="s"&gt;http_502&lt;/span&gt; &lt;span class="s"&gt;http_503&lt;/span&gt; &lt;span class="s"&gt;http_504&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="mi"&gt;60m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Cache-Status&lt;/span&gt; &lt;span class="nv"&gt;$upstream_cache_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;public,&lt;/span&gt; &lt;span class="s"&gt;no-transform&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Caddy 的配置参考这里：https://github.com/streetwriters/notesnook-sync-server/issues/20#issuecomment-2763248500&lt;/p&gt;
&lt;figure class="code-block" data-language="nginx"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;NGINX&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 nginx 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;notes.example.io&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5264&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;mono.example.io&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6264&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;events.example.io&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7264&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;auth.example.io&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8264&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;正常情况下访问 &lt;code&gt;https://mono.example.io&lt;/code&gt; 能看到 Monograph 的默认页面就成功了。&lt;/p&gt;
&lt;h2 id="在-notesnook-客户端自定义服务器"&gt;在 Notesnook 客户端自定义服务器&lt;/h2&gt;
&lt;p&gt;下载 Notesnook 的客户端，进入 Settings -&amp;gt; Servers，填写以下端点：&lt;/p&gt;
&lt;p&gt;Sync server URL: &lt;code&gt;https://notes.example.io&lt;/code&gt; &lt;br&gt;
Auth server URL: &lt;code&gt;https://auth.example.io&lt;/code&gt; &lt;br&gt;
Events server URL: &lt;code&gt;https://events.example.io&lt;/code&gt; &lt;br&gt;
Monograph server URL: &lt;code&gt;https:mono.example.io&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后就可以输入你的邮箱注册账户了~&lt;/p&gt;
&lt;p&gt;需要注意的是，与 Vikunja 和 Vaultwarden 不同，Notesnook 的邮件验证不是可选项，所以需要填写可以接收到邮件的地址。&lt;/p&gt;
&lt;p&gt;如果需要关闭注册，可以在自己注册完后修改 &lt;code&gt;.env&lt;/code&gt; ，加入 &lt;code&gt;DISABLE_ACCOUNT_CREATION=1&lt;/code&gt; 。&lt;/p&gt;</description></item><item><title>Cloudflare Zero Trust Tunnel 内网穿透注意事项</title><link>https://obsp.de/zh/posts/cloudflare-tunnel/</link><guid isPermaLink="true">https://obsp.de/zh/posts/cloudflare-tunnel/</guid><pubDate>Wed, 02 Apr 2025 14:10:15 +0000</pubDate><description>&lt;p&gt;网上用 Cloudflare Tunnel 进行穿透的教程有不少，不过对连接的部分都说得比较含糊其词。&lt;/p&gt;
&lt;p&gt;站长个人尝试穿透家里的 x86 Debian 小主机，在安装 Cloudflared 时遇到了以下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 Debian 脚本，遇到 &lt;code&gt;not create ICMPv4 proxy: Group ID 0 is not between ping group 1 to 0 nor ICMPv6 proxy: socket: permission denied&lt;/code&gt; 的问题。&lt;/li&gt;
&lt;li&gt;使用 docker 脚本，一开始没有用宿主机网络，导致连接一直超时。&lt;/li&gt;
&lt;li&gt;使用宿主机网络后，由于默认的协议是 QUIC ，会被阻断，需要改成 HTTP2 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最终版的运行命令：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;docker run -d --network host cloudflare/cloudflared:latest tunnel --no-autoupdate run --token &amp;lt;your-token&amp;gt; --protocol http2&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;这样就好了……大概吧！&lt;/p&gt;</description></item><item><title>Caddy 使用 Buypass Go SSL 作为证书提供商</title><link>https://obsp.de/zh/posts/caddy-with-buypass-ssl/</link><guid isPermaLink="true">https://obsp.de/zh/posts/caddy-with-buypass-ssl/</guid><pubDate>Mon, 31 Mar 2025 17:55:51 +0000</pubDate><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好啊，今天教大家一个脱裤子放屁的 Caddy 小技巧。&lt;/p&gt;
&lt;p&gt;由 Let&amp;rsquo;s Encrypt 获取的 SSL 证书一般有效期为 90 天。虽然可以采用 Certbot 和 acme.sh 一类的脚本，或者交给 Caddy 自动续期，但可能还是有些小伙伴想要获取有效期更长的免费证书。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.buypass.com/products/tls-ssl-certificates/go-ssl"&gt;Buypass Go SSL/TLS&lt;/a&gt; 是一家挪威证书签发商的产品，基于 ACME ，证书的有效期为 180 天。那么，如何在 Caddy 中使用它替换默认的 Let&amp;rsquo;s Encrypt 呢？&lt;/p&gt;
&lt;h2 id="自定义兼容-acme-的-ca-的通用语法"&gt;自定义兼容 ACME 的 CA 的通用语法&lt;/h2&gt;
&lt;p&gt;在没有特别指明的情况下，Caddy 通常会使用 Let&amp;rsquo;s Encrypt 或者 ZeroSSL 来作为 Certificate Authority。你也可以自定义 CA，这里有两种语法，可用于兼容 ACME 的签发者：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一种，放在 &lt;code&gt;Caddyfile&lt;/code&gt; 开头，作用于全局：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="code-block" data-language="nginx"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;NGINX&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 nginx 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="s"&gt;acme_ca&lt;/span&gt; &lt;span class="s"&gt;https://acme.zerossl.com/v2/DV90&lt;/span&gt; &lt;span class="c1"&gt;# 换成自定义 CA
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="s"&gt;email&lt;/span&gt; &lt;span class="s"&gt;hi@example.dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;第二种，放在每个域名的大括号里面，作用于域名：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="code-block" data-language="nginx"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;NGINX&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 nginx 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;tls&lt;/span&gt; &lt;span class="s"&gt;hi@example.dev&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kn"&gt;ca&lt;/span&gt; &lt;span class="s"&gt;https://acme.zerossl.com/v2/DV90&lt;/span&gt; &lt;span class="c1"&gt;# 换成自定义 CA
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;很可惜的是，经过我的尝试，这两种语法下 Buypass Go SSL 都无法获取到证书。大概是因为 Buypass 在某次更新后添加了 ARI 支持，导致了 Caddy 的 &lt;code&gt;panic:certificate worker: runtime error: invalid memory address&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;解决方法有两个：一是使用 Certbot 进行注册，再手动设置 Caddy 读取获得的证书；二是使用最新的 &lt;code&gt;acmez&lt;/code&gt; 仓库和 &lt;code&gt;xcaddy&lt;/code&gt; 自行构建能够获取 Buypass Go SSL 证书的 Caddy 。&lt;/p&gt;
&lt;h2 id="方法一使用-certbot-手动获取证书不推荐"&gt;方法一：使用 Certbot 手动获取证书（不推荐）&lt;/h2&gt;
&lt;p&gt;按照 Certbot 的官方教程进行安装，此处不再赘述。&lt;/p&gt;
&lt;p&gt;安装完成之后，运行如下命令：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# certbot register -m &lt;span class="s1"&gt;&amp;#39;YOUR_EMAIL&amp;#39;&lt;/span&gt; --agree-tos --server &lt;span class="s1"&gt;&amp;#39;https://api.buypass.com/acme/directory&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;此时，需要使用你的电子邮件地址在 Buypass 上注册，和 ZeroSSL 是一样的。&lt;/p&gt;
&lt;p&gt;如果你不希望你的 web server （ Nginx Apache 都可以，但不是 Caddy ！）暂时停机的话，就选用 &lt;code&gt;webroot&lt;/code&gt; 方式完成 ACME Challenge ，需要运行的命令是：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# certbot certonly --webroot -w /var/www/example.com/public_html/ -d example.com -d www.example.com --server &lt;span class="s1"&gt;&amp;#39;https://api.buypass.com/acme/directory&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;如果你用的是 Caddy ，由于 Caddy 默认的 https 重定向策略，导致 80 端口的验证几乎成为了不可能做到的事（不要说可以关掉 tls ，关掉 tls 做完验证之后就要重新开启 tls 读取获得的证书，下次续期的时候就会失败）。那就选用 &lt;code&gt;standalone&lt;/code&gt; ：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# certonly --standalone --email &lt;span class="s1"&gt;&amp;#39;xxx@example.org&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;example.org&amp;#39;&lt;/span&gt; --server &lt;span class="s1"&gt;&amp;#39;https://api.buypass.com/acme/directory&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;然后给 Caddy 读取证书的权限：&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# chmod &lt;span class="m"&gt;755&lt;/span&gt; /etc/letsencrypt/live/ 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# chmod &lt;span class="m"&gt;755&lt;/span&gt; /etc/letsencrypt/archive/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# chmod &lt;span class="m"&gt;644&lt;/span&gt; /etc/letsencrypt/live/example.org/*.pem
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# chmod &lt;span class="m"&gt;644&lt;/span&gt; /etc/letsencrypt/archive/example.org/*.pem &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;最后在 Caddyfile 配置 &lt;code&gt;tls /etc/letsencrypt/live/example.org/fullchain.pem /etc/letsencrypt/live/example.org/privkey.pem&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;当然，只是做实验而已，不推荐任何人进行以上操作。同时使用 Caddy 和 Certbot 的行为纯粹是吃力不讨好，Caddy 默认会升级成 https ，并对证书进行自动续期，所以你几乎感受不到证书有效期的短暂和续期的麻烦。&lt;/p&gt;
&lt;h2 id="方法二使用-xcaddy--构建包含最新-acmez-模块的-caddy"&gt;方法二：使用 xcaddy 构建包含最新 acmez 模块的 Caddy&lt;/h2&gt;
&lt;p&gt;首先，我们需要安装最新版的 Go 。&lt;/p&gt;
&lt;p&gt;以 Linux x86-64 为例，运行 &lt;code&gt;wget https://go.dev/dl/go1.24.1.linux-amd64.tar.gz&lt;/code&gt; ，然后再运行 &lt;code&gt;rm -rf /usr/local/go &amp;amp;&amp;amp; tar -C /usr/local -xzf go1.24.1.linux-amd64.tar.gz&lt;/code&gt; 和 &lt;code&gt;export PATH=$PATH:/usr/local/go/bin&lt;/code&gt; ， 最后运行 &lt;code&gt;go version&lt;/code&gt; 验证即可。&lt;/p&gt;
&lt;p&gt;然后，我们需要安装 xcaddy。&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:~/go/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# xcaddy build --with github.com/mholt/acmez/v3@v3.1.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;最后，用 xcaddy 构建的新可执行文件替换掉原来的 Caddy 可执行文件。这一步的操作与 Caddy 的运行方式有关，此处仅展示 &lt;code&gt;systemd&lt;/code&gt; 服务的替换方法。&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 bash 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# systemctl stop caddy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# cp /usr/bin/caddy /usr/bin/caddy.bak
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# cp ./caddy /usr/bin/caddy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# chown caddy:caddy /usr/bin/caddy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# chmod &lt;span class="m"&gt;755&lt;/span&gt; /usr/bin/caddy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root@acme:~# systemctl start caddy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;此时再在 &lt;code&gt;Caddyfile&lt;/code&gt; 中使用语法：&lt;/p&gt;
&lt;figure class="code-block" data-language="nginx"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;NGINX&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 nginx 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="s"&gt;acme_ca&lt;/span&gt; &lt;span class="s"&gt;https://api.buypass.com/acme/directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="s"&gt;email&lt;/span&gt; &lt;span class="s"&gt;hi@example.dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;就不会再出现 &lt;code&gt;panic&lt;/code&gt; 了。&lt;/p&gt;
&lt;p&gt;不过，如果你的网站之前已经有了 SSL 证书，那么你需要手动删除 &lt;code&gt;/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory&lt;/code&gt; 下的证书文件，然后重启 Caddy 。访问你的网站，如果没有套 CF 的话，就能看到 Buypass 签发的证书了；如果你套了 CF ，那么看到的依然是边缘证书。&lt;/p&gt;</description></item><item><title>本站推出新的公益服务：Vaultwarden，Wallos，Vikunja</title><link>https://obsp.de/zh/posts/new-services-vaultwarden-wallos-vikunja/</link><guid isPermaLink="true">https://obsp.de/zh/posts/new-services-vaultwarden-wallos-vikunja/</guid><pubDate>Thu, 27 Mar 2025 22:09:03 +0000</pubDate><description>&lt;p&gt;本站推出新的公益服务：&lt;/p&gt;
&lt;p&gt;Vaultwarden 密码管理器，不必多说。https://vw.obsp.de&lt;/p&gt;
&lt;p&gt;Wallos 订阅管理器。https://wallos.obsp.de&lt;/p&gt;
&lt;p&gt;Vikunja 日程管理器，支持CalDAV。https://vikunja.obsp.de&lt;/p&gt;
&lt;p&gt;请注意，技术上我们无法获取您保存在Vaultwarden中的密码，但是可以删库跑路。
所以，做好备份！&lt;/p&gt;
&lt;p&gt;为了避免潜在的滥用发件服务器隐患，所有服务的SMTP功能都被关闭了，这也就意味着您不能通过电子邮件重设密码。请谨记！&lt;/p&gt;
&lt;p&gt;Enjoy~&lt;/p&gt;</description></item><item><title>在Codeberg Pages上使用Hexo搭建博客</title><link>https://obsp.de/zh/posts/hosting-blog-on-codeberg-pages/</link><guid isPermaLink="true">https://obsp.de/zh/posts/hosting-blog-on-codeberg-pages/</guid><pubDate>Tue, 18 Mar 2025 22:16:28 +0000</pubDate><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;Numerus Fixus 项目的考试终于结束了，发挥得比较一般，没什么把握。放榜之后再整理一下自己的笔记和经验分享给大家吧（虽然感觉几乎没有人用得到，不过万一呢）。&lt;/p&gt;
&lt;p&gt;放榜之前都是空闲时间，顺手把旧文章通过&lt;a href="https://github.com/AlanDecode/Typecho-Plugin-Tp2MD"&gt;Tp2MD &lt;/a&gt;插件导出了，写一写obsp 的Hexo 镜像搭建过程吧。&lt;/p&gt;
&lt;p&gt;Typecho 的部署相对来说简单一些，可以直接docker compose，数据库每天备份，但是用久了感觉还是有点不顺手，尤其是发布和修改文章非常麻烦，每次发现错别字都要手动登录后台捉虫。也许直接从Markdown生成HTML的静态站点生成器更适合我一点。静态站点的内容可以托管在GitHub Pages、Gitlab Pages、Bitbucket Cloud和Codeberg Pages等，省去了挑选和维护服务器的烦恼，从写作文章到发布和查看效果的过程也更快速便捷。更重要的是，我发现Typecho 站点被搜索引擎抓取的时候似乎很难抓到文章标题，常常将正文的内容作为标题收录，这就导致了通过搜索关键词发现obsp 的读者往往会因为搜索引擎抓取的标题太奇怪而不愿意点进来，而Hexo 站点就没有这种缺点。Hexo 的缺点大概是每次重装系统/换新设备，都要重新配环境。其他比较出名的静态站点生成器例如Hugo 和Jekyll 我还完全没有尝试过。&lt;/p&gt;
&lt;p&gt;这篇文章主要聚焦使用Codeberg Pages的静态站点托管服务来托管Hexo 生成的页面打造博客的过程。Hexo 的官方文档对于Git部署说得也很详细，不过我会加入一些自定义和个性化的过程，例如使用主题、利用Giscus 连接GitHub repo discussions 作为评论区等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以下操作在基于Arch Linux的发行版Manjaro Linux（内核：Linux 6.12.17-1-MANJARO）上完成，操作系统为64位&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="安装必要依赖"&gt;安装必要依赖&lt;/h2&gt;
&lt;p&gt;Git 和 Node.js，由于自带，此处不再赘述，其它操作系统/发行版可以参考&lt;a href="https://hexo.io/docs/#Install-Git"&gt;Hexo官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="安装hexo"&gt;安装Hexo&lt;/h2&gt;
&lt;p&gt;Hexo 的安装非常简单：&lt;code&gt;npm install -g hexo-cli&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;安装完成后，我们可以新建一个站点，每一个站点的设置和数据会以文件夹的形式储存，所以我们在你期望的根目录处进行init：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;$ hexo init &amp;lt;folder&amp;gt; 
$ cd &amp;lt;folder&amp;gt; 
$ npm install&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;这样，一个基本的站点目录结构就完成了。&lt;/p&gt;
&lt;p&gt;新手可能会一头雾水：我的文章应该放在哪里？站点的设置文件在哪里？这个文件夹就是我的站点源码吗？&lt;/p&gt;
&lt;p&gt;其实，Hexo生成的静态站点现在还没有出现，我们的站点文件夹中存放的是用来生成HTML文件的资源。我们可以通过使用Hexo的各种命令或者直接读写站点文件夹中的文件来修改资源，从而修改最终生成的页面。&lt;/p&gt;
&lt;p&gt;文件夹应该是这个结构：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;. 
├── _config.yml 
├── package.json 
├── scaffolds 
├── source 
| ├── _drafts 
| └── _posts 
└── themes&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;很好理解，&lt;code&gt;_config.yml&lt;/code&gt;是配置文件，&lt;code&gt;package.json&lt;/code&gt;是应用程序的信息，&lt;code&gt;scaffolds&lt;/code&gt;文件夹中存放模板，&lt;code&gt;source&lt;/code&gt;存放的则是决定最终生成页面的文章内容的资源，包括尚未完成的草稿&lt;code&gt;_drafts&lt;/code&gt;和已经准备好渲染和发布的&lt;code&gt;_posts&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;以Observer&amp;rsquo;s Space Hexo站为例，我用Obsidian打开&lt;code&gt;~/obsp/source&lt;/code&gt;文件夹作为图谱，在图谱下面，可以看到我正在写的这篇《在Codeberg Pages上使用Hexo搭建博客》归属在&lt;code&gt;_drafts&lt;/code&gt;文件夹里，而大家在Observer&amp;rsquo;s Space Hexo站上能看到的历史文章都归属在&lt;code&gt;_posts&lt;/code&gt;文件夹里。当我们执行生成命令的时候，&lt;code&gt;_drafts&lt;/code&gt;文件夹里的文章不会生成页面，当你准备好将它们发布的时候才会被移动到&lt;code&gt;_posts&lt;/code&gt;文件夹，参与网页的生成。生成的HTML文件会被放在&lt;code&gt;public&lt;/code&gt;文件夹里，当我们使用GitHub Pages/Codeberg Pages/Bitbucket Cloud的静态站点托管服务时，我们做的主要是通过Hexo命令使用Git来将本地的&lt;code&gt;public&lt;/code&gt;文件夹与远程仓库进行同步，Pages服务通过读取仓库中的内容展示网站。（GitLab Pages的部署略有不同，GitLab用户可以参考Hexo和GitLab Pages的官方文档，此处不再赘述。）&lt;/p&gt;
&lt;p&gt;Themes存放的是主题文件的配置，我们现在先不管它。&lt;/p&gt;
&lt;h2 id="修改配置文件"&gt;修改配置文件&lt;/h2&gt;
&lt;p&gt;现在我们需要做的事是修改配置文件。仍然以Observer&amp;rsquo;s Space为例，由于&lt;code&gt;_config.yml&lt;/code&gt;太长了，我只截取重要的部分进行说明：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;title: Observer&amp;#39;s Space Mirror #站点的标题
favicon: https://s21.ax1x.com/2025/02/11/pEn6KsJ.png #访问网站时显示的头图，我选用了可以免费使用的Google Noto Emoji中ringed planet的Android 10版本，新版设计可以参考https://emojipedia.org/ringed-planet#designs
avatar: https://s21.ax1x.com/2025/02/11/pEn6KsJ.png #网站的头像，细心的朋友可能发现我偷懒了，和头图用的是同一张嘿嘿
subtitle: &amp;#39;宇宙中的观星者&amp;#39; #副标题
description: &amp;#39;This is a sister website of obsp.de. (Still in beta)&amp;#39; #描述
keywords: #关键词，懒得想
author: Yuki Kamiki #神木友希的大名
language: en
timezone: &amp;#39;UTC&amp;#39;

## Set your site url here. For example, if you use GitHub Page, set url as &amp;#39;https://username.github.io/project&amp;#39;
url: https://hexo.obsp.de #这个可以设置成你等下要绑定的自定义域名，就像obsp一样，如果你想用Codeberg给的域名，就请设置成https://USERNAME.codeberg.page。错误的域名设置将会导致CSS和JavaScript资源加载失败，只能访问到文字内容。

#。。。有一群乌鸦飞过
#。。。有一群乌鸦飞过

deploy: #涉及到使用Hexo自动部署命令的配置
 type: &amp;#39;git&amp;#39; #我们使用git的方式进行自动部署
 repo: git@codeberg.org:kamiki/obsp.git #Repo的地址，https或者ssh都可以。我的 pages 仓库拿去做别的内容了（欢迎大家访问~），所以给 obsp 创建了一个新仓库。如果你要用Codeberg Pages 并且在 Codeberg 提供的域名根目录 https://USERNAME.codeberg.page 访问的话就应该是git@codeberg.org:USERNAME/pages.git
 branch: main #跟踪远程仓库的分支
 message: updates #commit时的提交信息&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;啊，配置文件搞定了！不要忘记保存退出哦！&lt;/p&gt;
&lt;h2 id="生成静态站点"&gt;生成静态站点&lt;/h2&gt;
&lt;p&gt;你可以使用&lt;code&gt;hexo new [layout] xxx&lt;/code&gt;来新建一篇文章。&lt;/p&gt;
&lt;p&gt;当你指明 layout 是draft/page/post 的时候，hexo 会将生成的 md 文件存放在&lt;code&gt;source&lt;/code&gt;目录下对应类型的文件夹中，如果省略 layout 参数，那么默认生成的是 post 。&lt;/p&gt;
&lt;p&gt;如果还没想好写什么，没关系，打开 &lt;code&gt;source/_posts&lt;/code&gt;，会发现那里面并不是空白的，我们在执行&lt;code&gt;hexo init&lt;/code&gt;的时候自动生成了一个 md 文件，它可以作为博客第一篇文章的资源。&lt;/p&gt;
&lt;p&gt;执行&lt;code&gt;hexo g&lt;/code&gt;，会发现生成了一个&lt;code&gt;public&lt;/code&gt;文件夹。执行&lt;code&gt;hexo deploy&lt;/code&gt;进行部署，会将这个文件夹中的内容发布到刚才在&lt;code&gt;_config.yml&lt;/code&gt;中填写的仓库地址。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;等一下……！好像不太对……&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="发布到codeberg-pages"&gt;发布到Codeberg Pages&lt;/h2&gt;
&lt;p&gt;第一次用git的小伙伴可能不知道怎么连接远程仓库，&lt;del&gt;请善用搜索引擎&lt;/del&gt; 和平时连小鸡的方式差不多，用公私钥或者用户名密码都可以。什么你说你没有连过小鸡？&lt;del&gt;请善用搜索引擎&lt;/del&gt; 执行&lt;code&gt;ssh-keygen -t ed25519 -C &amp;quot;your_email@example.com&amp;quot;&lt;/code&gt;，然后你就会在&lt;code&gt;~/.ssh&lt;/code&gt;找到公私钥，公钥是那个文件名带 pub 的，私钥是不带的，把公钥内容上传到&lt;a href="https://codeberg.org/user/settings/keys"&gt; Codeberg 设置中 &lt;/a&gt;，然后再执行&lt;code&gt;hexo deploy&lt;/code&gt;即可。&lt;/p&gt;
&lt;h2 id="enjoy"&gt;Enjoy！&lt;/h2&gt;
&lt;p&gt;稍等片刻（Codeberg 正在码头给你整 SSL 证书），即可在&lt;code&gt;https://USERNAME.codeberg.page/&lt;/code&gt;查看你的 landscape 主题 Hexo 博客。&lt;/p&gt;
&lt;h2 id="绑定自定义域名"&gt;绑定自定义域名&lt;/h2&gt;
&lt;p&gt;把你的域名 CNAME 解析到&lt;code&gt;username.codeberg.page&lt;/code&gt;，然后在仓库里新建一个&lt;code&gt;.domains&lt;/code&gt;文件，输入你想要的域名&lt;code&gt;example.org&lt;/code&gt;，再等一会（Codeberg 正在码头给你整 SSL 证书）即可通过自定义域名访问。&lt;/p&gt;
&lt;p&gt;什么你说 root 不能有 CNAME 记录？&lt;del&gt;你怎么不托管到能做CNAME拉平的NS提供商&lt;/del&gt; 也没问题，A记录解析到&lt;code&gt;217.197.91.145&lt;/code&gt;，AAAA记录解析到&lt;code&gt;2001:67c:1401:20f0::1&lt;/code&gt;，然后TXT记录&lt;code&gt;username.codeberg.org&lt;/code&gt;即可。&lt;/p&gt;
&lt;h2 id="主题配置以本站为例"&gt;主题配置（以本站为例）&lt;/h2&gt;
&lt;p&gt;本站采用的是 &lt;a href="https://xaoxuu.com/wiki/stellar/"&gt;Stellar&lt;/a&gt; 主题，感谢 xaoxuu 大佬开发和开源！&lt;/p&gt;
&lt;p&gt;因为我比较懒，所以采用了稳定版安装：&lt;code&gt;npm i hexo-theme-stellar&lt;/code&gt;
找到&lt;code&gt;_config.yml&lt;/code&gt;文件中的&lt;code&gt;theme: landscape&lt;/code&gt;改成&lt;code&gt;theme: stellar&lt;/code&gt;然后重新运行&lt;code&gt;hexo g&lt;/code&gt;和&lt;code&gt;hexo deploy&lt;/code&gt;即可。&lt;/p&gt;
&lt;h2 id="自定义字体以本站为例"&gt;自定义字体（以本站为例）&lt;/h2&gt;
&lt;p&gt;本站用的是霞鹜文楷（无衬线体、衬线体）和 PT Mono（等宽）。&lt;/p&gt;
&lt;p&gt;新建一个专门给 Stellar 主题用的配置文件&lt;code&gt;_config.stellar.yml&lt;/code&gt;，写入以下内容：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;inject:
 head:
 - &amp;lt;link rel=&amp;#34;stylesheet&amp;#34; href=&amp;#34;https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-web/style.css&amp;#34; /&amp;gt;
 - &amp;lt;link rel=&amp;#34;preconnect&amp;#34; href=&amp;#34;https://fonts.bunny.net&amp;#34;&amp;gt;
 - &amp;lt;link href=&amp;#34;https://fonts.bunny.net/css?family=pt-mono:400&amp;#34; rel=&amp;#34;stylesheet&amp;#34; /&amp;gt;
style:
 font-family:
 logo: &amp;#39;LXGW WenKai Screen&amp;#39;
 body: &amp;#39;LXGW WenKai Screen&amp;#39;
 code: &amp;#39;PT Mono&amp;#39;
 codeblock: &amp;#39;PT Mono&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;注意 Stellar 仓库里的&lt;code&gt;_config.stellar.yml&lt;/code&gt;示例使用了单引号内套双引号&lt;code&gt;'&amp;quot;LXGW WenKai Screen&amp;quot;,...'&lt;/code&gt;，实测这样子无法应用字体，去掉双引号即可。&lt;/p&gt;
&lt;h2 id="giscus-评论区"&gt;Giscus 评论区&lt;/h2&gt;
&lt;p&gt;和 Typecho 不同，Hexo 默认是没有评论区的，而大部分博主还是希望能够听到读者的反馈，那么就有了各种各样的评论插件。Giscus 使用 GitHub 仓库的 Discussions 功能，按照 &lt;a href="https://giscus.app/zh-CN"&gt;https://giscus.app/zh-CN&lt;/a&gt; 上的指导创建好公开仓库、安装 giscus app、启用 Discussions功能后，把你的用户名/仓库名复制粘贴进去（很奇怪，手动输入会提示无法连接，复制粘贴就可以，这个 bug 可以复现，&lt;a href="https://jachinzhang1.github.io/2025/02/04/hexo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E8%AF%84%E8%AE%BA%E7%B3%BB%E7%BB%9F/"&gt;有人遇到了同样的问题&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;然后在 Stellar 配置文件里写入：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;comments: 
 giscus: 
 enable: true 
 repo: &amp;lt;YOUR_GITHUB_USERNAME/YOUR_REPO_NAME&amp;gt; 
 repo_id: &amp;lt;YOUR_REPO_ID&amp;gt; 
 category: &amp;lt;YOUR_CATEGORY_NAME&amp;gt; 
 category_id: &amp;lt;YOUR_CATEGORY_ID&amp;gt; 
 mapping: pathname 
 reactions_enabled: 1 
 emit_metadata: 0 
 input_position: bottom 
 lang: en 
 loading: lazy 
 crossorigin: anonymous&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;重新运行一下&lt;code&gt;hexo g &amp;amp;&amp;amp; hexo deploy&lt;/code&gt;即可。&lt;/p&gt;</description></item><item><title>本站推出剪贴板服务：Microbin</title><link>https://obsp.de/zh/posts/obsp-microbin/</link><guid isPermaLink="true">https://obsp.de/zh/posts/obsp-microbin/</guid><pubDate>Mon, 17 Mar 2025 12:13:39 +0000</pubDate><description>&lt;p&gt;Observer&amp;rsquo;s Space推出了一个新的公共&lt;a href="https://microbin.eu/"&gt;Microbin&lt;/a&gt;实例，欢迎大家使用。&lt;/p&gt;
&lt;p&gt;实例地址：https://bin.obsp.de&lt;/p&gt;
&lt;p&gt;您可以通过设置有效期、阅读次数限制和权限来保证数据安全。&lt;/p&gt;
&lt;p&gt;有关权限的说明：&lt;/p&gt;
&lt;h3 id="公开"&gt;公开&lt;/h3&gt;
&lt;p&gt;该隐私级别允许所有人查找、查看、修改和删除您的上传。&lt;/p&gt;
&lt;h3 id="未列出推荐"&gt;未列出（推荐）&lt;/h3&gt;
&lt;p&gt;未列出的上传无法被找到，除非有人知道其唯一的随机标识符。 如果有人知道此标识符，他们就可以查看、修改和删除上传内容。&lt;/p&gt;
&lt;h3 id="只读"&gt;只读&lt;/h3&gt;
&lt;p&gt;使用此隐私设置，除非有人知道上传内容的唯一随机标识符，否则无法找到该上传内容。 如果有人知道该标识符，则可以查看内容，但如果不输入上传密码，则无法修改或删除内容。&lt;/p&gt;
&lt;h3 id="隐私"&gt;隐私&lt;/h3&gt;
&lt;p&gt;使用该隐私设置，除非有人知道上传的唯一随机标识符，否则无法找到上传内容。 如果有人知道该标识符，则在未输入上传密码的情况下无法查看、修改或删除该内容。 您的上传及其附件都经过加密，因此存储安全。&lt;/p&gt;
&lt;h3 id="保密"&gt;保密&lt;/h3&gt;
&lt;p&gt;使用此隐私设置，除非有人知道上传的唯一随机标识符，否则无法找到上传内容。 如果有人知道该标识符，那么在未输入上传密码的情况无法查看、修改或删除该内容。 您的浏览器会向我们发送一个已经加密的版本，因此未加密的数据和密码根本不会离开您的设备。 此选项要求您在访问数据时多次输入密码，但非常安全。&lt;/p&gt;</description></item><item><title>2025.03.12更新</title><link>https://obsp.de/zh/posts/2025-03-12-2025.03.12%E6%9B%B4%E6%96%B0/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-03-12-2025.03.12%E6%9B%B4%E6%96%B0/</guid><pubDate>Wed, 12 Mar 2025 09:01:35 +0800</pubDate><description>&lt;p&gt;家里云暂时掉线，站长要考试，服务器不能放在房间里=.=&lt;/p&gt;
&lt;p&gt;对本站访问没有影响~&lt;/p&gt;</description></item><item><title>Rclone以WebDAV方式挂载Hetzner Storage Box</title><link>https://obsp.de/zh/posts/2025-03-10-rclone%E4%BB%A5webdav%E6%96%B9%E5%BC%8F%E6%8C%82%E8%BD%BDhetzner_storage_box/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-03-10-rclone%E4%BB%A5webdav%E6%96%B9%E5%BC%8F%E6%8C%82%E8%BD%BDhetzner_storage_box/</guid><pubDate>Mon, 10 Mar 2025 12:12:00 +0800</pubDate><description>&lt;p&gt;疑似有点太水了，想不出这个有什么出教程的必要（不是）。&lt;/p&gt;
&lt;p&gt;本来想尝试&lt;a href="https://rcloneui.com/"&gt;Rclone UI&lt;/a&gt;，奈何AppImage版在Arch上根本启动不了，我试着在AUR里找了一下&lt;code&gt;yay rclone-ui&lt;/code&gt;，居然没找到……原来AUR也不是万能的，那就继续用命令行的原版Rclone吧。&lt;/p&gt;
&lt;p&gt;Hetzner买Storage Box的过程就跳过了。因为hz是PAYG的模式，所以账单一般下个月才会出。&lt;/p&gt;
&lt;p&gt;进控制台：&lt;code&gt;https://robot.hetzner.com/storage&lt;/code&gt; ，Reset一下根用户密码。然后打开WebDAV、SSH，用WebDAV或SSH工具进去创建一个空的子文件夹，如果没有子文件夹的话是无法创建子账户的。&lt;/p&gt;
&lt;p&gt;然后去sub-account创建一个子账户，打开WebDAV和External-access，以便在互联网上访问。&lt;/p&gt;
&lt;p&gt;子账户的WebDAV地址是&lt;code&gt;https://uxxxx(换成你的主账户id)-subx(换成你的子账户id).your-storagebox.de&lt;/code&gt;，账户是uxxxx-subx，密码是你设置的密码。&lt;/p&gt;
&lt;p&gt;Rclone官方一键安装脚本：&lt;code&gt;sudo -v ; curl https://rclone.org/install.sh | sudo bash&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;按照提示操作选择新建远端，起个名字（以Hetzner为例）选中WebDAV，然后把上面的地址、用户名和密码填进去，剩余部分留空即可。&lt;/p&gt;
&lt;p&gt;挂载：&lt;code&gt;rclone mount hetzner: /path/to/mnt &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个时候就会发现Rclone提示这个远端不支持流式传输（streaming），需要加上关于缓存文件的参数。&lt;/p&gt;
&lt;p&gt;那么命令就可以修改成&lt;code&gt;rclone mount hetzner: /path/to/mnt --vfs-cache-mode writes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;运行命令的时候会发现只能前台运行，如果退出执行命令就会导致停止挂载。&lt;/p&gt;
&lt;p&gt;解决办法也很简单，加入后台运行参数即可：&lt;code&gt;rclone mount remote:path /path/to/mount --vfs-cache-mode writes --daemon&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>2025.03.09更新</title><link>https://obsp.de/zh/posts/2025-03-09-2025.03.09%E6%9B%B4%E6%96%B0/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-03-09-2025.03.09%E6%9B%B4%E6%96%B0/</guid><pubDate>Sun, 09 Mar 2025 17:41:21 +0800</pubDate><description>&lt;p&gt;站长在备考，这个月更新得比较少。&lt;/p&gt;
&lt;p&gt;准备把从今往后的博文从Typecho镜像到Hexo，Hexo网站托管在Codeberg Pages，暂时还没开发出Hexo的玩法，不过网站已经搭建起来了：https://hexo.obsp.de，欢迎访问（目前里面什么都没有）&lt;/p&gt;
&lt;p&gt;Roadmap：买Servarica的大盘鸡，为读者提供Nextcloud免费服务~&lt;/p&gt;</description></item><item><title>CalDAV及CardDAV服务器Radicale V3部署指南+Caddy V2一行反代</title><link>https://obsp.de/zh/posts/2025-03-05-caldav%E5%8F%8Acarddav%E6%9C%8D%E5%8A%A1%E5%99%A8radicale_v3%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97-caddy_v2%E4%B8%80%E8%A1%8C%E5%8F%8D%E4%BB%A3/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-03-05-caldav%E5%8F%8Acarddav%E6%9C%8D%E5%8A%A1%E5%99%A8radicale_v3%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97-caddy_v2%E4%B8%80%E8%A1%8C%E5%8F%8D%E4%BB%A3/</guid><pubDate>Wed, 05 Mar 2025 11:58:00 +0800</pubDate><description>&lt;p&gt;&lt;a href="https://sabre.io/baikal/"&gt;Baïkal&lt;/a&gt;的部署比较简单，然而由于某种神秘原因，Baïkal的日历在iOS上无法连接，即使我按照同类Github Issue中的解决方案在反代配置中使用了rewrite依然无法战胜，只好用回我的老朋友&lt;a href="https://radicale.org"&gt;Radicale&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;之前小鸡配置比较低，只跑得起这种超轻量级的服务，尽管现在在小鸡上部署了owncloud和nextcloud，我还是更习惯Radicale的管理界面，就有了今天这篇文章。
Radicale官方的文档写得还是很详细的，可以自行查阅。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前置条件：以下操作在Debian 12 bookworm中使用root用户完成。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="安装步骤"&gt;安装步骤&lt;/h2&gt;
&lt;p&gt;安装Python3虚拟环境：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;root@debvmde:~# apt install python3-venv&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;创建并进入radicale虚拟环境：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;root@debvmde:~$ python3 -m venv radicale
root@debvmde:~$ radicale/bin/pip install pip --upgrade setuptools wheel
root@debvmde:~$ source radicale/bin/activate&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;这时可以看到&lt;code&gt;root@debvmde:~# &lt;/code&gt;已经变成了&lt;code&gt;(radicale) root@debvmde:~# &lt;/code&gt;，说明我们已经进入虚拟环境，可以pip install了。&lt;/p&gt;
&lt;p&gt;运行一下：&lt;code&gt;pip install --upgrade radicale &amp;amp;&amp;amp; radicale --version&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;创建配置文件：&lt;code&gt;mkdir -p .config/radicale &amp;amp;&amp;amp; nano .config/radicale/config&lt;/code&gt;，根据官方文档的说法：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Radicale tries to load configuration files from &lt;code&gt;/etc/radicale/config&lt;/code&gt; and &lt;code&gt;~/.config/radicale/config&lt;/code&gt;. Custom paths can be specified with the &lt;code&gt;--config /path/to/config&lt;/code&gt; command line argument or the RADICALE_CONFIG environment variable. Multiple configuration files can be separated by : (resp. ; on Windows). Paths that start with ? are optional.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;创建用户名和密码文件&lt;code&gt;.config/radicale/users&lt;/code&gt;，可选的加密方式有plain/md5/bcrypt/autodetect，此处选用md5. （当然md5也并不是首选，关于怎么用htpasswd，请自行查阅官方文档）&lt;/p&gt;
&lt;p&gt;我们希望选用的用户名和密码分别是user和password，如果采用plain加密，&lt;code&gt;.config/radicale/users&lt;/code&gt;内就会存有明文的用户名和密码&lt;code&gt;user:password&lt;/code&gt;，这样显然是不太安全的，所以需要以哈希值的形式存储密码。&lt;/p&gt;
&lt;p&gt;使用htpasswd md5哈希生成器，把密码加密，得到形如&lt;code&gt;$apr1$j9JJ7hNw$aY8K5tLzVq4hi1XX/CnD70&lt;/code&gt;的字符串，我们的密码文件中加入&lt;code&gt;user:$apr1$j9JJ7hNw$aY8K5tLzVq4hi1XX/CnD70&lt;/code&gt;一行即可。&lt;/p&gt;
&lt;p&gt;打开配置文件&lt;code&gt;~/.config/radicale/config&lt;/code&gt;，加入以下内容：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;[server]
hosts = 0.0.0.0:5232, [::]:5232

[storage]
filesystem_folder=/root/radicale/collections

[auth]
type = htpasswd
htpasswd_filename = /root/.config/radicale/users
htpasswd_encryption = md5

[server]
max_connections = 20
# 100 Megabyte
max_content_length = 100000000
# 30 seconds
timeout = 30

[auth]
# Average delay after failed login attempts in seconds
delay = 1&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;现在可以启动服务了：运行&lt;code&gt;radicale&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;访问&lt;code&gt;&amp;lt;serverip&amp;gt;:5232&lt;/code&gt;，输入user和password，看看是否能成功进入。&lt;/p&gt;
&lt;p&gt;创建service：&lt;code&gt;nano /etc/systemd/system/radicale.service&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;加入以下内容：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;[Unit]
Description=A simple CalDAV (calendar) and CardDAV (contact) server

[Service]
ExecStart=/root/radicale/bin/python /root/radicale/bin/radicale

Restart=on-failure

[Install]
WantedBy=default.target&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;启动：&lt;code&gt;systemctl enable radicale &amp;amp;&amp;amp; systemctl start radicale&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="反代"&gt;反代&lt;/h2&gt;
&lt;p&gt;Caddy的配置非常简单：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;radicale.example.org {
	reverse_proxy localhost:5232
}&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;h2 id="使用官方文档中的sha-512对密码进行哈希时的注意事项"&gt;使用官方文档中的SHA-512对密码进行哈希时的注意事项&lt;/h2&gt;
&lt;p&gt;接下来是radicale官方文档的错误：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The secure way
The users file can be created and managed with htpasswd:&lt;/p&gt;
&lt;h3 id="create-a-new-htpasswd-file-with-the-user-user1-using-sha-512-as-hash-method"&gt;Create a new htpasswd file with the user &amp;ldquo;user1&amp;rdquo; using SHA-512 as hash method&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ htpasswd -5 -c /path/to/users user1&lt;/code&gt;
&lt;code&gt;New password:&lt;/code&gt;
&lt;code&gt;Re-type new password:&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="add-another-user"&gt;Add another user&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ htpasswd -5 /path/to/users user2&lt;/code&gt;
&lt;code&gt;New password:&lt;/code&gt;
&lt;code&gt;Re-type new password:&lt;/code&gt;
Authentication can be enabled with the following configuration:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;[auth]
type = htpasswd
htpasswd_filename = /path/to/users
htpasswd_encryption = autodetect #注意看注意看&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p&gt;千万不要！用！autodetect！&lt;/p&gt;
&lt;p&gt;是什么方法就写什么方法！比如我用SHA-512，就老老实实写&lt;code&gt;htpasswd_encryption = sha512&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;否则你会在&lt;code&gt;journalctl --unit radicale.service&lt;/code&gt;收获一串美丽的报错：&lt;code&gt;radicale.service: Failed to determine user credentials: No such process&lt;/code&gt;/&lt;code&gt;Failed at step USER spawning /root/radicale/bin/python: No such process&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="参考文献"&gt;参考文献：&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://radicale.org/v3.html"&gt;https://radicale.org/v3.html&lt;/a&gt;
&lt;a href="https://sigmdel.ca/michel/ha/aml912/radicale_en.html"&gt;https://sigmdel.ca/michel/ha/aml912/radicale_en.html&lt;/a&gt;
&lt;a href="https://www.atlantic.net/dedicated-server-hosting/how-to-install-radicale-calendar-caldav-and-carddav-on-ubuntu-20-04/"&gt;https://www.atlantic.net/dedicated-server-hosting/how-to-install-radicale-calendar-caldav-and-carddav-on-ubuntu-20-04/&lt;/a&gt;&lt;/p&gt;</description></item><item><title>SearXNG接入Open WebUI显示Error searching的解决方法</title><link>https://obsp.de/zh/posts/2025-03-03-searxng%E6%8E%A5%E5%85%A5open_webui%E6%98%BE%E7%A4%BAerror_searching%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-03-03-searxng%E6%8E%A5%E5%85%A5open_webui%E6%98%BE%E7%A4%BAerror_searching%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</guid><pubDate>Mon, 03 Mar 2025 10:22:00 +0800</pubDate><description>&lt;p&gt;可能是由于没开启JSON输出导致的，&lt;/p&gt;
&lt;p&gt;在您的&lt;code&gt;/usr/local/searxng-docker/searxng/settings.yml&lt;/code&gt;加入如下内容：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;search:
 formats:
 - html
 - json&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;OpenWebUI的搜索字符串不是%s，也不是空白，是&lt;code&gt;&amp;lt;query&amp;gt;&lt;/code&gt;，例如使用本站SearXNG实例进行搜索，要在联网搜索处配置的URL是&lt;code&gt;https://q.obsp.de/search?q=&amp;lt;query&amp;gt;&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Microsoft Account辅助邮箱验证bug:That doesn't match the alternate email associated with your account.</title><link>https://obsp.de/zh/posts/microsoft_account_bug/</link><guid isPermaLink="true">https://obsp.de/zh/posts/microsoft_account_bug/</guid><pubDate>Tue, 25 Feb 2025 21:56:00 +0800</pubDate><description>&lt;p&gt;Bug描述: 格式为xx.abcd@example.org的邮箱是微软账号的辅助邮箱之一。试图验证辅助邮箱地址，输入地址后显示 &amp;ldquo;That doesn&amp;rsquo;t match the alternate email associated with your account. The correct email starts with &amp;ldquo;xx&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Bug原因: 前端程序员太弱智。&lt;/p&gt;
&lt;p&gt;解决方案: 把第三个字符&lt;code&gt;.&lt;/code&gt;换成URL编码的&lt;code&gt;%2E&lt;/code&gt;即可。&lt;/p&gt;
&lt;p&gt;类似问题:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In my case, I only have two letters as an email name. For example, if my alternative email &lt;a href="mailto:is%60ab@xyz.org"&gt;is`ab@xyz.org&lt;/a&gt;&lt;code&gt;: Verify your email - We will send a verification code to &lt;/code&gt;&lt;a href="mailto:ab*****@XYZ.org"&gt;ab*****@XYZ.org&lt;/a&gt;&lt;code&gt;. To verify that this is your email address, enter it below. When I key in &lt;/code&gt;&lt;a href="mailto:ab@xyz.org"&gt;ab@xyz.org&lt;/a&gt;` , it always shows: That doesn&amp;rsquo;t match the alternate email associated with your account. The correct email starts with &amp;ldquo;ab&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;解决方案: 目前无解。&lt;/p&gt;
&lt;p&gt;参考文献: &lt;a href="https://answers.microsoft.com/en-us/outlook_com/forum/all/alternate-email-not-recognized-for-verification/3cb094ef-d1da-4294-891f-0f4d8cd7b3a2"&gt;Alternate Email not recognized for Verification code&lt;/a&gt;&lt;/p&gt;</description></item><item><title>2025.2.20更新</title><link>https://obsp.de/zh/posts/2025-02-20-2025.2.20%E6%9B%B4%E6%96%B0/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-20-2025.2.20%E6%9B%B4%E6%96%B0/</guid><pubDate>Thu, 20 Feb 2025 20:56:44 +0800</pubDate><description>&lt;p&gt;本站邮箱服务neomail.cc的欧洲分站正在筹备中，站长已购入新的域名neomail.nl，预计将使用瑞士托管商Migadu或Infomaniak的服务。&lt;/p&gt;</description></item><item><title>docker自建Bitwarden的社区精简高级版Vaultwarden，并使用Caddy反代</title><link>https://obsp.de/zh/posts/2025-02-19-docker%E8%87%AA%E5%BB%BAbitwarden%E7%9A%84%E7%A4%BE%E5%8C%BA%E7%B2%BE%E7%AE%80%E9%AB%98%E7%BA%A7%E7%89%88vaultwarden%E5%B9%B6%E4%BD%BF%E7%94%A8caddy%E5%8F%8D%E4%BB%A3/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-19-docker%E8%87%AA%E5%BB%BAbitwarden%E7%9A%84%E7%A4%BE%E5%8C%BA%E7%B2%BE%E7%AE%80%E9%AB%98%E7%BA%A7%E7%89%88vaultwarden%E5%B9%B6%E4%BD%BF%E7%94%A8caddy%E5%8F%8D%E4%BB%A3/</guid><pubDate>Wed, 19 Feb 2025 20:44:00 +0800</pubDate><description>&lt;p&gt;在体验过市面上一圈密码管理器（第三方托管+自建）之后，我还是可耻地选择了商业，选择每个月掏2刀给Proton让它赚=。= 不过我是因为太懒&amp;amp;对密码管理的需求太复杂才做出如此选择的，大部分热爱折腾的mjj还是喜欢Keepass和Vaultwarden，今天就来讲一下小鸡自建Vaultwarden的配置。&lt;/p&gt;
&lt;h2 id="启动-docker-容器"&gt;启动 Docker 容器&lt;/h2&gt;
&lt;p&gt;先mk个dir ：&lt;code&gt;mkdir vw &amp;amp;&amp;amp; cd vw&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker run&lt;/code&gt;比较快，不过我个人觉得&lt;code&gt;docker compose&lt;/code&gt;方便一些，&lt;code&gt;nano docker-compose.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;services:
 bitwarden:
 image: vaultwarden/server:latest
 container_name: bitwarden
 restart: unless-stopped
 environment:
 - DOMAIN=https://vw.mydomain.org/asuperrandomstring
 - SIGNUPS_ALLOWED=true
 - WEB_VAULT_ENABLED=true
 volumes:
 - ./vw-data:/data
 ports:
 - &amp;#34;8080:80&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;记得把域名的A记录加上，&lt;code&gt;vw.mydomain.org&lt;/code&gt;指向Vaultwarden服务器的ip。&lt;/p&gt;
&lt;p&gt;这里的path&lt;code&gt;/asuperrandomstring&lt;/code&gt;可以拿密码管理器生成一个，然后写在纸上或者文本文档里。千万不要只放在你建好的Vaultwarden密码库里，原因你懂的，就像不要给2FA验证器加2FA登录验证一样……&lt;/p&gt;
&lt;h2 id="配置caddyfile"&gt;配置&lt;code&gt;Caddyfile&lt;/code&gt;:&lt;/h2&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;vw.mydomain.org {
 route {
 reverse_proxy /asuperrandomstring/* localhost:8080 {
 header_up X-Real-IP {http.request.header.Cf-Connecting-Ip}
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;配置好一起启动：&lt;code&gt;docker compose up -d &amp;amp;&amp;amp; systemctl reload caddy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;访问&lt;code&gt;https://vw.mydomain.org/asuperrandomstring/&lt;/code&gt;，创建账号，导入密码，不再赘述。&lt;/p&gt;
&lt;p&gt;创建完所有需要的账号之后，记得关闭访客注册~&lt;/p&gt;
&lt;p&gt;关闭访客注册的方式是&lt;code&gt;docker compose down&lt;/code&gt;，然后修改&lt;code&gt;docker-compose.yaml&lt;/code&gt;中&lt;code&gt;SIGNUPS_ALLOWED&lt;/code&gt;的值为&lt;code&gt;false&lt;/code&gt;，再&lt;code&gt;docker compose up -d&lt;/code&gt;即可。&lt;/p&gt;
&lt;h2 id="数据备份"&gt;数据备份&lt;/h2&gt;
&lt;p&gt;为防止数据丢失，可以用之前提到过的&lt;a href="https://obsp.de/index.php/archives/filen-cli-backup.html"&gt;使用Filen CLI定时备份服务器数据&lt;/a&gt;备份你的vw-data文件夹~如果这么做，确保你的Filen账号和密码是自己记得的，而不是存储在Vaultwarden保险库里面的，否则一旦服务器上的数据受损，想从Filen取回备份的时候，会把自己反锁在外面。&lt;/p&gt;</description></item><item><title>Keepass多端同步折腾笔记（Linux+Android+iPadOS）</title><link>https://obsp.de/zh/posts/2025-02-18-keepass%E5%A4%9A%E7%AB%AF%E5%90%8C%E6%AD%A5%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0linux-android-ipados/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-18-keepass%E5%A4%9A%E7%AB%AF%E5%90%8C%E6%AD%A5%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0linux-android-ipados/</guid><pubDate>Tue, 18 Feb 2025 09:53:00 +0800</pubDate><description>&lt;h2 id="同步方案"&gt;同步方案&lt;/h2&gt;
&lt;p&gt;密码在Nextcloud上存一份，手机本地存一份，PC本地存一份。&lt;/p&gt;
&lt;h3 id="android"&gt;Android&lt;/h3&gt;
&lt;p&gt;手机本地文件夹用&lt;a href="https://foldersync.io/"&gt;Foldersync&lt;/a&gt;和Nextcloud进行双向同步（想要用free and open source software的话，下载&lt;a href="https://rclone.org/"&gt;rclone&lt;/a&gt;的安卓GUI &lt;a href="https://roundsync.com/"&gt;Round Sync&lt;/a&gt;或者&lt;a href="https://termux.dev/en/"&gt;Termux&lt;/a&gt;命令行模拟器直接用&lt;a href="https://rclone.org/"&gt;rclone&lt;/a&gt;吧，我只用过前者，不支持双向同步功能，作者说这个功能实现遇到了困难，搁置了。rclone的本体是支持&lt;a href="https://rclone.org/bisync/"&gt;bisync&lt;/a&gt;的，但是我懒得在小屏幕上折腾命令行），设置定时任务，每小时一次。我用的安卓Keepass客户端是&lt;a href="https://www.keepassdx.com/"&gt;KeepassDX&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id="linux-pc"&gt;Linux PC&lt;/h3&gt;
&lt;p&gt;Linux桌面将Nextcloud文件夹挂载到本地文件目录，双向同步对文件的修改；或者用rclone连接远端，&lt;code&gt;crontab -e&lt;/code&gt;定时运行&lt;code&gt;rclone bisync&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;客户端+浏览器扩展是&lt;a href="https://keepassxc.org/"&gt;KeepassXC&lt;/a&gt;。（我用的浏览器是Brave，需要注意KeepassXC不支持Flatpak版的Brave，请用官方脚本/软件源安装）&lt;/p&gt;
&lt;h3 id="ios"&gt;iOS&lt;/h3&gt;
&lt;p&gt;iPad用&lt;a href="https://strongboxsafe.com/"&gt;Strongbox&lt;/a&gt;，fetch远程的WebDAV文件夹，或者&lt;a href="https://github.com/kaich/FantasyPass"&gt;奇密（Fantasy Pass）&lt;/a&gt;，后者很便宜，不过部分代码是闭源的，且作者不再维护，GitHub仓库上一次更新是7年前。&lt;/p&gt;
&lt;p&gt;Strongbox免费版满足大部分需求，并且ui、自动填充2FA和数据库警报都很好用，生成密码的功能也很全面（还可以加盐，但是我不太懂为什么加盐要在密码管理器生成明文密码这一步进行）。&lt;/p&gt;
&lt;p&gt;踩坑：用Argon2加密的iOS用户，一定要记得去加密方式里面把内存调到32MB以下，默认好像是64；如果保持默认的话由于iOS对每个应用的内存限制，在使用输入法内嵌自动填充的时候会崩溃。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;这套方案唯一的问题就是Nextcloud对数据的处理有时候不太尽如人意，经常因为文件名相同内容不同就报错，但是直接忽略，取最新版就OK了。或者换别的WebDAV/S3提供商，例如&lt;a href="https://koofr.eu"&gt;Koofr&lt;/a&gt;，&lt;a href="https://www.hetzner.com/storage/storage-box/"&gt;Hetzner Storage Box&lt;/a&gt;，&lt;a href="https://www.scaleway.com/en/object-storage/"&gt;Scaleway 对象存储&lt;/a&gt;，&lt;a href="https://www.storj.io/"&gt;Storj&lt;/a&gt;等等。（之前同步的时候用过&lt;a href="https://backblaze.com"&gt;Backblaze B2&lt;/a&gt;，我的感想是最好不要用B2，B2的list object操作是计费的，rclone非常吃这个操作，尤其是文件版本多/小文件多的情况，即使在同步命令里加上&lt;code&gt;--fast-list --local-no-check-updated&lt;/code&gt;也能把一天中的2500次操作吃光。）&lt;/p&gt;</description></item><item><title>Arch Linux(GNOME)安装Fcitx 5输入法+中州韵Rime+雾凇拼音</title><link>https://obsp.de/zh/posts/2025-02-18-arch_linuxgnome%E5%AE%89%E8%A3%85fcitx_5%E8%BE%93%E5%85%A5%E6%B3%95-%E4%B8%AD%E5%B7%9E%E9%9F%B5rime-%E9%9B%BE%E5%87%87%E6%8B%BC%E9%9F%B3/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-18-arch_linuxgnome%E5%AE%89%E8%A3%85fcitx_5%E8%BE%93%E5%85%A5%E6%B3%95-%E4%B8%AD%E5%B7%9E%E9%9F%B5rime-%E9%9B%BE%E5%87%87%E6%8B%BC%E9%9F%B3/</guid><pubDate>Tue, 18 Feb 2025 09:25:00 +0800</pubDate><description>&lt;p&gt;以下配置适用于GNOME桌面，KDE和Xfce用户请参考其它文章。&lt;/p&gt;
&lt;h2 id="安装fcitx-5和中州韵rime"&gt;安装Fcitx 5和中州韵Rime：&lt;/h2&gt;
&lt;p&gt;Fcitx 5用AUR helper装：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;yay -S fcitx5 fcitx5-rime fcitx5-configtool fcitx5-gtk fcitx5-qt fcitx5-config-qt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我一直用sudo yay，发现以sudo运行的时候会提示不建议，直接yay即可。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nano&lt;/code&gt;一下&lt;code&gt;/etc/environment&lt;/code&gt;，加入如下内容&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;XMODIFIERS=@im=@fcitx
QT_IM_MODULE=fcitx
GTK_IM_MODULE=fcitx&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;h2 id="安装必要的gnome组件"&gt;安装必要的GNOME组件：&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://extensions.gnome.org/extension/261/kimpanel/"&gt;安装Input Method Panel&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;重启一下，打开Fcitx 5配置，把中州韵Rime添加到键盘里面。&lt;/p&gt;
&lt;h2 id="安装雾凇拼音"&gt;安装雾凇拼音：&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;yay -S rime-ice-git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;改一下Rime的配置：&lt;code&gt;nano /.local/share/fcitx5/rime/default.custom.yaml&lt;/code&gt;，写入：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;patch:
 # 仅使用「雾凇拼音」的默认配置，配置此行即可
 __include: rime_ice_suggestion:/
 # 以下根据自己所需自行定义，仅做参考。
 # 针对对应处方的定制条目，请使用 &amp;lt;recipe&amp;gt;.custom.yaml 中配置，例如 rime_ice.custom.yaml
 __patch:
 key_binder/bindings/+:
 # 开启逗号句号翻页
 - { when: paging, accept: comma, send: Page_Up }
 - { when: has_menu, accept: period, send: Page_Down }&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;参考文献：https://github.com/iDvel/rime-ice&lt;/p&gt;</description></item><item><title>2025.2.17更新</title><link>https://obsp.de/zh/posts/2025-02-17-2025.2.17%E6%9B%B4%E6%96%B0/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-17-2025.2.17%E6%9B%B4%E6%96%B0/</guid><pubDate>Mon, 17 Feb 2025 23:15:56 +0800</pubDate><description>&lt;p&gt;通过编辑&lt;code&gt;limiter.toml&lt;/code&gt;，
修复了本站SearXNG实例风控严重，搜索时重定向回主页的问题。&lt;/p&gt;</description></item><item><title>使用Filen CLI定时备份服务器数据</title><link>https://obsp.de/zh/posts/2025-02-16-%E4%BD%BF%E7%94%A8filen_cli%E5%AE%9A%E6%97%B6%E5%A4%87%E4%BB%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%B0%E6%8D%AE/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-16-%E4%BD%BF%E7%94%A8filen_cli%E5%AE%9A%E6%97%B6%E5%A4%87%E4%BB%BD%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%B0%E6%8D%AE/</guid><pubDate>Sun, 16 Feb 2025 20:04:59 +0800</pubDate><description>&lt;p&gt;这一篇文章没有docker了，docker方便又无聊，很多服务的部署讲来讲去就那样，Caddy的反代只有一行，更没营养，所以讲点别的吧。&lt;/p&gt;
&lt;p&gt;今天的主角是德国的加密云盘&lt;a href="https://filen.io"&gt;filen.io&lt;/a&gt;，定价宜人，虽然对于大量存储而言比不上Storj B2 OVH这些便宜的对象存储有竞争力，个人使用也算是便宜大碗该有的都有，不像&lt;a href="https://proton.me/drive"&gt;Proton Drive&lt;/a&gt;一样是个挂着加密噱头的半成品。尽管由于加密的特性不支持WebDAV挂载，Filen团队还是别出心裁地开发了一套命令行工具――Filen CLI，让用户可以实现云盘挂载到本地目录的同时，还提供了在本地运行支持WebDAV和S3协议的Filen镜像服务器的功能，用户登录镜像服务器的WebDAV或S3地址，即可轻松挂载Filen云存储到其他设备(具体的操作，等有空了另写一篇)。Filen给免费用户的空间也十分慷慨，初始10 GB，后续可以通过邀请码邀请新用户注册，双方都可以额外获得10 GB，免费用户的空间最高可以达到50GB。有兴趣的朋友可以试一试用&lt;a href="https://filen.io/r/7bbe615e47fdaea5fbd676a9564bdde6"&gt;我的邀请码&lt;/a&gt;注册，即可获得20 GB的免费空间。需要注意的是，Filen的TOS明确禁止一人持有多个免费账户，想要薅羊毛的还是谨慎。&lt;/p&gt;
&lt;p&gt;言归正传，接下来我们进入使用场景：Filen账号1个，Linux VPS 1台，VPS上运行着一项服务，数据位于文件夹/path/to/myservice，此时我们希望做到的是：每天凌晨2点自动备份该文件夹中的数据到Filen云端，VPS本地数据覆盖Filen云端数据。&lt;/p&gt;
&lt;p&gt;连接服务器，执行命令&lt;code&gt;curl -sL https://filen.io/cli.sh | bash&lt;/code&gt;，安装Filen CLI。&lt;/p&gt;
&lt;p&gt;安装完成后即可通过&lt;code&gt;filen [options...]&lt;/code&gt;与Filen CLI交互。&lt;/p&gt;
&lt;p&gt;第一次使用应该会要求输入账号和密码（还有2FA code，如果你设置了的话），并且询问是否保存。因为我们要让filen同步命令每天运行，所以就选择保存，以后需要删除的话可以用&lt;code&gt;filen delete-credentials&lt;/code&gt;这条命令删掉。&lt;/p&gt;
&lt;p&gt;nano 一个 sync 脚本&lt;code&gt;sync-local-to-cloud.sh&lt;/code&gt;：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;export PATH=$HOME/.filen-cli/bin:$PATH 
#这个很重要，不然会出现command not found的报错
 
SERVICE_DATA_DIR=&amp;#34;/path/to/myservice&amp;#34; #需要备份的数据目录
 
REMOTE_FILEN_DIR=&amp;#34;/myserverdata/myservice&amp;#34; #远端目录
 
# Sync the folder to Filen
filen sync $SERVICE_DATA_DIR:ltc:$REMOTE_FILEN_DIR
#ltc的意思是local to cloud，本地同步到云端&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Filen命令行官方教学：https://docs.filen.io/docs/cli/&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然后使用&lt;code&gt;crontab -e&lt;/code&gt;设置定时任务：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;00 2 * * * /root/sync-local-to-cloud.sh &amp;gt;&amp;gt; /root/sync.log 2&amp;gt;&amp;amp;1
#每天2点运行一次备份脚本，并且输出log到指定的文件sync.log&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;大功告成！&lt;/p&gt;
&lt;p&gt;第二天早上起来，记得看看log里是否有成功备份的记录。&lt;/p&gt;</description></item><item><title>开普勒第二定律推导</title><link>https://obsp.de/zh/posts/2025-02-15-%E5%BC%80%E6%99%AE%E5%8B%92%E7%AC%AC%E4%BA%8C%E5%AE%9A%E5%BE%8B%E6%8E%A8%E5%AF%BC/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-15-%E5%BC%80%E6%99%AE%E5%8B%92%E7%AC%AC%E4%BA%8C%E5%AE%9A%E5%BE%8B%E6%8E%A8%E5%AF%BC/</guid><pubDate>Sat, 15 Feb 2025 19:50:00 +0800</pubDate><description>&lt;p&gt;&lt;img src="https://i.111666.best/image/AKmU3nPO4DPApfENaXXILd.jpeg" alt="过程"&gt;&lt;/p&gt;</description></item><item><title>本站SearXNG实例存在的问题</title><link>https://obsp.de/zh/posts/2025-02-14-%E6%9C%AC%E7%AB%99searxng%E5%AE%9E%E4%BE%8B%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-14-%E6%9C%AC%E7%AB%99searxng%E5%AE%9E%E4%BE%8B%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98/</guid><pubDate>Fri, 14 Feb 2025 21:19:56 +0800</pubDate><description>&lt;p&gt;Check this GitHub issue: &lt;a href="https://github.com/searxng/searxng/issues/3191"&gt;https://github.com/searxng/searxng/issues/3191&lt;/a&gt;&lt;/p&gt;</description></item><item><title>去谷歌化路漫漫（一）：Proton和电子邮件</title><link>https://obsp.de/zh/posts/2025-02-14-%E5%8E%BB%E8%B0%B7%E6%AD%8C%E5%8C%96%E8%B7%AF%E6%BC%AB%E6%BC%AB%E4%B8%80proton%E5%92%8C%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-14-%E5%8E%BB%E8%B0%B7%E6%AD%8C%E5%8C%96%E8%B7%AF%E6%BC%AB%E6%BC%AB%E4%B8%80proton%E5%92%8C%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6/</guid><pubDate>Fri, 14 Feb 2025 17:36:00 +0800</pubDate><description>&lt;p&gt;这篇文章将会很长，去谷歌化的路程是我对所见种种感受改变，对自己十几年互联网生活反思、改进，有失有得的过程。每一期我都会聊一些产品，以及我个人的体验，不客观不真实，仅供参考。今天先讲去谷歌化中的谷歌：Proton。&lt;/p&gt;
&lt;p&gt;2024年底，我开始去谷歌化了。&lt;/p&gt;
&lt;p&gt;原因和大多数用户的追求隐私/支持自由软件不同，我只是想要一个简洁的互联网生活。&lt;/p&gt;
&lt;p&gt;希望我的邮件客户端不要再讨好我，从大数据中预测这一封放进社交媒体还是收件箱; 希望每次登录账户时输入密码和2步验证代码就能成功登录，不必拿出另一部设备进行一番操作; 希望在使用YouTube/B站时，我能选择自己想要的客户端，而不是把一切都寄托在谷歌/叔叔恩赐一般的优化; 希望我的密码可以跟着我的人走，而不是被绑在Chrome里; 希望讨厌的广告，不要总是霸占搜索引擎的前排; 希望我的账户信息，不会因为我少加了一个密保邮箱和手机号，就面临着随时把自己锁在外面的风险……&lt;/p&gt;
&lt;p&gt;当我刷着Reddit，r/deGoogling这个版块出现在眼前，我一下子就明白了它的意思: 是的，这就是我一直想要的!&lt;/p&gt;
&lt;p&gt;……吗?&lt;/p&gt;
&lt;p&gt;似乎不是。&lt;/p&gt;
&lt;p&gt;论坛里的大家，兴致勃勃地讨论着谷歌套件的替代品，让我感到很受鼓舞。在欧美地区去谷歌化堪称“逆天而行”，离开了谷歌的便利工具，许多人甚至感到一夜变成了原始人。作为手足无措的用户中的一员，我开始从101看起。&lt;/p&gt;
&lt;p&gt;去谷第一剑，先斩gmail。而热度最高的gmail，以至于谷歌套件的替代品，就是Proton。&lt;/p&gt;
&lt;p&gt;Protonmail，这是一个我在2020年就听说的名字。“端到端加密”“尊重隐私”的邮件，它几乎凭借一己之力，将这原本小众的需求成功带进了大众的视野。从前的Proton只有邮件服务，而如今却大有要做成隐私友好的网络服务提供商中的Google的意思：VPN，密码管理器，邮件，云盘，日历，还有24年底面向Visionary用户（一种类似于给Proton进行了风投的顶级订阅用户，这些用户不仅能获得所有产品的最高级权益，还能保证享受以后的所有升级。当然订阅费很贵，并且根据reddit用户回忆，这种订阅只在Proton初创和24年黑五后开始强推Wallet的时间段开放过）进行了加密货币钱包的内测。&lt;/p&gt;
&lt;p&gt;然而，这个提供商却让我感到比Google还令人无所适从。&lt;/p&gt;
&lt;p&gt;打开&lt;a href="https://proton.me"&gt;它的网站&lt;/a&gt;，紫与蓝的配色十分醒目，居中衬线体的标语是“A better internet starts with privacy and freedom（更好的互联网从隐私和自由开始）”，并在首页展示了Proton的各项业务与最新的促销信息。不得不说，Proton的UI进步不小，从2020年初以一把锁为Logo，灰扑扑的朴素模样，到如今大方优雅、细致入微的紫色系平面设计，像是在向互联网昭示这个新兴的企业已经完成了从丑小鸭到白天鹅的蜕变。&lt;/p&gt;
&lt;p&gt;作为一个穷鬼，我首先做的就是点开定价一项项察看。在2024年11月，Proton各项服务的定价（根据我的回忆，此处我会找到互联网档案的资料补上）是这样的：各项服务的基础功能免费，各自的会员定价不同。邮件服务Mail Plus是4.99USD/mo，年付八折；第二大业务VPN的Plus是9.99USD/mo，两年付五折；密码管理器Pass Plus是2.99USD/mo，年付价格为1.99USD/mo（Pass与邮箱别名服务SimpleLogin两项业务的会员在早些时候是分开的，分别定价2.99USD/mo。24年底完成了Pass Plus和SimpleLogin Premium的合并，合并后提价至3.99USD/mo，年付七五折）；云盘Drive 200GB的空间需要收取4.99USD/mo，年付八折。&lt;/p&gt;
&lt;p&gt;这定价不能算贵，但也不算便宜。在欣赏完这些对隐私大有助益的服务之后，我不禁汗流浃背：这样子一项一项加起来，一个月的花费接近30刀。当然，实际上不需要这么多，因为——&lt;/p&gt;
&lt;p&gt;Proton将每一项服务的单价抬高，最终的目的就是吸引用户把目光投向中间高亮的、用紫色框框围起来的选项——Proton Unlimited，月付12.99USD——当然，这不重要——年付9.99USD/mo。此时，刚刚在心中算了一笔账的用户就会发现：哎，这个划算！&lt;/p&gt;
&lt;p&gt;不仅如此，Proton在购买页面从未告知客户：当用户购买了一项服务的Plus之后，便不能在同一个Proton账户上叠加其他服务的Plus。也就是说，“我只买Mail Plus和Pass Plus/SimpleLogin就够了”的想法是根本不可能实现的，在一项服务的Plus之上，只有直接购买Proton Unlimited的选项。而且，由于月付与年付的价格差异，Proton吸引客户更多地购买Unlimited的年付套餐。&lt;/p&gt;
&lt;p&gt;然而，根据互联网上用户的反馈和我个人的经验来看，这种捆绑定价的策略让顾客并不满意：不少人抱怨Proton涉足云盘、密码管理器的领域，而首推的邮箱服务和VPN却并没有进行多少优化，多而不精。&lt;/p&gt;
&lt;p&gt;我也忍不住想，如果去谷歌化的过程就是从一个公司跳到另一个公司，相信它对安全和隐私的承诺，并且每个月掏出9.99刀给它，相信这样就能买到安心，那么为什么不直接相信谷歌呢？&lt;/p&gt;
&lt;p&gt;当然，我们有证据说明谷歌对用户的隐私不够尊重，而Proton做得远比谷歌好；但是，这样足够吗？&lt;/p&gt;
&lt;p&gt;就拿邮箱来说，Proton为了保证E2EE（端到端加密，只有收信人和发信人能够解密信息），取消了对邮件行业标准IMAP/SMTP的支持，而后又顶不住客户的需求和自己的客户端实在太难用的事实，重新在桌面端加入了Bridge。这种打折腿给根拐的行为两面不讨好，对于真正需要隐私的客户而言，他们大可以去选择OpenPGP加密和Signal聊天软件这些更加成熟且广为接受的产品，而对于普通的客户而言，他们只是想用熟悉方便的客户端收发邮件，都要为此付出一笔月费，而且在移动端，Proton至今没有对第三方邮件客户端的支持。&lt;/p&gt;
&lt;p&gt;至于Proton此举对E2EE的实现效果，我只能说：&lt;strong&gt;如有&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果与Proton用户通信的对象位于另一个邮件服务器，那通信对象的邮件提供商一样能够阅读他们之间通信的内容。为了保障同外部联系人的通信也是E2EE，需要提前设置一个密码，对方收到邮件后，需要输入密码才能阅读。问题在于，OpenPGP就在那里，既有Thunderbird和eM Client的原生支持，又有Mailvelope这样的浏览器插件，在电子邮件中实现加密通信早就不是什么新鲜事，Proton此举无异于画蛇添足。Proton的一个主要营销点是总部位于瑞士，然而“瑞士的隐私法律”管辖住的也只有Proton保留与分享你的信息的权力。正如我刚才所说，你的邮件来自互联网各处，发向互联网各处。即使谷歌，或者说以GAFAM为首的数据矿工不再能够收集你收件箱中的信息，但你还是把真实的邮箱地址暴露在各种零售商的mailing list中，那么收集这些公开的信息一样可以拼凑出你的用户画像。&lt;/p&gt;
&lt;p&gt;当然，我也有不少Proton的好话要说：除了云盘实在没得夸，邮件也不符合我心中的评价标准，Pass和VPN我还是颇为欣赏的。Pass和SimpleLogin别名互通、会员合并之后，已经成为了我心目中Bitwarden之下第一密码管理器（KeePass论外，从灵活性/自建成本来说它应该是断层第一，但是KeePass的好用程度取决于客户端，用户体验上限高、下限低，参差不齐），开源的客户端、方便的导入与导出、独步天下的别名生成与管理，基础功能如附件、分类模板、密码生成和密码口令生成、2FA代码储存和填写，除了无法自建和云端优先这两个小小缺憾，可称得上是该有的都有，后来居上，大有压1Password一头之势。VPN功能丰富，体验稳定，直连也不会捶胸顿足，IP比较脏，但也是在所难免。&lt;/p&gt;
&lt;p&gt;当时的我只是想找一个适合穷鬼、可以用IMAP/SMTP并且看起来不像随时要跑路的提供商，当然，如果不是大公司的产品，我会更乐意。找来找去，目光锁定了mailbox.org和posteo.de，这两家各有优劣，起价都便宜，前者的优势是有域名邮箱支持，后者定价是PAYG的模式，并且用户忘记按时付款只会暂停发信功能，不会暂停收信功能（我说这个不是教唆网友去薅羊毛，只是说假如我被车撞了在医院里躺了半年，不会因为错过缴费而失去这半年里发到我的地址的重要邮件）。后来用了一会，两家我都不太满意，mailbox的网页从我这里加载实在是太慢了，符合对德国网络的刻板印象；posteo是因为最后我还是更想用域名邮箱一点，但是他们家出于隐私保护不希望保存用户的域名信息。&lt;/p&gt;
&lt;p&gt;之所以最后还是用上了域名邮箱，是因为频繁地注册各类账号的我很需要一个别名服务，买不起SimpleLogin和Addy.io，琢磨自建的时候突然发现：不对啊，自建肯定需要一个域名，这整个域名都是我的，我还在这里装模作样地enable和disable做什么，直接开个catch-all，遇到垃圾就block，岂不美哉！而且，未来SimpleLogin或其它别名服务的域名可能为他人注册，或者挪作他用，但是只要我一直续费域名，域名就一直属于我，便宜又可靠。&lt;/p&gt;
&lt;p&gt;所以我最后靠上的邮箱解决方案是几个主要域名不开catch-all，几个次要域名开catch-all接收垃圾邮件（不发送的话直接用Cloudflare路由），买的是MXRoute家的不按邮箱个数计费只按容量计费的邮件托管服务，一年下来的域名钱和邮箱钱加起来只要15刀，花五分之一的钱享受比Mail Plus+SimpleLogin更自由的服务（这个数字还能腰斩，明年要放生一个.cc域名）。&lt;/p&gt;
&lt;p&gt;当然这个时候就有人要问了，有什么坏处吗？如有吧，比如MXRoute的老板不在瑞士，我的域名registrar也不在瑞士，Cloudflare也不在瑞士，但是我是穷鬼，穷鬼是不会追求这个的，反正本站也不在瑞士（本站的主机托管在风景不知道怎么样反正站长没去过的德国纽伦堡）……&lt;/p&gt;
&lt;p&gt;说完了服务端，该说客户端了。我没有试过所有风评“值得一试”的客户端，比如Claw Mail，我的体验很浅，仅仅是从Fedora换到Arch的时候试了一下，后来发现还是Thunderbird比较得劲。Thunderbird是一个成熟、面面俱到的开源客户端，赖舒服了就不想走，最大的特色大概是自动发现收发信服务器，安卓端的FairEmail用的也是tb的检索功能。不习惯Thunderbird，想要更简洁一点的Linux桌面用户可以尝试一下Evolution。FairEmail是我心目中的安卓最佳，尽管UI褒贬不一，但是功能肯定是最多的，对于默认不加载外部图像的邮件排版得非常好，一目了然，其他客户端往往都需要加载出图片才能看出邮件的内容。K-9无功无过，被Thunderbird合并后表现更加亮眼。eM Client，还没有试过。最近买了NameCrane的三年套餐，SmarterMail为每个顶级域送3个license，过段时间会试试看。&lt;/p&gt;
&lt;p&gt;至于各大邮件厂商自己的应用，网易邮箱大师的桌面端和移动端都一般般，校园邮薅的永久vip去广告版尚可一用，但是Thunderbird和FairEmail珠玉在前，只能说是食之无用，弃之可惜。QQ和foxmail也是一样。Tuta的邮件客户端倒是没有广告，不过搜索功能很垃圾，也不能导入/导出。&lt;/p&gt;
&lt;p&gt;未完待续，本期水完邮箱，下期水一下密码管理软件吧。&lt;/p&gt;</description></item><item><title>2025.2.13 更新</title><link>https://obsp.de/zh/posts/2025-02-13-2025.2.13_%E6%9B%B4%E6%96%B0/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-13-2025.2.13_%E6%9B%B4%E6%96%B0/</guid><pubDate>Thu, 13 Feb 2025 21:34:00 +0800</pubDate><description>&lt;p&gt;取消了访问时出现的Cloudflare的JavaScript质询页面。&lt;/p&gt;
&lt;p&gt;本博客主机提供商rarecloud.io正在经历大规模DDoS，攻击者跑了本博客托管VPS 1TB传入流量，让站长草木皆兵，不得不防。&lt;/p&gt;
&lt;p&gt;现已将博客迁移到Netcup。&lt;/p&gt;</description></item><item><title>docker compose部署订阅管理软件wallos，并启用telegram机器人通知</title><link>https://obsp.de/zh/posts/2025-02-11-docker_compose%E9%83%A8%E7%BD%B2%E8%AE%A2%E9%98%85%E7%AE%A1%E7%90%86%E8%BD%AF%E4%BB%B6wallos%E5%B9%B6%E5%90%AF%E7%94%A8telegram%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%80%9A%E7%9F%A5/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-11-docker_compose%E9%83%A8%E7%BD%B2%E8%AE%A2%E9%98%85%E7%AE%A1%E7%90%86%E8%BD%AF%E4%BB%B6wallos%E5%B9%B6%E5%90%AF%E7%94%A8telegram%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%80%9A%E7%9F%A5/</guid><pubDate>Tue, 11 Feb 2025 00:48:00 +0800</pubDate><description>&lt;p&gt;话费，域名，邮箱，VPS，密码管理器，云存储，流媒体会员……数字生活的订阅开支无处不在，却难以管理。你是否也有需要的服务到期忘记续费，而不需要的服务不小心自动续费的烦恼? 别担心，已经有开发者推出了解决方案，那就是订阅管理软件&lt;a href="https://github.com/ellite/Wallos"&gt;Wallos&lt;/a&gt;。这是一个可以自部署的开源项目，不仅有多种货币和支付方式，还有各种各样的提醒方式，包括email, discord, pushover, telegram, gotify和webhooks。项目部署支持baremetal和docker，无论你是想高度自定义，还是想简便快捷，都可以做到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这篇文章聚焦使用docker compose的方式部署Wallos管理订阅，然后使用tg机器人通知来实现提醒。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="wallos的部署"&gt;Wallos的部署&lt;/h2&gt;
&lt;p&gt;我们先&lt;code&gt;mkdir wallos &amp;amp;&amp;amp; cd wallos&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;编辑一下&lt;code&gt;nano docker-compose.yaml&lt;/code&gt;，添加如下内容:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;services:
 wallos:
 container_name: wallos
 image: bellamy/wallos:latest
 ports:
 - &amp;#34;8282:80/tcp&amp;#34;#左边改成你喜欢的端口
 environment:
 TZ: &amp;#39;America/Toronto&amp;#39;#改成你所在的时区
 # Volumes store your data between container upgrades
 volumes:
 - &amp;#39;./db:/var/www/html/db&amp;#39;
 - &amp;#39;./logos:/var/www/html/images/uploads/logos&amp;#39;
 restart: unless-stopped&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;最后，&lt;code&gt;docker compose up -d&lt;/code&gt;，大功告成!&lt;/p&gt;
&lt;p&gt;运行&lt;code&gt;docker compose logs -f&lt;/code&gt;看看是否正常启动。&lt;/p&gt;
&lt;h2 id="解析域名设置反向代理"&gt;解析域名，设置反向代理&lt;/h2&gt;
&lt;p&gt;把域名wallos.mydomain.org解析到服务器ip地址。&lt;/p&gt;
&lt;p&gt;这里用的是Caddy V2，Apache和Nginx用户请自行同理可得。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nano /etc/caddy/Caddyfile&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;加入以下内容:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;wallos.mydomain.org {
 reverse_proxy localhost:8282
}&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;重载一下Caddy: &lt;code&gt;systemctl reload caddy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;打开浏览器访问wallos.mydomain.org，看看是否正常。创建管理员账号，设置首选货币等，此处不再赘述。&lt;/p&gt;
&lt;h2 id="telegram机器人绑定"&gt;Telegram机器人绑定&lt;/h2&gt;
&lt;p&gt;进入Wallos，点击头像，点击[设置]，点击[通知]中的[Telegram]。&lt;/p&gt;
&lt;p&gt;打开Telegram，向@BotFather发送/newbot，按照提示操作。&lt;/p&gt;
&lt;p&gt;创建完成后，BotFather会发送一条信息:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;Done! Congratulations on your new bot. You will find it at t.me/&amp;lt;botname›. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you&amp;#39;ve finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.

Use this token to access the HTTP API:‹yourapi›
Keep your token secure and store it safely, it can be used by anyone to control your bot.

For a description of the Bot API, see this page: https://core.telegram.org/bots/api&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;把获得的&lt;code&gt;&amp;lt;yourapi&amp;gt;&lt;/code&gt;填入Wallos Telegram通知的第一栏。&lt;/p&gt;
&lt;p&gt;浏览器访问&lt;code&gt;https://api.telegram.org/bot&amp;lt;yourapi&amp;gt;/getUpdates&lt;/code&gt;，回到tg点击&lt;code&gt;t.me/&amp;lt;botname&amp;gt;&lt;/code&gt;给你的机器人发条消息，然后浏览器刷新，即可获得一个JSON文件，其中键&amp;quot;id&amp;quot;的值就是你的chat id，把这个值填入Wallos Telegram通知的第二行，勾选启用，点击测试。&lt;/p&gt;
&lt;p&gt;如果tg机器人给你发送了消息，说明配置成功运行。接下来只要为订阅设置通知日期，就可以静待Wallos的自动提醒了!&lt;/p&gt;</description></item><item><title>Logseq插件市场无法打开</title><link>https://obsp.de/zh/posts/2025-02-10-logseq%E6%8F%92%E4%BB%B6%E5%B8%82%E5%9C%BA%E6%97%A0%E6%B3%95%E6%89%93%E5%BC%80/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-10-logseq%E6%8F%92%E4%BB%B6%E5%B8%82%E5%9C%BA%E6%97%A0%E6%B3%95%E6%89%93%E5%BC%80/</guid><pubDate>Mon, 10 Feb 2025 18:16:00 +0800</pubDate><description>&lt;p&gt;首先看一下小猫咪在哪个端口运行，一般原版clash和mihomo party是7890，clash verge rev是7897。&lt;/p&gt;
&lt;p&gt;把logseq的网络代理改为system 127.0.0.1:&amp;lt;小猫咪端口&amp;gt;&lt;/p&gt;
&lt;p&gt;有的人可能到这里就解决问题了。&lt;/p&gt;
&lt;p&gt;如果你解决不了，刚好又是Linux的话，可以继续看一下。&lt;/p&gt;
&lt;p&gt;我在看obsidian的git插件readme时忽然福至心灵，插件作者对Linux用户说&lt;a href="https://github.com/Vinzent03/obsidian-git"&gt;Flatpak is not recommended, because it doesn&amp;rsquo;t have access to all system files.&lt;/a&gt;我突然想到自己的Logseq也是flatpak，于是打开Logseq官网下了一个AppImage当对照组，果然AppImage可以连接插件市场，问题解决了。&lt;/p&gt;</description></item><item><title>docker搭建owncloud和onlyoffice并使用caddy反代，实现在线编辑文档</title><link>https://obsp.de/zh/posts/2025-02-10-docker%E6%90%AD%E5%BB%BAowncloud%E5%92%8Conlyoffice%E5%B9%B6%E4%BD%BF%E7%94%A8caddy%E5%8F%8D%E4%BB%A3%E5%AE%9E%E7%8E%B0%E5%9C%A8%E7%BA%BF%E7%BC%96%E8%BE%91%E6%96%87%E6%A1%A3/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-10-docker%E6%90%AD%E5%BB%BAowncloud%E5%92%8Conlyoffice%E5%B9%B6%E4%BD%BF%E7%94%A8caddy%E5%8F%8D%E4%BB%A3%E5%AE%9E%E7%8E%B0%E5%9C%A8%E7%BA%BF%E7%BC%96%E8%BE%91%E6%96%87%E6%A1%A3/</guid><pubDate>Mon, 10 Feb 2025 15:05:00 +0800</pubDate><description>&lt;p&gt;前置条件：Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz 1 Virtual Core；Debian 12 bookworm；Caddy v2；docker 和 docker compose&lt;/p&gt;
&lt;p&gt;owncloud使用docker compose部署，首先创建一个你自己喜欢的目录，等下我们把这个docker容器中的文件映射到这个目录。命令：&lt;code&gt;mkdir whatever/path/to/owncloud &amp;amp;&amp;amp; cd whatever/path/to/owncloud&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;编辑一下dockerfile：&lt;code&gt;nano docker-compose.yml&lt;/code&gt;，模板来自&lt;a href="https://doc.owncloud.com/server/next/admin_manual/installation/docker/#docker-compose"&gt;官方&lt;/a&gt;：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt; volumes:
 files:
 driver: local
 mysql:
 driver: local
 redis:
 driver: local
 
 services:
 owncloud:
 image: owncloud/server:latest
 container_name: owncloud_server
 restart: always
 ports:
 - 8080:8080 #左边改成你自己喜欢的端口
 depends_on:
 - mariadb
 - redis
 environment:
 - OWNCLOUD_DOMAIN=owncloud.mydomain.org
 - OWNCLOUD_TRUSTED_DOMAINS=owncloud.mydomain.org,my_ip_address 
 #注意，如果要添加多个信任域名/ip，用半角逗号分隔，不要加空格，不要加端口号。
 - OWNCLOUD_DB_TYPE=mysql
 - OWNCLOUD_DB_NAME=owncloud
 - OWNCLOUD_DB_USERNAME=owncloud
 - OWNCLOUD_DB_PASSWORD=owncloud
 - OWNCLOUD_DB_HOST=mariadb
 - OWNCLOUD_ADMIN_USERNAME=admin
 - OWNCLOUD_ADMIN_PASSWORD=Th1sPa$$wordIs$uper$ecret!
 - OWNCLOUD_MYSQL_UTF8MB4=true
 - OWNCLOUD_REDIS_ENABLED=true
 - OWNCLOUD_REDIS_HOST=redis
 healthcheck:
 test: [&amp;#34;CMD&amp;#34;, &amp;#34;/usr/bin/healthcheck&amp;#34;]
 interval: 30s
 timeout: 10s
 retries: 5
 volumes:
 - files:/mnt/data
 
 mariadb:
 image: mariadb:10.11 # minimum required ownCloud version is 10.9
 container_name: owncloud_mariadb
 restart: always
 environment:
 - MYSQL_ROOT_PASSWORD=owncloud
 - MYSQL_USER=owncloud
 - MYSQL_PASSWORD=owncloud
 - MYSQL_DATABASE=owncloud
 - MARIADB_AUTO_UPGRADE=1
 command: [&amp;#34;--max-allowed-packet=128M&amp;#34;, &amp;#34;--innodb-log-file-size=64M&amp;#34;]
 healthcheck:
 test: [&amp;#34;CMD&amp;#34;, &amp;#34;mysqladmin&amp;#34;, &amp;#34;ping&amp;#34;, &amp;#34;-u&amp;#34;, &amp;#34;root&amp;#34;, &amp;#34;--password=owncloud&amp;#34;]
 interval: 10s
 timeout: 5s
 retries: 5
 volumes:
 - mysql:/var/lib/mysql
 
 redis:
 image: redis:6
 container_name: owncloud_redis
 restart: always
 command: [&amp;#34;--databases&amp;#34;, &amp;#34;1&amp;#34;]
 healthcheck:
 test: [&amp;#34;CMD&amp;#34;, &amp;#34;redis-cli&amp;#34;, &amp;#34;ping&amp;#34;]
 interval: 10s
 timeout: 5s
 retries: 5
 volumes:
 - redis:/data
 &lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;然后 docker 启动！ &lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;接下来就是把域名owncloud.mydomain.org解析到服务器ip地址，（这个时候编辑caddyfile并重启caddy也可以，不过我们装完onlyoffice再一起改）等下的onlyoffice也需要一个域名，所以解析的时候顺便把office.mydomain.org也解析到服务器的ip地址吧。&lt;/p&gt;
&lt;p&gt;可以使用&lt;code&gt;docker ps&lt;/code&gt;查看owncloud和依赖有没有正常运行，如果不行，看一下&lt;code&gt;docker compose logs -f&lt;/code&gt;，排查原因。如果行，登录你刚刚在docker-compose.yml里面设置的owncloud管理员账户，进行基础设置，然后到owncloud的应用市场market里面下载onlyoffice这个应用。&lt;/p&gt;
&lt;p&gt;owncloud设置大功告成，可以开始设置onlyoffice了：&lt;/p&gt;
&lt;p&gt;我们在你喜欢的端口（比如12345）上面跑一个onlyoffice的docker 容器：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;docker run -i -t -d -p 12345:80 --restart=always onlyoffice/documentserver&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;这个时候运行一下&lt;code&gt;docker ps&lt;/code&gt;，查看刚刚创建的这个容器id，然后运行：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt; docker exec &amp;lt;onlyoffice-container-id&amp;gt; /var/www/onlyoffice/documentserver/npm/json -f /etc/onlyoffice/documentserver/local.json &amp;#39;
 - services.CoAuthoring.secret.session.string&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;可能就有人要问了：这个命令是干嘛的呢？&lt;/p&gt;
&lt;p&gt;运行之后返回的字符串是我们onlyoffice的JWT secret key，等下在owncloud里面设置要用。把这个字符串记下来，下文中我们会用&amp;lt;JWT_secret&amp;gt;指代它。&lt;/p&gt;
&lt;p&gt;好了，现在我们来设置Caddy吧。之所以在这一步设置Caddy，是因为onlyoffice（以及collabora online）要求服务端和客户端使用同样的协议，比如同为https或者同为http。我们使用Caddy进行反代，Caddy会自动给域名申请证书，这样owncloud客户端和onlyoffice服务端都是https协议的了。&lt;/p&gt;
&lt;p&gt;如果你用的是nginx或者apache，也是一样的，如果需要https，请给两个域名手动申请证书，这里就不详细展开了。&lt;/p&gt;
&lt;p&gt;我们编辑一下caddyfile吧：&lt;code&gt;nano /etc/caddy/Caddyfile&lt;/code&gt;
加上下面这两行：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt; owncloud.mydomain.org {
 	reverse_proxy localhost:8080
 }
 
 office.mydomain.org {
 	reverse_proxy localhost:12345
 }&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;保存并退出，重载一下caddy：&lt;code&gt;systemctl reload caddy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;现在通过owncloud.mydomain.org可以以https访问我们的owncloud实例了。
进入owncloud设置，左菜单栏下滑到底部选择“额外的”，在这里你可以设置onlyoffice服务器。&lt;/p&gt;
&lt;p&gt;我们在ONLYOFFICE Docs地址那里填入office.mydomain.org，在秘钥那里填入&amp;lt;JWT_secret&amp;gt;。&lt;/p&gt;
&lt;p&gt;点击保存，大功告成！&lt;/p&gt;
&lt;p&gt;尝试在owncloud文件中打开一个.docx结尾的文档，你会看到onlyoffice加载并启动。&lt;/p&gt;
&lt;p&gt;好了，结束了！&lt;/p&gt;
&lt;p&gt;吐槽一下：&lt;/p&gt;
&lt;p&gt;这篇文章是在Logseq上面写的。Logseq也太难用了吧，这个块状排版真的不适合习惯普通段落式的人……尤其是代码块难用得蛋疼，一旦把一个块变为代码块就无法回到文本编辑模式了……&lt;/p&gt;
&lt;p&gt;Logseq的多端同步也很糟糕，使用git自动commit让我不得不每分钟看见一次错误信息，而且iOS的git客户端Working Copy也比较贵。目前我的做法是Syncthing 在 Android手机和Linux PC之间同步，38块钱买一个iOS的Syncthing客户端Möbius Sync高级版（当然由于iOS的后台机制，功能是残废的）把iPad也加入同步链。Syncthing没有中心服务器，我又不能保证有一台设备24小时运行且可访问（这个问题在我整了台小主机之后解决了。我要试试用小主机当中心服务器看看是否改善），所以经常在同步后出现文件冲突，非常烦人。&lt;/p&gt;
&lt;p&gt;iOS用户需要注意：Logseq只能打开本地文件夹，而且是在Logseq的iOS应用沙盒中的文件夹，这个问题没办法通过挂载云存储解决。要想和其他设备同步，只能在你的同步软件上面下功夫，让同步软件能够访问Logseq的应用沙盒，Möbius Sync如此，Working Copy应该也是如此（没用过）。&lt;/p&gt;</description></item><item><title>tabby使用插件cloud sync settings利用gitlab gist同步设置遇到的问题</title><link>https://obsp.de/zh/posts/2025-02-09-tabby%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6cloud_sync_settings%E5%88%A9%E7%94%A8gitlab_gist%E5%90%8C%E6%AD%A5%E8%AE%BE%E7%BD%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-02-09-tabby%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6cloud_sync_settings%E5%88%A9%E7%94%A8gitlab_gist%E5%90%8C%E6%AD%A5%E8%AE%BE%E7%BD%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/</guid><pubDate>Sun, 09 Feb 2025 16:49:00 +0800</pubDate><description>&lt;p&gt;我的PC系统是Manjaro Linux，用tabby当ssh工具。&lt;/p&gt;
&lt;p&gt;tabby的官方同步需要自己host一个实例，我不太信得过自己的服务器手艺&lt;del&gt;99.9%downtime&lt;/del&gt;，又想要经常备份，巧的是tabby插件市场有一个叫cloud sync settings的插件，可以通过s3/webdav/gist同步设置，于是我就选了gitlab的snippet（gist同步插件上的提示是github或者gitlab，但是据说gitee也行。没试过）&lt;/p&gt;
&lt;p&gt;本来用得好好的，每20s同步一次，偶尔会报错，但是大多是网络问题，过一会就自己恢复了。&lt;/p&gt;
&lt;p&gt;今天在使用的时候突然持续报错，每次同步都出错，我一看logs：&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="复制"
 data-copied-label="已复制！"
 data-failed-label="复制失败"
 aria-label="复制 text 代码"&gt;复制&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;error: Upload settings file from local| \
Exception: Error: Request failed with status code 400&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;我心想：莫非是access token过期了？&lt;/p&gt;
&lt;p&gt;然而登上gitlab一看，我设置的期限是1年，还有11个月才会过期。&lt;/p&gt;
&lt;p&gt;我试着rotate了一下，换了新的token，报错继续。&lt;/p&gt;
&lt;p&gt;此时我发现test connection的时候是无异常的，报错是在开始尝试上传本地配置的时候出现的。&lt;/p&gt;
&lt;p&gt;我试着把gist id留空，让它创建新的snippet，这下没问题了。&lt;/p&gt;
&lt;p&gt;过了几分钟，我打开gitlab，看到snippet的编辑时间是just now，说明把配置同步到这个snippet没有问题，只有那个旧的snippet是不能用的。&lt;/p&gt;
&lt;p&gt;我把gist id改回旧snippet的，果然还是不行。&lt;/p&gt;
&lt;p&gt;这下只好用这个新的snippet了。这个插件的同步都是单向同步，可以选择是云端到本地还是本地到云端，我直接把最新的设置从本地传上去，继续使用。&lt;/p&gt;
&lt;p&gt;（不过除了刚configure完那一下的方向可以选择，以后的同步都是本地到云端，这个不能改。也就是说假如计算机A、B都连接了同一个云端文件，A做出的改动传到云端，此时你打开计算机B，是不会看到A做出的改动的。B本地的配置文件会直接覆写云端文件。）&lt;/p&gt;
&lt;p&gt;虽然找到了问题所在，但是我不知道原因是什么，等我去提个issue看看吧……&lt;/p&gt;
&lt;p&gt;收到的回复：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am not sure yet about the Gitlab issue with snippet, but as I know that Github limited the requests, I think Gitlab is also did the same thing, you can try to switch to other reliable provider like Dropbox, or S3.&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>Greetings from obsp.de!</title><link>https://obsp.de/zh/posts/2025-01-20-greetings_from_obsp.de/</link><guid isPermaLink="true">https://obsp.de/zh/posts/2025-01-20-greetings_from_obsp.de/</guid><pubDate>Mon, 20 Jan 2025 09:10:00 +0800</pubDate><description/></item></channel></rss>