3364 words
17 minutes
Arisaka.ETH 博客简介
2026-09-29
Blog
Blog

起因#

最开始的 Arisaka.ETH 其实就是一个普通的个人博客。
基于 Fuwari 修改,使用 Astro 构建静态页面。

后来陆续加上了 Arweave、ENS、文章加密、Live2D、MIDI、Telegram Moment、CTF、MoeQ、AI 聊天、在线写作等功能,单纯把所有东西继续塞在博客仓库里已经不太合适。

因此现在项目主要拆成两个仓库:

简单来说就是:

arisaka.eth
↓
博客 / 内容 / 前端
↓
Cloudflare Workers
↓
D1 / R2 / Telegram / GitHub / AI / Ethereum

静态的东西尽量保持静态,需要实时处理的东西全部拆到 Worker。

arisaka.eth 主站#

主站目前使用 Astro 5 + Svelte,博客的文章、About、友链、Moment 页面以及大部分前端交互都放在这里。

主要技术栈:

  • Astro / Svelte
  • TypeScript
  • Tailwind CSS
  • Swup
  • Pagefind
  • Arweave
  • ENS
  • GitHub Actions

文章依然以 Markdown 为主:

Markdown
↓
Astro
↓
dist/

不过生成 dist/ 后,并不是直接上传到普通静态托管。

Arweave + ENS#

博客目前主要通过 Arweave 永久发布,再由 ENS 指向当前版本。

发布流程大概是:

Markdown
↓
Astro Build
↓
dist/
↓
Arweave
↓
Path Manifest
↓
ENS contenthash
↓
arisaka.eth

构建完成后会对文件计算 SHA-256。

如果一个文件的内容之前已经上传过,就直接复用原来的 Arweave Transaction,不需要每次发布都重新上传整个博客。

也就是:

旧文件
→ 复用原 Transaction
修改过的文件
→ 上传新的 Transaction

最终再生成新的 Path Manifest。

Manifest 可以理解成当前博客版本的文件表:

/
→ index.html
/posts/example/
→ posts/example/index.html
/_entry/example.js
→ 对应的 Arweave Transaction

然后将 Manifest 写入 arisaka.eth 的 ENS contenthash。

因此实际上访问链路是:

arisaka.eth
↓
ENS
↓
Arweave Manifest
↓
具体页面文件

相比每次整个站点重新上传,这种方式能少传不少重复资源。

发布验证#

Arweave 返回 Transaction ID 不代表公共网关一定已经可以立即读取。

所以在更新 ENS 之前,还会先验证新版本。

主要检查:

  • Manifest 是否可以读取。
  • 页面和静态资源是否可以读取。
  • 文件大小是否正确。
  • SHA-256 是否一致。
  • MIME 类型是否正确。
  • 页面路由是否正常。

流程大概是:

上传 Arweave
↓
生成 Manifest
↓
公网验证
↓
全部通过
↓
更新 ENS

如果验证失败,就不会切换 ENS。

旧版本仍然继续工作。

这样至少不会因为新版本刚上传但网关还没有同步完成,直接把线上站点切到一个不可访问的版本。

ENS Gas#

更新 ENS 本质上还是一笔 Ethereum 交易,自然也要考虑 Gas。

平时博客更新并没有必要为了立即上线,在 Base Fee 很高的时候硬发一笔交易。

因此部署流程设置了 Base Fee 上限。

Arweave 上传完成
↓
检查 Base Fee
↓
费用较低
↓
更新 ENS

如果费用超过设置值,则:

Arweave 上传完成
↓
暂不更新 ENS
↓
release-watch
↓
继续等待
↓
费用满足条件
↓
激活新版本

也就是把“上传”和“上线”拆成两个阶段。

Arweave 上的新版本可以已经准备完成,但 ENS 仍然保持旧版本。

GitHub Actions#

上面的发布过程主要通过 GitHub Actions 自动完成。

