图床是很多人第一次真正用上对象存储的场景写博客要贴图截图一多本地路径、第三方图床的失效链接、配额限制轮番找上门。把图床后端换成自己的 S3 兼容存储一次配好之后上传是 CtrlV 的事链接在自己手里不担心哪天服务关停。这篇文章走完从 PicGo 配置到链接输出的完整链路以 RustFS 为后端其他 S3 兼容存储的配置项基本一致。PicGo 在整个链路里的位置PicGo 的定位是把图片上传变成创作流程里无缝的一步官方 README 的原话Whether you’re writing a blog post, taking notes, or authoring developer docs, PicGo helps you upload images in one step and automatically copies the resulting link.它在整条链路里负责三件事监听剪贴板或文件拖拽、调 S3 接口上传、把可访问的 URL 复制回剪贴板。写 Markdown 时的典型流程变成截图或复制图片、按快捷键、粘贴链接三步完成不再有先打开图床网站、上传、复制链接这个中断创作节奏的循环。它支持的图床分两层内置的七牛、腾讯云 COS、又拍云、阿里云 OSS、GitHub、SM.MS、Imgur和插件扩展的。README 里明确一句PicGo itself will no longer add new third-party Image hosts by default新图床要靠插件生态插件列表里包括 AWS S3、Cloudflare R2、MinIO 等。S3 插件的选型是wayjam/picgo-plugin-s3README 里写明支持 Amazon S3 与兼容 S3 API 的云存储例如 Backblaze B2支持 PicGo GUI也支持 MinIO。“支持 MinIO” 意味着 path-style 访问是现成的对接 RustFS 这类同样默认 path-style 的存储时不用额外折腾。安装两条路GUI 用户在插件市场搜s3直接下载Core 用户picgo add s3。配置入口是picgo set uploader s3。这个插件是社区维护的不是 PicGo 官方出品版本组合会直接影响能不能连上。仓库的提交历史里有fix compatibility with new ver of client-s3这类修复说明 PicGo 本体、client-s3 依赖、插件三者版本对不上就会出问题。后面遇到上传报错先别急着改配置把插件升到最新、必要时连 PicGo 本体一起升再排别的因素。PicGo 还有 Typora、Obsidian 等编辑器的集成方案Typora 里直接配上传服务指向 PicGo 的 HTTP 接口默认http://127.0.0.1:9876写文档时粘贴图片自动上传这套组合是中文技术写作圈最常见的搭配。对接细节在 PicGo 官方文档的配合 Typora一节这里不展开。配置文件本身值得备份一份。PicGo 的配置存在用户目录下Windows 是%APPDATA%\picgomacOS 与 Linux 是~/.picgo换机器时把config.json拷过去就能直接复用整套图床设置不用再填一遍。要注意里面存着明文密钥备份文件别随手丢进公开仓库。还有一点容易误会PicGo 的相册只记录上传历史删掉相册里的条目不会删掉远端对象清理存储要在对象存储侧做。S3 插件的配置项逐个看插件 README 给的配置项列表里对接 RustFS 需要动的是这几项配置键作用对接 RustFS 的值accessKeyIDS3 凭证 IDRustFS 的 access keysecretAccessKeyS3 凭证密钥RustFS 的 secret keybucketName桶名已建好的图床桶endpoint自定义终端节点http://rustfs-host:9000pathStyleAccesspath-style 访问模式必须设为trueuploadPath上传路径模板{year}/{month}/{fullName}之类outputURLPattern输出 URL 模板见下节acl上传对象的 ACLRustFS 不做对象 ACL见下节两个容易配错的键单独说。pathStyleAccess默认falsevirtual-hosted styleRustFS 官方文档明确写了 Path Style 是默认访问方式bucket 名在请求路径里http://localhost:9000/my-bucket/hello.txt不用 DNS 配置virtual-hosted style 需要RUSTFS_SERVER_DOMAINS配置了对应域名才能工作。插件设true才能对上 RustFS 的默认行为。acl默认public-read但 RustFS 官方兼容性矩阵里 ACL authorization 被标为 Excludedintentionally unsupported也就是明确不支持对象级 ACL。图床桶的公开访问要靠桶策略控制RustFS 的 Bucket policies 是已测试覆盖状态插件里的 acl 值在 RustFS 上不会生效。图床桶的公开读策略要在 RustFS 侧单独配。把这些键对着 RustFS 侧看一遍会更清楚每个值为什么这么填输出 URL 模板决定链接长什么样插件上传成功后会把链接复制到剪贴板链接的形态由outputURLPattern决定。README 给的示例是https://img.example.com/{bucket}/{path}如果 RustFS 前面有 Nginx 或 CDN 做反向代理模板可以写成代理域名 路径如果没有代理、直接访问 RustFS模板可以指向http://host:9000/{bucket}/{path}。这两个字段的职责要分开看填混了是新手最常见的失败来源endpoint是真正发 S3 请求的那个地址指向 RustFS 本体http://host:9000outputURLPattern只负责把上传结果的路径拼成 Markdown 里的访问链接不参与上传。前面挂了 HTTPS 代理的场景下只有链接用代理域名endpoint仍要写后端真实地址。把endpoint也填成 CDN 或代理域名的话S3 请求发到那里找不到桶上传直接失败。选哪种形态要考虑链接的存活期。直接指向http://host:9000意味着以后换域名、换端口、上 HTTPS文章里所有旧链接都得改一轮指向一个代理域名则把这些变化挡在后面迁移时只动 Nginx 配置。另外如果文章最终发布在 HTTPS 站点上图床链接是http://会被浏览器拦成混合内容这也是倾向于挂一层代理域名并配好证书的原因。这个模板还有两个实用变量{year}和{month}常用在uploadPath里做目录分层{md5}可以用作文件名避免重名。README 给的完整配置示例{picgo-plugin-s3:{accessKeyID:xxx,secretAccessKey:xxxxx,bucketName:my-bucket,uploadPath:{year}/{md5}.{extName},endpoint:s3.us-west-000.backblazeb2.com,outputURLPattern:https://img.example.com/{bucket}/{path}}}uploadPath用{year}/{md5}.{extName}的好处是按年分目录、文件名用内容哈希避免重复上传同名文件。{fullName}则保留原文件名适合在意可读性的场景但同名文件会互相覆盖。还有三个实用配置项顺带说。proxy支持给上传请求走 HTTP 代理内网走代理出公网的场景用得上urlPrefix和urlSuffix在 README 里标了已废弃用outputURLPattern替代旧配置迁过来时注意别再用废弃键。rejectUnauthorized默认true只有后端是自签证书时才需要设成false。这个开关的代价得讲清楚设成false等于把整条链路的 TLS 证书校验关掉access key、签名、图片内容全在这条连接上跑中间节点不校验就能读走。它适合内网自签证书的个人环境生产应该把 CA 证书装进跑 PicGo 的那台机器的信任库、保持校验开着而不是靠关校验换取连通。桶策略怎么配才能公开读RustFS 的 ACL 不可用公开读要靠桶策略。最小可用的一条 Allow 策略给s3:GetObject{Version:2012-10-17,Statement:[{Sid:PublicReadGetObject,Effect:Allow,Principal:*,Action:s3:GetObject,Resource:arn:aws:s3:::picgo-images/*}]}配完策略后上传的图片任何人都能通过 URL 读到但写操作仍然需要签名凭证。这个公开读、受控写的形态就是图床的标准形态。Principal写成*的准确含义是互联网上任何匿名访问者不持有任何身份就能读。这个桶、这个前缀下的对象因此全部对外可读内部架构截图、带报错信息的调试图、还没发布的稿件配图只要落在同一前缀下就会被外部直接下载。落笔时把公开范围收窄到专门前缀比如public/*私密内容放别处这条边界比事后清理要省事得多。要注意的是这条策略只对列在 Resource 里的前缀生效。如果同一个桶里还存了私密文件策略里要限定到图床用的前缀比如arn:aws:s3:::picgo-images/public/*插件侧的uploadPath也对应加前缀两边对上才不会把私密路径暴露出去。RustFS 兼容性矩阵里 Bucket policies 标的是已测试覆盖状态Put、get、delete 三种操作都验证过这条路径官方支持。给 PicGo 用的凭证应该单独开一对不要复用管理员 key。最小权限的做法是只给s3:PutObject和s3:GetObjectResource 限定在图床桶的图床前缀下。这样即使配置文件泄露前面说了里面有明文密钥影响范围也只是能往图床目录传图不会波及桶里的其他数据。对应的策略长这样可以直接照抄替换成自己的桶名{Version:2012-10-17,Statement:[{Sid:PicGoUploadOnly,Effect:Allow,Action:[s3:PutObject,s3:GetObject],Resource:arn:aws:s3:::picgo-images/public/*}]}Action里只放上传和读回删除、列举一概不给。图床场景没有删图需求给出去就多一条误删的通路。客户端要做前缀预览才需要s3:ListBucket那种情况资源级写桶本身arn:aws:s3:::picgo-images而不是/*/*会把桶内所有前缀都圈进来。验证这个策略的办法很直接拿这一对凭证配进 PicGo 传一张图能成换一个不带此策略的账号去下载同一个对象应当直接被拒。桶策略配完之后还有一层值得做配一下 Block Public Access。AWS 官方把公开访问阻断分成四项BlockPublicAcls、IgnorePublicAcls、BlockPublicPolicy、RestrictPublicBuckets图床桶的推荐组合是四项里策略相关的两项都关、ACL 相关的两项都开BlockPublicAcls: true、IgnorePublicAcls: true挡住顺手加个 public-read这类操作图床的公开读交给桶策略不需要 ACLBlockPublicPolicy: false这一项开着会拒绝带公开访问的桶策略图床用不上RestrictPublicBuckets: false这一项开着同样会挡掉带公开策略桶上的匿名请求图床必须留着公开读就不能开改的方式是调标准 S3 的PutPublicAccessBlock四个布尔值一次提交不用逐项改。RustFS 兼容性矩阵里 Public access block 是已测试覆盖的能力这层保护用得上。连不上先看 PicGo 的日志图床这堆配置的问题几乎都能在日志里定位不用靠猜。GUI 打开日志面板重新传一次原始的 S3 报错会直接指向原因报 404 找不到桶或对象多数是pathStyleAccess没设成true或者endpoint填成了代理域名报证书相关是rejectUnauthorized与 CA 信任的问题报AccessDenied要么桶策略没配公开读要么凭证权限不够只给s3:PutObject不给s3:GetObject也会是这个结果签名错误配上 403通常是 access key、secret key 填错或者密钥里有特殊字符被 shell 转义掉。Core 用户可以命令行跑picgo upload any.jpg输出比 GUI 面板完整报错堆栈也在。从第三方图床迁过来的迁移账已经在第三方图床SM.MS、Imgur 之类存了大量图片的话迁移要算三笔账第一笔旧链接的存活期。迁移期间新旧图床并存旧链接继续有效直到确认所有文章的图片引用都改完才考虑下线旧图床。批量改引用通常靠脚本扫数据库或静态文件里的旧域名。第二笔图片的搬运方式。第三方图床多数不提供 S3 接口搬运要么手动下载再上传要么写脚本抓图片 URL 下载后走 S3 上传。图片量大的话用mc mirror不可行源端不是 S3得写个简单脚本循环下载上传或者借助 rclone 的 HTTP 后端。这条路有几个边界要先知道rclone 的 HTTP 后端是只读的官方文档写明它只能读取 web 服务器给出的文件列表不能当双向同步用而且对方服务得真的吐目录列表才吃得下不吐列表的图床这条路径直接走不通。频率限制、防盗链、token 校验也压在这条链路上批量抓取容易被限流甚至封禁。规模大的话把并发压下来再分批跑--transfers 1、--tpslimit指望一次跑完通常会被中途掐掉。第三笔URL 结构是否保留。如果第三方图床的 URL 结构能映射过来比如按文件名可以让新链接保持相同路径迁移脚本一次性改域名如果结构对不上就得全量改文章里的引用。后者工作量大但一劳永逸前者省事但留下了对旧结构的依赖。搬运完成一定要做一轮校验。图片迁移里最常见的失败形式是无声的少数几张图在中间环节损坏或只传了一半页面上看起来是断链排查时却要一张张点开。写个脚本对新桶做全量列举逐个 HEAD 检查返回码和Content-Length和源端的大小对得上才算搬完。这一步的耗时通常只有搬运的几分之一但能提前捞出绝大多数问题。顺手可以在这轮迁移里加两个增量一是给图床桶配上生命周期规则比如临时目录 7 天过期配合截图先扔临时目录、定稿后再挪正式目录的工作流二是把outputURLPattern直接指向未来的 CDN 域名即使现在没有 CDN将来接了 CDN 之后文章里的链接不用再改一轮。工作流层面再补一个实践PicGo 支持自定义快捷键和上传后重命名配一个截图 → 自动上传 → 自动复制 Markdown 格式链接的完整链路之后写文章时截图和贴图的手感接近本地文件。这套链路真正运转起来之后图床后端是哪家反而变得不重要这也是自托管的好处之一接口是标准的 S3后端想换随时换。图床这套配置一旦跑通后面往图床桶里加 CDN、加缩略图处理都是增量的事。RustFS 以 Apache 2.0 许可开源仓库在 github.com/rustfs/rustfsS3 寻址风格的官方说明在 administration/protocols/s3。接口是标准的 S3后端想换随时能换这才是自托管图床最实在的收益。