返回站点目录

我不是咕咕鸽

https://blog.laoda.de/

该站点暂未提供站点简介,可继续查看文章聚合、状态检测与相关资源入口。

技术 Docker服务器

状态

状态正常

非常久没有和大家更新了,因为最近家里和公司事情比较多。 今天趁着难得的闲暇赶紧和大家更新一波。 我手上的这台 iON VPS,原来的续费价格是 20.19 美元/年 。 不知不觉已经7年时间了。 最近收到 iON 的通知:从 2026 年 8 月 1 日 起,这台机器将被调整为 40 美元/月 的 iON Pro 1 套餐。 不升级套餐的话,8月5号正式停机。(说实话有点流氓了,至少等合约结束吧,而且才给了5天的时间迁移数据) 也就是说,原来一年只要 20.19 美元,以后一年要 480 美元,续费成本接近原来的 24 倍! 所以,是时候和它告别了…… 11.11 美元年付套餐已经有用户反馈收到类似通知;我自己的 20.19 美元年付套餐已经确认收到。至于 22.22 美元年付套餐是否会统一调整,我猜大概率也会,包括之前还有68刀/年的,估计也会被清退。(可以看出公司经营状况确实不咋地了) 至此,一代神鸡iON,就此消失! 借着这个机会,除了回顾这台用了六七年的 iON,我也想着打开探针,把自己还在持有的 VPS 和独立服务器重新盘点一遍。 本期视频链接: 年付 20.19 美元,变成月付 40 美元 这是我收到的通知截图, 我这个账户对应的调整如下: 项目 原套餐 调整后的套餐 套餐 iON-L09 iON Pro 1 计费 20.19 美元/年 40 美元/月,即 480 美元/年 CPU 1 vCPU 4 vCPU 内存 2 GB 8 GB 硬盘 40 GB SSD 160 GB 流量 1 TB/月,出站单向计费 5 TB/月 生效日期 — 2026 年 8 月 1 日 新套餐的配置确实高了不少,但我过去留着它的原因就是便宜、三网联通优化和单向1T流量。价格变成 40 美元/月之后,他的替代品可太多了,实在是没有继续保留的必要。 曾经的“神鸡” 我第一次知道这个套餐,是在 HostLoc 上看到有人讨论。后来我花了三四百元溢价收来,从 2019 年开始续费,一直用到现在。 它的配置放到今天其实在这个价位依旧能打:1 核、2 GB 内存、40 GB SSD、1 Gbps 带宽。但它每月有 1 TB 出站单向计费 流量,过去还长期有 AS4837 联通优化回程。对于反向代理和日常小服务来说,一个字,香! 我最早拿它做反向代理,后来又把它当过一段时间的你懂的的主力机器。六七年下来,即使算上当初的二手溢价,它也早就回本了。单看价格、线路和使用年限,这仍然是我买过最划算的服务器之一。 iON 这些年也不是一直顺风顺水。线路经历过多次调整;2023 年 5 月,洛杉矶机房还曾因烟雾报警停电,等待消防人员排查,我这台机器大约 20 天后才恢复。后来线路恢复过 AS4837,但稳定性和服务状态都有过反复。 完整的历史更新可以看我之前的文章: 。 顺便盘点一下我手上的服务器 借着这次 iON 退场,我又打开了自己的 。 打算回顾一下手头上的小鸡们。 下面这些价格和配置,都是我在 2026 年 8 月 2 日录制视频时 ,自己账户里显示的套餐与续费信息,很多都是活动套餐,确实已经“绝版”了,不过能顶多久,没人知道。 只能说不在乎天长地久,只在乎曾经拥有吧。 BuyVM:便宜的存储块和不限流量 我的 BuyVM 最早在卢森堡,后来迁到了瑞士。现在账户里显示 VPS 续费 54 美元/年,折算下来每月 4.5 美元;另外还挂了一块 256 GB 的存储块,续费只要 15 美元/年。单看容量和流量,它到现在依然很有特色。 我当初买它,主要就是看中不限流量和便宜的存储块,拿来刷 PT 很舒服。后来不怎么玩 PT 了,这台机器也就逐渐进入吃灰状态。录视频时我重新登录后台看了一眼,本轮在线时间只有 23 天,平时基本没跑什么重要服务。 所以 BuyVM 对我来说其实用途已经消失了。机器本身和存储块都不贵,但如果继续放着吃灰,好像也是一笔支出,后面看自己经济情况是否继续续费。 还记得盯着监控抢卢森堡的存储块的日子…… 往期回顾: CloudCone:便宜,性价比不错 我的 CloudCone 是活动时抢到的套餐,1 核、2 GB 内存、60 GB 硬盘,续费只要 10.44 美元/年。这个价位确实便宜,配置也够跑一些轻量应用,所以我一直留着。 不过最近确实遇到一个小插曲,截止写文章的时候,它因为迁移机房已经离线不止两周,控制面板里一直显示 Offline,尝试 Reboot 也没有恢复。原来有一个简单应用和 Telegram 群机器人跑在上面,停机之后我只能把机器人迁到别的服务器。 一年十美元出头的机器,买来折腾或做非关键服务没有问题。但这次长时间停机也再次提醒我:阿三真的很不靠谱…… 往期回顾: DMIT:接替 iON 的主力机器 我有两台 DMIT,一台是 EB CORONA,一台是 PRO WEE,都是活动时抢到的,续费分别是 49.9 美元和 36.9 美元。CORONA 有 2 TB 流量,线路也有优化,但目前基本处于吃灰状态。 真正派上用场的是 PRO WEE。它后来接替 iON,成为我的主力机器,线路表现不错,价格也还能接受。不过它每月只有 550 GB 流量,而且是双向计费;和 iON 的 1 TB 出站单向流量相比,日常使用不能那么随意了。 Hetzner:博客主机,稳定省心,欧洲阿里云 我目前主要使用三台 Hetzner,博客也部署在这里。它的服务器镜像、快照、备份和防火墙功能都比较完整,控制面板成熟,价格和性能也比较均衡,属于不需要经常操心的类型。 录视频时,博客这台机器本轮已经连续在线 320 天。之前博客偶尔无法访问,并不是 Hetzner 服务器本身不稳定,而是遇到过流量攻击,加上博客程序存在内存泄漏问题。后来在Halo项目负责人仙总的帮助下,成功迁移数据库并处理掉内存问题,最近这段时间一直比较稳,所以博客还会继续放在这里。 省心! 注册账号之前需要身份证,现在不知道怎么样了,有新注册的小伙伴也欢迎评论区留言。 往期回顾: HostHatch:3 年 65 美元的大盘鸡 HostHatch 的特色就是大盘鸡,这台配置:1 GB 内存、1 TB 硬盘、10 TB 流量,续费 3 年 65 美元。最初买它主要也是刷 PT,也打算拿来备份体积较大的内容。录视频时,它本轮已经稳定在线 400 多天,单看价格、容量和在线表现都很划算。 它的问题也很明确。1 TB 硬盘虽然够大,但只有 1 GB 内存,刷 PT 时内存和CPU 偶尔会顶满,只能控制任务和速度;另外新机开通和工单回复都偏慢。现在我很少再管它,容量优势没有真正用起来,后面可能出掉,也可能到期不续费。 往期回顾: Naranja Tech:18 欧元年付的荷兰小钢炮 这台 Naranja Tech 是黑五买到的荷兰套餐,2 GB 内存、50 GB 硬盘、5 TB 流量,年付 18 欧元。欧洲机器给的流量比较足,这个配置拿来跑轻量项目也不会太局促。 我有一个小项目长期部署在上面,实际使用下来很稳。录视频前不久我手动重启过一次,所以监控只显示在线 55 天;重启之前,它已经连续运行了 400 多天。它没有特别花哨的控制面板和附加功能,但价格、性能和稳定性比较平衡。 netcup:性能高,但要留意合约取消时间 我以前用的是 netcup 很有名的 RS 2000。它更像一台半独立服务器,CPU 资源是独享的,性能比普通 VPS 更稳定;每月流量超过 20 TB 后才会限速到 500 Mbps,过去很多人买它就是为了刷 PT。 后来因为预算和实际需求,我用不到这么重的配置,就换成了月付 4.74 欧元的 VPS 500:4 核、4 GB 内存、128 GB SSD。这个套餐在 24 小时内流量超过 2 TB 后会限速到 200 Mbps,对我来说仍然接近够用,性能和价格也比较平衡。 netcup 真正需要注意的是合约规则。确定不续费时,要在 Cancellation 页面提前取消,最好预留一到两个月;错过窗口后仍可能继续出账单,拖着不处理还会进入催缴流程(律师函警告!)。账号里长期没有任何服务,也可能被清理。机器本身没什么问题,但德国公司的合同管理比较严格,不能按照普通月付 VPS 的习惯随用随停。 OVH:硬盘“开奖”成功的独服 OVH 以前经常放出一批带有“开奖”性质的独立服务器:标称配置不变,但实际开通后,硬盘数量或带宽偶尔会高于页面规格。我当时也买了一台,标称是 2×960 GB SSD,实际开出来却是 4×960 GB,算是硬盘开奖成功。 这台机器使用 Intel Xeon E3-1231 v3、32 GB 内存,月付 16.99 欧元。我把大约一半硬盘作为实际可用空间,另一部分拿来做 RAID。对于需要较大容量、又希望有冗余的服务来说,这个价格还是不错的。 OVH 的另外两个特点是自带 DDoS 防护和不限流量,适合放容量或流量需求较大的服务。它的官网和账户体系不算好用,空账户长期没有服务也可能被清理,但这台机器本身已经稳定使用了很久,我暂时没有淘汰它的打算。 往期回顾: RackNerd:适合新手折腾的低价 VPS 我的 RackNerd 年付大约 14.18 美元,512 MB 内存、10 GB 硬盘,属于很典型的入门低价 VPS。它和 CloudCone 类似,配置不高,但一年只要几十元人民币,非常适合拿来熟悉 Linux、练习部署环境或跑极轻量的服务的新手小伙伴。 这台机器买来之后我一直正常续费,实际上很少操作,基本也是吃灰状态。 它最重要的特色就是试错成本低,非常适合新手第一次接触 VPS。 之前整理的便宜的套餐在这边,有兴趣的小伙伴可以看看能不能捡漏: 往期回顾: SpartanHost:主力反代鸡 我的 SpartanHost 是 2021 年黑五抢到的 AMD 半价套餐,最初价格是 31.68 美元/年。后来第二年黑五我让官方把配置和价格一起翻倍,升级到 2 GB 内存、50 GB 硬盘,套餐本身续费约 63.63 美元/年;再加上额外的 DDoS 防护,目前总续费约 101.76 美元/年。 它曾经长期承担博客反向代理,洛杉矶线路对联通比较友好。后来博客持续遭到 DDoS 攻击,一个月要花好多钱买额外流量,定不值了我才把反代迁走,后来我加了DDoS 防护包,承担我其他几个网站的反代任务。 SpartanHost 的稳定性和工单体验一直不错,回复速度很快(老板亲自指挥,亲自回复),没想到不知不觉我都已经用了接近五年了…… 比较遗憾的是,它这两年黑五似乎没有再做当年那种力度的活动,讨论热度也低了不少。 往期回顾: BandwagonHost:我手上数量最多,也最稳定的一组 我现在持有最多的还是搬瓦工,主要有四台。它们的纸面参数配置一般,但稳定性已经经历了考验:录视频时,其中一台已经连续在线 998 天,49 美元那台更是连续在线 1043 天,没有重启也没有掉线。 CN2 GIA 限量版 :实付 46.59 美元/年,512 MB 内存、10 GB 硬盘、500 GB 流量。配置很小,但能切换 CN2 GIA、CN2 GIA-E 和日本软银等多个机房,我平时放在日本机房,作为备用机器使用; MEGABOX PRO :NodeSeek 联名款,45.68 美元/年,2 GB 内存、40 GB SSD、2 TB 流量。电信、联通、移动都有不同程度的线路优化,但不能切换机房;我当时有点跟风购买,现在基本吃灰; The Plan V2 :110 美元/年,2 GB 内存、40 GB 硬盘、2 TB 流量,是目前的博客反向代理。本轮已经在线 541 天,可以切换迪拜、香港 CMI、日本软银等更多机房,价格最高; 2018 双十一套餐 :29.88 美元/年,2 GB 内存、40 GB 硬盘、1 TB 流量,是我二手收来的老套餐。我把不少基础应用放在上面,硬盘已经接近用满;它只能切换部分机房,线路是 CN2 GT,晚高峰可能拥堵,但这个续费价很划算。 我一直喜欢搬瓦工,一个重要原因是 KiwiVM 控制面板成熟。它有独立账号、密码和两步验证,需要把机器借给别人或单独管理时,不用交出主账户。切换机房后,服务器配置和数据都保持不变,通常只需要改一下新的 IP 就能丝滑恢复业务;自动快照和备份也比较方便。 搬瓦工的常规套餐并不便宜,真正有吸引力的往往是少量活动款。 搬瓦工的套餐整理在这里: 。 往期回顾: 写在最后 iON 这台 20.19 美元年付 VPS,我用了六七年,已经足够值回当初付出的价格。它的退场当然有点可惜,但也再次提醒我们: 没有永远的低价套餐,也没有真正可以传家的 VPS。 大家最好不要为“绝版”支付高于机器本身好几倍的溢价,也不要把重要数据的安全寄托在任何一家服务器厂商身上。日常做好备份,让自己始终保留迁移的能力,比守着一台所谓的传家宝机器更重要。 关于服务器备份,我之前整理过一篇总览:

