初等記憶體

Recent content on 初等記憶體

About

<h2 id="記憶體自述">記憶體自述</h2> <div style="position: relative; text-align: center; height: auto;"> <iframe src="//music.163.com/outchain/player?id=1903723794&auto=0&amp;height=66&amp;type=2" frameborder="no" > </iframe> </div> <p>直到一切都消失不见,才发现曾经近在眼前。</p> <p>无法阻止,也无法释怀。</p> <p>唯有记录,</p> <p>在我身边。</p> <p style="text-align: right"> <img src="https://axionl.me/img/sign.svg"/> </p> <blockquote> <p>其餘資訊</p> <ul> <li><a class="link" href="https://t.me/loving4ever" target="_blank" rel="noopener" >Telegram: 言文</a></li> <li><a class="link" href="https://github.com/axionl.gpg" target="_blank" rel="noopener" >GPG: 1FDB DCE2 D26B D8F1 00EE 2E73 B1B9 AAD8 BE7E 7326</a></li> <li><a class="link" href="https://amane-live.fars.ee/" target="_blank" rel="noopener" >余忆留声机: 一种相思,两处闲愁</a></li> </ul> </blockquote>

2022/11/23
阅读更多

Photos

2022/11/23
阅读更多

Maddy 自建邮件服务

<img src="https://axionl.me/p/maddy-%E8%87%AA%E5%BB%BA%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1/banner.png" alt="Featured image of post Maddy 自建邮件服务" /><p>从白嫖 Yandex 域名邮箱到 FastMail 付费托管邮箱,一直在找适合自己的域名邮件服务,传统的自建方式模块过于分散,上手难度较大,后在 NickCao 老师推荐下尝试了 Maddy。Maddy 目前只提供了命令行运行的 Linux 服务端,WebUI 或者本地客户端需自行选择,仅需手动配置一下 POP3/IMAP(收) 和 SMTP(发) 服务端地址即可。我目前使用的是 <a class="link" href="https://www.thunderbird.net/" target="_blank" rel="noopener" >Thunderbird</a> (电脑)和 <a class="link" href="https://k9mail.app/" target="_blank" rel="noopener" >K-9 Mail</a>(Android)。</p> <blockquote> <p>下载:<a class="link" href="https://github.com/foxcpp/maddy/releases" target="_blank" rel="noopener" >Github</a> | <a class="link" href="https://maddy.email/builds/" target="_blank" rel="noopener" >上游服务器</a> 文档:<a class="link" href="https://maddy.email" target="_blank" rel="noopener" >maddy.email</a></p> </blockquote> <h2 id="服务器">服务器</h2> <p>部分 VPS 提供商会为了防止广告等原因会禁用 25 号 TCP 端口的 SMTP 端口,但多数情况下(比如 Google Cloud 就不允许)也可以开工单说明邮箱用途和性质,对于个人性质的域名邮箱来说一般不会有太多限制。如果机器本身或者 VPS 提供商还配有防火墙,请打开对应端口(<code>25</code>,<code>465</code>,<code>993</code>)。</p> <p>使用邮件服务需要关闭 CDN 代理,故存在机器 IP 暴露风险,在选择机器时候注意<strong>避开其它敏感数据服务</strong>。</p> <h2 id="配置">配置</h2> <p>可以从 docker 拉取,但这里以 binary + 自己配置方式为主。</p> <h3 id="maddyconf">maddy.conf</h3> <blockquote> <p><a class="link" href="https://github.com/foxcpp/maddy/blob/master/maddy.conf" target="_blank" rel="noopener" >完整版配置</a></p> </blockquote> <p>一般放在 <code>/etc/maddy/maddy.conf</code></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt"> 10 </span><span class="lnt"> 11 </span><span class="lnt"> 12 </span><span class="lnt"> 13 </span><span class="lnt"> 14 </span><span class="lnt"> 15 </span><span class="lnt"> 16 </span><span class="lnt"> 17 </span><span class="lnt"> 18 </span><span class="lnt"> 19 </span><span class="lnt"> 20 </span><span class="lnt"> 21 </span><span class="lnt"> 22 </span><span class="lnt"> 23 </span><span class="lnt"> 24 </span><span class="lnt"> 25 </span><span class="lnt"> 26 </span><span class="lnt"> 27 </span><span class="lnt"> 28 </span><span class="lnt"> 29 </span><span class="lnt"> 30 </span><span class="lnt"> 31 </span><span class="lnt"> 32 </span><span class="lnt"> 33 </span><span class="lnt"> 34 </span><span class="lnt"> 35 </span><span class="lnt"> 36 </span><span class="lnt"> 37 </span><span class="lnt"> 38 </span><span class="lnt"> 39 </span><span class="lnt"> 40 </span><span class="lnt"> 41 </span><span class="lnt"> 42 </span><span class="lnt"> 43 </span><span class="lnt"> 44 </span><span class="lnt"> 45 </span><span class="lnt"> 46 </span><span class="lnt"> 47 </span><span class="lnt"> 48 </span><span class="lnt"> 49 </span><span class="lnt"> 50 </span><span class="lnt"> 51 </span><span class="lnt"> 52 </span><span class="lnt"> 53 </span><span class="lnt"> 54 </span><span class="lnt"> 55 </span><span class="lnt"> 56 </span><span class="lnt"> 57 </span><span class="lnt"> 58 </span><span class="lnt"> 59 </span><span class="lnt"> 60 </span><span class="lnt"> 61 </span><span class="lnt"> 62 </span><span class="lnt"> 63 </span><span class="lnt"> 64 </span><span class="lnt"> 65 </span><span class="lnt"> 66 </span><span class="lnt"> 67 </span><span class="lnt"> 68 </span><span class="lnt"> 69 </span><span class="lnt"> 70 </span><span class="lnt"> 71 </span><span class="lnt"> 72 </span><span class="lnt"> 73 </span><span class="lnt"> 74 </span><span class="lnt"> 75 </span><span class="lnt"> 76 </span><span class="lnt"> 77 </span><span class="lnt"> 78 </span><span class="lnt"> 79 </span><span class="lnt"> 80 </span><span class="lnt"> 81 </span><span class="lnt"> 82 </span><span class="lnt"> 83 </span><span class="lnt"> 84 </span><span class="lnt"> 85 </span><span class="lnt"> 86 </span><span class="lnt"> 87 </span><span class="lnt"> 88 </span><span class="lnt"> 89 </span><span class="lnt"> 90 </span><span class="lnt"> 91 </span><span class="lnt"> 92 </span><span class="lnt"> 93 </span><span class="lnt"> 94 </span><span class="lnt"> 95 </span><span class="lnt"> 96 </span><span class="lnt"> 97 </span><span class="lnt"> 98 </span><span class="lnt"> 99 </span><span class="lnt">100 </span><span class="lnt">101 </span><span class="lnt">102 </span><span class="lnt">103 </span><span class="lnt">104 </span><span class="lnt">105 </span><span class="lnt">106 </span><span class="lnt">107 </span><span class="lnt">108 </span><span class="lnt">109 </span><span class="lnt">110 </span><span class="lnt">111 </span><span class="lnt">112 </span><span class="lnt">113 </span><span class="lnt">114 </span><span class="lnt">115 </span><span class="lnt">116 </span><span class="lnt">117 </span><span class="lnt">118 </span><span class="lnt">119 </span><span class="lnt">120 </span><span class="lnt">121 </span><span class="lnt">122 </span><span class="lnt">123 </span><span class="lnt">124 </span><span class="lnt">125 </span><span class="lnt">126 </span><span class="lnt">127 </span><span class="lnt">128 </span><span class="lnt">129 </span><span class="lnt">130 </span><span class="lnt">131 </span><span class="lnt">132 </span><span class="lnt">133 </span><span class="lnt">134 </span><span class="lnt">135 </span><span class="lnt">136 </span><span class="lnt">137 </span><span class="lnt">138 </span><span class="lnt">139 </span><span class="lnt">140 </span><span class="lnt">141 </span><span class="lnt">142 </span><span class="lnt">143 </span><span class="lnt">144 </span><span class="lnt">145 </span><span class="lnt">146 </span><span class="lnt">147 </span><span class="lnt">148 </span><span class="lnt">149 </span><span class="lnt">150 </span><span class="lnt">151 </span><span class="lnt">152 </span><span class="lnt">153 </span><span class="lnt">154 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="c1"># 预设了三个变量,方便后续使用 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">$(hostname)</span> <span class="p">=</span> <span class="s">mail.example.com</span> </span></span><span class="line"><span class="cl">$<span class="s">(primary_domain)</span> <span class="p">=</span> <span class="s">example.com</span> </span></span><span class="line"><span class="cl">$<span class="s">(local_domains)</span> <span class="p">=</span> $<span class="s">(primary_domain)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># 如果要使用 nginx 反代,这里可以选择 tls off,但如此一来没法生成 dkim 密钥对 </span></span></span><span class="line"><span class="cl"><span class="c1"># 在之后检查时日志内会有安全警告,故推荐直接用 maddy 管理 </span></span></span><span class="line"><span class="cl"><span class="c1"># tls off </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="s">tls</span> <span class="s">file</span> <span class="s">/etc/letsencrypt/live/</span>$<span class="s">(local_domains)/fullchain.pem</span> <span class="s">/etc/letsencrypt/live/</span>$<span class="s">(local_domains)/privkey.pem</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># 数据用 SQLite3 存储较为简单、轻量 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="s">auth.pass_table</span> <span class="s">local_authdb</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">table</span> <span class="s">sql_table</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">driver</span> <span class="s">sqlite3</span> </span></span><span class="line"><span class="cl"> <span class="s">dsn</span> <span class="s">credentials.db</span> </span></span><span class="line"><span class="cl"> <span class="s">table_name</span> <span class="s">passwords</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="s">storage.imapsql</span> <span class="s">local_mailboxes</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">driver</span> <span class="s">sqlite3</span> </span></span><span class="line"><span class="cl"> <span class="s">dsn</span> <span class="s">imapsql.db</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># ---------------------------------------------------------------------------- </span></span></span><span class="line"><span class="cl"><span class="c1"># SMTP endpoints + message routing </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"><span class="s">hostname</span> $<span class="s">(hostname)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="s">table.chain</span> <span class="s">local_rewrites</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">optional_step</span> <span class="s">regexp</span> <span class="s">&#34;(.+)\+(.+)@(.+)&#34;</span> <span class="s">&#34;</span><span class="nv">$1@$3&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s">optional_step</span> <span class="s">static</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">entry</span> <span class="s">postmaster</span> <span class="s">postmaster@</span>$<span class="s">(primary_domain)</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">optional_step</span> <span class="s">file</span> <span class="s">/etc/maddy/aliases</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="s">msgpipeline</span> <span class="s">local_routing</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">destination</span> <span class="s">postmaster</span> $<span class="s">(local_domains)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">modify</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">replace_rcpt</span> <span class="s">&amp;local_rewrites</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">deliver_to</span> <span class="s">&amp;local_mailboxes</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">default_destination</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">reject</span> <span class="mi">550</span> <span class="mi">5</span><span class="s">.1.1</span> <span class="s">&#34;User</span> <span class="s">doesn&#39;t</span> <span class="s">exist&#34;</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># smtp 使用 25 号端口发送邮件 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="s">smtp</span> <span class="s">tcp://[::]:25</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># tls self_signed </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">limits</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Up to 20 msgs/sec across max. 10 SMTP connections. </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">all</span> <span class="s">rate</span> <span class="mi">20</span> <span class="s">1s</span> </span></span><span class="line"><span class="cl"> <span class="s">all</span> <span class="s">concurrency</span> <span class="mi">10</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">dmarc</span> <span class="s">yes</span> </span></span><span class="line"><span class="cl"> <span class="s">check</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">require_mx_record</span> </span></span><span class="line"><span class="cl"> <span class="s">dkim</span> <span class="c1"># 若无则不检查 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="s">spf</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">source</span> $<span class="s">(local_domains)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">reject</span> <span class="mi">501</span> <span class="mi">5</span><span class="s">.1.8</span> <span class="s">&#34;Use</span> <span class="s">Submission</span> <span class="s">for</span> <span class="s">outgoing</span> <span class="s">SMTP&#34;</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">default_source</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">destination</span> <span class="s">postmaster</span> $<span class="s">(local_domains)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">deliver_to</span> <span class="s">&amp;local_routing</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">default_destination</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">reject</span> <span class="mi">550</span> <span class="mi">5</span><span class="s">.1.1</span> <span class="s">&#34;User</span> <span class="s">doesn&#39;t</span> <span class="s">exist&#34;</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># 如果使用 nginx 反代则这里监听到本地端口即可 tcp://127.0.0.1:587 </span></span></span><span class="line"><span class="cl"><span class="c1"># 不使用则邮件客户端以 SSL/TLS 方式直接访问该地址 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="s">submission</span> <span class="s">tls://[::]:465</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">limits</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Up to 50 msgs/sec across any amount of SMTP connections. </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">all</span> <span class="s">rate</span> <span class="mi">50</span> <span class="s">1s</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">auth</span> <span class="s">&amp;local_authdb</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">source</span> $<span class="s">(local_domains)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">check</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">authorize_sender</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">prepare_email</span> <span class="s">&amp;local_rewrites</span> </span></span><span class="line"><span class="cl"> <span class="s">user_to_email</span> <span class="s">identity</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">destination</span> <span class="s">postmaster</span> $<span class="s">(local_domains)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">deliver_to</span> <span class="s">&amp;local_routing</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">default_destination</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">modify</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">dkim</span> $<span class="s">(primary_domain)</span> $<span class="s">(local_domains)</span> <span class="s">default</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">deliver_to</span> <span class="s">&amp;remote_queue</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">default_source</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">reject</span> <span class="mi">501</span> <span class="mi">5</span><span class="s">.1.8</span> <span class="s">&#34;Non-local</span> <span class="s">sender</span> <span class="s">domain&#34;</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="s">target.remote</span> <span class="s">outbound_delivery</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">limits</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Up to 20 msgs/sec across max. 10 SMTP connections </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="c1"># for each recipient domain. </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">destination</span> <span class="s">rate</span> <span class="mi">20</span> <span class="s">1s</span> </span></span><span class="line"><span class="cl"> <span class="s">destination</span> <span class="s">concurrency</span> <span class="mi">10</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">mx_auth</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">dane</span> </span></span><span class="line"><span class="cl"> <span class="s">mtasts</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">cache</span> <span class="s">fs</span> </span></span><span class="line"><span class="cl"> <span class="s">fs_dir</span> <span class="s">mtasts_cache/</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">local_policy</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">min_tls_level</span> <span class="s">encrypted</span> </span></span><span class="line"><span class="cl"> <span class="s">min_mx_level</span> <span class="s">none</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="s">target.queue</span> <span class="s">remote_queue</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">target</span> <span class="s">&amp;outbound_delivery</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="s">autogenerated_msg_domain</span> $<span class="s">(primary_domain)</span> </span></span><span class="line"><span class="cl"> <span class="s">bounce</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">destination</span> <span class="s">postmaster</span> $<span class="s">(local_domains)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">deliver_to</span> <span class="s">&amp;local_routing</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="s">default_destination</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">reject</span> <span class="mi">550</span> <span class="mi">5</span><span class="s">.0.0</span> <span class="s">&#34;Refusing</span> <span class="s">to</span> <span class="s">send</span> <span class="s">DSNs</span> <span class="s">to</span> <span class="s">non-local</span> <span class="s">addresses&#34;</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"> <span class="err">}</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># ---------------------------------------------------------------------------- </span></span></span><span class="line"><span class="cl"><span class="c1"># IMAP endpoints </span></span></span><span class="line"><span class="cl"><span class="c1"># 同上,使用 nginx 反代则改为监听本地端口 tcp://127.0.0.1:143 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="s">imap</span> <span class="s">tls://[::]:993</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">auth</span> <span class="s">&amp;local_authdb</span> </span></span><span class="line"><span class="cl"> <span class="s">storage</span> <span class="s">&amp;local_mailboxes</span> </span></span><span class="line"><span class="cl"><span class="err">}</span> </span></span></code></pre></td></tr></table> </div> </div><h3 id="nginx-config可选">Nginx Config(可选)</h3> <p>如果是由 Maddy 自身直接处理 TLS 则不需要该项配置</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">upstream</span> <span class="s">maddy_imaps</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 指向本地 Maddy IMAP 监听服务端口 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">server</span> <span class="n">127.0.0.1</span><span class="p">:</span><span class="mi">143</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">upstream</span> <span class="s">maddy_smtps</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 指向本地 Maddy SMTP 服务监听端口 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">server</span> <span class="n">127.0.0.1</span><span class="p">:</span><span class="mi">587</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">listen</span> <span class="mi">993</span> <span class="s">ssl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">maddy_imaps</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_certificate</span> <span class="s">/etc/letsencrypt/live/&lt;example.com&gt;/fullchain.pem</span><span class="p">;</span> <span class="c1"># 换成自己的域名证书 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">ssl_certificate_key</span> <span class="s">/etc/letsencrypt/live/&lt;example.com&gt;/privkey.pem</span><span class="p">;</span> <span class="c1"># 换成自己的域名证书 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">ssl_protocols</span> <span class="s">SSLv3</span> <span class="s">TLSv1.1</span> <span class="s">TLSv1.2</span> <span class="s">TLSv1.3</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_ciphers</span> <span class="s">HIGH:!aNULL:!MD5</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_session_timeout</span> <span class="s">4h</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_handshake_timeout</span> <span class="s">30s</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">listen</span> <span class="mi">465</span> <span class="s">ssl</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">maddy_smtps</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_certificate</span> <span class="s">/etc/letsencrypt/live/&lt;example.com&gt;/fullchain.pem</span><span class="p">;</span> <span class="c1"># managed by Certbot </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">ssl_certificate_key</span> <span class="s">/etc/letsencrypt/live/&lt;example.com&gt;/privkey.pem</span><span class="p">;</span> <span class="c1"># managed by Certbot </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kn">ssl_protocols</span> <span class="s">SSLv3</span> <span class="s">TLSv1.1</span> <span class="s">TLSv1.2</span> <span class="s">TLSv1.3</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_ciphers</span> <span class="s">HIGH:!aNULL:!MD5</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_session_timeout</span> <span class="s">4h</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">ssl_handshake_timeout</span> <span class="s">30s</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><h3 id="注册账户">注册账户</h3> <p>maddy 的 systemd service 正常启动后可以使用:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo -u maddy -s <span class="c1"># 切换到 maddy 用户</span> </span></span><span class="line"><span class="cl">$ maddyctl creds create &lt;username@example.com&gt; --password &lt;YourPassword&gt; </span></span><span class="line"><span class="cl">$ maddyctl creds list <span class="c1"># 查看你刚创建的用户名</span> </span></span><span class="line"><span class="cl">$ maddyctl imap-acct create &lt;username@example.com&gt; <span class="c1"># 创建一个邮件储存账户</span> </span></span><span class="line"><span class="cl">$ maddyctl imap-acct list <span class="c1"># 查看刚创建的 imap 储存账户</span> </span></span><span class="line"><span class="cl">$ maddyctl imap-mboxes list &lt;username@example.com&gt; <span class="c1"># 可以看到该账户下有哪些分类</span> </span></span><span class="line"><span class="cl">$ maddyctl imap-msgs list &lt;username@example.com&gt; &lt;INBOX&gt; <span class="c1"># 可以查看当前账户对应分类接收到的邮件,一般收件在 INBOX 中</span> </span></span></code></pre></td></tr></table> </div> </div><h2 id="dns-设置">DNS 设置</h2> <p>这里以 Cloudflare 托管 DNS 服务为例,其它应该也能找到对应 DNS 设置面板。使用了子域名 <code>mail.example.com</code> 作为邮件服务专用。</p> <table> <thead> <tr> <th style="text-align:center">类型</th> <th style="text-align:center">名称</th> <th style="text-align:center">内容</th> <th>代理状态</th> </tr> </thead> <tbody> <tr> <td style="text-align:center">A</td> <td style="text-align:center">mail</td> <td style="text-align:center">服务器实际 IPv4 地址</td> <td>仅限 DNS</td> </tr> <tr> <td style="text-align:center">AAAA</td> <td style="text-align:center">mail</td> <td style="text-align:center">服务器实际 IPv6 地址(如果有)</td> <td>仅限 DNS</td> </tr> <tr> <td style="text-align:center">MX</td> <td style="text-align:center">@</td> <td style="text-align:center">mail.example.com</td> <td>仅限 DNS</td> </tr> <tr> <td style="text-align:center">TXT</td> <td style="text-align:center">mail</td> <td style="text-align:center">v=spf1 mx ~all</td> <td>仅限 DNS</td> </tr> </tbody> </table> <p>spf 值这里推荐使用仅允许 mx,若有其他来源也可以添加。</p> <h2 id="reverse-dns">Reverse DNS</h2> <p>为了避免拒收,进一步提高邮件投递率,需要配置 rDNS 以便收信方邮件服务商溯源。</p> <p>以 Vultr 为例,其 Reverse DNS 页面在 <code>机器详情 &gt; Settings &gt; IPv4 / IPv6</code> 选项卡内,在 Reverse DNS 中填入自己的邮件服务域名即可,如 mail.example.com,如果存在多条公共 IP 地址,则都需要填写。</p> <h2 id="多域名配置-如无可跳过">多域名配置 (如无可跳过)</h2> <ol> <li>在 <code>local_domains</code> 后面加上新的域名</li> </ol> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">$(primary_domain)</span> <span class="p">=</span> <span class="s">example.com</span> </span></span><span class="line"><span class="cl">$<span class="s">(secondary_domain)</span> <span class="p">=</span> <span class="s">example.org</span> </span></span><span class="line"><span class="cl">$<span class="s">(local_domains)</span> <span class="p">=</span> $<span class="s">(primary_domain)</span> $<span class="s">(secondary_domain)</span> </span></span></code></pre></td></tr></table> </div> </div><ol start="2"> <li>在 tls file 后面添加对应的证书,如果用 nginx 反代的话则添加对应的 host 路由和域名。</li> </ol> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">tls</span> <span class="s">file</span> <span class="s">/etc/letsencrypt/live/</span>$<span class="s">(primary_domain)/fullchain.pem</span> <span class="s">/etc/letsencrypt/live/</span>$<span class="s">(primary_domain)/privkey.pem</span> <span class="s">/etc/letsencrypt/live/</span>$<span class="s">(secondary_domain)/fullchain.pem</span> <span class="s">/etc/letsencrypt/live/</span>$<span class="s">(secondary_domain)/privkey.pem</span> </span></span></code></pre></td></tr></table> </div> </div><ol start="3"> <li> <p>将新域名的 dkim 密钥内容也添加到 DNS 的 TXT 记录中</p> </li> <li> <p>dmarc 和 rDNS 同理</p> </li> </ol> <h2 id="客户端">客户端</h2> <p>客户端有诸多选择,不变的是手动设置方式:</p> <ul> <li>IMAP 和 SMTP 服务器地址都设置为 <code>mail.example.com</code>,连接方式均为 <code>SSL/TLS</code>,用户名和密码均为先前所设置,认证方式均为 <code>Normal Password</code>。</li> <li>IMAP 端口为 <code>993</code>,</li> <li>SMTP 端口为 <code>465</code></li> </ul> <h2 id="小结">小结</h2> <p><img src="https://axionl.me/p/maddy-%E8%87%AA%E5%BB%BA%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1/oh_my_yandex.jpg" width="300" height="300" srcset="https://axionl.me/p/maddy-%E8%87%AA%E5%BB%BA%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1/oh_my_yandex_hu01f01a67232b7fa6f27a9ebc9f5bf054_12968_480x0_resize_q75_box.jpg 480w, https://axionl.me/p/maddy-%E8%87%AA%E5%BB%BA%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1/oh_my_yandex_hu01f01a67232b7fa6f27a9ebc9f5bf054_12968_1024x0_resize_q75_box.jpg 1024w" loading="lazy" alt="Oh My Yandex" class="gallery-image" data-flex-grow="100" data-flex-basis="240px" ></p> <p>可以写一些稍微正经的内容,发送到 <a class="link" href="https://www.mail-tester.com" target="_blank" rel="noopener" >https://www.mail-tester.com</a> 内所给的邮件地址,来测试自己的邮箱评分。</p> <p>虽然说自建邮箱可能还是存在邮件投递到垃圾箱里的问题,但有了 Maddy 后搭建一个自己的域名邮箱确实变成了相对简单的工作。</p>

2022/1/10
阅读更多

[OhMyQt 系列] _00__搭建环境

<img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/banner.png" alt="Featured image of post [OhMyQt 系列] _00__搭建环境" /><h2 id="前言">前言</h2> <blockquote> <p>项目地址: <a class="link" href="https://github.com/axionl/OhMyQt" target="_blank" rel="noopener" >axionl/OhMyQt</a></p> </blockquote> <ul> <li>项目在 Windows 11 和 ArchLinux 下进行测试</li> <li>Qt 版本: 5.15.2</li> </ul> <p>本文是 OhMyQt 系列的第 00 章节,主要介绍在 Windows 11 和 ArchLinux 下如何搭建一个可用的 Qt 开发环境。</p> <h2 id="在-archlinux-上搭建">在 ArchLinux 上搭建</h2> <h3 id="基本软件安装">基本软件安装</h3> <p>首先你需要安装 qt5-base,同时推荐安装对应的文档包 qt5-doc</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo pacman -S qt5-base </span></span><span class="line"><span class="cl">$ sudo pacman -S qt5-doc <span class="c1"># 可选</span> </span></span></code></pre></td></tr></table> </div> </div><p>然后安装一个合用的编辑器,这里推荐使用 qtcreator</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo pacman -S qtcreator </span></span></code></pre></td></tr></table> </div> </div><p>因为本教程主要是介绍 QML 的开发,所以还需要安装 qt5-declarative</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo pacman -S qt5-declarative </span></span></code></pre></td></tr></table> </div> </div><p>安装完成之后验证版本信息</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ qmake -v </span></span><span class="line"><span class="cl">QMake version 3.1 </span></span><span class="line"><span class="cl">Using Qt version 5.15.2 in /usr/lib </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">$ pacman -Qs qt5-declarative </span></span><span class="line"><span class="cl">local/qt5-declarative 5.15.2+kde+r29-1 <span class="o">(</span>qt qt5<span class="o">)</span> </span></span><span class="line"><span class="cl"> Classes <span class="k">for</span> QML and JavaScript languages </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">$ qtcreator -version </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Qt Creator 4.15.2 based on Qt 5.15.2 </span></span></code></pre></td></tr></table> </div> </div><h3 id="软件配置">软件配置</h3> <p>首先打开 qtcreator,新建一个 QtQuick 项目</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator1.png" width="1901" height="1039" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator1_hu9cc792a18713befa1403035370c08e65_117515_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator1_hu9cc792a18713befa1403035370c08e65_117515_1024x0_resize_box_3.png 1024w" loading="lazy" alt="新建项目" class="gallery-image" data-flex-grow="182" data-flex-basis="439px" ></p> <p>然后输入名称和路径</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator2.png" width="791" height="519" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator2_hu82ebd35a7a537086a44dfe1df5c51482_34221_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator2_hu82ebd35a7a537086a44dfe1df5c51482_34221_1024x0_resize_box_3.png 1024w" loading="lazy" alt="项目名和路径" class="gallery-image" data-flex-grow="152" data-flex-basis="365px" ></p> <p>下一步编译系统建议选择 cmake</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator3.png" width="801" height="528" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator3_hu83a700fec5c8bc92f5882469d3c9647c_24219_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator3_hu83a700fec5c8bc92f5882469d3c9647c_24219_1024x0_resize_box_3.png 1024w" loading="lazy" alt="编译系统" class="gallery-image" data-flex-grow="151" data-flex-basis="364px" ></p> <p>QT 版本保持默认</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator4.png" width="802" height="527" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator4_hu4e4328150d0e1974479982c49c290434_27008_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator4_hu4e4328150d0e1974479982c49c290434_27008_1024x0_resize_box_3.png 1024w" loading="lazy" alt="QT 版本" class="gallery-image" data-flex-grow="152" data-flex-basis="365px" ></p> <p>因为暂时是教学项目,暂时不添加语言文件</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator5.png" width="797" height="526" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator5_hub4baf059e27fe7b35971168e3ae803de_34446_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator5_hub4baf059e27fe7b35971168e3ae803de_34446_1024x0_resize_box_3.png 1024w" loading="lazy" alt="语言文件" class="gallery-image" data-flex-grow="151" data-flex-basis="363px" ></p> <p>kit 保持默认的 Desktop 就好,因为我们最终也是要做一个桌面应用</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator6.png" width="795" height="527" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator6_hu5d51efb104b39963d45195934fa21366_29308_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator6_hu5d51efb104b39963d45195934fa21366_29308_1024x0_resize_box_3.png 1024w" loading="lazy" alt="Kit" class="gallery-image" data-flex-grow="150" data-flex-basis="362px" ></p> <p>会创建的文件如下所示,你也可以选择加入 git 作为版本控制</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator7.png" width="793" height="524" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator7_hu6a73af58d9accf7d29cff1cb151119d5_41002_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator7_hu6a73af58d9accf7d29cff1cb151119d5_41002_1024x0_resize_box_3.png 1024w" loading="lazy" alt="summary" class="gallery-image" data-flex-grow="151" data-flex-basis="363px" ></p> <p>点击左下角的运行按钮,就可以看到运行的 HelloWorld 窗口了</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator8.png" width="1899" height="1036" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator8_hu17238bd37e7d8ec27abbdccce7f9c34e_89297_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/qtcreator8_hu17238bd37e7d8ec27abbdccce7f9c34e_89297_1024x0_resize_box_3.png 1024w" loading="lazy" alt="HelloWorld" class="gallery-image" data-flex-grow="183" data-flex-basis="439px" ></p> <p>恭喜你,已经搭建好了本教程的开发环境!</p> <h2 id="在-windows-11-上搭建">在 Windows 11 上搭建</h2> <h3 id="通过-installer-安装">通过 Installer 安装</h3> <blockquote> <p><a class="link" href="https://www.qt.io/download" target="_blank" rel="noopener" >https://www.qt.io/download</a></p> </blockquote> <p>注册账号并下载开源版本(需遵守 GPL 许可并限于个人使用)</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/installation_folder.png" width="952" height="632" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/installation_folder_hu65d40ae02004ed0aec9f4b6e8006dc1d_233150_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/installation_folder_hu65d40ae02004ed0aec9f4b6e8006dc1d_233150_1024x0_resize_box_3.png 1024w" loading="lazy" alt="installation folder" class="gallery-image" data-flex-grow="150" data-flex-basis="361px" ></p> <p><strong>此处安装路径至今未支持带空格</strong>,故不要选择常见的 <code>C:\Program Files</code> 等目录</p> <p>手动最小化安装则选择 <code>Custom Installation</code></p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/select_components.png" width="1187" height="632" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/select_components_hue6768a7fe11425ee2810af592f8759dd_307295_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/select_components_hue6768a7fe11425ee2810af592f8759dd_307295_1024x0_resize_box_3.png 1024w" loading="lazy" alt="select components" class="gallery-image" data-flex-grow="187" data-flex-basis="450px" ></p> <p>至少需要选择一个版本的 Qt 支持(如 <code>MinGW 8.1.0 64-bit</code> 包含了 Qt 和其他所需的一些环境),而 <code>QtCreator</code> 作为 IDE 在下方已经被默认勾选了,还有 <code>CMake</code> 如果需要或是已经手动安装则可消去。等待联网装好后即可使用</p> <h3 id="通过第三方包管理器安装">通过第三方包管理器安装</h3> <p><code>chocolately</code> 也提供了 <a class="link" href="https://community.chocolatey.org/packages/qtcreator" target="_blank" rel="noopener" >QtCreator</a> 的安装脚本和基本的 <a class="link" href="https://community.chocolatey.org/packages/qt5-default" target="_blank" rel="noopener" >Qt5 SDK</a> 环境</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="n">choco</span> <span class="n">install</span> <span class="n">-y</span> <span class="nb">qt5-default</span> <span class="n">qtcreato</span> </span></span></code></pre></td></tr></table> </div> </div><p>脚本会自动下载安装,默认路径为 <code>C:\Qt</code></p> <h3 id="配置工具链">配置工具链</h3> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/toolkits.png" width="993" height="714" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/toolkits_hu4d86a381a539d2b3e5022c6c05038585_42673_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/toolkits_hu4d86a381a539d2b3e5022c6c05038585_42673_1024x0_resize_box_3.png 1024w" loading="lazy" alt="toolkits" class="gallery-image" data-flex-grow="139" data-flex-basis="333px" ></p> <p>QtCreator 左边 <code>Projects</code> 有配置工具链的地方,通常来说会自动检测是否已经安装对应工具并设置,如果需要自定义或者不在查找范围内的目录,则可以手动进行指定。</p> <p>若在开发时终端需要运行 Qt 程序,则需要将库所在目录添加到环境变量 <code>PATH</code> 中(如 <code>C:\Qt\5.15.2\mingw81_64\bin</code>)</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/environment.png" width="618" height="585" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/environment_hu3b5b6aaa656ce69f1077215323465e69_28183_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-_00__%E6%90%AD%E5%BB%BA%E7%8E%AF%E5%A2%83/environment_hu3b5b6aaa656ce69f1077215323465e69_28183_1024x0_resize_box_3.png 1024w" loading="lazy" alt="environment" class="gallery-image" data-flex-grow="105" data-flex-basis="253px" ></p> <p>若是打包和分发的话则需要带上对应的动态链接库。</p> <h2 id="小结">小结</h2> <p>Linux 平台上开发相对方便,而 Windows 平台上开发则受到没有合适包管理的限制,需要自己折腾一下。下一节将会正式开始教程中的第一个 Demo,一个用于展示自己内容的 Hello World 程序。</p>

2021/7/23
阅读更多

[OhMyQt 系列] 01_HelloWorld

<img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/banner.png" alt="Featured image of post [OhMyQt 系列] 01_HelloWorld" /><h2 id="前言">前言</h2> <blockquote> <p>项目地址: <a class="link" href="https://github.com/axionl/OhMyQt" target="_blank" rel="noopener" >axionl/OhMyQt</a></p> </blockquote> <ul> <li>项目在 Windows 11 和 ArchLinux 下进行测试</li> <li>Qt 版本: 5.15.2</li> </ul> <p>本文以项目构建的角度介绍一个简单 Qt 应用的开发,其余具体的环境安装和配置稍后会在第 0 节内容中放出。</p> <h2 id="从看见到设计">从看见到设计</h2> <p>我们需要一个简单的展示页面作为上手的第一个项目:</p> <ul> <li>一个醒目的标题</li> <li>一段用于描述的文字</li> <li>还有一个好看的背景</li> </ul> <p>假设我们已经做了出来,它长下面这个样子,你需要做的是把它保留在你的想象中,我们再回过头来考虑如何实现。</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/preview.png" width="1920" height="1080" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/preview_hu85caa472e9bc90ba71d5339c7711fdd3_1488388_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/preview_hu85caa472e9bc90ba71d5339c7711fdd3_1488388_1024x0_resize_box_3.png 1024w" loading="lazy" alt="preview" class="gallery-image" data-flex-grow="177" data-flex-basis="426px" ></p> <h3 id="布局">布局</h3> <p>我们把窗口想象成一个盒子(而不是一个平面或者一张桌子,因为盒子横看有四壁,俯瞰有纵深),而所需要填入的内容当成另一个小些的盒子,套入到窗口这个大盒子中。结合我们多年糊 PPT 的水平,可以对界面有如下设计要求:</p> <ul> <li>内容整体居中</li> <li>标题和描述上下排布</li> <li>内容之间最好有一定的间距</li> </ul> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/layout.png" width="730" height="549" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/layout_hua50c8d7697c036278e5e2f296d277e31_33333_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/layout_hua50c8d7697c036278e5e2f296d277e31_33333_1024x0_resize_box_3.png 1024w" loading="lazy" alt="layout" class="gallery-image" data-flex-grow="132" data-flex-basis="319px" ></p> <p>如上图所示,我们把这一些要求的集合称为 “布局” 也即 Layout,它决定了我们软件设计的基本框架。上面的每一项具体内容称为 “元素” 即 Element / Item。有的人设计 PPT 的时候会说:“应甲方要求,标题一定要大!”,那这属于元素的“属性”,即 Property。于是我们来抽象实现一下这个布局(<strong>注意不是直接能用的代码</strong>):</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="c1">// 基本元素如下 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">Window</span> <span class="p">{</span> <span class="c1">// 窗口一个大盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">Box</span> <span class="p">{</span> <span class="c1">// 里面套个小盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">Title</span> <span class="p">{}</span> <span class="c1">// 盒子里面有标题 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">Description</span> <span class="p">{}</span> <span class="c1">// 标题下面有描述 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>进一步加上居中对齐和间距:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Window</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">Box</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.centerIn:</span> <span class="nx">parent</span> <span class="c1">// 居中对齐大盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="nx">Title</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.horizontalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">horizontalCenter</span> <span class="c1">// 水平对齐小盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Description</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.horizontalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">horizontalCenter</span> <span class="c1">// 水平对齐小盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>可以看到,这里引用了一个 <code>parent</code> 的概念,实际上指代上一层父级元素,套娃套在里面的才是娃,文字和描述的父级元素是小盒子,小盒子的父级元素是大窗口。再补上元素的属性和间距:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Window</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">width:</span> <span class="mi">960</span> <span class="c1">// 窗口宽度 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">height:</span> <span class="mi">720</span> <span class="c1">// 窗口高度 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="nx">Box</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.centerIn:</span> <span class="nx">parent</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitWidth:</span> <span class="mi">360</span> <span class="c1">// 宽度 360 单位(多数情况下理解为像素) </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">implicitHeight:</span> <span class="mi">128</span> <span class="c1">// 高度 128 单位 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="nx">Title</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.horizontalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">horizontalCenter</span> <span class="c1">// 水平对齐小盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">font.pixelSize:</span> <span class="mi">24</span> <span class="c1">// 标题一定要大 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Box</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">visible:</span> <span class="kc">false</span> <span class="c1">// 一个看不见的盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">implicitHeight:</span> <span class="mi">16</span> <span class="c1">// 用于拉开标题和描述的间距 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Description</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.horizontalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">horizontalCenter</span> <span class="c1">// 水平对齐小盒子 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">font.pixelSize:</span> <span class="mi">16</span> <span class="c1">// 精致的描述用小字 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>这些具体元素我们以后会写到如何实现,现在需要用 <code>QtQuick.Controls</code> 提供的一些默认元素替代:</p> <ul> <li>Box-&gt;Rectangle</li> <li>Title / Description -&gt; Text</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span><span class="p">.</span><span class="nx">Window</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span><span class="p">.</span><span class="nx">Controls</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nx">Window</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">width:</span> <span class="mi">960</span> </span></span><span class="line"><span class="cl"> <span class="k">height:</span> <span class="mi">720</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.centerIn:</span> <span class="nx">parent</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitWidth:</span> <span class="mi">360</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitHeight:</span> <span class="mi">128</span> </span></span><span class="line"><span class="cl"> <span class="k">color:</span> <span class="s2">&#34;#ef7e9ceb&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Text</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">id: title</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.horizontalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">horizontalCenter</span> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;这是一个大标题&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">font.pixelSize:</span> <span class="mi">48</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">color:</span> <span class="s2">&#34;transparent&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitHeight:</span> <span class="mi">16</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Text</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">id: description</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.horizontalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">horizontalCenter</span> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;我吞下玻璃会伤身体&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">font.pixelSize:</span> <span class="mi">24</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>实现之后的效果可能长成了这个样子,对了,但没全对。因为我们这个 <code>Rectangle</code> 不太智能,是个“硬盒”,元素之间会挤在一起,而不是自动拉开保持社交距离。</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/awesome_qml.png" width="506" height="249" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/awesome_qml_hu9bc5d2d9adaff629e5840cb94ad029ac_12553_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/awesome_qml_hu9bc5d2d9adaff629e5840cb94ad029ac_12553_1024x0_resize_box_3.png 1024w" loading="lazy" alt="awesome_qml" class="gallery-image" data-flex-grow="203" data-flex-basis="487px" ></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span><span class="lnt">35 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span><span class="p">.</span><span class="nx">Window</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span><span class="p">.</span><span class="nx">Controls</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span><span class="p">.</span><span class="nx">Layouts</span> <span class="mf">1.12</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nx">Window</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">width:</span> <span class="mi">960</span> </span></span><span class="line"><span class="cl"> <span class="k">height:</span> <span class="mi">720</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.centerIn:</span> <span class="nx">parent</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitWidth:</span> <span class="mi">360</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitHeight:</span> <span class="mi">128</span> </span></span><span class="line"><span class="cl"> <span class="k">color:</span> <span class="s2">&#34;#ef7e9ceb&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">ColumnLayout</span> <span class="p">{</span> <span class="c1">// 一个上下布局的模板 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">anchors.fill:</span> <span class="nx">parent</span> <span class="c1">// 沾满小盒子的空间 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">spacing:</span> <span class="mi">16</span> <span class="c1">// 原先看不见的盒子用默认提供的间距属性实现 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="nx">Text</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">id: title</span> </span></span><span class="line"><span class="cl"> <span class="k">Layout.alignment:</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">AlignHCenter</span> <span class="o">|</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">AlignVCenter</span> <span class="c1">// 换成 Layout 下的居中对齐模式 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">text:</span> <span class="s2">&#34;这是一个大标题&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">font.pixelSize:</span> <span class="mi">48</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Text</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">id: description</span> </span></span><span class="line"><span class="cl"> <span class="k">Layout.alignment:</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">AlignHCenter</span> <span class="o">|</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">AlignVCenter</span> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;我吞下玻璃会伤身体&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">font.pixelSize:</span> <span class="mi">24</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>可以把 <code>ColumnLayout</code> 当作一个更加智能的盒子,它能够把挤在一起的元素上下依次排开。而与之相对的 <code>RowLayout</code> 则是将元素左右排开。</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/column_layout.png" width="490" height="294" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/column_layout_hu4ea178bc7978c0e5fcfe35c6378179d7_12025_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/column_layout_hu4ea178bc7978c0e5fcfe35c6378179d7_12025_1024x0_resize_box_3.png 1024w" loading="lazy" alt="column_layout" class="gallery-image" data-flex-grow="166" data-flex-basis="400px" ></p> <p>对比原本想像中的布局要求,可以说是基本实现了(<del>打个九折不过分吧</del>)。</p> <h3 id="样式">样式</h3> <p>基本的元素提供了默认的样式和属性,回顾想象图目前还缺少:</p> <ul> <li>按钮</li> <li>背景图</li> </ul> <p>以一个基本的矩形为例,有如下常用属性(<a class="link" href="https://doc.qt.io/qt-5/qml-qtquick-rectangle.html" target="_blank" rel="noopener" >QtQuick-Rectangle</a>)</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span><span class="p">.</span><span class="nx">Controls</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">width:</span> <span class="mi">16</span> </span></span><span class="line"><span class="cl"> <span class="k">height:</span> <span class="mi">16</span> </span></span><span class="line"><span class="cl"> <span class="k">radius:</span> <span class="mi">8</span> <span class="c1">// 圆角半径 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">color:</span> <span class="s2">&#34;white&#34;</span> <span class="c1">// 颜色 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">border.width:</span> <span class="mi">1</span> <span class="c1">// 边框宽度 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">border.color:</span> <span class="s2">&#34;whitesmoke&#34;</span> <span class="c1">// 边框颜色 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>带阴影的矩形可以这样实现:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtQuick</span><span class="p">.</span><span class="nx">Controls</span> <span class="mf">2.12</span> </span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">QtGraphicalEffects</span> <span class="mf">1.0</span> <span class="c1">// 包含 DropShadow 效果 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="k">layer.enabled:</span> <span class="kc">true</span> </span></span><span class="line"><span class="cl"> <span class="k">layer.effect:</span> <span class="nx">DropShadow</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">horizontalOffset:</span> <span class="mi">1</span> <span class="c1">// 横向偏移 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">verticalOffset:</span> <span class="mi">1</span> <span class="c1">// 纵向偏移 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">radius:</span> <span class="mi">16</span> <span class="c1">// 阴影半径 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">samples:</span> <span class="mi">17</span> <span class="c1">// 采样率(越高效果越好,性能消耗也增大) </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">color:</span> <span class="s2">&#34;#10000000&#34;</span> <span class="c1">// ARGB(透明度,红,绿,蓝) </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>为矩形添加渐变色:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="k">rotation:</span> <span class="mi">0</span> <span class="c1">// 渐变角度 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">gradient:</span> <span class="nx">Gradient</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">GradientStop</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">position:</span> <span class="mi">0</span> </span></span><span class="line"><span class="cl"> <span class="k">color:</span> <span class="s2">&#34;#ef7e9ceb&#34;</span> <span class="c1">// 起始颜色 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 可以添加多段 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="nx">GradientStop</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">position:</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="k">color:</span> <span class="s2">&#34;#c5000000&#34;</span> <span class="c1">// 结束颜色 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p><code>qml.qrc</code> 文件中管理所有的静态资源,可以右键在编辑器中打开,然后添加图片资源:</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/qml_qrc.png" width="729" height="367" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/qml_qrc_hue4d97a6005f65bf1e466ac417d253cc9_33146_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/qml_qrc_hue4d97a6005f65bf1e466ac417d253cc9_33146_1024x0_resize_box_3.png 1024w" loading="lazy" alt="qml.qrc" class="gallery-image" data-flex-grow="198" data-flex-basis="476px" ></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">id: background</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="nx">Image</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.fill:</span> <span class="nx">background</span> <span class="c1">// 填充背景矩形 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">source:</span> <span class="s2">&#34;qrc:/background.png&#34;</span> <span class="c1">// 图片资源 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">fillMode:</span> <span class="nx">Image</span><span class="p">.</span><span class="nx">PreserveAspectCrop</span> <span class="c1">// 填充方式 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">z:</span><span class="o">-</span><span class="mi">1</span> <span class="c1">// 由于需要将渐变色作为滤镜效果,所以图片的层级下调 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>如果去掉 z 轴高度设置会发现渐变色在图片下层不可见:</p> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/no_z_index.png" width="770" height="639" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/no_z_index_hue6132802dfb0d56078da81ce75c0bc19_420996_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/no_z_index_hue6132802dfb0d56078da81ce75c0bc19_420996_1024x0_resize_box_3.png 1024w" loading="lazy" alt="no_z_index" class="gallery-image" data-flex-grow="120" data-flex-basis="289px" ></p> <h2 id="事件和交互">事件和交互</h2> <h3 id="事件触发">事件触发</h3> <p>虽然 <code>QtQuick.Controls</code> 中提供了 <code>Button</code> 控件,但是我们仍然可以先为自己创立一个简单的按钮,通过鼠标点击这一事件理解其基本实现。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span><span class="lnt">35 </span><span class="lnt">36 </span><span class="lnt">37 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">id: button</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitHeight:</span> <span class="mi">48</span> </span></span><span class="line"><span class="cl"> <span class="k">implicitWidth:</span> <span class="mi">156</span> </span></span><span class="line"><span class="cl"> <span class="k">radius:</span> <span class="nx">implicitHeight</span> <span class="o">/</span> <span class="mi">2</span> </span></span><span class="line"><span class="cl"> <span class="k">border.color:</span> <span class="s2">&#34;white&#34;</span> <span class="c1">// 边框颜色 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">color:</span> <span class="s2">&#34;#ef7e9ceb&#34;</span> <span class="c1">// 背景颜色 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="nx">Text</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.verticalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">verticalCenter</span> <span class="c1">// 居中对齐 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">anchors.horizontalCenter:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">horizontalCenter</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;初等記憶體&#34;</span> <span class="c1">// 文字内容 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">color:</span> <span class="s2">&#34;white&#34;</span> <span class="c1">// 文字颜色 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 大小和位置覆盖全按钮的鼠标动作区域 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">MouseArea</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">anchors.fill:</span> <span class="nx">parent</span> </span></span><span class="line"><span class="cl"> <span class="k">Layout.alignment:</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">AlignHCenter</span> <span class="o">|</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">AlignVCenter</span> </span></span><span class="line"><span class="cl"> <span class="k">hoverEnabled:</span> <span class="kc">true</span> <span class="c1">// 允许响应鼠标停留 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="k">onEntered:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">parent</span><span class="p">.</span><span class="nx">color</span> <span class="o">=</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">lighter</span><span class="p">(</span><span class="nx">button</span><span class="p">.</span><span class="nx">color</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">);</span> <span class="c1">// 进入颜色变深 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">parent</span><span class="p">.</span><span class="nx">opacity</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span> <span class="c1">// 不透明 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">onExited:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">parent</span><span class="p">.</span><span class="nx">color</span> <span class="o">=</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">lighter</span><span class="p">(</span><span class="nx">button</span><span class="p">.</span><span class="nx">color</span><span class="p">,</span> <span class="mf">0.9</span><span class="p">);</span> <span class="c1">// 退出颜色变浅 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">parent</span><span class="p">.</span><span class="nx">opacity</span> <span class="o">=</span> <span class="mf">0.7</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">onClicked:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">parent</span><span class="p">.</span><span class="nx">color</span> <span class="o">=</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">lighter</span><span class="p">(</span><span class="nx">button</span><span class="p">.</span><span class="nx">color</span><span class="p">,</span> <span class="mf">1.1</span><span class="p">);</span> <span class="c1">// 点击颜色变亮 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p><code>Qt.lighter()</code> 和 <code>Qt.darker()</code>是 <code>color</code> 属性内置的两个方法,用于相对调整颜色的深浅。运行时,鼠标放到按钮上方即可看见按钮颜色变化。</p> <blockquote> <p><a class="link" href="https://doc.qt.io/qt-5/qml-qtqml-qt.html#darker-method" target="_blank" rel="noopener" >darker-method</a> | <a class="link" href="https://doc.qt.io/qt-5/qml-qtqml-qt.html#lighter-method" target="_blank" rel="noopener" >lighter-method</a></p> </blockquote> <p>这里有一个常见用法可以将需要设置的属性用 <code>property</code> 暴露出来,这样便于统一设置和更改,以及将来要写自己组件时便于外部设置,使用 <code>state</code> 可以提供若干个对象默认状态进行切换,这部分后面会见到。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">property</span> <span class="nx">string</span> <span class="k">buttonColor:</span> <span class="s2">&#34;#ef7e9ceb&#34;</span> </span></span><span class="line"><span class="cl"> <span class="nx">property</span> <span class="nx">string</span> <span class="nx">buttonText</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">color:</span> <span class="nx">buttonColor</span> <span class="c1">// 引用属性值 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><h3 id="事件交互响应">事件交互(响应)</h3> <p>那么如何使得按钮能够响应我需要的事件呢,比如说点击按钮后用系统默认浏览器打开一个网址:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span><span class="lnt">9 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">property</span> <span class="nx">string</span> <span class="k">url:</span> <span class="s2">&#34;https://axionl.me&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;ClickMe!&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">onClicked:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">openURL</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span> <span class="c1">// 我们需要实现一个形如这样的方法 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>好在作为一个常见功能,QML 已经将其置为默认对象的方法之一,点击后即可调用默认浏览器打开所设置的网址</p> <blockquote> <p><a class="link" href="https://doc.qt.io/qt-5/qml-qtqml-qt.html#openUrlExternally-method" target="_blank" rel="noopener" >openUrlExternally-method</a></p> </blockquote> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span><span class="lnt">9 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">property</span> <span class="nx">string</span> <span class="k">url:</span> <span class="s2">&#34;https://axionl.me&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;ClickMe!&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">onClicked:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">Qt</span><span class="p">.</span><span class="nx">openUrlExternally</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span> <span class="c1">// 使用默认提供的方法 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><h3 id="自定义类型和方法">自定义类型和方法</h3> <p>在 C++ 中先写一个基于 <code>QObject</code> 父类的类对象,并实现所需的方法。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="c1">// my_button.hpp </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">class</span> <span class="nc">MyButton</span> <span class="o">:</span> <span class="k">public</span> <span class="n">QObject</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">Q_OBJECT</span> </span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span> </span></span><span class="line"><span class="cl"> <span class="k">explicit</span> <span class="n">MyButton</span><span class="p">(</span><span class="n">QObject</span> <span class="o">*</span><span class="n">parent</span> <span class="o">=</span> <span class="k">nullptr</span><span class="p">)</span> <span class="p">{};</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">Q_INVOKABLE</span> <span class="kt">void</span> <span class="nf">openUrl</span><span class="p">(</span><span class="k">const</span> <span class="n">QUrl</span><span class="o">&amp;</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kt">bool</span> <span class="n">err</span> <span class="o">=</span> <span class="n">QDesktopServices</span><span class="o">::</span><span class="n">openUrl</span><span class="p">(</span><span class="n">url</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">qDebug</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Failed to open url&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">};</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span></code></pre></td></tr></table> </div> </div><p>我们用 <code>Q_INVOKABLE</code> 宏声明了一个可以被外部调用(指 QML 中用 javascript 调用)的方法 <code>void openUrl(const QUrl&amp; url)</code>。</p> <blockquote> <p><a class="link" href="https://doc.qt.io/qt-5/qdesktopservices.html" target="_blank" rel="noopener" >QDesktopServices</a> | 该方法是对 <code>QDesktopServices</code> 类中打开链接方法的套用,其在不同平台下其会调用系统浏览器来打开链接。当然也可以自行实现,比如 Linux 平台如果装了 <code>extra/xdg-utils</code> 可以利用 <code>xdg-open</code> 来打开链接。</p> </blockquote> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="c1">// main.cpp </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">import</span> <span class="s">&#34;my_button.hpp&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">QGuiApplication</span> <span class="nf">app</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="c1">// 声明自定义对象 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">qmlRegisterType</span><span class="o">&lt;</span><span class="n">MyButton</span><span class="o">&gt;</span><span class="p">(</span><span class="s">&#34;MyApp&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s">&#34;MyButton&#34;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="n">QQmlApplicationEngine</span> <span class="n">engine</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">app</span><span class="p">.</span><span class="n">exec</span><span class="p">();</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>简单注册如上,<code>qmlRegisterType&lt;类名&gt;(&quot;包名&quot;, 主版本号, 次版本号, &quot;对象名&quot;);</code>,然后在所需的 qml 文件中引入。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">MyApp</span> <span class="mf">1.0</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nx">Item</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// 以对象名为组件名 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">MyButton</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">id: myButton</span> <span class="c1">// 实例化对象 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">property</span> <span class="nx">string</span> <span class="k">url:</span> <span class="s2">&#34;https://axionl.me&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;ClickMe!&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">onClicked:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">myButton</span><span class="p">.</span><span class="nx">openUrl</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span> <span class="c1">// 使用自己实现的方法 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>也可以先在 C++ 中实例化一个对象,再传入对象引用。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="c1">// main.cpp </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">import</span> <span class="s">&#34;my_button.hpp&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">QGuiApplication</span> <span class="nf">app</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 实例化一个对象 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">MyButton</span> <span class="n">my_button</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// ... </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="n">QQmlApplicationEngine</span> <span class="n">engine</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 传入对象引用 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">engine</span><span class="p">.</span><span class="n">rootContext</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">setContextProperty</span><span class="p">(</span><span class="n">QStringLiteral</span><span class="p">(</span><span class="s">&#34;myButtonObject&#34;</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">my_button</span><span class="p">);</span> <span class="c1">// 全局使用时,设置唯一名称 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">app</span><span class="p">.</span><span class="n">exec</span><span class="p">();</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>此时我们不再需要注册声明该对象,也无需在 QML 中引入和实例化,而是直接调用该对象的方法。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span><span class="lnt">9 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-qml" data-lang="qml"><span class="line"><span class="cl"><span class="nx">Rectangle</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">property</span> <span class="nx">string</span> <span class="k">url:</span> <span class="s2">&#34;https://axionl.me&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">text:</span> <span class="s2">&#34;ClickMe!&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">onClicked:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">myButtonObjecton</span><span class="p">.</span><span class="nx">openUrl</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span> <span class="c1">// 直接调用对象方法 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><h2 id="小结">小结</h2> <p><img src="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/last_preview.png" width="940" height="471" srcset="https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/last_preview_hueb96b8e2c06422b565322dc1e99119b2_309234_480x0_resize_box_3.png 480w, https://axionl.me/p/ohmyqt-%E7%B3%BB%E5%88%97-01_helloworld/last_preview_hueb96b8e2c06422b565322dc1e99119b2_309234_1024x0_resize_box_3.png 1024w" loading="lazy" alt="preview" class="gallery-image" data-flex-grow="199" data-flex-basis="478px" ></p> <p>至此已经实现了 Demo 的全部功能:</p> <ul> <li>一个用于展示的界面</li> <li>能用默认浏览器打开链接的按钮</li> </ul> <p>本节基本介绍了 QML 的组织结构和简单事件交互的实现方法,下一节将以一个新的例子来介绍信号量和信号槽这一对重要的概念,以及 C++ 后端代码到 QML 的数据绑定实现。</p> <blockquote> <p>另外还建立了个人讨论群方便大家互相交流: <a class="link" href="https://t.me/Qt_CN" target="_blank" rel="noopener" >https://t.me/Qt_CN</a></p> </blockquote>