现在一次完整发布涉及:

  • 安装依赖。
  • Astro 检查。
  • TypeScript 检查。
  • 构建博客。
  • 恢复 Arweave 上传缓存。
  • 对比文件 SHA-256。
  • 增量上传文件。
  • 生成 Manifest。
  • 验证公网 Arweave 内容。
  • 检查 Ethereum Base Fee。
  • 更新 ENS。
  • 保存上传缓存和 outbox。

因此现在 GitHub Actions 已经不只是简单的:

pnpm build

而更接近完整的发布流水线。

文章加密#

博客支持直接发布加密文章。

没有采用密码直接加密正文的方式,而是拆成 KEK / DEK 两层。

密码
↓
scrypt
↓
KEK
↓
AES-256-GCM
↓
DEK
↓
AES-256-GCM
↓
正文 HTML

其中:

  • KEK:由用户密码通过 scrypt 派生。
  • DEK:每次构建随机生成。
  • KEK 只负责加密 DEK。
  • DEK 才真正负责加密文章正文。

这样密码派生和正文加密是分开的。

加密文章的正文不会进入:

  • 首页摘要。
  • RSS 正文。
  • Pagefind 搜索。
  • 初始文章目录。

用户输入密码后才会在浏览器内完成解密。

页面切换时已经解密的 DOM 也会清理。

当然,这种设计依然不能解决弱密码被离线爆破的问题,所以真要使用还是得设置高强度密码。

Provenance#

既然文章经过:

ENS
↓
Manifest
↓
Arweave File

那么也可以反过来查询当前看到的页面到底来自哪次发布。

因此文章底部加入了 Provenance 信息。

大概会显示:

ENS contenthash
↓
Arweave Manifest TX
↓
文章 File TX
↓
Content Digest
↓
App Version

主要就是方便确认:

当前网页到底对应哪一个 Arweave Transaction 和哪一次博客发布。

并不是什么完整的密码学证明,只是把原本隐藏在发布流程里的信息展示出来。

Live2D#

博客桌面端还放了一个 Live2D Roro。

最开始只是单纯的桌宠,后面又给它加上了聊天和文章话题功能。

AI 请求不会直接从浏览器调用模型 API,而是:

Browser
↓
live2d-chat Worker
↓
AI API

API Key 只保存到 Worker Secret。

Worker 负责请求检查、额度限制和模型调用。

在公开文章中,还可以根据文章内容生成相关话题。

首页、About、Friends 等非文章页面则主要播放动漫语录。

所以 Roro 现在已经不只是页面装饰,也算一个独立的交互入口了。

MIDI#

侧栏播放器使用 SpessaSynth 在浏览器直接合成 MIDI。

支持:

  • .mid
  • .midi
  • .rmi
  • 顺序播放
  • 随机播放
  • 单曲循环
  • 播放列表
  • 进度拖动

合成器、AudioWorklet 和 SoundFont 都会等用户真正开始播放以后再加载,没必要刚打开博客就全部下载。

虚拟终端#

Live2D 菜单里还能打开一个虚拟终端。

终端不是简单打印几行固定文字,而是做了基本的命令解析、历史记录、Tab 补全和虚拟文件系统。

例如:

Terminal window
ls
cd
pwd
cat
open
whoami
uname
date
neofetch
history
clear
exit

里面的部分文件直接来源于博客真实内容。

例如:

~/README/
~/posts/
~/about.md

open 还可以直接打开博客文章。

Moment#

Moment 是后来加的一个 Telegram 动态页面。

主要展示 @azusabot0 中的:

  • 文字。
  • 图片。
  • 视频封面。
  • 原 Telegram 帖子链接。

最开始很容易想到直接用 GitHub Actions 定时同步:

Telegram
↓
GitHub Actions
↓
生成数据
↓
重新构建博客

但这样最大的问题就是:

发一条 Telegram,就得把整个博客重新构建和发布一次。

感觉多少有点抽象。

所以后来把同步完全拆到了 arisaka.eth-workers。

现在变成:

Telegram
↓
Moment Worker
↓
D1 + R2
↓
moment.000.moe
↓
博客 Moment 页面

帖子信息保存到 D1。

