<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>家庭实验室 on 魔都水滴</title>
    <link>https://blog.margrop.net/tag/%E5%AE%B6%E5%BA%AD%E5%AE%9E%E9%AA%8C%E5%AE%A4/</link>
    <description>Recent content in 家庭实验室 on 魔都水滴</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Fri, 19 Jun 2026 10:58:00 +0800</lastBuildDate>
    <atom:link href="https://blog.margrop.net/tag/%E5%AE%B6%E5%BA%AD%E5%AE%9E%E9%AA%8C%E5%AE%A4/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Ubuntu 关机要等 90 秒?Python asyncio 服务不肯接 SIGTERM 的排查与修复</title>
      <link>https://blog.margrop.net/post/ubuntu-shutdown-stuck/</link>
      <pubDate>Fri, 19 Jun 2026 10:58:00 +0800</pubDate>
      <guid>https://blog.margrop.net/post/ubuntu-shutdown-stuck/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;先说结论&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;这台升级到 24.04 的代理机,关机变成了一件折磨人的事:&lt;code&gt;systemctl reboot&lt;/code&gt; 之后,SSH 不掉、灯不灭,要干等 &lt;strong&gt;90 秒&lt;/strong&gt; 才进入真正的 shutdown 流程。journalctl 里很明确:&lt;code&gt;smart-proxy.service: State &#39;stop-sigterm&#39; timed out. Killing.&lt;/code&gt;——systemd 给了 SIGTERM,等 90 秒没人响应,只能 SIGKILL 强杀。&lt;/p&gt;&#xA;&lt;p&gt;根因不是 systemd 的错,也不是 Ubuntu 的错,是 &lt;strong&gt;Python 的 asyncio 服务在收到 SIGTERM 后,没有真正去结束它的子任务&lt;/strong&gt;——它正卡在某个 &lt;code&gt;socket.recv&lt;/code&gt; 上,Python 解释器根本不主动去看&amp;quot;有人叫我走&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;修复分两步,缺一不可:&lt;strong&gt;(1)&lt;/strong&gt; 给两个 Python unit 写 systemd drop-in,把 &lt;code&gt;TimeoutStopSec&lt;/code&gt; 从默认的 90s 缩到 20s;&lt;strong&gt;(2)&lt;/strong&gt; 在 Python 服务里主动遍历 &lt;code&gt;asyncio.all_tasks()&lt;/code&gt;,收到 SIGTERM 后把所有 in-flight 的 handler &lt;code&gt;cancel()&lt;/code&gt; 掉,再用 &lt;code&gt;asyncio.wait_for(self.stop(), timeout=10)&lt;/code&gt; 包一层做兜底。改完之后,同样一次 &lt;code&gt;reboot&lt;/code&gt;,从 90 秒掉到 1 秒。&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>一句话让 Claude 把 Ubuntu 22.04 升级到 24.04:一次几乎不用盯屏幕的跨 LTS 实战</title>
      <link>https://blog.margrop.net/post/upgrade-ubuntu-via-agent/</link>
      <pubDate>Fri, 19 Jun 2026 10:58:00 +0800</pubDate>
      <guid>https://blog.margrop.net/post/upgrade-ubuntu-via-agent/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;先说结论&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;这台远端机器是家里那台 24 小时开机的代理机,系统一直停在 Ubuntu 22.04.4 LTS(jammy),内核 5.15。一句&amp;quot;帮我把 192.168.103.182 升级到 ubuntu24.04&amp;quot;,Claude 就替我拆开了旧柜子,在另一个房间装上了新柜子,过程中它自己开了 tmux 守夜、自己起了 fallback sshd 备胎、自己用 &lt;code&gt;do-release-upgrade -f DistUpgradeViewNonInteractive&lt;/code&gt; 跑完了整段流水线。25 分钟后主机回来,内核已经是 6.8.0-124,所有服务依旧在听。&lt;/p&gt;&#xA;&lt;p&gt;本文不是讲 do-release-upgrade 怎么用,那是 Ubuntu 官方文档的事;本文是讲&amp;quot;当 Agent 拿到 SSH 之后,它在做什么、为什么这么做、以及哪些坑你必须提前排&amp;quot;。&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>群晖把我的 root 锁在门外三道，我花了二十分钟把它教会</title>
      <link>https://blog.margrop.net/post/synology-root-and-ssh-key/</link>
      <pubDate>Thu, 18 Jun 2026 16:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/post/synology-root-and-ssh-key/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR（先说结论）&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;群晖（Synology DSM）为了安全，默认把 SSH 关掉、还禁止 root 直接登录——这是好事，别骂它。这篇文章就是带你&amp;quot;按规矩&amp;quot;把门打开：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;控制面板 → 终端机和 SNMP → 启用 SSH&lt;/strong&gt;（把大门打开）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;用普通账户登录 + &lt;code&gt;sudo -i&lt;/code&gt; 切到 root&lt;/strong&gt;（先去物业借钥匙）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;修改 &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 里 &lt;code&gt;PermitRootLogin yes&lt;/code&gt;&lt;/strong&gt;（跟门卫说&amp;quot;root 是自己人&amp;quot;）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;synouser --setpw root xxx&lt;/code&gt; 设密码 + 把公钥写到 &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;&lt;/strong&gt;（换一把永远不会丢的钥匙）&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;全程大约二十分钟，比想象简单。我会把每一步都用&amp;quot;钥匙和锁&amp;quot;的比喻讲清楚，再附上 vi 编辑器三秒钟入门、常见报错和 Q&amp;amp;A。&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>我把 AnythingLLM 装进 Docker 之后，容器像一只上紧发条的小松鼠——反复重启直到我把那个 1000:1000 给它</title>
      <link>https://blog.margrop.net/post/anythingllm-docker-deploy/</link>
      <pubDate>Wed, 17 Jun 2026 20:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/post/anythingllm-docker-deploy/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR（先说结论）&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;AnythingLLM 这只&amp;quot;啥都能塞进去的 LLM 口袋书&amp;quot;官方就有 Docker 镜像，正常情况下 &lt;code&gt;docker run&lt;/code&gt; 一行就能起；但它里面那位 &lt;code&gt;anythingllm&lt;/code&gt; 用户很挑剔——挂给它的宿主机目录必须属于 &lt;code&gt;1000:1000&lt;/code&gt;，否则它写不动自己的 SQLite 数据库，Prisma 启动迁移会直接挂掉，容器就会进入&amp;quot;重启—挂掉—再重启&amp;quot;的死循环。修起来不到一分钟：&lt;code&gt;chown -R 1000:1000 /你的数据目录&lt;/code&gt;，再 &lt;code&gt;docker compose up -d&lt;/code&gt; 一次，它就乖乖听 &lt;code&gt;3001&lt;/code&gt; 了。&lt;/p&gt;&#xA;&lt;p&gt;本文顺道把&amp;quot;Prisma 那个 &lt;code&gt;file:../storage/anythingllm.db&lt;/code&gt; 相对路径为什么能坑死人&amp;quot;讲清楚，最后附上 Portainer stack 写法、几条常见坑、以及中英两个 demo 地址。&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>PhotoPrism 启动 5 分钟还在装系统包？一次 PHOTOPRISM_INIT=intel 的踩坑与正确配置</title>
      <link>https://blog.margrop.net/post/photoprism-init-intel-stuck/</link>
      <pubDate>Sat, 13 Jun 2026 08:30:00 +0800</pubDate>
      <guid>https://blog.margrop.net/post/photoprism-init-intel-stuck/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR（先说结论）&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;PhotoPrism Plus 镜像的 &lt;code&gt;docker-compose.yml&lt;/code&gt; 里有一行 &lt;code&gt;PHOTOPRISM_INIT: &amp;quot;intel&amp;quot;&lt;/code&gt;，看似只是给 Intel 核显开加速，实际上它在容器&lt;strong&gt;第一次启动&lt;/strong&gt;时会去 &lt;code&gt;archive.ubuntu.com&lt;/code&gt; 跑一次完整的 &lt;code&gt;apt-get dist-upgrade&lt;/code&gt;，再装 7 个 GPU / VA-API 包。这套流程 &lt;strong&gt;5–10 分钟起步&lt;/strong&gt;，整个期间容器内部没有任何进程在监听 &lt;code&gt;2342&lt;/code&gt; 端口。你的 &lt;code&gt;docker ps&lt;/code&gt; 显示 &lt;code&gt;Up&lt;/code&gt;、Portainer 一切绿、&lt;code&gt;ss -ltn&lt;/code&gt; 也看到 &lt;code&gt;0.0.0.0:2342&lt;/code&gt; 在 LISTEN——但浏览器一访问就是 &lt;strong&gt;ERR_CONNECTION_RESET / Connection reset by peer&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;解法非常简单：&lt;strong&gt;删掉或清空那一行&lt;/strong&gt;。你 &lt;code&gt;devices&lt;/code&gt; 里已经挂上 &lt;code&gt;/dev/dri/renderD128&lt;/code&gt; 直通，驱动和 VA-API 用户态工具由宿主机提供，容器里再装一遍是纯浪费。&lt;/p&gt;&#xA;&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>域名买完别只会解析：用 Cloudflare Tunnel 把内网服务变成公网 HTTPS</title>
      <link>https://blog.margrop.net/post/cloudflare-tunnel-public-https-no-public-ip/</link>
      <pubDate>Thu, 21 May 2026 11:38:00 +0800</pubDate>
      <guid>https://blog.margrop.net/post/cloudflare-tunnel-public-https-no-public-ip/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;先说结论&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;前两篇已经讲了两件事：先用低成本 &lt;code&gt;.xyz&lt;/code&gt; 域名把个人实验室的入口成本压下来，再把域名交给 Cloudflare 做 NS、DNS、DDNS、邮箱转发和基础自动化。本文继续往下走，讲“域名玩法 5”：用 Cloudflare Tunnel 把家里、NAS、虚拟机、Docker 容器里的 Web 服务发布成公网 &lt;code&gt;https://&lt;/code&gt; 地址。重点是：&lt;strong&gt;外部访问走 443 HTTPS，家里路由器不需要开 80/443 端口，也不需要把源站公网 IP 暴露出去&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;如果你前面已经买好了便宜域名，并且域名 NS 已经切到 Cloudflare，那么 Tunnel 就是非常值得继续折腾的一步。它能把 &lt;code&gt;http://localhost:8080&lt;/code&gt;、&lt;code&gt;http://192.0.2.10:5000&lt;/code&gt; 这类内网服务，变成 &lt;code&gt;https://nas.speedtest.example.xyz&lt;/code&gt; 这样的公网入口。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;本文是这两篇文章的后续：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;《80 块买十年：个人建站域名，为什么我会优先看 6 位数字 .xyz》&lt;br&gt;&#xA;原文地址：&lt;a href=&#34;https://mp.weixin.qq.com/s/tbefnWGFI0QBFlRVcYjVEw&#34;&gt;https://mp.weixin.qq.com/s/tbefnWGFI0QBFlRVcYjVEw&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;《80元十年域名别浪费：交给 Cloudflare 才算真正用起来》&lt;br&gt;&#xA;原文地址：&lt;a href=&#34;https://mp.weixin.qq.com/s/h0o-vtGB_zj1aptaumBztQ&#34;&gt;https://mp.weixin.qq.com/s/h0o-vtGB_zj1aptaumBztQ&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;前文已经讲了便宜 &lt;code&gt;.xyz&lt;/code&gt; 域名、Cloudflare NS、DNS、DDNS、企业邮箱这些基础配置。本文不再重复“怎么把域名接入 Cloudflare”，而是假设你已经有一个在 Cloudflare 托管的域名，然后继续讲：如何用 Cloudflare Tunnel 给内网服务开一个公网 HTTPS 入口。&lt;/p&gt;&#xA;&lt;p&gt;本文所有示例都使用 &lt;code&gt;example.xyz&lt;/code&gt;、&lt;code&gt;nas.speedtest.example.xyz&lt;/code&gt;、&lt;code&gt;192.0.2.10&lt;/code&gt;、&lt;code&gt;localhost&lt;/code&gt;、&lt;code&gt;&amp;lt;TUNNEL_TOKEN&amp;gt;&lt;/code&gt; 这类文档占位符，不包含真实域名、真实账号、真实 Token、真实 Zone ID、真实内网地址或任何私有信息。你照着做时，把占位符替换成自己的域名和服务地址即可。&lt;/p&gt;</description>
    </item>
    <item>
      <title>80 块买十年：个人建站域名，为什么我会优先看 6 位数字 .xyz</title>
      <link>https://blog.margrop.net/post/low-cost-xyz-domain-for-personal-labs/</link>
      <pubDate>Tue, 12 May 2026 19:10:00 +0800</pubDate>
      <guid>https://blog.margrop.net/post/low-cost-xyz-domain-for-personal-labs/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;先说结论&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;如果你的主要用途是家庭自用、个人测试、临时 Demo、本地服务外部回调、开发环境验证，而不是企业官网、正式品牌、长期公开产品，那么我会优先推荐低成本 &lt;code&gt;.xyz&lt;/code&gt; 域名，尤其是国内平台上经常能看到的 6 位数以上纯数字 &lt;code&gt;.xyz&lt;/code&gt;。以腾讯云域名注册页面实际结算价为准，某些 6 位数以上纯数字 &lt;code&gt;.xyz&lt;/code&gt; 组合可以做到首年和续费都很低，按 8 元/年理解，一次买 10 年也就是 80 元。这不是“最体面”的域名方案，但对个人实验室来说，它往往是最不折腾、最便宜、也最够用的方案。&lt;/p&gt;&#xA;&lt;p&gt;这篇文章只讨论非正式建站和个人用途。公司品牌、商业项目、公开产品、需要长期传播的站点，不建议只因为便宜就选一串数字域名。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;很多人第一次折腾个人站点、家庭实验室、NAS 回调、Webhook、临时 API、测试面板时，都会卡在一个很不起眼的问题上：到底要不要买域名，买什么域名。&lt;/p&gt;&#xA;&lt;p&gt;我自己的建议很简单：如果这是一个严肃品牌，域名应该认真挑；如果只是个人自用和测试环境，别把太多钱和精力浪费在名字上。域名在这类场景里的价值不是“好听”，而是给服务一个稳定入口，让 DNS、HTTPS、反向代理、证书续期、第三方回调、远程访问这些事情变得顺手。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
