[{"content":"这张更像是在走廊里遇到的一束光，亮处和暗处的对比很明显。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-13/sunset-corridor-reflection/","summary":"夕阳从走廊外照进来，在地面上留下很亮的一条反光。","title":"走廊里的夕阳"},{"content":"远山和校园隔着一层雾气，画面安静下来。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-13/misty-campus-hills/","summary":"远处的山被雾气压低，校园的树和建筑铺在前景里。","title":"远山和校园"},{"content":"日光压低以后，建筑的轮廓和天空的颜色刚好留在同一个画面里。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-13/campus-sunset-building/","summary":"日落贴着校园建筑落下，天空和屋檐都被染成了暖色。","title":"校园日落"},{"content":"我为什么想做 LinkNest 现在一个人手里往往不止一台设备：电脑、手机、平板、服务器，甚至还有临时使用的开发机。\n真正麻烦的不是“能不能传一个文件”，而是这些设备经常处在不稳定的网络环境里：\n家里、学校、实验室的网络来回切换 DHCP 让设备 IP 经常变化 两台设备不一定同时在线 大文件传到一半可能因为网络波动中断 云服务器上已经有资源，但缺少一个清晰的索引和拉取入口 所以我想做一个项目，暂时叫 LinkNest，中文名是 链巢。\n它不是想重新做一个普通网盘，也不是简单复制一个局域网传输工具，而是想在“局域网直连”和“云端资源池”之间做一个更轻量、更适合个人和小团队自部署的传输系统。\nLinkNest 想解决什么问题 LinkNest 的核心目标是：在动态网络环境里，让多台设备依然能通过稳定身份、在线状态和云端资源索引完成可靠传输。\n我最想先解决的是这几件事：\n设备身份不要绑定 IP\n设备第一次启动时生成稳定的 Device ID，后续即使 IP 变了，服务端也知道它还是同一台设备。\n服务端维护设备在线状态\n客户端通过 WebSocket 发送心跳，服务端记录设备名、设备类型、当前局域网 IP、端口和最后在线时间。\n云端保留资源索引\n文件上传后，服务端记录文件元数据。另一台设备即使不和上传设备同时在线，也能从云端资源列表里拉取文件。\n大文件支持断点续传\n上传和下载按分片执行，中断后只补缺失分片，并通过 hash 校验完整性。\n后续优先局域网直连\n当两台设备在同一局域网内时，优先走直连；不可达时再用云端作为兜底。\n第一阶段先做小闭环 这个项目不能一开始就把所有客户端、P2P、WebRTC、UI 和云存储都堆上去。\n我现在更倾向于先做一个能跑通的最小闭环：\nGo 服务端基础框架 设备注册接口 Go CLI 客户端原型 Device ID 生成和持久化 WebSocket 心跳 云端设备列表 API IP 变化后设备身份不变、在线状态能更新 这个阶段完成后，项目至少能证明一个关键点：\n设备身份和网络地址可以解耦，动态 IP 不应该让设备关系失效。\nMVP 版本会包含什么 在第一阶段之后，MVP 会继续往“真正能传资源”的方向推进：\n用户注册和登录 多设备绑定到同一账号 在线设备展示 文件上传到云服务器 云端资源列表 从云端下载文件 大文件分片上传 断点续传和 SHA-256 校验 基础 Web UI 或客户端界面 这个版本的定位很明确：\n先让一台设备上传资源，另一台设备可以稳定地看到并拉取。等这个链路跑稳，再去做局域网发现、直连传输和更复杂的调度。\n技术路线 目前计划的技术选择是：\n服务端：Go 数据库：早期 SQLite，后续可切换 PostgreSQL 文件存储：早期本地磁盘，后续可接 MinIO 或 S3 兼容存储 通信：HTTP REST API + WebSocket 客户端：先做 Go CLI 原型，后续考虑 Flutter 多端客户端 部署：先本地运行，后续整理 Docker Compose 模块上大致会分成：\n用户认证模块 设备身份模块 设备在线状态模块 云端资源模块 分片传输模块 传输任务模块 Web 管理界面 我希望这个项目一开始就保持边界清晰。\n功能可以慢慢加，但设备、文件、任务、传输协议这些核心概念不能混在一起。\n我希望它最终变成什么样 我希望 LinkNest 最后能成为一个开源、轻量、可自部署的多端资源传输系统。\n理想状态下，它可以做到：\n个人可以把它部署在自己的云服务器上 多台设备能长期绑定到同一个账号 云端能看到哪些设备在线、哪些资源可下载 大文件传输中断后可以继续 同一局域网内优先直连，不行再走云端 有清晰的 README、API 文档、部署文档和演示视频 它不需要一开始就对标成熟网盘。\n我更希望它先把“设备身份稳定、状态可见、资源可拉取、传输可恢复”这几件事做扎实。\n我想邀请更多人参与 LinkNest 现在还处在早期设计和原型阶段，正适合一起讨论和参与。\n如果你对下面这些方向感兴趣，欢迎来找我：\nGo 后端开发 WebSocket、HTTP API、任务状态设计 文件分片、断点续传、hash 校验 Web UI 或 Flutter 多端客户端 Docker Compose 部署 README、API 文档、贡献指南 局域网发现、P2P、WebRTC、QUIC 等后续传输方案 安全设计，比如 token、设备公钥、签名校验和端到端加密 这个项目需要的不只是写代码的人。\n如果你愿意帮忙看架构、提需求、测试弱网场景、整理文档、做演示视频，也都很有价值。\n你可以通过我的 GitHub 主页或邮箱联系我：\nGitHub: lexiaox Gmail: ldy3087146292@gmail.com 等仓库和基础原型整理好后，我也会继续把开发进展写到这里。\n如果你也遇到过多设备传文件、动态 IP、云端资源管理和大文件中断这些问题，欢迎一起把 LinkNest 做出来。\n","permalink":"https://lexiaox.github.io/BlogWeb/posts/linknest-multi-device-transfer-system/","summary":"\u003ch2 id=\"我为什么想做-linknest\"\u003e我为什么想做 LinkNest\u003c/h2\u003e\n\u003cp\u003e现在一个人手里往往不止一台设备：电脑、手机、平板、服务器，甚至还有临时使用的开发机。\u003cbr\u003e\n真正麻烦的不是“能不能传一个文件”，而是这些设备经常处在不稳定的网络环境里：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e家里、学校、实验室的网络来回切换\u003c/li\u003e\n\u003cli\u003eDHCP 让设备 IP 经常变化\u003c/li\u003e\n\u003cli\u003e两台设备不一定同时在线\u003c/li\u003e\n\u003cli\u003e大文件传到一半可能因为网络波动中断\u003c/li\u003e\n\u003cli\u003e云服务器上已经有资源，但缺少一个清晰的索引和拉取入口\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e所以我想做一个项目，暂时叫 \u003cstrong\u003eLinkNest\u003c/strong\u003e，中文名是 \u003cstrong\u003e链巢\u003c/strong\u003e。\u003cbr\u003e\n它不是想重新做一个普通网盘，也不是简单复制一个局域网传输工具，而是想在“局域网直连”和“云端资源池”之间做一个更轻量、更适合个人和小团队自部署的传输系统。\u003c/p\u003e\n\u003ch2 id=\"linknest-想解决什么问题\"\u003eLinkNest 想解决什么问题\u003c/h2\u003e\n\u003cp\u003eLinkNest 的核心目标是：在动态网络环境里，让多台设备依然能通过稳定身份、在线状态和云端资源索引完成可靠传输。\u003c/p\u003e\n\u003cp\u003e我最想先解决的是这几件事：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e设备身份不要绑定 IP\u003cbr\u003e\n设备第一次启动时生成稳定的 \u003ccode\u003eDevice ID\u003c/code\u003e，后续即使 IP 变了，服务端也知道它还是同一台设备。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e服务端维护设备在线状态\u003cbr\u003e\n客户端通过 WebSocket 发送心跳，服务端记录设备名、设备类型、当前局域网 IP、端口和最后在线时间。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e云端保留资源索引\u003cbr\u003e\n文件上传后，服务端记录文件元数据。另一台设备即使不和上传设备同时在线，也能从云端资源列表里拉取文件。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e大文件支持断点续传\u003cbr\u003e\n上传和下载按分片执行，中断后只补缺失分片，并通过 hash 校验完整性。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e后续优先局域网直连\u003cbr\u003e\n当两台设备在同一局域网内时，优先走直连；不可达时再用云端作为兜底。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"第一阶段先做小闭环\"\u003e第一阶段先做小闭环\u003c/h2\u003e\n\u003cp\u003e这个项目不能一开始就把所有客户端、P2P、WebRTC、UI 和云存储都堆上去。\u003cbr\u003e\n我现在更倾向于先做一个能跑通的最小闭环：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGo 服务端基础框架\u003c/li\u003e\n\u003cli\u003e设备注册接口\u003c/li\u003e\n\u003cli\u003eGo CLI 客户端原型\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eDevice ID\u003c/code\u003e 生成和持久化\u003c/li\u003e\n\u003cli\u003eWebSocket 心跳\u003c/li\u003e\n\u003cli\u003e云端设备列表 API\u003c/li\u003e\n\u003cli\u003eIP 变化后设备身份不变、在线状态能更新\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这个阶段完成后，项目至少能证明一个关键点：\u003cbr\u003e\n\u003cstrong\u003e设备身份和网络地址可以解耦，动态 IP 不应该让设备关系失效。\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"mvp-版本会包含什么\"\u003eMVP 版本会包含什么\u003c/h2\u003e\n\u003cp\u003e在第一阶段之后，MVP 会继续往“真正能传资源”的方向推进：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e用户注册和登录\u003c/li\u003e\n\u003cli\u003e多设备绑定到同一账号\u003c/li\u003e\n\u003cli\u003e在线设备展示\u003c/li\u003e\n\u003cli\u003e文件上传到云服务器\u003c/li\u003e\n\u003cli\u003e云端资源列表\u003c/li\u003e\n\u003cli\u003e从云端下载文件\u003c/li\u003e\n\u003cli\u003e大文件分片上传\u003c/li\u003e\n\u003cli\u003e断点续传和 SHA-256 校验\u003c/li\u003e\n\u003cli\u003e基础 Web UI 或客户端界面\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这个版本的定位很明确：\u003cbr\u003e\n先让一台设备上传资源，另一台设备可以稳定地看到并拉取。等这个链路跑稳，再去做局域网发现、直连传输和更复杂的调度。\u003c/p\u003e","title":"LinkNest：我想做一个更可靠的多端资源传输系统"},{"content":"同样是花和灯，这张更靠近，也更暖。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/blossoms-around-lantern/","summary":"灯光把花簇一层层托起来，夜里会比白天更浓。","title":"灯下花簇"},{"content":"这张很适合留作这一组夜景里的收尾镜头。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/lake-pavilion-reflection/","summary":"从圆形框景里看出去，湖面把灯光和建筑一起收进去了。","title":"湖边倒影"},{"content":"这张更偏线条感，像夜里展开的一张网。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/branches-under-night-sky/","summary":"被路灯染亮的枝条，在夜空里铺开了细密的线条。","title":"夜空下的枝条"},{"content":"灯亮起来以后，花就像被单独照了出来。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/blossom-lantern-night/","summary":"夜色一暗，花枝和灯光就都变得很有故事感。","title":"夜里的花与灯"},{"content":"塔身、屋檐和树叶都还留着一点傍晚的温度。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/pagoda-at-dusk/","summary":"暮色刚落下来时，古塔和屋檐一起保留了最后一点亮色。","title":"暮色里的古塔"},{"content":"最有氛围感的一张，几乎只剩轮廓和余光。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/pagoda-silhouette-sunset/","summary":"逆光下的人像与古塔一起沉进落日。","title":"落日中的古塔剪影"},{"content":"一尊佛像和一座古塔，把地点气质一下定住了。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/monk-statue-and-pagoda/","summary":"佛像与古塔同框，画面很稳，也很西安。","title":"佛像与古塔"},{"content":"树林把这一天的光切成了很多层。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/cedar-grove-sunlight/","summary":"高高的树影之间，天光从林间落下来。","title":"林间天光"},{"content":"同一组春日花景里更柔和的一张。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/spring-blossom-blue-sky/","summary":"柔和的花枝被蓝天托起，画面很轻。","title":"春日花枝"},{"content":"西安春天的一抹明亮颜色。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-05/cherry-blossom-blue-sky/","summary":"西安春日里的一枝樱花，在清亮蓝天下舒展开来。","title":"樱花与蓝天"},{"content":"这一张主要用来记录首页头像素材的加载效果。\n","permalink":"https://lexiaox.github.io/BlogWeb/gallery/2026-04-04/avatar-loop/","summary":"当前首页使用的头像动图素材。","title":"头像动图测试"},{"content":"为什么要做这个项目 我想搭一个真正能长期使用的个人博客，而不是只在本地打开一次主题示例页面。\n对我来说，一个博客站至少要满足几件事：\n能稳定写文章 能把已有项目经历整理上线 后续还能继续改样式、改结构、改部署，而不是每次重头来一遍 所以这次项目真正做的，其实不是“套一个主题”，而是把博客从本地示例一步步整理成一个能持续维护的站点。\n站点是怎么搭起来的 我选的是 Hugo + PaperMod 这套组合。\n原因比较直接：\nHugo 适合写静态博客 Markdown 写文章效率高 部署简单 和 GitHub、Cloudflare 这类工具链很容易接起来 PaperMod 则帮我先解决了：\n首页模式 文章页结构 目录（TOC） 标签页 一些基础 SEO 元信息 这样我一开始就不用从零写模板，而是先把内容结构和部署链路跑通。\n我做了哪些关键整理 1. 清掉官方示例内容 主题自带的示例页更适合演示功能，不适合直接变成自己的博客。\n所以我先做的不是“加功能”，而是：\n清理默认示例内容 只保留自己的 posts 结构 创建文章 archetype 把站点导航收成更适合个人博客的样子 这一步很重要，因为它决定了这个站到底是“演示模板”还是“个人项目”。\n2. 把已有项目经历转成文章 博客不能只有壳子，没有内容。\n所以后面我把自己已有的项目经历逐步整理成文章，例如：\ndotfiles 仓库整理与同步 Kubernetes 静态网页部署实践 在这个过程中，我也发现一件事：\n项目笔记和博客文章其实不是同一种写法。\n笔记更偏内部记录 博客更偏对外表达 所以后来我会先把项目整理成笔记，再把它改写成更适合公开阅读的博客版本。\n3. 给站点补上独立的 Gallery 照片模块 后面我又做了一件和普通文章不太一样的事：给站点加了一个独立的 Gallery 页面。\n我不想把照片简单塞进博客文章里，因为两者的阅读方式完全不同：\n文章适合连续阅读 照片更适合按组浏览，再点进去细看 所以这次照片模块采用的是另一套结构：\n右上角导航新增 Gallery 页面按日期分组 每个日期组是一张独立卡片 卡片顶部显示日期、出处、作者 组内照片直接用缩略图网格展示 点击单张照片进入独立详情页 单图详情页也不是普通博客正文结构，而是：\n上面一张大图 图下方是日期、出处、作者 再下面是一排标签 这样一来，照片页的阅读体验就和文章页自然分开了。\n我还把照片内容并进了站点现有的 tags 体系：\n每张图片都作为独立内容项 每张图片都有自己的 tags 这些标签会和文章标签一起出现在 /tags/ 页面里 这样标签页不只是文章索引，也能反过来帮照片做分类。\n部署时真正踩过的坑 1. baseURL 没改，文章点击直接 404 刚开始最典型的问题就是：页面本地能看，但线上一点击文章就跳错地方。\n根因其实很简单：\nHugo 生成很多绝对链接时会依赖 baseURL 如果这里还是占位域名，文章页、图片、canonical、RSS 都会一起错 也就是说，这不是“某个链接写错了”，而是站点的基准域名没配置对。\n2. Cloudflare 构建失败，不是 Hugo 问题，而是主题目录状态有问题 后面我把项目接到 Cloudflare 时，第一次并不是 Hugo 构建失败，而是仓库在拉取时就出问题了。\n原因是：\nthemes/PaperMod 仍然是 submodule 指针 但仓库里并没有把子模块映射处理干净 最后的解决方式，是把主题直接纳入仓库，改成普通目录。\n这一步修完之后，Cloudflare 才能真正开始构建站点。\n3. Cloudflare 新流程和很多旧教程不一样 这次部署时，我用到的已经不是很多旧教程里的老版 Pages 界面了，而是更接近 Workers Builds 的新流程。\n整个链路变成了：\nGitHub 仓库触发构建 Cloudflare 安装 Hugo 执行 hugo --gc --minify 再用 Wrangler 把 public/ 部署出去 这一步还额外要求补上：\nwrangler.jsonc compatibility_date 否则部署命令会直接失败。\n4. 域名没真正接上时，头像会丢 后来页面里还出现过“头像不显示”的问题。\n本质上不是图片文件没了，而是：\n页面里图片地址已经按正式 baseURL 生成 但正式域名并没有真正绑定到当前部署 所以网页是在一个地址打开，图片却去另一个域名请求，最后自然失败。\n为了先保证站点可用，我最后把 baseURL 暂时切到了当前已经成功上线的 workers.dev 地址。\n样式这部分我后面又做了什么 把站点跑起来之后，我没有停在默认主题样式，而是继续调整了阅读体验。\n主要做了两类改动：\n首页 保留 profile 模式 调整背景颜色和卡片质感 换掉演示素材 目标是让首页更像“个人站入口”，而不是主题默认演示页。\n文章页 把外层文章卡片做宽 控制正文阅读列不要太长 再把正文列整体往右挪一点 这样页面读起来会更自然：\n外层宽度不会太局促 正文又不会一行太长 留白也比默认布局更舒服 这部分其实已经不只是换色，而是在调阅读布局。\n这个项目最后真正给了我什么 从结果看，这次我得到的不只是一个博客页面，而是一整套可继续维护的基础设施：\n一个能访问的个人博客 一套 Hugo + PaperMod 的站点结构 一条 GitHub 到 Cloudflare 的自动部署链路 一种“项目笔记 -\u0026gt; 博客文章”的内容整理流程 一版已经开始有自己风格的页面样式 对我来说，这种价值比“单次把页面做出来”更重要。\n后面还想继续做什么 如果继续完善，我最想补的是：\n接入真正属于自己的自定义域名 把站点标题、按钮文案、编辑链接这些细节收干净 继续整理更多项目经历上线 再慢慢把中文阅读排版打磨得更顺手 继续扩展 Gallery，把更多真实照片整理进来 这类工作都不是推翻重来，而是在现有基础上的持续迭代。\n","permalink":"https://lexiaox.github.io/BlogWeb/posts/building-blogweb-with-hugo-papermod-and-cloudflare/","summary":"\u003ch2 id=\"为什么要做这个项目\"\u003e为什么要做这个项目\u003c/h2\u003e\n\u003cp\u003e我想搭一个真正能长期使用的个人博客，而不是只在本地打开一次主题示例页面。\u003c/p\u003e\n\u003cp\u003e对我来说，一个博客站至少要满足几件事：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e能稳定写文章\u003c/li\u003e\n\u003cli\u003e能把已有项目经历整理上线\u003c/li\u003e\n\u003cli\u003e后续还能继续改样式、改结构、改部署，而不是每次重头来一遍\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e所以这次项目真正做的，其实不是“套一个主题”，而是把博客从本地示例一步步整理成一个能持续维护的站点。\u003c/p\u003e\n\u003ch2 id=\"站点是怎么搭起来的\"\u003e站点是怎么搭起来的\u003c/h2\u003e\n\u003cp\u003e我选的是 \u003ccode\u003eHugo + PaperMod\u003c/code\u003e 这套组合。\u003c/p\u003e\n\u003cp\u003e原因比较直接：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eHugo 适合写静态博客\u003c/li\u003e\n\u003cli\u003eMarkdown 写文章效率高\u003c/li\u003e\n\u003cli\u003e部署简单\u003c/li\u003e\n\u003cli\u003e和 GitHub、Cloudflare 这类工具链很容易接起来\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003ePaperMod 则帮我先解决了：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e首页模式\u003c/li\u003e\n\u003cli\u003e文章页结构\u003c/li\u003e\n\u003cli\u003e目录（TOC）\u003c/li\u003e\n\u003cli\u003e标签页\u003c/li\u003e\n\u003cli\u003e一些基础 SEO 元信息\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这样我一开始就不用从零写模板，而是先把内容结构和部署链路跑通。\u003c/p\u003e\n\u003ch2 id=\"我做了哪些关键整理\"\u003e我做了哪些关键整理\u003c/h2\u003e\n\u003ch3 id=\"1-清掉官方示例内容\"\u003e1. 清掉官方示例内容\u003c/h3\u003e\n\u003cp\u003e主题自带的示例页更适合演示功能，不适合直接变成自己的博客。\u003c/p\u003e\n\u003cp\u003e所以我先做的不是“加功能”，而是：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e清理默认示例内容\u003c/li\u003e\n\u003cli\u003e只保留自己的 \u003ccode\u003eposts\u003c/code\u003e 结构\u003c/li\u003e\n\u003cli\u003e创建文章 archetype\u003c/li\u003e\n\u003cli\u003e把站点导航收成更适合个人博客的样子\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这一步很重要，因为它决定了这个站到底是“演示模板”还是“个人项目”。\u003c/p\u003e\n\u003ch3 id=\"2-把已有项目经历转成文章\"\u003e2. 把已有项目经历转成文章\u003c/h3\u003e\n\u003cp\u003e博客不能只有壳子，没有内容。\u003c/p\u003e\n\u003cp\u003e所以后面我把自己已有的项目经历逐步整理成文章，例如：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003edotfiles\u003c/code\u003e 仓库整理与同步\u003c/li\u003e\n\u003cli\u003eKubernetes 静态网页部署实践\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e在这个过程中，我也发现一件事：\u003c/p\u003e\n\u003cp\u003e项目笔记和博客文章其实不是同一种写法。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e笔记更偏内部记录\u003c/li\u003e\n\u003cli\u003e博客更偏对外表达\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e所以后来我会先把项目整理成笔记，再把它改写成更适合公开阅读的博客版本。\u003c/p\u003e\n\u003ch3 id=\"3-给站点补上独立的-gallery-照片模块\"\u003e3. 给站点补上独立的 \u003ccode\u003eGallery\u003c/code\u003e 照片模块\u003c/h3\u003e\n\u003cp\u003e后面我又做了一件和普通文章不太一样的事：给站点加了一个独立的 \u003ccode\u003eGallery\u003c/code\u003e 页面。\u003c/p\u003e\n\u003cp\u003e我不想把照片简单塞进博客文章里，因为两者的阅读方式完全不同：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e文章适合连续阅读\u003c/li\u003e\n\u003cli\u003e照片更适合按组浏览，再点进去细看\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e所以这次照片模块采用的是另一套结构：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e右上角导航新增 \u003ccode\u003eGallery\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e页面按日期分组\u003c/li\u003e\n\u003cli\u003e每个日期组是一张独立卡片\u003c/li\u003e\n\u003cli\u003e卡片顶部显示日期、出处、作者\u003c/li\u003e\n\u003cli\u003e组内照片直接用缩略图网格展示\u003c/li\u003e\n\u003cli\u003e点击单张照片进入独立详情页\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e单图详情页也不是普通博客正文结构，而是：\u003c/p\u003e","title":"把 Hugo 博客从本地示例站搭成可上线的个人网站"},{"content":"项目目标 这次我想做的不是“写一个网页”，而是把一条完整的部署链路走通：\n写一个最小静态网页 用 Docker 打包成 nginx 镜像 把镜像推送到 Docker Hub 在云上的 Kubernetes 集群里部署 最后通过 SSH 隧道在本地浏览器访问 项目本身不复杂，但它把前端、容器、镜像仓库和 K8s 串在了一起。对我来说，这比单独学某一个命令更有价值。\n我做了哪些事情 整个过程大致分成五步：\n编写欢迎页：HTML、CSS、JavaScript 编写 Dockerfile，把静态资源打包进 nginx 编写 deployment.yaml 和 service.yaml 在本地 WSL 安装并验证 Docker 环境 把镜像推送到 Docker Hub，再部署到华为云 Kubernetes 最后访问路径也打通了：\n服务器本机验证：curl http://127.0.0.1:30523 本地浏览器访问：http://127.0.0.1:8080 使用 SSH 隧道：ssh -N -L 8080:127.0.0.1:30523 huaweiyun 核心工作流 1. 本地构建镜像 在 WSL 中完成镜像构建和推送：\ncd /mnt/d/codex/static-k8s-demo docker login -u \u0026lt;dockerhub-user\u0026gt; docker build -t leoduya/static-web-demo:latest . docker push leoduya/static-web-demo:latest 这里最关键的是把网页内容和 nginx 一起打进镜像，这样 Kubernetes 节点只需要拉镜像，不需要再手动拷贝网页文件。\n2. 云端部署 在云主机上完成 Kubernetes 资源创建和滚动更新：\nkubectl apply -f /home/ledy2025/deployment.yaml kubectl apply -f /home/ledy2025/service.yaml kubectl set image deployment/static-web-demo nginx=leoduya/static-web-demo:latest kubectl rollout status deployment/static-web-demo kubectl get pods -o wide kubectl get svc -o wide 其中我最常用的几个检查命令是：\nkubectl get pods -o wide kubectl get svc -o wide kubectl describe pod \u0026lt;pod名\u0026gt; 前两个看整体状态，最后一个专门用来排查异常事件。\n3. 本地访问验证 服务虽然部署在远端，但我没有直接暴露公网端口，而是先用 SSH 隧道做本地验证：\nssh -N -L 8080:127.0.0.1:30523 huaweiyun 然后直接在浏览器打开：\nhttp://127.0.0.1:8080 这种做法很适合调试早期版本，因为它简单、安全，而且不需要额外改防火墙或 ingress。\n这次踩过的坑 Docker Hub 登录名和仓库命名空间不是一回事 我一开始用邮箱登录 Docker Hub，没有意识到“登录账号”和“推送仓库前缀”是两件不同的事。\n最后真正能成功拉取的镜像名是：\nleoduya/static-web-demo:latest WSL 和云主机是两个独立环境 本地 Docker 登录成功，不代表云主机也有相同环境。\nWSL 和远端主机的：\n登录状态 文件 代理配置 镜像缓存 都是彼此独立的。\nkubectl apply 成功不等于应用能跑 这是最容易误判的一点。\n资源对象创建成功，只说明 YAML 没有语法问题，不说明镜像能拉下来，也不说明容器已经起来。\n真正要继续检查的是：\nPod 是否进入 Running 是否出现 ImagePullBackOff Service 端口是否正确暴露 这次学到的东西 这个项目虽然很小，但我真正打通了一条完整的容器化部署链路：\n写静态网页 打包 Docker 镜像 推送镜像仓库 Kubernetes 部署 Service 暴露服务 排查镜像拉取问题 用 SSH 隧道完成本地验证 对我来说，这比单独背 kubectl 命令更重要。因为只有把整条链路走通，才会知道问题到底会出现在“镜像、集群、网络还是本地访问”哪一层。\n后续想继续优化的方向 补上 Ingress，而不是只用 NodePort 用 CI 自动构建和推送镜像 给部署文件加上更清晰的环境变量和版本管理 把这套最小流程整理成可重复复用的模板项目 ","permalink":"https://lexiaox.github.io/BlogWeb/posts/kubernetes-static-site-deployment/","summary":"\u003ch2 id=\"项目目标\"\u003e项目目标\u003c/h2\u003e\n\u003cp\u003e这次我想做的不是“写一个网页”，而是把一条完整的部署链路走通：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e写一个最小静态网页\u003c/li\u003e\n\u003cli\u003e用 Docker 打包成 nginx 镜像\u003c/li\u003e\n\u003cli\u003e把镜像推送到 Docker Hub\u003c/li\u003e\n\u003cli\u003e在云上的 Kubernetes 集群里部署\u003c/li\u003e\n\u003cli\u003e最后通过 SSH 隧道在本地浏览器访问\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e项目本身不复杂，但它把前端、容器、镜像仓库和 K8s 串在了一起。对我来说，这比单独学某一个命令更有价值。\u003c/p\u003e\n\u003ch2 id=\"我做了哪些事情\"\u003e我做了哪些事情\u003c/h2\u003e\n\u003cp\u003e整个过程大致分成五步：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e编写欢迎页：HTML、CSS、JavaScript\u003c/li\u003e\n\u003cli\u003e编写 Dockerfile，把静态资源打包进 nginx\u003c/li\u003e\n\u003cli\u003e编写 \u003ccode\u003edeployment.yaml\u003c/code\u003e 和 \u003ccode\u003eservice.yaml\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e在本地 WSL 安装并验证 Docker 环境\u003c/li\u003e\n\u003cli\u003e把镜像推送到 Docker Hub，再部署到华为云 Kubernetes\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e最后访问路径也打通了：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e服务器本机验证：\u003ccode\u003ecurl http://127.0.0.1:30523\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e本地浏览器访问：\u003ccode\u003ehttp://127.0.0.1:8080\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e使用 SSH 隧道：\u003ccode\u003essh -N -L 8080:127.0.0.1:30523 huaweiyun\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"核心工作流\"\u003e核心工作流\u003c/h2\u003e\n\u003ch3 id=\"1-本地构建镜像\"\u003e1. 本地构建镜像\u003c/h3\u003e\n\u003cp\u003e在 WSL 中完成镜像构建和推送：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /mnt/d/codex/static-k8s-demo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker login -u \u0026lt;dockerhub-user\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker build -t leoduya/static-web-demo:latest .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker push leoduya/static-web-demo:latest\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里最关键的是把网页内容和 nginx 一起打进镜像，这样 Kubernetes 节点只需要拉镜像，不需要再手动拷贝网页文件。\u003c/p\u003e","title":"一次完整的 Kubernetes 静态网页部署实践"},{"content":"为什么要做这个项目 每次换机器、重装系统或者迁移开发环境时，最烦的往往不是装软件，而是把一堆零散配置重新凑起来：\nshell 配置 Vim / Neovim 配置 Git 配置 SSH 配置 这些文件平时分散在各处，真到迁移时就会发现：\n有的改动忘了记 有的文件路径不统一 有的手动复制很容易漏 所以我给自己定了一个目标：把常用配置整理成一个 dotfiles 仓库，并且让它能在新机器上快速部署。\n这个项目的目标不是“备份几个隐藏文件”，而是把环境管理变成一个可维护的流程。\n这次做了什么 这次项目包含了几个明确步骤：\n收集现有配置文件 设计 dotfiles 目录结构 编写安装脚本 初始化 Git 仓库并同步到 GitHub 补 README 和使用说明 最终成果不是“多了一个文件夹”，而是得到了一套可以复用的环境管理方式。\n我是怎么组织这个仓库的 核心思路不是把所有文件乱丢进去，而是按类型组织：\nshell nvim / vim git ssh 这样做的好处是：\n以后查找配置更快 哪一类配置要迁移，一眼就能找到 后续做安装脚本和备份逻辑也更自然 这个项目的关键不在“文件数量”，而在于结构是否清晰。\n安装脚本为什么重要 如果只是把配置文件备份到 GitHub，其实还不够。\n真正有价值的是：在一台新机器上，能不能快速恢复出接近原来的环境。\n所以我写了一个安装脚本，负责做三件事：\n创建符号链接 备份已有文件 支持安装和卸载 这就把“手动复制配置”变成了“运行一个脚本恢复环境”。\n同步到 GitHub 的意义 这一步的意义不只是备份。\n把 dotfiles 放进 GitHub 后，我获得了三个好处：\n配置有版本历史 可以在多台机器之间同步 新环境部署时不需要从零开始整理 这次仓库也顺利创建并推送成功，整个流程跑通之后，环境管理这件事就从“临时救火”变成了“可维护工程”。\n过程中比较关键的点 1. 配置收集要先做取舍 不是所有隐藏文件都应该进仓库。\n有些文件适合同步，有些则不应该直接公开，例如：\n临时缓存 强依赖本机路径的内容 含敏感信息的配置 所以 dotfiles 的核心不是“全拷贝”，而是“筛选后再管理”。\n2. 脚本要考虑已有环境 在新机器上部署时，最怕的是直接覆盖已有配置。\n所以安装脚本必须考虑：\n先备份旧文件 再创建链接 否则一旦覆盖掉现有环境，恢复成本很高。\n3. Git 管理让配置真正可追踪 一旦配置进了 Git 仓库，每次改动都会留下历史。\n这意味着以后你不需要靠记忆去回想：\n某个别名是什么时候加的 某段 shell 配置为什么改成现在这样 历史本身就是最好的说明文档。\n这次项目的结果 这次我最终完成了：\ndotfiles 仓库的目录整理 安装脚本编写 README 和说明文档 Git 仓库初始化 GitHub 推送 从“零散配置文件”走到“可迁移的环境仓库”，这是一个很典型但很实用的小项目。\n后面还能怎么继续做 我之后还想继续补几件事：\n在新机器上完整测试部署流程 评估是否引入 stow 这类工具，让链接管理更模块化 把依赖安装过程进一步标准化 对敏感配置和公开配置做更清晰的隔离 如果这些也补齐，整个 dotfiles 系统就会更稳。\n这类项目对我有什么价值 它不会像做一个网页那样“看起来很炫”，但它非常实用。\n因为它解决的是一个真实问题：\n如何让自己的开发环境可复制、可迁移、可维护。\n对个人开发者来说，这类项目的价值常常比一个演示型项目更长期。\n它不只是一个小工具仓库，更像是一套属于自己的开发环境基础设施。\n","permalink":"https://lexiaox.github.io/BlogWeb/posts/building-and-syncing-dotfiles/","summary":"\u003ch2 id=\"为什么要做这个项目\"\u003e为什么要做这个项目\u003c/h2\u003e\n\u003cp\u003e每次换机器、重装系统或者迁移开发环境时，最烦的往往不是装软件，而是把一堆零散配置重新凑起来：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eshell 配置\u003c/li\u003e\n\u003cli\u003eVim / Neovim 配置\u003c/li\u003e\n\u003cli\u003eGit 配置\u003c/li\u003e\n\u003cli\u003eSSH 配置\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这些文件平时分散在各处，真到迁移时就会发现：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e有的改动忘了记\u003c/li\u003e\n\u003cli\u003e有的文件路径不统一\u003c/li\u003e\n\u003cli\u003e有的手动复制很容易漏\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e所以我给自己定了一个目标：把常用配置整理成一个 \u003ccode\u003edotfiles\u003c/code\u003e 仓库，并且让它能在新机器上快速部署。\u003cbr\u003e\n这个项目的目标不是“备份几个隐藏文件”，而是把环境管理变成一个可维护的流程。\u003c/p\u003e\n\u003ch2 id=\"这次做了什么\"\u003e这次做了什么\u003c/h2\u003e\n\u003cp\u003e这次项目包含了几个明确步骤：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e收集现有配置文件\u003c/li\u003e\n\u003cli\u003e设计 \u003ccode\u003edotfiles\u003c/code\u003e 目录结构\u003c/li\u003e\n\u003cli\u003e编写安装脚本\u003c/li\u003e\n\u003cli\u003e初始化 Git 仓库并同步到 GitHub\u003c/li\u003e\n\u003cli\u003e补 README 和使用说明\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e最终成果不是“多了一个文件夹”，而是得到了一套可以复用的环境管理方式。\u003c/p\u003e\n\u003ch2 id=\"我是怎么组织这个仓库的\"\u003e我是怎么组织这个仓库的\u003c/h2\u003e\n\u003cp\u003e核心思路不是把所有文件乱丢进去，而是按类型组织：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eshell\u003c/li\u003e\n\u003cli\u003envim / vim\u003c/li\u003e\n\u003cli\u003egit\u003c/li\u003e\n\u003cli\u003essh\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这样做的好处是：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e以后查找配置更快\u003c/li\u003e\n\u003cli\u003e哪一类配置要迁移，一眼就能找到\u003c/li\u003e\n\u003cli\u003e后续做安装脚本和备份逻辑也更自然\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这个项目的关键不在“文件数量”，而在于结构是否清晰。\u003c/p\u003e\n\u003ch2 id=\"安装脚本为什么重要\"\u003e安装脚本为什么重要\u003c/h2\u003e\n\u003cp\u003e如果只是把配置文件备份到 GitHub，其实还不够。\u003cbr\u003e\n真正有价值的是：在一台新机器上，能不能快速恢复出接近原来的环境。\u003c/p\u003e\n\u003cp\u003e所以我写了一个安装脚本，负责做三件事：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e创建符号链接\u003c/li\u003e\n\u003cli\u003e备份已有文件\u003c/li\u003e\n\u003cli\u003e支持安装和卸载\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e这就把“手动复制配置”变成了“运行一个脚本恢复环境”。\u003c/p\u003e\n\u003ch2 id=\"同步到-github-的意义\"\u003e同步到 GitHub 的意义\u003c/h2\u003e\n\u003cp\u003e这一步的意义不只是备份。\u003c/p\u003e\n\u003cp\u003e把 dotfiles 放进 GitHub 后，我获得了三个好处：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e配置有版本历史\u003c/li\u003e\n\u003cli\u003e可以在多台机器之间同步\u003c/li\u003e\n\u003cli\u003e新环境部署时不需要从零开始整理\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这次仓库也顺利创建并推送成功，整个流程跑通之后，环境管理这件事就从“临时救火”变成了“可维护工程”。\u003c/p\u003e","title":"把 dotfiles 整理成仓库并同步到 GitHub"}]