logo NodeSeekbeta

依赖udp同步的笔记同步神器-anytype在v6小鸡上还是只能用host模式 记录下

在v6下搞docker,别的还好,遇到anytype这种就炸了。
https://anytype.io/
https://github.com/grishy/any-sync-bundle/tree/main
测试了1G内存的🐥会非常勉强,需要做更精细的控制。(于是放弃)
而2G内存如果用官方的yml安装,也可能在大量同步笔记时候炸鸡。所以搞了这个‘高可用’方案。

【原项目建议最小内存1G--适用于非常轻量的个人使用,且限制了数据库缓存(WiredTiger Cache)的情况下。在这种规格下,建议通过 command 限制 MongoDB 的缓存大小】

为啥anytype这么特别? (踩了v6小鸡安装的坑)

  • 协议偏好:TCP 稳健派 vs UDP 激进派
    其他程序:几乎 100% 依赖 TCP。TCP 协议栈在内核层面已经进化了数十年,能够非常聪明地处理包大小(MSS 协商)、重传和排序。

Anytype:为了追求极速同步,它优先使用 QUIC (基于 UDP)。

IPv6 的致命点:IPv6 对 UDP 的包大小(MTU)极其敏感。

TCP 发现包太大会自动缩小(MTU 路径发现)。

UDP 发现包太大(比如图片切块),在 IPv6 下直接丢弃,且不一定会给发送端任何反馈。这就是为什么你发现“文字能通,图片永远转圈”。

  • 身份绑定的复杂性 (Node Discovery)
    其他程序:DNS 告诉你是哪个 IP,你连上就行了。

Anytype:服务器会生成一个 PeerID。它不只是看 IP,它会把 IP、端口、协议和 PeerID 全部打包在一起。

IPv6 的干扰:如果你的 VPS 有多个 IPv6 地址(这在 IPv6 环境中很常见),Anytype 可能会错误地选择了那个“不可达”的临时地址进行广播,导致客户端拿到了错误的联系方式。
...

最后还是用docker的host模式,加上一些限制内存的措施,确保在2G内存上安全稳定。

附上docker-compose.yml 再加个彩蛋。

version: '3.8'

services:
  anytype-bundle:
    image: ghcr.io/grishy/any-sync-bundle:minimal
    container_name: anytype-bundle
    restart: always
    network_mode: "host"
    environment:
      - ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS=YOURdomain1.com,YOURDOMAIN2.com
      - ANY_SYNC_BUNDLE_INIT_MONGO_URI=mongodb://127.0.0.1:27017/
      - ANY_SYNC_BUNDLE_INIT_REDIS_URI=redis://127.0.0.1:6379/
      - ANY_SYNC_BUNDLE_INIT_TCP_LISTEN_ADDR=[::]:33010
      - ANY_SYNC_BUNDLE_INIT_UDP_LISTEN_ADDR=[::]:33020
      - ANY_SYNC_BUNDLE_LOG_LEVEL=info
    volumes:
      - ./data:/data
    # ─── 核心修改:等待数据库健康后再启动 ───
    depends_on:
      mongodb:
        condition: service_healthy

  mongodb:
    image: mongo:6.0
    container_name: anytype-mongodb
    restart: always
    network_mode: "host"
    # 这里通过命令直接限制 WiredTiger 缓存为 512MB
    command: ["mongod", "--replSet", "rs0", "--wiredTigerCacheSizeGB", "0.5", "--bind_ip_all"]
    deploy:
      resources:
        limits:
          memory: 1G # 容器总内存上限
    volumes:
      - ./mongo-data:/data/db
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 15s
      timeout: 5s
      retries: 5

  redis:
    image: redis/redis-stack-server:latest
    container_name: anytype-redis
    restart: always
    network_mode: "host"
    deploy:
      resources:
        limits:
          memory: 256M
    volumes:
      - ./redis-data:/data

注意,用host模式必须做好防护。docker内部的端口尤其是数据库不能暴露在公网。

#手动运行下数据库初始化(手动激活副本集)

docker exec -it anytype-mongodb mongosh --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: '127.0.0.1:27017'}]})"

结果应该是: { ok: 1 }

实时同步依赖 (Change Streams)
Anytype 是一个“实时协作”软件。当你在一台设备上修改笔记时,其他设备需要立刻收到通知。

Anytype 的机制:Anytype 的后端(Any-Sync)使用了 MongoDB 的 Change Streams(变更流) 功能。
这是一种允许应用程序“监听”数据库实时变化的机制。

MongoDB 的限制:MongoDB 规定,Change Streams 功能只能在“副本集(Replica Set)”模式下使用。
普通的“单机模式(Standalone)”是不支持监听实时变化的。

如果使用 All-in-One(全内置) 镜像),在内部有一个启动脚本,它会在后台悄悄帮你执行 rs.initiate(),
用户感觉不到它的存在。

但现在的情况是:为了适配你的 VPS 内存并保证稳定性,我们手动拆分了架构,
使用了 Minimal 镜像 + 官方 MongoDB 镜像。

官方标准 MongoDB 镜像(mongo:6.0)的设计原则是“纯净”,它不会自动帮你初始化副本集,
因为它不知道你是否想加入现有的集群。
nano /etc/default/ufw
确保其中的 IPV6=yes
sudo ufw default deny incoming
sudo ufw default allow outgoing

sudo ufw allow 33010/tcp
sudo ufw allow 33020/udp
sudo ufw allow ssh #或者sudo ufw allow 2222/tcp
sudo ufw enable
sudo ufw status verbose
#应该只有下面这几个
To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
33010/tcp                  ALLOW IN    Anywhere
33020/udp                  ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)
33010/tcp (v6)             ALLOW IN    Anywhere (v6)
33020/udp (v6)             ALLOW IN    Anywhere (v6)

