自托管全平台 Todo 系统搭建实录:选型、部署与二次开发

自托管全平台 Todo 系统搭建实录:选型、部署与二次开发

日期:2026-09-02 | 标签:自托管 · Docker · 二次开发 · Go · Vue

本文记录一次完整的自托管待办系统落地过程:从开源选型、容器化部署,到邀请制改造、社区版功能解锁、自定义镜像构建与多端客户端接入。全程跑在一台 2C / 1.6G 内存 的小服务器上,所有源码与构建链路完全自持。


一、背景与需求

需要一个"自己的"待办系统,而不是把数据放在别人家 SaaS 里。硬性要求:

  • 五端可用:Web / Android / iOS / 鸿蒙(HarmonyOS NEXT) / macOS
  • 数据自持:自建服务器 + 自有域名子域,全站 HTTPS
  • 可控可二次开发:源码在自己手里,随时改

二、方案选型:为什么是 Vikunja

先做了两条路线的对比:

维度 全自研(Flutter 五端 + 自建后端) 开源 Vikunja + 自研补缺
上线时间 10~12 周(单人全栈) 1 天
Web 自研 PWA 官方,功能完整
Android / iOS 自研 Flutter 官方 App
macOS 自研 官方桌面端已归档 → PWA / CalDAV
鸿蒙 自研 ArkTS 无官方 → PWA 兜底,后续自研
二次开发 完全自主 AGPLv3,自用/自托管无限制

Vikunja 打动我的点:

  • 功能完整度在自托管里是第一梯队:清单/看板/甘特/表格四种视图、子任务、标签、优先级、截止日期、重复任务、提醒、附件、评论、CalDAV(可直接喂给系统日历/提醒事项)、Todoist/Trello/微软待办一键导入;
  • 部署极轻:API 与前端打进单个容器,支持 SQLite——对 1.6G 内存的服务器非常友好;
  • 维护活跃:v2.6.0(2026-08-31 发布)两个月内连发四个版本,380 commits / 45 个新特性;
  • 单容器、单二进制:前端通过 go:embed 打进 Go 二进制,交付物只有一个文件。

结论:Vikunja 为主,需要什么自研补什么,而不是从零造轮子。

三、部署架构

