Docker + acme.sh 搭建 Tailscale 自建 DERP 中继节点完整记录
Docker + acme.sh 搭建 Tailscale 自建 DERP 中继节点完整记录1. 方案架构为了实现证书自动申请、续期与 DERP 服务无缝衔接,采用 Docker Compose 进行单机编排。acme-sh 容器负责通过 Cloudflare DNS 验证申请 Let’s Encrypt 证书并导出为 DERP 所需的格式;derper 容器挂载相同的证书目录并运行中继服务。 1234567891011121314[Cloudflare DNS-01 验证] │ ▼ ┌─────────── derp-acme (容器) ───────────┐ │ 使用 CF_Token 申请证书,并将其安装转换为 │ │ derp.example.com.crt / .key │ └───────────────────┬────────────────────┘ │ (共享挂载 ./certs) ▼ ┌────────── tailscale-...
Go + Next.js 实现 2FA (TOTP) 的踩坑记录与架构复盘
Go + Next.js 实现 2FA (TOTP) 的踩坑记录与架构复盘最近给手头的项目加上了基于 TOTP(基于时间的一次性密码)的双因子认证(2FA)流程。整体思路是传统的双阶段认证(Two-Stage): 第一步先验密码,对上了且开启了 2FA,后端不发正式 Token,而是塞给前端一个只有 3 ~ 5 分钟寿命的 challengeToken。 前端监听到这个状态,通过 Next.js 的 URL 参数(比如 /login?step=2fa)做深链接导航,直接切到 2FA 验证页,用 input-otp 组件让用户填 6 位动态码,最后去后端换取真正的登录凭证。 本来以为一套流程跑下来挺顺畅,但利用AI对着提交的 Git Diff 仔细过了一遍安全性后,发现里面其实藏了不少逻辑漏洞和优化空间。趁着还没线上翻车,把这次的数据库改动和后续的重构 TODO 记录下来(感激AI的review)。 数据库表结构变更这次 2FA 的底层支持直接做在了原有的 users 表上,通过变更 DDL 增加了三个字段: 12345ALTER TABLE usersADD COLUMN t...
🚀 零公网 IP 实现内网穿透:Tailscale 安装与多端互联异地访问指南
🚀 零公网 IP 实现内网穿透:Tailscale 安装与多端互联异地访问指南📡 什么是 Tailscale?在日常折腾 HomeLab、NAS 或者远程办公时,我们经常需要在外网访问家里的设备。传统的方案要么需要运营商提供公网 IP(配合 DDNS),要么需要使用带有中心服务器的内网穿透工具(如 Frp、Nps),但这两种方案要么门槛高,要么受限于中心服务器的带宽。 Tailscale 是一种基于 Mesh(网状)拓扑结构的虚拟私人网络(VPN)工具。它能让你分散在各地的设备(手机、电脑、服务器、路由器)无视复杂的网络环境(如大内网、双重路由、NAT 限制),安全地连接在同一个虚拟局域网内。 Tailscale 的核心优势: 去中心化(P2P 连接):虽然有控制服务器协调连接,但设备之间的数据传输是端到端(Peer-to-Peer)的。一旦打洞成功,流量直接在两台设备间传输,速度取决于你的宽带上限,不限速。 无感接入:设备加入网络后,会获得一个固定的内网 IP(100.x.x.x 网段),无论你身处何地,直接访问这个 IP 就能连接设备。 极其简单的配置:无需折腾复杂的证书...
在 Next.js 16 (Turbopack) 中集成 RDKit.js 渲染 SMILES 的工程实践与踩坑指南
在 Next.js 16 (Turbopack) 中集成 RDKit.js 渲染 SMILES 的工程实践与踩坑指南前言在生物制药数字化(AIDD、LIMS 等系统)的前端开发中,SMILES(简化分子线性输入规范)是表示分子结构最常用的文本格式。为了在前端实现“输入文本,实时预览 2D 分子结构”,我们需要引入化学信息学界的工业级开源工具包:RDKit.js。 由于 RDKit.js 底层依赖重型的 WebAssembly (Wasm) 编译产物,当它遇到 Next.js 16 默认的 Turbopack 构建流以及 SSR(服务端预渲染) 架构时,会引发一系列经典的打包与运行时崩溃。 本文将完整记录基于 Next.js 16 + Bun + TypeScript 栈集成 RDKit.js 的踩坑心路历程,并分享最终的离线化解决方案。 一、 初次尝试与经典的 “fs” 编译炸弹按照常规的前端模块化思维,我们首先会通过 Bun 安装依赖: 1bun add @rdkit/rdkit 然后在一个标准的 Client Component 中尝试动态加载: 12// 尝试通过全局或动...
🚀 玩转 Homelab:使用 Helm 在 K3s 中部署 Gitea,并接入外部 PostgreSQL
🚀 玩转 Homelab:使用 Helm 在 K3s 中部署 Gitea,并接入外部 PostgreSQL在自己的内网(Homelab)环境里,拥有一个轻量、好用、全加密(HTTPS)的代码托管系统是每个开发者的梦想。今天这篇博客就带大家复盘,如何利用 K3s、Traefik 网关以及外部自建的 PostgreSQL 数据库,一步步搭建属于自己的 Gitea。 我们将摒弃复杂的理论,用最纯粹的实战步骤,带你避开内网部署中的那些“隐藏大坑”。 🏗️ 整体架构一览为了让整个系统足够轻量且好维护,我们采用了以下设计方案: flowchart TB subgraph Client ["开发者环境 (Client)"] Browser["浏览器 (HTTPS)"] GitCli["Git 客户端 (SSH)"] end subgraph K3S ["K3s 集群边界 (K3s Cluster)"] direction TB subgraph Net ["网络与路由 (Ingres...
使用K3s 从零部署 CloudNativePG:打造 Homelab 统一 PostgreSQL 平台
K3s 从零部署 CloudNativePG:打造 Homelab 统一 PostgreSQL 平台前言在很多 CloudNativePG 教程中,通常只会介绍如何安装 Operator 并创建一个 PostgreSQL 集群。 但在实际 Homelab 场景中,我们更希望: 整个 K3s 集群共用一套 PostgreSQL 服务 支持 Spring Boot 项目 支持 Gitea 支持 Immich 支持未来新增应用 能够通过固定 IP 从局域网访问数据库 保留未来扩容多节点 K3s 的能力 因此本文不只是介绍 CloudNativePG 的安装,而是介绍如何将其建设为 Homelab 的统一 PostgreSQL 平台。 环境说明K3s 节点1192.168.31.215 路由器小米路由器 DHCP 配置: 1192.168.31.5 - 192.168.31.229 MetalLB 地址规划12345192.168.31.230 postgres192.168.31.231 ingress192.168.31.232 gitea192.168.31.233 ar...
从零开始:在 K3s 中部署 CloudNativePG 并成功实现外部直连(全流程实战)
前言本文是在我的Homelab 单节点 K3s(IP: 192.168.0.103)上部署 CloudNativePG (CNPG) 1.29 版本。 123kubectl get nodesNAME STATUS ROLES AGE VERSIONearzer Ready control-plane 53d v1.35.4+k3s1 过程安装 kubectl-cnpg 插件(强烈推荐)CloudNativePG 提供了一个非常强大的 kubectl 插件,可以帮助你轻松查看数据库状态、进行主备切换、查看日志等。 在你的控制端(或 K3s 节点上)运行以下命令安装: 1curl -sSfL https://github.com/cloudnative-pg/cloudnative-pg/raw/main/hack/install-cnpg-plugin.sh | sudo sh -s -- -b /usr/local/bin 如果你是中国用户,你可以使用gh-proxy来加速 1curl -sSfL https://gh-...
🚀 记一次本地 Homelab 环境 Gitea 升级迁移与 Actions (CI/CD) 完美集成实战
🚀 记一次本地 Homelab 环境 Gitea 升级迁移与 Actions (CI/CD) 完美集成实战📌 前言最近对本地 Homelab 服务器的私有代码托管平台 Gitea 进行了一次大版本升级迁移(升级至 v1.26)。原本以为只是一次简单的 docker compose up,没想到中间经历了数据库连错库、旧运行器残留、以及 Gitea Actions (Act Runner) 容器网络隔离等一连串经典的暗坑。 本文将完整记录这次迁移过程、踩坑原因以及最终如何完美集成兼容 GitHub Actions 语法的 Gitea Actions 并成功跑通 Go 自动化编译 的全案。 🛠️ 第一阶段:大版本迁移与数据库踩坑在新的服务器上,我准备好了原有的数据卷 ./data 并编写了新的 docker-compose.yaml。然而初次启动后,发现网页端一片空白,原有的代码仓库、组织和用户全都不见了! 🔍 踩坑原因检查 docker-compose.yaml 发现,在环境变量中误指定了一个全新的数据库名: 12- GITEA__database__NAME=...
2026年5月8号面试总结
前言2026年5月8号有一场面试,是对方架构师来面试的我。总的来说,面试的效果不是很好。面试的岗位是前端岗位,但是问的很多是后端架构的设计,我认为我的准备不是很好,我需要继续努力补全自己。 问题Java的虚拟线程问题:Java的虚拟线程是什么,怎么创建? 回答如下: 在Java 21中,引入了虚拟线程(Virtual Threads)来简化和增强并发性,这使得在Java中编程并发程序更容易、更高效。 虚拟线程,也称为“用户模式线程(user-mode threads)”或“纤程(fibers)”。该功能旨在简化并发编程并提供更好的可扩展性。虚拟线程是轻量级的,这意味着它们可以比传统线程创建更多数量,并且开销要少得多。这使得在自己的线程中运行单独任务或请求变得更加实用,即使在高吞吐量的程序中也是如此。 在Java 21中创建和使用虚拟线程有多种方法: 1. 使用静态构建器方法Thread.startVirtualThread方法将可运行对象作为参数来创建,并立即启动虚拟线程,具体如下代码: 123456Runnable runnable = () -> { S...
2026年5月7号面试总结
前言2026年5月7号有两场面试,本文记录了自己预先准备面试题和面试中遇到的问题。 笔试防抖 (Debounce)原理:在事件被触发 $n$ 秒后再执行回调,如果在这 $n$ 秒内又被触发,则重新计时。适用于:搜索框输入、窗口大小调整 1234567891011121314function debounce<T extends (...args: any[]) => void>( func: T, wait: number): (...args: Parameters<T>) => void { let timeout: ReturnType<typeof setTimeout> | null = null; return function (this: any, ...args: Parameters<T>) { if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { func.a...