多设备同步迅速!

J0BpLd82stogtMQ59qfLHX4qfiPXPZ1X.webp

  • 好文。我都是用2c4g的机子部署的。
    xhj017

  • @Cosmossofa #1 mongo太炸内存了 我在1G上各种失去同步的响应 不知道官方指导说的1G内存怎么跑的(有swap也不行啊)

  • 本地优先是我需要的。小鸡炸了也没事。

    要保护好client-config文件和助记词 -- 千万不要泄露/丢失。

    Anytype 的底层设计逻辑:“本地优先(Local-first)”

    是的,你的数据非常安全。 即使你的 VPS 彻底爆炸、硬盘报废且没有任何备份,你手机和电脑里的数据依然是完整且可用的。


    🛡️ 为什么数据不会丢失?

    Anytype 与 Notion 或 Slack 不同,它不是一个“云端应用”,而是一个 P2P(点对点)应用

    • 数据的主权在客户端:你的笔记、图片和对象首先存储在你设备的本地数据库中。
    • 服务器的角色:你的自建服务器(Bundle)在架构中被称为 Sync Node(同步节点)。它的作用像是一个“加密中转站”和“全天候在线的备份镜像”。
    • 断网可用:即便服务器关机,你依然可以在电脑上正常写笔记、看图。等到服务器修复后,客户端会自动把这段时间产生的增量数据“推”上去。

    🔄 如果服务器彻底丢失,如何恢复?

    如果你无法找回 ./data(即丢失了服务器的 PeerID 和私钥),你无法让新服务器“伪装”成旧服务器。但你可以通过以下流程轻松恢复:

    1. 导出备份(最稳妥)

    在服务器出问题期间,或者在尝试连接新服务器之前,在 Windows 客户端上执行:

    • 进入 Settings -> Flow -> Export Data
    • 选择 Anytype 格式(导出为 .anytype 备份文件)。这会包含你所有的空间、页面和关联关系。
    2. 建立新“网络”
    1. 在新 VPS 上按照我们之前的 Docker 配置重新启动一套全新的环境。
    2. 获取新的 client-config.yml
    3. 客户端切换
    1. 退出登录:在电脑端 Logout。
    2. 清理旧设置:删除电脑上的 %AppData%/anytype2 文件夹(注意:如果你有多个账户,确保备份了 Recovery Phrase)。
    3. 重新加入:使用你的 Recovery Phrase 登录,并在登录界面选择 Self-hosted,导入新服务器的配置文件。
    4. 数据重推

    一旦你登录成功,由于你手里的 Recovery Phrase 没变,客户端会发现空间是空的,它会利用本地缓存的数据(或通过导入刚才的备份文件)重新将所有数据 Push 到新服务器上。


    ⚠️ 唯一需要保护的东西:Recovery Phrase

    对于 Anytype 用户来说,唯一的致命损失是丢失“助记词(Recovery Phrase)”

    • 服务器丢了:可以重建。
    • ./data 丢了:可以重申领身份证(New PeerID)。
    • 助记词丢了:没有任何人(包括 Anytype 官方或你的服务器)能解密你本地的数据库,那才是真正的数字末日。

    💡 建议的下一步:

    既然你现在的服务器已经运行在双栈网络下,我建议你现在就去电脑端执行一次 Export Data,保存一个 .anytype 文件到本地硬盘或 U 盘里。

  • @tof #0

    请教大佬:你的自托管方案在安卓客户端可以正常使用吗?
    我在windows端正常,但是安卓客户端无法加载配置文件。。。

  • @tof #5

    那可能是我的部署问题,我试一下你的方案吧,感谢

  • @Nodeseeker666 #6 我重新部署了 compose config应该差不多。修改后的--这次我没考虑v6

    services:
      anytype-bundle:
        image: ghcr.io/grishy/any-sync-bundle:minimal
        container_name: anytype-bundle
        restart: always
        ports:
          - "33010:33010"
          - "33020:33020/udp"
        environment:
          # 这里填入你最终决定的域名
          - ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS=YOUR-DOMAIN.com
          - ANY_SYNC_BUNDLE_INIT_MONGO_URI=mongodb://anytype-mongodb:27017/
          - ANY_SYNC_BUNDLE_INIT_REDIS_URI=redis://anytype-redis:6379/
          - ANY_SYNC_BUNDLE_LOG_LEVEL=info
        volumes:
          - ./data:/data
        depends_on:
          anytype-mongodb:
            condition: service_healthy
          anytype-redis:
            condition: service_started
        networks:
          - anytype-net
    
      anytype-mongodb:
        image: mongo:6.0
        container_name: anytype-mongodb
        restart: always
        # 自动开启副本集,并确保节点绑定所有接口
        command: ["mongod", "--replSet", "rs0", "--wiredTigerCacheSizeGB", "0.5", "--bind_ip_all"]
        volumes:
          - ./mongo-data:/data/db
        healthcheck:
          # 优化后的初始化逻辑,直接尝试初始化,忽略重复错误
          test: ["CMD", "mongosh", "--quiet", "--eval", "try { rs.initiate() } catch(e) {}; db.adminCommand('ping')"]
          interval: 10s
          timeout: 5s
          retries: 5
          start_period: 20s
        networks:
          - anytype-net
    
      anytype-redis:
        image: redis/redis-stack-server:latest
        container_name: anytype-redis
        restart: always
        networks:
          - anytype-net
    
    networks:
      anytype-net:
        driver: bridge
    

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有63942位seeker

🎉欢迎新用户🎉