图片和视频封面保存到 R2。

博客只请求:

https://moment.000.moe/api/posts

然后在浏览器里动态显示。

这样 Telegram 新增一条动态和博客构建就完全没关系了。

arisaka.eth-workers#

随着动态服务越来越多,最后将 Cloudflare Workers 单独拆到了:

TomorrowX6/arisaka.eth-workers

它现在更像整个博客的后端 Monorepo。

目前主要包括:

blog-proxy/
ctf-game/
live2d-chat/
moment/
release-watch/
waline/
randimg/
writer/
docs/

每个 Worker 都有自己的依赖、配置和部署流程。

blog-proxy#

blog-proxy 负责把 Arweave 上的博客通过:

000.moe
www.000.moe

提供出来。

Worker 会读取 arisaka.eth 当前的 ENS contenthash,找到对应的 Arweave Manifest。

不过不会让每一个访问请求都实时查询 Ethereum RPC。

ENS 解析结果会缓存在 Cloudflare Edge,缓存过期后再后台刷新。

大概就是:

用户
↓
000.moe
↓
Cloudflare Worker
↓
缓存的 ENS Manifest
↓
Arweave

这样 ENS 仍然是博客版本的来源,但不会直接卡在每一个用户请求的关键路径上。

moment#

moment 负责 Telegram 频道同步。

Worker 定时读取 Telegram getUpdates:

Telegram
↓
Worker
├─ D1
└─ R2
↓
Moment API

D1 保存帖子和同步状态。

R2 保存图片以及视频封面。

主站只负责展示。

live2d-chat#

live2d-chat 主要负责 Roro 聊天以及相关 AI 功能。

包括:

  • AI 对话。
  • 文章话题生成。
  • 话题缓存。
  • 请求来源检查。
  • 调用额度限制。
  • MoeQ 桌宠相关接口。

模型 API Key 全部放在 Worker 后端。

release-watch#

release-watch 主要负责博客版本激活和状态监控。

当一次部署已经把内容上传到 Arweave,但因为:

  • Arweave 网关尚未完全可读。
  • Ethereum Base Fee 太高。

暂时不能更新 ENS 时,就由它继续跟踪。

新版本
↓
Arweave 已上传
↓
等待可用
↓
等待低 Gas
↓
触发激活
↓
ENS 更新

同时也会对相关站点进行监控。

waline#

评论系统也迁到了 Worker。

前端仍然使用 Waline 客户端,但后端数据保存在 D1。

Blog
↓
Waline
↓
Worker
↓
D1

这样评论功能就不再需要单独维护传统服务器。

randimg#

随机图片服务以前使用:

FastAPI
+
nginx
+
SQLite

现在已经迁移成:

Cloudflare Worker
+
R2
+
D1

WebP 图片保存到 R2。

图片索引保存到 D1。

并且旧的图片 ID 和 URL 结构继续保持兼容,所以博客里已有的链接不需要因为迁移重新修改。

Writer#

writer 用来解决写博客的问题。

目前同时为 Android Writer 和 Web Writer 提供后端。

登录使用 GitHub App。

编辑完成后可以直接把 Markdown 提交到:

TomorrowX6/arisaka.eth

的 main 分支。

整个过程就变成了:

Writer
↓
GitHub
↓
arisaka.eth
↓
GitHub Actions
↓
Astro
↓
Arweave
↓
ENS

也就是说,不一定非要在电脑上:

Terminal window
git clone
vim
git add
git commit
git push

才能更新文章。

在 Writer 里写完也可以直接发布。

Web Writer 本身还包括文章列表、Markdown 编辑、实时预览、标题大纲、字数统计、命令面板等功能。

CTF#

CTF 后来基本已经从博客本体独立出来了。

博客中主要留下入口,而实际游戏运行在 ctf-game 中。

包括:

  • Plasma 风格浏览器桌面。
  • CTF 关卡。
  • 运行时 Worker。
  • 存档。
  • 自动化测试。
  • 部署流程。

主站负责:

展示入口

Worker 负责:

真正运行

