Archive of

Lost the SSH public key? Derive it from the private key

An SSH .pub file is only the public half of a key pair. If it is missing, or if it is an old file that no longer matches the private key, regenerate it from the private key:

cp ~/.ssh/id_rsa.pub ~/.ssh/id_rsa.pub.backup 2>/dev/null || true
ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
chmod 644 ~/.ssh/id_rsa.pub

ssh-keygen -y derives and prints the public key without exposing the private key. This also fixes errors such as:

identity_sign: private key ... contents do not match public

Verify the resulting public-key fingerprint with:

ssh-keygen -lf ~/.ssh/id_rsa.pub

diffwalk is better as a conversation than a lecture

diffwalk's marketed flow is one-shot: your coding agent captures the diff, writes the entire walkthrough, and hands you a rendered review to read. That works for sharing, but when the goal is understanding what the agent just changed, a finished lecture is the wrong shape — you read passively, and the agent's framing never gets challenged.

Two changes made it click for me.

Publish is not part of the loop. diffwalk publish only mints a share link. For your own comprehension, diffwalk view (loopback-only) is already the end of the pipeline — and often you don't need the browser at all:

diffwalk changes            # list captured blocks
diffwalk change change-009  # read one block in the terminal

Interleave instead of pre-authoring. Instead of letting the agent write all of explanations.yaml up front, go one block at a time: the agent explains the block and gives its own take (correctness, risks, simpler alternatives), then stops and waits. I answer with mine. We decide jointly whether the block deserves a GitHub comment — the draft is shown in full and posted only after I confirm the exact text. Then, next block.

The agreed explanation for each block still lands in explanations.yaml as the loop runs, so a final diffwalk check doubles as "did we actually cover every change". The review conversation produces the walkthrough document as a side effect, instead of the document replacing the conversation.

One implementation note: I didn't edit diffwalk's own SKILL.md — it ships through npm and an update would clobber the edit. The pair-review loop lives in a separate custom skill beside it.

我的笔记现在会自动发到 X —— 靠一个只读 Redis 旁观者

我有一个手动发推的 CLI(Playwright 驱动登录状态的 Firefox 网页,所以不需要 X API key),还有一个多用户笔记应用(HappyNotes)把新笔记推入 Redis 队列同步到 Mastodon。想让它俩连起来:笔记一发,自动发推。但不想动共享后端、毕竟这是一个很私人的hack,也没法子支持其他用户。

演进三步

1. CLI → 微服务。 把发推核心抽成库,Node 内置 http 起一个服务,只监听 Tailscale IP(100.x.y.z:8090)——内网即鉴权,零依赖、零鉴权代码。任何 tailnet 机器都能入队:

curl -X POST http://100.x.y.z:8090/add \
  -H 'content-type: application/json' \
  -d '{"text":"hello","post_at":"2026-09-07T09:00:00+12:00"}'

2. 图片走 data URL。 别的机器没有你这台的文件系统,multipart 又要新依赖——所以图片在 JSON body 里以 data:image/png;base64,... 传输,Playwright setInputFiles 喂内存 buffer,最多 4 张。

3. 只读 Redis 旁观者。 这是关键设计:不 LPOP(会偷走 Mastodon 消费者的消息),只每秒 LRANGE queue + ZRANGEBYSCORE processing,用 redis-cli 子进程(还是零新依赖)。然后三道过滤:

userId == OWNER_USER_ID  &&  action == "CREATE"  &&  isPrivate == false

task.id 落盘去重(先写 seen 再发布,失败绝不重试——重试 = 重复推文),冷启动基线首轮只记已见不发布,防止积压刷屏。

代价:一个有意的竞态窗口

队列项最多活 5 秒(消费者 LPOP 后成功后 ZREM,删了就没了,没有 deleted 队列可拣漏)。所以旁观者"几乎不漏但不保证"——如果任务在两次轮询之间被消费完,就错过了。要修复的话要动HappyNotes后端(加一个 synced 队列,成功时 LPUSH + 1 小时 TTL),但只为我一个人需求改全站代码不太值,先接受这个妥协看看。