1. 唠嗑 拖更好久了,今天来更新一下。(顺便吐槽今天德国踢的太臭了) 如果你平时用 Obsidian,应该多少都纠结过同步的问题。 先简单介绍一下 Obsidian。 Obsidian 是一个本地优先的 Markdown 笔记软件,所有笔记本质上都是一个个 .md 文件,放在自己的电脑或手机里。你可以用它写日记、做读书笔记、整理项目资料,也可以把它当成自己的「第二大脑」或者个人知识库。 它和普通云笔记最大的区别是:数据不被某个平台锁死。哪天不用 Obsidian 了,这些 Markdown 文件依然可以直接打开、迁移、备份。 这两年大模型火起来之后,Obsidian 的玩法也更有意思了。你可以把自己的笔记库整理成长期知识库,再配合 ChatGPT、Claude、Cursor、Cherry Studio,甚至 MCP 这类工具,让 AI 在你授权的范围内读取、总结、检索、改写自己的资料。 比如: 让 AI 帮你总结过去写过的文章 从自己的笔记里找某个项目的资料 把零散记录整理成博客、周报、方案 基于自己的知识库问答,而不是每次都从零开始问 AI 但前提是,你的笔记要能稳定同步、备份,并且在多台设备上都能访问。不然电脑上写了一半,手机上看不到;手机拍的图片附件,电脑上又没同步过来,体验就很割裂,很难顶。 今天咕咕给大家分享一个可以私有化部署的 Obsidian 同步服务: Fast Note Sync Service 。 它搭配 Obsidian 插件 Fast Note Sync 使用,可以把自己的 Obsidian 笔记库同步到自己的服务器上,支持多端实时同步、附件同步、历史版本、回收站、分享链接,甚至还支持 REST API 和 MCP。 简单来说,就是: 自己搭一个 Obsidian 私有同步服务器,让不同系统的电脑、手机、平板之间的笔记可以实时同步。 笔记软件 2. Fast Note Sync 简介 Fast Note Sync 分成两部分: Fast Note Sync Service :服务端,部署在自己的 VPS 上 Fast Note Sync for Obsidian :Obsidian 插件,安装在自己的 Obsidian 里 它具体都能干什么? Obsidian 笔记多端实时同步 支持图片、音频、视频等附件同步 支持 .obsidian 配置同步 支持笔记历史版本 支持回收站恢复 支持离线编辑后自动合并 支持通过网页后台管理笔记库 支持分享笔记链接 支持 REST API / MCP,方便后续接入 AI 工具 适合的场景包括: 不想订阅 Obsidian Sync,但又想多设备同步 想把笔记数据放在自己的服务器上 想在手机、电脑、平板之间同步同一个 Vault 想给自己的知识库留一个 Web 管理后台 想后续把 Obsidian 笔记接入 AI / MCP 工具 注意:目前官方 Roadmap 里端到端加密还在计划中,我们都是特别注意隐私的,所以强烈建议只部署在自己信任的服务器上,不要放到来路不明的机器上。 2.1 和 iCloud / Obsidian Sync 怎么选? 看到这里,可能有小伙伴可能会问: 我直接用 iCloud 不就好了?或者买官方 Obsidian Sync 不就好了?为啥还要自己折腾这个? 这个问题要分情况看。 如果你所有设备都是 Apple 生态,比如 Mac + iPhone + iPad,而且笔记不多、附件不大,那 iCloud 确实是最省事的选择。系统自带,不需要额外部署,也不用折腾服务器。 但是 iCloud 本质上是「文件夹同步」,不是专门为 Obsidian 做的同步服务。它没有 Obsidian 专用的网页后台、同步日志、Vault 管理、REST API、MCP、Git 自动化这些东西。如果你还想在 Windows、Android、Linux 上一起用,体验就没那么统一了。 官方 Obsidian Sync 是最省心、最稳妥的方案。官方页面写得很清楚,它支持端到端加密、版本历史、跨平台同步、离线编辑后再合并,还能细粒度控制同步哪些配置和文件。 价格方面,截至 2026 年 6 月 30 日,Obsidian Sync Standard 年付是 4 美元/月,月付是 5 美元/月,包含 1 个同步库、1GB 总存储、单文件 5MB 限制、1 个月版本历史;Sync Plus 年付是 8 美元/月,月付是 10 美元/月,包含 10 个同步库、10GB 总存储、单文件 200MB 限制、12 个月版本历史,还可以升级到 100GB 存储。 Fast Note Sync 是给这些人准备的: 已经有自己的 VPS 或家里服务器 想把 Obsidian 同步服务放在自己手里 不想按月订阅,但愿意自己维护服务 附件比较多,想用自己的磁盘空间 想要 Web 管理后台,可以在线查看笔记、附件和同步日志 想把 Obsidian 接入 REST API / MCP,让 AI 工具读写自己的知识库 想折腾 Git 自动化、远程备份、对象存储、WebDAV 等扩展玩法 简单对比一下: 方案 适合谁 优点 不足 iCloud Apple 全家桶用户 系统自带,最省事 跨平台一般,本质是文件夹同步 Obsidian Sync 想省心、重视稳定和隐私的人 官方维护,端到端加密,体验完整 需要订阅,有存储和单文件大小限制 Fast Note Sync 有 VPS、喜欢自托管的人 私有部署、空间自己定、支持后台/API/MCP 需要自己维护,目前端到端加密还在 Roadmap 所以咕咕的建议是: 如果你只是 Mac + iPhone 轻度同步,继续用 iCloud 就行。 如果你想要最稳、最省心,而且不差钱,官方 Obsidian Sync 依然是首选。 但如果你本来就有 VPS,又想把 Obsidian 变成一个真正属于自己的私有知识库,甚至后面还想接 AI / MCP / 自动化,那 Fast Note Sync 就很值得折腾一下。 不过还是要提醒一句:目前 Fast Note Sync 的端到端加密还在 Roadmap 里,服务端是可以处理你的笔记内容的。所以不要部署在不信任的服务器上,也不要把后台裸奔公网。重要笔记库第一次同步前,建议先做好本地备份。 3. 相关地址 官方 GitHub 地址: 服务端: Obsidian 插件: Obsidian Sync 官方页面: 截至 2026 年 6 月 30 日,服务端大概 1.7k 个 star,插件大概 2.5k 个 star,感兴趣的小伙伴可以去给项目点个星星。 当然如果你想自己用 Docker 搭建一个,那我们就继续往下! 4. 搭建环境 服务器:咕咕这边建议用 1G 内存以上的 VPS,比如 ,当然你也可以选择其他 。重点看磁盘空间,因为后面笔记附件也会存到服务器上。 系统:Debian 11 / Debian 12 / Ubuntu 都可以。 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(等有空了补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装目录: sudo -i mkdir -p /root/data/docker_data/fast-note-sync cd /root/data/docker_data/fast-note-sync 接着我们来编辑 docker-compose.yml : vim docker-compose.yml 贴入下面内容: services: fast-note-sync-service: image: haierkeys/fast-note-sync-service:latest container_name: fast-note-sync-service restart: unless-stopped ports: - "9000:9000" volumes: - ./storage:/fast-note-sync/storage - ./config:/fast-note-sync/config 其中左边的 9000 可以改成服务器上没有用过的端口,比如 9010 。 修改完成之后,英文输入法下按 i 修改,完成之后按 esc ,然后输入 :wq 保存退出。 6.3 下载默认配置文件 官方默认配置比较长,我们直接拉一份下来: cd /root/data/docker_data/fast-note-sync mkdir -p config storage wget -O config/config.yaml https://raw.githubusercontent.com/haierkeys/fast-note-sync-service/master/config/config.yaml 如果 GitHub 拉取比较慢,可以用作者提供的 CNB 镜像: wget -O config/config.yaml https://cnb.cool/haierkeys/fast-note-sync-service/-/git/raw/master/config/config.yaml 然后编辑配置文件: vim config/config.yaml 先改这几个地方: server: ext-api-url: "https://note.example.com" #改成你自己之后用的域名链接 security: auth-token-key: "换成一串足够长的随机字符串" share-token-key: "再换成另一串足够长的随机字符串" user: register-is-enable: true ext-api-url 改成你自己的域名。 auth-token-key 和 share-token-key 不要用默认值,随便找个密码生成器生成两串长一点的随机字符串即可。 这里先保持: register-is-enable: true 因为我们第一次进去还要注册账号。等注册完第一个账号之后,后面再关掉注册。 6.4 查看端口是否被占用 查看端口是否被占用,以 9000 为例: lsof -i:9000 如果啥也没出现,表示端口未被占用,可以继续。 如果出现: -bash: lsof: command not found 运行: apt install lsof 如果端口被占用了,就把 docker-compose.yml 里的左边端口改一下,比如: ports: - "9010:9000" 后面反向代理的时候也记得填新的端口。 6.5 启动 Fast Note Sync Service cd /root/data/docker_data/fast-note-sync docker compose up -d 查看日志: docker compose logs -f 没报错的话,理论上访问: http://服务器IP:9000 就能看到后台页面。 不过这个服务是拿来同步笔记的,不建议直接裸奔公网,咕咕建议一定要配置 HTTPS 反向代理。 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名购买、域名解析: 之后登录 Nginx Proxy Manager,添加一个新的 Proxy Host。 按下面这样填: Domain Names: note.example.com Scheme: http Forward Hostname / IP:服务器内网 IP 或 Docker 网桥 IP Forward Port: 9000 Websockets Support:打开 Block Common Exploits:打开 SSL 那边: 勾选 Request a new SSL Certificate 勾选 Force SSL 填邮箱 同意协议 保存 Fast Note Sync 会用到 WebSocket,同步服务要实时通信,所以 NPM 里 Websockets Support 记得打开。 配置好之后,访问你自己的域名,比如: https://note.example.com 就能进入后台。 7.2 利用 Caddy 反向代理 如果你用的是 Caddy,也可以这样写: note.example.com { reverse_proxy 127.0.0.1:9000 } Caddy 会自动申请和续期 SSL 证书,适合不想点面板的小伙伴。 8. 初始化账号与关闭注册 设置管理员 第一次打开网页后台,会让你注册账号。 注册完成并登录之后,我们建议马上关闭注册,避免别人也跑来注册。 回到服务器: cd /root/data/docker_data/fast-note-sync vim config/config.yaml 找到: user: register-is-enable: true 改成: user: register-is-enable: false 然后重启: docker compose restart 这样后面就不会开放注册了。 9. Obsidian 插件配置 9.1 安装插件 打开 Obsidian: 设置 -> 第三方插件 -> 浏览 搜索: Fast Note Sync 如果插件市场里能搜到,直接安装启用即可。 如果搜不到,可以去 GitHub Releases 手动下载: 下载这几个文件: main.js styles.css manifest.json 然后放到你的 Obsidian 库目录: 你的库/.obsidian/plugins/fast-note-sync/ 重启 Obsidian 后启用插件。 9.2 授权连接服务端 回到 Fast Note Sync Service 的网页后台,访问你自己的域名: https://note.example.com 右上角可以更改语言: 左侧进入: Note Vaults 可以看到官方提供了两种方式: One-click Authorization for Obsidian Copy API Config 如果你的电脑能正常唤起 Obsidian,可以直接点 One-click Authorization for Obsidian 。 如果不行,就点 Copy API Config ,然后手动粘贴到 Obsidian 插件设置里。 配置完成之后,Obsidian 就会开始和服务端同步。 电脑上我试了下没问题。 iPhone上我遇到无法粘贴复制的内容的问题, 只能在手机浏览器里登录网页,然后用一键授权的方式来配置了 = = 10. 使用体验 部署好之后,大概就是这样: 电脑上新建一篇笔记 手机 Obsidian 打开同一个 Vault 修改会同步到服务端 其他设备在线后自动拉取变化 图片、附件也可以一起同步 网页后台可以查看 Vault、笔记、附件和同步日志 如果你平时 Obsidian 里有很多图片,建议服务器磁盘稍微大一点,不然附件多了之后很快就会占空间。 另外,如果你准备把 .obsidian 配置也同步,建议先拿一个测试库试一下,确认插件、主题、配置都没问题之后,再同步主力库。 11. 更新与卸载 更新 cd /root/data/docker_data/fast-note-sync docker compose pull docker compose up -d 停止 docker compose down 卸载 cd /root/data/docker_data/fast-note-sync docker compose down cd /root/data/docker_data rm -rf fast-note-sync 删除前记得先备份: /root/data/docker_data/fast-note-sync/storage /root/data/docker_data/fast-note-sync/config 你的笔记数据和配置都在这里。 12. 注意事项 这个项目目前还没有端到端加密,官方 Roadmap 里写了后续计划支持,所以不要部署在不信任的服务器上。 第一次注册完账号之后,记得关闭注册。 auth-token-key 和 share-token-key 不要用默认值。 建议只通过 HTTPS 域名访问,不要直接把 9000 端口裸露给公网。 Nginx Proxy Manager 里记得打开 WebSocket 支持。 重要笔记库第一次同步前,建议先备份一份本地 Vault。 如果你的笔记库里有很多大附件,第一次同步可能会比较慢,耐心等一下。 13. 最后 Fast Note Sync Service 这个项目还是挺有意思的,它不是简单的 WebDAV 同步,而是专门给 Obsidian 做的一套私有同步服务。 相比 WebDAV,它多了网页后台、历史版本、回收站、附件管理、分享、REST API、MCP 这些能力;相比官方 Obsidian Sync,它的优势就是可以自己部署,数据放在自己的服务器上,空间也由自己的硬盘决定。 当然,目前端到端加密还没完成,所以更适合放在自己的 VPS 或家里服务器上自用。 如果你本来就是 Obsidian 重度用户,又刚好有自己的 VPS,可以折腾一下。

作为一个需要正常访问国际互联网的人士,为了关键时刻不掉链子(尤其是最近,有的小伙伴应该有体会)我觉得有必要给自己增加一个备选方案。 我之前去德国正好 ,支持eSIM,月底又要去珠海出差,顺便去澳门一趟,就想着要不索性换个思路:直接用一张境外 SIM 卡在大陆漫游,原生 IP,开机即用,全程不需要任何额外姿势。 我先前去香港买过一个hahasim的卡,保号1年10元,接接验证码还不错,不过没流量(或者说流量非常贵)。 对比了 CMHK、CTM 快卡、澳门蓝卡几家之后,最终选了 3HK 大湾区 DIY 储值卡:HK$268 一年 45GB 流量(折人民币约 235 元,5 元/GB),支持 eSIM 远程购买、邮箱收码扫码即用;大陆漫游可在移动/联通/电信三网之间切换(一般是联通);开热点之后电脑、iPad 也可以共享香港 IP。性价比、便捷度、稳定性三个维度综合下来,我个人认为是目前能找到的最优解,于是有了这篇折腾记录。 以下内容需要你有一部支持海外eSIM的手机(比如港版iPhone,国产的iPhone Air我估计不行) 首先登录官网购买,选择365日组合——大湾区(内地、香港、澳门) 官网地址: 外游数据这个,自己选择,实测好像即使选暂不使用也会开始套餐计费时间, 在大陆的话直接选择eSIM,实体卡只能寄到香港地址,(注意国产的iPhone 17 Air好像只支持大陆的eSIM,这个可能用不了) 确认套餐没问题,40G+5G 输入一个邮箱,会帮你创建账号, 收到验证码,登录,建立账户, 选择付款方式,支付宝微信都可以,我这边用了汇丰的Red信用卡,有4%的返现。 成功! 邮件会收到一个二维码, 打开手机的蜂窝网络,添加eSIM, 很快就能加上,记得打开数据漫游, 下载官方APP——My3 用手机号码加验证码登录,然后记得打开这个开关 蜂窝数据切换到3HK的卡就能上网了!不需要任何额外姿势。 如果要在香港使用通话和短信功能,需要在app里实名一下。 非香港人士选港澳通行证就可以了。 注意: 这张卡是"流量套餐 + 通话短信按量扣费"模式。年套餐 268 港币里只包含 45GB 数据流量,不含任何通话或短信额度。需要打电话/发短信,得在 My3 App 里单独充值余额,按下面这个标准扣费—— 接验证码(大陆+香港):免费 ✅ 用来注册海外平台超香 大陆漫游接打电话:HK$6-8/分钟 偏贵,建议走微信语音 大陆发短信:HK$3.5/条 贵,能用微信就别用 SMS 香港本地通话:可单独买 HK$20/10000 分钟包,几乎白送 总结一句:当数据卡用最划算,通话短信都用其他方式替代。 大家有别的更好用的卡也欢迎评论区分享!

1. 唠嗑 聊到「私人网盘」,大部分人第一反应都是 Nextcloud、Seafile 这种「全家桶」——文件、日历、联系人、笔记、视频会议,功能丰富,大而全,但是比较吃机器的配置,VPS 性能差点儿的(比如 1G 内存的),像 Nextcloud 可能都跑不动。 很多时候我们的需求其实非常朴素: 「我能不能在 Mac 的 Finder 里直接打开 VPS 上的某个目录,像本地文件夹一样拖拖拽拽?」 答案是可以,而且根本不用上 Nextcloud,一个轻量级的 WebDAV 服务就够了。 2. hacdias/webdav 简介 今天咕咕给大家介绍的 就是这种「需求小但精准」的代表作——Go 写的独立 WebDAV 服务器,整个程序就一个二进制文件,配置文件用 YAML,跑起来内存占用几十兆。 这个作者也是之前和大家介绍的 File Browser 项目的作者。 旧文回顾: 它能干什么? 把 VPS 上的某个目录通过 WebDAV 协议暴露出来 支持多用户、多目录、多权限(CRUD 自由组合) 支持 bcrypt 哈希密码(不用让明文密码裸奔) 反代友好(配合 Nginx Proxy Manager 体验非常丝滑) 没有 Web 界面、没有数据库、没有花里胡哨的功能 适合的场景包括: Obsidian / Joplin / Logseq 这类笔记软件的云端同步 KeePass / KeePassXC 数据库远程同步 手机相册备份目标盘 多设备共享一个工作目录 摆脱 iCloud / OneDrive / Dropbox 的容量焦虑 部署完之后,Mac、Windows、iPhone、Android,以及 Obsidian、Joplin、KeePassXC 这种支持 WebDAV 的应用都能直接挂上来用。 可以让你告别云盘焦虑! 3. 相关地址 官方 GitHub 地址: (目前 5.5k 个 star,欢迎大家去给项目点星星!) 当然如果你想自己用 docker 搭建一个,那我们就继续往下! 4. 搭建环境 服务器:咕咕这边用的香港的 ,虽然 1 核 512M 都够用,重点看磁盘大小和带宽,但是因为用的 docker 搭建,还是建议服务器内存 1G 以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩周补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/webdav cd /root/data/docker_data/webdav 接着我们来编辑下 docker-compose.yml vim docker-compose.yml services: webdav: image: hacdias/webdav:latest container_name: webdav restart: unless-stopped ports: - "6065:6065" # 左边的端口可以换成 vps 上没有用过的端口 volumes: - ./config.yml:/config.yml:ro - ./data:/data 其中的左边的 6065 可以改成服务器上没有用过的端口。 修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 生成 bcrypt 密码 为了不让明文密码裸奔在配置文件里,我们用 bcrypt 哈希一下密码。Debian / Ubuntu 上一行命令搞定: apt install -y apache2-utils htpasswd -nbBC 12 "" '你想用的密码' | tr -d ':' | sed 's/\$2y/\$2a/' 会输出一串长这样的玩意儿: $2a$12$8gsX8AKtD3EMdmksoqSDLAIEMEMMrSbKx/q0GG 把它整段复制下来,待会儿要塞到 config.yml 里。 如果你嫌麻烦,明文密码也是合法配置——把 config.yml 设成 chmod 600 也能挡住一般场景。但既然多花一行命令的事儿,咕咕还是建议哈希一下。 写 config.yml vim config.yml 贴入: address: 0.0.0.0 port: 6065 prefix: / # 反代后面一定要打开,让 webdav 信任 X-Forwarded-* 头 behindProxy: true # 全局默认权限(被下方 users 覆盖) permissions: none directory: /data users: - username: gugu password: "{bcrypt}$2a$12$你刚才生成的那一长串" directory: /data/gugu permissions: CRUD # 想再加一个只读访客?解开注释即可 # - username: guest # password: "{bcrypt}$2a$12$xxx" # directory: /data/public # permissions: R 关于 permissions 这一栏: C = Create(创建)、 R = Read(读取)、 U = Update(更新)、 D = Delete(删除)。给自己用就 CRUD 全开,分享给朋友的访客号建议只给 R 。 保存退出,给配置文件上锁: chmod 600 config.yml mkdir -p data 6.3 查看端口是否被占用 查看端口是否被占用(以 6065 为例),输入: lsof -i:6065 #查看 6065 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 6.4 启动 webdav cd /root/data/docker_data/webdav docker compose up -d # 注意,老版本用户用 docker-compose up -d docker compose logs -f 镜像非常小,等待拉取好镜像,出现 Created 的字样之后, 如果日志里看到类似: Listening on 0.0.0.0:6065 并且没有报错,那就说明跑起来了。 Ctrl+C 退出日志查看,容器还在后台老老实实工作。 这时候你已经可以在浏览器里访问 http://你的 VPS-IP:6065 试试,输入用户名密码会看到一个空空荡荡的目录列表,说明服务正常。 但是我们部署在公网一定要考虑使用反向代理工具配置 SSL! 不用域名直接 IP 访问也不是不行,但 Mac 的 Finder 在 HTTP 下挂 WebDAV 会自动降级成只读,套上 HTTPS 才能愉快地读写。所以域名 + HTTPS 是强烈推荐的。 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo 上 6 位数字的 xyz 续费永远都是 0.99 美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 打开你的 NPM 后台,新建一个 Proxy Host : Details 标签页: Domain Names :填你的域名,比如 webdav.example.com Scheme : http Forward Hostname / IP :填你 VPS 的内网 IP,或者直接 宿主机 IP Forward Port : 6065 Cache Assets :关 Block Common Exploits :⚠️ 一定要关! NPM 这个开关会拦掉一些 WebDAV 的 HTTP 方法(比如 PROPFIND 、 MKCOL 、 PROPPATCH ),不关你客户端会一直 405 报错,搞得你怀疑人生 Websockets Support :打开 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 6065 IP 填写: 如果 Nginx Proxy Manager 和 webdav 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 webdav 所在的服务器 IP 就行。 SSL 标签页: SSL Certificate :选 Request a new SSL Certificate ,签个 Let's Encrypt Force SSL :打开 HTTP/2 Support :打开 HSTS Enabled :可选 Advanced 标签页: 加上这段 nginx 配置( 这段非常关键,没它传大文件会断开 ): client_max_body_size 0; proxy_request_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; 保存。 然后访问域名就可以访问了! 7.2 利用宝塔面板(应该类似,没具体尝试) 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:6065/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 8. 使用教程 在 Mac 上挂载 打开 Finder,按 ⌘K (或者菜单栏 → 前往 → 连接服务器),输入: https://webdav.example.com 回车,会弹出登录框,填你在 config.yml 里设置的用户名 + 原始密码 (注意不是 bcrypt 哈希!)。点连接,VPS 上的目录就会以网络盘的形式出现在 Finder 侧边栏。 到这里你就可以拖拖拽拽了——复制、改名、双击打开、新建文件夹,全都和本地操作没差。 不过你本地到服务器的连接速度也很重要,像我测试的时候用的是美国的机器,反代用的香港,本地在上海操作,就会没有那么的丝滑。 如果用美国线路优化的 VPS,就会丝滑很多。 我还测试了上海的服务器,就和本地操作没差别了。 8.1 更新 webdav 这个项目后续会持续有更新,所以提供一个更新的方式。 cd /root/data/docker_data/webdav docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 8.2 卸载 webdav 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/webdav docker compose down cd .. rm -rf /root/data/docker_data/webdav # 完全删除 可以卸载得很干净。 9. 使用问题 一个不得不吐槽的问题 Mac 自带的 Finder WebDAV 客户端……怎么说呢, 残废 。具体表现: 传大文件容易卡死或者超时 偶尔莫名其妙断连,得重新挂载 会自动生成一堆 .DS_Store 、 ._xxx 这类隐藏文件污染目录 写入速度比读取慢得多 偶尔会把刚创建的文件「藏起来」让你看不到,刷新一下又出来了 如果你只是偶尔传几个文档应付一下没问题。但想当主力网盘用,我搜索了一下,网络上推荐了几个第三方客户端: Mountain Duck (付费,约 $39 一次性)—— 真正把 WebDAV 挂成本地盘符,Finder 里和访问 iCloud Drive 几乎没差,支持离线缓存、上传队列、智能同步 Cyberduck (免费)—— Mountain Duck 同公司出品,但只能「打开」不能「挂载」,操作起来更像 FTP 客户端 rclone mount (免费,命令行)—— 不嫌折腾的话稳定性和性能都很好,还能加 VFS 缓存提速 大家有用到好用的客户端也欢迎在评论区分享交流! 安全小贴士 WebDAV 暴露在公网上,几条小建议: config.yml 一定 chmod 600 ,避免被同机器的其他用户读到你的哈希 NPM 里给这个 host 加 IP 白名单 ,或者套一层 Cloudflare Access、Authelia 之类的二次认证,公网裸奔风险还是不小的 fail2ban 也可以装一下 ,防止有人暴力试密码 重要数据用 rclone 同步一份到 Backblaze B2 或 Cloudflare R2 —— hacdias/webdav 本身没有版本管理,删了就真的删了。B2 的 5GB 免费额度对小型笔记同步绰绰有余 VPS 快照定期开起来 ,多花不了几块钱,关键时刻能救命 大家使用上还有什么心得或者问题,欢迎评论区交流~ 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star。 参考资料 官方 GitHub:

起点 —— 上一篇挖的坑,这一篇填了 2026年5月1日更新:国区Apple Store也上架了,中文名字:续然,欢迎大家使用和反馈意见! 写完 Claude Code 魔改 Halo 主题,文末我顺手挖了个坑——说下一步想试试用 Claude 做个 iOS App。当时吹牛成分 60%、好奇成分 40%,没想到这话放出去不到一周,App 真就写完提审了。 动手之前我给自己定了两个目标。 一是摸一下 Claude 的能力边界 。Halo 主题那种活,网页端改改 CSS,对 Claude 来说是舒适区。iOS 开发就不一样——SwiftUI、SwiftData、真机调试、证书签名、App Store 审核,每一环都是新坑,我想看看咕咕坐副驾这事能跑多远。 二是真有这个需求 。不是为了写博客硬编的场景——我自己就囤着一堆 VPS、域名、邮箱、SaaS 订阅,哪天哪个扣费、哪天哪个到期,全靠Notion记录肉眼盯。(好吧我承认起因其实是怕自己的 、 等等传家宝 VPS 忘记续费 = =)。 上一篇的评论区,有朋友推荐了 ——一个自托管的订阅管理工具。我确实很早之前 Docker 部署了一份( ),跑在自己 VPS 上。但 Wallos 的提醒路径是 邮件 ——我经常忘记开邮箱…… 而且我的邮箱太多了…… 手机这东西一天看 50 次。如果有一个 App 把提醒直接推到锁屏——肯定不会错过。 动机清晰了。 2026 年 4 月 21 日晚上 7 点 28 分,我在一个空文件夹里打开 Claude Code,告诉它: 「做一个 iOS App,追踪订阅和域名到期时间,本地存储,不要账号,不要云」——就这么开始。 图标 —— 一只带铃铛的鸽子 整个项目里, 唯一一个从一开始我就想清楚、没让 Claude 瞎发挥的东西 ,就是这只鸽子。 🕊️ 鸽子 = 信使 ——按时把消息捎到 🔔 铃铛 = 通知 ——到点了响一声 🧡 粉橙渐变 ——压住"催债感"和"焦虑感",DueSub 做的是温柔的提醒,不是红底白字的「还有 3 天扣费!」 图标里的一图二喻,其实就是这个 App 的产品哲学—— 非打扰式、按时到达、不制造焦虑 。 功能是"续费提醒",视觉符号是"捎着铃铛的信鸽"——我只用一句话把这个意向说给 Claude 听,基本上两版就过了。这也是这次协作里我觉得最神奇的地方—— 你脑子里模糊的画面,只要你能用一句话说清楚它的内核,Claude 基本能把它落到像素上 。 当然也适配黑暗模式: 成品长这样 话不多说,直接上图。 主列表 :支出和收入同屏对照,月付、季付、年付、一次性,所有周期一个列表里理清。顶部那个粉橙色的卡片显示「30 天内还有几笔」,是我自己每天打开 App 最想第一眼看到的信息。 主屏小组件 :桌面上直接看 30 天内即将到期的 N 项,不用点进 App。对我来说这就是这个 App 存在的意义—— 你手机锁屏那一眼就能看到的东西,你不会忘记 。 锁屏小组件 :只显示下一笔——不用解锁、不用打开 App、锁屏状态下直接看到名字和剩余天数。这个我真的挺喜欢。 整个视觉语言贯穿的还是那个「 非打扰式 」的产品哲学——数字不标红、不用感叹号、不搞「紧急!」这种暴力美学,就是温柔地提醒你一句。 4 天 18 个 commit 我之前没写过 Swift,一行都没有。 Xcode也没装过。 SwiftUI、SwiftData、WidgetKit(分别是苹果用来做 iOS 界面、存本地数据、做桌面/锁屏小组件的三套框架)是啥,全是边做边查。但不妨碍我 4 天写完、上架、提审。 日期 Commits 主要内容 04-21(周二) 7 初版 + 改名 + 图标 + 隐私声明 + 截图策略 04-22(周三) 4 通知 P0 三连击 + 真机验证 + 修复上线 04-23(周四) 3 多币种实时汇率(opt-in) 04-24(周五) 4 Bundle ID 改名 + ASC 元数据 + 提交审核 我大部分时间在干嘛? 打字、截图、点按钮 。写需求、看效果、给 Claude 反馈「这里不对、那里要改、审美再收一点」。真正「坐键盘前敲 Swift 语法」的时间——几乎没有。 但这不是说 Claude Code 是魔法。它不是。它会在一个看起来简单的地方疯狂跑偏,能爽到停不下来,也能气到想砸键盘。 下面是翻车现场合集。 翻车现场 1:改名三连跳(放了自己一鸽子) App 一开始没专门起名字,我自用编译好之后,手机里最早就显示叫「续费提醒」。土是土了点,但直白。 第二天我觉得这名字太像 90 年代共享软件,改叫 Renewly 。英文名、现代感、听起来像个 SaaS。 然后我犯了一个很愚蠢的错误——没有第一时间去 App Store 查重名 。名字定了、图标调了、本地化文件改了、隐私政策的 title 也换了——一直到我准备提交审核之前,才突然想起来去搜一下。 被人占了 。有一个已经在跑的订阅工具 app 也叫 Renewly,功能还不完全重叠——这种重名是 Apple 审核环节大概率会让你改的那种。 这个锅就是没经验导致的。作为一个日常就在 App Store 搜 app、自己也跑一堆自托管工具的人,「 发布前先查名字 」这种事不用别人教。我偏偏就忘了—— 咕咕本咕,放了自己一鸽子 。 只能第三次改名。这次更彻底:英文叫 DueSub (Due + Subscription 的缝合),中文叫 续然 (续是续费,然是自然、果然)。 踩完坑的经验—— 名字想好之后第一件事:去 App Store 和 Google Play 都搜一遍 ,再去 Namecheap 看看 .com 在不在(最好但不是必须),都空着再去设计图标、改代码、改本地化。我这次反过来做,付了 3 次改名的工时。 翻车现场 2:通知的三个坑(同一天翻了三次) 这个 App 最核心的功能是 到期提醒 ——你订的 VPS 明天扣费,我提前 7 天、3 天、1 天推送,当天上午 9 点再补一刀。这个功能不对,整个 App 就是一个「带搜索框的表格」。 结果 04-22 那天我翻了三次车,一天修了三个 BUG。 坑一:九点以后打开 App,当天的提醒丢了。 调度逻辑是每天早上 9 点对「当天到期的项」推送一次。自测 OK。结果某天下午我打开 App,发现 明明有一项今天到期,却没推送 。根因是 App 长时间没启动,调度队列里根本没这条通知,系统自然不会补发。修法是 App 启动时 fallback 检查一次——当天有到期项、现在已经过 9 点、用户今天还没收到,就立刻补推。 坑二:自动续费的文案跟代码对不上。 设置页之前有个「自动续费项默认不提醒」的开关,文案写「你已经设置自动扣款了,不需要再提醒」。听起来很合理—— 但代码不是这么做的 。代码里自动续费项依然推 7/3/1 天的提醒,只有当天 9 点不推。这种「你说的是 A,代码做的是 B」最阴——不 crash、不报错、但就是跟用户说好的不一样。改了文案——提前几天提一下不是坏事,是自动扣那一刻再来一下才烦。 坑三:App 开着的时候,通知横幅被系统吞了。 iOS 的默认行为——App 在前台时,系统不弹横幅。理由是 App 自己处理。这对聊天软件合理,对一个 到期提醒 App 就是「你盯着屏幕的时候我就不提醒你」——有点傻缺。我真机上复现:早上 9 点 App 开着,该推的一声没响。我当时盯着屏幕看了 10 分钟,以为自己又写炸了。修法其实很简单——一行代码的事,让 App 主动告诉系统「前台也请弹」。 三个坑修完、真机回归测过,这一天我感受到了**「软件这事儿怎么每个角落都能翻车」**的经典真理。Claude 写代码很快,但它不会替你想清楚「用户下午 3 点才打开 App 会发生什么」——你得自己变成产品经理兼测试,一个个角落去踩。 翻车现场 3:Bundle ID 的最后一公里 全程最让我心里一凉的一次。 Apple 的 Bundle ID(可以理解成 App 的全球唯一身份证号)一旦在 App Store Connect 注册并提交过 build(也就是打包上传过一次安装文件), 就不能改了 ——要改只能注册新 App ID、开新 App 记录,之前填的元数据、截图、审核资料 全部作废从头来 。 我最早的 Bundle ID 前缀用了我日常 handle,但开发者账号实名信息是另一个名字。两边对不上,Apple 校验过不去。 只能改 Bundle ID。改它意味着: Xcode 里所有 target 的 Bundle ID 主 App 和小组件共享数据用的 App Group 证书和描述文件要重签 测试机上装过的旧版全部失效,要重装 最阴的是 App Group(让主 App 和桌面小组件共享数据的一个"命名空间",Apple 规定这个名字全球唯一)。我旧的 group 名改新的时候报冲突, 说这个名字有人用过 (其实是我自己,几分钟前注册的、没删)。加了个 .shared 后缀绕过—— 这个本来不应该存在的后缀永远留在我代码里了 ,作为一个「我和 Apple 的命名空间规则打过架」的纪念。 修完、重签、重装、回归——折腾好一会儿才终于走到 App Store Connect 那一步。 翻车现场 4:App Store Connect 是一个 checkbox 地狱 到这一步我才意识到: 写 App 只是上架这件事的冰山一角 。真正的灾难在 ASC。 让我列一下这次填过的表(不完整,凭记忆): App 名称 / 副标题 / 推广文本 / 描述 / 关键词( 英文中文两套 ) 主类别 / 副类别 / 年龄评级(一份 20 多题的问卷,问我「本 App 是否包含恐怖元素」——我做的是订阅提醒) 版本号 / 构建号 / 上架日期 隐私政策 URL( 每种语言一份 ,英文一个、中文一个) Marketing URL / Support URL 12 张截图 (6.9" iPhone,iPad 跳过只做 iPhone) App Icon 1024×1024 App 隐私标签 (和代码里的 PrivacyInfo.xcprivacy 是两份 独立的表 ,要自己保证内容一致) 内容版权声明(不是 App 的版权,是 App 里用没用别人的素材) 出口合规性(加密算法使用) IDFA 使用声明 DSA 欧盟合规声明 (新增的,个人开发者选 Non-Trader 才不会公开家庭住址) App 审核联系方式 + 演示账户 + 审核备注 定价 + 可用国家地区 Mac / Vision Pro 兼容性(默认勾,记得关) 发布方式(自动 / 手动) 整理这份清单用了一下午。一个勾一个勾过,途中踩的坑: 勾了「所有国家」才发现默认包括中国大陆——国区要备案我没备,赶紧去掉 英文的隐私政策 URL 填完以为就行——ASC 跳出红 banner: 简体中文 - 隐私政策网址 - 此栏为必填项 。每种语言要独立一个 URL 以为提交时会弹「出口合规 / IDFA / 内容版权」三连问——这次居然没弹。后来才知道如果你在 Info.plist / xcprivacy / App 信息页都提前答过了,ASC 不再来烦你 备注里写了 Settings 里有个 "Seed Sample Data" 按钮——结果代码里英文按钮实际叫 "Fill sample data"。 自己的 App,按钮名都记错 。这给 Apple reviewer 看到,他大概会觉得我连自己 app 都没摸熟 最后所有字段绿勾、红 banner 消失、「提交以供审核」按钮可点。深呼吸,点下去。 然后画面一片祥和。 没有弹窗、没有「你确定吗」、没有任何仪式感 ——左侧栏那个黄色小点变成「正在等待审核」,就这样。 吐槽一下:上架 App 这个流程,真的太复杂了 我要认真吐一下槽。 Apple 的 App Store 是 iOS 生态的核心支柱, 用户体验 堪称行业标杆。但它对 开发者 的体验,是另一个世界。 作为一个独立开发者、副业开发者、个人开发者,你要面对: ¥688 / 年的开发者费 ($99)。App 一分钱不赚,每年也得续。这个我认——毕竟用了整套工具链和分发网络。但作为一个常年买小鸡的人,每年还是要叹一声—— 这笔钱够我买一台好的服务器了 。 证书体系 。App ID / Bundle ID / Provisioning Profile / Distribution Certificate / Push Certificate……随便一个过期,线上炸。 ASC 的 30 多个字段 + 一堆合规声明 。DSA、隐私标签、xcprivacy、内容版权、出口合规、IDFA、年龄问卷、类别、定价、可用区。 审核的黑盒 。什么时候过、为什么过、拒了怎么改,全靠邮件 + Resolution Center。 多语言隔离填写 。每个 locale 要独立填描述、关键词、URL——漏一个返工重来。 这套流程对成熟团队(有专门的 release manager 和 compliance)是日常,对一个摸着石头过河的独立开发者—— 「完成率劝退」比代码本身高 10 倍 。我完全理解为什么那么多做了一半的 iOS App 最后没上架——不是代码没写完,是 上架的活儿没人愿意干 。 我这次能走完,95%的功劳给 Claude——不是它写代码快,是它陪我 把这 30 多个 checkbox 一个一个过完 。如果让我自己看 Apple 文档去填,我多半中途就放弃了。 顺便说一下国区(其实我想上的) 这次上架,我 主动跳过了中国大陆区 ——但这不是我本意。 我本来是想上国区的。不是说看重国区下载量,就是觉得一个中文作者做的中文工具,中文应用商店里没它,多少有点别扭。 备案这事我是知道的。早几年折腾服务器域名的时候,我就顺手买了一个十年的 .cn 域名挂在腾讯云、老老实实走完了 ICP 备案;当时配套也买过一台大陆服务器——但国内服务器的体验真的让人一言难尽, 带宽小得可怜还卖得贵 ,到期我就没续费了。反正我要跑的服务都在境外,非要在大陆放一台机器没必要。 这次准备上架国区,我盘算了一下以为挺简单—— .cn 域名还在、备案号还在、腾讯云账号还在、实名信息跟 Apple 开发者账号对得上。填个备案号就完事了吧? 结果一查流程才发现:光域名备案不够,备案得绑定在一台「正在使用中」的境内服务器上 。我那台大陆机子早停了,备案跟着就失效了——要恢复, 得再租一台国内服务器 。 这就有意思了—— 我这个 App 完全离线、不联网、不部署任何东西 。我不需要服务器,没有任何后端服务要跑,也没有任何 API 要托管。仅仅因为「要在国区上架」这个理由,我被强制要求去租一台 我完全用不上的大陆服务器 。 这一点我是真的不太理解 。规则的本意大概是想给在国区上架的 App 留一个可追溯的境内主体,但对一个纯离线工具来说,服务器这条要求完全是空转——我没有后端、没有数据回传、没有任何需要境内托管的东西,却要为此每年多掏一份云厂商的钱。 算一下数:国内一台够用的轻量应用服务器(能用于备案的最低配起), 一年 400 块起步 ;加上 Apple 开发者 ¥688;加上偶尔维护备案的小成本——对一个 不收费的免费 App , 每年多花 1000 多块为爱发电,实在不太合理 。 所以这次我选了 All Countries except China mainland ——174 个区全部开放,国区先空着。等哪天要做付费功能,有盈利模型能把这 1000 多块年成本覆盖掉,再回头补上国区不迟。 如果你也是个人开发者、做的是免费离线工具—— 国区这件事可以先放一放 。把其他 174 个区跑通、把用户反馈收起来、把付费模型验证出来,再回头说国内的事。 Claude Code 作为 iOS IDE 的体验 回到这次最初的目标之一——「 摸一下能力边界 」。 我不懂 Swift,不会 SwiftUI 的声明式语法,SwiftData 的 @Model 到现在也讲不清原理。但一点都不慌——因为 我不需要懂,我只需要知道我要什么 。 「加一个设置项,让用户切换显示货币」——Claude 去加 「这个列表行高再紧一点」——Claude 去改 「真机跑一下,别只编译通过」——Claude 真的会去 build、装真机、抓日志 「这个通知为啥没弹,你查一下」——Claude 读上下文、看 delegate、写假设、去验证 我的角色从「写代码」变成了「 定需求 + 审效果 + 做判断 」。前者要懂语法,后者要懂用户。 爽的地方: 你不再因为「不会 Swift」而放弃 iOS App 这个想法 。 不爽的地方: 它跑偏的时候你得看得出来 ——代码能力不是凭空被替代的,你能让 Claude 写代码,但你必须能看出它写的是不是你要的。 这次 4 天我踩到的 能力边界 大致在: 需要你当产品经理 。Claude 不会主动想「用户下午 3 点打开会怎样」这种边界 case 需要你当测试 。它说「已修复」不等于修了,必须你跑一遍真机复现 证书 / 签名 / 打包 的坑它不一定记得住,要你盯着它去查 Apple 最新文档 ASC 的字段含义 它有时候会猜错,你得自己去对官方说明 其他的——写代码、读 API、调 debug、查栈、写测试——它比我快得多得多。 成本账 粗略算一下这个 App 到目前为止的总投入: 项 成本 Apple 开发者账号 ¥688 / 年 Claude Code 订阅 已订的 Max 档,这次没多花 域名 + 托管 ¥0(Landing 和隐私政策挂 GitHub Pages) 服务器 ¥0(没备案,不需要) 图标 ¥0(Claude + SF Symbols + 手调) 截图 ¥0(模拟器截图 + 简单标题文字) 总计 ¥688 跳过国区之后,全部成本就剩一张 Apple 开发者年费。对一个本来一行 Swift 都没写过的人—— 这个投入产出比,放在 Claude Code 出现之前是完全不可想象的 。 写在最后 这不是一篇「你也能 4 天做 iOS App」的鸡血文。你不能,我也不能——如果换一个更复杂的 App(联网、账号、支付、音视频、AI 推理),这个周期会翻好几倍。 但有一件事值得说—— 「我不会写代码」,不再是做东西的理由了 。 过去十年,「有想法但不会写代码」是劝退无数人的天花板。想做的东西卡在「找不到技术合伙人 / 招不起开发」这一步上。今天这个天花板被 Claude Code 这类工具 狠狠砸了一个洞 。 不是洞下面一切都容易,iOS 上架流程该复杂还是复杂,国区备案的门槛该离谱还是离谱。但 写代码本身 这件事,从「不可能」,变成了「 要不要动手 」。 当开发成本降到接近于零,真正稀缺的是什么 这次最让我想明白的一件事—— 开发成本的下降,反过来把产品问题推到了台前 。 过去「找到真实需求 + 招到靠谱开发 + 写出能用的代码」三件事捆在一起,能搞定其中任何一个都算稀罕。现在 Claude Code 把第二、第三件事的门槛降到了个人能承受的范围——那第一件事—— 你到底在解决一个真问题,还是在自嗨 ——就变成了唯一卡住你的那个门槛。 我这个 App 是我自己的小真实痛点。规模不大,也不打算做成生意。但在做的过程里我越来越相信—— 在今天,独立开发者是真的能吃上饭的 。这两年海外 indie dev 圈里,赚到钱的人一抓一大把,有年收入几十万美金的,也有做个 $10/月小工具把生活费搞出来的。核心都不是他们代码写得多牛——而是 他们找到了一小群真实付费用户的真实需求 。 代码这条护城河正在被填掉, 用户洞察 成了新的护城河。对不少想副业糊口、或者想看能不能全职独立的人,这可能是十年难遇的一个窗口期——代码能力第一次不再是硬卡点,但同时, 你对用户的理解 的重要性被放大到前所未有。 下一个坑 这次 DueSub 是免费 App,没盈利模型——它本来就是给我自己用的,顺手做成了产品。下一个我想挖的坑,是 做一个带付费功能的 App :走一遍订阅 / 一次性内购的完整流程,去碰 StoreKit、走税务合规、看看 Apple 30% 抽水到底怎么切、体验一下从 0 到第一个付费用户的全过程。 具体做啥还没定—— 先把用户洞察这一步想清楚再动手 。这次改名三连已经告诉我了,想清楚再动手,比手快多跑一版省事得多。 最后 回到博客侧边栏那四个字—— learn or earn 。 这回 learn 到了 iOS 一整套流程、Claude 能力边界、App Store 审核的全部坑;earn 谈不上,免费 App 没盈利模型。但 摸清一条从 0 到 1 的路径 本身,就是这次最大的 earn——下一次就知道怎么走第二步了。 App 叫 DueSub / 续然 ,现在在审核队列里,预计 24–48 小时后上架 非国区 App Store。免费、离线、不要账号——如果你也跟我一样囤着一堆 VPS 和域名、又老忘开邮箱——装一个试试。 国区的事儿,以后再说吧。 咕咕下一篇接着挖坑。 后记 · 发博客之前 App 先过审了 正文写到上面那段「在审核队列里,预计 24-48 小时」的时候,App 确实还在审核。结果博客还没来得及排版发出,苹果那边先完事了。 这篇博客发出来的时候,App 就已经在 除中国大陆外 的各区 App Store 可搜可下了: —— 免费、离线、无需登录。

前几天一个周末,我把自己博客用的主题 fork 了一份,花了 3 天时间,发了 61 个版本,解决了一堆多年来一直想改但没改的小问题,顺便踩了 N 个大坑。 这篇就把这段 "非程序员 × Claude Code" 的 vibe coding 实录和大家分享一下。也顺便聊聊用下来对几家 AI 订阅的真实看法,还有一点关于 "软件门槛" 和 "肉身能力" 的胡思乱想。 内容会有点长,但都是真金白银踩出来的体感。 起点——博客主题好久没更新了 咕咕这个博客用的是 平台 + 主题,作者是 。这个主题其实非常好用,做得也很漂亮,我前前后后用了好几年,一直挺满意的。 但最近一两年,作者因为工作原因更新慢了下来。这完全可以理解——开源主题作者本来就是用爱发电,有自己的主业和生活,能一直维护已经很难得。但对用户来说就是: 有几个小 bug 长期挂着没修 有几个想要的小功能提了 issue,但一直排队 深色模式有些细节抠得不够到位 性能上有些可以优化但还没做 这些东西我其实都想自己动手改,但之前一直有个拦路虎—— 我不是程序员 ,平时也就是会点 VPS 折腾、写写 Docker 命令,真要去啃主题源码,心里还是怯的。 那为啥现在突然敢动手了?因为一个东西: Claude Code 。 从伸手党到自己动手——为什么决定 fork 我在openclaw出来不久的时候,一开始订阅了kimi的coding plan,后来觉得不够好用,听网上一堆人说GPT和Claude好用,就尝试订阅了 Claude Pro(每月 20 美元那种),偶尔用来问问问题、写写文案,确实不错。但一直没用它写代码——总觉得太复杂了。 直到有一次,我随口让 Claude 帮忙看了下主题里一段 CSS,想改个小边距。它不光改对了,还顺带解释了为什么这么改,甚至告诉我 "你这个改动会影响移动端,建议加个 media query"。 那一瞬间我突然意识到: 原来这事儿是可以自己干的。 不是说我一夜之间学会了前端,而是——有 Claude 在,你只要能描述清楚你要什么、看得懂 git diff、会复制报错,剩下的它都能兜住。这个门槛突然低到一个非程序员也能跨过去的程度。 然后想法就顺理成章了: 要等主题原作者更新?大概率很难 要发 PR?很多功能其实只有我自己需要(比如一些自定义侧边栏、适配自己阅读习惯的字号行高),发上去作者不一定合,也不应该合 那 fork 一份自己维护就好了 打定主意,Max 套餐开了(后面会细聊为啥咬牙从 Pro 升 Max),本地 Halo 环境跑了起来,VS Code + Claude Code 都准备好了。 然后故事就开始了。 3 天 61 个版本——一发不可收拾,根本停不下来 一开始我没想到会搞这么快。本来预期是"一周弄个能用的版本",结果 Claude Code 的节奏推着我往前走,一发不可收拾,根本停不下来。 这是真实的 git log 统计: 日期 星期 发了多少版 主要做了啥 2026-04-19 周日 5 版 热身 · SEO / 初版性能优化 2026-04-20 周一 44 版 暴风骤雨日 · 翻车最多的一天 2026-04-21 周二 12 版 安全补丁 hotfix 系列收官 3 天, 61 个 tag。 作为对比,原作者过去两年发了大概 30 个版本——Claude Code 这个节奏,直接比原作者快 15 倍 (当然其实很多小改动完全可以合并起来一次发版,不知道是Claude还是我给的skill的原因,这边图快就直接一个改动一个 tag 推了,虚胖成分有一些)。 当然这不是在炫耀速度。这个速度之所以能跑起来,是因为: 小需求说清楚 → Claude 几分钟就能改完 + 解释 本地跑一下 → 有问题直接贴报错给它 没问题 → git commit + tag + push GitHub Actions 自动打 release zip → Halo Console 一键升级 整个反馈循环被压缩到 10 分钟以内 。人只要负责 "想清楚要啥 + 看一眼做对了没"。 仓库在这里,感兴趣的同学可以看下完整的 commit history: 👉 翻车现场合集——61 版里至少一半是在修前一版 光说 "3 天 61 版" 好像挺厉害,但真相是: 其中至少一半,都是在修前一个版本的坑 。 下面这几个翻车故事,是这 3 天里最有代表性的。不展开技术细节,就聊故事感——让大家感受下 AI 写代码到底是啥体验。 翻车现场 1——"3 + 1 = 31" 的谜题 想加一个 "相关推荐" 小卡片,在每篇文章末尾展示 3 篇同分类的文章。 Claude 几分钟写完,跑起来一看—— 页面上出现了 30 张卡 。多了整整 27 张。 让它修,它改了一版,还是 30 张。再改一版,还是 30 张。 连续 10 个候选版本 (rc.01 → rc.10) 都没修对。 最后逼它把变量打出来看,才发现是个非常隐蔽的类型问题:后台的 "3" 不是数字 3,是字符串 "3"。字符串 "3" + 1 在某些语言里等于 "31",然后程序就真的去拉了 31 条文章。 一个让人哭笑不得的 bug。但也正因为它这么反直觉,Claude 自己怎么改都绕着走。 教训 :AI 连续两三版修不对同一个问题的时候, 不要再让它瞎猜了 ,直接让它打印真实的变量值。否则就是纯纯在 token 上烧钱。 翻车现场 2——给 jQuery 加了个小优化, 整站炸了 Claude 看了下主题代码,说 "jQuery 这样加载影响性能,给它加个属性可以异步加载,更快"。 听着有道理,就让它改了。 改完推上去,整个博客都打不开了。 翻车原因说起来也合理:jQuery 延迟加载之后,页面里好几十处其他脚本依赖它的地方跑得太早,找不到 jQuery,整站一片红。 最后直接把改动撤了,恢复原状。这个 "优化" 看上去简单,但真要做对,得把主题里几十处相关脚本一起调整,不是一下午能搞完的活。 教训 : 性能优化类的改动,生产验证前别打 tag 。开个浏览器点一遍主要功能再发版。省事儿的 10 分钟,能省下一晚上查事故。 翻车现场 3——四版 UI 改造, 全被自己否了 这个可能是最戏剧性的一段。 我某天看动态页觉得不够好看,跟 Claude 说 "美化一下,搞得稍微有设计感一些"。 Claude 很兴致地给了个方案,发了 rc.01。我一看: 不好看,页面上这个蓝色小圈圈是干啥的 。 Claude:好的,我马上收紧!发 rc.02,删了一些元素。 我: 完全不想要这套了 。 Claude:那换个设计语言!现代社交网络风格!rc.03。 我: ……这是好看了,但布局塌了,左边一列文字挤得一个字一行 。 Claude:我修!rc.04,布局修对了。 我: 还是没有原来的好看。算了,咱们回原版吧。 四个版本全废,一键 git revert 回到上一个 stable。 这段翻车事后反思,根因不是 Claude 审美差——而是我一开始就没把"想要什么样"说清楚。它在自己的审美空间里闭环了四版,每版都是"它认为的美化",但和我脑子里那个隐约的画面完全对不上。 所以后来我给自己加了条规矩: 以后让 AI 做视觉类改动,开工前必须给它一个参考站 URL,或者几个情绪板关键词 。不然就是四版起步的翻车。 翻车现场 4——修了个隐私漏洞, 结果所有内容都不显示了 这个翻车比较严重。 起因是一个真实的安全问题:后台某条动态设为 "私有" 之后,但 管理员登录访问博客前台 ,私有动态居然还能看到内容。 典型的主题层漏了一道过滤。 我让 Claude 加过滤,它加了一行判断,推上去。 我一看: 所有动态都不显示了 ,包括本该显示的公开动态。 再查才发现 Claude 用错了比较方式——字段的实际类型和它以为的不一样,导致判断永远失败,全部被过滤掉了。 再改一版,修对了。再改一版,清理掉调试代码,收官。 就为了修这一个 bug,一口气发了三个补丁版本。 教训 :AI 的判断看起来都挺合理,但 一定要跑真实数据验证 。不然它拍胸脯说"这下肯定对了",上生产之后可能就是另一个故事。 翻车现场 5——CSS 里的神秘竖条 这个就是翻车 3 里那个 rc.03。上完新设计,打开一看—— 两列内容里,左边一列宽大约 100 像素, 右边一列 800 像素 。左边那列里的文字被压得一个字一行竖着排,像被揉过一样。 Claude 以为是自己写错了参数,改了好几次边距和间距,都没用。 后来查出来是 CSS 里的一个经典陷阱——简单说就是 "平均分配宽度" 这个写法遇到了 "不能换行的超长内容",两边就不平均了。 这个坑是前端圈公认的经典老坑,但如果不踩一次,你根本不知道会有这种问题。 这就是 vibe coding 的真实面貌——你不是在写代码, 你是在和各种古怪的坑玩。 一个月付了 $168 AI 订阅费, 为啥最后都用 Claude 聊完翻车,顺带聊一下订阅选择。因为这一波下来,我对 "哪家 AI 最值得付费" 有了点新认识。 从 Pro 咬牙升到 Max 我一开始用的是 Claude Pro(每月 $20),觉得挺够用的,日常搞点东西没啥压力。 但真要让 Claude Code 做 vibe coding 这种 "一天 20+ 版本" 的节奏,Pro 套餐 很快就触顶 ——几个小时就把当日额度用完,然后就只能干等着刷新。 咬着牙升了 Max(每月 $100)。升级那一刻说实话是有点肉疼的。 但用了两天就想通了: 100 美元每月, 当学费其实很便宜。 你想想——如果我去找个独立开发者,把这 3 天干的活外包出去,主题 fork + 新 feature + 安全补丁 + SEO 优化 + 性能优化 + 几个 bug fix,怎么也得 500-1000 美元 起步。Max 这 $100,算当月 "学费 + 外包费",比外包便宜,比自学快,最后东西还完全属于我自己。 除此之外,我还可以用它来辅助我的工作(excel里也可以用Claude了)。 顺便放张 Max 使用面板截图给大家感受一下强度: 一周用到 77%(All models),Current session 也跑了 17%。真用爆了。 月付 $168 的 AI 订阅全家桶 为了写这篇,我盘了下自己每月的 AI 订阅费: 工具 月费 主要用途 实测体感 Claude Max $100 Claude Code / 主力 vibe coding / 长对话 ⭐⭐⭐⭐⭐ 最稳最聪明,开发主力 ChatGPT Plus $20 OpenClaw / 日常问答 / GPT 5.4 ⭐⭐⭐⭐ 最近升级的 GPT 5.4 确实聪明了不少 Gemini Pro $20 偶尔对比答案,长文本场景 ⭐⭐⭐ 特定场景还行 Kimi ¥199 (≈$28) 中文长文,OpenClaw 备用 ⭐⭐ 体感明显弱一个档次 合计:约 $168 / 月。 这个钱多不多?不少。但我就当学习AI交的学费了,之后看看能不能取消掉1-2个不常用的。 一条选型经验——选你能力范围内最好的模型 过去这段时间同一套 OpenClaw 工具链,我分别用 Kimi 2.5、GPT 5.4 试了一遍: Kimi 2.5 :总感觉差点意思,回答经常跑偏,得反复 prompt 才能拉回来 GPT 5.4 :明显聪明,大部分需求一次过 同一个任务,两家结果差异非常大。 我得出的一条经验是: 选模型,在自己能力范围内选最好的,会用得更爽。 便宜 20% 但差 50%,算总账其实亏。因为 差的模型浪费的不是钱, 是你自己的时间和耐心,甚至你还会怀疑这个东西真的好用吗? 。就和你刚学琴买了一把300块的吉他,F和弦怎么也按不出来,你会怀疑自己我是不是真的学不会吉他。 最后吐个槽。GPT 的 Codex(OpenAI 自己的命令行工具)我也试了,结果最大的体验是——老是弹这个: ⚠️ Agent failed before reply: OAuth token refresh failed for openai-codex: Failed to refresh OAuth token for openai-codex. Please try again or re-authenticate. 每几天就让你重新登录一次。体感很差。 最大的收获是"掌控感" 聊完钱,聊聊情绪。 回头看这 3 天、61 个版本、5 个典型翻车、一堆踩坑笔记,最大的收获 不是技术层面的 ,而是—— 掌控感 。 自己的博客, 自己说了算 这种感觉很难形容。 以前看到博客某个样式不喜欢,只能 忍 。 想加个小功能,只能 发 issue 等 。 字号大小、行高、颜色有几个小细节不爽,只能 告诉自己习惯就好 。 现在? 想改就改。想删就删。想加啥加啥。不喜欢这个主题了—— 甚至可以让 Claude 帮我从头写一个 。 这种 "一切都在自己手里" 的感觉太爽了! 从博客到小工具, 软件的门槛在降低 而且这事不只是博客。 我最近还在琢磨几个小想法——想做一个 iOS 小 APP,专门用来提醒自己 VPS 和各种订阅服务到期续费,免得哪天 "传家宝 VPS" 突然就没了(其实现在已经做差不多了);也想搞个 Chrome 插件,还没想好具体做啥,但感觉总能用上。 这些以前想都不敢想。Swift 学起来多难啊,Xcode 折腾一整天,可能只出一个崩溃。 但现在,描述清楚需求,Claude 自己写、自己跑、自己调、自己解释。 一个非程序员,3 天能发 61 版博客主题,那做个小 APP 呢?可能也就是一个周末。 软件的门槛,正在以肉眼可见的速度降低。 不是说程序员没用了—— 真正的工程问题还是需要专业的人 。但对于 "我想给自己做个小东西" 这个诉求,门槛已经被 AI 摊平了。这是肉眼可见的变化。 写在最后——软件门槛降低了, 下一道墙在哪? 聊到这儿,我想说点更胡思乱想的东西。 如果软件门槛真的降低了, 那下一道墙在哪? 我最近一直在琢磨这个问题。想法不成熟,但大致是这样。 第一道墙:硬件。 代码可以让 AI 写,但服务器会坏、光纤会断、电池会衰减、螺丝会生锈。未来 3 年 5 年,能动手改硬件、能拆能焊、能找到小众元器件、能修能造的人,可能反而稀缺。毕竟 AI 目前还没长手。 当然,机器人也在路上。 特斯拉的 Optimus、波士顿动力的 Atlas、还有国内各种人形机器人——再过几年,它们大概率能帮你做一部分 "需要手" 的事:拧螺丝、搬东西、简单清洁、甚至可能插插线修修电路。 但即便如此,我觉得还是有些事它们短期干不了: 判断当下最该做什么 (机器人执行能力强,但"决定干啥"这事儿,还是人擅长) 精细手艺和经验 (修老机器、拆解古董电子产品、手工打磨——这些靠的是感觉和长年积累的手感) 和人打交道的事 (带朋友打麻将、哄孩子睡觉、陪父母聊天——这些不是机器人来"代做"就有意义的) 所以硬件这道墙,会被机器人慢慢矮化,但不会马上塌。 第二道墙, 可能更有意思——肉身能力。 我越来越觉得, 那些必须花时间训练才能掌握的能力——英语、西班牙语、游泳、骑车、做饭、长跑——反而变得越来越重要 。 为啥?因为软件可以让 AI 写,文案可以让 AI 编,图片可以让 AI 画,视频可以让 AI 生成。硬件未来让机器人干。 但你的身体不能让 AI 代练,也不能让机器人代练。 机器人再强,也没法替你去学一门新语言的肌肉记忆、没法替你游 1000 米感受水的阻力、没法替你骑车穿过一整座城市感受风。这些东西必须是你自己的时间、汗水、年轻换来的。 当所有人都有 AI 之后,大家的 "软件产出" 会被拉到差不多的水平。再过几年当所有人都有机器人之后,生活里 "需要动手做的事" 也会被拉到差不多。 那时候,真正拉开差距的,反而是那些 最原始、最肉身、最不能外包 的能力。 所以如果你问我,现在手头有点闲时间,该干啥? 去学一门新语言。去游个泳。去学骑公路车。去学做红烧肉。 因为那些东西,将来可能比写代码还值钱。 主题的仓库在这里: 。 如果你也用 Joe3 主题,欢迎去下载最新版 zip 试试看,也欢迎提 issue。我维护得动,就一直维护下去,维护不动了,至少留一份 fork 给后来人。 OK,那差不多今天就聊这些,不知道有没有勾起大家 vibe coding 的兴趣,也欢迎大家评论区留下你的 AI 订阅搭配和踩坑经历~

大家好,好久没有和大家更新了。 最近好像也没找到特别有意思的 Docker 项目,随着 Claude Code 和 OpenClaw 的火爆,其实玩 Docker 也变得容易了很多,感觉是不是都没啥必要出教程啥的了,直接让它们搞定。 前俩天看到国内最早的云服务新浪云终止服务了。 今天就来做个回顾吧,回顾一下我买过的服务器们。 展示页面 这个是目前咕咕持有的服务器的情况(不完全,有些比如新的VMRACK、还有腾讯云香港轻量的34元/月的没挂上去),很多都是老伙计了,可能现在买不太到。 地址: 不知道大家看到这个页面的第一反应是什么,应该第一时间是看到了两台红色的已经阵亡的机器。(当然还有其他的阵亡的机器可能被我删了 = =) 第一台是 Olink ,Olink 当年最出名的是没记错的话是德国的 AS9929,36 刀/年的,还有一个是 GRE 隧道,给线路不好的服务器用的。 老板运营一直很佛系,机器很稳,用的很省心。咕咕收过一台二手的,续费 36 刀/年的,后来自己又原价买了一台。 去年好像迁移过一次机房到圣何塞,今年没想到就运营困难了,官宣了停止运营。挺可惜的(对了推广收益好像还没结算给我 = =) 第二个是搬瓦工的 The Chicken ,这个其实是咕咕忘记续费了,导致阵亡。性价比其实还可以,但是和我其他的搬瓦工比起来不够高(否则我也不会忘记续费了哈哈) 然后咕咕自己看到的是 在线天数 。 毫无疑问,这个代表了稳定性。 买 VPS,可能一开始是为了折腾,但是时间久了之后,尤其是跑了一些项目或者部署了一些网站之后,最在乎的,肯定是稳定性。 虽然之前和大家反复强调不要相信任何一个服务器厂商,对于自己在意的数据一定要多备份。但是三天两头宕机的厂商,即使再便宜,我们也会被搞的很难受。 搬瓦工 Bandwagonhost——稳定省心 而搬瓦工在咕咕看来,就是稳定的代表。 我刚接触 VPS 的时候,就已经早闻大名,搬瓦工到现在至少有 10 年历史了吧。我目前的博客用的反向代理就是 The Plan V2 的套餐,一年 100 多刀,可以切换十多个机房(不过我基本没切过,一直用的 DC09),这两三年时间下来,很稳定。 还有其他的几台,也基本买了之后配置完就没怎么登录过。 双十一套餐 的,一年才 29 刀,我跑了几个基础服务在上面,包括现在这个服务器网站就是跑这个上面的。 49 刀的套餐 ,曾经很多 mjj 溢价想要购买,咕咕这台是后面一次放货的时候抢的,500G 流量,和 The Plan V2 一样可以切换十多个机房,CN2 GIA 最便宜的套餐了,目前咕咕畅游互联网全靠它,转眼都快 3 年了。 所以如果要推荐一个稳定好用的机器,咕咕第一选择也会考虑推荐搬瓦工,前俩天还用 Claude Code 辅助做了一个简单的 HTML,上面是瓦工的产品合集: 斯巴达 Spartanhost——稳如老狗 另外值得一提的是斯巴达。 这台也是黑五抢的,当时是 31 刀/年,AMD 处理器,第二年黑五的时候升级了配置,变成 62 刀/年,然后之前老被 DDoS,刷流量(咕咕曾经一个月买了 5T 的额外流量 = =),后来我狠心加钱买了升级防御包和年付流量包,目前好像也是 100 多刀/年,我主要搭了 NPM,用做反向代理。非常稳。 老板就是客服,回复非常快,之前黑五 3、4 点熬夜抢鸡的场景历历在目,只是这俩年好像老板也佛系了,没怎么搞黑五活动。 BuyVM——PT 党最爱 BuyVM 之前最大的优势就是无限流量,而且便宜,VPS 只要 3 刀/月好像,加上你可以买存储块,256G、512G、1T 的都可以,所以是 PT 党们的最爱,尤其是卢森堡无视 DMCA。 咕咕这个就是卢森堡 + 256G 的最丐版配置,不过前段时间好像换老板了,卢森堡的机器也迁移到了瑞士。咕咕 PT 退烧了,偶尔用家宽下点影视作品,这个机器已经好久没登录过了。 CloudCone——黑五神机 CloudCone 这台是黑五抢的机器,看看已经是 3 年前的了~ 1 核 2G 内存 60G 硬盘,重点是续费只要 10.44 刀/年 ,放着挂着看看就很香。(之前还有一个 7 刀的,出掉了,那台价格更便宜,但是配置不如这台) DMIT——瓦工上游 DMIT 的两台,头脑发热跟风抢的,虽然说 DMIT 是搬瓦工的上游,但是我怎么觉得没搬瓦工稳呢。 线路不错,他家的一个特色是默认是用密钥登录服务器的,第一次买的小伙伴可能有点晕晕的,不知道怎么连接服务器,我应该出过小白教程,安全性确实会高一些。 Hetzner——欧洲小钢炮 机器性能强,价格低,性价比非常高,而且很稳。咕咕目前的博客就部署在它们家美西的机器上,支持备份快照,防火墙也很好用,非常省心。 他家的竞拍的独立服务器也非常受 PT 党的欢迎,曾经拥有过两台,后面因为价格和自己用不上那么高的性能而没有继续续费。 竞拍地址: 唯一遗憾是之前咕咕写的这篇文章 ,被一个热心的 PT 网友转载了,然后他是给大家介绍用来类似薅羊毛刷 PT 的(新用户可以用邀请链接注册可以获得 20 欧的试用金),他那篇文章火了,然后他也是好心好像用了他和我的邀请链接,最后导致咕咕的账户的链接推广功能被官方禁止了(认为通过我链接注册账户的都是低价值客户 = =) 之前其实 Hetzner 注册挺难的,现在不知道会不会容易一些了。 HostHatch——大盘鸡 关键词: 大盘鸡 。 咕咕这个是 3 年 65 刀的,1T 的硬盘,不多说,它家的机器基本也是 PT 党用来刷 PT 用的,我这台是 3 年付的黑五版本,他家稳定性一般,不过对 PT 来说还好,我也很久没登录过了。 Naranja Tech——荷兰小钢炮 荷兰小钢炮,黑五买的,他家黑五基本都有活动,这台好像 18 欧/年,2G 够用,目前搭了几个简单的小服务,也是稳的一批~ Netcup——性价比高 和 Hetzner 一样,性价比非常高的商家,之前一直持有他家的 RS2000,基本是一个小杜甫的状态,无限流量,性能还牛逼,一个月十多欧,非常好用,目前由于经济原因,换了一台 VPS,很好用的机器。 唯一需要提醒的是他家是有 合约制 的,不续费要提前告知,否则一样给你续费,不交钱给你寄律师函! OVH——抗 DDoS 法国的,最出名的是抗 DDoS,独立服务器比较有名,经常会有抽奖(买到的实际配置比官方标注的高,比如 2T 硬盘变成 4T,200M 带宽变成 1G 的),咕咕现在持有一台,每个月付费 16.99 欧,2×960 GB SSD SATA(抽奖开出来是 4×960 GB SSD SATA)的 KS-LE-2。 他家基本不给大陆的小伙伴注册,咕咕当时还不是很严格,找的代理帮忙注册的账号。 然后 OVH 和 Netcup 似乎你账号上没有订购东西,一段时间之后会删除账号,OVH 的话可以买个 OVH 后缀的域名挂着,一年就几欧。 RackNerd——性价比之王 性价比之王,最适合新人入坑的鸡,很多一年就 10 刀左右。够折腾,高配一些的价格也便宜。 之前有整理过列表,看看能不能捡漏~ 地址:https://blog.laoda.de/vps iON——曾经的神鸡 咕咕这台是 2019 年的,续费 20.19 刀的款,联通优化线路,1T 的流量,还是 单向流量 !非常高性价比的鸡。 当然最有性价比的是双十一的,同配置但是续费 11.11 刀的。咕咕曾经也有过,后来转手了。 iON 现在老板似乎也换过人了,机器也稍微偏贵一些,但是品质可以。 写在最后 好了,差不多了。 现在一个一个看过来,满满都是当年玩鸡的回忆。 可惜现在工作生活琐事太多,玩鸡的时间越来越少了。 列表上的这些鸡,除非是厂商跑路了或者是咕咕实在是入不敷出了,大概率会继续续费下去,也算是对过去日子的一些回忆。 OK,那差不多今天就聊这些,不知道有没有勾起老玩家的回忆,也欢迎大家评论区留下你印象最深的机器~ 参考链接: 搬瓦工产品合集: VPS 推荐列表: Hetzner 竞拍独服:

2026-07-20 更新 VMRack 洛杉矶自建机房部分服务器上线补货了。 提供洛杉矶了 VPS、裸金属服务器、CDN、对象存储及云转码等服务。 VPS 支持多种线路选择: • 三网精品:CN2 GIA / AS9929 / CMIN2 • 三网优化:163 / 10099 / CMI • 美国原生:Cogent / Arelion • 支持附加 IPv4 及内网 IP 适合建站、海外业务、流媒体以及对大陆连接质量有要求的用户。不同地区和运营商体验可能不同,可以根据自己的网络环境测试。 官网及套餐: 本群合作推广,具体配置、价格及库存以官网为准。 举例: VMRack - L3.VPS.DC2.2C2G.Base • CPU:2 核 • 内存:2 GB • 硬盘:20 GB • 带宽:500 Mbps • 流量:1 TB • 线路:三网精品 (CN2/9929/CMIN2) 💰 价格:$9.99/月 🛍 购买地址: #AFF VMRack - L1.VPS.2C4G.Base • CPU:2 核 • 内存:4 GB • 硬盘:60 GB • 带宽:5000 Mbps • 流量:4 TB • 线路:美国原生 (Cogent/Arelion) 💰 价格:$5.66/月 🛍 购买地址: #AFF 唠嗑 好久没折腾服务器了,今天趁着下雨在家,把手头目前已有的服务器都登录了一遍,收拾一下,也顺便给大家更新一下咕咕服务器的最新近况~ 地址: 目前持有的机器很多都是老古董了,买不到了。 Olink 实在是可惜,由于资金问题,加上老板本来就佛系运营,已经官宣停止运营了。 搬瓦工还是稳如老狗,咕咕的几台机器都已经要超过 1000 天在线了(The Chicken忘记续费被取消了 = =),之前黑五买的 Naranja Tech 也累计运行 886 天。 对搬瓦工有感兴趣的小伙伴可以在这边选购: CN2 GIA 高速优化线路 VPS 选购: 好久没买新的机器了,今天来测一下 VMRACK 这家宣传做了三网优化的精品线路的机器,看看到底怎么样。 1. VMRACK 简介 官网地址: VMRACK 主打三网优化线路,电信走 CN2 GIA,联通走 9929,移动走 CMIN2,看宣传还是挺有吸引力的。 先说结论,实测下来,线路确实非常稳。 2. 机器信息 咕咕手头的这台是 2 核 2G 内存,40G 硬盘,1.5T 流量/月,500Mbps 带宽 ,三网精品线路(电信走 CN2 GIA,联通走 9929,移动走 CMIN2)。 注意商家把三网优化线路和三网精品线路做了区分,三网精品线路更高一级 续费价格: 不过现在官方有特惠活动, 相同线路,最高性价比的35刀/年的售罄了,49刀/年的也售罄了,99刀/年的还有,还是比我这个常规款便宜不少 = = 地址: 3. 开始测试 这边咕咕用的是融合怪测试脚本,一键跑完各项测试~ curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh GitHub 地址:https://github.com/spiritLHLS/ecs 3.1 CPU 情况 CPU 型号:AMD EPYC-Rome Processor 单核跑分 1581 ,多核跑分 3178 ,表现还是不错的~ 3.2 内存 & 硬盘 内存读写速度正常,日常使用(跑 Docker 服务)没问题,fio 的 19.5k IOPS 足够撑住大多数应用场景。但 dd 测试暴露出读取性能偏弱,如果跑读密集型的业务(比如大量随机读取的数据库查询),可能会感受到瓶颈。 3.3 流媒体解锁 解锁情况还是比较理想的,直接丢几张图: Netflix、Disney+、ChatGPT、YouTube 这些主流平台基本都解锁了,日常使用完全够用~ 3.4 IP 质量 IP 为原生 IP,信任得分 100,整体质量不错。 3.5 邮件端口 主流邮箱的 SMTP 端口基本都是开放的,有自建邮件服务需求的小伙伴可以参考~ 3.6 三网回程情况 这个是重点了!确实是三网优化: 电信回程 :走 CN2 GIA 联通回程 :走 AS9929 移动回程 :走 CMIN2 三网都走了优化线路。 3.7 回程路由 从路由追踪可以看到,电信确实走了 CN2 GIA 到香港再回国,联通走的 9929,移动走的 CMIN2,和宣传一致。 3.8 PING 情况 150ms 左右,属于美西正常 ping 值。 3.9 测速情况 上下行基本都能跑满 500Mbps 的带宽,表现相当不错。 另外,VMRACK 支持 1 美元更换 IP ,对于有换 IP 需求的小伙伴来说还是比较友好的。 4. 总结 VPS 性能不错,具体的参数大家可以看上面的测评结果。 简单总结一下亮点: IP 为原生 IP ,解锁能力优秀 三网精品线路 :电信 CN2 GIA + 联通 9929 + 移动 CMIN2 1 美元更换 IP 的服务 有感兴趣的小伙伴可以去官网看看: 附:完整测试数据 点击展开完整测试报告 测评频道: https://t.me/+UHVoo2U4VyA5NTQ1 VPS融合怪版本:2026.02.28 Shell项目地址:https://github.com/spiritLHLS/ecs Go项目地址 [推荐]:https://github.com/oneclickvirt/ecs ---------------------基础信息查询--感谢所有开源项目---------------------- CPU 型号 : AMD EPYC-Rome Processor CPU 核心数 : 2 CPU 频率 : 2595.124 MHz CPU 缓存 : L1: 64.00 KB / L2: 1.00 MB / L3: 32.00 MB AES-NI指令集 : ✔ Enabled VM-x/AMD-V支持 : ❌ Disabled 内存 : 208.23 MiB / 1.92 GiB Swap : [ no swap partition or swap file detected ] 硬盘空间 : 1.67 GiB / 39.17 GiB 启动盘路径 : /dev/vda1 系统在线时间 : 1 days, 1 hour 27 min 负载 : 0.54, 0.16, 0.05 系统 : Debian GNU/Linux 12 (bookworm) (x86_64) 架构 : x86_64 (64 Bit) 内核 : 6.1.0-33-amd64 TCP加速方式 : bbr 虚拟化架构 : KVM NAT类型 : Full Cone IPV4 ASN : AS46783 EASY LINK LLC IPV4 位置 : Los Angeles / California / US ------------------------CPU测试--通过sysbench测试------------------------- -> CPU 测试中 (Fast Mode, 1-Pass @ 5sec) 1 线程测试(单核)得分: 1581 Scores 2 线程测试(多核)得分: 3178 Scores --------------------内存测试--感谢lemonbench开源---------------------------- -> 内存测试 Test (Fast Mode, 1-Pass @ 5sec) 单线程读测试: 41550.52 MB/s 单线程写测试: 19125.71 MB/s --------------------磁盘dd读写测试--感谢lemonbench开源-------------------- -> 磁盘IO测试中 (4K Block/1M Block, Direct Mode) 测试操作 写速度 读速度 100MB-4K Block 34.7 MB/s (8465 IOPS, 3.02s) 5.6 MB/s (1357 IOPS, 18.86s) 1GB-1M Block 554 MB/s (528 IOPS, 1.89s) 122 MB/s (115 IOPS, 8.62s) ----------------------磁盘fio读写测试--感谢yabs开源----------------------- Block Size | 4k (IOPS) | 64k (IOPS) ------ | --- ---- | ---- ---- Read | 78.10 MB/s (19.5k) | 1.02 GB/s (16.0k) Write | 78.31 MB/s (19.5k) | 1.03 GB/s (16.1k) Total | 156.41 MB/s (39.1k) | 2.06 GB/s (32.2k) | | Block Size | 512k (IOPS) | 1m (IOPS) ------ | --- ---- | ---- ---- Read | 612.37 MB/s (1.1k) | 528.12 MB/s (515) Write | 644.91 MB/s (1.2k) | 563.29 MB/s (550) Total | 1.25 GB/s (2.4k) | 1.09 GB/s (1.0k) ---------------流媒体解锁--感谢oneclickvirt/UnlockTests测试---------------- Can not detect IPv6 Address 测试时间: 2026-04-11 08:37:32 IPV4: ============[ 跨国平台 ]============ Apple YES (Region: USA) BingSearch YES (Region: US) Claude YES Dazn YES (Region: US) Disney+ YES (Region: US) Gemini YES (Region: US) GoogleSearch YES Google Play Store YES (Region: US) IQiYi YES (Region: US) Instagram Licensed Audio YES KOCOWA YES MetaAI YES Netflix YES (Region: US) Netflix CDN US OneTrust YES (Region: US CALIFORNIA) ChatGPT YES (Region: US) Paramount+ YES Amazon Prime Video YES (Region: US) Reddit YES SonyLiv YES (Region: IN) Sora YES (Region: US) Spotify Registration NO Steam Store YES (Community Available) (Region: US) TVBAnywhere+ YES (Region: US) TikTok YES (Region: US) Viu.com YES Wikipedia Editability YES YouTube Region YES YouTube CDN LAX ---------------------TikTok解锁--感谢lmc999的源脚本--------------------- Tiktok Region: 【US】 -------------IP质量检测--基于oneclickvirt/securityCheck使用-------------- 数据仅作参考,不代表100%准确,如果和实际情况不一致请手动查询多个数据库比对 以下为各数据库编号,输出结果后将自带数据库来源对应的编号 ipinfo数据库 [0] | scamalytics数据库 [1] | virustotal数据库 [2] | abuseipdb数据库 [3] | ip2location数据库 [4] ip-api数据库 [5] | ipwhois数据库 [6] | ipregistry数据库 [7] | ipdata数据库 [8] | db-ip数据库 [9] ipapiis数据库 [A] | ipapicom数据库 [B] | bigdatacloud数据库 [C] | dkly数据库 [D] | ipqualityscore数据库 [E] ipintel数据库 [F] | ipfighter数据库 [G] | fraudlogix数据库 [H] | cloudflare数据库 [I] | IPV4: 安全得分: 信任得分(越高越好): 100 [8] VPN得分(越低越好): 0 [8] 代理得分(越低越好): 0 [8] 社区投票-无害: 0 [2] 社区投票-恶意: 0 [2] 威胁得分(越低越好): 1 [8] 欺诈得分(越低越好): 65 [E] 滥用得分(越低越好): 0 [3] ASN滥用得分(越低越好): 0.0152 (Elevated) [A] 公司滥用得分(越低越好): 0.0009 (Low) [A] 威胁级别: low [B] 流量占比: 真人(越高越好)47% [I] 机器人(越低越好)52% [I] 黑名单记录统计:(有多少黑名单网站有记录): 无害记录数: 0 [2] 恶意记录数: 0 [2] 可疑记录数: 0 [2] 无记录数: 94 [2] 安全信息: 使用类型: unknown [8] hosting [0 3 7 C] business [A] 公司类型: hosting [A] isp [0 7] 浏览器类型: 主流82% 其他17% [I] 设备类型: 桌面77% 移动22% 其他0% [I] 操作系统类型: 主流94% 其他5% [I] 是否云提供商: Yes [7] 是否数据中心: No [5 8 A G] Yes [0 C] 是否移动设备: No [5 A C G] Yes [E] 是否代理: No [0 4 5 7 8 A B C] Yes [E G] 是否VPN: Yes [E] No [0 7 A C G] 是否Tor: No [0 3 7 8 A B C E] 是否Tor出口: No [7] 是否网络爬虫: No [A B E] 是否匿名: No [7 8] 是否攻击者: No [7 8] 是否滥用者: No [7 8 A C E] 是否威胁: No [7 8 C] 是否中继: No [0 7 8 C] 是否Bogon: No [7 8 A C] 是否机器人: No [E] DNS-黑名单: 304(Total_Check) 0(Clean) 10(Blacklisted) 17(Other) Google搜索可行性:NO ------------邮件端口检测--基于oneclickvirt/portchecker开源------------ Platform SMTP SMTPS POP3 POP3S IMAP IMAPS LocalPort ✔ ✔ ✔ ✔ ✔ ✔ QQ ✔ ✔ ✔ ✘ ✔ ✘ 163 ✔ ✔ ✔ ✘ ✔ ✘ Sohu ✔ ✔ ✔ ✘ ✔ ✘ Yandex ✔ ✔ ✔ ✘ ✔ ✘ Gmail ✔ ✔ ✘ ✘ ✘ ✘ Outlook ✔ ✘ ✔ ✘ ✔ ✘ Office365 ✔ ✘ ✔ ✘ ✔ ✘ Yahoo ✔ ✔ ✘ ✘ ✘ ✘ MailCOM ✔ ✔ ✔ ✘ ✔ ✘ MailRU ✔ ✔ ✘ ✘ ✔ ✘ AOL ✔ ✔ ✘ ✘ ✘ ✘ GMX ✔ ✔ ✔ ✘ ✔ ✘ Sina ✔ ✘ ✔ ✘ ✔ ✘ Apple ✘ ✔ ✘ ✘ ✘ ✘ FastMail ✘ ✔ ✘ ✘ ✘ ✘ ProtonMail✘ ✘ ✘ ✘ ✘ ✘ MXRoute ✔ ✘ ✔ ✘ ✔ ✘ Namecrane ✔ ✘ ✔ ✘ ✔ ✘ XYAMail ✘ ✘ ✘ ✘ ✘ ✘ ZohoMail ✘ ✔ ✘ ✘ ✘ ✘ Inbox_eu ✔ ✔ ✔ ✘ ✘ ✘ Free_fr ✘ ✔ ✔ ✘ ✔ ✘ -------------上游及三网回程--基于oneclickvirt/backtrace开源-------------- 国家: US 城市: Los Angeles 服务商: AS46783 EASY LINK LLC 北京电信v4 219.141.140.10 电信CN2GIA [精品线路] 北京联通v4 202.106.195.68 联通9929 [优质线路] 北京移动v4 221.179.155.161 移动CMI [普通线路] 移动CMIN2 [精品线路] 上海电信v4 202.96.209.133 检测不到回程路由节点的IPV4地址 上海联通v4 210.22.97.1 联通9929 [优质线路] 联通4837 [普通线路] 上海移动v4 211.136.112.200 移动CMI [普通线路] 移动CMIN2 [精品线路] 广州电信v4 58.60.188.222 电信CN2GIA [精品线路] 广州联通v4 210.21.196.6 联通9929 [优质线路] 广州移动v4 120.196.165.24 移动CMI [普通线路] 移动CMIN2 [精品线路] 成都电信v4 61.139.2.69 电信CN2GIA [精品线路] 成都联通v4 119.6.6.6 联通9929 [优质线路] 联通4837 [普通线路] 成都移动v4 211.137.96.205 移动CMI [普通线路] 移动CMIN2 [精品线路] 准确线路自行查看详细路由,本测试结果仅作参考 同一目标地址多个线路时,检测可能已越过汇聚层,除第一个线路外,后续信息可能无效 ----------------------回程路由--基于nexttrace开源----------------------- 依次测试电信/联通/移动经过的地区及线路,核心程序来自nexttrace,请知悉! 广州电信 58.60.188.222 0.52 ms * RFC1918 1.24 ms AS4134 [CHINANET-US] 美国 加利福尼亚 洛杉矶 chinatelecom.com.cn 电信 149.67 ms * [CN2-Global] 中国 香港 chinatelecom.cn 电信 150.87 ms * [CN2-BackBone] 中国 广东 广州 chinatelecom.cn 电信 157.66 ms * [CN2-BackBone] 中国 广东 广州 chinatelecom.cn 电信 151.97 ms AS4134 [CHINANET-BB] 中国 广东 广州 chinatelecom.com.cn 电信 159.56 ms AS4134 [APNIC-AP] 中国 广东 深圳 chinatelecom.com.cn 电信 广州联通 210.21.196.6 1.12 ms * RFC1918 1.31 ms AS10099 中国 香港 chinaunicomglobal.com 165.78 ms AS10099 [CUG-BACKBONE] 中国 香港 chinaunicomglobal.com 联通 172.08 ms AS9929 [CNC-BACKBONE] 中国 上海 chinaunicom.cn 联通 CUII 149.18 ms AS9929 [CNC-BACKBONE] 中国 广东 广州 chinaunicom.cn 联通 CUII 160.21 ms * [CNC-BACKBONE] 中国 广东 广州 190.30 ms AS17623 [APNIC-AP] 中国 广东 深圳 chinaunicom.cn 联通 193.60 ms AS17623 中国 广东 深圳 宝安区 chinaunicom.cn 联通 广州移动 120.196.165.24 0.68 ms * RFC1918 1.12 ms AS58807 [CMIN2-NET] 中国 cmi.chinamobile.com 移动 129.02 ms AS58807 [CMIN2-NET] 美国 加利福尼亚 洛杉矶 cmi.chinamobile.com 移动 126.54 ms AS58807 [CMIN2-NET] 中国 上海 洛杉矶-上海 cmi.chinamobile.com 移动 129.41 ms AS9808 [CMNET] 中国 上海 X-I chinamobileltd.com 移动 128.32 ms AS9808 [CMNET] 中国 上海 I-C chinamobileltd.com 移动 129.03 ms AS9808 [CMNET] 中国 上海 chinamobileltd.com 153.36 ms AS9808 [CMNET] 中国 北京 chinamobileltd.com 移动 151.70 ms AS9808 [CMNET] 中国 北京 chinamobileltd.com 移动 154.29 ms AS9808 [CMNET] 中国 北京 chinamobileltd.com 移动 153.31 ms AS56040 [APNIC-AP] 中国 广东 深圳 gd.10086.cn 移动 ---------------------自动更新测速节点列表--本脚本原创---------------------- 位置 上传速度 下载速度 延迟 丢包率 Speedtest.net 511.98 Mbps 494.12 Mbps 1.04 0.0% 检测到错误:Aborted或core dumped,终止测速 执行出现错误,如果有必要请使用 https://github.com/oneclickvirt/ecs 进行测试,避免环境依赖出现问题 联通上海5G 535.07 Mbps 546.82 Mbps 173.12 NULL 检测到错误:Aborted或core dumped,终止测速 执行出现错误,如果有必要请使用 https://github.com/oneclickvirt/ecs 进行测试,避免环境依赖出现问题 电信Suzhou5G 513.73 Mbps 553.80 Mbps 138.52 NULL 电信Zhenjiang5G 557.11 Mbps 548.61 Mbps 137.32 NULL 移动Suzhou 372.13 Mbps 0.39 Mbps 137.64 0.0% ------------------------------------------------------------------------ 总共花费 : 6 分 1 秒 时间 : Sat Apr 11 08:41:13 UTC 2026 ------------------------------------------------------------------------

好久没和大家更新博客了,这段时间真的事情巨多(家里的事情+公司出差+回来之后研究小龙虾和Claude Code),一直想着有时间就要更新一下,但是每次回到家忙完事情已经挺晚了,也感觉累了,就迟迟没有动手写博客。 今天趁着闲暇时间,赶紧和大家更新一波。 购机篇 我在3月份的时候出差去了趟德国,工作结束之后,我突然心血来潮说要不要整一部国际版的iPhone。 熟悉咕咕的小伙伴知道,其实我目前有一台美版的iPhone SE3在服役中(支持实体SIM+eSIM),手感非常好,续航问题我也通过背夹电池的方法解决了(官方方案的升级版,后续和大家分享,可以重度使用一天),但问题就是屏幕确实有点小了,打字也容易按错。 "这次出差算是比较成功的,下次带队不知道是啥时候了,买台iPhone 17 Pro Max奖励一下自己,也顺便给大家实测一下在德国购买iPhone的流程和后续的退税流程。" 嗯,我是这么说服我自己的。虽然我知道港版功能一样,而且更便宜…… 德国这边对换手机这件事情好像他们不怎么在意,地铁公交上很多都用的旧手机。因为我第二天就要出发返回国内,我先看了下小红书攻略,说最好提前预约,然后门店提货,会更稳一点,于是我就用我的SE3 在苹果的德国官网上下单了一台蓝色 512G的iPhone 17 Pro Max,用的Apple Pay选的Chase的信用卡付款的,选择线下提货的地点是慕尼黑玛丽亚广场的苹果官方旗舰店。 早起过去,人果然很多。 和工作人员说明来取货,出示了订单和二维码, 很快小姐姐就给我拿来了手机。 图片 小姐姐 然后我表示我需要退税,小姐姐似乎不太懂,询问了身边的同事,叽里咕噜的,德语我们也听不懂,然后小姐姐去拿来了一张单子,长这样: 你敢相信,在德国给外国人的退税的单子居然是德语的,而且小姐姐很抱歉地说没有英语的版本,可能需要我们自己借助翻译填写一下…… 而且由于我们是线上下单的,店里并没有给我们发票。(我查询了一下发现退税需要有退税单和发票还有实物的) 退税踩坑 我虽然表示震惊,但也接受了,凭借我多年官网购买iPhone的经验,苹果官方肯定会给我邮件发一个发票的,而且看起来这个表是苹果官方的退税,没有中间商赚差价,可以退全额,你知道德国的电子产品消费税是多少吗?19% ! 怪不得感觉大家都换新手机不感冒…… 然而新的问题出现了…… 我下午翻看邮件,一直没有等到苹果给我发的发票,没有发票就意味着海关不可能给我退税,我又查了下,发现在官网购买的手机,电子发票可能是在24-48小时内送达到邮箱!我的机票是第二天早上的,时间上来不及了。 于是我和同事又感受了大概四十分钟的地铁,从慕尼黑机场的希尔顿返回到玛丽亚广场。我找了个理由和店员说支付的时候卡弄错了,想要把这台网上订的、上午刚提的iPhone退了,然后换一张卡在店里直接买一台。 这次接待的是一个小哥,小哥欣然同意表示没问题,简单检查了手机之后,直接给我退了款,然后我换了一张招行的卡支付,他还是把这台手机给了我,并熟练地给我global blue的单子,告诉我可以退大概10%的税,而且在玛丽亚广场不远的地方有global blue的柜台。(同事略懂日语,还和小哥谈起了他手臂上的纹身 = =) 我们过去之后工作人员给盖了章,然后工作人员让我出示了信用卡(因为我打算退到信用卡里),最后告诉了我慕尼黑机场的海关的位置,以及告诉我敲完海关的章需要把信封交给机场的global blue的柜台,整体体验非常丝滑。 退税篇 手机总算告一段落,但是还有一个问题出现了…… 本来我打算直接拆封使用的,但是我还是做了一下功课——购买的产品不能使用,一旦使用了之后海关不给退税! 所以还是只能老老实实装在包里,等第二天退完税之后使用。 我这次是慕尼黑 T1 航站楼出发,海关在 B 区,Global Blue 就在对面,流程还算比较清楚。 如果你买的东西是走 Global Blue 退税的,而且放在托运行李箱里,值机的时候一定要先跟工作人员说: 箱子里有 tax free items 工作人员会先给你的行李箱贴标签,然后你拿着箱子去海关那边盖章,盖完之后海关会直接帮你把行李托运走,因为前面已经贴好标签了,所以不用担心箱子乱跑。 之后你再去对面的 Global Blue 柜台办后续就可以。 如果退税物品是在随身行李里,正常来说是要过完安检以后,再去里面的海关检查盖章。 不过我这次也直接拿给外面的海关看了,结果他也给我盖章了,所以我这次就一起退掉了,但这个我感觉不一定每次都行,可能还是看现场工作人员。 几个小经验: 小额的东西一般不太查 大额的基本都会查 买的东西千万别拆封 这次买的手机,海关那边检查了非常久,反复用手指抠,问我是不是拆过。所以像 iPhone 这种高价值商品,千万别提前拆,不然很容易影响退税。 顺便说一下,这次除了手机,我在 dm 也顺手买了一些东西。dm 的退税流程和 Global Blue 不太一样,麻烦不少——要么你下次回店里,拿海关盖章的小票再去退;要么走国内的易退税邮寄处理。两种我个人觉得都不算方便,有机会还是直接去店里退更省事。不过这次 dm 的东西,海关是完全没检查。 最终到手价格 最后,看看这部手机退税下来一共花了多少钱: 手机原价 13525.96 - 汇率优惠 13.53 - Global Blue 退税 1340.78 - 招行1%返现 131.46 = 12040.19 元 招行那个1%的返现好像实际到账不足1%? 比起国内还是贵了不少的。 关于德国版 iPhone 最后,给大家看一下德国版本的iPhone有啥不一样的地方: 其实除了这个CE标识,其他的和港版应该是一样的,日常eSIM需求的更推荐港版,我这个就当纪念了 = =

1. 唠嗑 好久没更新了,这周来和大家分享一个自托管的在线文件转换器。支持超过一千种不同格式。使用 TypeScript、Bun 和 Elysia 编写。 2. ConvertX 简介 功能特性 将文件转换为不同格式 一次处理多个文件 密码保护 多个账户 支持转换的类型 Converter Use case Converts from Converts to Vector images 7 17 JPEG XL 11 11 SVG 1 1 Images 45 23 HEIF 2 4 LaTeX 1 1 E-books 26 19 Documents 41 22 Data Files 5 4 Documents 43 65 Outlook 1 1 VCF to CSV Contacts 1 1 Vector images 4 2 Images 245 183 Images 167 130 3D Assets 77 23 Video ~472 ~199 Raster to vector 4 11 Raster to vector 8 1 Documents 6 1 3. 相关地址 官方 GitHub 地址: (目前 15.3k 个 star,欢迎大家去给项目点星星!) 当然如果你想自己用 docker 搭建一个,那我们就继续往下! 4. 搭建环境 服务器:咕咕这边用的香港的 ,建议服务器内存 1G 以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩周补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/convertx cd /root/data/docker_data/convertx 接着我们来编辑下 docker-compose.yml vim docker-compose.yml services: convertx: image: ghcr.io/c4illin/convertx container_name: convertx restart: unless-stopped ports: - "3001:3000" environment: - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken123412 # will use randomUUID() if unset # - HTTP_ALLOWED=true # uncomment this if accessing it over a non-https connection 如果通过 http 访问的话取消这行注释 - LANGUAGE=zh volumes: - ./data:/app/data 其中的左边的 3001 可以改成服务器上没有用过的端口。 修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 环境变量 所有都是可选的,但建议设置 JWT_SECRET 。 Name Default Description JWT_SECRET when unset it will use the value from randomUUID() 用于签署 JSON Web Token 的一串密钥字符串 ACCOUNT_REGISTRATION false 允许用户注册账户 HTTP_ALLOWED false 允许 HTTP 连接,仅在本地部署的时候将它设置为 true ALLOW_UNAUTHENTICATED false 允许未认证用户使用该服务,仅在本地部署的时候将它设置为 true。 AUTO_DELETE_EVERY_N_HOURS 24 每 n 小时检查一次,删除超过 n 小时的文件,设置为 0 表示禁用。 WEBROOT 将根路径设置为 "/convert" 的地址将在 "example.com/convert/" 上提供网站。 FFMPEG_ARGS 传递给 ffmpeg 输入文件的参数,例如 -hwaccel vaapi 。有关硬件加速的更多信息,请参见 。 FFMPEG_OUTPUT_ARGS 传递给 ffmpeg 输出的参数,例如 -preset veryfast HIDE_HISTORY false 隐藏历史页面 LANGUAGE en 用于格式化日期字符串的语言,指定为 UNAUTHENTICATED_USER_SHARING false 所有未经身份验证用户共享转换历史记录 MAX_CONVERT_PROCESS 0 允许的最大并发转换进程数量。设置为 0 表示无限制。 按需添加修改,修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 6.3 查看端口是否被占用 查看端口是否被占用(以 3001 为例),输入: lsof -i:3001 #查看 3001 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 6.4 启动 convertx cd /root/data/docker_data/convertx docker compose up -d # 注意,老版本用户用 docker-compose up -d 镜像还是比较大的…… 耐心等待拉取好镜像,出现 Created 的字样之后, 理论上我们就可以输入 http://ip:3001 访问了。 但是我们部署在公网一定要考虑使用反向代理工具配置 SSL! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo 上 6 位数字的 xyz 续费永远都是 0.99 美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 3001 IP 填写: 如果 Nginx Proxy Manager 和 convertx 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 convertx 所在的服务器 IP 就行。 然后访问域名就可以访问了! 7.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:3001/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 8. 使用教程 非常简单,所见即所得。 先注册账号: JPG 转换成 webp MP4 转 AVI 正常转换。 转换历史: 服务器里还是能看到文件的, 8.1 更新 convertx 这个项目后续会持续有更新,所以提供一个更新的方式。 cd /root/data/docker_data/convertx docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 8.2 卸载 convertx 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/convertx docker compose down cd .. rm -rf /root/data/docker_data/convertx # 完全删除 可以卸载得很干净。 9. 使用问题 大家使用上有什么心得或者问题,欢迎评论区交流~ 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star。 参考资料 官方 GitHub:

1. 唠嗑 好久没更新了,这周来和大家分享一个 AI 驱动的图表生成器——Next-AI-Draw.io 2. Next-AI-Draw.io 简介 Next-AI-Draw.io 是一个集成了 AI 功能的 Next.js 网页应用,与 draw.io 图表无缝结合。通过自然语言命令和 AI 辅助可视化来创建、修改和增强图表。 功能特性 LLM 驱动的图表创建 :利用大语言模型通过自然语言命令直接创建和操作 draw.io 图表 基于图像的图表复制 :上传现有图表或图像,让 AI 自动复制和增强 PDF 和文本文件上传 :上传 PDF 文档和文本文件,提取内容并从现有文档生成图表 AI 推理过程显示 :查看支持模型的 AI 思考过程(OpenAI o1/o3、Gemini、Claude 等) 图表历史记录 :全面的版本控制,跟踪所有更改,允许您查看和恢复 AI 编辑前的图表版本 交互式聊天界面 :与 AI 实时对话来完善您的图表 云架构图支持 :专门支持生成云架构图(AWS、GCP、Azure) 动画连接器 :在图表元素之间创建动态动画连接器,实现更好的可视化效果 3. 相关地址 官方 GitHub 地址: (目前 13k 个 star,欢迎大家去给项目点星星!) Demo 地址: 当然如果你体验完还想自己用 docker 搭建一个,那我们就继续往下! 4. 搭建环境 服务器:咕咕这边用的香港的 ,建议服务器内存 1G 以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩周补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/drawioai cd /root/data/docker_data/drawioai 接着我们来编辑下 docker-compose.yml vim docker-compose.yml name: nextaidrawio services: next-ai-draw-io: ports: - 3001:3000 env_file: - .env image: ghcr.io/dayuanjiang/next-ai-draw-io:latest 其中的左边的 3001 可以改成服务器上没有用过的端口。 修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 接着我们来编辑下 .env cd /root/data/docker_data/drawioai vim .env 粘贴如下内容: # AI Provider Configuration # AI_PROVIDER: Which provider to use # Options: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, gateway # Default: bedrock AI_PROVIDER=bedrock # AI_MODEL: The model ID for your chosen provider (REQUIRED) AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0 # AWS Bedrock Configuration # AWS_REGION=us-east-1 # AWS_ACCESS_KEY_ID=your-access-key-id # AWS_SECRET_ACCESS_KEY=your-secret-access-key # Note: Claude and Nova models support reasoning/extended thinking # BEDROCK_REASONING_BUDGET_TOKENS=12000 # Optional: Claude reasoning budget in tokens (1024-64000) # BEDROCK_REASONING_EFFORT=medium # Optional: Nova reasoning effort (low/medium/high) # OpenAI Configuration # OPENAI_API_KEY=sk-... # OPENAI_BASE_URL=https://api.openai.com/v1 # Optional: Custom OpenAI-compatible endpoint # OPENAI_ORGANIZATION=org-... # Optional # OPENAI_PROJECT=proj_... # Optional # Note: o1/o3/gpt-5 models automatically enable reasoning summary (default: detailed) # OPENAI_REASONING_EFFORT=low # Optional: Reasoning effort (minimal/low/medium/high) - for o1/o3/gpt-5 # OPENAI_REASONING_SUMMARY=detailed # Optional: Override reasoning summary (none/brief/detailed) # Anthropic (Direct) Configuration # ANTHROPIC_API_KEY=sk-ant-... # ANTHROPIC_BASE_URL=https://your-custom-anthropic/v1 # ANTHROPIC_THINKING_TYPE=enabled # Optional: Anthropic extended thinking (enabled) # ANTHROPIC_THINKING_BUDGET_TOKENS=12000 # Optional: Budget for extended thinking in tokens # Google Generative AI Configuration # GOOGLE_GENERATIVE_AI_API_KEY=... # GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta # Optional: Custom endpoint # GOOGLE_CANDIDATE_COUNT=1 # Optional: Number of candidates to generate # GOOGLE_TOP_K=40 # Optional: Top K sampling parameter # GOOGLE_TOP_P=0.95 # Optional: Nucleus sampling parameter # Note: Gemini 2.5/3 models automatically enable reasoning display (includeThoughts: true) # GOOGLE_THINKING_BUDGET=8192 # Optional: Gemini 2.5 thinking budget in tokens (for more/less thinking) # GOOGLE_THINKING_LEVEL=high # Optional: Gemini 3 thinking level (low/high) # Azure OpenAI Configuration # Configure endpoint using ONE of these methods: # 1. AZURE_RESOURCE_NAME - SDK constructs: https://{name}.openai.azure.com/openai/v1{path} # 2. AZURE_BASE_URL - SDK appends /v1{path} to your URL # If both are set, AZURE_BASE_URL takes precedence. # AZURE_RESOURCE_NAME=your-resource-name # AZURE_API_KEY=... # AZURE_BASE_URL=https://your-resource.openai.azure.com/openai # Alternative: Custom endpoint # AZURE_REASONING_EFFORT=low # Optional: Azure reasoning effort (low, medium, high) # AZURE_REASONING_SUMMARY=detailed # Ollama (Local) Configuration # OLLAMA_BASE_URL=http://localhost:11434/api # Optional, defaults to localhost # OLLAMA_ENABLE_THINKING=true # Optional: Enable thinking for models that support it (e.g., qwen3) # OpenRouter Configuration # OPENROUTER_API_KEY=sk-or-v1-... # OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 # Optional: Custom endpoint # DeepSeek Configuration # DEEPSEEK_API_KEY=sk-... # DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 # Optional: Custom endpoint # SiliconFlow Configuration (OpenAI-compatible) # Base domain can be .com or .cn, defaults to https://api.siliconflow.com/v1 # SILICONFLOW_API_KEY=sk-... # SILICONFLOW_BASE_URL=https://api.siliconflow.com/v1 # Optional: switch to https://api.siliconflow.cn/v1 if needed # Vercel AI Gateway Configuration # Get your API key from: https://vercel.com/ai-gateway # Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5" # AI_GATEWAY_API_KEY=... # Langfuse Observability (Optional) # Enable LLM tracing and analytics - https://langfuse.com # LANGFUSE_PUBLIC_KEY=pk-lf-... # LANGFUSE_SECRET_KEY=sk-lf-... # LANGFUSE_BASEURL=https://cloud.langfuse.com # EU region, use https://us.cloud.langfuse.com for US # Temperature (Optional) # Controls randomness in AI responses. Lower = more deterministic. # Leave unset for models that don't support temperature (e.g., GPT-5.1 reasoning models) # TEMPERATURE=0 # Access Control (Optional) # ACCESS_CODE_LIST=your-secret-code,another-code # Draw.io Configuration (Optional) # NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net # Default: https://embed.diagrams.net # Use this to point to a self-hosted draw.io instance # PDF Input Feature (Optional) # Enable PDF file upload to extract text and generate diagrams # Enabled by default. Set to "false" to disable. # ENABLE_PDF_INPUT=true # NEXT_PUBLIC_MAX_EXTRACTED_CHARS=150000 # Max characters for PDF/text extraction (default: 150000) 这里头有很多选项可以开启,当然你也可以不管它,到网页端输入,这样的话就保存在本地浏览器里了,如果在这边输入,所有知道这个网址的人都能用你的 AI 模型。 假设我们用的 OPEN AI 的,其实 .env 里面只要这仨: AI_PROVIDER=openai AI_MODEL=gpt-4o OPENAI_API_KEY=your_api_key your_api_key 改成你的 API 密钥。 修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 6.3 查看端口是否被占用 查看端口是否被占用(以 3001 为例),输入: lsof -i:3001 #查看 3001 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 6.4 启动 drawioai cd /root/data/docker_data/drawioai docker compose up -d # 注意,老版本用户用 docker-compose up -d 耐心等待拉取好镜像,出现 done 的字样之后, 理论上我们就可以输入 http://ip:3001 访问了。 但是我们部署在公网一定要考虑使用反向代理工具配置 SSL! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo 上 6 位数字的 xyz 续费永远都是 0.99 美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 3001 IP 填写: 如果 Nginx Proxy Manager 和 drawioai 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 drawioai 所在的服务器 IP 就行。 然后访问域名就可以访问了! 7.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:3001/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 8. 使用教程 非常简单,所见即所得。 登录页, 语言设置,改成中文, 简单展示 右边可以输入提示词,比如让它画一只猫猫, 画的熊猫不太像…… 当然你也可以隐藏掉这个提示词输入框,把它当成一个普通的画图软件来用, 如果用默认的,其实是用的项目作者的 API,速率可能会有限制,当然用自己的 API 就没有这个问题了,如果手上富裕,也可以考虑捐赠作者,支持开源项目长久发展。 8.1 更新 drawioai 这个项目后续会持续有更新,所以提供一个更新的方式。 cd /root/data/docker_data/drawioai docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 8.2 卸载 drawioai 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/drawioai docker compose down cd .. rm -rf /root/data/docker_data/drawioai # 完全删除 可以卸载得很干净。 9. 使用问题 那个保存按钮点了似乎没有任何反馈…… 不过画的图导出是没问题的。 大家使用上有什么心得或者问题,欢迎评论区交流~ 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star。 参考资料 官方 GitHub:

1. 唠嗑 好久没更新了,这周来和大家分享一个好用的书签工具——Karakeep 2. karakeep 简介 Karakeep(前身为 Hoarder)是一款可自托管的「万物皆可存」书签应用,专为数据囤积爱好者打造,融入一丝 AI 魔法。 功能 🔗 书签链接、记录简单笔记,并存储图片和 PDF 文件。 ⬇️ 自动抓取链接标题、描述和预览图。 📋 将书签整理到不同列表中。 🔎 全文本搜索所有存储内容。 ✨ 基于 AI(类似 ChatGPT)自动打标签和生成摘要。支持使用 Ollama 运行本地模型! 🤖 规则引擎,实现自定义管理。 🎆 图像 OCR,提取图片中的文字。 🔖 Chrome 插件和 Firefox 扩展,快速添加书签。 📱 iOS 应用和 Android 应用。 📰 从 RSS 订阅源自动归档内容。 🔌 REST API 和多种客户端。 🌐 多语言支持。 🖍️ 标记并保存归档内容中的高亮片段。 🗄️ 完整页面归档(使用 monolith),防止链接失效。 ▶️ 使用 yt-dlp 自动归档视频。 ☑️ 支持批量操作。 🔐 支持 SSO 单点登录。 🌙 深色模式。 💾 优先支持自托管。 ⬇️ 从 Chrome、Pocket、Linkwarden、Omnivore、Tab Session Manager 导入书签。 🔄 通过 floccus 与浏览器书签自动同步。 [计划中] 移动端离线阅读、书签语义搜索,…… 3. 相关地址 官方 GitHub 地址: (目前 21.1k 个 star,欢迎大家去给项目点星星!) Demo 地址: 默认账号密码: Email: demo@karakeep.app Password: demodemo 当然如果你体验完还想自己用 docker 搭建一个,那我们就继续往下! 4. 搭建环境 服务器:咕咕这边用的香港的 ,建议服务器内存 1G 以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩周补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/karakeep cd /root/data/docker_data/karakeep 接着我们来编辑下 docker-compose.yml vim docker-compose.yml services: web: image: ghcr.io/karakeep-app/karakeep:latest restart: unless-stopped volumes: # By default, the data is stored in a docker volume called "data". # If you want to mount a custom directory, change the volume mapping to: # - /path/to/your/directory:/data - ./data:/data ports: - 3030:3000 env_file: - .env environment: MEILI_ADDR: http://meilisearch:7700 BROWSER_WEB_URL: http://chrome:9222 # OPENAI_API_KEY: ... # You almost never want to change the value of the DATA_DIR variable. # If you want to mount a custom directory, change the volume mapping above instead. DATA_DIR: /data # DON'T CHANGE THIS chrome: image: gcr.io/zenika-hub/alpine-chrome:124 restart: unless-stopped command: - --no-sandbox - --disable-gpu - --disable-dev-shm-usage - --remote-debugging-address=0.0.0.0 - --remote-debugging-port=9222 - --hide-scrollbars meilisearch: image: getmeili/meilisearch:v1.13.3 restart: unless-stopped env_file: - .env environment: MEILI_NO_ANALYTICS: "true" volumes: - ./meilisearch:/meili_data 其中的左边的 3030 可以改成服务器上没有用过的端口,如果要用到AI功能的话,可以取消 OPENAI_API_KEY 的注释,填入自己的密钥。 修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 接着我们来编辑下 .env cd /root/data/docker_data/karakeep vim .env 粘贴如下内容: KARAKEEP_VERSION=release NEXTAUTH_SECRET=super_random_string MEILI_MASTER_KEY=another_random_string NEXTAUTH_URL=http://localhost:3000 其中的 http://localhost:3000 这个改成你自己之后用域名访问的地址,比如我的 https://karakeep.gugu.ovh 其中的 super_random_string 和 another_random_string 需要我们自己生成。 我们可以先在命令行里面输入 openssl rand -base64 36 生成随机的字符,然后替代它们。 我需要放在 .env 里的内容就是: KARAKEEP_VERSION=release NEXTAUTH_SECRET=Lu4YuG5pXji2/QB6blXaoWnfK2WdtKifPhEBt0UQCHqkxCAe MEILI_MASTER_KEY=wJ222cTFWJ0/9Jnu38gU9Wb+AdCk2SBmQ687MdTbxT8c/V0b NEXTAUTH_URL=https://karakeep.gugu.ovh 修改完成之后,同样的,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 6.3 查看端口是否被占用 查看端口是否被占用(以 3030 为例),输入: lsof -i:3030 #查看 3030 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 6.4 启动 karakeep cd /root/data/docker_data/karakeep docker compose up -d # 注意,老版本用户用 docker-compose up -d 耐心等待拉取好镜像,出现 done 的字样之后, 理论上我们就可以输入 http://ip:3030 访问了。 但是这边这个服务必须先搞一下反向代理!既然是加密的项目,不能用 http 使用明文传输,我们部署在公网一定要考虑使用反向代理工具配置 SSL! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo 上 6 位数字的 xyz 续费永远都是 0.99 美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 3030 IP 填写: 如果 Nginx Proxy Manager 和 karakeep 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 karakeep 所在的服务器 IP 就行。 然后访问域名就可以访问了! 7.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:3030/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 8. 使用教程 非常简单,所见即所得。 登录, 首先注册一个账号, 后台页面, 用户设置, 如果觉得英文不习惯,也可以改成中文, 输入一个网址,测试一下, 支持给你存下来的网页打标签, 服务器统计情况, Karakeep是支持浏览器插件的,这边可以直接下载浏览器插件,方便浏览网页的时候使用, 输入你搭建好的域名,然后登录即可, 使用 也支持下载完整页面, 还有一些AI功能,大家可以自行探索。或者查看 8.1 更新 karakeep 这个项目后续会持续有更新,所以提供一个更新的方式。 cd /root/data/docker_data/karakeep docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 8.2 卸载 karakeep 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/karakeep docker compose down cd .. rm -rf /root/data/docker_data/karakeep # 完全删除 可以卸载得很干净。 9. 使用简介 大家有问题欢迎评论区交流。 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star。 参考资料 官方 GitHub:

1. 唠嗑 好久没更新了,这周来和大家分享一个阅后即焚工具,安全高效的一次性私密信息共享平台——OTS(One-Time-Secret) 2. OTS 简介 OTS 是一个一次性私密信息分享平台。私密信息在发送到服务器之前,会在浏览器中使用对称的 256 位 AES 加密进行加密。之后会生成一个包含私密信息的 ID 和密码的 URL。密码永远不会发送到服务器,因此服务器无法以合理的方式解密它所传递的私密信息。此外,私密信息在第一次读取后会立即被删除。 功能 私密信息在浏览器中通过 AES 256 位加密进行加密 服务器永远不会接收到明文私密信息 私密信息在第一次读取后即被删除 3. 相关地址 官方 GitHub 地址: (目前 638 个 star,欢迎大家去给项目点星星!) Demo 地址: 当然如果你看到这边还想自己用 docker 搭建一个,那我们就继续往下! 4. 搭建环境 服务器:咕咕这边用的香港的 ,建议服务器内存 1G 以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩天补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/OTS cd /root/data/docker_data/OTS 接着我们来编辑下 docker-compose.yml vim docker-compose.yml services: app: image: ghcr.io/luzifer/ots:latest container_name: ots-app restart: always ports: - 3003:3000 environment: # Optional, see "Customization" in README #CUSTOMIZE: '/etc/ots/customize.yaml' # See README for details REDIS_URL: redis://redis:6379/0 # 168h = 1w SECRET_EXPIRY: "604800" # "mem" or "redis" (See README) STORAGE_TYPE: redis depends_on: - redis redis: image: redis:latest container_name: ots-redis restart: always volumes: - ./data:/data 环境变量的说明如下: 环境变量 描述 REDIS_URL Redis 数据库的连接字符串,格式为 redis://USR:PWD@HOST:PORT/DB 。用于指定 OTS 应用连接的 Redis 实例 SECRET_EXPIRY 秘密的过期时间,单位为秒(默认 0 = 无过期)。此示例中设置为 604800 (168 小时 = 1 周) STORAGE_TYPE 用于指定存储类型,可以是 mem (内存存储)或 redis (使用 Redis 存储秘密) 最后,在 ots 目录下执行以下命令一键启动: 其中的左边的 3003 可以改成服务器上没有用过的端口,记得修改自己的用户名和密码,修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 6.3 查看端口是否被占用 查看端口是否被占用(以 3003 为例),输入: lsof -i:3003 #查看 3003 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 6.4 启动 OTS cd /root/data/docker_data/OTS docker compose up -d # 注意,老版本用户用 docker-compose up -d 耐心等待拉取好镜像,出现 done 的字样之后, 理论上我们就可以输入 http://ip:3003 访问了。 但是这边这个服务必须先搞一下反向代理!既然是加密的项目,不能用 http 使用明文传输,我们部署在公网一定要考虑使用反向代理工具配置 SSL! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo 上 6 位数字的 xyz 续费永远都是 0.99 美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 3003 IP 填写: 如果 Nginx Proxy Manager 和 OTS 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 OTS 所在的服务器 IP 就行。 然后访问域名就可以访问了! 7.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:3003/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 8. 使用教程 非常简单,所见即所得。 8.1 更新 OTS 这个项目后续会持续有更新,所以提供一个更新的方式。 cd /root/data/docker_data/OTS docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 8.2 卸载 OTS 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/OTS docker compose down cd .. rm -rf /root/data/docker_data/OTS # 完全删除 可以卸载得很干净。 9. 常见问题及注意点 大家有问题欢迎评论区交流。 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star。 参考资料 官方 GitHub:

简介 Perplexity是什么? Perplexity 是一个结合了 AI 聊天机器人和搜索引擎功能的工具,旨在快速提供准确、简洁的答案,并附上可靠的来源引用。它通过理解用户问题,从网络上实时抓取信息,整理成清晰的回答,特别适合需要快速查资料或做研究的用户。 Pro版本的功能: Perplexity Pro 提供更高级的功能,收费20美元/月,功能包括: 多模型选择 :用户可切换使用 GPT-4o、Claude 3.7、Gemini Flash 等不同语言模型,灵活应对不同需求。 Pages报告生成 :可根据用户需求生成定制化的精美简报,适合研究或商务用途。 高级搜索功能 :如学术论文模式,专注搜索学术资源(如 Arxiv、SemanticScholar),提升研究效率。 更高使用额度 :相比免费版,Pro 版支持更多查询次数和更复杂的任务。 如果你需要快速查资料、做研究或验证信息,Perplexity 是最佳选择,尤其适合学术和专业场景。 领取条件 1个PayPal账号(我用的是美区,据网友测试国区也可以) 能访问真正的互联网 Perplexity 账号(可以现场注册) 领取方式 领取地址: 链接PayPal, 我这边登录的是美区的PayPal, 选择接受优惠, 成功领取! 已经可以正常使用了! 如果担心被反薅,记得取消 Perplexity 自动续费订阅, 访问: 取消订阅即可,不影响使用 同样的,Perplexity 还支持Mac桌面端,也可以用起来了! 注意点 这次似乎国区的PayPal也可以领取。 有不少网友评价Perplexity玩不起,之前三星的泄漏的一年优惠码,没用几天被收回了。 也有网友表示现在这次打开会提示开通过了。 You are already subscribed or have previously subscribed to Perplexity Pro. 还有一些一注册上就风控,即使美区也是,原先注册好的死活都不让领说领过了,换无数个邮箱尝试依旧如此,这个时候可以考虑换一个干净的IP再试试。

1. 唠嗑 这两三年来,我们搭建了不少好玩的Docker应用,其实一直陆陆续续有小伙伴在视频评论区和博客的评论区询问,怎么样才能让我的Docker应用更安全一点? 这一期,我们就来介绍这样一个工具,用最简单的方式来给我们的Docker应用加一层保险! 效果展示: 保存之后。 再次打开简历这个网页。 提示需要登录了。 输入账号密码,继续 进来了。 2. Tinyauth简介 Tinyauth 是个简单好用的认证中间件,能给你的 Docker 应用加个简洁的登录页面,或者支持 Google、GitHub 以及其他平台的 OAuth 认证。它跟主流代理比如 Nginx Proxy Manager、Traefik、Nginx 和 Caddy 都能无缝配合。 3. 相关地址 官方GitHub地址: (目前5.3K个star,欢迎大家去给项目点星星!) 文档地址: Demo地址: 下面我们就用 Tinyauth配合 Nginx Proxy Manager,简单分享一下是怎么搭建这个中间件从而来保护我们的Docker应用的。 4. 搭建环境 服务器:这边强烈建议用非大陆的服务器,咕咕这边用的香港的 ,建议服务器内存1G以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩天补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 我们这边假设大家都装好了Nginx Proxy Manger, 假设Nginx Proxy Manger在服务器上的路径是 ~/data/docker_data/npm 这边的话就只需要修改一下即可。 官方文档给的参考如下: services: npm: container_name: npm image: jc21/nginx-proxy-manager:2 restart: unless-stopped ports: - 80:80 - 443:443 - 81:81 volumes: - npm-data:/data - npm-letsencrypt:/etc/letsencrypt nginx: container_name: nginx image: nginx:latest restart: unless-stopped tinyauth: container_name: tinyauth image: ghcr.io/steveiliop56/tinyauth:v3 restart: unless-stopped environment: - SECRET_FILE=/secret.txt - APP_URL=http://tinyauth.example.com - USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password volumes: npm-data: npm-letsencrypt: 如果直接使用,你一定会遇到很多问题。 我不会告诉你我用官方的这个配置文件,折腾了大半小时 = = 这边直接用咕咕修改过的这个: 重新编辑你的NPM的 docker-compose.yaml 文件,改成下面这个: services: npm: container_name: npm image: jc21/nginx-proxy-manager:2 restart: unless-stopped ports: - 80:80 - 443:443 - 81:81 volumes: - ./data:/data # 冒号左边的 ./data改成你自己实际的路径 - ./letsencrypt:/etc/letsencrypt # 冒号左边的 ./letsencrypt改成你自己实际的路径 tinyauth: container_name: tinyauth image: ghcr.io/steveiliop56/tinyauth:v3 restart: unless-stopped environment: - SECRET=kX9mPqW3zT7rY2vN8bL4jF6hD1cA5eK2 # 这个也要修改 - APP_URL=https://auth.gugu.ovh #这个也要修改 - USERS=roy:$$2a$$10$$oXMqgLV/S1hrknYQht.WYu7YfWxkI4VriQn/y # user:password 这边要修改 然后我们来看需要修改的部分。 SECRET ,这个的话,直接在命令行操作: 生成secret: openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32 && echo 输出类似: kX9mPqW3zT7rY2vN8bL4jF6hD1cA5eK2 填入yaml即可。 APP_URL 这里改成你的tinyauth将来想要使用的域名,比如我这边 https://auth.gugu.ovh USERS ,这个官方也提供了方法,生成用户名和密码。 docker run -i -t --rm ghcr.io/steveiliop56/tinyauth:v3 user create --interactive 为docker格式化输出这个,记得选 yes (按住shift再按小键盘方向键就可以选) 原因是USERS这边在yaml文件里要做哈希转换(比如哈希中的 $ 需要正确转义为 $$ ,命令行的 Docker 格式会自动处理。),如果不格式化输出会有问题。 当然也支持多个用户,可以再打一遍代码,设置第二个用户, 如果是一个用户,我们直接贴到yaml文件里即可,但是如果像我们现在这样,有三个用户,直接放进yaml文件会有点长,而且也不好维护。 所以,我们可以把用户配置放入 .env 文件,可以这样做: 编辑 ~/data/docker_data/npm/.env : SECRET=kX9mPqW3zT7rY2vN8bL4jF6hD1cA5eK2 APP_URL=https://auth.gugu.ovh USERS=roy:$$2a$$10$$oXMqgLV/S1hrknYQht.WYu7YfEjBdSB5rIicj23O8WxkI4VriQn/y,gugu:$$2a$$10$$s.nq/KZLBlFwUiSUNyRUGe0j2pp.A9/.tecuX7QsCBq242baKgKJ.,xixi:$$2a$$10$$6smj5Y/PD.jB1a2Z2gDAxOVnlcv8HfINtLrzCmmTjMZLObBwV8DoW 修改 docker-compose.yaml 以引用 .env : services: npm: container_name: npm image: jc21/nginx-proxy-manager:2 restart: unless-stopped ports: - 80:80 - 443:443 - 81:81 volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt nginx: container_name: nginx image: nginx:latest restart: unless-stopped tinyauth: container_name: tinyauth image: ghcr.io/steveiliop56/tinyauth:v3 restart: unless-stopped env_file: - .env # environment: # - SECRET=kX9mPqW3zT7rY2vN8bL4jF6hD1cA5eK2 # - APP_URL=https://auth.gugu.ovh # - USERS=roy:$$2a$$10$$oXMqgLV/S1hrknYQht.WYu7YfEjBdSB5rIicj23O8WxkI4VriQn/y # user:password 这样基本上就OK啦。 重新启动NPM cd ~/data/docker_data/npm docker compose down docker compose up -d docker compose logs tinyauth 出现类似,代表成功。 这边我们先搞一下反向代理! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo上 6位数字的xyz续费永远都是0.99美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 配置Tinyauth的反向代理 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 确保“阻止常见漏洞”(Bolck Common Exploits)选项是关掉的。如果这选项开了,Nginx 会把 URL 里的查询参数给拦下来,而 Tinyauth 得靠这些参数才能正常工作。 tinyauth 这个是容器的名字,因为它和NPM在一个网络下,所以可以用容器名字来代替域名, 3000 是内部的端口,保持即可不要更改。 然后访问域名就可以访问了! 7.2 配置需要保护的应用的反向代理 我这边以之前介绍的简历应用( )为例子, 找到你需要添加登录页的应用的对应的NPM配置,在Advance里面添加: # Root location location / { # Pass the request to the app proxy_pass $forward_scheme://$server:$port; # Add other app specific config here # Tinyauth auth request auth_request /tinyauth; error_page 401 = @tinyauth_login; } # Tinyauth auth request location /tinyauth { # Pass request to Tinyauth proxy_pass http://tinyauth:3000/api/auth/nginx; # Pass the request headers proxy_set_header x-forwarded-proto $scheme; proxy_set_header x-forwarded-host $http_host; proxy_set_header x-forwarded-uri $request_uri; } # Tinyauth login redirect location @tinyauth_login { return 302 https://auth.gugu.ovh/login?redirect_uri=$scheme://$http_host$request_uri; # Make sure to replace the https://auth.gugu.ovh with your own app URL } 其他地方都不用管,只要把 https://auth.gugu.ovh 替换成你自己的tinyauth的域名即可。 保存之后。 再次打开简历这个网页。 提示需要登录了。 输入账号密码,继续 进来了。 8. 使用教程 其他的也类似,只需要在对应应用的NPM的Advance里面加: # Root location location / { # Pass the request to the app proxy_pass $forward_scheme://$server:$port; # Add other app specific config here # Tinyauth auth request auth_request /tinyauth; error_page 401 = @tinyauth_login; } # Tinyauth auth request location /tinyauth { # Pass request to Tinyauth proxy_pass http://tinyauth:3000/api/auth/nginx; # Pass the request headers proxy_set_header x-forwarded-proto $scheme; proxy_set_header x-forwarded-host $http_host; proxy_set_header x-forwarded-uri $request_uri; } # Tinyauth login redirect location @tinyauth_login { return 302 https://auth.gugu.ovh/login?redirect_uri=$scheme://$http_host$request_uri; # Make sure to replace the https://auth.gugu.ovh with your own app URL } 即可。 PS: /tinyauth 这个路径可以随便改,指南里用这个名字只是为了方便。 9. 常见问题及注意点 大家有问题欢迎评论区交流。 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star ⭐️ 参考资料 官方GitHub:

前言 Dify是一个开源的AI应用开发平台,简单来说,它能让普通人也能轻松创建智能聊天机器人或AI助手。 它通过拖拽式的界面,整合了各种大语言模型和工具,比如可以连接企业知识库或外部API,让AI能回答专业问题或完成复杂任务。无论是程序员还是非技术人员,都能用它快速搭建AI应用,省时省力。Dify还支持本地部署,数据安全有保障,特别适合想快速上手AI的团队或个人。 其实Dify已经火了有很长时间了,之前嫌麻烦,我一直没怎么用。最近,公司里的一批小伙伴都换了配置更高的新电脑,纷纷在本地部署起了Dify,这一期就来一个小白级别的教程,和大家简单分享一下Dify的本地部署。 准备工作 Dify本身官方对电脑硬件的建议要求是: CPU >= 2 Core RAM >= 4 GiB 如果你打算做完全本地的部署,即不用云端的大模型, 如果你是windows电脑,建议配置如下: 内存 :至少 16GB 系统内存(推荐 24GB 以上,留 8GB 给操作系统和其他软件)。 GPU (可选但推荐):至少 16GB VRAM 的 GPU(如 AMD Radeon RX 9070 XT 或 NVIDIA RTX 3090),支持 MXFP4 量化。GPU 内存带宽需高(如 GDDR6X/GDDR7,1000+ GB/s)。 CPU :支持 CPU 推理,但速度较慢。推荐高性能 CPU(如 AMD Ryzen 9 5900X 或 Intel Core i9)。 如果是Mac的话, 硬件 :Apple Silicon(M1/M2/M3/M4),至少 16GB 统一内存(推荐 32GB)。 我自己这边用的是Mac,部署起来相对简单一些,用Windows的小伙伴可能会稍微复杂一丢丢。 Windows安装Docker可以参考这个:https://docs.docker.com/desktop/windows/install/#wsl-2-backend 本质都是先在本地电脑安装好Docker,然后通过Docker来部署Dify。 安装Docker 本地装docker,可以去docker官网直接下载一个安装包。 官方地址: https://www.docker.com/products/docker-desktop/ 下载之后,就像安装普通软件一样,安装一下。 然后打开docker。 打开Mac的终端,输入 docker -v ,有版本号说明安装好了。 这边还会涉及docker换源,由于防火墙的原因,我们大陆可能无法访问docker的镜像仓库,建议大家换成中科大的源: https://docker.mirrors.ustc.edu.cn/ 改成这个: { "builder": { "gc": { "defaultKeepStorage": "20GB", "enabled": true } }, "experimental": false, "features": { "buildkit": true }, "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn" ] } 然后记得点击应用并重启。 使用如下命令查看Docker配置, docker info 就可以了。 安装Dify Docker安装好之后,接下来安装我们今天的主角——Dify。 文档地址: https://docs.dify.ai/en/introduction 这边一直关注咕咕的小伙伴可以很轻松的安装好。 Mac和Linux其实差不多,我们直接像操作VPS一样来就行。 mkdir ./data/docker_data cd data/docker_data git clone --branch "$(curl -s https://api.github.com/repos/langgenius/dify/releases/latest | jq -r .tag_name)" https://github.com/langgenius/dify.git # 从GitHub下载dify源码 cd dify/docker # 进入文件夹 cp .env.example .env # 拷贝一份配置文件 这里如果后续有需求,可以自行修改这个配置文件,一起来看一下 # ------------------------------ # Environment Variables for API service & worker # ------------------------------ # ------------------------------ # Common Variables # ------------------------------ # The backend URL of the console API, # used to concatenate the authorization callback. # If empty, it is the same domain. # Example: https://api.console.dify.ai CONSOLE_API_URL= # The front-end URL of the console web, # used to concatenate some front-end addresses and for CORS configuration use. # If empty, it is the same domain. # Example: https://console.dify.ai CONSOLE_WEB_URL= # Service API Url, # used to display Service API Base Url to the front-end. # If empty, it is the same domain. # Example: https://api.dify.ai SERVICE_API_URL= # WebApp API backend Url, # used to declare the back-end URL for the front-end API. # If empty, it is the same domain. # Example: https://api.app.dify.ai APP_API_URL= # WebApp Url, # used to display WebAPP API Base Url to the front-end. # If empty, it is the same domain. # Example: https://app.dify.ai APP_WEB_URL= # File preview or download Url prefix. # used to display File preview or download Url to the front-end or as Multi-model inputs; # Url is signed and has expiration time. # Setting FILES_URL is required for file processing plugins. # - For https://example.com, use FILES_URL=https://example.com # - For http://example.com, use FILES_URL=http://example.com # Recommendation: use a dedicated domain (e.g., https://upload.example.com). # Alternatively, use http://<your-ip>:5001 or http://api:5001, # ensuring port 5001 is externally accessible (see docker-compose.yaml). FILES_URL= # INTERNAL_FILES_URL is used for plugin daemon communication within Docker network. # Set this to the internal Docker service URL for proper plugin file access. # Example: INTERNAL_FILES_URL=http://api:5001 INTERNAL_FILES_URL= # Ensure UTF-8 encoding LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 PYTHONIOENCODING=utf-8 # ------------------------------ # Server Configuration # ------------------------------ # The log level for the application. # Supported values are `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` LOG_LEVEL=INFO # Log file path LOG_FILE=/app/logs/server.log # Log file max size, the unit is MB LOG_FILE_MAX_SIZE=20 # Log file max backup count LOG_FILE_BACKUP_COUNT=5 # Log dateformat LOG_DATEFORMAT=%Y-%m-%d %H:%M:%S # Log Timezone LOG_TZ=UTC # Debug mode, default is false. # It is recommended to turn on this configuration for local development # to prevent some problems caused by monkey patch. DEBUG=false # Flask debug mode, it can output trace information at the interface when turned on, # which is convenient for debugging. FLASK_DEBUG=false # Enable request logging, which will log the request and response information. # And the log level is DEBUG ENABLE_REQUEST_LOGGING=False # A secret key that is used for securely signing the session cookie # and encrypting sensitive information on the database. # You can generate a strong key using `openssl rand -base64 42`. SECRET_KEY=sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U # Password for admin user initialization. # If left unset, admin user will not be prompted for a password # when creating the initial admin account. # The length of the password cannot exceed 30 characters. INIT_PASSWORD= # Deployment environment. # Supported values are `PRODUCTION`, `TESTING`. Default is `PRODUCTION`. # Testing environment. There will be a distinct color label on the front-end page, # indicating that this environment is a testing environment. DEPLOY_ENV=PRODUCTION # Whether to enable the version check policy. # If set to empty, https://updates.dify.ai will be called for version check. CHECK_UPDATE_URL=https://updates.dify.ai # Used to change the OpenAI base address, default is https://api.openai.com/v1. # When OpenAI cannot be accessed in China, replace it with a domestic mirror address, # or when a local model provides OpenAI compatible API, it can be replaced. OPENAI_API_BASE=https://api.openai.com/v1 # When enabled, migrations will be executed prior to application startup # and the application will start after the migrations have completed. MIGRATION_ENABLED=true # File Access Time specifies a time interval in seconds for the file to be accessed. # The default value is 300 seconds. FILES_ACCESS_TIMEOUT=300 # Access token expiration time in minutes ACCESS_TOKEN_EXPIRE_MINUTES=60 # Refresh token expiration time in days REFRESH_TOKEN_EXPIRE_DAYS=30 # The maximum number of active requests for the application, where 0 means unlimited, should be a non-negative integer. APP_MAX_ACTIVE_REQUESTS=0 APP_MAX_EXECUTION_TIME=1200 # ------------------------------ # Container Startup Related Configuration # Only effective when starting with docker image or docker-compose. # ------------------------------ # API service binding address, default: 0.0.0.0, i.e., all addresses can be accessed. DIFY_BIND_ADDRESS=0.0.0.0 # API service binding port number, default 5001. DIFY_PORT=5001 # The number of API server workers, i.e., the number of workers. # Formula: number of cpu cores x 2 + 1 for sync, 1 for Gevent # Reference: https://docs.gunicorn.org/en/stable/design.html#how-many-workers SERVER_WORKER_AMOUNT=1 # Defaults to gevent. If using windows, it can be switched to sync or solo. SERVER_WORKER_CLASS=gevent # Default number of worker connections, the default is 10. SERVER_WORKER_CONNECTIONS=10 # Similar to SERVER_WORKER_CLASS. # If using windows, it can be switched to sync or solo. CELERY_WORKER_CLASS= # Request handling timeout. The default is 200, # it is recommended to set it to 360 to support a longer sse connection time. GUNICORN_TIMEOUT=360 # The number of Celery workers. The default is 1, and can be set as needed. CELERY_WORKER_AMOUNT= # Flag indicating whether to enable autoscaling of Celery workers. # # Autoscaling is useful when tasks are CPU intensive and can be dynamically # allocated and deallocated based on the workload. # # When autoscaling is enabled, the maximum and minimum number of workers can # be specified. The autoscaling algorithm will dynamically adjust the number # of workers within the specified range. # # Default is false (i.e., autoscaling is disabled). # # Example: # CELERY_AUTO_SCALE=true CELERY_AUTO_SCALE=false # The maximum number of Celery workers that can be autoscaled. # This is optional and only used when autoscaling is enabled. # Default is not set. CELERY_MAX_WORKERS= # The minimum number of Celery workers that can be autoscaled. # This is optional and only used when autoscaling is enabled. # Default is not set. CELERY_MIN_WORKERS= # API Tool configuration API_TOOL_DEFAULT_CONNECT_TIMEOUT=10 API_TOOL_DEFAULT_READ_TIMEOUT=60 # ------------------------------- # Datasource Configuration # -------------------------------- ENABLE_WEBSITE_JINAREADER=true ENABLE_WEBSITE_FIRECRAWL=true ENABLE_WEBSITE_WATERCRAWL=true # ------------------------------ # Database Configuration # The database uses PostgreSQL. Please use the public schema. # It is consistent with the configuration in the 'db' service below. # ------------------------------ DB_USERNAME=postgres DB_PASSWORD=difyai123456 DB_HOST=db DB_PORT=5432 DB_DATABASE=dify # The size of the database connection pool. # The default is 30 connections, which can be appropriately increased. SQLALCHEMY_POOL_SIZE=30 # The default is 10 connections, which allows temporary overflow beyond the pool size. SQLALCHEMY_MAX_OVERFLOW=10 # Database connection pool recycling time, the default is 3600 seconds. SQLALCHEMY_POOL_RECYCLE=3600 # Whether to print SQL, default is false. SQLALCHEMY_ECHO=false # If True, will test connections for liveness upon each checkout SQLALCHEMY_POOL_PRE_PING=false # Whether to enable the Last in first out option or use default FIFO queue if is false SQLALCHEMY_POOL_USE_LIFO=false # Maximum number of connections to the database # Default is 100 # # Reference: https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-MAX-CONNECTIONS POSTGRES_MAX_CONNECTIONS=100 # Sets the amount of shared memory used for postgres's shared buffers. # Default is 128MB # Recommended value: 25% of available memory # Reference: https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-SHARED-BUFFERS POSTGRES_SHARED_BUFFERS=128MB # Sets the amount of memory used by each database worker for working space. # Default is 4MB # # Reference: https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-WORK-MEM POSTGRES_WORK_MEM=4MB # Sets the amount of memory reserved for maintenance activities. # Default is 64MB # # Reference: https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAINTENANCE-WORK-MEM POSTGRES_MAINTENANCE_WORK_MEM=64MB # Sets the planner's assumption about the effective cache size. # Default is 4096MB # # Reference: https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-EFFECTIVE-CACHE-SIZE POSTGRES_EFFECTIVE_CACHE_SIZE=4096MB # ------------------------------ # Redis Configuration # This Redis configuration is used for caching and for pub/sub during conversation. # ------------------------------ REDIS_HOST=redis REDIS_PORT=6379 REDIS_USERNAME= REDIS_PASSWORD=difyai123456 REDIS_USE_SSL=false # SSL configuration for Redis (when REDIS_USE_SSL=true) REDIS_SSL_CERT_REQS=CERT_NONE # Options: CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED REDIS_SSL_CA_CERTS= # Path to CA certificate file for SSL verification REDIS_SSL_CERTFILE= # Path to client certificate file for SSL authentication REDIS_SSL_KEYFILE= # Path to client private key file for SSL authentication REDIS_DB=0 # Whether to use Redis Sentinel mode. # If set to true, the application will automatically discover and connect to the master node through Sentinel. REDIS_USE_SENTINEL=false # List of Redis Sentinel nodes. If Sentinel mode is enabled, provide at least one Sentinel IP and port. # Format: `<sentinel1_ip>:<sentinel1_port>,<sentinel2_ip>:<sentinel2_port>,<sentinel3_ip>:<sentinel3_port>` REDIS_SENTINELS= REDIS_SENTINEL_SERVICE_NAME= REDIS_SENTINEL_USERNAME= REDIS_SENTINEL_PASSWORD= REDIS_SENTINEL_SOCKET_TIMEOUT=0.1 # List of Redis Cluster nodes. If Cluster mode is enabled, provide at least one Cluster IP and port. # Format: `<Cluster1_ip>:<Cluster1_port>,<Cluster2_ip>:<Cluster2_port>,<Cluster3_ip>:<Cluster3_port>` REDIS_USE_CLUSTERS=false REDIS_CLUSTERS= REDIS_CLUSTERS_PASSWORD= # ------------------------------ # Celery Configuration # ------------------------------ # Use standalone redis as the broker, and redis db 1 for celery broker. (redis_username is usually set by defualt as empty) # Format as follows: `redis://<redis_username>:<redis_password>@<redis_host>:<redis_port>/<redis_database>`. # Example: redis://:difyai123456@redis:6379/1 # If use Redis Sentinel, format as follows: `sentinel://<redis_username>:<redis_password>@<sentinel_host1>:<sentinel_port>/<redis_database>` # For high availability, you can configure multiple Sentinel nodes (if provided) separated by semicolons like below example: # Example: sentinel://:difyai123456@localhost:26379/1;sentinel://:difyai12345@localhost:26379/1;sentinel://:difyai12345@localhost:26379/1 CELERY_BROKER_URL=redis://:difyai123456@redis:6379/1 CELERY_BACKEND=redis BROKER_USE_SSL=false # If you are using Redis Sentinel for high availability, configure the following settings. CELERY_USE_SENTINEL=false CELERY_SENTINEL_MASTER_NAME= CELERY_SENTINEL_PASSWORD= CELERY_SENTINEL_SOCKET_TIMEOUT=0.1 # ------------------------------ # CORS Configuration # Used to set the front-end cross-domain access policy. # ------------------------------ # Specifies the allowed origins for cross-origin requests to the Web API, # e.g. https://dify.app or * for all origins. WEB_API_CORS_ALLOW_ORIGINS=* # Specifies the allowed origins for cross-origin requests to the console API, # e.g. https://cloud.dify.ai or * for all origins. CONSOLE_CORS_ALLOW_ORIGINS=* # ------------------------------ # File Storage Configuration # ------------------------------ # The type of storage to use for storing user files. STORAGE_TYPE=opendal # Apache OpenDAL Configuration # The configuration for OpenDAL consists of the following format: OPENDAL_<SCHEME_NAME>_<CONFIG_NAME>. # You can find all the service configurations (CONFIG_NAME) in the repository at: https://github.com/apache/opendal/tree/main/core/src/services. # Dify will scan configurations starting with OPENDAL_<SCHEME_NAME> and automatically apply them. # The scheme name for the OpenDAL storage. OPENDAL_SCHEME=fs # Configurations for OpenDAL Local File System. OPENDAL_FS_ROOT=storage # ClickZetta Volume Configuration (for storage backend) # To use ClickZetta Volume as storage backend, set STORAGE_TYPE=clickzetta-volume # Note: ClickZetta Volume will reuse the existing CLICKZETTA_* connection parameters # Volume type selection (three types available): # - user: Personal/small team use, simple config, user-level permissions # - table: Enterprise multi-tenant, smart routing, table-level + user-level permissions # - external: Data lake integration, external storage connection, volume-level + storage-level permissions CLICKZETTA_VOLUME_TYPE=user # External Volume name (required only when TYPE=external) CLICKZETTA_VOLUME_NAME= # Table Volume table prefix (used only when TYPE=table) CLICKZETTA_VOLUME_TABLE_PREFIX=dataset_ # Dify file directory prefix (isolates from other apps, recommended to keep default) CLICKZETTA_VOLUME_DIFY_PREFIX=dify_km # S3 Configuration # S3_ENDPOINT= S3_REGION=us-east-1 S3_BUCKET_NAME=difyai S3_ACCESS_KEY= S3_SECRET_KEY= # Whether to use AWS managed IAM roles for authenticating with the S3 service. # If set to false, the access key and secret key must be provided. S3_USE_AWS_MANAGED_IAM=false # Azure Blob Configuration # AZURE_BLOB_ACCOUNT_NAME=difyai AZURE_BLOB_ACCOUNT_KEY=difyai AZURE_BLOB_CONTAINER_NAME=difyai-container AZURE_BLOB_ACCOUNT_URL=https://<your_account_name>.blob.core.windows.net # Google Storage Configuration # GOOGLE_STORAGE_BUCKET_NAME=your-bucket-name GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64= # The Alibaba Cloud OSS configurations, # ALIYUN_OSS_BUCKET_NAME=your-bucket-name ALIYUN_OSS_ACCESS_KEY=your-access-key ALIYUN_OSS_SECRET_KEY=your-secret-key ALIYUN_OSS_ENDPOINT=https://oss-ap-southeast-1-internal.aliyuncs.com ALIYUN_OSS_REGION=ap-southeast-1 ALIYUN_OSS_AUTH_VERSION=v4 # Don't start with '/'. OSS doesn't support leading slash in object names. ALIYUN_OSS_PATH=your-path # Tencent COS Configuration # TENCENT_COS_BUCKET_NAME=your-bucket-name TENCENT_COS_SECRET_KEY=your-secret-key TENCENT_COS_SECRET_ID=your-secret-id TENCENT_COS_REGION=your-region TENCENT_COS_SCHEME=your-scheme # Oracle Storage Configuration # OCI_ENDPOINT=https://your-object-storage-namespace.compat.objectstorage.us-ashburn-1.oraclecloud.com OCI_BUCKET_NAME=your-bucket-name OCI_ACCESS_KEY=your-access-key OCI_SECRET_KEY=your-secret-key OCI_REGION=us-ashburn-1 # Huawei OBS Configuration # HUAWEI_OBS_BUCKET_NAME=your-bucket-name HUAWEI_OBS_SECRET_KEY=your-secret-key HUAWEI_OBS_ACCESS_KEY=your-access-key HUAWEI_OBS_SERVER=your-server-url # Volcengine TOS Configuration # VOLCENGINE_TOS_BUCKET_NAME=your-bucket-name VOLCENGINE_TOS_SECRET_KEY=your-secret-key VOLCENGINE_TOS_ACCESS_KEY=your-access-key VOLCENGINE_TOS_ENDPOINT=your-server-url VOLCENGINE_TOS_REGION=your-region # Baidu OBS Storage Configuration # BAIDU_OBS_BUCKET_NAME=your-bucket-name BAIDU_OBS_SECRET_KEY=your-secret-key BAIDU_OBS_ACCESS_KEY=your-access-key BAIDU_OBS_ENDPOINT=your-server-url # Supabase Storage Configuration # SUPABASE_BUCKET_NAME=your-bucket-name SUPABASE_API_KEY=your-access-key SUPABASE_URL=your-server-url # ------------------------------ # Vector Database Configuration # ------------------------------ # The type of vector store to use. # Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `oceanbase`, `opengauss`, `tablestore`,`vastbase`,`tidb`,`tidb_on_qdrant`,`baidu`,`lindorm`,`huawei_cloud`,`upstash`, `matrixone`, `clickzetta`. VECTOR_STORE=weaviate # Prefix used to create collection name in vector database VECTOR_INDEX_NAME_PREFIX=Vector_index # The Weaviate endpoint URL. Only available when VECTOR_STORE is `weaviate`. WEAVIATE_ENDPOINT=http://weaviate:8080 WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih # The Qdrant endpoint URL. Only available when VECTOR_STORE is `qdrant`. QDRANT_URL=http://qdrant:6333 QDRANT_API_KEY=difyai123456 QDRANT_CLIENT_TIMEOUT=20 QDRANT_GRPC_ENABLED=false QDRANT_GRPC_PORT=6334 QDRANT_REPLICATION_FACTOR=1 # Milvus configuration. Only available when VECTOR_STORE is `milvus`. # The milvus uri. MILVUS_URI=http://host.docker.internal:19530 MILVUS_DATABASE= MILVUS_TOKEN= MILVUS_USER= MILVUS_PASSWORD= MILVUS_ENABLE_HYBRID_SEARCH=False MILVUS_ANALYZER_PARAMS= # MyScale configuration, only available when VECTOR_STORE is `myscale` # For multi-language support, please set MYSCALE_FTS_PARAMS with referring to: # https://myscale.com/docs/en/text-search/#understanding-fts-index-parameters MYSCALE_HOST=myscale MYSCALE_PORT=8123 MYSCALE_USER=default MYSCALE_PASSWORD= MYSCALE_DATABASE=dify MYSCALE_FTS_PARAMS= # Couchbase configurations, only available when VECTOR_STORE is `couchbase` # The connection string must include hostname defined in the docker-compose file (couchbase-server in this case) COUCHBASE_CONNECTION_STRING=couchbase://couchbase-server COUCHBASE_USER=Administrator COUCHBASE_PASSWORD=password COUCHBASE_BUCKET_NAME=Embeddings COUCHBASE_SCOPE_NAME=_default # pgvector configurations, only available when VECTOR_STORE is `pgvector` PGVECTOR_HOST=pgvector PGVECTOR_PORT=5432 PGVECTOR_USER=postgres PGVECTOR_PASSWORD=difyai123456 PGVECTOR_DATABASE=dify PGVECTOR_MIN_CONNECTION=1 PGVECTOR_MAX_CONNECTION=5 PGVECTOR_PG_BIGM=false PGVECTOR_PG_BIGM_VERSION=1.2-20240606 # vastbase configurations, only available when VECTOR_STORE is `vastbase` VASTBASE_HOST=vastbase VASTBASE_PORT=5432 VASTBASE_USER=dify VASTBASE_PASSWORD=Difyai123456 VASTBASE_DATABASE=dify VASTBASE_MIN_CONNECTION=1 VASTBASE_MAX_CONNECTION=5 # pgvecto-rs configurations, only available when VECTOR_STORE is `pgvecto-rs` PGVECTO_RS_HOST=pgvecto-rs PGVECTO_RS_PORT=5432 PGVECTO_RS_USER=postgres PGVECTO_RS_PASSWORD=difyai123456 PGVECTO_RS_DATABASE=dify # analyticdb configurations, only available when VECTOR_STORE is `analyticdb` ANALYTICDB_KEY_ID=your-ak ANALYTICDB_KEY_SECRET=your-sk ANALYTICDB_REGION_ID=cn-hangzhou ANALYTICDB_INSTANCE_ID=gp-ab123456 ANALYTICDB_ACCOUNT=testaccount ANALYTICDB_PASSWORD=testpassword ANALYTICDB_NAMESPACE=dify ANALYTICDB_NAMESPACE_PASSWORD=difypassword ANALYTICDB_HOST=gp-test.aliyuncs.com ANALYTICDB_PORT=5432 ANALYTICDB_MIN_CONNECTION=1 ANALYTICDB_MAX_CONNECTION=5 # TiDB vector configurations, only available when VECTOR_STORE is `tidb_vector` TIDB_VECTOR_HOST=tidb TIDB_VECTOR_PORT=4000 TIDB_VECTOR_USER= TIDB_VECTOR_PASSWORD= TIDB_VECTOR_DATABASE=dify # Matrixone vector configurations. MATRIXONE_HOST=matrixone MATRIXONE_PORT=6001 MATRIXONE_USER=dump MATRIXONE_PASSWORD=111 MATRIXONE_DATABASE=dify # Tidb on qdrant configuration, only available when VECTOR_STORE is `tidb_on_qdrant` TIDB_ON_QDRANT_URL=http://127.0.0.1 TIDB_ON_QDRANT_API_KEY=dify TIDB_ON_QDRANT_CLIENT_TIMEOUT=20 TIDB_ON_QDRANT_GRPC_ENABLED=false TIDB_ON_QDRANT_GRPC_PORT=6334 TIDB_PUBLIC_KEY=dify TIDB_PRIVATE_KEY=dify TIDB_API_URL=http://127.0.0.1 TIDB_IAM_API_URL=http://127.0.0.1 TIDB_REGION=regions/aws-us-east-1 TIDB_PROJECT_ID=dify TIDB_SPEND_LIMIT=100 # Chroma configuration, only available when VECTOR_STORE is `chroma` CHROMA_HOST=127.0.0.1 CHROMA_PORT=8000 CHROMA_TENANT=default_tenant CHROMA_DATABASE=default_database CHROMA_AUTH_PROVIDER=chromadb.auth.token_authn.TokenAuthClientProvider CHROMA_AUTH_CREDENTIALS= # Oracle configuration, only available when VECTOR_STORE is `oracle` ORACLE_USER=dify ORACLE_PASSWORD=dify ORACLE_DSN=oracle:1521/FREEPDB1 ORACLE_CONFIG_DIR=/app/api/storage/wallet ORACLE_WALLET_LOCATION=/app/api/storage/wallet ORACLE_WALLET_PASSWORD=dify ORACLE_IS_AUTONOMOUS=false # relyt configurations, only available when VECTOR_STORE is `relyt` RELYT_HOST=db RELYT_PORT=5432 RELYT_USER=postgres RELYT_PASSWORD=difyai123456 RELYT_DATABASE=postgres # open search configuration, only available when VECTOR_STORE is `opensearch` OPENSEARCH_HOST=opensearch OPENSEARCH_PORT=9200 OPENSEARCH_SECURE=true OPENSEARCH_VERIFY_CERTS=true OPENSEARCH_AUTH_METHOD=basic OPENSEARCH_USER=admin OPENSEARCH_PASSWORD=admin # If using AWS managed IAM, e.g. Managed Cluster or OpenSearch Serverless OPENSEARCH_AWS_REGION=ap-southeast-1 OPENSEARCH_AWS_SERVICE=aoss # tencent vector configurations, only available when VECTOR_STORE is `tencent` TENCENT_VECTOR_DB_URL=http://127.0.0.1 TENCENT_VECTOR_DB_API_KEY=dify TENCENT_VECTOR_DB_TIMEOUT=30 TENCENT_VECTOR_DB_USERNAME=dify TENCENT_VECTOR_DB_DATABASE=dify TENCENT_VECTOR_DB_SHARD=1 TENCENT_VECTOR_DB_REPLICAS=2 TENCENT_VECTOR_DB_ENABLE_HYBRID_SEARCH=false # ElasticSearch configuration, only available when VECTOR_STORE is `elasticsearch` ELASTICSEARCH_HOST=0.0.0.0 ELASTICSEARCH_PORT=9200 ELASTICSEARCH_USERNAME=elastic ELASTICSEARCH_PASSWORD=elastic KIBANA_PORT=5601 # Using ElasticSearch Cloud Serverless, or not. ELASTICSEARCH_USE_CLOUD=false ELASTICSEARCH_CLOUD_URL=YOUR-ELASTICSEARCH_CLOUD_URL ELASTICSEARCH_API_KEY=YOUR-ELASTICSEARCH_API_KEY ELASTICSEARCH_VERIFY_CERTS=False ELASTICSEARCH_CA_CERTS= ELASTICSEARCH_REQUEST_TIMEOUT=100000 ELASTICSEARCH_RETRY_ON_TIMEOUT=True ELASTICSEARCH_MAX_RETRIES=10 # baidu vector configurations, only available when VECTOR_STORE is `baidu` BAIDU_VECTOR_DB_ENDPOINT=http://127.0.0.1:5287 BAIDU_VECTOR_DB_CONNECTION_TIMEOUT_MS=30000 BAIDU_VECTOR_DB_ACCOUNT=root BAIDU_VECTOR_DB_API_KEY=dify BAIDU_VECTOR_DB_DATABASE=dify BAIDU_VECTOR_DB_SHARD=1 BAIDU_VECTOR_DB_REPLICAS=3 # VikingDB configurations, only available when VECTOR_STORE is `vikingdb` VIKINGDB_ACCESS_KEY=your-ak VIKINGDB_SECRET_KEY=your-sk VIKINGDB_REGION=cn-shanghai VIKINGDB_HOST=api-vikingdb.xxx.volces.com VIKINGDB_SCHEMA=http VIKINGDB_CONNECTION_TIMEOUT=30 VIKINGDB_SOCKET_TIMEOUT=30 # Lindorm configuration, only available when VECTOR_STORE is `lindorm` LINDORM_URL=http://lindorm:30070 LINDORM_USERNAME=lindorm LINDORM_PASSWORD=lindorm LINDORM_QUERY_TIMEOUT=1 # OceanBase Vector configuration, only available when VECTOR_STORE is `oceanbase` OCEANBASE_VECTOR_HOST=oceanbase OCEANBASE_VECTOR_PORT=2881 OCEANBASE_VECTOR_USER=root@test OCEANBASE_VECTOR_PASSWORD=difyai123456 OCEANBASE_VECTOR_DATABASE=test OCEANBASE_CLUSTER_NAME=difyai OCEANBASE_MEMORY_LIMIT=6G OCEANBASE_ENABLE_HYBRID_SEARCH=false # opengauss configurations, only available when VECTOR_STORE is `opengauss` OPENGAUSS_HOST=opengauss OPENGAUSS_PORT=6600 OPENGAUSS_USER=postgres OPENGAUSS_PASSWORD=Dify@123 OPENGAUSS_DATABASE=dify OPENGAUSS_MIN_CONNECTION=1 OPENGAUSS_MAX_CONNECTION=5 OPENGAUSS_ENABLE_PQ=false # huawei cloud search service vector configurations, only available when VECTOR_STORE is `huawei_cloud` HUAWEI_CLOUD_HOSTS=https://127.0.0.1:9200 HUAWEI_CLOUD_USER=admin HUAWEI_CLOUD_PASSWORD=admin # Upstash Vector configuration, only available when VECTOR_STORE is `upstash` UPSTASH_VECTOR_URL=https://xxx-vector.upstash.io UPSTASH_VECTOR_TOKEN=dify # TableStore Vector configuration # (only used when VECTOR_STORE is tablestore) TABLESTORE_ENDPOINT=https://instance-name.cn-hangzhou.ots.aliyuncs.com TABLESTORE_INSTANCE_NAME=instance-name TABLESTORE_ACCESS_KEY_ID=xxx TABLESTORE_ACCESS_KEY_SECRET=xxx TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE=false # Clickzetta configuration, only available when VECTOR_STORE is `clickzetta` CLICKZETTA_USERNAME= CLICKZETTA_PASSWORD= CLICKZETTA_INSTANCE= CLICKZETTA_SERVICE=api.clickzetta.com CLICKZETTA_WORKSPACE=quick_start CLICKZETTA_VCLUSTER=default_ap CLICKZETTA_SCHEMA=dify CLICKZETTA_BATCH_SIZE=100 CLICKZETTA_ENABLE_INVERTED_INDEX=true CLICKZETTA_ANALYZER_TYPE=chinese CLICKZETTA_ANALYZER_MODE=smart CLICKZETTA_VECTOR_DISTANCE_FUNCTION=cosine_distance # ------------------------------ # Knowledge Configuration # ------------------------------ # Upload file size limit, default 15M. UPLOAD_FILE_SIZE_LIMIT=15 # The maximum number of files that can be uploaded at a time, default 5. UPLOAD_FILE_BATCH_LIMIT=5 # ETL type, support: `dify`, `Unstructured` # `dify` Dify's proprietary file extraction scheme # `Unstructured` Unstructured.io file extraction scheme ETL_TYPE=dify # Unstructured API path and API key, needs to be configured when ETL_TYPE is Unstructured # Or using Unstructured for document extractor node for pptx. # For example: http://unstructured:8000/general/v0/general UNSTRUCTURED_API_URL= UNSTRUCTURED_API_KEY= SCARF_NO_ANALYTICS=true # ------------------------------ # Model Configuration # ------------------------------ # The maximum number of tokens allowed for prompt generation. # This setting controls the upper limit of tokens that can be used by the LLM # when generating a prompt in the prompt generation tool. # Default: 512 tokens. PROMPT_GENERATION_MAX_TOKENS=512 # The maximum number of tokens allowed for code generation. # This setting controls the upper limit of tokens that can be used by the LLM # when generating code in the code generation tool. # Default: 1024 tokens. CODE_GENERATION_MAX_TOKENS=1024 # Enable or disable plugin based token counting. If disabled, token counting will return 0. # This can improve performance by skipping token counting operations. # Default: false (disabled). PLUGIN_BASED_TOKEN_COUNTING_ENABLED=false # ------------------------------ # Multi-modal Configuration # ------------------------------ # The format of the image/video/audio/document sent when the multi-modal model is input, # the default is base64, optional url. # The delay of the call in url mode will be lower than that in base64 mode. # It is generally recommended to use the more compatible base64 mode. # If configured as url, you need to configure FILES_URL as an externally accessible address so that the multi-modal model can access the image/video/audio/document. MULTIMODAL_SEND_FORMAT=base64 # Upload image file size limit, default 10M. UPLOAD_IMAGE_FILE_SIZE_LIMIT=10 # Upload video file size limit, default 100M. UPLOAD_VIDEO_FILE_SIZE_LIMIT=100 # Upload audio file size limit, default 50M. UPLOAD_AUDIO_FILE_SIZE_LIMIT=50 # ------------------------------ # Sentry Configuration # Used for application monitoring and error log tracking. # ------------------------------ SENTRY_DSN= # API Service Sentry DSN address, default is empty, when empty, # all monitoring information is not reported to Sentry. # If not set, Sentry error reporting will be disabled. API_SENTRY_DSN= # API Service The reporting ratio of Sentry events, if it is 0.01, it is 1%. API_SENTRY_TRACES_SAMPLE_RATE=1.0 # API Service The reporting ratio of Sentry profiles, if it is 0.01, it is 1%. API_SENTRY_PROFILES_SAMPLE_RATE=1.0 # Web Service Sentry DSN address, default is empty, when empty, # all monitoring information is not reported to Sentry. # If not set, Sentry error reporting will be disabled. WEB_SENTRY_DSN= # ------------------------------ # Notion Integration Configuration # Variables can be obtained by applying for Notion integration: https://www.notion.so/my-integrations # ------------------------------ # Configure as "public" or "internal". # Since Notion's OAuth redirect URL only supports HTTPS, # if deploying locally, please use Notion's internal integration. NOTION_INTEGRATION_TYPE=public # Notion OAuth client secret (used for public integration type) NOTION_CLIENT_SECRET= # Notion OAuth client id (used for public integration type) NOTION_CLIENT_ID= # Notion internal integration secret. # If the value of NOTION_INTEGRATION_TYPE is "internal", # you need to configure this variable. NOTION_INTERNAL_SECRET= # ------------------------------ # Mail related configuration # ------------------------------ # Mail type, support: resend, smtp, sendgrid MAIL_TYPE=resend # Default send from email address, if not specified # If using SendGrid, use the 'from' field for authentication if necessary. MAIL_DEFAULT_SEND_FROM= # API-Key for the Resend email provider, used when MAIL_TYPE is `resend`. RESEND_API_URL=https://api.resend.com RESEND_API_KEY=your-resend-api-key # SMTP server configuration, used when MAIL_TYPE is `smtp` SMTP_SERVER= SMTP_PORT=465 SMTP_USERNAME= SMTP_PASSWORD= SMTP_USE_TLS=true SMTP_OPPORTUNISTIC_TLS=false # Sendgid configuration SENDGRID_API_KEY= # ------------------------------ # Others Configuration # ------------------------------ # Maximum length of segmentation tokens for indexing INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000 # Member invitation link valid time (hours), # Default: 72. INVITE_EXPIRY_HOURS=72 # Reset password token valid time (minutes), RESET_PASSWORD_TOKEN_EXPIRY_MINUTES=5 CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES=5 OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES=5 # The sandbox service endpoint. CODE_EXECUTION_ENDPOINT=http://sandbox:8194 CODE_EXECUTION_API_KEY=dify-sandbox CODE_MAX_NUMBER=9223372036854775807 CODE_MIN_NUMBER=-9223372036854775808 CODE_MAX_DEPTH=5 CODE_MAX_PRECISION=20 CODE_MAX_STRING_LENGTH=80000 CODE_MAX_STRING_ARRAY_LENGTH=30 CODE_MAX_OBJECT_ARRAY_LENGTH=30 CODE_MAX_NUMBER_ARRAY_LENGTH=1000 CODE_EXECUTION_CONNECT_TIMEOUT=10 CODE_EXECUTION_READ_TIMEOUT=60 CODE_EXECUTION_WRITE_TIMEOUT=10 TEMPLATE_TRANSFORM_MAX_LENGTH=80000 # Workflow runtime configuration WORKFLOW_MAX_EXECUTION_STEPS=500 WORKFLOW_MAX_EXECUTION_TIME=1200 WORKFLOW_CALL_MAX_DEPTH=5 MAX_VARIABLE_SIZE=204800 WORKFLOW_PARALLEL_DEPTH_LIMIT=3 WORKFLOW_FILE_UPLOAD_LIMIT=10 # Workflow storage configuration # Options: rdbms, hybrid # rdbms: Use only the relational database (default) # hybrid: Save new data to object storage, read from both object storage and RDBMS WORKFLOW_NODE_EXECUTION_STORAGE=rdbms # Repository configuration # Core workflow execution repository implementation # Options: # - core.repositories.sqlalchemy_workflow_execution_repository.SQLAlchemyWorkflowExecutionRepository (default) # - core.repositories.celery_workflow_execution_repository.CeleryWorkflowExecutionRepository CORE_WORKFLOW_EXECUTION_REPOSITORY=core.repositories.sqlalchemy_workflow_execution_repository.SQLAlchemyWorkflowExecutionRepository # Core workflow node execution repository implementation # Options: # - core.repositories.sqlalchemy_workflow_node_execution_repository.SQLAlchemyWorkflowNodeExecutionRepository (default) # - core.repositories.celery_workflow_node_execution_repository.CeleryWorkflowNodeExecutionRepository CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY=core.repositories.sqlalchemy_workflow_node_execution_repository.SQLAlchemyWorkflowNodeExecutionRepository # API workflow run repository implementation API_WORKFLOW_RUN_REPOSITORY=repositories.sqlalchemy_api_workflow_run_repository.DifyAPISQLAlchemyWorkflowRunRepository # API workflow node execution repository implementation API_WORKFLOW_NODE_EXECUTION_REPOSITORY=repositories.sqlalchemy_api_workflow_node_execution_repository.DifyAPISQLAlchemyWorkflowNodeExecutionRepository # Workflow log cleanup configuration # Enable automatic cleanup of workflow run logs to manage database size WORKFLOW_LOG_CLEANUP_ENABLED=false # Number of days to retain workflow run logs (default: 30 days) WORKFLOW_LOG_RETENTION_DAYS=30 # Batch size for workflow log cleanup operations (default: 100) WORKFLOW_LOG_CLEANUP_BATCH_SIZE=100 # HTTP request node in workflow configuration HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760 HTTP_REQUEST_NODE_MAX_TEXT_SIZE=1048576 HTTP_REQUEST_NODE_SSL_VERIFY=True # Respect X-* headers to redirect clients RESPECT_XFORWARD_HEADERS_ENABLED=false # SSRF Proxy server HTTP URL SSRF_PROXY_HTTP_URL=http://ssrf_proxy:3128 # SSRF Proxy server HTTPS URL SSRF_PROXY_HTTPS_URL=http://ssrf_proxy:3128 # Maximum loop count in the workflow LOOP_NODE_MAX_COUNT=100 # The maximum number of tools that can be used in the agent. MAX_TOOLS_NUM=10 # Maximum number of Parallelism branches in the workflow MAX_PARALLEL_LIMIT=10 # The maximum number of iterations for agent setting MAX_ITERATIONS_NUM=99 # ------------------------------ # Environment Variables for web Service # ------------------------------ # The timeout for the text generation in millisecond TEXT_GENERATION_TIMEOUT_MS=60000 # Allow rendering unsafe URLs which have "data:" scheme. ALLOW_UNSAFE_DATA_SCHEME=false # Maximum number of tree depth in the workflow MAX_TREE_DEPTH=50 # ------------------------------ # Environment Variables for db Service # ------------------------------ # The name of the default postgres user. POSTGRES_USER=${DB_USERNAME} # The password for the default postgres user. POSTGRES_PASSWORD=${DB_PASSWORD} # The name of the default postgres database. POSTGRES_DB=${DB_DATABASE} # postgres data directory PGDATA=/var/lib/postgresql/data/pgdata # ------------------------------ # Environment Variables for sandbox Service # ------------------------------ # The API key for the sandbox service SANDBOX_API_KEY=dify-sandbox # The mode in which the Gin framework runs SANDBOX_GIN_MODE=release # The timeout for the worker in seconds SANDBOX_WORKER_TIMEOUT=15 # Enable network for the sandbox service SANDBOX_ENABLE_NETWORK=true # HTTP proxy URL for SSRF protection SANDBOX_HTTP_PROXY=http://ssrf_proxy:3128 # HTTPS proxy URL for SSRF protection SANDBOX_HTTPS_PROXY=http://ssrf_proxy:3128 # The port on which the sandbox service runs SANDBOX_PORT=8194 # ------------------------------ # Environment Variables for weaviate Service # (only used when VECTOR_STORE is weaviate) # ------------------------------ WEAVIATE_PERSISTENCE_DATA_PATH=/var/lib/weaviate WEAVIATE_QUERY_DEFAULTS_LIMIT=25 WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true WEAVIATE_DEFAULT_VECTORIZER_MODULE=none WEAVIATE_CLUSTER_HOSTNAME=node1 WEAVIATE_AUTHENTICATION_APIKEY_ENABLED=true WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih WEAVIATE_AUTHENTICATION_APIKEY_USERS=hello@dify.ai WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED=true WEAVIATE_AUTHORIZATION_ADMINLIST_USERS=hello@dify.ai # ------------------------------ # Environment Variables for Chroma # (only used when VECTOR_STORE is chroma) # ------------------------------ # Authentication credentials for Chroma server CHROMA_SERVER_AUTHN_CREDENTIALS=difyai123456 # Authentication provider for Chroma server CHROMA_SERVER_AUTHN_PROVIDER=chromadb.auth.token_authn.TokenAuthenticationServerProvider # Persistence setting for Chroma server CHROMA_IS_PERSISTENT=TRUE # ------------------------------ # Environment Variables for Oracle Service # (only used when VECTOR_STORE is oracle) # ------------------------------ ORACLE_PWD=Dify123456 ORACLE_CHARACTERSET=AL32UTF8 # ------------------------------ # Environment Variables for milvus Service # (only used when VECTOR_STORE is milvus) # ------------------------------ # ETCD configuration for auto compaction mode ETCD_AUTO_COMPACTION_MODE=revision # ETCD configuration for auto compaction retention in terms of number of revisions ETCD_AUTO_COMPACTION_RETENTION=1000 # ETCD configuration for backend quota in bytes ETCD_QUOTA_BACKEND_BYTES=4294967296 # ETCD configuration for the number of changes before triggering a snapshot ETCD_SNAPSHOT_COUNT=50000 # MinIO access key for authentication MINIO_ACCESS_KEY=minioadmin # MinIO secret key for authentication MINIO_SECRET_KEY=minioadmin # ETCD service endpoints ETCD_ENDPOINTS=etcd:2379 # MinIO service address MINIO_ADDRESS=minio:9000 # Enable or disable security authorization MILVUS_AUTHORIZATION_ENABLED=true # ------------------------------ # Environment Variables for pgvector / pgvector-rs Service # (only used when VECTOR_STORE is pgvector / pgvector-rs) # ------------------------------ PGVECTOR_PGUSER=postgres # The password for the default postgres user. PGVECTOR_POSTGRES_PASSWORD=difyai123456 # The name of the default postgres database. PGVECTOR_POSTGRES_DB=dify # postgres data directory PGVECTOR_PGDATA=/var/lib/postgresql/data/pgdata # ------------------------------ # Environment Variables for opensearch # (only used when VECTOR_STORE is opensearch) # ------------------------------ OPENSEARCH_DISCOVERY_TYPE=single-node OPENSEARCH_BOOTSTRAP_MEMORY_LOCK=true OPENSEARCH_JAVA_OPTS_MIN=512m OPENSEARCH_JAVA_OPTS_MAX=1024m OPENSEARCH_INITIAL_ADMIN_PASSWORD=Qazwsxedc!@#123 OPENSEARCH_MEMLOCK_SOFT=-1 OPENSEARCH_MEMLOCK_HARD=-1 OPENSEARCH_NOFILE_SOFT=65536 OPENSEARCH_NOFILE_HARD=65536 # ------------------------------ # Environment Variables for Nginx reverse proxy # ------------------------------ NGINX_SERVER_NAME=_ NGINX_HTTPS_ENABLED=false # HTTP port NGINX_PORT=80 # SSL settings are only applied when HTTPS_ENABLED is true NGINX_SSL_PORT=443 # if HTTPS_ENABLED is true, you're required to add your own SSL certificates/keys to the `./nginx/ssl` directory # and modify the env vars below accordingly. NGINX_SSL_CERT_FILENAME=dify.crt NGINX_SSL_CERT_KEY_FILENAME=dify.key NGINX_SSL_PROTOCOLS=TLSv1.1 TLSv1.2 TLSv1.3 # Nginx performance tuning NGINX_WORKER_PROCESSES=auto NGINX_CLIENT_MAX_BODY_SIZE=100M NGINX_KEEPALIVE_TIMEOUT=65 # Proxy settings NGINX_PROXY_READ_TIMEOUT=3600s NGINX_PROXY_SEND_TIMEOUT=3600s # Set true to accept requests for /.well-known/acme-challenge/ NGINX_ENABLE_CERTBOT_CHALLENGE=false # ------------------------------ # Certbot Configuration # ------------------------------ # Email address (required to get certificates from Let's Encrypt) CERTBOT_EMAIL=your_email@example.com # Domain name CERTBOT_DOMAIN=your_domain.com # certbot command options # i.e: --force-renewal --dry-run --test-cert --debug CERTBOT_OPTIONS= # ------------------------------ # Environment Variables for SSRF Proxy # ------------------------------ SSRF_HTTP_PORT=3128 SSRF_COREDUMP_DIR=/var/spool/squid SSRF_REVERSE_PROXY_PORT=8194 SSRF_SANDBOX_HOST=sandbox SSRF_DEFAULT_TIME_OUT=5 SSRF_DEFAULT_CONNECT_TIME_OUT=5 SSRF_DEFAULT_READ_TIME_OUT=5 SSRF_DEFAULT_WRITE_TIME_OUT=5 # ------------------------------ # docker env var for specifying vector db type at startup # (based on the vector db type, the corresponding docker # compose profile will be used) # if you want to use unstructured, add ',unstructured' to the end # ------------------------------ COMPOSE_PROFILES=${VECTOR_STORE:-weaviate} # ------------------------------ # Docker Compose Service Expose Host Port Configurations # ------------------------------ EXPOSE_NGINX_PORT=80 EXPOSE_NGINX_SSL_PORT=443 # ---------------------------------------------------------------------------- # ModelProvider & Tool Position Configuration # Used to specify the model providers and tools that can be used in the app. # ---------------------------------------------------------------------------- # Pin, include, and exclude tools # Use comma-separated values with no spaces between items. # Example: POSITION_TOOL_PINS=bing,google POSITION_TOOL_PINS= POSITION_TOOL_INCLUDES= POSITION_TOOL_EXCLUDES= # Pin, include, and exclude model providers # Use comma-separated values with no spaces between items. # Example: POSITION_PROVIDER_PINS=openai,openllm POSITION_PROVIDER_PINS= POSITION_PROVIDER_INCLUDES= POSITION_PROVIDER_EXCLUDES= # CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP CSP_WHITELIST= # Enable or disable create tidb service job CREATE_TIDB_SERVICE_JOB_ENABLED=false # Maximum number of submitted thread count in a ThreadPool for parallel node execution MAX_SUBMIT_COUNT=100 # The maximum number of top-k value for RAG. TOP_K_MAX_VALUE=10 # ------------------------------ # Plugin Daemon Configuration # ------------------------------ DB_PLUGIN_DATABASE=dify_plugin EXPOSE_PLUGIN_DAEMON_PORT=5002 PLUGIN_DAEMON_PORT=5002 PLUGIN_DAEMON_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi PLUGIN_DAEMON_URL=http://plugin_daemon:5002 PLUGIN_MAX_PACKAGE_SIZE=52428800 PLUGIN_PPROF_ENABLED=false PLUGIN_DEBUGGING_HOST=0.0.0.0 PLUGIN_DEBUGGING_PORT=5003 EXPOSE_PLUGIN_DEBUGGING_HOST=localhost EXPOSE_PLUGIN_DEBUGGING_PORT=5003 # If this key is changed, DIFY_INNER_API_KEY in plugin_daemon service must also be updated or agent node will fail. PLUGIN_DIFY_INNER_API_KEY=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 PLUGIN_DIFY_INNER_API_URL=http://api:5001 ENDPOINT_URL_TEMPLATE=http://localhost/e/{hook_id} MARKETPLACE_ENABLED=true MARKETPLACE_API_URL=https://marketplace.dify.ai FORCE_VERIFYING_SIGNATURE=true PLUGIN_STDIO_BUFFER_SIZE=1024 PLUGIN_STDIO_MAX_BUFFER_SIZE=5242880 PLUGIN_PYTHON_ENV_INIT_TIMEOUT=120 PLUGIN_MAX_EXECUTION_TIMEOUT=600 # PIP_MIRROR_URL=https://pypi.tuna.tsinghua.edu.cn/simple PIP_MIRROR_URL= # https://github.com/langgenius/dify-plugin-daemon/blob/main/.env.example # Plugin storage type, local aws_s3 tencent_cos azure_blob aliyun_oss volcengine_tos PLUGIN_STORAGE_TYPE=local PLUGIN_STORAGE_LOCAL_ROOT=/app/storage PLUGIN_WORKING_PATH=/app/storage/cwd PLUGIN_INSTALLED_PATH=plugin PLUGIN_PACKAGE_CACHE_PATH=plugin_packages PLUGIN_MEDIA_CACHE_PATH=assets # Plugin oss bucket PLUGIN_STORAGE_OSS_BUCKET= # Plugin oss s3 credentials PLUGIN_S3_USE_AWS=false PLUGIN_S3_USE_AWS_MANAGED_IAM=false PLUGIN_S3_ENDPOINT= PLUGIN_S3_USE_PATH_STYLE=false PLUGIN_AWS_ACCESS_KEY= PLUGIN_AWS_SECRET_KEY= PLUGIN_AWS_REGION= # Plugin oss azure blob PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME= PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING= # Plugin oss tencent cos PLUGIN_TENCENT_COS_SECRET_KEY= PLUGIN_TENCENT_COS_SECRET_ID= PLUGIN_TENCENT_COS_REGION= # Plugin oss aliyun oss PLUGIN_ALIYUN_OSS_REGION= PLUGIN_ALIYUN_OSS_ENDPOINT= PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID= PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET= PLUGIN_ALIYUN_OSS_AUTH_VERSION=v4 PLUGIN_ALIYUN_OSS_PATH= # Plugin oss volcengine tos PLUGIN_VOLCENGINE_TOS_ENDPOINT= PLUGIN_VOLCENGINE_TOS_ACCESS_KEY= PLUGIN_VOLCENGINE_TOS_SECRET_KEY= PLUGIN_VOLCENGINE_TOS_REGION= # ------------------------------ # OTLP Collector Configuration # ------------------------------ ENABLE_OTEL=false OTLP_TRACE_ENDPOINT= OTLP_METRIC_ENDPOINT= OTLP_BASE_ENDPOINT=http://localhost:4318 OTLP_API_KEY= OTEL_EXPORTER_OTLP_PROTOCOL= OTEL_EXPORTER_TYPE=otlp OTEL_SAMPLING_RATE=0.1 OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000 OTEL_MAX_QUEUE_SIZE=2048 OTEL_MAX_EXPORT_BATCH_SIZE=512 OTEL_METRIC_EXPORT_INTERVAL=60000 OTEL_BATCH_EXPORT_TIMEOUT=10000 OTEL_METRIC_EXPORT_TIMEOUT=30000 # Prevent Clickjacking ALLOW_EMBED=false # Dataset queue monitor configuration QUEUE_MONITOR_THRESHOLD=200 # You can configure multiple ones, separated by commas. eg: test1@dify.ai,test2@dify.ai QUEUE_MONITOR_ALERT_EMAILS= # Monitor interval in minutes, default is 30 minutes QUEUE_MONITOR_INTERVAL=30 # Swagger UI configuration SWAGGER_UI_ENABLED=true SWAGGER_UI_PATH=/swagger-ui.html # Celery schedule tasks configuration ENABLE_CLEAN_EMBEDDING_CACHE_TASK=false ENABLE_CLEAN_UNUSED_DATASETS_TASK=false ENABLE_CREATE_TIDB_SERVERLESS_TASK=false ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK=false ENABLE_CLEAN_MESSAGES=false ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK=false ENABLE_DATASETS_QUEUE_MONITOR=false ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK=true 太长了,其实很多都可以安装好之后,我们慢慢配置的,这边先无视它…… 然后处于好奇,我们来看一下它的 docker-compose.yml 文件 # ================================================================== # WARNING: This file is auto-generated by generate_docker_compose # Do not modify this file directly. Instead, update the .env.example # or docker-compose-template.yaml and regenerate this file. # ================================================================== x-shared-env: &shared-api-worker-env CONSOLE_API_URL: ${CONSOLE_API_URL:-} CONSOLE_WEB_URL: ${CONSOLE_WEB_URL:-} SERVICE_API_URL: ${SERVICE_API_URL:-} APP_API_URL: ${APP_API_URL:-} APP_WEB_URL: ${APP_WEB_URL:-} FILES_URL: ${FILES_URL:-} INTERNAL_FILES_URL: ${INTERNAL_FILES_URL:-} LANG: ${LANG:-en_US.UTF-8} LC_ALL: ${LC_ALL:-en_US.UTF-8} PYTHONIOENCODING: ${PYTHONIOENCODING:-utf-8} LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_FILE: ${LOG_FILE:-/app/logs/server.log} LOG_FILE_MAX_SIZE: ${LOG_FILE_MAX_SIZE:-20} LOG_FILE_BACKUP_COUNT: ${LOG_FILE_BACKUP_COUNT:-5} LOG_DATEFORMAT: ${LOG_DATEFORMAT:-%Y-%m-%d %H:%M:%S} LOG_TZ: ${LOG_TZ:-UTC} DEBUG: ${DEBUG:-false} FLASK_DEBUG: ${FLASK_DEBUG:-false} ENABLE_REQUEST_LOGGING: ${ENABLE_REQUEST_LOGGING:-False} SECRET_KEY: ${SECRET_KEY:-sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U} INIT_PASSWORD: ${INIT_PASSWORD:-} DEPLOY_ENV: ${DEPLOY_ENV:-PRODUCTION} CHECK_UPDATE_URL: ${CHECK_UPDATE_URL:-https://updates.dify.ai} OPENAI_API_BASE: ${OPENAI_API_BASE:-https://api.openai.com/v1} MIGRATION_ENABLED: ${MIGRATION_ENABLED:-true} FILES_ACCESS_TIMEOUT: ${FILES_ACCESS_TIMEOUT:-300} ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-60} REFRESH_TOKEN_EXPIRE_DAYS: ${REFRESH_TOKEN_EXPIRE_DAYS:-30} APP_MAX_ACTIVE_REQUESTS: ${APP_MAX_ACTIVE_REQUESTS:-0} APP_MAX_EXECUTION_TIME: ${APP_MAX_EXECUTION_TIME:-1200} DIFY_BIND_ADDRESS: ${DIFY_BIND_ADDRESS:-0.0.0.0} DIFY_PORT: ${DIFY_PORT:-5001} SERVER_WORKER_AMOUNT: ${SERVER_WORKER_AMOUNT:-1} SERVER_WORKER_CLASS: ${SERVER_WORKER_CLASS:-gevent} SERVER_WORKER_CONNECTIONS: ${SERVER_WORKER_CONNECTIONS:-10} CELERY_WORKER_CLASS: ${CELERY_WORKER_CLASS:-} GUNICORN_TIMEOUT: ${GUNICORN_TIMEOUT:-360} CELERY_WORKER_AMOUNT: ${CELERY_WORKER_AMOUNT:-} CELERY_AUTO_SCALE: ${CELERY_AUTO_SCALE:-false} CELERY_MAX_WORKERS: ${CELERY_MAX_WORKERS:-} CELERY_MIN_WORKERS: ${CELERY_MIN_WORKERS:-} API_TOOL_DEFAULT_CONNECT_TIMEOUT: ${API_TOOL_DEFAULT_CONNECT_TIMEOUT:-10} API_TOOL_DEFAULT_READ_TIMEOUT: ${API_TOOL_DEFAULT_READ_TIMEOUT:-60} ENABLE_WEBSITE_JINAREADER: ${ENABLE_WEBSITE_JINAREADER:-true} ENABLE_WEBSITE_FIRECRAWL: ${ENABLE_WEBSITE_FIRECRAWL:-true} ENABLE_WEBSITE_WATERCRAWL: ${ENABLE_WEBSITE_WATERCRAWL:-true} DB_USERNAME: ${DB_USERNAME:-postgres} DB_PASSWORD: ${DB_PASSWORD:-difyai123456} DB_HOST: ${DB_HOST:-db} DB_PORT: ${DB_PORT:-5432} DB_DATABASE: ${DB_DATABASE:-dify} SQLALCHEMY_POOL_SIZE: ${SQLALCHEMY_POOL_SIZE:-30} SQLALCHEMY_MAX_OVERFLOW: ${SQLALCHEMY_MAX_OVERFLOW:-10} SQLALCHEMY_POOL_RECYCLE: ${SQLALCHEMY_POOL_RECYCLE:-3600} SQLALCHEMY_ECHO: ${SQLALCHEMY_ECHO:-false} SQLALCHEMY_POOL_PRE_PING: ${SQLALCHEMY_POOL_PRE_PING:-false} SQLALCHEMY_POOL_USE_LIFO: ${SQLALCHEMY_POOL_USE_LIFO:-false} POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100} POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-128MB} POSTGRES_WORK_MEM: ${POSTGRES_WORK_MEM:-4MB} POSTGRES_MAINTENANCE_WORK_MEM: ${POSTGRES_MAINTENANCE_WORK_MEM:-64MB} POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-4096MB} REDIS_HOST: ${REDIS_HOST:-redis} REDIS_PORT: ${REDIS_PORT:-6379} REDIS_USERNAME: ${REDIS_USERNAME:-} REDIS_PASSWORD: ${REDIS_PASSWORD:-difyai123456} REDIS_USE_SSL: ${REDIS_USE_SSL:-false} REDIS_SSL_CERT_REQS: ${REDIS_SSL_CERT_REQS:-CERT_NONE} REDIS_SSL_CA_CERTS: ${REDIS_SSL_CA_CERTS:-} REDIS_SSL_CERTFILE: ${REDIS_SSL_CERTFILE:-} REDIS_SSL_KEYFILE: ${REDIS_SSL_KEYFILE:-} REDIS_DB: ${REDIS_DB:-0} REDIS_USE_SENTINEL: ${REDIS_USE_SENTINEL:-false} REDIS_SENTINELS: ${REDIS_SENTINELS:-} REDIS_SENTINEL_SERVICE_NAME: ${REDIS_SENTINEL_SERVICE_NAME:-} REDIS_SENTINEL_USERNAME: ${REDIS_SENTINEL_USERNAME:-} REDIS_SENTINEL_PASSWORD: ${REDIS_SENTINEL_PASSWORD:-} REDIS_SENTINEL_SOCKET_TIMEOUT: ${REDIS_SENTINEL_SOCKET_TIMEOUT:-0.1} REDIS_USE_CLUSTERS: ${REDIS_USE_CLUSTERS:-false} REDIS_CLUSTERS: ${REDIS_CLUSTERS:-} REDIS_CLUSTERS_PASSWORD: ${REDIS_CLUSTERS_PASSWORD:-} CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://:difyai123456@redis:6379/1} CELERY_BACKEND: ${CELERY_BACKEND:-redis} BROKER_USE_SSL: ${BROKER_USE_SSL:-false} CELERY_USE_SENTINEL: ${CELERY_USE_SENTINEL:-false} CELERY_SENTINEL_MASTER_NAME: ${CELERY_SENTINEL_MASTER_NAME:-} CELERY_SENTINEL_PASSWORD: ${CELERY_SENTINEL_PASSWORD:-} CELERY_SENTINEL_SOCKET_TIMEOUT: ${CELERY_SENTINEL_SOCKET_TIMEOUT:-0.1} WEB_API_CORS_ALLOW_ORIGINS: ${WEB_API_CORS_ALLOW_ORIGINS:-*} CONSOLE_CORS_ALLOW_ORIGINS: ${CONSOLE_CORS_ALLOW_ORIGINS:-*} STORAGE_TYPE: ${STORAGE_TYPE:-opendal} OPENDAL_SCHEME: ${OPENDAL_SCHEME:-fs} OPENDAL_FS_ROOT: ${OPENDAL_FS_ROOT:-storage} CLICKZETTA_VOLUME_TYPE: ${CLICKZETTA_VOLUME_TYPE:-user} CLICKZETTA_VOLUME_NAME: ${CLICKZETTA_VOLUME_NAME:-} CLICKZETTA_VOLUME_TABLE_PREFIX: ${CLICKZETTA_VOLUME_TABLE_PREFIX:-dataset_} CLICKZETTA_VOLUME_DIFY_PREFIX: ${CLICKZETTA_VOLUME_DIFY_PREFIX:-dify_km} S3_ENDPOINT: ${S3_ENDPOINT:-} S3_REGION: ${S3_REGION:-us-east-1} S3_BUCKET_NAME: ${S3_BUCKET_NAME:-difyai} S3_ACCESS_KEY: ${S3_ACCESS_KEY:-} S3_SECRET_KEY: ${S3_SECRET_KEY:-} S3_USE_AWS_MANAGED_IAM: ${S3_USE_AWS_MANAGED_IAM:-false} AZURE_BLOB_ACCOUNT_NAME: ${AZURE_BLOB_ACCOUNT_NAME:-difyai} AZURE_BLOB_ACCOUNT_KEY: ${AZURE_BLOB_ACCOUNT_KEY:-difyai} AZURE_BLOB_CONTAINER_NAME: ${AZURE_BLOB_CONTAINER_NAME:-difyai-container} AZURE_BLOB_ACCOUNT_URL: ${AZURE_BLOB_ACCOUNT_URL:-https://<your_account_name>.blob.core.windows.net} GOOGLE_STORAGE_BUCKET_NAME: ${GOOGLE_STORAGE_BUCKET_NAME:-your-bucket-name} GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64: ${GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64:-} ALIYUN_OSS_BUCKET_NAME: ${ALIYUN_OSS_BUCKET_NAME:-your-bucket-name} ALIYUN_OSS_ACCESS_KEY: ${ALIYUN_OSS_ACCESS_KEY:-your-access-key} ALIYUN_OSS_SECRET_KEY: ${ALIYUN_OSS_SECRET_KEY:-your-secret-key} ALIYUN_OSS_ENDPOINT: ${ALIYUN_OSS_ENDPOINT:-https://oss-ap-southeast-1-internal.aliyuncs.com} ALIYUN_OSS_REGION: ${ALIYUN_OSS_REGION:-ap-southeast-1} ALIYUN_OSS_AUTH_VERSION: ${ALIYUN_OSS_AUTH_VERSION:-v4} ALIYUN_OSS_PATH: ${ALIYUN_OSS_PATH:-your-path} TENCENT_COS_BUCKET_NAME: ${TENCENT_COS_BUCKET_NAME:-your-bucket-name} TENCENT_COS_SECRET_KEY: ${TENCENT_COS_SECRET_KEY:-your-secret-key} TENCENT_COS_SECRET_ID: ${TENCENT_COS_SECRET_ID:-your-secret-id} TENCENT_COS_REGION: ${TENCENT_COS_REGION:-your-region} TENCENT_COS_SCHEME: ${TENCENT_COS_SCHEME:-your-scheme} OCI_ENDPOINT: ${OCI_ENDPOINT:-https://your-object-storage-namespace.compat.objectstorage.us-ashburn-1.oraclecloud.com} OCI_BUCKET_NAME: ${OCI_BUCKET_NAME:-your-bucket-name} OCI_ACCESS_KEY: ${OCI_ACCESS_KEY:-your-access-key} OCI_SECRET_KEY: ${OCI_SECRET_KEY:-your-secret-key} OCI_REGION: ${OCI_REGION:-us-ashburn-1} HUAWEI_OBS_BUCKET_NAME: ${HUAWEI_OBS_BUCKET_NAME:-your-bucket-name} HUAWEI_OBS_SECRET_KEY: ${HUAWEI_OBS_SECRET_KEY:-your-secret-key} HUAWEI_OBS_ACCESS_KEY: ${HUAWEI_OBS_ACCESS_KEY:-your-access-key} HUAWEI_OBS_SERVER: ${HUAWEI_OBS_SERVER:-your-server-url} VOLCENGINE_TOS_BUCKET_NAME: ${VOLCENGINE_TOS_BUCKET_NAME:-your-bucket-name} VOLCENGINE_TOS_SECRET_KEY: ${VOLCENGINE_TOS_SECRET_KEY:-your-secret-key} VOLCENGINE_TOS_ACCESS_KEY: ${VOLCENGINE_TOS_ACCESS_KEY:-your-access-key} VOLCENGINE_TOS_ENDPOINT: ${VOLCENGINE_TOS_ENDPOINT:-your-server-url} VOLCENGINE_TOS_REGION: ${VOLCENGINE_TOS_REGION:-your-region} BAIDU_OBS_BUCKET_NAME: ${BAIDU_OBS_BUCKET_NAME:-your-bucket-name} BAIDU_OBS_SECRET_KEY: ${BAIDU_OBS_SECRET_KEY:-your-secret-key} BAIDU_OBS_ACCESS_KEY: ${BAIDU_OBS_ACCESS_KEY:-your-access-key} BAIDU_OBS_ENDPOINT: ${BAIDU_OBS_ENDPOINT:-your-server-url} SUPABASE_BUCKET_NAME: ${SUPABASE_BUCKET_NAME:-your-bucket-name} SUPABASE_API_KEY: ${SUPABASE_API_KEY:-your-access-key} SUPABASE_URL: ${SUPABASE_URL:-your-server-url} VECTOR_STORE: ${VECTOR_STORE:-weaviate} VECTOR_INDEX_NAME_PREFIX: ${VECTOR_INDEX_NAME_PREFIX:-Vector_index} WEAVIATE_ENDPOINT: ${WEAVIATE_ENDPOINT:-http://weaviate:8080} WEAVIATE_API_KEY: ${WEAVIATE_API_KEY:-WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih} QDRANT_URL: ${QDRANT_URL:-http://qdrant:6333} QDRANT_API_KEY: ${QDRANT_API_KEY:-difyai123456} QDRANT_CLIENT_TIMEOUT: ${QDRANT_CLIENT_TIMEOUT:-20} QDRANT_GRPC_ENABLED: ${QDRANT_GRPC_ENABLED:-false} QDRANT_GRPC_PORT: ${QDRANT_GRPC_PORT:-6334} QDRANT_REPLICATION_FACTOR: ${QDRANT_REPLICATION_FACTOR:-1} MILVUS_URI: ${MILVUS_URI:-http://host.docker.internal:19530} MILVUS_DATABASE: ${MILVUS_DATABASE:-} MILVUS_TOKEN: ${MILVUS_TOKEN:-} MILVUS_USER: ${MILVUS_USER:-} MILVUS_PASSWORD: ${MILVUS_PASSWORD:-} MILVUS_ENABLE_HYBRID_SEARCH: ${MILVUS_ENABLE_HYBRID_SEARCH:-False} MILVUS_ANALYZER_PARAMS: ${MILVUS_ANALYZER_PARAMS:-} MYSCALE_HOST: ${MYSCALE_HOST:-myscale} MYSCALE_PORT: ${MYSCALE_PORT:-8123} MYSCALE_USER: ${MYSCALE_USER:-default} MYSCALE_PASSWORD: ${MYSCALE_PASSWORD:-} MYSCALE_DATABASE: ${MYSCALE_DATABASE:-dify} MYSCALE_FTS_PARAMS: ${MYSCALE_FTS_PARAMS:-} COUCHBASE_CONNECTION_STRING: ${COUCHBASE_CONNECTION_STRING:-couchbase://couchbase-server} COUCHBASE_USER: ${COUCHBASE_USER:-Administrator} COUCHBASE_PASSWORD: ${COUCHBASE_PASSWORD:-password} COUCHBASE_BUCKET_NAME: ${COUCHBASE_BUCKET_NAME:-Embeddings} COUCHBASE_SCOPE_NAME: ${COUCHBASE_SCOPE_NAME:-_default} PGVECTOR_HOST: ${PGVECTOR_HOST:-pgvector} PGVECTOR_PORT: ${PGVECTOR_PORT:-5432} PGVECTOR_USER: ${PGVECTOR_USER:-postgres} PGVECTOR_PASSWORD: ${PGVECTOR_PASSWORD:-difyai123456} PGVECTOR_DATABASE: ${PGVECTOR_DATABASE:-dify} PGVECTOR_MIN_CONNECTION: ${PGVECTOR_MIN_CONNECTION:-1} PGVECTOR_MAX_CONNECTION: ${PGVECTOR_MAX_CONNECTION:-5} PGVECTOR_PG_BIGM: ${PGVECTOR_PG_BIGM:-false} PGVECTOR_PG_BIGM_VERSION: ${PGVECTOR_PG_BIGM_VERSION:-1.2-20240606} VASTBASE_HOST: ${VASTBASE_HOST:-vastbase} VASTBASE_PORT: ${VASTBASE_PORT:-5432} VASTBASE_USER: ${VASTBASE_USER:-dify} VASTBASE_PASSWORD: ${VASTBASE_PASSWORD:-Difyai123456} VASTBASE_DATABASE: ${VASTBASE_DATABASE:-dify} VASTBASE_MIN_CONNECTION: ${VASTBASE_MIN_CONNECTION:-1} VASTBASE_MAX_CONNECTION: ${VASTBASE_MAX_CONNECTION:-5} PGVECTO_RS_HOST: ${PGVECTO_RS_HOST:-pgvecto-rs} PGVECTO_RS_PORT: ${PGVECTO_RS_PORT:-5432} PGVECTO_RS_USER: ${PGVECTO_RS_USER:-postgres} PGVECTO_RS_PASSWORD: ${PGVECTO_RS_PASSWORD:-difyai123456} PGVECTO_RS_DATABASE: ${PGVECTO_RS_DATABASE:-dify} ANALYTICDB_KEY_ID: ${ANALYTICDB_KEY_ID:-your-ak} ANALYTICDB_KEY_SECRET: ${ANALYTICDB_KEY_SECRET:-your-sk} ANALYTICDB_REGION_ID: ${ANALYTICDB_REGION_ID:-cn-hangzhou} ANALYTICDB_INSTANCE_ID: ${ANALYTICDB_INSTANCE_ID:-gp-ab123456} ANALYTICDB_ACCOUNT: ${ANALYTICDB_ACCOUNT:-testaccount} ANALYTICDB_PASSWORD: ${ANALYTICDB_PASSWORD:-testpassword} ANALYTICDB_NAMESPACE: ${ANALYTICDB_NAMESPACE:-dify} ANALYTICDB_NAMESPACE_PASSWORD: ${ANALYTICDB_NAMESPACE_PASSWORD:-difypassword} ANALYTICDB_HOST: ${ANALYTICDB_HOST:-gp-test.aliyuncs.com} ANALYTICDB_PORT: ${ANALYTICDB_PORT:-5432} ANALYTICDB_MIN_CONNECTION: ${ANALYTICDB_MIN_CONNECTION:-1} ANALYTICDB_MAX_CONNECTION: ${ANALYTICDB_MAX_CONNECTION:-5} TIDB_VECTOR_HOST: ${TIDB_VECTOR_HOST:-tidb} TIDB_VECTOR_PORT: ${TIDB_VECTOR_PORT:-4000} TIDB_VECTOR_USER: ${TIDB_VECTOR_USER:-} TIDB_VECTOR_PASSWORD: ${TIDB_VECTOR_PASSWORD:-} TIDB_VECTOR_DATABASE: ${TIDB_VECTOR_DATABASE:-dify} MATRIXONE_HOST: ${MATRIXONE_HOST:-matrixone} MATRIXONE_PORT: ${MATRIXONE_PORT:-6001} MATRIXONE_USER: ${MATRIXONE_USER:-dump} MATRIXONE_PASSWORD: ${MATRIXONE_PASSWORD:-111} MATRIXONE_DATABASE: ${MATRIXONE_DATABASE:-dify} TIDB_ON_QDRANT_URL: ${TIDB_ON_QDRANT_URL:-http://127.0.0.1} TIDB_ON_QDRANT_API_KEY: ${TIDB_ON_QDRANT_API_KEY:-dify} TIDB_ON_QDRANT_CLIENT_TIMEOUT: ${TIDB_ON_QDRANT_CLIENT_TIMEOUT:-20} TIDB_ON_QDRANT_GRPC_ENABLED: ${TIDB_ON_QDRANT_GRPC_ENABLED:-false} TIDB_ON_QDRANT_GRPC_PORT: ${TIDB_ON_QDRANT_GRPC_PORT:-6334} TIDB_PUBLIC_KEY: ${TIDB_PUBLIC_KEY:-dify} TIDB_PRIVATE_KEY: ${TIDB_PRIVATE_KEY:-dify} TIDB_API_URL: ${TIDB_API_URL:-http://127.0.0.1} TIDB_IAM_API_URL: ${TIDB_IAM_API_URL:-http://127.0.0.1} TIDB_REGION: ${TIDB_REGION:-regions/aws-us-east-1} TIDB_PROJECT_ID: ${TIDB_PROJECT_ID:-dify} TIDB_SPEND_LIMIT: ${TIDB_SPEND_LIMIT:-100} CHROMA_HOST: ${CHROMA_HOST:-127.0.0.1} CHROMA_PORT: ${CHROMA_PORT:-8000} CHROMA_TENANT: ${CHROMA_TENANT:-default_tenant} CHROMA_DATABASE: ${CHROMA_DATABASE:-default_database} CHROMA_AUTH_PROVIDER: ${CHROMA_AUTH_PROVIDER:-chromadb.auth.token_authn.TokenAuthClientProvider} CHROMA_AUTH_CREDENTIALS: ${CHROMA_AUTH_CREDENTIALS:-} ORACLE_USER: ${ORACLE_USER:-dify} ORACLE_PASSWORD: ${ORACLE_PASSWORD:-dify} ORACLE_DSN: ${ORACLE_DSN:-oracle:1521/FREEPDB1} ORACLE_CONFIG_DIR: ${ORACLE_CONFIG_DIR:-/app/api/storage/wallet} ORACLE_WALLET_LOCATION: ${ORACLE_WALLET_LOCATION:-/app/api/storage/wallet} ORACLE_WALLET_PASSWORD: ${ORACLE_WALLET_PASSWORD:-dify} ORACLE_IS_AUTONOMOUS: ${ORACLE_IS_AUTONOMOUS:-false} RELYT_HOST: ${RELYT_HOST:-db} RELYT_PORT: ${RELYT_PORT:-5432} RELYT_USER: ${RELYT_USER:-postgres} RELYT_PASSWORD: ${RELYT_PASSWORD:-difyai123456} RELYT_DATABASE: ${RELYT_DATABASE:-postgres} OPENSEARCH_HOST: ${OPENSEARCH_HOST:-opensearch} OPENSEARCH_PORT: ${OPENSEARCH_PORT:-9200} OPENSEARCH_SECURE: ${OPENSEARCH_SECURE:-true} OPENSEARCH_VERIFY_CERTS: ${OPENSEARCH_VERIFY_CERTS:-true} OPENSEARCH_AUTH_METHOD: ${OPENSEARCH_AUTH_METHOD:-basic} OPENSEARCH_USER: ${OPENSEARCH_USER:-admin} OPENSEARCH_PASSWORD: ${OPENSEARCH_PASSWORD:-admin} OPENSEARCH_AWS_REGION: ${OPENSEARCH_AWS_REGION:-ap-southeast-1} OPENSEARCH_AWS_SERVICE: ${OPENSEARCH_AWS_SERVICE:-aoss} TENCENT_VECTOR_DB_URL: ${TENCENT_VECTOR_DB_URL:-http://127.0.0.1} TENCENT_VECTOR_DB_API_KEY: ${TENCENT_VECTOR_DB_API_KEY:-dify} TENCENT_VECTOR_DB_TIMEOUT: ${TENCENT_VECTOR_DB_TIMEOUT:-30} TENCENT_VECTOR_DB_USERNAME: ${TENCENT_VECTOR_DB_USERNAME:-dify} TENCENT_VECTOR_DB_DATABASE: ${TENCENT_VECTOR_DB_DATABASE:-dify} TENCENT_VECTOR_DB_SHARD: ${TENCENT_VECTOR_DB_SHARD:-1} TENCENT_VECTOR_DB_REPLICAS: ${TENCENT_VECTOR_DB_REPLICAS:-2} TENCENT_VECTOR_DB_ENABLE_HYBRID_SEARCH: ${TENCENT_VECTOR_DB_ENABLE_HYBRID_SEARCH:-false} ELASTICSEARCH_HOST: ${ELASTICSEARCH_HOST:-0.0.0.0} ELASTICSEARCH_PORT: ${ELASTICSEARCH_PORT:-9200} ELASTICSEARCH_USERNAME: ${ELASTICSEARCH_USERNAME:-elastic} ELASTICSEARCH_PASSWORD: ${ELASTICSEARCH_PASSWORD:-elastic} KIBANA_PORT: ${KIBANA_PORT:-5601} ELASTICSEARCH_USE_CLOUD: ${ELASTICSEARCH_USE_CLOUD:-false} ELASTICSEARCH_CLOUD_URL: ${ELASTICSEARCH_CLOUD_URL:-YOUR-ELASTICSEARCH_CLOUD_URL} ELASTICSEARCH_API_KEY: ${ELASTICSEARCH_API_KEY:-YOUR-ELASTICSEARCH_API_KEY} ELASTICSEARCH_VERIFY_CERTS: ${ELASTICSEARCH_VERIFY_CERTS:-False} ELASTICSEARCH_CA_CERTS: ${ELASTICSEARCH_CA_CERTS:-} ELASTICSEARCH_REQUEST_TIMEOUT: ${ELASTICSEARCH_REQUEST_TIMEOUT:-100000} ELASTICSEARCH_RETRY_ON_TIMEOUT: ${ELASTICSEARCH_RETRY_ON_TIMEOUT:-True} ELASTICSEARCH_MAX_RETRIES: ${ELASTICSEARCH_MAX_RETRIES:-10} BAIDU_VECTOR_DB_ENDPOINT: ${BAIDU_VECTOR_DB_ENDPOINT:-http://127.0.0.1:5287} BAIDU_VECTOR_DB_CONNECTION_TIMEOUT_MS: ${BAIDU_VECTOR_DB_CONNECTION_TIMEOUT_MS:-30000} BAIDU_VECTOR_DB_ACCOUNT: ${BAIDU_VECTOR_DB_ACCOUNT:-root} BAIDU_VECTOR_DB_API_KEY: ${BAIDU_VECTOR_DB_API_KEY:-dify} BAIDU_VECTOR_DB_DATABASE: ${BAIDU_VECTOR_DB_DATABASE:-dify} BAIDU_VECTOR_DB_SHARD: ${BAIDU_VECTOR_DB_SHARD:-1} BAIDU_VECTOR_DB_REPLICAS: ${BAIDU_VECTOR_DB_REPLICAS:-3} VIKINGDB_ACCESS_KEY: ${VIKINGDB_ACCESS_KEY:-your-ak} VIKINGDB_SECRET_KEY: ${VIKINGDB_SECRET_KEY:-your-sk} VIKINGDB_REGION: ${VIKINGDB_REGION:-cn-shanghai} VIKINGDB_HOST: ${VIKINGDB_HOST:-api-vikingdb.xxx.volces.com} VIKINGDB_SCHEMA: ${VIKINGDB_SCHEMA:-http} VIKINGDB_CONNECTION_TIMEOUT: ${VIKINGDB_CONNECTION_TIMEOUT:-30} VIKINGDB_SOCKET_TIMEOUT: ${VIKINGDB_SOCKET_TIMEOUT:-30} LINDORM_URL: ${LINDORM_URL:-http://lindorm:30070} LINDORM_USERNAME: ${LINDORM_USERNAME:-lindorm} LINDORM_PASSWORD: ${LINDORM_PASSWORD:-lindorm} LINDORM_QUERY_TIMEOUT: ${LINDORM_QUERY_TIMEOUT:-1} OCEANBASE_VECTOR_HOST: ${OCEANBASE_VECTOR_HOST:-oceanbase} OCEANBASE_VECTOR_PORT: ${OCEANBASE_VECTOR_PORT:-2881} OCEANBASE_VECTOR_USER: ${OCEANBASE_VECTOR_USER:-root@test} OCEANBASE_VECTOR_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OCEANBASE_VECTOR_DATABASE: ${OCEANBASE_VECTOR_DATABASE:-test} OCEANBASE_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai} OCEANBASE_MEMORY_LIMIT: ${OCEANBASE_MEMORY_LIMIT:-6G} OCEANBASE_ENABLE_HYBRID_SEARCH: ${OCEANBASE_ENABLE_HYBRID_SEARCH:-false} OPENGAUSS_HOST: ${OPENGAUSS_HOST:-opengauss} OPENGAUSS_PORT: ${OPENGAUSS_PORT:-6600} OPENGAUSS_USER: ${OPENGAUSS_USER:-postgres} OPENGAUSS_PASSWORD: ${OPENGAUSS_PASSWORD:-Dify@123} OPENGAUSS_DATABASE: ${OPENGAUSS_DATABASE:-dify} OPENGAUSS_MIN_CONNECTION: ${OPENGAUSS_MIN_CONNECTION:-1} OPENGAUSS_MAX_CONNECTION: ${OPENGAUSS_MAX_CONNECTION:-5} OPENGAUSS_ENABLE_PQ: ${OPENGAUSS_ENABLE_PQ:-false} HUAWEI_CLOUD_HOSTS: ${HUAWEI_CLOUD_HOSTS:-https://127.0.0.1:9200} HUAWEI_CLOUD_USER: ${HUAWEI_CLOUD_USER:-admin} HUAWEI_CLOUD_PASSWORD: ${HUAWEI_CLOUD_PASSWORD:-admin} UPSTASH_VECTOR_URL: ${UPSTASH_VECTOR_URL:-https://xxx-vector.upstash.io} UPSTASH_VECTOR_TOKEN: ${UPSTASH_VECTOR_TOKEN:-dify} TABLESTORE_ENDPOINT: ${TABLESTORE_ENDPOINT:-https://instance-name.cn-hangzhou.ots.aliyuncs.com} TABLESTORE_INSTANCE_NAME: ${TABLESTORE_INSTANCE_NAME:-instance-name} TABLESTORE_ACCESS_KEY_ID: ${TABLESTORE_ACCESS_KEY_ID:-xxx} TABLESTORE_ACCESS_KEY_SECRET: ${TABLESTORE_ACCESS_KEY_SECRET:-xxx} TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE: ${TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE:-false} CLICKZETTA_USERNAME: ${CLICKZETTA_USERNAME:-} CLICKZETTA_PASSWORD: ${CLICKZETTA_PASSWORD:-} CLICKZETTA_INSTANCE: ${CLICKZETTA_INSTANCE:-} CLICKZETTA_SERVICE: ${CLICKZETTA_SERVICE:-api.clickzetta.com} CLICKZETTA_WORKSPACE: ${CLICKZETTA_WORKSPACE:-quick_start} CLICKZETTA_VCLUSTER: ${CLICKZETTA_VCLUSTER:-default_ap} CLICKZETTA_SCHEMA: ${CLICKZETTA_SCHEMA:-dify} CLICKZETTA_BATCH_SIZE: ${CLICKZETTA_BATCH_SIZE:-100} CLICKZETTA_ENABLE_INVERTED_INDEX: ${CLICKZETTA_ENABLE_INVERTED_INDEX:-true} CLICKZETTA_ANALYZER_TYPE: ${CLICKZETTA_ANALYZER_TYPE:-chinese} CLICKZETTA_ANALYZER_MODE: ${CLICKZETTA_ANALYZER_MODE:-smart} CLICKZETTA_VECTOR_DISTANCE_FUNCTION: ${CLICKZETTA_VECTOR_DISTANCE_FUNCTION:-cosine_distance} UPLOAD_FILE_SIZE_LIMIT: ${UPLOAD_FILE_SIZE_LIMIT:-15} UPLOAD_FILE_BATCH_LIMIT: ${UPLOAD_FILE_BATCH_LIMIT:-5} ETL_TYPE: ${ETL_TYPE:-dify} UNSTRUCTURED_API_URL: ${UNSTRUCTURED_API_URL:-} UNSTRUCTURED_API_KEY: ${UNSTRUCTURED_API_KEY:-} SCARF_NO_ANALYTICS: ${SCARF_NO_ANALYTICS:-true} PROMPT_GENERATION_MAX_TOKENS: ${PROMPT_GENERATION_MAX_TOKENS:-512} CODE_GENERATION_MAX_TOKENS: ${CODE_GENERATION_MAX_TOKENS:-1024} PLUGIN_BASED_TOKEN_COUNTING_ENABLED: ${PLUGIN_BASED_TOKEN_COUNTING_ENABLED:-false} MULTIMODAL_SEND_FORMAT: ${MULTIMODAL_SEND_FORMAT:-base64} UPLOAD_IMAGE_FILE_SIZE_LIMIT: ${UPLOAD_IMAGE_FILE_SIZE_LIMIT:-10} UPLOAD_VIDEO_FILE_SIZE_LIMIT: ${UPLOAD_VIDEO_FILE_SIZE_LIMIT:-100} UPLOAD_AUDIO_FILE_SIZE_LIMIT: ${UPLOAD_AUDIO_FILE_SIZE_LIMIT:-50} SENTRY_DSN: ${SENTRY_DSN:-} API_SENTRY_DSN: ${API_SENTRY_DSN:-} API_SENTRY_TRACES_SAMPLE_RATE: ${API_SENTRY_TRACES_SAMPLE_RATE:-1.0} API_SENTRY_PROFILES_SAMPLE_RATE: ${API_SENTRY_PROFILES_SAMPLE_RATE:-1.0} WEB_SENTRY_DSN: ${WEB_SENTRY_DSN:-} NOTION_INTEGRATION_TYPE: ${NOTION_INTEGRATION_TYPE:-public} NOTION_CLIENT_SECRET: ${NOTION_CLIENT_SECRET:-} NOTION_CLIENT_ID: ${NOTION_CLIENT_ID:-} NOTION_INTERNAL_SECRET: ${NOTION_INTERNAL_SECRET:-} MAIL_TYPE: ${MAIL_TYPE:-resend} MAIL_DEFAULT_SEND_FROM: ${MAIL_DEFAULT_SEND_FROM:-} RESEND_API_URL: ${RESEND_API_URL:-https://api.resend.com} RESEND_API_KEY: ${RESEND_API_KEY:-your-resend-api-key} SMTP_SERVER: ${SMTP_SERVER:-} SMTP_PORT: ${SMTP_PORT:-465} SMTP_USERNAME: ${SMTP_USERNAME:-} SMTP_PASSWORD: ${SMTP_PASSWORD:-} SMTP_USE_TLS: ${SMTP_USE_TLS:-true} SMTP_OPPORTUNISTIC_TLS: ${SMTP_OPPORTUNISTIC_TLS:-false} SENDGRID_API_KEY: ${SENDGRID_API_KEY:-} INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-4000} INVITE_EXPIRY_HOURS: ${INVITE_EXPIRY_HOURS:-72} RESET_PASSWORD_TOKEN_EXPIRY_MINUTES: ${RESET_PASSWORD_TOKEN_EXPIRY_MINUTES:-5} CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES: ${CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES:-5} OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES: ${OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES:-5} CODE_EXECUTION_ENDPOINT: ${CODE_EXECUTION_ENDPOINT:-http://sandbox:8194} CODE_EXECUTION_API_KEY: ${CODE_EXECUTION_API_KEY:-dify-sandbox} CODE_MAX_NUMBER: ${CODE_MAX_NUMBER:-9223372036854775807} CODE_MIN_NUMBER: ${CODE_MIN_NUMBER:--9223372036854775808} CODE_MAX_DEPTH: ${CODE_MAX_DEPTH:-5} CODE_MAX_PRECISION: ${CODE_MAX_PRECISION:-20} CODE_MAX_STRING_LENGTH: ${CODE_MAX_STRING_LENGTH:-80000} CODE_MAX_STRING_ARRAY_LENGTH: ${CODE_MAX_STRING_ARRAY_LENGTH:-30} CODE_MAX_OBJECT_ARRAY_LENGTH: ${CODE_MAX_OBJECT_ARRAY_LENGTH:-30} CODE_MAX_NUMBER_ARRAY_LENGTH: ${CODE_MAX_NUMBER_ARRAY_LENGTH:-1000} CODE_EXECUTION_CONNECT_TIMEOUT: ${CODE_EXECUTION_CONNECT_TIMEOUT:-10} CODE_EXECUTION_READ_TIMEOUT: ${CODE_EXECUTION_READ_TIMEOUT:-60} CODE_EXECUTION_WRITE_TIMEOUT: ${CODE_EXECUTION_WRITE_TIMEOUT:-10} TEMPLATE_TRANSFORM_MAX_LENGTH: ${TEMPLATE_TRANSFORM_MAX_LENGTH:-80000} WORKFLOW_MAX_EXECUTION_STEPS: ${WORKFLOW_MAX_EXECUTION_STEPS:-500} WORKFLOW_MAX_EXECUTION_TIME: ${WORKFLOW_MAX_EXECUTION_TIME:-1200} WORKFLOW_CALL_MAX_DEPTH: ${WORKFLOW_CALL_MAX_DEPTH:-5} MAX_VARIABLE_SIZE: ${MAX_VARIABLE_SIZE:-204800} WORKFLOW_PARALLEL_DEPTH_LIMIT: ${WORKFLOW_PARALLEL_DEPTH_LIMIT:-3} WORKFLOW_FILE_UPLOAD_LIMIT: ${WORKFLOW_FILE_UPLOAD_LIMIT:-10} WORKFLOW_NODE_EXECUTION_STORAGE: ${WORKFLOW_NODE_EXECUTION_STORAGE:-rdbms} CORE_WORKFLOW_EXECUTION_REPOSITORY: ${CORE_WORKFLOW_EXECUTION_REPOSITORY:-core.repositories.sqlalchemy_workflow_execution_repository.SQLAlchemyWorkflowExecutionRepository} CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY: ${CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY:-core.repositories.sqlalchemy_workflow_node_execution_repository.SQLAlchemyWorkflowNodeExecutionRepository} API_WORKFLOW_RUN_REPOSITORY: ${API_WORKFLOW_RUN_REPOSITORY:-repositories.sqlalchemy_api_workflow_run_repository.DifyAPISQLAlchemyWorkflowRunRepository} API_WORKFLOW_NODE_EXECUTION_REPOSITORY: ${API_WORKFLOW_NODE_EXECUTION_REPOSITORY:-repositories.sqlalchemy_api_workflow_node_execution_repository.DifyAPISQLAlchemyWorkflowNodeExecutionRepository} WORKFLOW_LOG_CLEANUP_ENABLED: ${WORKFLOW_LOG_CLEANUP_ENABLED:-false} WORKFLOW_LOG_RETENTION_DAYS: ${WORKFLOW_LOG_RETENTION_DAYS:-30} WORKFLOW_LOG_CLEANUP_BATCH_SIZE: ${WORKFLOW_LOG_CLEANUP_BATCH_SIZE:-100} HTTP_REQUEST_NODE_MAX_BINARY_SIZE: ${HTTP_REQUEST_NODE_MAX_BINARY_SIZE:-10485760} HTTP_REQUEST_NODE_MAX_TEXT_SIZE: ${HTTP_REQUEST_NODE_MAX_TEXT_SIZE:-1048576} HTTP_REQUEST_NODE_SSL_VERIFY: ${HTTP_REQUEST_NODE_SSL_VERIFY:-True} RESPECT_XFORWARD_HEADERS_ENABLED: ${RESPECT_XFORWARD_HEADERS_ENABLED:-false} SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128} SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} MAX_PARALLEL_LIMIT: ${MAX_PARALLEL_LIMIT:-10} MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-99} TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} ALLOW_UNSAFE_DATA_SCHEME: ${ALLOW_UNSAFE_DATA_SCHEME:-false} MAX_TREE_DEPTH: ${MAX_TREE_DEPTH:-50} POSTGRES_USER: ${POSTGRES_USER:-${DB_USERNAME}} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-${DB_PASSWORD}} POSTGRES_DB: ${POSTGRES_DB:-${DB_DATABASE}} PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata} SANDBOX_API_KEY: ${SANDBOX_API_KEY:-dify-sandbox} SANDBOX_GIN_MODE: ${SANDBOX_GIN_MODE:-release} SANDBOX_WORKER_TIMEOUT: ${SANDBOX_WORKER_TIMEOUT:-15} SANDBOX_ENABLE_NETWORK: ${SANDBOX_ENABLE_NETWORK:-true} SANDBOX_HTTP_PROXY: ${SANDBOX_HTTP_PROXY:-http://ssrf_proxy:3128} SANDBOX_HTTPS_PROXY: ${SANDBOX_HTTPS_PROXY:-http://ssrf_proxy:3128} SANDBOX_PORT: ${SANDBOX_PORT:-8194} WEAVIATE_PERSISTENCE_DATA_PATH: ${WEAVIATE_PERSISTENCE_DATA_PATH:-/var/lib/weaviate} WEAVIATE_QUERY_DEFAULTS_LIMIT: ${WEAVIATE_QUERY_DEFAULTS_LIMIT:-25} WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: ${WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED:-true} WEAVIATE_DEFAULT_VECTORIZER_MODULE: ${WEAVIATE_DEFAULT_VECTORIZER_MODULE:-none} WEAVIATE_CLUSTER_HOSTNAME: ${WEAVIATE_CLUSTER_HOSTNAME:-node1} WEAVIATE_AUTHENTICATION_APIKEY_ENABLED: ${WEAVIATE_AUTHENTICATION_APIKEY_ENABLED:-true} WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS: ${WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS:-WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih} WEAVIATE_AUTHENTICATION_APIKEY_USERS: ${WEAVIATE_AUTHENTICATION_APIKEY_USERS:-hello@dify.ai} WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED: ${WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED:-true} WEAVIATE_AUTHORIZATION_ADMINLIST_USERS: ${WEAVIATE_AUTHORIZATION_ADMINLIST_USERS:-hello@dify.ai} CHROMA_SERVER_AUTHN_CREDENTIALS: ${CHROMA_SERVER_AUTHN_CREDENTIALS:-difyai123456} CHROMA_SERVER_AUTHN_PROVIDER: ${CHROMA_SERVER_AUTHN_PROVIDER:-chromadb.auth.token_authn.TokenAuthenticationServerProvider} CHROMA_IS_PERSISTENT: ${CHROMA_IS_PERSISTENT:-TRUE} ORACLE_PWD: ${ORACLE_PWD:-Dify123456} ORACLE_CHARACTERSET: ${ORACLE_CHARACTERSET:-AL32UTF8} ETCD_AUTO_COMPACTION_MODE: ${ETCD_AUTO_COMPACTION_MODE:-revision} ETCD_AUTO_COMPACTION_RETENTION: ${ETCD_AUTO_COMPACTION_RETENTION:-1000} ETCD_QUOTA_BACKEND_BYTES: ${ETCD_QUOTA_BACKEND_BYTES:-4294967296} ETCD_SNAPSHOT_COUNT: ${ETCD_SNAPSHOT_COUNT:-50000} MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin} ETCD_ENDPOINTS: ${ETCD_ENDPOINTS:-etcd:2379} MINIO_ADDRESS: ${MINIO_ADDRESS:-minio:9000} MILVUS_AUTHORIZATION_ENABLED: ${MILVUS_AUTHORIZATION_ENABLED:-true} PGVECTOR_PGUSER: ${PGVECTOR_PGUSER:-postgres} PGVECTOR_POSTGRES_PASSWORD: ${PGVECTOR_POSTGRES_PASSWORD:-difyai123456} PGVECTOR_POSTGRES_DB: ${PGVECTOR_POSTGRES_DB:-dify} PGVECTOR_PGDATA: ${PGVECTOR_PGDATA:-/var/lib/postgresql/data/pgdata} OPENSEARCH_DISCOVERY_TYPE: ${OPENSEARCH_DISCOVERY_TYPE:-single-node} OPENSEARCH_BOOTSTRAP_MEMORY_LOCK: ${OPENSEARCH_BOOTSTRAP_MEMORY_LOCK:-true} OPENSEARCH_JAVA_OPTS_MIN: ${OPENSEARCH_JAVA_OPTS_MIN:-512m} OPENSEARCH_JAVA_OPTS_MAX: ${OPENSEARCH_JAVA_OPTS_MAX:-1024m} OPENSEARCH_INITIAL_ADMIN_PASSWORD: ${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-Qazwsxedc!@#123} OPENSEARCH_MEMLOCK_SOFT: ${OPENSEARCH_MEMLOCK_SOFT:--1} OPENSEARCH_MEMLOCK_HARD: ${OPENSEARCH_MEMLOCK_HARD:--1} OPENSEARCH_NOFILE_SOFT: ${OPENSEARCH_NOFILE_SOFT:-65536} OPENSEARCH_NOFILE_HARD: ${OPENSEARCH_NOFILE_HARD:-65536} NGINX_SERVER_NAME: ${NGINX_SERVER_NAME:-_} NGINX_HTTPS_ENABLED: ${NGINX_HTTPS_ENABLED:-false} NGINX_PORT: ${NGINX_PORT:-80} NGINX_SSL_PORT: ${NGINX_SSL_PORT:-443} NGINX_SSL_CERT_FILENAME: ${NGINX_SSL_CERT_FILENAME:-dify.crt} NGINX_SSL_CERT_KEY_FILENAME: ${NGINX_SSL_CERT_KEY_FILENAME:-dify.key} NGINX_SSL_PROTOCOLS: ${NGINX_SSL_PROTOCOLS:-TLSv1.1 TLSv1.2 TLSv1.3} NGINX_WORKER_PROCESSES: ${NGINX_WORKER_PROCESSES:-auto} NGINX_CLIENT_MAX_BODY_SIZE: ${NGINX_CLIENT_MAX_BODY_SIZE:-100M} NGINX_KEEPALIVE_TIMEOUT: ${NGINX_KEEPALIVE_TIMEOUT:-65} NGINX_PROXY_READ_TIMEOUT: ${NGINX_PROXY_READ_TIMEOUT:-3600s} NGINX_PROXY_SEND_TIMEOUT: ${NGINX_PROXY_SEND_TIMEOUT:-3600s} NGINX_ENABLE_CERTBOT_CHALLENGE: ${NGINX_ENABLE_CERTBOT_CHALLENGE:-false} CERTBOT_EMAIL: ${CERTBOT_EMAIL:-your_email@example.com} CERTBOT_DOMAIN: ${CERTBOT_DOMAIN:-your_domain.com} CERTBOT_OPTIONS: ${CERTBOT_OPTIONS:-} SSRF_HTTP_PORT: ${SSRF_HTTP_PORT:-3128} SSRF_COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid} SSRF_REVERSE_PROXY_PORT: ${SSRF_REVERSE_PROXY_PORT:-8194} SSRF_SANDBOX_HOST: ${SSRF_SANDBOX_HOST:-sandbox} SSRF_DEFAULT_TIME_OUT: ${SSRF_DEFAULT_TIME_OUT:-5} SSRF_DEFAULT_CONNECT_TIME_OUT: ${SSRF_DEFAULT_CONNECT_TIME_OUT:-5} SSRF_DEFAULT_READ_TIME_OUT: ${SSRF_DEFAULT_READ_TIME_OUT:-5} SSRF_DEFAULT_WRITE_TIME_OUT: ${SSRF_DEFAULT_WRITE_TIME_OUT:-5} EXPOSE_NGINX_PORT: ${EXPOSE_NGINX_PORT:-80} EXPOSE_NGINX_SSL_PORT: ${EXPOSE_NGINX_SSL_PORT:-443} POSITION_TOOL_PINS: ${POSITION_TOOL_PINS:-} POSITION_TOOL_INCLUDES: ${POSITION_TOOL_INCLUDES:-} POSITION_TOOL_EXCLUDES: ${POSITION_TOOL_EXCLUDES:-} POSITION_PROVIDER_PINS: ${POSITION_PROVIDER_PINS:-} POSITION_PROVIDER_INCLUDES: ${POSITION_PROVIDER_INCLUDES:-} POSITION_PROVIDER_EXCLUDES: ${POSITION_PROVIDER_EXCLUDES:-} CSP_WHITELIST: ${CSP_WHITELIST:-} CREATE_TIDB_SERVICE_JOB_ENABLED: ${CREATE_TIDB_SERVICE_JOB_ENABLED:-false} MAX_SUBMIT_COUNT: ${MAX_SUBMIT_COUNT:-100} TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-10} DB_PLUGIN_DATABASE: ${DB_PLUGIN_DATABASE:-dify_plugin} EXPOSE_PLUGIN_DAEMON_PORT: ${EXPOSE_PLUGIN_DAEMON_PORT:-5002} PLUGIN_DAEMON_PORT: ${PLUGIN_DAEMON_PORT:-5002} PLUGIN_DAEMON_KEY: ${PLUGIN_DAEMON_KEY:-lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi} PLUGIN_DAEMON_URL: ${PLUGIN_DAEMON_URL:-http://plugin_daemon:5002} PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} PLUGIN_PPROF_ENABLED: ${PLUGIN_PPROF_ENABLED:-false} PLUGIN_DEBUGGING_HOST: ${PLUGIN_DEBUGGING_HOST:-0.0.0.0} PLUGIN_DEBUGGING_PORT: ${PLUGIN_DEBUGGING_PORT:-5003} EXPOSE_PLUGIN_DEBUGGING_HOST: ${EXPOSE_PLUGIN_DEBUGGING_HOST:-localhost} EXPOSE_PLUGIN_DEBUGGING_PORT: ${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003} PLUGIN_DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} PLUGIN_DIFY_INNER_API_URL: ${PLUGIN_DIFY_INNER_API_URL:-http://api:5001} ENDPOINT_URL_TEMPLATE: ${ENDPOINT_URL_TEMPLATE:-http://localhost/e/{hook_id}} MARKETPLACE_ENABLED: ${MARKETPLACE_ENABLED:-true} MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-https://marketplace.dify.ai} FORCE_VERIFYING_SIGNATURE: ${FORCE_VERIFYING_SIGNATURE:-true} PLUGIN_STDIO_BUFFER_SIZE: ${PLUGIN_STDIO_BUFFER_SIZE:-1024} PLUGIN_STDIO_MAX_BUFFER_SIZE: ${PLUGIN_STDIO_MAX_BUFFER_SIZE:-5242880} PLUGIN_PYTHON_ENV_INIT_TIMEOUT: ${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120} PLUGIN_MAX_EXECUTION_TIMEOUT: ${PLUGIN_MAX_EXECUTION_TIMEOUT:-600} PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} PLUGIN_STORAGE_TYPE: ${PLUGIN_STORAGE_TYPE:-local} PLUGIN_STORAGE_LOCAL_ROOT: ${PLUGIN_STORAGE_LOCAL_ROOT:-/app/storage} PLUGIN_WORKING_PATH: ${PLUGIN_WORKING_PATH:-/app/storage/cwd} PLUGIN_INSTALLED_PATH: ${PLUGIN_INSTALLED_PATH:-plugin} PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} PLUGIN_S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false} PLUGIN_S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} PLUGIN_S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} PLUGIN_S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} PLUGIN_AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} PLUGIN_AWS_SECRET_KEY: ${PLUGIN_AWS_SECRET_KEY:-} PLUGIN_AWS_REGION: ${PLUGIN_AWS_REGION:-} PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME: ${PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME:-} PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING: ${PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING:-} PLUGIN_TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} PLUGIN_TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} PLUGIN_TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} PLUGIN_ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} PLUGIN_ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} PLUGIN_ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} PLUGIN_ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} PLUGIN_VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} PLUGIN_VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} PLUGIN_VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} PLUGIN_VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} ENABLE_OTEL: ${ENABLE_OTEL:-false} OTLP_TRACE_ENDPOINT: ${OTLP_TRACE_ENDPOINT:-} OTLP_METRIC_ENDPOINT: ${OTLP_METRIC_ENDPOINT:-} OTLP_BASE_ENDPOINT: ${OTLP_BASE_ENDPOINT:-http://localhost:4318} OTLP_API_KEY: ${OTLP_API_KEY:-} OTEL_EXPORTER_OTLP_PROTOCOL: ${OTEL_EXPORTER_OTLP_PROTOCOL:-} OTEL_EXPORTER_TYPE: ${OTEL_EXPORTER_TYPE:-otlp} OTEL_SAMPLING_RATE: ${OTEL_SAMPLING_RATE:-0.1} OTEL_BATCH_EXPORT_SCHEDULE_DELAY: ${OTEL_BATCH_EXPORT_SCHEDULE_DELAY:-5000} OTEL_MAX_QUEUE_SIZE: ${OTEL_MAX_QUEUE_SIZE:-2048} OTEL_MAX_EXPORT_BATCH_SIZE: ${OTEL_MAX_EXPORT_BATCH_SIZE:-512} OTEL_METRIC_EXPORT_INTERVAL: ${OTEL_METRIC_EXPORT_INTERVAL:-60000} OTEL_BATCH_EXPORT_TIMEOUT: ${OTEL_BATCH_EXPORT_TIMEOUT:-10000} OTEL_METRIC_EXPORT_TIMEOUT: ${OTEL_METRIC_EXPORT_TIMEOUT:-30000} ALLOW_EMBED: ${ALLOW_EMBED:-false} QUEUE_MONITOR_THRESHOLD: ${QUEUE_MONITOR_THRESHOLD:-200} QUEUE_MONITOR_ALERT_EMAILS: ${QUEUE_MONITOR_ALERT_EMAILS:-} QUEUE_MONITOR_INTERVAL: ${QUEUE_MONITOR_INTERVAL:-30} SWAGGER_UI_ENABLED: ${SWAGGER_UI_ENABLED:-true} SWAGGER_UI_PATH: ${SWAGGER_UI_PATH:-/swagger-ui.html} ENABLE_CLEAN_EMBEDDING_CACHE_TASK: ${ENABLE_CLEAN_EMBEDDING_CACHE_TASK:-false} ENABLE_CLEAN_UNUSED_DATASETS_TASK: ${ENABLE_CLEAN_UNUSED_DATASETS_TASK:-false} ENABLE_CREATE_TIDB_SERVERLESS_TASK: ${ENABLE_CREATE_TIDB_SERVERLESS_TASK:-false} ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK: ${ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK:-false} ENABLE_CLEAN_MESSAGES: ${ENABLE_CLEAN_MESSAGES:-false} ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK: ${ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK:-false} ENABLE_DATASETS_QUEUE_MONITOR: ${ENABLE_DATASETS_QUEUE_MONITOR:-false} ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK: ${ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK:-true} services: # API service api: image: langgenius/dify-api:1.8.0 restart: always environment: # Use the shared environment variables. <<: *shared-api-worker-env # Startup mode, 'api' starts the API server. MODE: api SENTRY_DSN: ${API_SENTRY_DSN:-} SENTRY_TRACES_SAMPLE_RATE: ${API_SENTRY_TRACES_SAMPLE_RATE:-1.0} SENTRY_PROFILES_SAMPLE_RATE: ${API_SENTRY_PROFILES_SAMPLE_RATE:-1.0} PLUGIN_REMOTE_INSTALL_HOST: ${EXPOSE_PLUGIN_DEBUGGING_HOST:-localhost} PLUGIN_REMOTE_INSTALL_PORT: ${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003} PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} depends_on: db: condition: service_healthy redis: condition: service_started volumes: # Mount the storage directory to the container, for storing user files. - ./volumes/app/storage:/app/api/storage networks: - ssrf_proxy_network - default # worker service # The Celery worker for processing the queue. worker: image: langgenius/dify-api:1.8.0 restart: always environment: # Use the shared environment variables. <<: *shared-api-worker-env # Startup mode, 'worker' starts the Celery worker for processing the queue. MODE: worker SENTRY_DSN: ${API_SENTRY_DSN:-} SENTRY_TRACES_SAMPLE_RATE: ${API_SENTRY_TRACES_SAMPLE_RATE:-1.0} SENTRY_PROFILES_SAMPLE_RATE: ${API_SENTRY_PROFILES_SAMPLE_RATE:-1.0} PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} depends_on: db: condition: service_healthy redis: condition: service_started volumes: # Mount the storage directory to the container, for storing user files. - ./volumes/app/storage:/app/api/storage networks: - ssrf_proxy_network - default # worker_beat service # Celery beat for scheduling periodic tasks. worker_beat: image: langgenius/dify-api:1.8.0 restart: always environment: # Use the shared environment variables. <<: *shared-api-worker-env # Startup mode, 'worker_beat' starts the Celery beat for scheduling periodic tasks. MODE: beat depends_on: db: condition: service_healthy redis: condition: service_started networks: - ssrf_proxy_network - default # Frontend web application. web: image: langgenius/dify-web:1.8.0 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} APP_API_URL: ${APP_API_URL:-} SENTRY_DSN: ${WEB_SENTRY_DSN:-} NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0} TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} CSP_WHITELIST: ${CSP_WHITELIST:-} ALLOW_EMBED: ${ALLOW_EMBED:-false} ALLOW_UNSAFE_DATA_SCHEME: ${ALLOW_UNSAFE_DATA_SCHEME:-false} MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-https://marketplace.dify.ai} MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai} TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-} INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-} PM2_INSTANCES: ${PM2_INSTANCES:-2} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} MAX_PARALLEL_LIMIT: ${MAX_PARALLEL_LIMIT:-10} MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-99} MAX_TREE_DEPTH: ${MAX_TREE_DEPTH:-50} ENABLE_WEBSITE_JINAREADER: ${ENABLE_WEBSITE_JINAREADER:-true} ENABLE_WEBSITE_FIRECRAWL: ${ENABLE_WEBSITE_FIRECRAWL:-true} ENABLE_WEBSITE_WATERCRAWL: ${ENABLE_WEBSITE_WATERCRAWL:-true} # The postgres database. db: image: postgres:15-alpine restart: always environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-difyai123456} POSTGRES_DB: ${POSTGRES_DB:-dify} PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata} command: > postgres -c 'max_connections=${POSTGRES_MAX_CONNECTIONS:-100}' -c 'shared_buffers=${POSTGRES_SHARED_BUFFERS:-128MB}' -c 'work_mem=${POSTGRES_WORK_MEM:-4MB}' -c 'maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-64MB}' -c 'effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4096MB}' volumes: - ./volumes/db/data:/var/lib/postgresql/data healthcheck: test: [ 'CMD', 'pg_isready', '-h', 'db', '-U', '${PGUSER:-postgres}', '-d', '${POSTGRES_DB:-dify}' ] interval: 1s timeout: 3s retries: 60 # The redis cache. redis: image: redis:6-alpine restart: always environment: REDISCLI_AUTH: ${REDIS_PASSWORD:-difyai123456} volumes: # Mount the redis data directory to the container. - ./volumes/redis/data:/data # Set the redis password when startup redis server. command: redis-server --requirepass ${REDIS_PASSWORD:-difyai123456} healthcheck: test: [ 'CMD', 'redis-cli', 'ping' ] # The DifySandbox sandbox: image: langgenius/dify-sandbox:0.2.12 restart: always environment: # The DifySandbox configurations # Make sure you are changing this key for your deployment with a strong key. # You can generate a strong key using `openssl rand -base64 42`. API_KEY: ${SANDBOX_API_KEY:-dify-sandbox} GIN_MODE: ${SANDBOX_GIN_MODE:-release} WORKER_TIMEOUT: ${SANDBOX_WORKER_TIMEOUT:-15} ENABLE_NETWORK: ${SANDBOX_ENABLE_NETWORK:-true} HTTP_PROXY: ${SANDBOX_HTTP_PROXY:-http://ssrf_proxy:3128} HTTPS_PROXY: ${SANDBOX_HTTPS_PROXY:-http://ssrf_proxy:3128} SANDBOX_PORT: ${SANDBOX_PORT:-8194} PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} volumes: - ./volumes/sandbox/dependencies:/dependencies - ./volumes/sandbox/conf:/conf healthcheck: test: [ 'CMD', 'curl', '-f', 'http://localhost:8194/health' ] networks: - ssrf_proxy_network # plugin daemon plugin_daemon: image: langgenius/dify-plugin-daemon:0.2.0-local restart: always environment: # Use the shared environment variables. <<: *shared-api-worker-env DB_DATABASE: ${DB_PLUGIN_DATABASE:-dify_plugin} SERVER_PORT: ${PLUGIN_DAEMON_PORT:-5002} SERVER_KEY: ${PLUGIN_DAEMON_KEY:-lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi} MAX_PLUGIN_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} PPROF_ENABLED: ${PLUGIN_PPROF_ENABLED:-false} DIFY_INNER_API_URL: ${PLUGIN_DIFY_INNER_API_URL:-http://api:5001} DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} PLUGIN_REMOTE_INSTALLING_HOST: ${PLUGIN_DEBUGGING_HOST:-0.0.0.0} PLUGIN_REMOTE_INSTALLING_PORT: ${PLUGIN_DEBUGGING_PORT:-5003} PLUGIN_WORKING_PATH: ${PLUGIN_WORKING_PATH:-/app/storage/cwd} FORCE_VERIFYING_SIGNATURE: ${FORCE_VERIFYING_SIGNATURE:-true} PYTHON_ENV_INIT_TIMEOUT: ${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120} PLUGIN_MAX_EXECUTION_TIMEOUT: ${PLUGIN_MAX_EXECUTION_TIMEOUT:-600} PLUGIN_STDIO_BUFFER_SIZE: ${PLUGIN_STDIO_BUFFER_SIZE:-1024} PLUGIN_STDIO_MAX_BUFFER_SIZE: ${PLUGIN_STDIO_MAX_BUFFER_SIZE:-5242880} PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} PLUGIN_STORAGE_TYPE: ${PLUGIN_STORAGE_TYPE:-local} PLUGIN_STORAGE_LOCAL_ROOT: ${PLUGIN_STORAGE_LOCAL_ROOT:-/app/storage} PLUGIN_INSTALLED_PATH: ${PLUGIN_INSTALLED_PATH:-plugin} PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} AWS_SECRET_KEY: ${PLUGIN_AWS_SECRET_KEY:-} AWS_REGION: ${PLUGIN_AWS_REGION:-} AZURE_BLOB_STORAGE_CONNECTION_STRING: ${PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING:-} AZURE_BLOB_STORAGE_CONTAINER_NAME: ${PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME:-} TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} ports: - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" volumes: - ./volumes/plugin_daemon:/app/storage depends_on: db: condition: service_healthy # ssrf_proxy server # for more information, please refer to # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F ssrf_proxy: image: ubuntu/squid:latest restart: always volumes: - ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template - ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh entrypoint: [ 'sh', '-c', "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh" ] environment: # pls clearly modify the squid env vars to fit your network environment. HTTP_PORT: ${SSRF_HTTP_PORT:-3128} COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid} REVERSE_PROXY_PORT: ${SSRF_REVERSE_PROXY_PORT:-8194} SANDBOX_HOST: ${SSRF_SANDBOX_HOST:-sandbox} SANDBOX_PORT: ${SANDBOX_PORT:-8194} networks: - ssrf_proxy_network - default # Certbot service # use `docker-compose --profile certbot up` to start the certbot service. certbot: image: certbot/certbot profiles: - certbot volumes: - ./volumes/certbot/conf:/etc/letsencrypt - ./volumes/certbot/www:/var/www/html - ./volumes/certbot/logs:/var/log/letsencrypt - ./volumes/certbot/conf/live:/etc/letsencrypt/live - ./certbot/update-cert.template.txt:/update-cert.template.txt - ./certbot/docker-entrypoint.sh:/docker-entrypoint.sh environment: - CERTBOT_EMAIL=${CERTBOT_EMAIL} - CERTBOT_DOMAIN=${CERTBOT_DOMAIN} - CERTBOT_OPTIONS=${CERTBOT_OPTIONS:-} entrypoint: [ '/docker-entrypoint.sh' ] command: [ 'tail', '-f', '/dev/null' ] # The nginx reverse proxy. # used for reverse proxying the API service and Web service. nginx: image: nginx:latest restart: always volumes: - ./nginx/nginx.conf.template:/etc/nginx/nginx.conf.template - ./nginx/proxy.conf.template:/etc/nginx/proxy.conf.template - ./nginx/https.conf.template:/etc/nginx/https.conf.template - ./nginx/conf.d:/etc/nginx/conf.d - ./nginx/docker-entrypoint.sh:/docker-entrypoint-mount.sh - ./nginx/ssl:/etc/ssl # cert dir (legacy) - ./volumes/certbot/conf/live:/etc/letsencrypt/live # cert dir (with certbot container) - ./volumes/certbot/conf:/etc/letsencrypt - ./volumes/certbot/www:/var/www/html entrypoint: [ 'sh', '-c', "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh" ] environment: NGINX_SERVER_NAME: ${NGINX_SERVER_NAME:-_} NGINX_HTTPS_ENABLED: ${NGINX_HTTPS_ENABLED:-false} NGINX_SSL_PORT: ${NGINX_SSL_PORT:-443} NGINX_PORT: ${NGINX_PORT:-80} # You're required to add your own SSL certificates/keys to the `./nginx/ssl` directory # and modify the env vars below in .env if HTTPS_ENABLED is true. NGINX_SSL_CERT_FILENAME: ${NGINX_SSL_CERT_FILENAME:-dify.crt} NGINX_SSL_CERT_KEY_FILENAME: ${NGINX_SSL_CERT_KEY_FILENAME:-dify.key} NGINX_SSL_PROTOCOLS: ${NGINX_SSL_PROTOCOLS:-TLSv1.1 TLSv1.2 TLSv1.3} NGINX_WORKER_PROCESSES: ${NGINX_WORKER_PROCESSES:-auto} NGINX_CLIENT_MAX_BODY_SIZE: ${NGINX_CLIENT_MAX_BODY_SIZE:-100M} NGINX_KEEPALIVE_TIMEOUT: ${NGINX_KEEPALIVE_TIMEOUT:-65} NGINX_PROXY_READ_TIMEOUT: ${NGINX_PROXY_READ_TIMEOUT:-3600s} NGINX_PROXY_SEND_TIMEOUT: ${NGINX_PROXY_SEND_TIMEOUT:-3600s} NGINX_ENABLE_CERTBOT_CHALLENGE: ${NGINX_ENABLE_CERTBOT_CHALLENGE:-false} CERTBOT_DOMAIN: ${CERTBOT_DOMAIN:-} depends_on: - api - web ports: - '${EXPOSE_NGINX_PORT:-80}:${NGINX_PORT:-80}' - '${EXPOSE_NGINX_SSL_PORT:-443}:${NGINX_SSL_PORT:-443}' # The Weaviate vector store. weaviate: image: semitechnologies/weaviate:1.19.0 profiles: - '' - weaviate restart: always volumes: # Mount the Weaviate data directory to the con tainer. - ./volumes/weaviate:/var/lib/weaviate environment: # The Weaviate configurations # You can refer to the [Weaviate](https://weaviate.io/developers/weaviate/config-refs/env-vars) documentation for more information. PERSISTENCE_DATA_PATH: ${WEAVIATE_PERSISTENCE_DATA_PATH:-/var/lib/weaviate} QUERY_DEFAULTS_LIMIT: ${WEAVIATE_QUERY_DEFAULTS_LIMIT:-25} AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: ${WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED:-false} DEFAULT_VECTORIZER_MODULE: ${WEAVIATE_DEFAULT_VECTORIZER_MODULE:-none} CLUSTER_HOSTNAME: ${WEAVIATE_CLUSTER_HOSTNAME:-node1} AUTHENTICATION_APIKEY_ENABLED: ${WEAVIATE_AUTHENTICATION_APIKEY_ENABLED:-true} AUTHENTICATION_APIKEY_ALLOWED_KEYS: ${WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS:-WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih} AUTHENTICATION_APIKEY_USERS: ${WEAVIATE_AUTHENTICATION_APIKEY_USERS:-hello@dify.ai} AUTHORIZATION_ADMINLIST_ENABLED: ${WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED:-true} AUTHORIZATION_ADMINLIST_USERS: ${WEAVIATE_AUTHORIZATION_ADMINLIST_USERS:-hello@dify.ai} # Qdrant vector store. # (if used, you need to set VECTOR_STORE to qdrant in the api & worker service.) qdrant: image: langgenius/qdrant:v1.7.3 profiles: - qdrant restart: always volumes: - ./volumes/qdrant:/qdrant/storage environment: QDRANT_API_KEY: ${QDRANT_API_KEY:-difyai123456} # The Couchbase vector store. couchbase-server: build: ./couchbase-server profiles: - couchbase restart: always environment: - CLUSTER_NAME=dify_search - COUCHBASE_ADMINISTRATOR_USERNAME=${COUCHBASE_USER:-Administrator} - COUCHBASE_ADMINISTRATOR_PASSWORD=${COUCHBASE_PASSWORD:-password} - COUCHBASE_BUCKET=${COUCHBASE_BUCKET_NAME:-Embeddings} - COUCHBASE_BUCKET_RAMSIZE=512 - COUCHBASE_RAM_SIZE=2048 - COUCHBASE_EVENTING_RAM_SIZE=512 - COUCHBASE_INDEX_RAM_SIZE=512 - COUCHBASE_FTS_RAM_SIZE=1024 hostname: couchbase-server container_name: couchbase-server working_dir: /opt/couchbase stdin_open: true tty: true entrypoint: [ "" ] command: sh -c "/opt/couchbase/init/init-cbserver.sh" volumes: - ./volumes/couchbase/data:/opt/couchbase/var/lib/couchbase/data healthcheck: # ensure bucket was created before proceeding test: [ "CMD-SHELL", "curl -s -f -u Administrator:password http://localhost:8091/pools/default/buckets | grep -q '\\[{' || exit 1" ] interval: 10s retries: 10 start_period: 30s timeout: 10s # The pgvector vector database. pgvector: image: pgvector/pgvector:pg16 profiles: - pgvector restart: always environment: PGUSER: ${PGVECTOR_PGUSER:-postgres} # The password for the default postgres user. POSTGRES_PASSWORD: ${PGVECTOR_POSTGRES_PASSWORD:-difyai123456} # The name of the default postgres database. POSTGRES_DB: ${PGVECTOR_POSTGRES_DB:-dify} # postgres data directory PGDATA: ${PGVECTOR_PGDATA:-/var/lib/postgresql/data/pgdata} # pg_bigm module for full text search PG_BIGM: ${PGVECTOR_PG_BIGM:-false} PG_BIGM_VERSION: ${PGVECTOR_PG_BIGM_VERSION:-1.2-20240606} volumes: - ./volumes/pgvector/data:/var/lib/postgresql/data - ./pgvector/docker-entrypoint.sh:/docker-entrypoint.sh entrypoint: [ '/docker-entrypoint.sh' ] healthcheck: test: [ 'CMD', 'pg_isready' ] interval: 1s timeout: 3s retries: 30 # get image from https://www.vastdata.com.cn/ vastbase: image: vastdata/vastbase-vector profiles: - vastbase restart: always environment: - VB_DBCOMPATIBILITY=PG - VB_DB=dify - VB_USERNAME=dify - VB_PASSWORD=Difyai123456 ports: - '5434:5432' volumes: - ./vastbase/lic:/home/vastbase/vastbase/lic - ./vastbase/data:/home/vastbase/data - ./vastbase/backup:/home/vastbase/backup - ./vastbase/backup_log:/home/vastbase/backup_log healthcheck: test: [ 'CMD', 'pg_isready' ] interval: 1s timeout: 3s retries: 30 # pgvecto-rs vector store pgvecto-rs: image: tensorchord/pgvecto-rs:pg16-v0.3.0 profiles: - pgvecto-rs restart: always environment: PGUSER: ${PGVECTOR_PGUSER:-postgres} # The password for the default postgres user. POSTGRES_PASSWORD: ${PGVECTOR_POSTGRES_PASSWORD:-difyai123456} # The name of the default postgres database. POSTGRES_DB: ${PGVECTOR_POSTGRES_DB:-dify} # postgres data directory PGDATA: ${PGVECTOR_PGDATA:-/var/lib/postgresql/data/pgdata} volumes: - ./volumes/pgvecto_rs/data:/var/lib/postgresql/data healthcheck: test: [ 'CMD', 'pg_isready' ] interval: 1s timeout: 3s retries: 30 # Chroma vector database chroma: image: ghcr.io/chroma-core/chroma:0.5.20 profiles: - chroma restart: always volumes: - ./volumes/chroma:/chroma/chroma environment: CHROMA_SERVER_AUTHN_CREDENTIALS: ${CHROMA_SERVER_AUTHN_CREDENTIALS:-difyai123456} CHROMA_SERVER_AUTHN_PROVIDER: ${CHROMA_SERVER_AUTHN_PROVIDER:-chromadb.auth.token_authn.TokenAuthenticationServerProvider} IS_PERSISTENT: ${CHROMA_IS_PERSISTENT:-TRUE} # OceanBase vector database oceanbase: image: oceanbase/oceanbase-ce:4.3.5-lts container_name: oceanbase profiles: - oceanbase restart: always volumes: - ./volumes/oceanbase/data:/root/ob - ./volumes/oceanbase/conf:/root/.obd/cluster - ./volumes/oceanbase/init.d:/root/boot/init.d environment: OB_MEMORY_LIMIT: ${OCEANBASE_MEMORY_LIMIT:-6G} OB_SYS_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai} OB_SERVER_IP: 127.0.0.1 MODE: mini ports: - "${OCEANBASE_VECTOR_PORT:-2881}:2881" healthcheck: test: [ 'CMD-SHELL', 'obclient -h127.0.0.1 -P2881 -uroot@test -p$${OB_TENANT_PASSWORD} -e "SELECT 1;"' ] interval: 10s retries: 30 start_period: 30s timeout: 10s # Oracle vector database oracle: image: container-registry.oracle.com/database/free:latest profiles: - oracle restart: always volumes: - source: oradata type: volume target: /opt/oracle/oradata - ./startupscripts:/opt/oracle/scripts/startup environment: ORACLE_PWD: ${ORACLE_PWD:-Dify123456} ORACLE_CHARACTERSET: ${ORACLE_CHARACTERSET:-AL32UTF8} # Milvus vector database services etcd: container_name: milvus-etcd image: quay.io/coreos/etcd:v3.5.5 profiles: - milvus environment: ETCD_AUTO_COMPACTION_MODE: ${ETCD_AUTO_COMPACTION_MODE:-revision} ETCD_AUTO_COMPACTION_RETENTION: ${ETCD_AUTO_COMPACTION_RETENTION:-1000} ETCD_QUOTA_BACKEND_BYTES: ${ETCD_QUOTA_BACKEND_BYTES:-4294967296} ETCD_SNAPSHOT_COUNT: ${ETCD_SNAPSHOT_COUNT:-50000} volumes: - ./volumes/milvus/etcd:/etcd command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd healthcheck: test: [ 'CMD', 'etcdctl', 'endpoint', 'health' ] interval: 30s timeout: 20s retries: 3 networks: - milvus minio: container_name: milvus-minio image: minio/minio:RELEASE.2023-03-20T20-16-18Z profiles: - milvus environment: MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin} volumes: - ./volumes/milvus/minio:/minio_data command: minio server /minio_data --console-address ":9001" healthcheck: test: [ 'CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live' ] interval: 30s timeout: 20s retries: 3 networks: - milvus milvus-standalone: container_name: milvus-standalone image: milvusdb/milvus:v2.5.15 profiles: - milvus command: [ 'milvus', 'run', 'standalone' ] environment: ETCD_ENDPOINTS: ${ETCD_ENDPOINTS:-etcd:2379} MINIO_ADDRESS: ${MINIO_ADDRESS:-minio:9000} common.security.authorizationEnabled: ${MILVUS_AUTHORIZATION_ENABLED:-true} volumes: - ./volumes/milvus/milvus:/var/lib/milvus healthcheck: test: [ 'CMD', 'curl', '-f', 'http://localhost:9091/healthz' ] interval: 30s start_period: 90s timeout: 20s retries: 3 depends_on: - etcd - minio ports: - 19530:19530 - 9091:9091 networks: - milvus # Opensearch vector database opensearch: container_name: opensearch image: opensearchproject/opensearch:latest profiles: - opensearch environment: discovery.type: ${OPENSEARCH_DISCOVERY_TYPE:-single-node} bootstrap.memory_lock: ${OPENSEARCH_BOOTSTRAP_MEMORY_LOCK:-true} OPENSEARCH_JAVA_OPTS: -Xms${OPENSEARCH_JAVA_OPTS_MIN:-512m} -Xmx${OPENSEARCH_JAVA_OPTS_MAX:-1024m} OPENSEARCH_INITIAL_ADMIN_PASSWORD: ${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-Qazwsxedc!@#123} ulimits: memlock: soft: ${OPENSEARCH_MEMLOCK_SOFT:--1} hard: ${OPENSEARCH_MEMLOCK_HARD:--1} nofile: soft: ${OPENSEARCH_NOFILE_SOFT:-65536} hard: ${OPENSEARCH_NOFILE_HARD:-65536} volumes: - ./volumes/opensearch/data:/usr/share/opensearch/data networks: - opensearch-net opensearch-dashboards: container_name: opensearch-dashboards image: opensearchproject/opensearch-dashboards:latest profiles: - opensearch environment: OPENSEARCH_HOSTS: '["https://opensearch:9200"]' volumes: - ./volumes/opensearch/opensearch_dashboards.yml:/usr/share/opensearch-dashboards/config/opensearch_dashboards.yml networks: - opensearch-net depends_on: - opensearch # opengauss vector database. opengauss: image: opengauss/opengauss:7.0.0-RC1 profiles: - opengauss privileged: true restart: always environment: GS_USERNAME: ${OPENGAUSS_USER:-postgres} GS_PASSWORD: ${OPENGAUSS_PASSWORD:-Dify@123} GS_PORT: ${OPENGAUSS_PORT:-6600} GS_DB: ${OPENGAUSS_DATABASE:-dify} volumes: - ./volumes/opengauss/data:/var/lib/opengauss/data healthcheck: test: [ "CMD-SHELL", "netstat -lntp | grep tcp6 > /dev/null 2>&1" ] interval: 10s timeout: 10s retries: 10 ports: - ${OPENGAUSS_PORT:-6600}:${OPENGAUSS_PORT:-6600} # MyScale vector database myscale: container_name: myscale image: myscale/myscaledb:1.6.4 profiles: - myscale restart: always tty: true volumes: - ./volumes/myscale/data:/var/lib/clickhouse - ./volumes/myscale/log:/var/log/clickhouse-server - ./volumes/myscale/config/users.d/custom_users_config.xml:/etc/clickhouse-server/users.d/custom_users_config.xml ports: - ${MYSCALE_PORT:-8123}:${MYSCALE_PORT:-8123} # Matrixone vector store. matrixone: hostname: matrixone image: matrixorigin/matrixone:2.1.1 profiles: - matrixone restart: always volumes: - ./volumes/matrixone/data:/mo-data ports: - ${MATRIXONE_PORT:-6001}:${MATRIXONE_PORT:-6001} # https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html # https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-prod-prerequisites elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.14.3 container_name: elasticsearch profiles: - elasticsearch - elasticsearch-ja restart: always volumes: - ./elasticsearch/docker-entrypoint.sh:/docker-entrypoint-mount.sh - dify_es01_data:/usr/share/elasticsearch/data environment: ELASTIC_PASSWORD: ${ELASTICSEARCH_PASSWORD:-elastic} VECTOR_STORE: ${VECTOR_STORE:-} cluster.name: dify-es-cluster node.name: dify-es0 discovery.type: single-node xpack.license.self_generated.type: basic xpack.security.enabled: 'true' xpack.security.enrollment.enabled: 'false' xpack.security.http.ssl.enabled: 'false' ports: - ${ELASTICSEARCH_PORT:-9200}:9200 deploy: resources: limits: memory: 2g entrypoint: [ 'sh', '-c', "sh /docker-entrypoint-mount.sh" ] healthcheck: test: [ 'CMD', 'curl', '-s', 'http://localhost:9200/_cluster/health?pretty' ] interval: 30s timeout: 10s retries: 50 # https://www.elastic.co/guide/en/kibana/current/docker.html # https://www.elastic.co/guide/en/kibana/current/settings.html kibana: image: docker.elastic.co/kibana/kibana:8.14.3 container_name: kibana profiles: - elasticsearch depends_on: - elasticsearch restart: always environment: XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY: d1a66dfd-c4d3-4a0a-8290-2abcb83ab3aa NO_PROXY: localhost,127.0.0.1,elasticsearch,kibana XPACK_SECURITY_ENABLED: 'true' XPACK_SECURITY_ENROLLMENT_ENABLED: 'false' XPACK_SECURITY_HTTP_SSL_ENABLED: 'false' XPACK_FLEET_ISAIRGAPPED: 'true' I18N_LOCALE: zh-CN SERVER_PORT: '5601' ELASTICSEARCH_HOSTS: http://elasticsearch:9200 ports: - ${KIBANA_PORT:-5601}:5601 healthcheck: test: [ 'CMD-SHELL', 'curl -s http://localhost:5601 >/dev/null || exit 1' ] interval: 30s timeout: 10s retries: 3 # unstructured . # (if used, you need to set ETL_TYPE to Unstructured in the api & worker service.) unstructured: image: downloads.unstructured.io/unstructured-io/unstructured-api:latest profiles: - unstructured restart: always volumes: - ./volumes/unstructured:/app/data networks: # create a network between sandbox, api and ssrf_proxy, and can not access outside. ssrf_proxy_network: driver: bridge internal: true milvus: driver: bridge opensearch-net: driver: bridge internal: true volumes: oradata: dify_es01_data: 其实一个一个分析,它的结构和我们之前部署的docker项目是类似的,只不过一次性起了很多个容器。 我们这边可以先不管它,直接暴力拉起 docker compose up -d 我这边在公司,本来以为很快能拉好镜像,结果网非常的慢,我等不及就去吃中饭了,等到吃饭回来发现已经拉好了。 在这期间,如果决定要本地使用,不调用云端API的话,我们可以先把Ollama和本地的模型先下载起来。 安装Ollama(可选) 其实这个也可以用docker来装,但是既然给了安装版,我们就直接到官网下载即可。 什么是Ollama? Ollama 是一个便于本地部署和运行大型语言模型(Large Language Models, LLMs)的工具。使用通俗的语言来说,如果你想在自己的电脑上运行如 GPT这样的大型人工智能模型,而不是通过互联网连接到它们,那么 Ollama 是一个实现这一目标的工具。 Ollama支持非常多的开源模型,比如: 更多支持的模型可以看这边:https://ollama.com/library 当然它还支持自定义模型,这边就不深入了,有兴趣的可以研究:https://github.com/ollama/ollama 装好之后,命令行运行: ollama -v 查看到版本号说明安装好了。 接下来,比如我们下载一个llama3 8B的模型: ollama run llama3 这边可能需要比较长的时间,取决于你的网速。 下载好了就可以进行交互了: /? 可以查看帮助: /bye 可以退出 其实现在已经搞定了,你可以让它帮你干活了,但是每次都要用命令行,有些人觉得不方便,而且也不能给别人用,下面我们就给它搞一个图形化界面,扩展一下功能,也能让局域网里的其他小伙伴也用上你的这个模型(注意多人同时访问会导致机器负载飙升,具体取决于你机器的性能和模型参数的大小) 配置Dify 等所有容器都拉取好了,我们就可以通过在浏览器地址栏输入: 127.0.0.1 来访问Dify了。 第一次登录会让你注册一个管理员账号,然后我们就可以登录了, 后台是这个样子的, 我们先来到设置,配置一下大模型, 这边可以看到很多的模型供应商, 我们本地部署,就用ollama, 点击安装, 安装好了之后,来添加模型, 这里的基础URL,填写: http://host.docker.internal:11434 模型名称的话,看看我们ollama里下载了什么模型,打开终端,输入 ollama list 可以看到我这边下载了很多模型, 我们就选择gpt最近刚开源的 gpt-oss:20b 其他信息填写如图, 这边是支持函数调用的,选择是,不支持视觉, 这样我们就添加好一个模型啦。 如果你的本地电脑算力不够,也可以选择使用云端的模型,比如DeepSeek之类的, 添加方法也很简单,下载模型供应商,然后在这边点击配置, 填入你在DeepSeek官网拿到的API Key就行了。 OpenAI、ANTHROPIC也是类似的方法,填API Key就可以。 接着,我们就来创建一个空白应用, 这边选择最简单的,创建一个聊天助手, 创建的过程中,你可以调试,这边的自定义程度会高一些, 创建好之后,可以选择发布,或者点击探索, 就可以在这边调出使用了。 当然,这只是最简单的功能,dify的魅力在于 降低AI应用开发的门槛,快速构建和部署生产级的生成式AI应用 它通过以下核心功能来实现这个目标: 简化开发流程 :Dify是一个开源的大语言模型(LLM)应用开发平台,融合了后端即服务(BaaS)和LLMOps理念,提供可视化的Prompt编排、数据集管理、RAG(检索增强生成)引擎、Agent框架等功能,使开发者无需深入底层技术也能快速搭建AI应用。 支持低代码/无代码开发 :通过直观的界面和拖拽操作,非技术人员也能参与AI应用的定义和数据运营,极大地降低了开发门槛。 灵活的模型支持 :Dify支持多种主流大语言模型(如GPT、Claude、DeepSeek等)以及开源模型,开发者可根据需求灵活选择和切换模型。 高效的RAG和Agent功能 :Dify的RAG技术通过结合外部知识库提升生成内容的准确性,而Agent框架赋予AI自主推理和任务执行能力,适用于复杂场景如智能客服、内容生成等。189.html) 企业级应用支持 :Dify提供私有化部署、API集成、多租户隔离等功能,适合企业构建内部AI网关或快速将AI能力融入现有业务,助力AI转型。 总结来说,Dify通过提供一站式、易用的开发平台,帮助开发者、企业甚至非技术人员快速实现AI应用创意,节省开发时间,专注于创新和业务需求,特别适合从原型设计到生产部署的全流程管理。 大家可以自行探索一下Dify的高级玩法,我自己也在慢慢摸索学习,之后有机会可以和大家分享!

前阵子去欧洲玩了一段时间,鸽了挺久了,今天有点时间,立马来和大家分享好玩儿的Docker项目。 1. 介绍 ezBookkeeping 是一款轻量级的个人理财应用,你可以自己架设(自托管)。它界面简洁,用起来很顺手,记账功能也强大。 这款应用的设计核心就是 简单 和 便携 。它部署起来毫不费力,上手快,而且对系统资源的占用极小。无论是微型服务器、NAS 设备,还是树莓派,都能轻松运行。 ezBookkeeping 支持全平台,对各种设备都很友好。在手机、平板和电脑上用,体验都一样流畅。它还支持 PWA(渐进式网页应用),你可以直接把它添加到手机主屏幕,像本地应用一样方便地使用。 1.1 功能特点 开源与自托管 为你提供隐私和数据控制权 轻量又快速 性能卓越,即使在低配置环境下也能流畅运行 安装简单 支持 Docker 部署 兼容 SQLite, MySQL, PostgreSQL 数据库 跨平台运行(支持 Windows, macOS, Linux) 支持多种处理器架构 (x86, amd64, ARM) 界面友好 界面已针对移动和桌面设备优化 支持 PWA,在手机上可获得类似原生应用的体验 提供深色模式 AI 赋能 支持 MCP (Model Context Protocol),可集成 AI 功能 强大的记账功能 支持两级账户和分类 可为交易添加图片附件 具备地图位置追踪功能 支持周期性(重复)交易 提供高级筛选、搜索、数据可视化和分析功能 本地化与全球化 支持多种语言和货币 自动更新汇率 支持多时区显示 可自定义日期、数字和货币格式 安全保障 支持双重身份验证 (2FA) 登录频率限制 应用锁定功能(支持 PIN 码 / WebAuthn) 数据导入/导出 支持多种格式,包括 CSV, OFX, QFX, QIF, IIF, Camt.053, MT940, GnuCash, Firefly III, Beancount 等等 简单来说,这是一个记账的项目,然后它还可以支持MCP,可以搭配大语言模型一起食用。 2. 相关地址 官方GitHub地址: (目前958个star,欢迎大家去给作者点星星!) 官方Demo: 3. 搭建环境 服务器:咕咕这边用的是 ,当然你也可以选择其他 。内存1G以上即可,硬盘当然是越大越好啦。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 4. 搭建视频(有时间补充!) 4.1 YouTube 视频地址: 4.2 哔哩哔哩 哔哩哔哩: 5. 搭建方式 5.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 5.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/ezbookkeeping cd ezbookkeeping 我们来简单修改一下作者提供的 docker-compose.yml 文件 vim docker-compose.yml 咕咕修改之后的: services: ezbookkeeping: image: mayswind/ezbookkeeping container_name: ezbookkeeping restart: unless-stopped ports: - 8082:8080 volumes: - /etc/localtime:/etc/localtime:ro - ./storage:/ezbookkeeping/storage - ./log:/ezbookkeeping/log # - ./ezbookkeeping.ini:/ezbookkeeping/conf/ezbookkeeping.ini environment: - EBK_DATABASE_TYPE=mysql - EBK_DATABASE_HOST=mysql:3306 - EBK_DATABASE_NAME=ezbookkeeping - EBK_DATABASE_USER=ezbookkeeping - EBK_DATABASE_PASSWD=ezbookkeeping - EBK_LOG_MODE=file - EBK_SECURITY_SECRET_KEY=its_should_be_a_random_string - EBK_MCP_ENABLE_MCP=true # depends_on: # mysql: # condition: service_healthy mysql: image: mysql:8.0 container_name: ezbookkeeping-mysql restart: unless-stopped volumes: - ./data:/var/lib/mysql environment: - MYSQL_DATABASE=ezbookkeeping - MYSQL_USER=ezbookkeeping - MYSQL_PASSWORD=ezbookkeeping - MYSQL_ROOT_PASSWORD=ezbookkeeping healthcheck: test: ["CMD", "mysqladmin", "ping", "-p ezbookkeeping"] retries: 3 timeout: 5s 修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 接着创建文件夹,赋予文件权限: cd /root/data/docker_data/ezbookkeeping mkdir ./{data,log,storage} chmod a+rw {log,storage} 5.3 打开服务器防火墙(非必需)并访问网页 打开防火墙的端口 8082 举例,腾讯云打开方法如下(部分服务商没有自带的面板防火墙,就不用这步操作了): 类似图中的,这边我们填 8082 ,示例填 ezbookkeeping ,确定即可(如果你在 docker-compose 文件里换了 9009 ,这边就需要填 9009 ,以此类推) 查看端口是否被占用(以 8082 为例),输入: lsof -i:8082 #查看 8082 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 5.4 启动 ezbookkeeping cd /root/data/docker_data/ezbookkeeping docker compose up -d # 注意,老版本用户用 docker-compose up -d 等待拉取好镜像,出现 done 的字样之后, 理论上我们就可以输入 http://ip:8082 访问了。 但是这边我们推荐先搞一下反向代理! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo上 6位数字的xyz续费永远都是0.99美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 6. 反向代理 6.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 8082 IP 填写: 如果 Nginx Proxy Manager 和 ezbookkeeping 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 ezbookkeeping 所在的服务器 IP 就行。 6.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:8082/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 7. 使用教程 登录你的域名之后,可以看到首页: 这个时候需要创建一个新账号: 这里的预设分类,可以选择打开: 进入后台,结构很清晰: 我们先来添加一下账户: 比如,取名就叫做“咕咕的账本”: 先来手动添加一笔试试看: 可以正常添加: MCP配置 一开始我们就说了,这个账本支持MCP。 MCP(模型上下文协议)是由 Anthropic 开发的一种开放协议,它能让 AI 模型安全地连接到外部数据源和和工具。 简单来说,MCP 就像一座桥梁,它提供了一种通用的标准,让 AI 工具能安全地获取信息和执行操作,同时确保数据安全和用户可以掌控一切。 有了 MCP 协议,你就可以用自己喜欢的 AI 工具来: 添加交易记录 :比如,直接用自然语言(就是日常说话的方式)来创建交易,或者从不同的文件格式中批量导入交易记录。 查询交易数据 :比如,让你的 AI 工具帮你分析历史交易记录。 以及做更多的事情 。 更多MCP相关介绍可以看这边:https://ezbookkeeping.mayswind.net/mcp 这边我们来看看怎么配置。 首先找到右上角的用户设置, 生成MCP令牌: 有两种形式,分别是普通的令牌和json配置: 普通: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyVG9rZW5JZCI6IjMzOTQ1NTM3NTIyOTE5OTc4MzYiLCJqdGkiOiIzNzY4ODA2Njg4MjQ1NjbWUiOiJSb3kiLCJ0eXBlIjo1LCJpYXQiOjE3NTQ5ODc3NzcsImV4cCI6MTA5NzgzNTk4MTR9.7JL_4kzKhihatpfQxIkhD6Z_ibZ_vBYtcQ_D9UCV--0 JSON配置: { "mcpServers": { "ezbookkeeping-mcp": { "type": "streamable-http", "url": "http://localhost:8080/mcp", "headers": { "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyVG9rZW5JZCI6IjMzOTQ1NTM3NTIyOTE5OTc4MzYiLCJqdGkiOiIzNzY4ODA2Njg4hbWUiOiJSb3kiLCJ0eXBlIjo1LCJpYXQiOjE3NTQ5ODc3NzcsImV4cCI6MTA5NzgzNTk4MTR9.7JL_4kzKhihatpfQxIkhD6Z_ibZ_vBYtcQ_D9UCV--0" } } } } 这边可以保存下来,下次打开就看不到了。 Cherry Studio配置 这边以Cherry Studio为例,看看MCP怎么配置。 配置情况如图: 名称 :主要用于识别,例如: ezBookkeeping 类型 :选择 streamableHttp 协议 URL :为 你的域名/mcp 请求头 : 为 Authorization=Bearer {token} 接着可以查看一下可以调用的工具: 然后为了确保大模型能正确理解什么是今天,还需要加个时间MCP,这次我们选择json导入, { "mcpServers": { "mcp-server-time": { "command": "uvx", "args": ["mcp-server-time"] } } } 这下,你应该有两个MCP服务了。 查看一下时间工具的功能: 接着,我们到Cherry Studio里新建一个助手, 选择启用这两个MCP服务: 测试一下,可以看到回答的时候,调用了MCP服务, 这边提示错误,我没有说记录到哪个账本: 还需要说明交易类别, 后续, 记录成功! 不过这次似乎没有调用时间MCP,时间似乎有问题…… 然后我尝试使用Deepseek,直接不调用MCP服务了,很奇怪。 后续一直没调用MCP服务,暂时还没解决,我的是Mac,有解决的小伙伴可以评论区分享一下。 7.1 更新 ezbookkeeping cd /root/data/docker_data/ezbookkeeping docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 7.2 卸载 ezbookkeeping 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/ezbookkeeping docker compose down cd .. rm -rf /root/data/docker_data/ezbookkeeping # 完全删除 可以卸载得很干净。 8. 常见问题及注意点 还是MCP调用的问题,不是非常稳定,暂时不确定是不是Cherry Studio的问题。 9. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,项目处于初期,有能力给项目做贡献的同学,也欢迎积极加入到 [项目]https://github.com/mayswind/ezbookkeeping) 中来,贡献自己的一份力量! 最后,感谢开发人员们的辛苦付出,让我们能用到这么优秀的项目!也希望开源项目越来越好! 10. 参考资料 官方GitHub:

最近事情比较多,鸽了好一阵子了。前阵子Alist被收购的事情,相信不少小伙伴可能已经有了解。 这边给还不清楚的小伙伴简单梳理一下。 1. 介绍 AList是一个广受欢迎的开源项目,支持多种云存储的统一管理和WebDAV功能,因其便捷性和强大的网盘聚合能力在开发者社区中积累了近5万星标。 咕咕在自己的博客里也介绍过这个项目—— (突然发现已经是三年前的事情了 = =) 然而,2025年6月爆出的项目被出售事件引发了开源社区的激烈反响。 事件背景 项目出售 :据多方消息,AList被原开发者Xhofe出售给贵州不够科技有限公司(BugoTech),交易过程未公开,缺乏社区通知。原开发者确认项目已交由公司运营,自己仅保留代码审查权,main分支启用PR审核和CI自动构建以确保透明性。 争议操作 :新维护者(账号alist666)提交的PR #8633被发现包含收集用户设备信息并上传至私有服务器的代码,引发“供应链投毒”担忧,虽因社区压力撤回,但信任危机加剧。文档被大幅修改,移除原作者信息,新增商业化内容(如VIP技术支持和QQ群),官网域名从alist.nn.ci更换为alistgo.com,旧链接404。 收购方背景 :不够科技此前收购Hutool、LNMP等项目,伴随权限争议和疑似“投毒”历史,社区对其声誉存疑。 社区反应 愤怒与失望 : 信任崩塌 :社区对原开发者Xhofe未提前公告的“暗箱操作”表示强烈不满,认为此举背叛了开源精神,伤害了用户和贡献者的信任。GitHub Issues区被大量批评淹没,新维护者删除质疑帖并移除反对者进一步激化矛盾。 隐私担忧 :AList涉及网盘Token和Cookie等敏感数据,用户担心新版本可能泄露隐私或被植入恶意代码。社区建议锁定v3.40.0版本,解除网盘授权,并备份重要文件。 对收购方的质疑 :不够科技的“黑历史”被广泛讨论,社区将其与此前LNMP、Oneinstack的闭源和投毒争议联系起来,称其为“傻逼公司”“bug科技”,对其动机和代码安全性高度警惕。 社区自救与分叉 : 分叉项目 :开发者迅速行动,创建了OpenList等分叉项目,移除不可信代码并审计近半年提交,短时间内获数千星标,显示社区对替代方案的热情。 替代方案 :技术博客推荐Zdir、Cloudreve等私有网盘程序,鼓励用户迁移以规避风险。 安全建议 :社区提出锁定版本、校验哈希、隔离网络、自行编译等措施,强调去中心化分发和社区监督的重要性。 对原开发者的复杂情绪 : 理解与支持 :部分开发者(如@DIYgod)表示理解Xhofe的决定,认为开源维护的长期孤独和无回报让变现成为合理选择,祝福其获得回报。贡献图显示Xhofe个人提交超90%代码,凸显其付出。 批评与指责 :更多用户认为Xhofe将社区贡献和用户数据“卖给有前科的公司”是不道德的,称其“把用户当交易品”,损害了开源生态的信任。 评价 对原开发者的行为 :Xhofe作为主要贡献者有权在MIT/AGPL-3.0许可证框架内转让项目,但未公开透明的做法违背了开源社区的信任契约。 虽然其后续承诺代码审查,但“悄然退出”和“配合移除贡献者痕迹”让社区感到被背叛,凸显了单一开发者主导项目的风险。 对收购方的操作 :不够科技的历史记录和新代码的隐私收集行为引发合理担忧,其商业化导向(如VIP服务)与开源精神冲突。删除批评和接管仓库的“粗暴”操作进一步破坏信任,可能导致AList用户流失。 社区的应对 :社区的激烈反应反映了对开源项目透明度和数据安全的重视。分叉项目和替代方案的迅速涌现展现了开源生态的韧性,但也暴露了国内开源项目变现难、社区监督机制不足的问题。 对开源生态的启示 :AList事件敲响警钟,提醒用户关注项目维护的可持续性和开发者的稳定性。建议建立贡献者许可协议(CLA)和社区监督机制,防止类似“野蛮收购”。用户应谨慎选择涉及敏感数据的开源工具,优先考虑社区主导的项目。 结论 AList被收购事件引发了开源社区的信任危机,社区的愤怒源于对隐私安全和开源精神的双重担忧。尽管部分人理解原开发者的变现动机,但其隐秘操作和收购方的争议历史让事件成为“开源之耻”。社区通过分叉和替代方案展现了自救能力,但事件也暴露了国内开源生态在商业化与信任之间的矛盾。用户应保持警惕,优先选择透明、社区驱动的项目,同时推动更完善的开源治理机制。 这一期,我们就来分享一个Alist的开源替代——OpenList。 如果你之前安装过 Alist,其实只要在 docker-compose.yml 修改两处: 镜像名从 xhofe/alist 改为 openlistteam/openlist 映射的容器内的路径,由 /opt/alist/ 改为 /opt/openlist/ (如果添加了本地存储,记得也修改一下根文件夹路径,其他网盘不影响) 运行之前,最好对原来的数据进行一次备份,运行之后,原来怎么使用,现在还是一样的。 2. 相关地址 官方GitHub地址: (目前10.6k个star,欢迎大家去给作者点星星!) 3. 搭建环境 服务器:咕咕这边用的是OVH的杜甫,当然你也可以选择其他 。内存1G以上即可,硬盘当然是越大越好啦。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 4. 搭建视频(有时间补充!) 4.1 YouTube 视频地址: 4.2 哔哩哔哩 哔哩哔哩: 5. 搭建方式 5.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 5.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/openlist cd openlist 我们来简单修改一下作者提供的 docker-compose.yml 文件 vim docker-compose.yml 咕咕修改之后的(其实就是修改了端口号和删除了 version: "3.7" ): services: openlist: image: 'openlistteam/openlist:latest' container_name: openlist volumes: - '/data:/opt/openlist/data' - './share:/opt/openlist/share' ports: - '5288:5244' environment: - PUID=0 - PGID=0 - UMASK=022 restart: unless-stopped 修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 5.3 打开服务器防火墙(非必需)并访问网页 打开防火墙的端口 5288 举例,腾讯云打开方法如下(部分服务商没有自带的面板防火墙,就不用这步操作了): 类似图中的,这边我们填 5288 ,示例填 openlist ,确定即可(如果你在 docker-compose 文件里换了 9009 ,这边就需要填 9009 ,以此类推) 查看端口是否被占用(以 5288 为例),输入: lsof -i:5288 #查看 5288 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 5.4 启动 openlist cd /root/data/docker_data/openlist docker compose up -d # 注意,老版本用户用 docker-compose up -d 等待拉取好镜像,出现 done 的字样之后, 理论上我们就可以输入 http://ip:5288 访问了。 但是这边我们推荐先搞一下反向代理! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo上 6位数字的xyz续费永远都是0.99美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 6. 反向代理 6.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 5288 IP 填写: 如果 Nginx Proxy Manager 和 openlist 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 openlist 所在的服务器 IP 就行。 6.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:5288/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 7. 使用教程 首先来输入命令: docker compose logs 查看一下初始的登录密码: 这边的密码是 OVoPTw5B 打开域名登录: 默认用户名: admin 密码: OVoPTw5B 这个等下都可以自己修改。 登录之后发现是这样的,因为我们还没有添加任何的存储源: 点击首页下方的管理,来到后台。 我们来演示一下"添加存储"的操作, 这边先选择一个"本地存储", 挂载路径这边可以自己自定义, WebDAV策略选择“本地代理”就行, 根文件夹路径要填docker容器内部的路径,这边前面我们在 docker-compose.yml 里面映射了 ./share:/opt/openlist/share ,所以其实这边填 /opt/openlist/share 即可。 其他的选项按需自己设置。 点击保存之后,再返回首页,就有显示了,后续可以自己把文件传到服务器,这边就会显示出来了。 当然,AList之所以火爆,就是因为他可以添加很多个网盘的索引, 因为不同的小伙伴用的网盘也不一样,具体的细节,大家可以参考官方的文档来添加: 7.1 更新 openlist cd /root/data/docker_data/openlist docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 7.2 卸载 openlist 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/openlist docker compose down cd .. rm -rf /root/data/docker_data/openlist # 完全删除 可以卸载得很干净。 8. 常见问题及注意点 目前 OpenList 已经发布了 4.0 正式版,但依然存在一些已知问题,不过大部分功能已可稳定使用,具体迁移进度可在 中查看。 大家记得多多更新,使用最新版本的docker镜像。 目前项目的赞助页面还未建立: 后续建立了我也会支持,同时也希望有条件的小伙伴多多支持,赞赏开发者们一杯咖啡。这样也能让开源项目能够健康生存下去,少受商业的裹挟。 9. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,项目处于初期,有能力给项目做贡献的同学,也欢迎积极加入到 [项目]https://github.com/OpenListTeam/OpenList) 中来,贡献自己的一份力量! 最后,感谢开发人员们的辛苦付出,让我们能用到这么优秀的项目!也希望开源项目越来越好! 10. 参考资料 官方GitHub:

1. 唠嗑 之前和大家分享过: 今天我们再来分享一个UI更简洁漂亮的支持docker自建的本地图像处理工具——mazanoke 2. mazanoke简介 MAZANOKE 是一个简单的图像优化工具,可以在你的浏览器中运行,离线工作,并且始终保持你的图像私密,是市面上那些宣传“免费”在线图片处理工具的替代品。 2.1 功能 🖼️ 在浏览器中优化图像 调整图像质量 设置目标文件大小 设置最大宽度/高度 从剪贴板粘贴图像 在 JPG、PNG、WebP 之间转换 从 HEIC、AVIF、GIF、SVG 转换 🔒 注重隐私 离线工作 设备内处理图像 删除 EXIF 数据(位置、日期等) 无追踪 可安装的网络应用程序 计划中的功能 一次上传多个文件 支持更多图像文件类型 最近添加了从:HEIC、AVIF、GIF、SVG → JPG/PNG/WebP 的转换 记住上次使用的设置 裁剪图片 2.2 使用 官方Demo地址: 所见即所得,不多赘述。 其实,这个本质就是一个html页面,你可以直接点击右上角下载,这样就能离线使用了。 或者是在这边下载: 然后打开里面的 index.html 也可以直接离线使用。 当然如果你看到这边还想自己用docker搭建一个,那我们就继续往下! 3. 相关地址 官方GitHub地址: (目前1K个star,欢迎大家去给项目点星星!) 4. 搭建环境 服务器:咕咕这边用的香港的 ,建议服务器内存1G以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩天补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/mazanoke cd /root/data/docker_data/mazanoke 接着我们来编辑下 docker-compose.yml vim docker-compose.yml # Basic compose file for deploying MAZANOKE services: mazanoke: container_name: mazanoke image: ghcr.io/civilblur/mazanoke:latest ports: - "3474:80" restart: unless-stopped 其中的左边的 3474 可以改成服务器上没有用过的端口,记得修改自己的用户名和密码,修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 6.3 查看端口是否被占用 查看端口是否被占用(以 3474 为例),输入: lsof -i:3474 #查看 3474 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 6.4 启动 mazanoke cd /root/data/docker_data/mazanoke docker compose up -d # 注意,老版本用户用 docker-compose up -d 耐心等待拉取好镜像,出现 done 的字样之后, 理论上我们就可以输入 http://ip:3474 访问了。 但是这边这个服务必须先搞一下反向代理!http使用明文传输太危险,我们部署在公网一定要考虑使用反向代理工具配置SSL! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo上 6位数字的xyz续费永远都是0.99美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 3474 IP 填写: 如果 Nginx Proxy Manager 和 mazanoke 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 mazanoke 所在的服务器 IP 就行。 然后访问域名就可以访问了! 7.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:3474/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 8. 使用教程 非常简单,所见即所得。 8.1 更新 mazanoke 这个项目后续应该也会有更新,所以提供一个更新的方式。 cd /root/data/docker_data/mazanoke docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 8.2 卸载 mazanoke 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/mazanoke docker compose down cd .. rm -rf /root/data/docker_data/mazanoke # 完全删除 可以卸载得很干净。 9. 常见问题及注意点 大家有问题欢迎评论区交流。 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star ⭐️ 参考资料 官方GitHub:

1. 唠嗑 之前和大家分享过 , 功能很丰富,不过不支持中文。 这一期来介绍一个中文的,高颜值的在线开发工具箱。 2. jisuxiang简介 极速箱是一个高颜值的在线开发工具箱,提供各种程序员必备的开发工具,帮助开发者提升编程效率。本项目使用 Next.js 和 TailwindCSS 开发,拥有美观现代的界面设计。 在线演示: - 立即体验! 2.1 🚀 功能特点 多种分类工具 :JSON处理、编码解码、网络测试等多种实用工具 响应式设计 :适配各种设备尺寸,提供最佳用户体验 暗色主题 :保护眼睛的界面设计,适合长时间使用 高效搜索 :快速找到所需工具的强大搜索功能 收藏系统 :保存常用工具,方便快速访问 多语言支持 :支持中文和英文界面 2.2 🔧 包含工具 JSON格式化与验证 HTTP请求测试 时间戳转换 编码解码工具 正则表达式测试 加密解密工具 颜色选择与转换 代码格式化 JSON编辑器与转换器 IP地址查询 日期计算器 时区转换 文本统计 HTML/Markdown转换 图片压缩 二维码生成 CSS渐变生成器 更多实用工具... 2.3 使用 当然如果你看到这边想自己搭建一个,那我们就继续往下看! 3. 相关地址 官方GitHub地址: (目前261个star,欢迎大家去给项目点星星!) 4. 搭建环境 服务器:咕咕这边用的香港的 ,建议服务器内存1G以上,当然你也可以选择其他 。 系统:Debian 11 ( 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~) 安装好 Docker、Docker-compose( ) 【必需】域名一枚,并做好解析到服务器上( ) 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx( ) 【非必需本教程选用】安装好 Nginx Proxy Manager( ) 5. 搭建视频(过俩天补充 = =) 5.1 YouTube 视频地址: 5.2 哔哩哔哩 哔哩哔哩: 6. 搭建方式 6.1 安装 Docker 与 Nginx Proxy Manager 可以直接参考这篇内容: 6.2 创建安装目录 创建一下安装的目录: sudo -i mkdir -p /root/data/docker_data/jisuxiang cd /root/data/docker_data/jisuxiang 接着我们来编辑下 docker-compose.yml vim docker-compose.yml name: jisuxiang services: jisuxiang: container_name: jisuxiang restart: always ports: - 3001:3000 image: star7th/jisuxiang:latest 其中的左边的 3001 可以改成服务器上没有用过的端口,记得修改自己的用户名和密码,修改完成之后,可以在英文输入法下,按 i 修改,完成之后,按一下 esc ,然后 :wq 保存退出。 6.3 查看端口是否被占用 查看端口是否被占用(以 3001 为例),输入: lsof -i:3001 #查看 3001 端口是否被占用,如果被占用,重新自定义一个端口 如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~ 如果出现: -bash: lsof: command not found 运行: apt install lsof #安装 lsof 如果端口没有被占用(被占用了就修改一下端口,比如改成 8381 ,注意 docker 命令行里和防火墙都要改) 6.4 启动 jisuxiang cd /root/data/docker_data/jisuxiang docker compose up -d # 注意,老版本用户用 docker-compose up -d 耐心等待拉取好镜像,出现 done 的字样之后, 理论上我们就可以输入 http://ip:3001 访问了。 但是这边这个服务必须先搞一下反向代理!http使用明文传输太危险,我们部署在公网一定要考虑使用反向代理工具配置SSL! 做反向代理前,你需要一个域名! 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo上 6位数字的xyz续费永远都是0.99美元 = =) 如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有) 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 丑 古老 = =) 我们接着往下看! 7. 反向代理 7.1 利用 Nginx Proxy Manager 在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个: 域名一枚,并做好解析到服务器上 ( ) (名称改成你自己想要的域名前缀即可) 之后,登陆 Nginx Proxy Manager(不会的看这个: 安装 Nginx Proxy Manager ( )) 注意: Nginx Proxy Manager(以下简称 NPM)会用到 80 、 443 端口,所以本机不能占用(比如原来就有 Nginx) 直接丢几张图: 注意填写对应的 域名 、 IP 和 端口 ,按文章来的话,应该是 3001 IP 填写: 如果 Nginx Proxy Manager 和 jisuxiang 在同一台服务器上,可以在终端输入: ip addr show docker0 查看对应的 Docker 容器内部 IP。 否则直接填 jisuxiang 所在的服务器 IP 就行。 然后访问域名就可以访问了! 7.2 利用宝塔面板 发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置: 直接新建一个站点,不要数据库,不要 php,纯静态即可。 然后打开下面的配置,修改 Nginx 的配置。 代码如下: location / { proxy_pass http://127.0.0.1:3001/; # 注意改成你实际使用的端口 rewrite ^/(.*)$ /$1 break; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade-Insecure-Requests 1; proxy_set_header X-Forwarded-Proto https; } 此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。 有同学可能会问,为什么不直接用宝塔自带的反向代理功能。 也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = = 所以后来就不用了,直接用上面的方法来操作了。 8. 使用教程 非常简单,开箱即用。 8.1 更新 jisuxiang 这个项目后续应该也会有更新,所以提供一个更新的方式。 cd /root/data/docker_data/jisuxiang docker compose pull docker compose up -d # 请不要使用 docker compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。 docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像 提示: WARNING! This will remove all dangling images. Are you sure you want to continue? [y/N] 输入 y 利用 Docker 搭建的应用,更新非常容易~ 8.2 卸载 jisuxiang 同样进入安装页面,先停止所有容器。 cd /root/data/docker_data/jisuxiang docker compose down cd .. rm -rf /root/data/docker_data/jisuxiang # 完全删除 可以卸载得很干净。 9. 常见问题及注意点 暂时没有。 10. 结尾 祝大家用得开心,有问题可以去 GitHub 提 ,也可以在评论区互相交流探讨。 同时,有能力给项目做贡献的同学,也欢迎积极加入到 中来,贡献自己的一份力量! 最后,感谢作者 的辛苦付出,让我们能用到这么优秀的项目!欢迎大家都去给这个项目点个 star ⭐️ 参考资料 官方GitHub: