<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Oli 的小站</title><description>前端、Linux 命令与运维的折腾笔记</description><link>https://oliver6868686.tech/</link><language>zh_CN</language><item><title>收藏夹公开：最近在看的四个技术视频</title><link>https://oliver6868686.tech/posts/video-collection/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/video-collection/</guid><description>事件循环讲得最透的一个演讲，外加前端和 Linux 的入门课，都是免费视频，顺手分享。</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;带新人的时候总被问&quot;有没有视频可以推荐&quot;，每次现找太麻烦，干脆把收藏夹公开。下面四个都免费，都看过至少一遍，按主题排列。&lt;/p&gt;
&lt;h2&gt;Event Loop：Philip Roberts 的经典演讲&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1oV411k7XY&amp;amp;p=1&amp;amp;autoplay=0&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;2014 年 JSConf EU 的演讲，二十多分钟把 JavaScript 事件循环讲得干干净净，配合浏览器开发者工具现场演示，看完再去看宏任务微任务的各种总结文章就不晕了。准备前端面试背题之前，先把这个看了。&lt;/p&gt;
&lt;h2&gt;前端入门：pink 老师的 HTML + CSS&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV14J4114768&amp;amp;p=1&amp;amp;autoplay=0&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;两千万播放的课，五百多集，从 HTML 标签一路讲到移动端布局实战。我这种后端出身、偶尔被拉去救火前端的人，翻的就是它——零基础友好，跳着看也跟得上。已经出了新版，入门内容大同小异，看哪版都行。&lt;/p&gt;
&lt;h2&gt;Linux 入门：韩顺平的一周学会 Linux&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1Sv411r7vd&amp;amp;p=1&amp;amp;autoplay=0&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;讲得细，节奏慢，适合完全没有命令行经验的人。我把它当&quot;新人入职第一周教材&quot;用，比我自己讲省事，也比大多数公司内训靠谱。&lt;/p&gt;
&lt;h2&gt;30 分钟速览：GeekHour 的 Linux 入门&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1cq421w72c&amp;amp;p=1&amp;amp;autoplay=0&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;时间紧就看这个，半小时建立对 Linux 的全局认知，知道命令行能干什么，之后再按需深入。适合在完整课程和&quot;我只想快速了解一下&quot;之间犹豫的人。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;列表会不定期更新，看到值得二刷的再补进来。&lt;/p&gt;
</content:encoded></item><item><title>博客搬家：域名忘了续费</title><link>https://oliver6868686.tech/posts/domain-move/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/domain-move/</guid><description>一觉醒来发现域名过期被抢注了，干脆换个新的，内容和 RSS 全部搬了过来。</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上周最离谱的一件事：打开自己博客，跳转到一个域名出售页面。第一反应是被黑了，冷静下来一查——域名过期了，宽限期也过了，被域名贩子抢注，挂着一口价在卖。&lt;/p&gt;
&lt;h2&gt;事情经过&lt;/h2&gt;
&lt;p&gt;这个域名当时是随手注册的，没开自动续费，注册商的提醒邮件又躺在垃圾箱里。过期到被抢注之间其实有大概一个多月的窗口，我愣是一点没察觉。&lt;/p&gt;
&lt;p&gt;试着联系对方买回来，开口就是四位数，算了。&lt;/p&gt;
&lt;h2&gt;索性搬家&lt;/h2&gt;
&lt;p&gt;新域名注册好、证书签好、DNS 切过来，前后不到半小时。所有文章和 RSS 内容都迁移到了新域名，旧 RSS 地址作废，订阅的朋友麻烦换一下。&lt;/p&gt;
&lt;p&gt;:::note
以后所有域名一律开自动续费 + 日历双重提醒。便宜域名续费一年也就一杯奶茶钱，被抢注了赎回可是十倍不止。
:::&lt;/p&gt;
&lt;h2&gt;一点感想&lt;/h2&gt;
&lt;p&gt;域名这东西平时完全无感，出事的时候才知道它是一切的入口。数据我都有备份和源文件，损失的就是一个用了快一年的地址，不算心疼，但教训到位了。&lt;/p&gt;
</content:encoded></item><item><title>记一次 nginx upstream 调优：buffer 太小引起的 502</title><link>https://oliver6868686.tech/posts/nginx-502-buffer/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/nginx-502-buffer/</guid><description>线上一个反代服务间歇性 502，日志里写着 upstream sent too big header，排查过程和最终修复。</description><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前两天一个反代服务开始间歇性 502，有意思的是重启之后好一阵子，然后又冒出来，频率不高但很烦。打开 error.log 一看，关键行是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;upstream sent too big header while reading response header from upstream
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;原因&lt;/h2&gt;
&lt;p&gt;上游是一个走 Akamai 的站点，响应头里塞了一大串 &lt;code&gt;set-cookie&lt;/code&gt;。nginx 默认的 &lt;code&gt;proxy_buffer_size&lt;/code&gt; 只有 4k（新版本是 8k），响应头一旦超了就直接掐断，客户端看到的就是 502。&lt;/p&gt;
&lt;h2&gt;修复&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;proxy_buffer_size 16k;
proxy_buffers 8 16k;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;改完 reload，观察了两天没再出现。顺手还把 &lt;code&gt;proxy_connect_timeout&lt;/code&gt; 和 &lt;code&gt;proxy_read_timeout&lt;/code&gt; 收紧了一点，免得上游抽风时 worker 被拖死。&lt;/p&gt;
&lt;h2&gt;一点感想&lt;/h2&gt;
&lt;p&gt;这类参数默认值在小流量场景完全够用，所以平时根本注意不到。等上游换了 CDN 或者开始发大 cookie 的时候就突然爆雷，而且日志里的关键字和&quot;网关挂了&quot;的第一直觉完全对不上，容易往错误方向查。&lt;/p&gt;
</content:encoded></item><item><title>CSS 原生嵌套：可以扔掉预处理器了</title><link>https://oliver6868686.tech/posts/css-nesting/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/css-nesting/</guid><description>Chrome、Safari、Firefox 全线支持 CSS 嵌套两年后，我终于把项目里的 Sass 拆了。语法要点和踩过的坑。</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CSS 嵌套从新奇特性变成 Baseline 已经两年多，这次重构我把一个老项目的 Sass 全部拆掉了，构建链少一个依赖，样式行为零变化。&lt;/p&gt;
&lt;h2&gt;基本语法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;.card {
  padding: 1rem;

  &amp;amp; .title {
    font-size: 1.25rem;
  }

  &amp;amp;:hover {
    background: var(--bg-hover);
  }

  @media (width &amp;gt;= 768px) {
    padding: 2rem;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和 Sass 最大的区别是元素选择器必须带 &lt;code&gt;&amp;amp;&lt;/code&gt;。Sass 里可以直接写 &lt;code&gt;.card { h2 {} }&lt;/code&gt;，原生语法要求写成 &lt;code&gt;&amp;amp; h2&lt;/code&gt;，否则浏览器不认。迁移时脚本批量替换这一处就能跑通大半。&lt;/p&gt;
&lt;h2&gt;真正的好处&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;少一层编译，样式调试时 Sources 面板里看到的行号就是源文件行号&lt;/li&gt;
&lt;li&gt;媒体查询可以写在选择器内部，响应式样式不再割裂在文件两头&lt;/li&gt;
&lt;li&gt;和 &lt;code&gt;:has()&lt;/code&gt;、层叠层（&lt;code&gt;@layer&lt;/code&gt;）这些新能力组合使用，选择器权重问题好控多了&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;兼容性处理&lt;/h2&gt;
&lt;p&gt;老项目还要照顾旧浏览器的话，PostCSS 一行配置就能垫平：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;postcssPresetEnv({ features: { &apos;nesting-rules&apos;: true } })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新项目我建议直接裸写，不做任何转译。不支持嵌套的浏览器如今和不支持 flexbox 的占比差不多，为它们多养一个构建步骤不划算。&lt;/p&gt;
</content:encoded></item><item><title>Let&apos;s Encrypt 自动续期踩坑：证书续了，服务却没重载</title><link>https://oliver6868686.tech/posts/acme-renew-reload/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/acme-renew-reload/</guid><description>acme.sh 续期成功、文件也换新了，但服务还在用旧证书跑。reloadcmd 没写对的排查记录。</description><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;帮朋友排查一个&quot;证书过期&quot;的问题，翻日志发现 acme.sh 每次续期都是成功的，证书文件也是新的，但浏览器里看到的证书还是两个月前那张。&lt;/p&gt;
&lt;h2&gt;问题在哪&lt;/h2&gt;
&lt;p&gt;他当初装证书时 &lt;code&gt;--reloadcmd&lt;/code&gt; 写的是 &lt;code&gt;nginx -s reload&lt;/code&gt;，但真正用证书的是另一个进程，nginx 只是路过。证书续了、文件换了，那个进程却从头到尾没重启过，内存里还是旧证书。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/acme-broken-chain.svg&quot; alt=&quot;自动续期链条的四个环节，前三环全部成功，最后一环&amp;quot;服务加载新证书&amp;quot;从未发生，链条在这里断掉&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;正确姿势&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;acme.sh --install-cert -d example.com --ecc \
  --fullchain-file /path/fullchain.pem \
  --key-file /path/key.pem \
  --reloadcmd &quot;systemctl restart 你的服务&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键是 reloadcmd 必须指向真正加载证书的那个服务，而不是想当然写个 nginx。&lt;/p&gt;
&lt;h2&gt;验证方法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;echo | openssl s_client -connect 你的域名:443 -servername 你的域名 2&amp;gt;/dev/null \
  | openssl x509 -noout -dates
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外建议在日历上设个提醒，每两个月看一眼线上证书的 &lt;code&gt;notAfter&lt;/code&gt;，别完全信任自动续期。&lt;/p&gt;
</content:encoded></item><item><title>用 View Transitions API 给页面切换加动画</title><link>https://oliver6868686.tech/posts/view-transitions/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/view-transitions/</guid><description>不引入任何动画库，几十行 CSS 加两行 JS 实现列表到详情页的过渡动画，旧浏览器自动降级。</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;给客户的后台加页面切换动画，以前要么上 framer-motion，要么手写 opacity 加 transform。这次直接用 View Transitions API，零依赖，旧浏览器优雅降级成瞬间切换，没有任何报错。&lt;/p&gt;
&lt;h2&gt;多页应用的用法&lt;/h2&gt;
&lt;p&gt;Chrome 126 起支持跨文档过渡，只要一行 CSS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@view-transition {
  navigation: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列表页和详情页各自声明过渡名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.item-image {
  view-transition-name: item-image;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器会自动截图新旧状态做交叉淡入淡出。想定制动画，覆盖两个伪元素：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;::view-transition-old(item-image),
::view-transition-new(item-image) {
  animation-duration: 0.25s;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;单页应用&lt;/h2&gt;
&lt;p&gt;SPA 里在改 DOM 前包一层：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.startViewTransition(() =&amp;gt; updateListUI());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器对函数执行前后各截一帧，中间的过程自动补间。&lt;/p&gt;
&lt;h2&gt;几个坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;view-transition-name&lt;/code&gt; 必须全页唯一，列表里循环渲染的元素不能共用一个名字，否则动画直接失效&lt;/li&gt;
&lt;li&gt;页面根元素默认有一个 &lt;code&gt;root&lt;/code&gt; 过渡名，不想要整页淡入淡出就把它禁掉&lt;/li&gt;
&lt;li&gt;Firefox 和 Safari 的跨文档过渡支持滞后，做好渐进增强的预期，别把核心交互押在动画上&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>小内存服务器上跑服务的几点经验</title><link>https://oliver6868686.tech/posts/small-vps-tips/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/small-vps-tips/</guid><description>1G 内存的机器上跑了四五个服务一年多没重启过，swap、systemd 内存限制、日志控制的一点流水账。</description><pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一台 1G 内存的小鸡，跑了 nginx、两个 Go 写的小服务、一个 Telegram bot 和一堆 cron，一年多没重启过，内存稳定在 60% 左右。几点流水账经验。&lt;/p&gt;
&lt;h2&gt;swap 要给，但别太大&lt;/h2&gt;
&lt;p&gt;给 1G swap 图个安心，但 swap 不是内存不够的解药，是 OOM 的缓冲垫。如果 &lt;code&gt;free&lt;/code&gt; 里 swap 用量持续上涨，说明该砍服务了，而不是加大 swap。&lt;/p&gt;
&lt;h2&gt;systemd 内存限制&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;[Service]
MemoryMax=200M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;给每个服务设上限，谁漏内存谁死，别连累整机。Go 服务尤其要留意 RSS，默认 GC 策略在小内存机器上会多吃不少。&lt;/p&gt;
&lt;h2&gt;日志是隐形杀手&lt;/h2&gt;
&lt;p&gt;journald 默认不限制磁盘占用，小机器上务必设 &lt;code&gt;SystemMaxUse=200M&lt;/code&gt;。nginx 日志记得 logrotate，access log 里全是扫描器流量的话，一个月能吃掉几个 G。&lt;/p&gt;
&lt;h2&gt;能不常驻就不常驻&lt;/h2&gt;
&lt;p&gt;很多&quot;服务&quot;其实一个 cron 脚本就能解决，cron 跑完就退，内存零占用。每次想装新东西前先问自己：它真的需要 7x24 在内存里呆着吗？&lt;/p&gt;
</content:encoded></item><item><title>原生 dialog 元素：模态框的正确打开方式</title><link>https://oliver6868686.tech/posts/dialog-element/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/dialog-element/</guid><description>自带焦点圈定、Esc 关闭、顶层渲染，一个 HTML 元素解决模态框的三大顽疾，再也不用装组件库。</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上周拆掉项目里一个 300 行的手写模态框组件，换成原生 &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; 元素，组件文件从 300 行缩到 40 行。这东西值得单独记一篇。&lt;/p&gt;
&lt;h2&gt;为什么它更好&lt;/h2&gt;
&lt;p&gt;手写模态框绕不开三个坑：焦点会跑到页面背后、Esc 关不掉、z-index 层层加码还是被浮层盖住。&lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; 把三个问题全部内建解决：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;showModal()&lt;/code&gt; 打开后焦点圈定在对话框内，Tab 键出不去&lt;/li&gt;
&lt;li&gt;Esc 自动触发 close 事件&lt;/li&gt;
&lt;li&gt;渲染在 top layer，永远盖在普通层叠上下文之上，不用管 z-index&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本用法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dialog id=&quot;confirm&quot;&amp;gt;
  &amp;lt;p&amp;gt;确认删除这条记录？&amp;lt;/p&amp;gt;
  &amp;lt;form method=&quot;dialog&quot;&amp;gt;
    &amp;lt;button value=&quot;cancel&quot;&amp;gt;取消&amp;lt;/button&amp;gt;
    &amp;lt;button value=&quot;ok&quot;&amp;gt;删除&amp;lt;/button&amp;gt;
  &amp;lt;/form&amp;gt;
&amp;lt;/dialog&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const dlg = document.querySelector(&apos;#confirm&apos;);
dlg.showModal();
dlg.addEventListener(&apos;close&apos;, () =&amp;gt; {
  if (dlg.returnValue === &apos;ok&apos;) doDelete();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;form method=&quot;dialog&quot;&lt;/code&gt; 会自动关闭对话框并把按钮的 &lt;code&gt;value&lt;/code&gt; 塞进 &lt;code&gt;returnValue&lt;/code&gt;，连事件监听都省了。&lt;/p&gt;
&lt;h2&gt;样式&lt;/h2&gt;
&lt;p&gt;遮罩用 &lt;code&gt;::backdrop&lt;/code&gt; 伪元素：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dialog::backdrop {
  background: rgb(0 0 0 / 40%);
  backdrop-filter: blur(2px);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开时有默认的居中定位，可以用 &lt;code&gt;margin: auto&lt;/code&gt; 微调。出场动画建议用 &lt;code&gt;transition&lt;/code&gt; 配合 &lt;code&gt;allow-discrete&lt;/code&gt;，比 JS 控制 class 干净得多。&lt;/p&gt;
&lt;p&gt;唯一要注意的：&lt;code&gt;show()&lt;/code&gt; 和 &lt;code&gt;showModal()&lt;/code&gt; 是两种东西，前者是普通 inline 弹层，没有遮罩也不圈定焦点，别调混了。&lt;/p&gt;
</content:encoded></item><item><title>nginx 日志分析组合拳：awk 管道的几个实战姿势</title><link>https://oliver6868686.tech/posts/nginx-log-analysis/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/nginx-log-analysis/</guid><description>sort、uniq、awk 三件套处理 access log，从攻击源到流量峰值，几条管道全搞定。</description><pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;接手一台流量异常的机器，没装任何监控，只有一份 2G 的 access log。十分钟后问题定位完毕，靠的是几条管道。这类命令每次现查文档太慢，整理在这里。&lt;/p&gt;
&lt;h2&gt;请求数 Top 10 的来源 IP&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;awk &apos;{print $1}&apos; access.log | sort | uniq -c | sort -rn | head
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条管道值得拆开记：&lt;code&gt;uniq -c&lt;/code&gt; 只统计相邻重复行，所以前面必须先 &lt;code&gt;sort&lt;/code&gt;；后面再 &lt;code&gt;sort -rn&lt;/code&gt; 按计数倒序。三段式是日志分析的万能起手式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/awk-pipeline.svg&quot; alt=&quot;awk 管道各阶段的数据形态：从原始日志行逐级演变成 Top 10 统计结果&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;状态码分布&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;awk &apos;{print $9}&apos; access.log | sort | uniq -c | sort -rn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4xx 占比突然升高，配合上一条基本能锁死是谁在扫。&lt;/p&gt;
&lt;h2&gt;每小时请求量&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;awk &apos;{print substr($4, 14, 2)}&apos; access.log | sort | uniq -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$4&lt;/code&gt; 是时间字段，&lt;code&gt;substr&lt;/code&gt; 掐出小时位。画在脑子里就是一张流量曲线，哪个小时被打了 Peak 一目了然。&lt;/p&gt;
&lt;h2&gt;找出响应最慢的请求&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;awk &apos;($NF &amp;gt; 2){print $NF, $7}&apos; access.log | sort -rn | head
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前提是 log_format 里把 &lt;code&gt;$request_time&lt;/code&gt; 放在最后一个字段。超过 2 秒的请求按耗时倒序，URL 跟在后面。&lt;/p&gt;
&lt;h2&gt;旋转过的压缩日志&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;zcat access.log.*.gz | awk &apos;{print $1}&apos; | sort | uniq -c | sort -rn | head
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;zcat&lt;/code&gt; 直接读 gzip，不用先解压落盘。排查上周的问题 logrotate 已经转走了，这条是救命用的。&lt;/p&gt;
&lt;p&gt;一点体会：管道思维的本质是把大问题拆成每条命令只做一件事。真到几十 G 的日志再上 Go 写的专用工具，日常这几条够用十年。&lt;/p&gt;
</content:encoded></item><item><title>jq：命令行里处理 JSON 的瑞士军刀</title><link>https://oliver6868686.tech/posts/jq-json-cli/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/jq-json-cli/</guid><description>curl 拿到一坨 JSON 之后的故事：过滤、统计、转表，jq 十几个表达式覆盖日常九成需求。</description><pubDate>Sat, 28 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;API 返回的 JSON 越来越肥，肉眼读不动，Python 起进程又嫌重。jq 编译成 C，处理几百 MB 的文件也是瞬间的事，值得把常用表达式背下来。&lt;/p&gt;
&lt;h2&gt;看结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;curl -s https://api.github.com/repos/vercel/next.js | jq &apos;. | keys&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到陌生接口先列 key，比翻文档快。&lt;/p&gt;
&lt;h2&gt;过滤和取值&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;jq &apos;.items[] | select(.stargazers_count &amp;gt; 1000) | .full_name&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.[]&lt;/code&gt; 展开数组，&lt;code&gt;select&lt;/code&gt; 按条件过滤，竖线串联。读法接近自然语言：取出每项，筛出星数过千的，留下全名。&lt;/p&gt;
&lt;h2&gt;统计&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;jq &apos;[.[] | select(.state == &quot;open&quot;)] | length&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中括号把过滤结果重新收集成数组，&lt;code&gt;length&lt;/code&gt; 计数。&lt;/p&gt;
&lt;h2&gt;改造结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;jq &apos;map({name: .full_name, stars: .stargazers_count})&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;map&lt;/code&gt; 加对象构造，输出干净得可以直接贴进报告。再配 &lt;code&gt;-r&lt;/code&gt; 去掉字符串引号，输出直接能进 shell 循环。&lt;/p&gt;
&lt;h2&gt;转成 TSV 交给 awk&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;jq -r &apos;.[] | [.name, .size, .default_branch] | @tsv&apos; | column -t
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@tsv&lt;/code&gt; 是关键一步，jq 的输出和文本工具的输入在这里接上头。JSON 世界和 Unix 世界本来隔着一层，这条管道把它们焊在一起。&lt;/p&gt;
&lt;h2&gt;一个省 finger 的技巧&lt;/h2&gt;
&lt;p&gt;交互式排查时用 &lt;code&gt;jq &apos;keys&apos;&lt;/code&gt; 比翻官方文档 schema 快得多，接口字段都摆在明处。写脚本时再把确定下来的表达式固化进去，避免盲猜字段名。&lt;/p&gt;
</content:encoded></item><item><title>把家里的 DNS 换成 AdGuard Home 之后</title><link>https://oliver6868686.tech/posts/adguard-home/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/adguard-home/</guid><description>折腾了一晚上 AdGuard Home，家里所有设备的广告少了一大半。部署方式、上游 DNS 选择和几个坑。</description><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;家里路由器原厂固件的 DNS 比较弱，广告过滤全靠各设备上的客户端插件，维护成本高。周末把一台吃灰的树莓派翻出来装了 AdGuard Home，全家设备统一走它。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/adguard-dns-flow.svg&quot; alt=&quot;家里全部设备的 DNS 查询都先经过树莓派上的 AdGuard Home，广告域名在本地拦截，正常域名走 DoH 加密上游&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;部署&lt;/h2&gt;
&lt;p&gt;Docker 一条命令起服务，端口 53 记得别被 systemd-resolved 占了。路由器 DHCP 里把 DNS 指到树莓派的内网 IP，所有设备无感切换。&lt;/p&gt;
&lt;h2&gt;上游怎么选&lt;/h2&gt;
&lt;p&gt;试了几个组合，最后稳定在 DoH 上游 + 并发查询。国内环境直连 8.8.8.8 的 53 端口体验很差，DoH 虽然稍微慢一点但胜在稳定。AdGuard Home 支持&quot;最快响应优先&quot;，把几个上游都填上让它自己选。&lt;/p&gt;
&lt;h2&gt;效果&lt;/h2&gt;
&lt;p&gt;常用 App 的开屏广告少了一大半，页面加载明显干净。拦截统计里最狠的一天挡了三千多条请求，都是那几个视频 App 发的。&lt;/p&gt;
&lt;h2&gt;踩的坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;树莓派 SD 卡写日志容易写坏，日志等级调低或者干脆丢内存盘&lt;/li&gt;
&lt;li&gt;某些国产 App 检测到 DNS 拿不到广告域名会硬切自家 HTTP DNS，这种就没辙了，只能靠客户端规则补&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>磁盘又满了：df 和 du 对不上的排查思路</title><link>https://oliver6868686.tech/posts/disk-full-troubleshooting/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/disk-full-troubleshooting/</guid><description>df 显示 100% 但 du 找不到大文件，八成是已删除文件还被进程攥着。完整排查路径记录。</description><pubDate>Sun, 14 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;凌晨收到磁盘告警，登上去 &lt;code&gt;df -h&lt;/code&gt; 显示 97%，&lt;code&gt;du -sh /*&lt;/code&gt; 从根目录一路扫下来却对不上账，所有目录加起来少了一半。这种 df 和 du 对不上的场景，八成是同一个原因。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/disk-full-flow.svg&quot; alt=&quot;磁盘占满的排查路径：先 du 逐层找大文件，找得到就直接清理；对不上账则走 lsof 找已删除句柄的支线&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;标准排查路径&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;df -h
du -xh --max-depth=1 / 2&amp;gt;/dev/null | sort -rh | head
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-x&lt;/code&gt; 限定单文件系统，跳过挂载点，不然 du 会把别的盘也算进来。逐层往下钻，先定位到目录级。&lt;/p&gt;
&lt;h2&gt;df 和 du 对不上：找已删除文件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;lsof +L1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条列出所有「文件已删除但句柄还开着」的进程。Unix 的删除语义是解链接，只要还有进程攥着文件句柄，空间就不会释放，&lt;code&gt;df&lt;/code&gt; 统计的是这块空间，&lt;code&gt;du&lt;/code&gt; 看不见它。&lt;/p&gt;
&lt;p&gt;日志进程是惯犯：日志被 logrotate 挪走或被人手动 &lt;code&gt;rm&lt;/code&gt;，进程还按老句柄继续写。解决分两步：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 清空内容释放空间，进程无感
&amp;gt; /proc/23842/fd/4

# 根治：重启或 reload 对应服务，让它重新打开文件
systemctl reload app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意顺序：先 reload 再删文件是正解。先 &lt;code&gt;rm&lt;/code&gt; 再以为完事，空间根本不会回来，下次告警还是它。&lt;/p&gt;
&lt;h2&gt;常见的三个藏污点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/var/log/journal&lt;/code&gt;：&lt;code&gt;journalctl --vacuum-size=200M&lt;/code&gt; 直接约束&lt;/li&gt;
&lt;li&gt;Docker：&lt;code&gt;docker system prune -a --volumes&lt;/code&gt; 一把梭之前，先 &lt;code&gt;docker system df&lt;/code&gt; 看看都占在哪&lt;/li&gt;
&lt;li&gt;邮件队列和 core dump：&lt;code&gt;/var/spool&lt;/code&gt; 和 &lt;code&gt;/var/crash&lt;/code&gt; 值得单查&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;预防&lt;/h2&gt;
&lt;p&gt;在这类机器上放一个 cron：&lt;code&gt;df --output=pcent / | tail -1&lt;/code&gt; 超过 85 就告警。等用户喊着「磁盘满了」再登上去，排查窗口已经被挤没了。&lt;/p&gt;
</content:encoded></item><item><title>SSH 改造：密钥登录 + fail2ban</title><link>https://oliver6868686.tech/posts/ssh-hardening/</link><guid isPermaLink="true">https://oliver6868686.tech/posts/ssh-hardening/</guid><description>看了眼 auth.log，爆破尝试一分钟好几条。密钥登录、换端口、fail2ban 三件套的记录。</description><pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;新开一台 VPS，装完服务第一件事看 auth.log，好家伙，上线两小时爆破尝试已经刷了几百条，全冲着 root 来的。顺手把老三样做了个记录。&lt;/p&gt;
&lt;h2&gt;密钥登录&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen -t ed25519
ssh-copy-id root@服务器
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确认密钥能登之后，&lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 里改两处：&lt;code&gt;PasswordAuthentication no&lt;/code&gt;、&lt;code&gt;PermitRootLogin prohibit-password&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;:::caution
改完别断当前会话，开个新终端验证能登再退出，否则容易被自己锁在门外。
:::&lt;/p&gt;
&lt;h2&gt;fail2ban&lt;/h2&gt;
&lt;p&gt;默认的 sshd jail 就够用，maxretry 调到 3，bantime 拉长到一天。装完看下 &lt;code&gt;fail2ban-client status sshd&lt;/code&gt;，当天就能看到 ban 列表在涨。&lt;/p&gt;
&lt;h2&gt;换端口值不值&lt;/h2&gt;
&lt;p&gt;改个非标准端口能挡掉九成无脑扫描，日志清净很多，但安全性上是防君子不防小人——真定向扫描你照样暴露。我的结论是：改，主要为了日志干净，不是当安全手段。&lt;/p&gt;
</content:encoded></item></channel></rss>