MoeQ#

MoeQ 也放在 ctf-game 相关服务中。

包含题库、媒体处理和运行时逻辑。

目前题目不再只是单纯的文字选择题,还涉及角色、声优、音乐、语录、图片等不同类型。

题库及媒体处理本身比较重,因此和博客内容构建分开维护会方便很多。

不然更新一次题库就重新跑整个 Arweave 博客发布,多少有点离谱。

support/blog#

ctf-game/support/blog/ 保存 CTF 和博客之间真正需要共享的最小公开内容。

也就是说没有为了 CTF 再把博客整个复制进 Workers 仓库。

部署流程只导出需要更新的入口文件,然后再交给博客仓库。

两个项目之间尽量保持低耦合。

docs#

CTF 和 MoeQ 原本有一部分运行、维护和实现说明放在博客仓库。

拆分 Worker 后,相应文档也一起迁到了 arisaka.eth-workers/docs/。

这样 Worker 的代码、部署方式和对应运维说明都放在同一个仓库里。

现在的架构#

现在整体关系大概是:

┌──────────────────┐
│ arisaka.eth │
│ Astro / Svelte │
└────────┬─────────┘
│
GitHub Actions
│
▼
┌──────────────────┐
│ Arweave │
│ Manifest / Files │
└────────┬─────────┘
│
▼
ENS
│
▼
┌──────────────────┐
│ blog-proxy │
│ 000.moe │
└──────────────────┘
arisaka.eth
│
├── Moment ───────── moment Worker ───── D1 / R2 / Telegram
│
├── Roro ─────────── live2d-chat ─────── AI API
│
├── Comments ─────── waline ──────────── D1
│
├── Random Image ─── randimg ─────────── D1 / R2
│
├── Writer ───────── writer ───────────── GitHub
│
└── CTF / MoeQ ───── ctf-game

博客本体基本只负责内容和前端。

需要状态、数据库、第三方 API 或定时任务的功能则交给 Workers。

为什么拆开#

最明显的好处就是各部分不再互相绑死。

以前可能是:

Telegram 更新
↓
博客数据变化
↓
重新构建
↓
重新上传 Arweave
↓
更新 ENS

现在:

Telegram 更新
↓
Moment Worker
↓
D1 / R2

结束。

评论也是一样:

新增评论
↓
D1

不影响博客。

AI 出问题:

live2d-chat

出问题。

静态文章照样可以正常访问。

随机图片服务出问题,也不会影响 Arweave 上的文章。

最终把整个项目分成两种东西:

静态内容
→ arisaka.eth
动态服务
→ arisaka.eth-workers

各自独立部署,也各自独立维护。

总结#

Arisaka.ETH 最开始只是想做一个自己的博客。

后来慢慢变成:

Astro
+
Svelte
+
GitHub Actions
+
Arweave
+
ENS
+
Ethereum
+
Cloudflare Workers
+
D1
+
R2
+
Telegram
+
GitHub
+
AI

很多东西并不是一开始就计划好的。

基本都是遇到问题以后再慢慢改。

比如:

不想依赖传统服务器
→ Arweave
想使用链上域名
→ ENS
不想重复上传文件
→ SHA-256 增量缓存
Gas 太贵
→ release-watch
Telegram 更新不想重建博客
→ Moment Worker
不想继续维护图片服务器
→ R2 + D1
AI Key 不能放前端
→ live2d-chat
想随时写文章
→ Writer

做着做着,最后就成现在这样了。

不过目前两个仓库的职责已经比较清楚:

arisaka.eth 负责博客本身,arisaka.eth-workers 负责博客背后的动态服务。

至少相比以前什么都塞在一起,现在维护起来舒服多了。

图片预览#

Arisaka.ETH 博客简介
https://arisaka.eth.limo/posts/arisakablog/
Author
AzusaMoe
Published at
2026-09-29
License
CC BY-NC-SA 4.0
Provenance
ENS Contenthash
Resolving…
Article File TX
Resolving…
Content Digest
Resolving…
Source Version
Resolving…
Comments