2021/7/20
阅读更多

The Wayland Protocol

<img src="https://axionl.me/p/the-wayland-protocol/wayland.png" alt="Featured image of post The Wayland Protocol" /><blockquote> <p>最近还在填坑 &ldquo;The Wayland Protocol&rdquo; 这本书的翻译,由于内容比较独立,所以用 <a class="link" href="https://github.com/rust-lang/mdBook" target="_blank" rel="noopener" >mdbook</a> 单独构建了一份文档页。目前最大的个感受是其设计模式上和 grpc 有异曲同工之妙,等全篇完工后在博客简要介绍一下,希望不咕。</p> </blockquote> <p>文档地址:<a class="link" href="https://wayland.axionl.me" target="_blank" rel="noopener" >wayland.axionl.me</a></p>

2021/4/24
阅读更多

Linux 用户环境变量设置

<img src="https://axionl.me/p/linux-%E7%94%A8%E6%88%B7%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%AE%BE%E7%BD%AE/wallhaven-vmyg6l.jpg" alt="Featured image of post Linux 用户环境变量设置" /><h2 id="序言">序言</h2> <p>Linux 下的用户环境变量配置常显得十分琐碎,如 .xprofile、.pam_environment 亦或是各种 shell 配置文件。</p> <ul> <li>.xprofile 受限于 x11 服务,在 wayland 或未启动图形界面等情况下不会被读取。</li> <li>.pam_environment 由于提权漏洞频出(如:<a class="link" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-4708" target="_blank" rel="noopener" >CVE-2010-4708</a>、<a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2011-3148" target="_blank" rel="noopener" >CVE-2011-3148</a>),<strong>已经被上游遗弃</strong>,一些发行版为了兼容老的用户配置,在 /etc/pam.d/system-login 中临时加入 <code>session required pam_env.so user_readenv=1 </code> 以恢复读取该文件中环境变量的行为,并非长久之计。</li> </ul> <p>参考了依云的 <a class="link" href="https://blog.lilydjwg.me/2020/7/22/linux-environment-variables.215496.html" target="_blank" rel="noopener" >Linux 的环境变量怎么设</a> 一文,遂选用 systemd 的 <a class="link" href="https://www.freedesktop.org/software/systemd/man/environment.d.html" target="_blank" rel="noopener" >environment.d</a> 作为用户环境变量配置方案。</p> <h2 id="介绍">介绍</h2> <p>配置文件目录如下:</p> <ul> <li>~/.config/environment.d/*.conf [✓]</li> <li>/run/environment.d/*.conf</li> <li>/etc/environment.d/*.conf</li> <li>/usr/lib/environment.d/*.conf</li> <li>/etc/environment</li> </ul> <p>其写法如下:</p> <ul> <li>环境变量=值</li> <li>环境变量=${值:-如果为空的默认值}</li> <li>环境变量=${值:+添加值}</li> <li>可以读取 <code>$HOME</code>, <code>$PATH</code> 等原有的环境变量,如:<code>PATH=~/.local/bin:$PATH</code></li> </ul> <p><img src="https://axionl.me/p/linux-%E7%94%A8%E6%88%B7%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%AE%BE%E7%BD%AE/apps.png" width="1100" height="721" srcset="https://axionl.me/p/linux-%E7%94%A8%E6%88%B7%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%AE%BE%E7%BD%AE/apps_hu1b83020ba751949eb81872429f68c4ce_146399_480x0_resize_box_3.png 480w, https://axionl.me/p/linux-%E7%94%A8%E6%88%B7%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%AE%BE%E7%BD%AE/apps_hu1b83020ba751949eb81872429f68c4ce_146399_1024x0_resize_box_3.png 1024w" loading="lazy" alt="apps.conf" class="gallery-image" data-flex-grow="152" data-flex-basis="366px" ></p> <p>由于我是 fish + tmux 用户,需要自己导出一下生成的环境变量文件到命令行。</p> <p><code>~/.config/fish/conf.d/env_init.fish</code></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="k">if</span> <span class="k">not</span> <span class="nb">contains</span> <span class="nv">$PATH</span> <span class="nv">$USER</span> </span></span><span class="line"><span class="cl"> <span class="nf">export</span> <span class="o">(</span>/usr/lib/systemd/user-environment-generators/<span class="m">30</span>-systemd-environment-d-generator<span class="o">)</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/linux-%E7%94%A8%E6%88%B7%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%AE%BE%E7%BD%AE/show_env.png" width="1100" height="721" srcset="https://axionl.me/p/linux-%E7%94%A8%E6%88%B7%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%AE%BE%E7%BD%AE/show_env_hu719fd1b8da5a399c46eca1ab9f3b7901_572168_480x0_resize_box_3.png 480w, https://axionl.me/p/linux-%E7%94%A8%E6%88%B7%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%AE%BE%E7%BD%AE/show_env_hu719fd1b8da5a399c46eca1ab9f3b7901_572168_1024x0_resize_box_3.png 1024w" loading="lazy" alt="show environments" class="gallery-image" data-flex-grow="152" data-flex-basis="366px" ></p> <h2 id="关于-kdeplasma-的补充">关于 KDE/Plasma 的补充</h2> <blockquote> <p><a class="link" href="https://blog.davidedmundson.co.uk/blog/plasma-and-the-systemd-startup/" target="_blank" rel="noopener" >Plasma and the systemd startup</a></p> </blockquote> <p>在 Plasma 5.21 和 Systemd 246 以及后续更新版本中,可以使用 systemd 来启动和管理 KDE 服务。</p> <p>其中一个好处在于:其环境变量可直接继承自 <code>environment.d</code> 而无需再手动设置 <code>systemd-environment-d-generator</code> 生成的环境变量。</p> <p>启用后重启生效。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>kwriteconfig5 <span class="na">--file</span> startkderc <span class="na">--group</span> General <span class="na">--key</span> systemdBoot true </span></span></code></pre></td></tr></table> </div> </div><p>使用 Systemd 进行管理后可利用其 CGroups 限制资源分配和使用等诸多特性(或许可以给你的应用加上 cgproxy?),进一步细化和统一用户配置方案,再次感谢开发者为此做出的努力。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>systemctl <span class="na">--user</span> set-property app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope <span class="nv">MemoryMax</span><span class="o">=</span>1500M <span class="nv">MemoryLimit</span><span class="o">=</span>1G </span></span><span class="line"><span class="cl"><span class="nv">$ </span>systemctl <span class="na">--user</span> <span class="nb">status </span>app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope </span></span><span class="line"><span class="cl"> ● app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope <span class="na">- Telegram</span> Desktop </span></span><span class="line"><span class="cl"> <span class="nf">Loaded</span>: loaded <span class="o">(</span>/usr/share/applications/telegramdesktop.desktop<span class="p">;</span> <span class="nf">transient</span><span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="nf">Transient</span>: yes </span></span><span class="line"><span class="cl"> <span class="nf">Drop-In</span>: /run/user/<span class="m">1000</span>/systemd/transient/app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope.d </span></span><span class="line"><span class="cl"> └─<span class="m">50</span>-MemoryLimit.conf, <span class="m">50</span>-MemoryMax.conf </span></span><span class="line"><span class="cl"> <span class="nf">Active</span>: active <span class="o">(</span><span class="nf">running</span><span class="o">)</span> since Tue <span class="m">2021</span>-<span class="m">02</span>-<span class="m">23</span> <span class="m">21</span>:<span class="m">02</span>:<span class="m">37</span> CST<span class="p">;</span> <span class="nf">5min</span> ago </span></span><span class="line"><span class="cl"> <span class="nf">Tasks</span>: <span class="m">44</span> <span class="o">(</span><span class="nf">limit</span>: <span class="m">18425</span><span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="nf">Memory</span>: <span class="m">351</span>.0M <span class="o">(</span><span class="nf">max</span>: <span class="m">1</span>.4G limit: <span class="m">1</span>.0G<span class="o">)</span> </span></span><span class="line"><span class="cl"> <span class="nf">CPU</span>: <span class="m">20</span>.056s </span></span><span class="line"><span class="cl"> <span class="nf">CGroup</span>: /user.slice/user-<span class="m">1000</span>.slice/user@<span class="m">1000</span>.service/app.slice/app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope </span></span><span class="line"><span class="cl"> └─<span class="m">15363</span> /usr/bin/telegram-desktop <span class="na">-- </span></span></span></code></pre></td></tr></table> </div> </div>

2021/2/23
阅读更多

[归档] 用 Chezmoi 管理配置文件

<img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/wallhaven-j85o5q.jpg" alt="Featured image of post [归档] 用 Chezmoi 管理配置文件" /><h2 id="前言">前言</h2> <blockquote> <p>项目地址:<a class="link" href="https://github.com/twpayne/chezmoi" target="_blank" rel="noopener" >twpayne/chezmoi</a></p> </blockquote> <p>早前看了 Farseerfc 老师这篇译文,开始用 Stow 打理自己家目录中的配置文件,其实现非常简单直接:在一个特定的目录保留原始配置文件,并在其原本对应的位置创建软链接。</p> <blockquote> <p><a class="link" href="https://farseerfc.me/using-gnu-stow-to-manage-your-dotfiles.html" target="_blank" rel="noopener" >【譯】使用 GNU stow 管理你的點文件 </a></p> </blockquote> <p>相比之下,其有如下缺点促使我切换到 Chezmoi:</p> <ul> <li>需要手动创建目录结构,而不是根据现有配置文件来生成</li> <li>需要手动建立版本管理,本身不提供备份和恢复等功能</li> <li>2.3.0 版本加入了隐藏文件显式命名的方案,但还是需要手动添加 <code>dot-</code> 前缀</li> <li>隐私数据加密</li> </ul> <h2 id="安装">安装</h2> <blockquote> <p><a class="link" href="https://www.chezmoi.io/docs/install/" target="_blank" rel="noopener" >https://www.chezmoi.io/docs/install/</a></p> </blockquote> <p>多数包管理器可以搜索到此包进行安装,包括 Linux、BSD、macOS、Windows 在内的多种平台。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>curl <span class="na">-sfL</span> https://git.io/chezmoi <span class="o">|</span> <span class="nf">sh</span> </span></span></code></pre></td></tr></table> </div> </div><h2 id="快速上手">快速上手</h2> <h3 id="初始化">初始化</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi init <span class="na">--apply</span> </span></span></code></pre></td></tr></table> </div> </div><p>这会在 <code>~/.local/share/chezmoi</code> 中创建一个目录权限为 <code>700</code> 的 git 仓库,用于保存配置文件。</p> <ul> <li><code>$ chezmoi source-path</code> 可以列出其路径</li> <li><code>$ chezmoi cd</code> 可以直接进入到该目录</li> <li><code>--apply</code> 参数意为仓库创建后自动添加配置文件,默认为不创建</li> <li><code>$ chezmoi init &lt;git_repo&gt;</code> 可在新设备上初始化上传的配置仓库</li> </ul> <p>若包管理器未安装自动补全,则可由下例所示,添加到 &lt;bash/zsh/fish/powershell&gt; 配置当中。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi completion <span class="nb">fish</span> <span class="o">&gt;</span> ~/.config/<span class="nb">fish</span>/completions/chezmoi.<span class="nb">fish</span> </span></span></code></pre></td></tr></table> </div> </div><ul> <li><code>-o &lt;filename&gt;</code> 输出到文件,默认为标准输出</li> </ul> <h3 id="添加文件或目录">添加文件或目录</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi add .nanorc <span class="c"># 添加文件 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nv">$ </span>chezmoi add <span class="na">-x</span> .config/<span class="nb">fish</span>/<span class="nb">functions</span>/ <span class="c"># 添加文件夹 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nv">$ </span>chezmoi add <span class="na">-xa</span> .config/<span class="nb">fish</span>/<span class="nb">functions</span> <span class="c"># 递归添加文件夹和子目录下的全部内容 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nv">$ </span>chezmoi add <span class="na">-T</span> .xprofile <span class="c"># 添加临时内容 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nv">$ </span>chezmoi add .tmux.conf <span class="na">--follow</span> <span class="c"># 添加软链接对应的原始内容,而不是软链接符号 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nv">$ </span>chezmoi managed <span class="c"># 列出所管理的内容路径 </span></span></span></code></pre></td></tr></table> </div> </div><h3 id="编辑和应用">编辑和应用</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi edit ~/.bashrc <span class="na">--apply</span> </span></span></code></pre></td></tr></table> </div> </div><ul> <li>其默认不带 <code>--apply</code> 参数,所以编辑完成后也不会直接作用于源文件,<code>--dry-run</code> 参数可以空运行而不改变目标文件</li> <li><code>$EDITOR</code> 环境变量决定所使用的编辑器。</li> <li><code>$ chezmoi diff</code> 以查看改动</li> <li><code>$ chezmoi -v apply</code> 应用改动</li> </ul> <h3 id="同步与合并">同步与合并</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi <span class="nb">source </span>pull <span class="na">-- --rebase</span> <span class="o">&amp;&amp;</span> <span class="nf">chezmoi</span> diff </span></span><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi apply <span class="na">--verbose</span> </span></span></code></pre></td></tr></table> </div> </div><p>其同步操作与 git 本身无异,通过 <code>--</code> 可以传递所需参数。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi <span class="nb">source </span>add .nanorc </span></span><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi <span class="nb">source </span>commit <span class="na">-- -m</span> <span class="s2">&#34;Initial commit&#34;</span> </span></span></code></pre></td></tr></table> </div> </div><p>支持自动提交,但考虑到用户可能意外添加<strong>敏感数据</strong>的情况下不建议开启,配置文件的介绍将在稍后提到。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.config/chezmoi/chezmoi.toml</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sourceVCS</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="nx">autoCommit</span> <span class="p">=</span> <span class="kc">true</span> </span></span><span class="line"><span class="cl"> <span class="nx">autoPush</span> <span class="p">=</span> <span class="kc">false</span> </span></span></code></pre></td></tr></table> </div> </div><p>也可以一次打包成压缩包放 U 盘上硬件备份</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi archive <span class="na">--output</span><span class="o">=</span>dotfiles.tar </span></span></code></pre></td></tr></table> </div> </div><h3 id="小结">小结</h3> <p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/doctor.png" width="1048" height="826" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/doctor_huea984c6c4f2853d86b329b2a50c1808a_791757_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/doctor_huea984c6c4f2853d86b329b2a50c1808a_791757_1024x0_resize_box_3.png 1024w" loading="lazy" alt="doctor" class="gallery-image" data-flex-grow="126" data-flex-basis="304px" ></p> <p>其还提供了一个名为 <code>secret</code> 的参数,是各种加密存储管理软件命令行客户端的包装 (cli wrapper),<code>chezmoi doctor</code> 可以检测机器中对应可执行文件的安装状态,此部分内容和应用会在后面的数据模板中提及。</p> <p>至此,快速上手基本功能已介绍完毕,初次使用发现它像是一个功能丰富的 wrapper,提供了诸多自动化的帮助,包括编辑、冲突合并、同步和导出备份等。可以注意到一个与其他以软链接形式的 dotfiles manager 不同,由于在设计之初就考虑到了多设备、跨平台的兼容方案,chezmoi 并没有选择以软链接的形式来替换源文档所在路径,一方面提供了改动与应用之间的缓冲,另一方面规避了多平台的兼容性问题。</p> <h2 id="数据模板">数据模板</h2> <p>以 <a class="link" href="https://gohugo.io/templates/" target="_blank" rel="noopener" >Hugo</a> 作为博客或者使用过 <a class="link" href="https://pkg.go.dev/text/template" target="_blank" rel="noopener" >go template</a> 的朋友一定不会对此感到陌生,得益于 <code>golang</code> 的实现,chezmoi 不仅做到了单文件、跨平台使用,还继承了其强大的数据模板功能,可以对于不同的设备实现不同的数据配置,比如配置不同的 <code>ssh key</code>,这也是与诸多的 dotfiles manager 的不同之处。</p> <h3 id="创建模板">创建模板</h3> <p>以 <code>.gitconfig</code> 为例,原文如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">user</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="nx">axionl</span> </span></span><span class="line"><span class="cl"> <span class="nx">email</span> <span class="p">=</span> <span class="nx">axionl</span><span class="err">@</span><span class="nx">example</span><span class="p">.</span><span class="nx">com</span> </span></span></code></pre></td></tr></table> </div> </div><p>为了在不同的设备电脑上使用不同的 git 账户这一需求,要将其中的用户信息数据与配置文件进行绑定,而原文件将作为 <code>.tmpl</code> 为后缀的模板文件保存。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.local/share/chezmoi/dot_gitconfig.tmpl</span> </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">user</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;{{ .name }}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="nx">email</span> <span class="p">=</span> <span class="s2">&#34;{{ .email }}&#34;</span> </span></span></code></pre></td></tr></table> </div> </div><p>模板文件如上,<code>{{ }}</code> 是 go template 的数据变量标记符,<code>.</code> 代表了当前变量,即在非循环体内,<code>.</code> 就代表了传入的那个变量。一般的变量定义可由赋值表达式定义 <code>{{ $variable := Balabala }}</code>,不过这里 chezmoi 会根据配置文件自动生成和对应。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi add <span class="na">--autotemplate</span> ~/.gitconfig </span></span></code></pre></td></tr></table> </div> </div><p>chezmoi 提供了自动生成模板的功能,但是聪明的生成器未必懂你心意,正如我的 hostname 恰好等于 username 而在其他机器上未必如此。</p> <p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/tmpl.png" width="544" height="143" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/tmpl_hu93eb61e53e7ff68b8c9d717727c8f9e4_23718_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/tmpl_hu93eb61e53e7ff68b8c9d717727c8f9e4_23718_1024x0_resize_box_3.png 1024w" loading="lazy" alt="dot_gitconfig.tmpl" class="gallery-image" data-flex-grow="380" data-flex-basis="913px" ></p> <p>此时重新 <code>$ chezmoi edit ~/.gitconfig</code> 对应编辑的就是模板文件(就不要图快加上 <code>--apply</code> 了,以免产生不必要的手滑)。<code>$ chezmoi data</code> 的内容便是其妄加猜测的根源,对应变量也可以手动添加使用,如下,根据系统类型来判断模板中的内容是否作用于该机器,<code>-</code> 符号用于移除前或者后的空格,更多语法可去 <a class="link" href="https://golang.org/pkg/text/template/" target="_blank" rel="noopener" >go template</a> 参阅。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go-text-template" data-lang="go-text-template"><span class="line"><span class="cl"><span class="x"># ~/.local/share/chezmoi/dot_gitconfig.tmpl </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">eq</span><span class="w"> </span><span class="na">.chezmoi.os</span><span class="w"> </span><span class="s">&#34;linux&#34;</span><span class="w"> </span><span class="cp">-}}</span><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x">[core] </span></span></span><span class="line"><span class="cl"><span class="x"> editor = nvim </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="cp">}}</span><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x">[core] </span></span></span><span class="line"><span class="cl"><span class="x"> editor = </span><span class="cp">{{</span><span class="w"> </span><span class="na">.github.editor</span><span class="w"> </span><span class="cp">}}</span><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span><span class="x"> </span></span></span></code></pre></td></tr></table> </div> </div><p>对于大量需要编辑内容的配置文件,也可根据不同机器直接做多份同名文件,并对应进行切换。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go-text-template" data-lang="go-text-template"><span class="line"><span class="cl"><span class="x"># symlink_dot_bashrc.tmpl </span></span></span><span class="line"><span class="cl"><span class="x">.bashrc_</span><span class="cp">{{</span><span class="w"> </span><span class="na">.chezmoi.os</span><span class="w"> </span><span class="cp">}}</span><span class="x"> </span></span></span></code></pre></td></tr></table> </div> </div><p>这将会创建一个 <code>~/.bashrc</code> 的软链接到对应的配置文件, 若不希望以软链接形式,<code>{{ include &quot;.bashrc_linux&quot; }}</code> 可以直接替换为文件。<code>.chezmoiignore</code> 用于确保对应的系统安装对应的文件,意为如果不匹配,则忽略对应的配置文件,此外还有 <code>.chezmoiremove</code> 等,详见 <a class="link" href="https://www.chezmoi.io/docs/reference/#chezmoiignore" target="_blank" rel="noopener" >reference</a>。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go-text-template" data-lang="go-text-template"><span class="line"><span class="cl"><span class="x"># .chezmoiignore </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">ne</span><span class="w"> </span><span class="na">.chezmoi.os</span><span class="w"> </span><span class="s">&#34;darwin&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x">.bashrc_darwin </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">ne</span><span class="w"> </span><span class="na">.chezmoi.os</span><span class="w"> </span><span class="s">&#34;linux&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x">.bashrc_linux </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="x"> </span></span></span></code></pre></td></tr></table> </div> </div><p>在配置文件内写入对应的数据,TOML 语法可去 <a class="link" href="https://toml.io/" target="_blank" rel="noopener" >toml.io</a> 快速上手,大有(声)裨(安)益(利)。除此之外,chezmoi 还支持由 <a class="link" href="https://github.com/spf13/viper" target="_blank" rel="noopener" >github.com/spf13/viper</a> 的 <code>json</code>, <code>hcl</code>, <code>yaml</code> 等格式,均以 chezmoi 加不同扩展名命名,将会使用第一个被找到的配置文件。</p> <blockquote> <p><a class="link" href="https://www.chezmoi.io/docs/reference/#configuration-file" target="_blank" rel="noopener" >配置文件模板</a></p> </blockquote> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.config/chezmoi/chezmoi.toml</span> </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">data</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">data</span><span class="p">.</span><span class="nx">github</span><span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="nx">editor</span> <span class="p">=</span> <span class="s2">&#34;nano&#34;</span> </span></span></code></pre></td></tr></table> </div> </div><p>如下命令可临时调试时,脱离模板文件查看变量值是否正确。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi execute-template <span class="s1">&#39;{{- .github.editor -}}&#39;</span> </span></span><span class="line"><span class="cl"><span class="nf">nano</span>⏎ </span></span></code></pre></td></tr></table> </div> </div><h3 id="编辑配置">编辑配置</h3> <p>相比于自动创建模板的南辕北辙,通过智能人工从模板中自动生成 TOML 配置则显得较为靠谱。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go-text-template" data-lang="go-text-template"><span class="line"><span class="cl"><span class="x"># ~/test.tmpl </span></span></span><span class="line"><span class="cl"><span class="x"></span><span class="cp">{{-</span><span class="w"> </span><span class="nx">$email</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">promptString</span><span class="w"> </span><span class="s">&#34;email&#34;</span><span class="w"> </span><span class="cp">-}}</span><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x">[data] </span></span></span><span class="line"><span class="cl"><span class="x"> email = &#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$email</span><span class="w"> </span><span class="cp">}}</span><span class="x">&#34; </span></span></span></code></pre></td></tr></table> </div> </div><p>通过 <code>promptString</code> 这个函数解析等下从命令行中传入的参数,并传入到配置文件中。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi execute-template <span class="na">--init</span> <span class="na">--promptString</span> <span class="nv">email</span><span class="o">=</span>axionl@example.com <span class="o">&lt;</span> ~/test.tmpl </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">[</span>data<span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="nv">email</span> <span class="o">=</span> <span class="s2">&#34;axionl@example.com&#34;</span> </span></span></code></pre></td></tr></table> </div> </div><h3 id="管理私有数据">管理私有数据</h3> <p>chezmoi 通过检测原有目录和文件权限来判断是否属于私有,通常以 <code>private_</code> 为前缀, <code>0644</code> 为权限,<code>~/.local/share/chezmoi</code> 则为 <code>700</code> 权限,并且运行时会检查该文件夹权限是否正确。</p> <p>以下面三种方式为例,介绍 chezmoi 的加密部分:</p> <ul> <li>GPG</li> <li>Gnome Keyring</li> <li>KeepassXC</li> </ul> <p>其余还有 Lastpass, OnePassword 等<a class="link" href="https://www.chezmoi.io/docs/how-to/" target="_blank" rel="noopener" >用法</a>。</p> <h4 id="gpg">GPG</h4> <p>支持对称和非对称密钥加密两种方式。</p> <p>非对称加密方式中,可用如下命令查看接受方名称,所用 shell 支持的话也可以 <code>$ gpg --recipient &lt;Tab&gt;</code> 进行补全。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>gpg <span class="na">--list-public-keys</span> </span></span><span class="line"><span class="cl"><span class="nf">pub</span> ed25519 <span class="m">2019</span>-<span class="m">07</span>-<span class="m">04</span> <span class="o">[</span>SC<span class="o">]</span> <span class="o">[</span>expires: <span class="m">2024</span><span class="o">-</span><span class="m">07</span><span class="o">-</span><span class="m">02</span><span class="o">]</span> </span></span><span class="line"><span class="cl"> <span class="nf">1FDBDCE2D26BD8F100EE2E73B1B9AAD8BE7E7326</span> </span></span><span class="line"><span class="cl"><span class="nf">uid</span> <span class="o">[</span>ultimate<span class="o">]</span> ArielAxionL <span class="o">&lt;</span>i@axionl.me<span class="o">&gt;</span> </span></span></code></pre></td></tr></table> </div> </div><p>以接受方竟是我自己为例,在配置文件 <code>chezmoi.toml</code> 中填入:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.config/chezmoi/chezmoi.toml</span> </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;ArielAxionL&#34;</span> </span></span></code></pre></td></tr></table> </div> </div><p>将文件加密并添加,chezmoi 默认使用了 <code>gpg --armor</code> 参数使得加密文件可读,可以通过配置中,的 <code>command</code> 字段来定义 gpg 的其他参数。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi add <span class="na">--encrypt</span> test.toml </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/gpg-encrypt.png" width="903" height="643" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/gpg-encrypt_huee69e1b318899adb832639385df9f612_157054_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/gpg-encrypt_huee69e1b318899adb832639385df9f612_157054_1024x0_resize_box_3.png 1024w" loading="lazy" alt="加密内容" class="gallery-image" data-flex-grow="140" data-flex-basis="337px" > <img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/gpg-decrypt.png" width="1064" height="643" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/gpg-decrypt_hu7646430154d2e9eeed6a196a2da92269_150043_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/gpg-decrypt_hu7646430154d2e9eeed6a196a2da92269_150043_1024x0_resize_box_3.png 1024w" loading="lazy" alt="解密内容" class="gallery-image" data-flex-grow="165" data-flex-basis="397px" ></p> <p>对称加密:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.config/chezmoi/chezmoi.toml</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="nx">symmetric</span> <span class="p">=</span> <span class="kc">true</span> </span></span></code></pre></td></tr></table> </div> </div><h4 id="gnome-keyring">Gnome Keyring</h4> <blockquote> <p><a class="link" href="https://github.com/zalando/go-keyring" target="_blank" rel="noopener" >github.com/zalando/go-keyring</a></p> </blockquote> <p>Linux 中环境下,keyring 的实现目前还只支持 gnome-keyring,希望以后也能提供 kwallet 版本的接口。macOS 则支持 Keychain 存储帐号密码。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi secret keyring set <span class="na">--service</span><span class="o">=</span>github <span class="na">--user</span><span class="o">=&lt;</span>github-user<span class="o">&gt;</span> </span></span><span class="line"><span class="cl"><span class="nf">Password</span>: <span class="o">&lt;</span>github-token<span class="o">&gt;</span> </span></span></code></pre></td></tr></table> </div> </div><p>将 <code>set</code> 替换成 <code>get</code> 从命令行拿到所存密令,或者使用 <a class="link" href="https://wiki.gnome.org/Apps/Seahorse" target="_blank" rel="noopener" >Seahorse</a> 客户端查看。</p> <p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/seahorse.png" width="829" height="663" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/seahorse_hu3fea8af086834637024aba37480cea75_258598_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/seahorse_hu3fea8af086834637024aba37480cea75_258598_1024x0_resize_box_3.png 1024w" loading="lazy" alt="Seahorse 是一个管理 gnome-keyring 的软件" class="gallery-image" data-flex-grow="125" data-flex-basis="300px" ></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go-text-template" data-lang="go-text-template"><span class="line"><span class="cl"><span class="x"># ~/.local/share/chezmoi/dot_gitconfig.tmpl </span></span></span><span class="line"><span class="cl"><span class="x"> </span></span></span><span class="line"><span class="cl"><span class="x">[github] </span></span></span><span class="line"><span class="cl"><span class="x"> user = &#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.github.user</span><span class="w"> </span><span class="cp">}}</span><span class="x">&#34; </span></span></span><span class="line"><span class="cl"><span class="x"> token = &#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">keyring</span><span class="w"> </span><span class="s">&#34;github&#34;</span><span class="w"> </span><span class="na">.github.user</span><span class="w"> </span><span class="cp">}}</span><span class="x">&#34; </span></span></span></code></pre></td></tr></table> </div> </div><p><code>chezmoi.toml</code> 配置文件中写明用户名,模板中便可自动取用 gnome-keyring 先前所存 token,keyring 一般会在用户登陆时自动解锁 <a class="link" href="https://wiki.archlinux.org/index.php/GNOME/Keyring" target="_blank" rel="noopener" >GNOME/Keyring</a>。</p> <h3 id="keepassxc">KeepassXC</h3> <p>配置文件</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.config/chezmoi/chezmoi.toml</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">keepassxc</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="nx">args</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;--key-file&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/to/your/key&#34;</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="nx">database</span> <span class="p">=</span> <span class="s2">&#34;/path/to/your/kdbx&#34;</span> </span></span></code></pre></td></tr></table> </div> </div><p>默认字段有 <code>Notes</code>, <code>Password</code>, <code>URL</code>, <code>Username</code>, 测试获得密码字段,Entry 可以填入保存密码的 Title,keepassxc-cli 会返回一个匹配的结果。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi execute-template <span class="s1">&#39;{{ (keepassxc &#34;&lt;YourEntry&gt;&#34;).Password }}&#39;</span> </span></span></code></pre></td></tr></table> </div> </div><p>KeepassXC 支持自定义字段,比如你的密钥: <img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/attributes.png" width="970" height="761" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/attributes_hu197f908b303265833a38e5477e51affc_84315_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-%E7%94%A8-chezmoi-%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/attributes_hu197f908b303265833a38e5477e51affc_84315_1024x0_resize_box_3.png 1024w" loading="lazy" alt="KeepassXC Entry Attributes" class="gallery-image" data-flex-grow="127" data-flex-basis="305px" ></p> <p>在模板中用 <code>keepassxcAttribute</code> 获取:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>chezmoi execute-template <span class="s1">&#39;{{ keepassxcAttribute &#34;VPS Keyring&#34; &#34;public-key&#34; }}&#39;</span> </span></span></code></pre></td></tr></table> </div> </div><h2 id="结束">结束</h2> <p>得益于数据模板的引入,chezmoi 作为一款 dotfiles manager,其配置管理功能相较之下更加强大,也有更多新的功能和应用场景可以发掘。</p>

2021/1/29
阅读更多

[归档] Hello, My Firefox

<img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/wallhaven-eo5jko.png" alt="Featured image of post [归档] Hello, My Firefox" /><blockquote> <p>自用配置项,持续更新中&hellip;&hellip;</p> </blockquote> <h2 id="选项">选项</h2> <ul> <li>允许加载用户样式文件(userChrome.css)</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">toolkit.legacyUserProfileCustomizations.stylesheets</span> <span class="o">=</span> <span class="s">true</span> </span></span></code></pre></td></tr></table> </div> </div><ul> <li>调整浏览器标签宽度</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">browser.tabs.tabClipWidth</span> <span class="o">=</span> <span class="s">83</span> </span></span></code></pre></td></tr></table> </div> </div><ul> <li>硬件加速</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">media.ffmpeg.vaapi.enabled</span> <span class="o">=</span> <span class="s">true</span> </span></span><span class="line"><span class="cl"><span class="na">media.ffvpx.enabled</span> <span class="o">=</span> <span class="s">false</span> </span></span></code></pre></td></tr></table> </div> </div><p>配合系统环境变量使用 <code>MOZ_X11_EGL=1</code> 或 <code>MOZ_ENABLE_WAYLAND=1</code>,<code>MOZ_WEBRENDER=1</code></p> <ul> <li>Devtools</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">devtools.editor.tabsize</span> <span class="o">=</span> <span class="s">4</span> </span></span><span class="line"><span class="cl"><span class="na">devtools.debugger.remote-enabled</span> <span class="o">=</span> <span class="s">true</span> </span></span></code></pre></td></tr></table> </div> </div><p>DevTools 中还有 <code>Enable browser chrome and add-on debugging toolboxes</code> 可以打开,调试扩展常用选项</p> <ul> <li>易用模式</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">accessibility.typeaheadfind.enablesound</span> <span class="o">=</span> <span class="s">false</span> </span></span></code></pre></td></tr></table> </div> </div><p>默认开了这个选项,导致 Ctrl+F 搜索不到结果的时候笔记本的蜂鸣器会吼得很大声</p> <h2 id="主题">主题</h2> <p><img src="https://user-images.githubusercontent.com/5405629/45172944-21d91900-b24a-11e8-8bc5-03814121b0de.png" loading="lazy" alt="MaterialFox Preview" ></p> <blockquote> <p><a class="link" href="https://github.com/muckSponge/MaterialFox" target="_blank" rel="noopener" >MaterialFox</a></p> </blockquote> <h2 id="扩展">扩展</h2> <ul> <li> <p><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/tree-style-tab" target="_blank" rel="noopener" >Tree Style Tab</a></p> <p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/preview.png" width="1072" height="730" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/preview_hu9072e42a2e616cef52575a9e53a82241_181317_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/preview_hu9072e42a2e616cef52575a9e53a82241_181317_1024x0_resize_box_3.png 1024w" loading="lazy" alt="TST Preview" class="gallery-image" data-flex-grow="146" data-flex-basis="352px" ></p> <ul> <li> <p>userChrome.css 外部配置 <a class="link" href="https://github.com/axionl/dotfiles/blob/14ARE/private_dot_mozilla/csstheme/TreeStyleTab/treestyletab.css" target="_blank" rel="noopener" >treestyletab.css</a></p> </li> <li> <p>插件内嵌样式配置 <a class="link" href="https://github.com/axionl/dotfiles/blob/14ARE/private_dot_mozilla/csstheme/TreeStyleTab/custom.css" target="_blank" rel="noopener" >custom.css</a></p> </li> <li> <p>调试插件样式的方法</p> <ol> <li><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_add_ons.png" width="396" height="299" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_add_ons_hu9836c956d3836efb369769b8ab969da9_19689_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_add_ons_hu9836c956d3836efb369769b8ab969da9_19689_1024x0_resize_box_3.png 1024w" loading="lazy" alt="Debug Add-ons" class="gallery-image" data-flex-grow="132" data-flex-basis="317px" ></li> <li><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_runtime.png" width="692" height="211" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_runtime_hua8fcf25883582b3b0b7d6969060f8602_20453_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_runtime_hua8fcf25883582b3b0b7d6969060f8602_20453_1024x0_resize_box_3.png 1024w" loading="lazy" alt="Debug Runtime" class="gallery-image" data-flex-grow="327" data-flex-basis="787px" ></li> <li><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_tst_page.png" width="296" height="182" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_tst_page_huf113180348d425f9b61ec67b0e48fd76_12019_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_tst_page_huf113180348d425f9b61ec67b0e48fd76_12019_1024x0_resize_box_3.png 1024w" loading="lazy" alt="Debug TST Page" class="gallery-image" data-flex-grow="162" data-flex-basis="390px" ></li> <li><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_tst.png" width="1392" height="776" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_tst_hu6900f4fa9af3b1268034c8626457e55b_248265_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/debug_tst_hu6900f4fa9af3b1268034c8626457e55b_248265_1024x0_resize_box_3.png 1024w" loading="lazy" alt="Debug TST" class="gallery-image" data-flex-grow="179" data-flex-basis="430px" ></li> </ol> </li> </ul> </li> <li> <p><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/tst-bookmarks-subpanel" target="_blank" rel="noopener" >TST Bookmarks Subpanel</a></p> <p><img src="https://addons.cdn.mozilla.net/user-media/previews/thumbs/220/220713.png" loading="lazy" alt="TST Bookmarks Preview" ></p> </li> <li> <p><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/switchyomega" target="_blank" rel="noopener" >SwitchyOmega</a></p> <p><img src="https://addons.cdn.mozilla.net/user-media/previews/thumbs/183/183855.png" loading="lazy" alt="SwitchyOmega Preview" ></p> </li> <li> <p><a class="link" href="https://addons.mozilla.org/zh-CN/firefox/addon/auto-tab-discard" target="_blank" rel="noopener" >Auto Tab Discard</a></p> </li> <li> <p><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser" target="_blank" rel="noopener" >Keepassxc Browser</a></p> </li> </ul> <p><img src="https://addons.cdn.mozilla.net/user-media/previews/thumbs/234/234592.png" loading="lazy" alt="Keepassxc Browser Preview" ></p> <ul> <li><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/plasma-integration" target="_blank" rel="noopener" >Plasma Integration</a></li> </ul> <p><img src="https://addons.cdn.mozilla.net/user-media/previews/thumbs/202/202551.png" loading="lazy" alt="Plasma Integration Preview" ></p> <ul> <li> <p><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/ext-saladict" target="_blank" rel="noopener" >Saladict</a></p> <p><img src="https://addons.cdn.mozilla.net/user-media/previews/thumbs/203/203286.png" loading="lazy" alt="Saladict Preview" ></p> </li> <li> <p><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey" target="_blank" rel="noopener" >Tampermonkey</a></p> <p><img src="https://addons.cdn.mozilla.net/user-media/previews/thumbs/170/170870.png" loading="lazy" alt="Tampermonkey Preview" ></p> <ul> <li>一个用于 <a class="link" href="https://github.com/axionl/dotfiles/blob/14ARE/private_dot_mozilla/csstheme/Tampermonkey/archwiki.js" target="_blank" rel="noopener" >ArchWiki 目录</a> 侧边显示的脚本</li> </ul> <p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/archwiki.png" width="1756" height="998" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/archwiki_hu84283574b4dae8df688d8adb95f9e671_238647_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-hello-my-firefox/archwiki_hu84283574b4dae8df688d8adb95f9e671_238647_1024x0_resize_box_3.png 1024w" loading="lazy" alt="ArchWiki TOC" class="gallery-image" data-flex-grow="175" data-flex-basis="422px" ></p> </li> <li> <p><a class="link" href="https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher" target="_blank" rel="noopener" >User-Agent Switcher and Manager</a></p> <p><img src="https://addons.cdn.mozilla.net/user-media/previews/thumbs/188/188200.png" loading="lazy" alt="User-Agent Switcher and Manager Preview" ></p> </li> </ul>

2021/1/27
阅读更多

在 QEMU/KVM 虚拟机上安装 NixOS 发行版

<img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/preview.png" alt="Featured image of post 在 QEMU/KVM 虚拟机上安装 NixOS 发行版" /><h2 id="前言">前言</h2> <p>2019 年 1 月的时候知道了这个发行版,当时 <code>@NixOS_zh</code> 群刚建立(<del>后来这群凉了</del>),就开虚拟机玩了一下,时隔多年发现又有不少人对其颇感兴趣,便决定重新写一下安装相关的教程。</p> <p><strong>本文以 Arch Linux 作为宿主机,大体步骤与 Arch Wiki 相近</strong></p> <h2 id="qemukvm-虚拟机配置">QEMU/KVM 虚拟机配置</h2> <blockquote> <p>ArchLinux Wiki: <a class="link" href="https://wiki.archlinux.org/index.php/KVM" target="_blank" rel="noopener" >KVM</a> | <a class="link" href="https://wiki.archlinux.org/index.php/QEMU" target="_blank" rel="noopener" >QEMU</a> | <a class="link" href="https://wiki.archlinux.org/index.php/Libvirt" target="_blank" rel="noopener" >Libvirt</a></p> </blockquote> <h3 id="0-检测硬件是否支持-kvm">0. 检测硬件是否支持 KVM</h3> <p>一般情况下需要进入到 <code>BIOS</code> 对应页面打开虚拟化支持,常见对应设置项如下:</p> <ul> <li>AMD: SVM Support</li> <li>Intel: Intel Virtual Technology</li> </ul> <p>开启虚拟化后在宿主机上用命令行检测(比如我的是 AMD 的处理器):</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nv">LC_ALL</span><span class="o">=</span>C lscpu <span class="p">|</span> grep Virtualization </span></span><span class="line"><span class="cl">Virtualization: AMD-V </span></span></code></pre></td></tr></table> </div> </div><p>内核支持检测,如果使用的是 ArchLinux 提供的官方内核,即 <code>core/linux</code> 则已经包含了对应的 kvm 模块(<code>kvm</code>、<code>kvm_amd</code>或<code>kvm_intel</code>):</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ zgrep CONFIG_KVM /proc/config.gz </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_GUEST</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_MMIO</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_ASYNC_PF</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_VFIO</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_COMPAT</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_XFER_TO_GUEST_WORK</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_INTEL</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_AMD</span><span class="o">=</span>m <span class="c1"># 可以看到有该模块</span> </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_AMD_SEV</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_KVM_MMU_AUDIT</span><span class="o">=</span>y </span></span></code></pre></td></tr></table> </div> </div><p>查看这些内核模块是否已自动加载:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ lsmod <span class="p">|</span>grep kvm </span></span><span class="line"><span class="cl">kvm_amd <span class="m">114688</span> <span class="m">8</span> </span></span><span class="line"><span class="cl">ccp <span class="m">118784</span> <span class="m">1</span> kvm_amd </span></span><span class="line"><span class="cl">kvm <span class="m">933888</span> <span class="m">1</span> kvm_amd </span></span><span class="line"><span class="cl">irqbypass <span class="m">16384</span> <span class="m">1</span> kvm </span></span></code></pre></td></tr></table> </div> </div><p>如果没有自动加载则手动:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo modprobe kvm </span></span><span class="line"><span class="cl">$ sudo modprobe kvm_amd <span class="c1"># 对应你的 CPU 类型</span> </span></span></code></pre></td></tr></table> </div> </div><h3 id="1-准虚拟化使用-virtio">1. 准虚拟化(使用 VIRTIO)</h3> <p>检测 VIRTIO 模块是否可用:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ zgrep VIRTIO /proc/config.gz </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_BLK_MQ_VIRTIO</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_VSOCKETS</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_VSOCKETS_COMMON</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_NET_9P_VIRTIO</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_BLK</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_SCSI_VIRTIO</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_NET</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_CONSOLE</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_HW_RANDOM_VIRTIO</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_DRM_VIRTIO_GPU</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_MENU</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_PCI</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_PCI_LEGACY</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_VDPA</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_PMEM</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_BALLOON</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_MEM</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_INPUT</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_MMIO</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES</span><span class="o">=</span>y </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_DMA_SHARED_BUFFER</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_RPMSG_VIRTIO</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_VIRTIO_FS</span><span class="o">=</span>m </span></span><span class="line"><span class="cl"><span class="nv">CONFIG_CRYPTO_DEV_VIRTIO</span><span class="o">=</span>m </span></span></code></pre></td></tr></table> </div> </div><p>准虚拟化设备列表(主要确保以下几个模块有对应开启,若未开启则手动用 <code>modprobe</code> 命令开启):</p> <ul> <li>网络设备 (virtio-net)</li> <li>硬盘设备 (virtio-blk)</li> <li>控制器设备 (virtio-scsi)</li> </ul> <h3 id="2-安装-qemu">2. 安装 QEMU</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo pacman -S qemu </span></span></code></pre></td></tr></table> </div> </div><blockquote> <p>Arch Wiki: <a class="link" href="https://wiki.archlinux.org/index.php/PCI_passthrough_via_OVMF#Setting_up_IOMMU" target="_blank" rel="noopener" >PCI 直通</a></p> </blockquote> <p>如果需要启用 PCI 直通功能则需要在内核参数中添加 <code>intel_iommu=on</code> 或者 <code>amd_iommu=on</code>,同时可以在其后添加 <code>iommu=pt</code>,以防前者失效,以下命令检测是否开启成功,由于本人所用 <code>AMD Ryzen 5 4600U</code> 支持方面还有些问题,故此不做展示。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo dmesg <span class="p">|</span> grep -i -e DMAR -e IOMMU </span></span></code></pre></td></tr></table> </div> </div><h3 id="3-安装-libvirt">3. 安装 libvirt</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo pacman -S libvirt virt-manager dnsmasq edk2-ovmf </span></span></code></pre></td></tr></table> </div> </div><p>为了避免每次都需要询问 <code>root</code> 密码,建议将自己的用户添加到 <code>libvirt</code> 组:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo usermod -aG libvirt &lt;YourUserName&gt; </span></span></code></pre></td></tr></table> </div> </div><p>编辑服务端配置文件 <code>/etc/libvirt/libvirtd.conf</code>,取消如下几行的注释:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">unix_sock_group = &#34;libvirt&#34; </span></span><span class="line"><span class="cl">unix_sock_ro_perms = &#34;0777&#34; # set to 0770 to deny on-group libvirt users </span></span><span class="line"><span class="cl">unix_sock_rw_perms = &#34;0770&#34; </span></span><span class="line"><span class="cl">auth_unix_ro = &#34;none&#34; </span></span><span class="line"><span class="cl">auth_unix_rw = &#34;none&#34; </span></span></code></pre></td></tr></table> </div> </div><p>同时添加 ipv4 的内核转发参数:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo <span class="nb">echo</span> <span class="s1">&#39;net.ipv4.ip_forward = 1&#39;</span> &gt;&gt; /etc/sysctl.d/00-network.conf </span></span></code></pre></td></tr></table> </div> </div><p>设置开机启动和运行服务。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo systemctl <span class="nb">enable</span> --now libvirtd.service </span></span></code></pre></td></tr></table> </div> </div><h3 id="4-配置-virt-manager">4. 配置 virt-manager</h3> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/add_connection.png" width="799" height="264" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/add_connection_hua6b2f70857beea847a4e403761694cf4_45516_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/add_connection_hua6b2f70857beea847a4e403761694cf4_45516_1024x0_resize_box_3.png 1024w" loading="lazy" alt="添加连接" class="gallery-image" data-flex-grow="302" data-flex-basis="726px" ></p> <p>建议重启以应用之前的设置,此时在 <code>Virtual Machine Manager</code> 的界面应该可以看到一些已经连接上的服务端,如果没有则在菜单栏自行添加,推荐初次连接系统级服务来创建虚拟机。</p> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/new_vm.png" width="547" height="569" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/new_vm_hu8d65781e9df1dff7162eeaf71bdeb11e_38276_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/new_vm_hu8d65781e9df1dff7162eeaf71bdeb11e_38276_1024x0_resize_box_3.png 1024w" loading="lazy" alt="添加虚拟机" class="gallery-image" data-flex-grow="96" data-flex-basis="230px" ></p> <blockquote> <p><a class="link" href="https://nixos.org/download.html#nixos-iso" target="_blank" rel="noopener" >NixOS 镜像下载</a></p> </blockquote> <p>将下载到的镜像文件<strong>所在目录</strong>创建为文件系统池,随后在其中选择镜像文件进行加载。</p> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/add_image_pool.png" width="544" height="469" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/add_image_pool_hu970b1afb910bcaae4894d29acb9106cc_27256_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/add_image_pool_hu970b1afb910bcaae4894d29acb9106cc_27256_1024x0_resize_box_3.png 1024w" loading="lazy" alt="添加镜像池" class="gallery-image" data-flex-grow="115" data-flex-basis="278px" > <img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/select_image.png" width="790" height="569" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/select_image_hu2a8c4f1d1d239972d45bfa5118d53040_41702_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/select_image_hu2a8c4f1d1d239972d45bfa5118d53040_41702_1024x0_resize_box_3.png 1024w" loading="lazy" alt="选择镜像" class="gallery-image" data-flex-grow="138" data-flex-basis="333px" ></p> <p>设置合适的系统资源和网络配置等(初次使用推荐用 <code>NAT</code> 模式较为简单,<code>Bridge</code> 模式之后会提到如何配置)。</p> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/mems_and_cpus.png" width="540" height="569" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/mems_and_cpus_hu3f94115c352a8224f9934a0cf4916735_29442_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/mems_and_cpus_hu3f94115c352a8224f9934a0cf4916735_29442_1024x0_resize_box_3.png 1024w" loading="lazy" alt="虚拟机能用的内存和CPU核心数量设置" class="gallery-image" data-flex-grow="94" data-flex-basis="227px" > <img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/storage.png" width="540" height="569" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/storage_hu2d0361517b02270d862594eb87061b93_34400_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/storage_hu2d0361517b02270d862594eb87061b93_34400_1024x0_resize_box_3.png 1024w" loading="lazy" alt="存储空间设置" class="gallery-image" data-flex-grow="94" data-flex-basis="227px" ></p> <p>如果你的宿主机支持的话,推荐使用 <code>UEFI</code> 模式启动(由 <code>extra/edk2-ovmf</code> 这个提供,中途安装的话要重启 <code>libvirtd</code> 服务以生效)。</p> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/efi.png" width="1064" height="878" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/efi_hu9f899bb67943b59a1cb22147594f3a07_107289_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/efi_hu9f899bb67943b59a1cb22147594f3a07_107289_1024x0_resize_box_3.png 1024w" loading="lazy" alt="EFI Firmware" class="gallery-image" data-flex-grow="121" data-flex-basis="290px" ></p> <p>调整镜像到启动优先级最高,最后启动工具栏上的 Begin Install 就可以安装了。 <img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_order.png" width="1064" height="878" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_order_hu0104e4f34f9204d4af86754a01e3407e_89865_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_order_hu0104e4f34f9204d4af86754a01e3407e_89865_1024x0_resize_box_3.png 1024w" loading="lazy" alt="更改启动优先级" class="gallery-image" data-flex-grow="121" data-flex-basis="290px" ></p> <h2 id="nixos-系统安装">NixOS 系统安装</h2> <blockquote> <p><a class="link" href="https://nixos.org/manual/nixos/stable/" target="_blank" rel="noopener" >NixOS 使用手册</a></p> </blockquote> <h3 id="0-进入引导界面">0. 进入引导界面</h3> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/iso_grub.png" width="768" height="525" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/iso_grub_hu9ae0ec0bcf0062785606d81bd30497db_8765_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/iso_grub_hu9ae0ec0bcf0062785606d81bd30497db_8765_1024x0_resize_box_3.png 1024w" loading="lazy" alt="ISO Grub" class="gallery-image" data-flex-grow="146" data-flex-basis="351px" > <img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/nix_begin.png" width="1064" height="878" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/nix_begin_huafbe3e8e1b72f2541b162b11814cfc21_22482_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/nix_begin_huafbe3e8e1b72f2541b162b11814cfc21_22482_1024x0_resize_box_3.png 1024w" loading="lazy" alt="NixOS 镜像启动" class="gallery-image" data-flex-grow="121" data-flex-basis="290px" ></p> <p>由于我下载的是最小化镜像,所以并没有图形界面,如果下载的是带 <code>Gnome</code> 或者 <code>KDE</code> 的镜像的话应该可以看到界面了,稍后我也会以最小化镜像的方式开始安装图形界面。</p> <h3 id="1-磁盘分区">1. 磁盘分区</h3> <p>查看当前块设备状态,可以看到我们之前分配的盘 <code>vda</code> 还未被挂载</p> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/lsblk.png" width="449" height="152" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/lsblk_hu344fddbf0f37cde290abb7988bb0dc38_63573_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/lsblk_hu344fddbf0f37cde290abb7988bb0dc38_63573_1024x0_resize_box_3.png 1024w" loading="lazy" alt="lsblk" class="gallery-image" data-flex-grow="295" data-flex-basis="708px" ></p> <p>建议使用 GPT 分区表,按照可以按照图中对 <code>boot</code>、<code>swap</code>(可选)和 <code>root</code> 分区进行创建,注意下方 <code>Type</code> 选择对应的分区类型,<code>Write</code> 写入后退出。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo cfdisk /dev/vda </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/cfdisk.png" width="1025" height="759" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/cfdisk_hu4fb32da0e060ded920f2416639508d64_12319_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/cfdisk_hu4fb32da0e060ded920f2416639508d64_12319_1024x0_resize_box_3.png 1024w" loading="lazy" alt="cfdisk" class="gallery-image" data-flex-grow="135" data-flex-basis="324px" ></p> <p>格式化分区,可以看到格式化后效果如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo mkfs.fat -F32 /dev/vda1 </span></span><span class="line"><span class="cl">$ sudo mkswap /dev/vda2 </span></span><span class="line"><span class="cl">$ sudo swapon </span></span><span class="line"><span class="cl">$ sudo mkfs.xfs -L root /dev/vda3 </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/lsblk_label.png" width="1058" height="236" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/lsblk_label_hu760cf4292d45bf36294e06e878beb773_15356_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/lsblk_label_hu760cf4292d45bf36294e06e878beb773_15356_1024x0_resize_box_3.png 1024w" loading="lazy" alt="mkfs check" class="gallery-image" data-flex-grow="448" data-flex-basis="1075px" ></p> <h3 id="2-分区挂载">2. 分区挂载</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo mount /dev/vda3 /mnt </span></span><span class="line"><span class="cl">$ sudo mkdir -p /mnt/boot </span></span><span class="line"><span class="cl">$ sudo mount /dev/vda1 /mnt/boot </span></span></code></pre></td></tr></table> </div> </div><p>挂载后可以检查是否挂载成功,不要重复挂载。</p> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/mount.png" width="563" height="236" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/mount_hub30013d1b3472c1e3833db17bbd67cad_4425_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/mount_hub30013d1b3472c1e3833db17bbd67cad_4425_1024x0_resize_box_3.png 1024w" loading="lazy" alt="挂载" class="gallery-image" data-flex-grow="238" data-flex-basis="572px" ></p> <h3 id="3-系统配置">3. 系统配置</h3> <p>由命令生成默认的配置文件:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo nixos-generate-config --root /mnt </span></span><span class="line"><span class="cl">$ sudo nano /mnt/etc/nixos/configuration.nix </span></span></code></pre></td></tr></table> </div> </div><p>可以看到已经有了 <code>systemd-boot</code> 作为 <code>bootloader</code> 引导操作系统。其他一些基本配置,按照自己的需求取消注释并修改内容即可,注意创建用户 <code>users.users.&lt;YourUserName&gt;</code> 及其对应的用户组,完成后 <code>Ctrl + O</code> 保存。</p> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/config.png" width="1082" height="816" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/config_hucd270a1c19e44eafd2ee501c1b303a2d_19570_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/config_hucd270a1c19e44eafd2ee501c1b303a2d_19570_1024x0_resize_box_3.png 1024w" loading="lazy" alt="config" class="gallery-image" data-flex-grow="132" data-flex-basis="318px" ></p> <p>如果网络情况欠佳的话可以设置 <code>http_proxy</code> 或者更换更新频道到国内镜像站:</p> <blockquote> <p><a class="link" href="https://mirrors.tuna.tsinghua.edu.cn/help/nix/" target="_blank" rel="noopener" >TUNA Nix Help</a></p> </blockquote> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo nix-channel --add https://mirrors.tuna.tsinghua.edu.cn/nix-channels/nixos-20.09 nixos </span></span></code></pre></td></tr></table> </div> </div><p>使用 <code>sudo nixos-install</code> 进行安装并设置 <code>root</code> 密码,完成之后取消挂载并重启(记得更改启动项顺序到虚拟硬盘)。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo nixos-install </span></span><span class="line"><span class="cl">... </span></span><span class="line"><span class="cl">setting root password... </span></span><span class="line"><span class="cl">Enter new UNIX password: *** </span></span><span class="line"><span class="cl">Retype new UNIX password: *** </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">$ sudo umount -r /mnt </span></span><span class="line"><span class="cl">$ reboot </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_from_dev.png" width="1135" height="492" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_from_dev_hua4a363df8d1ea1c13471afa92cdc8452_56415_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_from_dev_hua4a363df8d1ea1c13471afa92cdc8452_56415_1024x0_resize_box_3.png 1024w" loading="lazy" alt="调整启动顺序" class="gallery-image" data-flex-grow="230" data-flex-basis="553px" ></p> <h2 id="nixos-系统配置和使用">NixOS 系统配置和使用</h2> <h3 id="0-检查引导状态">0. 检查引导状态</h3> <p>重启登陆后可以查看引导状态:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo bootctl status </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_status.png" width="984" height="822" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_status_hufa72d0cbb1b5710b183f3687d93852e2_23367_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/boot_status_hufa72d0cbb1b5710b183f3687d93852e2_23367_1024x0_resize_box_3.png 1024w" loading="lazy" alt="systemd-boot 状态" class="gallery-image" data-flex-grow="119" data-flex-basis="287px" ></p> <h3 id="1-配置桌面环境">1. 配置桌面环境</h3> <p><code>/etc/nixos/configuration.conf</code> 配置文件参考如下</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt"> 10 </span><span class="lnt"> 11 </span><span class="lnt"> 12 </span><span class="lnt"> 13 </span><span class="lnt"> 14 </span><span class="lnt"> 15 </span><span class="lnt"> 16 </span><span class="lnt"> 17 </span><span class="lnt"> 18 </span><span class="lnt"> 19 </span><span class="lnt"> 20 </span><span class="lnt"> 21 </span><span class="lnt"> 22 </span><span class="lnt"> 23 </span><span class="lnt"> 24 </span><span class="lnt"> 25 </span><span class="lnt"> 26 </span><span class="lnt"> 27 </span><span class="lnt"> 28 </span><span class="lnt"> 29 </span><span class="lnt"> 30 </span><span class="lnt"> 31 </span><span class="lnt"> 32 </span><span class="lnt"> 33 </span><span class="lnt"> 34 </span><span class="lnt"> 35 </span><span class="lnt"> 36 </span><span class="lnt"> 37 </span><span class="lnt"> 38 </span><span class="lnt"> 39 </span><span class="lnt"> 40 </span><span class="lnt"> 41 </span><span class="lnt"> 42 </span><span class="lnt"> 43 </span><span class="lnt"> 44 </span><span class="lnt"> 45 </span><span class="lnt"> 46 </span><span class="lnt"> 47 </span><span class="lnt"> 48 </span><span class="lnt"> 49 </span><span class="lnt"> 50 </span><span class="lnt"> 51 </span><span class="lnt"> 52 </span><span class="lnt"> 53 </span><span class="lnt"> 54 </span><span class="lnt"> 55 </span><span class="lnt"> 56 </span><span class="lnt"> 57 </span><span class="lnt"> 58 </span><span class="lnt"> 59 </span><span class="lnt"> 60 </span><span class="lnt"> 61 </span><span class="lnt"> 62 </span><span class="lnt"> 63 </span><span class="lnt"> 64 </span><span class="lnt"> 65 </span><span class="lnt"> 66 </span><span class="lnt"> 67 </span><span class="lnt"> 68 </span><span class="lnt"> 69 </span><span class="lnt"> 70 </span><span class="lnt"> 71 </span><span class="lnt"> 72 </span><span class="lnt"> 73 </span><span class="lnt"> 74 </span><span class="lnt"> 75 </span><span class="lnt"> 76 </span><span class="lnt"> 77 </span><span class="lnt"> 78 </span><span class="lnt"> 79 </span><span class="lnt"> 80 </span><span class="lnt"> 81 </span><span class="lnt"> 82 </span><span class="lnt"> 83 </span><span class="lnt"> 84 </span><span class="lnt"> 85 </span><span class="lnt"> 86 </span><span class="lnt"> 87 </span><span class="lnt"> 88 </span><span class="lnt"> 89 </span><span class="lnt"> 90 </span><span class="lnt"> 91 </span><span class="lnt"> 92 </span><span class="lnt"> 93 </span><span class="lnt"> 94 </span><span class="lnt"> 95 </span><span class="lnt"> 96 </span><span class="lnt"> 97 </span><span class="lnt"> 98 </span><span class="lnt"> 99 </span><span class="lnt">100 </span><span class="lnt">101 </span><span class="lnt">102 </span><span class="lnt">103 </span><span class="lnt">104 </span><span class="lnt">105 </span><span class="lnt">106 </span><span class="lnt">107 </span><span class="lnt">108 </span><span class="lnt">109 </span><span class="lnt">110 </span><span class="lnt">111 </span><span class="lnt">112 </span><span class="lnt">113 </span><span class="lnt">114 </span><span class="lnt">115 </span><span class="lnt">116 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># Edit this configuration file to define what should be installed on</span> </span></span><span class="line"><span class="cl"><span class="c1"># your system. Help is available in the configuration.nix(5) man page</span> </span></span><span class="line"><span class="cl"><span class="c1"># and in the NixOS manual (accessible by running ‘nixos-help’).</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">imports</span> <span class="o">=</span> </span></span><span class="line"><span class="cl"> <span class="p">[</span> <span class="c1"># Include the results of the hardware scan.</span> </span></span><span class="line"><span class="cl"> <span class="sr">./hardware-configuration.nix</span> </span></span><span class="line"><span class="cl"> <span class="p">];</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Use the systemd-boot EFI boot loader.</span> </span></span><span class="line"><span class="cl"> <span class="n">boot</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">systemd-boot</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">boot</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">efi</span><span class="o">.</span><span class="n">canTouchEfiVariables</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">networking</span><span class="o">.</span><span class="n">hostName</span> <span class="o">=</span> <span class="s2">&#34;axionl&#34;</span><span class="p">;</span> <span class="c1"># 设置 hostname.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Set your time zone.</span> </span></span><span class="line"><span class="cl"> <span class="n">time</span><span class="o">.</span><span class="n">timeZone</span> <span class="o">=</span> <span class="s2">&#34;Asia/Shanghai&#34;</span><span class="p">;</span> <span class="c1"># 设置时区</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># The global useDHCP flag is deprecated, therefore explicitly set to false here.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Per-interface useDHCP will be mandatory in the future, so this generated config</span> </span></span><span class="line"><span class="cl"> <span class="c1"># replicates the default behavior.</span> </span></span><span class="line"><span class="cl"> <span class="n">networking</span><span class="o">.</span><span class="n">useDHCP</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">networking</span><span class="o">.</span><span class="n">interfaces</span><span class="o">.</span><span class="n">enp1s0</span><span class="o">.</span><span class="n">useDHCP</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">networking</span><span class="o">.</span><span class="n">networkmanager</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> <span class="c1"># 启用 NetworkManager 替代默认的 DHCP</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Configure network proxy if necessary</span> </span></span><span class="line"><span class="cl"> <span class="n">networking</span><span class="o">.</span><span class="n">proxy</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;http://192.168.122.1:8888&#34;</span><span class="p">;</span> <span class="c1"># 设置一个外部代理(可选)</span> </span></span><span class="line"><span class="cl"> <span class="c1"># networking.proxy.noProxy = &#34;127.0.0.1,localhost,internal.domain&#34;;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Select internationalisation properties.</span> </span></span><span class="line"><span class="cl"> <span class="n">i18n</span><span class="o">.</span><span class="n">defaultLocale</span> <span class="o">=</span> <span class="s2">&#34;en_US.UTF-8&#34;</span><span class="p">;</span> <span class="c1"># 默认语言环境</span> </span></span><span class="line"><span class="cl"> <span class="c1"># console = {</span> </span></span><span class="line"><span class="cl"> <span class="c1"># font = &#34;Lat2-Terminus16&#34;;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># keyMap = &#34;us&#34;;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># };</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Configure keymap in X11</span> </span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">layout</span> <span class="o">=</span> <span class="s2">&#34;us&#34;</span><span class="p">;</span> <span class="c1"># 设置键盘布局</span> </span></span><span class="line"><span class="cl"> <span class="c1"># services.xserver.xkbOptions = &#34;eurosign:e&#34;;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Enable CUPS to print documents.</span> </span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">printing</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span> <span class="c1"># 启用打印服务(不需要可禁止)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Enable sound.</span> </span></span><span class="line"><span class="cl"> <span class="n">sound</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> <span class="c1"># 允许声音</span> </span></span><span class="line"><span class="cl"> <span class="n">hardware</span><span class="o">.</span><span class="n">pulseaudio</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Enable touchpad support (enabled default in most desktopManager).</span> </span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">libinput</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> <span class="c1"># 允许触摸板</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Define a user account. Don&#39;t forget to set a password with ‘passwd’.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 创建用户并添加到用户组</span> </span></span><span class="line"><span class="cl"> <span class="n">users</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="n">axionl</span> <span class="o">=</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">isNormalUser</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">extraGroups</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;wheel&#34;</span> <span class="s2">&#34;networkmanager&#34;</span> <span class="p">];</span> <span class="c1"># Enable ‘sudo’ for the user.</span> </span></span><span class="line"><span class="cl"> <span class="n">shell</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="p">;</span> <span class="c1"># 指定终端(默认为 bash)</span> </span></span><span class="line"><span class="cl"> <span class="p">};</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># List packages installed in system profile. To search, run:</span> </span></span><span class="line"><span class="cl"> <span class="c1"># $ nix search wget</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 在系统层面安装软件包</span> </span></span><span class="line"><span class="cl"> <span class="n">environment</span><span class="o">.</span><span class="n">systemPackages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="n">htop</span> </span></span><span class="line"><span class="cl"> <span class="n">neofetch</span> </span></span><span class="line"><span class="cl"> <span class="n">fish</span> </span></span><span class="line"><span class="cl"> <span class="n">spice-vdagent</span> </span></span><span class="line"><span class="cl"> <span class="n">virglrenderer</span> </span></span><span class="line"><span class="cl"> <span class="p">];</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Some programs need SUID wrappers, can be configured further or are</span> </span></span><span class="line"><span class="cl"> <span class="c1"># started in user sessions.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># programs.mtr.enable = true;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># programs.gnupg.agent = {</span> </span></span><span class="line"><span class="cl"> <span class="c1"># enable = true;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># enableSSHSupport = true;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># };</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># List services that you want to enable:</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Enable the OpenSSH daemon.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># services.openssh.enable = true;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Open ports in the firewall.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># networking.firewall.allowedTCPPorts = [ ... ];</span> </span></span><span class="line"><span class="cl"> <span class="c1"># networking.firewall.allowedUDPPorts = [ ... ];</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Or disable the firewall altogether.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># networking.firewall.enable = false;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># This value determines the NixOS release from which the default</span> </span></span><span class="line"><span class="cl"> <span class="c1"># settings for stateful data, like file locations and database versions</span> </span></span><span class="line"><span class="cl"> <span class="c1"># on your system were taken. It‘s perfectly fine and recommended to leave</span> </span></span><span class="line"><span class="cl"> <span class="c1"># this value at the release version of the first install of this system.</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Before changing this value read the documentation for this option</span> </span></span><span class="line"><span class="cl"> <span class="c1"># (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).</span> </span></span><span class="line"><span class="cl"> <span class="n">system</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;20.09&#34;</span><span class="p">;</span> <span class="c1"># Did you read the comment?</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># X Windows Server</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 启动 X 显示服务</span> </span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># services.qemuGuest.enable = true;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># services.spice-vdagentd.enable = true;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 允许 SDDM 作为窗口管理器</span> </span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">displayManager</span><span class="o">.</span><span class="n">sddm</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 安装 Plasma KDE 作为桌面环境</span> </span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">desktopManager</span><span class="o">.</span><span class="n">plasma5</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Packages</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 允许第三方闭源软件包</span> </span></span><span class="line"><span class="cl"> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">allowUnfree</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>修改配置文件之后需要使用命令重建并推荐重启生效:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo nixos-rebuild switch --upgrade </span></span></code></pre></td></tr></table> </div> </div><p>日常使用的时候理论上可以多套配置(profile)兼容和切换,当配置过多的时候可用 <code>nix-collect-garbage -d</code> 来完成,详见<a class="link" href="https://nixos.org/guides/nix-pills/garbage-collector.html" target="_blank" rel="noopener" >文档</a>。</p> <p>在用户层面安装软件包使用 <code>nix</code> 包管理器进行搜索和安装:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ nix search &lt;软件包名称&gt; <span class="c1"># 搜索</span> </span></span><span class="line"><span class="cl">$ nix-env -i &lt;软件包名称&gt; <span class="c1"># 安装</span> </span></span><span class="line"><span class="cl">$ nix-env -qa <span class="c1"># 列出可安装的包</span> </span></span><span class="line"><span class="cl">$ nix-env -e &lt;软件包名称&gt; <span class="c1"># 卸载软件包</span> </span></span><span class="line"><span class="cl">$ nix-env --rollback <span class="c1"># 软件包回滚</span> </span></span></code></pre></td></tr></table> </div> </div><blockquote> <p>更多用法可见<a class="link" href="https://nixos.org/manual/nix/stable/" target="_blank" rel="noopener" >官方文档</a></p> </blockquote> <h3 id="2-截图">2. 截图</h3> <p><img src="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/final.png" width="1960" height="1120" srcset="https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/final_hu716cf65f9b220f6ee002cb4b4595423f_2261830_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%9C%A8-qemu/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8A%E5%AE%89%E8%A3%85-nixos-%E5%8F%91%E8%A1%8C%E7%89%88/final_hu716cf65f9b220f6ee002cb4b4595423f_2261830_1024x0_resize_box_3.png 1024w" loading="lazy" alt="预览" class="gallery-image" data-flex-grow="175" data-flex-basis="420px" ></p>

2021/1/7
阅读更多

Protocol Buffers 和 gRPC(一)

<img src="https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/banner.jpg" alt="Featured image of post Protocol Buffers 和 gRPC(一)" /><h2 id="前言">前言</h2> <blockquote> <p>官方网站</p> <ul> <li><a class="link" href="https://grpc.io" target="_blank" rel="noopener" >gRPC</a></li> <li><a class="link" href="https://developers.google.com/protocol-buffers" target="_blank" rel="noopener" >Protocol Buffers</a></li> </ul> </blockquote> <h3 id="通俗易懂的说明">通俗易懂的说明</h3> <p><img src="https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/introduction.png" width="916" height="580" srcset="https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/introduction_hub0c6a23b7eac344035b836ecb5593f14_65920_480x0_resize_box_3.png 480w, https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/introduction_hub0c6a23b7eac344035b836ecb5593f14_65920_1024x0_resize_box_3.png 1024w" loading="lazy" alt="introduction" class="gallery-image" data-flex-grow="157" data-flex-basis="379px" ></p> <p>以宽泛的概念来说就是找到一种公共的中间体,以达到两者“交流”的目的。</p> <h3 id="有点复杂的介绍">有点复杂的介绍</h3> <p><a class="link" href="https://en.wikipedia.org/wiki/Remote_procedure_call" target="_blank" rel="noopener" >RPC(Remote Procedure Call)</a> 远程过程调用,可简单理解为远程的程间通讯。gRPC 是 Google 所实现的一个开源框架,Protocol Buffers 是用于通信的数据载体格式,有着高压缩率(序列化后)、跨平台、多语言等优点。</p> <p>虽然定义上是远程通讯,但扩展到仅本地使用就成了一种形式上的程间通讯。gRPC 通过网络栈实现这一过程,而 Apache Thrift 则包含了网络、命名管道、内存共享等多种数据交换形式,留作以后介绍和对比。通过序列化工具 (Protocol Buffers) 在节省带宽和减少传输时间的基础上,又能够轻易还原回原本的结构数据,便于多语言开发,部分软件也以此作为其 API 暴露方式,如 v2ray 等。</p> <p><img src="https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/flow.png" width="1019" height="467" srcset="https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/flow_hu357817bfc71fa842687faeb034a27405_25579_480x0_resize_box_3.png 480w, https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/flow_hu357817bfc71fa842687faeb034a27405_25579_1024x0_resize_box_3.png 1024w" loading="lazy" alt="流程示意图" class="gallery-image" data-flex-grow="218" data-flex-basis="523px" ></p> <h3 id="一个简单的例子">一个简单的例子</h3> <p>以一个消息内容为空的协议为例子,仅用于确认发送和响应。对于 <code>golang</code> 或者 <code>java</code> 等语言来说 <code>proto3</code> 支持以 <code>option</code> 的方式产生适合其代码引入的包封装 (<a class="link" href="https://developers.google.com/protocol-buffers/docs/tutorials" target="_blank" rel="noopener" >详见</a>)。</p> <h4 id="公共协议">公共协议</h4> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span><span class="lnt">9 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-proto" data-lang="proto"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">go_package</span> <span class="o">=</span> <span class="s">&#34;example.com/user/grpcGoExample&#34;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">HelloMessage</span> <span class="p">{}</span> <span class="c1">// 公共消息体,未包含任何字段 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="cm">/* 通讯服务声明,类似于我们常说的接口 */</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">Greeter</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="k">rpc</span> <span class="n">SayHello</span><span class="p">(</span><span class="n">HelloMessage</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">HelloMessage</span><span class="p">);</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err"> </span></span></span></code></pre></td></tr></table> </div> </div><p>使用 protocol buffers 第一步便是在 <code>proto</code> 文件中定于需要序列化的数据结构:这是一个以 <code>.proto</code> 为扩展名的普通文本文件。数据被构造为消息(<code>message</code>),其中每条消息包含数量不等的键值对(也可以为空),下面简单举例,传输到接收方可以直接从结构体中取出想要的数据,而从结构体到序列化压缩传输,再到还原成结构体这一过程对程序员来说是透明的,由 <code>protobuf</code> 生成的库文件自动完成。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-proto" data-lang="proto"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">Person</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kt">int32</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kt">bool</span> <span class="n">has_ponycopter</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err"> </span></span></span></code></pre></td></tr></table> </div> </div><h4 id="golang-客户端">golang 客户端</h4> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="nx">pb</span> <span class="s">&#34;example.com/user/grpcGoExample&#34;</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">// 具体方法实现 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">g</span> <span class="o">*</span><span class="nx">Greeter</span><span class="p">)</span> <span class="nf">sayHello</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// 生成上下文 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">ctx</span><span class="p">,</span> <span class="nx">cancel</span> <span class="o">:=</span> <span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">defer</span> <span class="nf">cancel</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 获取服务端响应,在执行这条指令后可以看到进入了服务端对应的方法 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">response</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">g</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nf">SayHello</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">pb</span><span class="p">.</span><span class="nx">HelloMessage</span><span class="p">{})</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Failed to get the response: %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">response</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Print</span><span class="p">(</span><span class="s">&#34;Hello Server&#34;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// 本地临时创建一个不安全的链接(socks5) </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kd">const</span> <span class="nx">Address</span> <span class="p">=</span> <span class="s">&#34;localhost:50051&#34;</span> </span></span><span class="line"><span class="cl"> <span class="nx">conn</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">grpc</span><span class="p">.</span><span class="nf">Dial</span><span class="p">(</span><span class="nx">Address</span><span class="p">,</span> <span class="nx">grpc</span><span class="p">.</span><span class="nf">WithInsecure</span><span class="p">(),</span> <span class="nx">grpc</span><span class="p">.</span><span class="nf">WithBlock</span><span class="p">())</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Failed to connect: %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="k">defer</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">client</span> <span class="p">=</span> <span class="nx">pb</span><span class="p">.</span><span class="nf">NewGreeterClient</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="nx">greeter</span> <span class="o">:=</span> <span class="nx">Greeter</span><span class="p">{</span><span class="nx">client</span><span class="p">:</span> <span class="nx">client</span><span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="nx">greeter</span><span class="p">.</span><span class="nf">sayHello</span><span class="p">()</span> <span class="c1">// 调用方法 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><h4 id="golang-服务端">golang 服务端</h4> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span><span class="lnt">35 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="nx">pb</span> <span class="s">&#34;example.com/user/grpcGoExample&#34;</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">server</span> <span class="kd">struct</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">pb</span><span class="p">.</span><span class="nx">UnimplementedGreeterServer</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">// 方法具体实现 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">server</span><span class="p">)</span> <span class="nf">SayHello</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">message</span> <span class="o">*</span><span class="nx">pb</span><span class="p">.</span><span class="nx">HelloMessage</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="nx">pb</span><span class="p">.</span><span class="nx">HelloMessage</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// 请求陷入该方法后输出字符串,表示服务器已经收到该指令 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Hello Client!&#34;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 返回响应 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="o">&amp;</span><span class="nx">pb</span><span class="p">.</span><span class="nx">HelloMessage</span><span class="p">{},</span> <span class="kc">nil</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// 监听一个本地端口 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">lis</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;0.0.0.0:50051&#34;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;failed to listen: %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Listen on: %v...&#34;</span><span class="p">,</span> <span class="nx">Address</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 创建服务端 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">s</span> <span class="o">:=</span> <span class="nx">grpc</span><span class="p">.</span><span class="nf">NewServer</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="nx">pb</span><span class="p">.</span><span class="nf">RegisterGreeterServer</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">server</span><span class="p">{})</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">s</span><span class="p">.</span><span class="nf">Serve</span><span class="p">(</span><span class="nx">lis</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;failed to serve: %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></td></tr></table> </div> </div><p>可以看到,在基本不需要涉及底层网络开发和同步协定的情况下实现了两个程序之间的通讯,其关键在于服务端重写方法的实现和客户端上下文的获取。在后续的介绍中还会就具体操作过程作详细说明。</p> <h2 id="protocol-buffers-简要">Protocol Buffers 简要</h2> <h3 id="跨语言类型的桥梁">跨语言类型的桥梁</h3> <blockquote> <p><a class="link" href="https://developers.google.com/protocol-buffers" target="_blank" rel="noopener" >Protocol Buffers</a></p> </blockquote> <p>作为一种语言中立的协议,其在多种语言中都有对应的变量类型转换,需要特别注意是否在转换过程中出现精度丢失现象,或者并非预期类型的现象,比如 <code>bytes</code> 对应到 <code>C++</code> 的类型为 <code>string</code>,需要自己适当转换。</p> <p><img src="https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/scalar.png" width="862" height="782" srcset="https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/scalar_hua19fb8bf80b659dd6c089a3e0794ccd5_114023_480x0_resize_box_3.png 480w, https://axionl.me/p/protocol-buffers-%E5%92%8C-grpc%E4%B8%80/scalar_hua19fb8bf80b659dd6c089a3e0794ccd5_114023_1024x0_resize_box_3.png 1024w" loading="lazy" alt="变量类型对应表 (https://developers.google.com/protocol-buffers/docs/overview#scalar)" class="gallery-image" data-flex-grow="110" data-flex-basis="264px" ></p> <p>此外支持通过嵌套类型,实现结构体形式的信息的传输和还原。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">PersonalInfo</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kt">int32</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kt">int32</span> <span class="n">score</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kd">enum</span> <span class="n">Hobby</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="n">GAMES</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="n">MUSIC</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="p">}</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="n">Hobby</span> <span class="n">hobby</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">PersonalResponse</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="kt">string</span> <span class="n">msg</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">ClassInfo</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="k">repeated</span> <span class="n">PersonalInfo</span> <span class="n">c_info</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">SearchInfo</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="k">rpc</span> <span class="n">Add</span><span class="p">(</span><span class="n">stream</span> <span class="n">PersonalInfo</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">PersonalResponse</span><span class="p">);</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err"> </span></span></span></code></pre></td></tr></table> </div> </div><p>更多语法说明可以参考<a class="link" href="https://developers.google.com/protocol-buffers/docs/proto3" target="_blank" rel="noopener" >官方文档</a>。</p> <h3 id="生成库文件">生成库文件</h3> <blockquote> <p><a class="link" href="https://github.com/protocolbuffers/protobuf/releases/latest" target="_blank" rel="noopener" >Protobuf Github Release 下载 (含 protoc)</a></p> </blockquote> <p>生成所需要用到两个工具,一个是 <code>protoc</code> 其本身,另一个是用于生成对应其他语言文件的插件,后者可能有些发行版已经将对应语言的插件打包到了 <code>grpc</code> 包内一起,视具体情况而定,比如 <code>protoc-gen-go-grpc</code> 正好没有,那就找到<a class="link" href="https://github.com/grpc/grpc-go" target="_blank" rel="noopener" >上游项目</a>自己安装一个并添加到环境变量中(方便输入)。</p> <ul> <li>命令行方式</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>protoc <span class="na">--plugin</span><span class="o">=</span>protoc-gen-go-grpc <span class="na">--go_out</span><span class="o">=</span>. <span class="na">--go-grpc_out</span><span class="o">=</span>. <span class="na">-I</span><span class="o">=</span>. <span class="nf">info</span>.proto </span></span></code></pre></td></tr></table> </div> </div><p>然后生成了 <code>info.pb.go</code> 和 <code>info_grpc.pb.go</code> 两个文件,大体上看前者管定义,后者管实现,属于同一个包。其他语言主要是将二进制插件和输出参数替换成对应语言的。</p> <ul> <li>cmake 包办</li> </ul> <p>可以参考如下 <code>.cmake</code> 文件,自行在 <code>CMakeLists.txt</code> 引入库文件和生成的头文件。</p> <p><code>protobuf.cmake</code></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span><span class="lnt">35 </span><span class="lnt">36 </span><span class="lnt">37 </span><span class="lnt">38 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="c"># Find Protobuf </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nb">find_package</span><span class="p">(</span><span class="s">Protobuf</span> <span class="s">REQUIRED</span><span class="p">)</span> <span class="c"># 如果没有需要自行安装 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="c"># Find Generator Executable # 找到两个用于生成库文件的可执行文件 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nb">find_program</span><span class="p">(</span><span class="s">PROTOBUF_PROTOC_EXECUTABLE</span> <span class="s">protoc</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="nb">find_program</span><span class="p">(</span><span class="s">GRPC_CC_PLUGIN_EXECUTABLE</span> <span class="s">grpc_cpp_plugin</span><span class="p">)</span> <span class="c"># 对应所需要生成的语言 </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="c"># Set Proto File Name </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">PROTO_NAME_LISTS</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;api&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="c"># Generate gRPC and protobuf Sources </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nb">foreach</span><span class="p">(</span><span class="s">PROTO_NAME</span> <span class="o">${</span><span class="nv">PROTO_NAME_LISTS</span><span class="o">}</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">set</span><span class="p">(</span><span class="s">GRPC_SOURCE</span> <span class="s2">&#34;${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.grpc.pb.cc&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">set</span><span class="p">(</span><span class="s">GRPC_HEADER</span> <span class="s2">&#34;${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.grpc.pb.h&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">set</span><span class="p">(</span><span class="s">PB_SOURCE</span> <span class="s2">&#34;${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.cc&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">set</span><span class="p">(</span><span class="s">PB_HEADER</span> <span class="s2">&#34;${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.h&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">set</span><span class="p">(</span><span class="s">PROTO_DIR</span> <span class="s2">&#34;${CMAKE_SOURCE_DIR}/misc/&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">set</span><span class="p">(</span><span class="s">PROTO_FILE</span> <span class="s2">&#34;${PROTO_DIR}/${PROTO_NAME}.proto&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">add_custom_command</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="s">OUTPUT</span> <span class="s2">&#34;${GRPC_SOURCE}&#34;</span> <span class="s2">&#34;${GRPC_HEADER}&#34;</span> <span class="s2">&#34;${PB_SOURCE}&#34;</span> <span class="s2">&#34;${PB_HEADER}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s">COMMAND</span> <span class="s2">&#34;${PROTOBUF_PROTOC_EXECUTABLE}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s">ARGS</span> </span></span><span class="line"><span class="cl"> <span class="s">--grpc_out=</span><span class="s2">&#34;${CMAKE_CURRENT_BINARY_DIR}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s">--cpp_out=</span><span class="s2">&#34;${CMAKE_CURRENT_BINARY_DIR}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s">-I=</span><span class="s2">&#34;${PROTO_DIR}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s">--plugin=protoc-gen-grpc=</span><span class="s2">&#34;${GRPC_CC_PLUGIN_EXECUTABLE}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;${PROTO_FILE}&#34;</span> </span></span><span class="line"><span class="cl"> <span class="s">DEPENDS</span> <span class="s2">&#34;${PROTO_FILE}&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">GRPC_HEADERS</span> <span class="s2">&#34;${GRPC_HEADER}&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">GRPC_SOURCES</span> <span class="s2">&#34;${GRPC_SOURCE}&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">PROTO_HEADERS</span> <span class="s2">&#34;${PB_HEADER}&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">PROTO_SOURCES</span> <span class="s2">&#34;${PB_SOURCE}&#34;</span><span class="p">)</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="nb">endforeach</span><span class="p">()</span><span class="err"> </span></span></span></code></pre></td></tr></table> </div> </div><h3 id="一个综合的案例">一个综合的案例</h3> <p><a class="link" href="https://github.com/v2fly/v2ray-core/blob/master/config.proto" target="_blank" rel="noopener" >v2ray config.proto</a></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kn">package</span> <span class="nn">v2ray</span><span class="o">.</span><span class="n">core</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">csharp_namespace</span> <span class="o">=</span> <span class="s">&#34;V2Ray.Core&#34;</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">go_package</span> <span class="o">=</span> <span class="s">&#34;v2ray.com/core&#34;</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">java_package</span> <span class="o">=</span> <span class="s">&#34;com.v2ray.core&#34;</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">option</span> <span class="n">java_multiple_files</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">import</span> <span class="s">&#34;common/serial/typed_message.proto&#34;</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">import</span> <span class="s">&#34;transport/config.proto&#34;</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="o">...</span><span class="err"> </span></span></span></code></pre></td></tr></table> </div> </div><p>可以看到首先引入了两个子 <code>proto</code> 文件,其中包含一些自己项目具体定义的消息类型,以 <code>typed_message.proto</code> 为例子,其中只包含了两种类型的两个字段。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">TypedMessage</span> <span class="p">{</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="c1">// The name of the message type, retrieved from protobuf API. </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kt">string</span> <span class="n">type</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span> <span class="c1">// Serialized proto message. </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kt">bytes</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err"> </span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err"> </span></span></span></code></pre></td></tr></table> </div> </div><p>最终通过层层嵌套构成了一个综合的信息结构体,一并打包发送。</p> <blockquote> <p><a class="link" href="https://wallhaven.cc/w/6k293l" target="_blank" rel="noopener" >Banner Artwork</a></p> </blockquote>