[Web PWA] [Android/iOS App] [macOS] [鸿蒙 PWA]
        \        |        |        /
         \       |        |       /
              todo.example.com   ← Caddy 反向代理(自动 Let's Encrypt 证书)
                       |
              ┌────────┴────────┐
              │   Vikunja 容器   │  ← 127.0.0.1:8090,仅本机可访问
              │   (SQLite 存储)  │
              └────────┬────────┘
                每日 cron 备份:SQLite 在线快照 + 附件打包,保留 14 天

几个关键决定:

  1. Caddy 做反代:配置即代码,新增子域 = Caddyfile 加一段,TLS 证书自动签发续期,零手工;
  2. SQLite 而非 PostgreSQL:单用户/小团队场景完全够用,省掉一个常驻数据库进程——1.6G 内存的机器经不起 Postgres + Redis 的折腾;
  3. 备份用 sqlite3 .backup 语义的在线快照(Python sqlite3 模块即可),不用停服务拷文件,避免文件锁导致的数据不一致;
  4. 代码托管到自建 GitLab:主仓库 + 官方移动端/桌面端仓库镜像,全部私有,附带 FORK-NOTES 文档说明基线版本与「同步上游 → 合并 → 推回」的更新工作流。

四、二次开发①:邀请制注册

需求很简单:不要开放注册,只有拿到邀请码的人才能注册。

后端(Go):

  • 新增配置项:service.registrationinvitecode(环境变量注入,留空 = 不校验,向后兼容);
  • 注册入口 v1/v2 共用同一个实现,改一处即全覆盖:邀请码缺失/错误时返回 400 + invalid_fields: ["inviteCode"]——这个格式能让前端把错误精确显示到输入框下方,而不是顶部一条笼统提示。

前端(Vue 3):

  • 注册页新增"邀请码"输入框,空值客户端拦截 + 服务端二次校验双保险;
  • i18n 补充中英文案。

验证:无码 → 400;错码 → 400;正确码 → 注册成功。三步全过,改配置即可换邀请码,无需再改代码。

五、二次开发②:解锁社区版管理后台

这里踩到一个"惊喜":Vikunja v2.4.0 起把管理后台(is_admin/admin 路由、CLI 授权)锁进了付费 Pro 授权。社区版用户即使注册了,也不存在"管理员"——CLI 直接拒绝:

The admin-panel license feature is not active; refusing to change the is_admin flag.

追代码发现门禁很集中,全部收敛在 license 模块两个函数上:

  • IsFeatureEnabled(FeatureAdminPanel) —— 后端路由中间件与 CLI 都走它;
  • EnabledProFeatures() —— 通过 /info 接口上报给前端,前端据此决定是否渲染 Admin 菜单。

作为自托管的 AGPL fork,改动就两处:前者对 FeatureAdminPanel 恒返回 true;后者在免费模式下也把 admin_panel 加进上报列表。重新编译部署后:

vikunja user set-admin <用户名> --admin
# → User "<用户名>" is now an instance admin.

前端 /info 返回 enabled_pro_features: ["admin_panel"],Admin 菜单出现,用户管理 / 项目管理 / 实例总览全部可用。同类 Pro 功能(如时间追踪)也留了一模一样的解锁口子,需要时照抄模式即可。

说明:这是在自托管实例上的自用修改,AGPLv3 完全允许。若做对外商业服务则需注意协议义务。

六、自定义镜像构建:低内存服务器上的正确姿势

官方 Dockerfile 面向多架构发布流水线设计,依赖 buildx + xgo(跨平台交叉编译),在小服务器上直接构建会遇到 BUILDPLATFORM 未定义等一堆问题。

于是自写了一个单架构 Dockerfile.custom,几个关键点:

  1. 国内镜像加速:npm/pnpm 走 npmmirror、Go modules 走 goproxy.cn、apk 换阿里云源——海外源在国内服务器上能卡到你怀疑人生;
  2. 串行编译GOFLAGS=-p=1。并行编译在 1.6G 内存上会内存抖动,单个大包(如 go-redis)能编译 25 分钟不完成;串行后峰值内存可控,速度反而稳定;
  3. cgo 注意:Vikunja 的 SQLite 驱动(mattn/go-sqlite3)是 cgo 实现,需要 gcc + musl-dev 并静态链接,CGO_ENABLED=0 会直接编译失败;
  4. swap 兜底:临时扩了 8G swapfile,编译期间内存水位平稳很多。

构建命令:

docker build -f Dockerfile.custom -t vikunja-custom:2.6.0 .

七、踩坑清单(都是真金白银换的)

解法
官方镜像默认 uid=1000 对 /app 无写权限,附件目录初始化失败导致容器反复重启 compose 里 user: "0:0"
service.jwtsecret 配置项已弃用 改用 service.secret(新版环境变量)
新版 GitLab CE 拒绝 deploy key 推送(can_push=true 也拒) 改用 Personal Access Token + HTTPS 推送
buildx 二进制下载损坏(65MB 的文件只下来 5MB),docker 命令 Bus error 下载后校验文件大小,损坏文件会导致整个 CLI 崩溃
后台构建任务随终端会话退出被误杀 用可跟踪的后台会话管理,不要裸 nohup
编译内存抖动,单包 25 分钟无进展 -p=1 串行编译 + 扩 swap
前端 go:embed 要求构建时 frontend/dist 必须存在 多阶段构建里先跑前端再编 Go

八、多端客户端现状

  • Android / iOS:官方 App(Flutter)登录页直接填服务器地址即可连自建实例;
  • macOS:官方桌面端(electron 封装)已归档停更——用 PWA 完全够用,或把 CalDAV 指给系统"提醒事项",零开发获得原生体验;
  • 鸿蒙:官方无客户端,浏览器 PWA 添加到桌面兜底;原生 ArkTS 客户端列入计划,复用同一套 REST API,客户端只做 UI,工作量可控。

九、小结

一套"自己的"全平台 Todo 系统,从零到可用:

  • 1 天:Vikunja 容器部署 + 域名反代 + HTTPS + 备份上线;
  • 半天:邀请制改造 + 管理后台解锁 + 自定义镜像构建链路打通;
  • 长期:源码、数据、构建、部署全链路自持,上游更新随时可合并。

开源 + 自托管 + 克制而精准的二次开发,是个人/小团队获得"私有 SaaS"性价比最高的路径。


本文为技术记录,所有域名、凭据均已脱敏。