2021/1/3
阅读更多

[归档] SmartDNS 一个智能分流的 DNS 服务

<img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-smartdns-%E4%B8%80%E4%B8%AA%E6%99%BA%E8%83%BD%E5%88%86%E6%B5%81%E7%9A%84-dns-%E6%9C%8D%E5%8A%A1/banner.jpg" alt="Featured image of post [归档] SmartDNS 一个智能分流的 DNS 服务" /><h2 id="介绍">介绍</h2> <blockquote> <p><a class="link" href="https://github.com/pymumu/smartdns" target="_blank" rel="noopener" >项目地址</a></p> </blockquote> <h3 id="dns">DNS</h3> <p>域名系统(英语:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。通常情况下本地的解析请求会发送到离你最近的 DNS 服务器,它可能是你的无线路由器、宿舍楼梯下的服务器或者运营商的地区性服务器等,但由于各种原因导致解析效果不理想,延迟高等问题,这时可以考虑换一个网络质量好的域名解析服务,错峰出行,减少拥堵。</p> <h3 id="smartdns">SmartDNS</h3> <p>SmartDNS 集成了多种出栈请求协议,包括常用的 <code>UDP</code> 和较为现代的 <code>DNS-Over-Https</code> 等,能够在给定的规则列表中挑选出一个延迟最低的域名解析服务,并向其发送请求。同时其内部也提供 DNS 缓存,如果缓存能够命中则直接从本地缓存中返回对应 IP 地址,如未能查找到则继续向上级 DNS 服务传播请求。</p> <p><img src="https://github.com/pymumu/smartdns/raw/master/doc/architecture.png" loading="lazy" alt="structure" ></p> <h2 id="配置">配置</h2> <blockquote> <p><a class="link" href="https://github.com/pymumu/smartdns/blob/master/etc/smartdns/smartdns.conf" target="_blank" rel="noopener" >默认配置文件</a></p> </blockquote> <blockquote> <p>推荐项目:<a class="link" href="https://github.com/felixonmars/dnsmasq-china-list" target="_blank" rel="noopener" >dnsmasq-china-list</a></p> </blockquote> <p>作者肥猫在其项目中提供了几个较为常用的匹配规则列表,对于非 Arch Linux 用户而言可以把项目克隆到本地然后构建对应的配置文件,而 Arch 用户可以从 <a class="link" href="https://www.archlinuxcn.org/archlinux-cn-repo-and-mirror/" target="_blank" rel="noopener" >CN 源</a> 里安装 <code>smartdns-china-list-git</code> 以获取自动更新。</p> <p>生成对应的配置文件操作如下,<a class="link" href="https://github.com/archlinuxcn/repo/blob/c2fe2e155a35da76a879b6a9d0fe21975413bce2/archlinuxcn/dnsmasq-china-list-git/PKGBUILD#L22" target="_blank" rel="noopener" >具体参考</a>,可见其生成形式为 <code>nameserver /&lt;域名&gt;/&lt;组名&gt;</code>,后面的组名就是对应我们后来需要的匹配规则而制定的。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>cd dnsmasq-china-list </span></span><span class="line"><span class="cl"><span class="nv">$ </span>make smartdns <span class="nv">SERVER</span><span class="o">=</span>china </span></span></code></pre></td></tr></table> </div> </div><p>默认的配置文件中有很多不常用的项目,作为自己的配置文件一般精简到自己需要的功能配置即可。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w"> </span><span class="c"># (可选)引入额外的规则列表,用绝对路径</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">conf-file /etc/smartdns/accelerated-domains.china.smartdns.conf</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">conf-file /etc/smartdns/apple.china.smartdns.conf</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">conf-file /etc/smartdns/google.china.smartdns.conf</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># 本地监听端口</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">bind [::]:53</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># 缓存大小</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">cache-size 4096</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># 重启时读取之前的缓存</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">cache-persist yes</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># 缓存文件存放位置</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">cache-file /var/cache/smartdns.cache</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># 传统 UDP 协议(以阿里 DNS 为例)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">server 223.5.5.5</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># DNS Over TLS (以 CloudFlare DNS 为例)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">server-tls 1.0.0.1</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># DNS Over Https (以烧饼 DNS 为例)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">server-https https://doh.dns.sb/dns-query -group china -group example</span><span class="w"> </span></span></span></code></pre></td></tr></table> </div> </div><blockquote> <p>更多 DNS 服务地址:<a class="link" href="https://dnscrypt.info/public-servers/" target="_blank" rel="noopener" >dnscrypt.info</a></p> </blockquote> <p>如果有多个分组需求,可以自定义规则配置文件,服务配置后面再添加 <code>-group [组名]</code>。</p> <p>被 <code>-exclude-default-group</code> 标记为排除在默认组之内的服务需至少在一个组才可能被访问到。</p> <h2 id="启动">启动</h2> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="c"># smartdns -c smartdns.conf </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="k">or</span> </span></span><span class="line"><span class="cl"><span class="c"># systemctl enable --now smartdns.service </span></span></span></code></pre></td></tr></table> </div> </div><p>默认在后台运行,推荐使用自带的 <a class="link" href="https://github.com/pymumu/smartdns/blob/master/systemd/smartdns.service.in" target="_blank" rel="noopener" ><code>systemd service</code></a> 来进行管理,如果 <code>/ect/resolv.conf</code> 没有被更改成监听本地的话可以检查一下文件是否有特殊标志位(<code>lsattr</code>),手动修改即可(<code>chattr</code> )。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>cat /etc/resolv.conf </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c"># DNS managed by SmartDNS </span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nf">nameserver</span> <span class="m">127</span>.<span class="m">0</span>.<span class="m">0</span>.<span class="m">1</span> </span></span></code></pre></td></tr></table> </div> </div><blockquote> <p><a class="link" href="https://wallhaven.cc/w/0qx3x7" target="_blank" rel="noopener" >Banner Artwork</a></p> </blockquote>

2021/1/2
阅读更多

[新手篇] 如何写一个简单的上膛机器人

<img src="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/banner.png" alt="Featured image of post [新手篇] 如何写一个简单的上膛机器人" /><h2 id="介绍">介绍</h2> <p>一个检测关键词并自动回复表情包的 <code>Telegram</code> 机器人。</p> <h2 id="开发">开发</h2> <h3 id="注册一个机器人">注册一个机器人</h3> <ol> <li> <p>访问 Telegram 的官方注册机器人 <a class="link" href="https://t.me/BotFather" target="_blank" rel="noopener" >@BotFather</a> 。</p> </li> <li> <p>使用 <code>/newbot</code> 命令创建一个新的机器人。它会询问你所要创建机器人的名字 (nickname) 和用户名 (username, 以<code>_bot</code>结尾),注册成功后会将机器人的 <code>TOKEN</code> 返回给你,而这个 <code>TOKEN</code> 用于告知服务器这个机器人就是(<del>大明湖畔那个夏雨荷</del>)对应注册的机器人。</p> </li> </ol> <p><img src="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/botfather.png" width="661" height="500" srcset="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/botfather_hucf6421b70a9b3e8ebac9202e8f165b34_180718_480x0_resize_box_3.png 480w, https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/botfather_hucf6421b70a9b3e8ebac9202e8f165b34_180718_1024x0_resize_box_3.png 1024w" loading="lazy" alt="botfather" class="gallery-image" data-flex-grow="132" data-flex-basis="317px" ></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">... </span></span><span class="line"><span class="cl">Use this token to access the HTTP API: </span></span><span class="line"><span class="cl"><span class="sb">`&lt;1145141919810:TheWholeSentenceIsToken&gt;`</span> </span></span><span class="line"><span class="cl">Keep your token secure and store it safely, ... </span></span></code></pre></td></tr></table> </div> </div><h3 id="安装-python-环境">安装 Python 环境</h3> <blockquote> <p>官方网站: <a class="link" href="https://www.python.org/downloads" target="_blank" rel="noopener" >https://www.python.org/downloads</a></p> </blockquote> <p>对于绝大多数 Linux 发行版来说从其自己的包管理器中安装 <code>Python</code> 即可(<del>2021 年了应该都默认 Python3?)</del></p> <p>Windows 则 Python 官方提供了安装器,记得把 <code>Python</code> 加到环境变量 (PATH) 的框给勾选上。</p> <p>为了避免开发环境不干净对后续开发和使用系统造成影响,建议创建一个用于该机器人项目的<a class="link" href="https://docs.python.org/zh-cn/3/tutorial/venv.html" target="_blank" rel="noopener" >虚拟环境(virtual environment)</a></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>python <span class="na">-m</span> venv .venv // 创建了一个名为 .venv 的隐藏文件夹 </span></span></code></pre></td></tr></table> </div> </div><p>对于国内使用 PyPi 源速度不是很给力的情况,可以考虑替换镜像源为<a class="link" href="https://mirrors.tuna.tsinghua.edu.cn/help/pypi/" target="_blank" rel="noopener" >清华源</a></p> <p>也可以写入默认配置文件 <code>~/.config/pip/pip.conf</code>(如果没有则<a class="link" href="https://pip.pypa.io/en/stable/user_guide/#config-file" target="_blank" rel="noopener" >创建</a>)</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[global]</span> </span></span><span class="line"><span class="cl"><span class="na">index-url</span> <span class="o">=</span> <span class="s">https://pypi.tuna.tsinghua.edu.cn/simple</span> </span></span></code></pre></td></tr></table> </div> </div><p>可以查看设置是否生效</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>pip config list </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nf">global</span>.index-<span class="nv">url</span><span class="o">=</span><span class="s1">&#39;https://pypi.tuna.tsinghua.edu.cn/simple&#39;</span> </span></span></code></pre></td></tr></table> </div> </div><p>创建虚拟环境后,您可以激活它,进入项目所在目录。</p> <ul> <li>在 Windows 上,运行:</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">tutorial-env\Scripts\activate.bat </span></span></code></pre></td></tr></table> </div> </div><ul> <li>在 Unix 或 MacOS 上,运行:</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ <span class="nb">source</span> tutorial-env/bin/activate </span></span></code></pre></td></tr></table> </div> </div><p>对于 <code>csh</code> 和 <code>fish shell</code> 分别对应名为 <code>activate.csh</code> 和 <code>activate.fish</code> 的脚本</p> <h3 id="安装软件包依赖">安装软件包依赖</h3> <blockquote> <p><a class="link" href="https://github.com/python-telegram-bot/python-telegram-bot" target="_blank" rel="noopener" >python telegram bot 项目地址</a></p> </blockquote> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>pip install python-telegram-bot <span class="na">--upgrade</span> </span></span></code></pre></td></tr></table> </div> </div><p>也可以将依赖导出到文件,便于开发迁移</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nv">$ </span>pip freeze <span class="o">&gt;</span> requirements.txt </span></span></code></pre></td></tr></table> </div> </div><h3 id="coding-time">Coding Time</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 从环境变量中读取 TOKEN 减少硬编码可能带来的泄漏风险</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 如果只是本地测试的话可以直接把 TOKEN 粘贴到程序中</span> </span></span><span class="line"><span class="cl"> <span class="n">TOKEN</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s1">&#39;TELEGRAM_BOT_TOKEN&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Updater 是 Dispatcher 的实现,用于机器人数据交互的前端,</span> </span></span><span class="line"><span class="cl"> <span class="c1"># 负责更新消息队列并交付给其他调度程序</span> </span></span><span class="line"><span class="cl"> <span class="n">updater</span> <span class="o">=</span> <span class="n">Updater</span><span class="p">(</span><span class="n">token</span><span class="o">=</span><span class="n">TOKEN</span><span class="p">,</span> <span class="n">use_context</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">dispatcher</span> <span class="o">=</span> <span class="n">updater</span><span class="o">.</span><span class="n">dispatcher</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># 开始拉取信息</span> </span></span><span class="line"><span class="cl"> <span class="n">updater</span><span class="o">.</span><span class="n">start_polling</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># 响应终止信号 Ctrl+C</span> </span></span><span class="line"><span class="cl"> <span class="n">updater</span><span class="o">.</span><span class="n">idle</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">main</span><span class="p">()</span> </span></span></code></pre></td></tr></table> </div> </div><p>上述几行代码构成了整个机器人的基本框架,包含了机器人身份认证和开启机器人服务等。我们还需要向该框架内注册具体的方法,以实现所需功能。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="n">update</span><span class="p">:</span> <span class="n">Update</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CallbackContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;&#34;&#34;Send a message when the command /start is issued.&#34;&#34;&#34;</span> </span></span><span class="line"><span class="cl"> <span class="n">reply_text</span> <span class="o">=</span> <span class="s2">&#34;你好~</span><span class="se">\n</span><span class="s2">&#34;</span> </span></span><span class="line"><span class="cl"> <span class="n">user</span> <span class="o">=</span> <span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">from_user</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">user</span><span class="o">.</span><span class="n">username</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">reply_text</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;您的用户名是: </span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">username</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">reply_text</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;ID: </span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">reply_text</span><span class="p">(</span><span class="n">reply_text</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">help_command</span><span class="p">(</span><span class="n">update</span><span class="p">:</span> <span class="n">Update</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CallbackContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;&#34;&#34;Send a message when the command /help is issued.&#34;&#34;&#34;</span> </span></span><span class="line"><span class="cl"> <span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">reply_text</span><span class="p">(</span><span class="s1">&#39;这里是帮助命令&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">echo</span><span class="p">(</span><span class="n">update</span><span class="p">:</span> <span class="n">Update</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CallbackContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;&#34;&#34;Echo the user message.&#34;&#34;&#34;</span> </span></span><span class="line"><span class="cl"> <span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">reply_text</span><span class="p">(</span><span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">text</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span> </span></span><span class="line"><span class="cl"> <span class="n">token</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s1">&#39;TELEGRAM_BOT_TOKEN&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">updater</span> <span class="o">=</span> <span class="n">Updater</span><span class="p">(</span><span class="n">token</span><span class="o">=</span><span class="n">token</span><span class="p">,</span> <span class="n">use_context</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">dispatcher</span> <span class="o">=</span> <span class="n">updater</span><span class="o">.</span><span class="n">dispatcher</span> </span></span><span class="line"><span class="cl"> <span class="n">dispatcher</span><span class="o">.</span><span class="n">add_handler</span><span class="p">(</span><span class="n">CommandHandler</span><span class="p">(</span><span class="s2">&#34;start&#34;</span><span class="p">,</span> <span class="n">start_command</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="n">dispatcher</span><span class="o">.</span><span class="n">add_handler</span><span class="p">(</span><span class="n">CommandHandler</span><span class="p">(</span><span class="s2">&#34;help&#34;</span><span class="p">,</span> <span class="n">help_command</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="n">dispatcher</span><span class="o">.</span><span class="n">add_handler</span><span class="p">(</span><span class="n">MessageHandler</span><span class="p">(</span><span class="n">Filters</span><span class="o">.</span><span class="n">text</span> <span class="o">&amp;</span> <span class="o">~</span><span class="n">Filters</span><span class="o">.</span><span class="n">command</span><span class="p">,</span> <span class="n">echo</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="n">updater</span><span class="o">.</span><span class="n">start_polling</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">updater</span><span class="o">.</span><span class="n">idle</span><span class="p">()</span> </span></span></code></pre></td></tr></table> </div> </div><p>从 <code>Update</code> 中可以拿到消息的类型、具体内容、发送者等关键信息,从 <code>CallbackContext</code> 中可以获取机器人本身的一些信息等。在使用 <code>\</code> 开头的对应命令后就能进入到对应的方法,也可以对此进行类封装。</p> <p>注意到除了 <code>CommandHandler</code> 之外还有其他类型的句柄如常规消息类的 <code>MessageHandler</code>、用于行内输入的的 <code>InlineQueryHandler</code> 等,请详见<a class="link" href="https://python-telegram-bot.readthedocs.io/en/stable/telegram.html" target="_blank" rel="noopener" >上游 API 文档</a>。</p> <p>回到需求,我们需要的检测关键词部分也是从 <code>Update</code> 中来,用关键词列表简单演示,当消息中包含关键词的时候发送一个表情,或者其他类型的回复<a class="link" href="https://python-telegram-bot.readthedocs.io/en/stable/telegram.message.html?highlight=Message" target="_blank" rel="noopener" >详见 Message 类文档</a>。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">telegram</span> <span class="kn">import</span> <span class="n">Update</span><span class="p">,</span> <span class="n">Sticker</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># 临时创建一个表情量</span> </span></span><span class="line"><span class="cl"><span class="n">sticker</span> <span class="o">=</span> <span class="n">Sticker</span><span class="p">(</span><span class="n">file_id</span><span class="o">=</span><span class="s2">&#34;CAACAgUAAxkBAAIMOV_jA7I0IAABMbqNVNGkJWZNiDRT6QACiwIAArL6ew6tFsY1eQy9Lx4E&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">file_unique_id</span><span class="o">=</span><span class="s2">&#34;AgADiwIAArL6ew4&#34;</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">480</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">512</span><span class="p">,</span> <span class="n">is_animated</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">echo</span><span class="p">(</span><span class="n">update</span><span class="p">:</span> <span class="n">Update</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CallbackContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">keys_list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;上膛&#34;</span><span class="p">,</span> <span class="s2">&#34;车主&#34;</span><span class="p">,</span> <span class="s2">&#34;特斯拉&#34;</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">keys_list</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">msg</span> <span class="o">=</span> <span class="n">update</span><span class="o">.</span><span class="n">message</span> </span></span><span class="line"><span class="cl"> <span class="n">msg</span><span class="o">.</span><span class="n">reply_sticker</span><span class="p">(</span><span class="n">sticker</span><span class="p">)</span> </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/echo_preview.png" width="596" height="284" srcset="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/echo_preview_hu13c889a0dde82791a75b695a30732db5_121401_480x0_resize_box_3.png 480w, https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/echo_preview_hu13c889a0dde82791a75b695a30732db5_121401_1024x0_resize_box_3.png 1024w" loading="lazy" alt="echo" class="gallery-image" data-flex-grow="209" data-flex-basis="503px" ></p> <p>至此初步效果已经达成,剩下要解决的两个疑问就是:</p> <ul> <li>Sticker 的信息是怎么来的?</li> <li>如何回复指定消息</li> </ul> <h4 id="sticker-信息">Sticker 信息</h4> <p>可以添加一个专用于获取表情包信息的句柄</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_sticker</span><span class="p">(</span><span class="n">update</span><span class="p">:</span> <span class="n">Update</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CallbackContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">sticker</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="n">dispatcher</span><span class="o">.</span><span class="n">add_handler</span><span class="p">(</span><span class="n">MessageHandler</span><span class="p">(</span><span class="n">Filters</span><span class="o">.</span><span class="n">sticker</span> <span class="o">&amp;</span> <span class="o">~</span><span class="n">Filters</span><span class="o">.</span><span class="n">command</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">get_sticker</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span></code></pre></td></tr></table> </div> </div><p>为了便于理解这里在服务端后台直接输出表情包信息作为日志,对于一些要动态添加表情包和对应关键字的需求来说,一个轻便的可持久化数据才是更好的选择。创建 Sticker 实例的时候注意不要遗漏必要的传入参数。</p> <p><img src="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/sticker_info.png" width="862" height="117" srcset="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/sticker_info_hu4c462ca90775e67664e71ad2a3337f27_61412_480x0_resize_box_3.png 480w, https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/sticker_info_hu4c462ca90775e67664e71ad2a3337f27_61412_1024x0_resize_box_3.png 1024w" loading="lazy" alt="sticker_info" class="gallery-image" data-flex-grow="736" data-flex-basis="1768px" ></p> <h4 id="如何回复指定的消息">如何回复指定的消息</h4> <p>简单来说这就是一个套娃过程,从包含关键字消息的 <code>reply_to_message</code> 成员可以判断并拿到上一条回复的消息,回复拿到的上一条消息即可,从而实现精准打击(x</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"> <span class="o">...</span> </span></span><span class="line"><span class="cl"> <span class="n">msg</span> <span class="o">=</span> <span class="n">update</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">reply_to_message</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">msg</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">msg</span><span class="o">.</span><span class="n">reply_sticker</span><span class="p">(</span><span class="n">sticker</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="o">...</span> </span></span></code></pre></td></tr></table> </div> </div><p><img src="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/reply.png" width="666" height="428" srcset="https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/reply_hu6c3252242f1a9b7b61af37965102af7d_192120_480x0_resize_box_3.png 480w, https://axionl.me/p/%E6%96%B0%E6%89%8B%E7%AF%87-%E5%A6%82%E4%BD%95%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%8A%E8%86%9B%E6%9C%BA%E5%99%A8%E4%BA%BA/reply_hu6c3252242f1a9b7b61af37965102af7d_192120_1024x0_resize_box_3.png 1024w" loading="lazy" alt="reply to your reply" class="gallery-image" data-flex-grow="155" data-flex-basis="373px" ></p> <h2 id="结束">结束</h2> <p>以简单的案例来介绍 Telegram 机器人的玩法,希望能够帮助到入门的萌新打造属于自己的机器人。(撒花 ~=o(<em>^▽^</em>)o~♪</p>

2020/12/31
阅读更多

Friends

2020/12/14
阅读更多

Teleport 小记

<img src="https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/banner.jpg" alt="Featured image of post Teleport 小记" /><h2 id="简介">简介</h2> <p>常用于跨平台集群管理,提供多设备认证、远程操作和反向代理。</p> <blockquote> <p>项目地址:<a class="link" href="https://github.com/gravitational/teleport" target="_blank" rel="noopener" >github.com/gravitational/teleport</a></p> </blockquote> <blockquote> <p>官方网站:<a class="link" href="https://goteleport.com/teleport" target="_blank" rel="noopener" >goteleport.com</a></p> </blockquote> <h2 id="安装">安装</h2> <p>有以下几种方式,单个可执行文件内已经包含 web 管理平台、服务端和客户端功能。(<em>Windows 目前仅支持客户端</em>)</p> <ul> <li><a class="link" href="https://gravitational.com/teleport/download/" target="_blank" rel="noopener" >可执行文件下载</a></li> <li><a class="link" href="https://quay.io/repository/gravitational/teleport?tab=tags" target="_blank" rel="noopener" >Docker 下载</a></li> <li><a class="link" href="https://goteleport.com/teleport/installing" target="_blank" rel="noopener" >编译安装</a></li> </ul> <h2 id="快速配置">快速配置</h2> <h3 id="服务端及-web-管理平台">服务端及 Web 管理平台</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">teleport</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">data_dir</span><span class="p">:</span><span class="w"> </span><span class="l">/var/lib/teleport</span><span class="w"> </span><span class="c"># 数据存放目录</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">auth_service</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cluster_name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;demo_cluster&#34;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">listen_addr</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="p">:</span><span class="m">3025</span><span class="w"> </span><span class="c"># 认证监听地址</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tokens</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">proxy,node,app:&lt;nodes_auth_token&gt;</span><span class="w"> </span><span class="c"># 节点认证密令</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">ssh_service</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">labels</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> </span><span class="l">staging</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">app_service</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">debug_app</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">proxy_service</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">listen_addr</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="p">:</span><span class="m">3023</span><span class="w"> </span><span class="c"># 反向代理监听地址</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">web_listen_addr</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="p">:</span><span class="m">3080</span><span class="w"> </span><span class="c"># web 管理页面地址</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tunnel_listen_addr</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="p">:</span><span class="m">3024</span><span class="w"> </span><span class="c"># 隧道监听地址</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">public_addr</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;https://example.domain.org:3025&gt;</span><span class="w"> </span><span class="c"># 反向代理公共地址</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">https_keypairs</span><span class="p">:</span><span class="w"> </span><span class="c"># 本地测试时可不设置证书</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">key_file</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;key_file_path&gt;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cert_file</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;cert_file_path&gt;</span><span class="w"> </span></span></span></code></pre></td></tr></table> </div> </div><p><em>可由如下命令启用服务端进行测试,要点如下:</em></p> <ul> <li>在本地测试时即便没有填写 <code>https_keypairs</code> 也仍然需要在监听的 <code>web</code> 地址前面加上 <code>https://</code>,或者使用 <code>--insecure-no-tls</code> 命令行参数对 <code>http://</code> 进行访问。</li> <li>如果配置中含有非对应权限目录或者证书文件,则调整到对应权限,如 <code>/var/lib/teleport</code> 仅限 <code>root</code> 权限访问,则需要调整到对应的权限再启动命令。</li> <li>所有的登陆行文均在 <code>web_listen_addr</code> 所指示的地址发生。</li> <li>注意证书所对应的域名与可访问的相同</li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ teleport start --config teleport.yaml </span></span></code></pre></td></tr></table> </div> </div><p><code>tctl</code> 是 <code>teleport</code> 的一个命令行管理工具,在 <code>teleport</code> 服务运行时,可以由它对用户、节点、密令等进行动态管理。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 创建一个初始用户</span> </span></span><span class="line"><span class="cl">$ tctl users add &lt;username&gt; &lt;login_user, login_group&gt; --config teleport.yaml </span></span></code></pre></td></tr></table> </div> </div><p>添加好用户后可访问提示的地址,输入账户密码,用手机两步验证器扫码后填入验证码即可初始化成功。配置文件中支持除 <code>otp</code> 外还支持 <code>github auth</code> 认证等方式。</p> <p><img src="https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/invite.png" width="884" height="734" srcset="https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/invite_hu9c7b42c1b5dbea967607293c8d3ece26_53712_480x0_resize_box_3.png 480w, https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/invite_hu9c7b42c1b5dbea967607293c8d3ece26_53712_1024x0_resize_box_3.png 1024w" loading="lazy" alt="注册界面" class="gallery-image" data-flex-grow="120" data-flex-basis="289px" > <img src="https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/login.png" width="700" height="705" srcset="https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/login_hu758d252b3ca4432e4cc31b1647d0f276_29819_480x0_resize_box_3.png 480w, https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/login_hu758d252b3ca4432e4cc31b1647d0f276_29819_1024x0_resize_box_3.png 1024w" loading="lazy" alt="登陆界面" class="gallery-image" data-flex-grow="99" data-flex-basis="238px" ></p> <blockquote> <p>常用的两步验客户端</p> <ul> <li><a class="link" href="https://authy.com/download/" target="_blank" rel="noopener" >Authy</a></li> <li><a class="link" href="https://www.google.com/landing/2step/" target="_blank" rel="noopener" >Google Authenticator</a></li> <li><a class="link" href="https://www.microsoft.com/en-us/account/authenticator" target="_blank" rel="noopener" >Microsoft Authenticator</a></li> </ul> </blockquote> <h3 id="子节点">子节点</h3> <p>除了服务端平台自己可以作为节点外,还可以添加其他的子节点构成集群,大体上分为两类:</p> <ul> <li>子节点有公网可以访问</li> <li>非公网节点需要反向代理(<code>roles</code> 内需要含有 <code>proxy</code>)。</li> </ul> <h4 id="静态子节点">静态子节点</h4> <p>静态子节点配置文件需要对应服务端里的地址和 <code>&lt;nodes_auth_token&gt;</code>,随后直接在节点上运行即可。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">teleport</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">nodename</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;my_laptop&#34;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">data_dir</span><span class="p">:</span><span class="w"> </span><span class="l">/var/lib/teleport/</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">auth_token</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;nodes_auth_token&gt;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">auth_servers</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">&lt;https://example.domain.org:3025&gt;</span><span class="w"> </span><span class="c"># 认证地址</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">proxy_service</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="c"># 本地子节点暂时无需启动反向代理服务端</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">ssh_service</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">labels</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> </span><span class="l">local_node</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">auth_service</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="c"># 本地子节点暂时无需启动认证服务端</span><span class="w"> </span></span></span></code></pre></td></tr></table> </div> </div><h4 id="动态子节点">动态子节点</h4> <p>动态子节点需要在服务端上进行添加,由以下命令生成一个临时的 <code>token</code> 以添加子节点。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ tctl nodes add --roles<span class="o">=</span>node,proxy --ttl<span class="o">=</span>5m --config teleport.yaml </span></span></code></pre></td></tr></table> </div> </div><p>得到形如下方的命令:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ teleport start <span class="se">\ </span></span></span><span class="line"><span class="cl"><span class="se"></span> --roles<span class="o">=</span>node,proxy <span class="se">\ </span></span></span><span class="line"><span class="cl"><span class="se"></span> --token<span class="o">=</span>&lt;random_token&gt; <span class="se">\ </span></span></span><span class="line"><span class="cl"><span class="se"></span> --auth-server<span class="o">=</span>&lt;https://example.domain.org:3025&gt; </span></span></code></pre></td></tr></table> </div> </div><p>添加成功后可以在 <code>web</code> 界面内看到所有的节点。 <img src="https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/cluster.png" width="822" height="181" srcset="https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/cluster_hu985d9a3ce60a2302a9eeab16abdb0ca9_12685_480x0_resize_box_3.png 480w, https://axionl.me/p/teleport-%E5%B0%8F%E8%AE%B0/cluster_hu985d9a3ce60a2302a9eeab16abdb0ca9_12685_1024x0_resize_box_3.png 1024w" loading="lazy" alt="Server Page" class="gallery-image" data-flex-grow="454" data-flex-basis="1089px" ></p> <h3 id="客户端">客户端</h3> <p>安装包内自带一个名为 <code>tsh</code> 的可执行文件,用于命令行认证和登陆</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ tsh login --proxy<span class="o">=</span>&lt;https://example.domain.org:3025&gt; --user<span class="o">=</span>&lt;username&gt; </span></span></code></pre></td></tr></table> </div> </div><p>登陆后可以使用 <code>ls</code> 命令查看已添加的节点</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ tsh ls </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Node Name Address Labels </span></span><span class="line"><span class="cl">-------------- -------------- ---------------- </span></span><span class="line"><span class="cl">demo_cluster 127.0.0.1:3022 <span class="nv">env</span><span class="o">=</span>core_service </span></span><span class="line"><span class="cl">my_laptop ⟵ Tunnel <span class="nv">env</span><span class="o">=</span>local_node </span></span></code></pre></td></tr></table> </div> </div><p>使用 <code>ssh</code> 来登陆节点终端</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ tsh ssh &lt;node_name&gt; </span></span></code></pre></td></tr></table> </div> </div><h2 id="高级配置">高级配置</h2> <h3 id="配合-k8s-管理服务">配合 K8S 管理服务</h3> <p>TBC&hellip;</p> <hr> <blockquote> <p><a class="link" href="https://wallhaven.cc/w/ymz1qx" target="_blank" rel="noopener" >Banner Artwork</a></p> </blockquote>

2020/12/6
阅读更多

Archives

2019/5/28
阅读更多

[归档] TinyTeX + VSCode

<img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-tinytex--vscode/banner.png" alt="Featured image of post [归档] TinyTeX + VSCode" /><p><code>TinyTeX</code> 是 yihui 制作的一款基于 TeX Live 的轻量级、跨平台、简单易用的 LaTeX 发行版。与安装占用空间庞大的完整版相比,TinyTex 按需所取、大幅精简,用 <code>tlmgr</code> 包管理器安装所需依赖即可基本使用。</p> <h2 id="项目链接">项目链接</h2> <blockquote> <p><a class="link" href="https://github.com/yihui/tinytex" target="_blank" rel="noopener" >Github</a> | <a class="link" href="https://yihui.name/tinytex/" target="_blank" rel="noopener" >文档</a></p> </blockquote> <h2 id="安装-tinytex">安装 TinyTex</h2> <h3 id="从-r-语言终端安装">从 R 语言终端安装</h3> <p>初始化 R 语言环境设置,主要是修改软件源方便后续下载,这部分参考 <a class="link" href="https://wiki.archlinux.org/index.php/R" target="_blank" rel="noopener" >Arch Wiki</a>。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo pacman -S r </span></span><span class="line"><span class="cl">$ touch ~/.Renviron ~/.Rprofile <span class="c1"># 创建两配置文件内容如下</span> </span></span></code></pre></td></tr></table> </div> </div><ul> <li><code>.Renviron</code></li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">R_HOME_USER</span> <span class="o">=</span> <span class="s">/path/to/your/r/directory # 设置你自己的目标路径,下同</span> </span></span><span class="line"><span class="cl"><span class="na">R_PROFILE_USER</span> <span class="o">=</span> <span class="s">${HOME}/.config/r/.Rprofile</span> </span></span><span class="line"><span class="cl"><span class="na">R_LIBS_USER</span> <span class="o">=</span> <span class="s">/path/to/your/r/library</span> </span></span><span class="line"><span class="cl"><span class="na">R_HISTFILE</span> <span class="o">=</span> <span class="s">/path/to/your/filename.Rhistory # Do not forget to append the .Rhistory</span> </span></span><span class="line"><span class="cl"><span class="na">MYSQL_HOME</span> <span class="o">=</span> <span class="s">/var/lib/mysql</span> </span></span></code></pre></td></tr></table> </div> </div><ul> <li><code>.Rprofile</code></li> </ul> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># The .First function is called after everything else in .Rprofile is executed</span> </span></span><span class="line"><span class="cl"><span class="n">.First</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Print a welcome message</span> </span></span><span class="line"><span class="cl"> <span class="nf">message</span><span class="p">(</span><span class="s">&#34;Welcome back &#34;</span><span class="p">,</span> <span class="nf">Sys.getenv</span><span class="p">(</span><span class="s">&#34;USER&#34;</span><span class="p">),</span><span class="s">&#34;!\n&#34;</span><span class="p">,</span><span class="s">&#34;working directory is:&#34;</span><span class="p">,</span> <span class="nf">getwd</span><span class="p">())</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nf">options</span><span class="p">(</span><span class="n">digits</span> <span class="o">=</span> <span class="m">12</span><span class="p">)</span> <span class="c1"># number of digits to print. Default is 7, max is 15</span> </span></span><span class="line"><span class="cl"><span class="nf">options</span><span class="p">(</span><span class="n">stringsAsFactors</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span> <span class="c1"># Disable default conversion of character strings to factors</span> </span></span><span class="line"><span class="cl"><span class="nf">options</span><span class="p">(</span><span class="n">show.signif.stars</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span> <span class="c1"># Don&#39;t show stars indicating statistical significance in model outputs</span> </span></span><span class="line"><span class="cl"><span class="n">error</span> <span class="o">&lt;-</span> <span class="nf">quote</span><span class="p">(</span><span class="nf">dump.frames</span><span class="p">(</span><span class="s">&#34;${R_HOME_USER}/testdump&#34;</span><span class="p">,</span> <span class="kc">TRUE</span><span class="p">))</span> <span class="c1"># post-mortem debugging facilities</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nf">options</span><span class="p">(</span><span class="s">&#34;repos&#34;</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="n">CRAN</span><span class="o">=</span><span class="s">&#34;https://mirrors.tuna.tsinghua.edu.cn/CRAN/&#34;</span><span class="p">))</span> <span class="c1"># 换用 tuna 源</span> </span></span></code></pre></td></tr></table> </div> </div><p>如果不在配置文件中修改源的话,接下来在交互模式内,第一次安装包的时候也会出现一个窗口需要你来选择源。由于弹出窗口是 <code>tk</code> 实现的,所以需手动安装其作为依赖。如果第一次安装选择错误,那么可以进入交互模式后输入 <code>chooseCRANmirror()</code> 来修改,如提示所言,退出交互模式为 <code>q()</code>,并且会问你是否保存会话。</p> <p>在命令行输入 R,进入交互模式</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="o">$</span> <span class="n">R</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">R</span> <span class="n">version</span> <span class="m">3.5.2</span> <span class="p">(</span><span class="m">2018-12-20</span><span class="p">)</span> <span class="o">--</span> <span class="s">&#34;Eggshell Igloo&#34;</span> </span></span><span class="line"><span class="cl"><span class="nf">Copyright </span><span class="p">(</span><span class="n">C</span><span class="p">)</span> <span class="m">2018</span> <span class="n">The</span> <span class="n">R</span> <span class="n">Foundation</span> <span class="kr">for</span> <span class="n">Statistical</span> <span class="n">Computing</span> </span></span><span class="line"><span class="cl"><span class="n">Platform</span><span class="o">:</span> <span class="n">x86_64</span><span class="o">-</span><span class="n">pc</span><span class="o">-</span><span class="n">linux</span><span class="o">-</span><span class="nf">gnu </span><span class="p">(</span><span class="m">64</span><span class="o">-</span><span class="n">bit</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="n">R</span> <span class="n">is</span> <span class="n">free</span> <span class="n">software</span> <span class="n">and</span> <span class="n">comes</span> <span class="n">with</span> <span class="n">ABSOLUTELY</span> <span class="n">NO</span> <span class="n">WARRANTY.</span> </span></span><span class="line"><span class="cl"><span class="n">You</span> <span class="n">are</span> <span class="n">welcome</span> <span class="n">to</span> <span class="n">redistribute</span> <span class="n">it</span> <span class="n">under</span> <span class="n">certain</span> <span class="n">conditions.</span> </span></span><span class="line"><span class="cl"><span class="n">Type</span> <span class="s">&#39;license()&#39;</span> <span class="n">or</span> <span class="s">&#39;licence()&#39;</span> <span class="kr">for</span> <span class="n">distribution</span> <span class="n">details.</span> </span></span><span class="line"><span class="cl"><span class="n">Natural</span> <span class="n">language</span> <span class="n">support</span> <span class="n">but</span> <span class="n">running</span> <span class="kr">in</span> <span class="n">an</span> <span class="n">English</span> <span class="n">locale</span> </span></span><span class="line"><span class="cl"><span class="n">R</span> <span class="n">is</span> <span class="n">a</span> <span class="n">collaborative</span> <span class="n">project</span> <span class="n">with</span> <span class="n">many</span> <span class="n">contributors.</span> </span></span><span class="line"><span class="cl"><span class="n">Type</span> <span class="s">&#39;contributors()&#39;</span> <span class="kr">for</span> <span class="n">more</span> <span class="n">information</span> <span class="n">and</span> </span></span><span class="line"><span class="cl"><span class="s">&#39;citation()&#39;</span> <span class="n">on</span> <span class="n">how</span> <span class="n">to</span> <span class="n">cite</span> <span class="n">R</span> <span class="n">or</span> <span class="n">R</span> <span class="n">packages</span> <span class="kr">in</span> <span class="n">publications.</span> </span></span><span class="line"><span class="cl"><span class="n">Type</span> <span class="s">&#39;demo()&#39;</span> <span class="kr">for</span> <span class="n">some</span> <span class="n">demos</span><span class="p">,</span> <span class="s">&#39;help()&#39;</span> <span class="kr">for</span> <span class="n">on</span><span class="o">-</span><span class="n">line</span> <span class="n">help</span><span class="p">,</span> <span class="n">or</span> </span></span><span class="line"><span class="cl"><span class="s">&#39;help.start()&#39;</span> <span class="kr">for</span> <span class="n">an</span> <span class="n">HTML</span> <span class="n">browser</span> <span class="n">interface</span> <span class="n">to</span> <span class="n">help.</span> </span></span><span class="line"><span class="cl"><span class="n">Type</span> <span class="s">&#39;q()&#39;</span> <span class="n">to</span> <span class="n">quit</span> <span class="n">R.</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="nf">install.packages</span><span class="p">(</span><span class="s">&#39;tinytex&#39;</span><span class="p">)</span> <span class="c1"># 安装 tinytex</span> </span></span><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="n">tinytex</span><span class="o">::</span><span class="nf">install_tinytex</span><span class="p">()</span> </span></span></code></pre></td></tr></table> </div> </div><p>如果使用非管理员权限安装的话,会出现以下提示,输入 <code>yes</code> 即将写入目录置于用户家目录下。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="nf">install.packages</span><span class="p">(</span><span class="s">&#39;tinytex&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="n">Warning</span> <span class="kr">in</span> <span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;tinytex&#34;</span><span class="p">)</span> <span class="o">:</span> </span></span><span class="line"><span class="cl"><span class="s">&#39;lib = &#34;/usr/lib/R/library&#34;&#39;</span> <span class="n">is</span> <span class="n">not</span> <span class="n">writable</span> </span></span><span class="line"><span class="cl"><span class="n">Would</span> <span class="n">you</span> <span class="n">like</span> <span class="n">to</span> <span class="n">use</span> <span class="n">a</span> <span class="n">personal</span> <span class="n">library</span> <span class="n">instead</span><span class="o">?</span> <span class="p">(</span><span class="n">yes</span><span class="o">/</span><span class="n">No</span><span class="o">/</span><span class="n">cancel</span><span class="p">)</span> </span></span></code></pre></td></tr></table> </div> </div><p>如果你看的是<a class="link" href="https://yihui.name/tinytex/cn/" target="_blank" rel="noopener" >中文文档</a>的话,其中默认使用了 <code>devtools</code> 这个包,你需要自己先手动安装,即 <code>install.packages('devtools')</code>,无特殊需求则等待安装结束。</p> <h3 id="从脚本安装">从脚本安装</h3> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">wget -qO- <span class="s2">&#34;https://yihui.name/gh/tinytex/tools/install-unx.sh&#34;</span> <span class="p">|</span> sh </span></span></code></pre></td></tr></table> </div> </div><h3 id="添加环境变量">添加环境变量</h3> <p>安装完成之后会在 <code>$HOME</code> 目录下出现一个 <code>.TinyTeX</code> 的文件夹,并且将其中的二进制软链接到了 <code>$HOME/bin</code>,你需要将这个文件夹添加到自己的 <code>$PATH</code> 中才可正常使用 <code>tlmgr</code> 等一系列工具。环境变量生效之后,TinyTeX 安装部分至此结束。</p> <h2 id="中文常用软件包配置">中文常用软件包配置</h2> <p>对于中文用户常用的包有 <code>ctex</code>、<code>xecjk</code>、<code>cjk</code>、<code>cjkpunct</code>、<code>fandol</code> 等,如果是直接用脚本安装的话可能需要手动装更多依赖,如果不确定要装什么包的话可以先找个简单的示例文档进行编译和预览,再根据报错提示来安装缺失的依赖包。</p> <p><code>tlmgr --gui</code> 提供了一个简易的图形界面用于软件包安装和配置。</p> <p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-tinytex--vscode/manager.png" width="753" height="767" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-tinytex--vscode/manager_hu3d77905170a70818a291540747ec6718_71748_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-tinytex--vscode/manager_hu3d77905170a70818a291540747ec6718_71748_1024x0_resize_box_3.png 1024w" loading="lazy" alt="texLiveManager" class="gallery-image" data-flex-grow="98" data-flex-basis="235px" ></p> <p>也使用 R 语言提供的交互式命令行安装:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">tlmgr_install</span><span class="p">(</span><span class="s">&#39;PackageName&#39;</span><span class="p">)</span> </span></span></code></pre></td></tr></table> </div> </div><p>或直接在命令行安装:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">tlmgr</span> <span class="n">install</span> <span class="s">&#39;PackageName&#39;</span> <span class="c1"># 安装</span> </span></span></code></pre></td></tr></table> </div> </div><h2 id="vscode-配置">VSCode 配置</h2> <p><a class="link" href="https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop" target="_blank" rel="noopener" >LaTeX Workshop</a> 插件提供 LaTeX 渲染、高亮和补全支持,可以在扩展商店中搜索安装。</p> <p><img src="https://axionl.me/p/%E5%BD%92%E6%A1%A3-tinytex--vscode/texpreview.png" width="1057" height="808" srcset="https://axionl.me/p/%E5%BD%92%E6%A1%A3-tinytex--vscode/texpreview_hu181cf4fa8d2506633d4cf13cb20a64de_95522_480x0_resize_box_3.png 480w, https://axionl.me/p/%E5%BD%92%E6%A1%A3-tinytex--vscode/texpreview_hu181cf4fa8d2506633d4cf13cb20a64de_95522_1024x0_resize_box_3.png 1024w" loading="lazy" alt="texpreview" class="gallery-image" data-flex-grow="130" data-flex-basis="313px" ></p> <p>预览效果如上,其默认编译器的 <code>pdflatex</code> 对 <code>ctex</code> 中文包支持不是很好,建议参考照官方文档说明,在文档中添加 <code>% !TEX program = xelatex</code> ( Magic comment),使用 xelatex 作为指定编译器。</p> <p>其余 LaTeX 具体书写语法可参见 <a class="link" href="https://tuna.moe/assets/slides/latex-talk-v2.0.pdf" target="_blank" rel="noopener" >TUNA 的演示文档</a>。</p>

2018/12/26
阅读更多

Search

2001/1/1
阅读更多