Filestash 架构解析:插件驱动的通用文件存储客户端与存储接入实战
【免费下载链接】filestash:file_folder: Universal File Storage Client项目地址: https://gitcode.com/GitHub_Trending/fi/filestash
Filestash 是一个"存储无关"的类 Dropbox 文件管理平台,能同时说通 FTP、SFTP、S3、SMB、WebDAV、IPFS 等数十种存储协议。本篇以官方 README 为核心,结合仓库源码,带你吃透它的插件化架构设计哲学、核心接口体系(IBackend等)、编译期插件与 WASM 运行时插件两条扩展路径,以及如何通过 Docker 在本地把这套系统真正跑起来。
Filestash 是什么:一个存储无关的文件管理器
Filestash 起源于一个"存储无关(storage agnostic)"的 Dropbox 式文件管理器——它的目标是让一个 Web 界面直接管理分散在各处、各协议的后端存储。README 明确指出它支持 FTP、SFTP、S3、SMB、WebDAV、IPFS,以及"另外约 20 种"存储协议。项目如今围绕3 个独立产品展开:
- Web 客户端:浏览器中可直接访问的文件管理器(前端在
public/目录,纯手写 Vanilla JS,无框架依赖); - Native 客户端(fdrive):用于在本地设备同步数据的独立项目;
- Gateways(网关):把已接入的存储再对外暴露为任意协议,例如把 S3 桶挂成 SFTP 服务端、WebDAV 服务端,甚至 MCP 网关。
README 用一句话概括了指导整个项目的哲学:
"anything that's not a fundamental truth of the universe lives in a plugin"(凡不是"宇宙基本真理"的东西,都放进插件里)。
这句话直接决定了它的工程形态:核心保持精简快速,一切可替换的观点(opinions)都下沉为插件。当需求变深或变"怪"时,答案是写插件,而不是 fork 代码库。
核心功能全览
插件驱动的架构
"插件驱动"不是营销话术而是字面意义的架构:核心只保留一组"宇宙真理"接口,其余能力——存储、认证、授权、搜索、缩略图、前端改动、HTTP 端点——全部由插件实现。这样使用者"只得到自己需要的东西,没有多余开销"。
通用访问:API 与网关
Web 客户端只是访问数据的方式之一。Filestash 提供完整 API,并可通过 Gateways 把数据以 SFTP、S3、FTP、WebDAV、MCP、AS2 等协议再次对外服务。仓库中已内置多个网关/协议相关组件可佐证这一点:
go.mod中同时依赖了github.com/mickael-kerjean/goftp、github.com/prasad83/goftp(FTP 服务端/客户端)、github.com/go-webdav、github.com/pkg/sftp(替换为mickael-kerjean/pkg-sftpfork),覆盖 FTP、SFTP、WebDAV 协议栈;- 仓库自带 MCP 网关插件 plg_handler_mcp,印证了 README 提到的 MCP 网关能力;
- 共享链接还实现了 WebDAV 处理器 webdav_handler.go,说明"把共享链接挂载为网络驱动器"在代码层是真实落地的。
存储与认证集成
README 的明确目标是支持市场上 100% 的存储与认证技术。从 go.mod 的依赖清单可以逐一印证它"说了就做到":
| 协议/后端 | 依赖证据(go.mod) |
|---|---|
| S3 / Azure Blob / Google Cloud Storage | aws/aws-sdk-go、Azure/azure-sdk-for-go/.../azblob、cloud.google.com/go/storage |
| SMB / NFS | hirochachacha/go-smb2、vmware/go-nfs-client |
| SFTP / FTP | pkg/sftp(fork)、goftp、fclairamb/ftpserverlib |
| Git 仓库当存储 | go-git/go-git/v6 |
| MySQL / PostgreSQL / SQLite | go-sql-driver/mysql、lib/pq、mattn/go-sqlite3与modernc.org/sqlite |
| Storj 分布式存储 | storj.io/uplink |
| LDAP / SAML / OTP / 2FA | go-ldap/ldap/v3、mickael-kerjean/saml、pquerna/otp |
| WASM 插件运行时 | tetratelabs/wazero(第 5 节详述) |
README 还举了一个集成深度的例子:可以构造一个"虚拟文件系统",把认证委托给 WordPress 站点,并用其角色驱动 RBAC 授权。仓库中 plg_authenticate_wordpress 插件目录即为该认证器的实现。
工作流引擎
README 提到可用"事件 → 动作链"自动化一切与文件相关的事情,从 Slack/邮件通知到完整的 MFT(管理文件传输)流水线。仓库的 server/pkg/workflow 目录包含trigger.go、action.go、job.go等调度核心,内置动作包括邮件通知、调用 API 等(见server/pkg/workflow/actions/下的notify_email.go、run_api.go)。
File Apps:面向几乎所有文件格式的应用层
Filestash 的"File Apps"体系按领域划分,覆盖面远超一般文件管理器:
| 领域 | 代表格式 |
|---|---|
| 摄影 | heif、nef、raw、cr2、dng 等 RAW 全家桶 + TIFF 查看器 |
| 天文 | fits、xisf |
| 科研 | LaTeX、PlantUML、Pandoc 编译链 |
| 音乐 | mid/midi、gp4/gp5(吉他谱) |
| GIS | geojson、shp、gpx、wms、dbf |
| 数据工程 | parquet、arrow、feather、avro、orc、hdf5/h5、netcdf/nc、rds/rda |
| 开发 | a、so、dll、tar、zip、har、pcap/pcapng、sqlite |
| 创意 | svg、psd、ai、sketch、cdr、各类字体、exr、tga、ktx、dpx 等 |
| 生物医药 | dicom、sam、bam、cif、pdb、xyz、sdf、mol/mol2、mmtf |
| Autodesk / Adobe | dwg/dxf;psd、ai、xd、dng、postscript、aco、ase、swf |
| 3D | fbx、gltf、obj、stl、step、mesh、ifc、dae |
| 刺绣 | dgt、dst、pes、sew、shv、vip 等 |
| 端到端加密 | pgp、gpg(见 plg_widget_pgp) |
除这些内置与发行应用外,前端还有对应的查看器骨架,如public/assets/pages/viewerpage/下的application_editor.js、application_image.js、application_table.js、application_map.js等,编辑器则可由 plg_editor_codemirror、plg_editor_wopi 提供。
主题与长尾能力
README 还列出了主题系统(GitHub、Apple、Dropbox、IBM 等视觉风格,对应public/assets/css/designsystem_*.css设计系统),AI 能力(智能搜索、智能文件夹、OCR),以及一大串长尾特性:版本管理、审计、公开站点、防病毒扫描、配额、聊天、Chromecast、按需视频转码、共享链接挂载为网络驱动器。其判断准则是:"如果你的问题涉及文件,我们要么已有插件,要么可以为你写一个。"
插件体系深入:从"宇宙真理"接口到两条实现路径
README 的 Plugins 章节是全文技术密度最高的部分。它把"插件"具体化为两组东西:一组核心接口,和两条实现路径。
核心接口:server/pkg/core
README 给出的"宇宙真理"就是 server/pkg/core 目录下的一组 Go 接口,每个关键组件一个。其中最老、最核心的是存储接口IBackend——每次你连接一个存储后端,实际调用的就是它。README 展示了其方法集(Ls/Stat/Cat/Mkdir/Rm/Mv/Save/Touch),而仓库中的完整定义见 storage.go,实际还包含初始化与登录表单两个方法:
// server/pkg/core/storage.go type IBackend interface { Init(params map[string]string, app *App) (IBackend, error) Ls(path string) ([]os.FileInfo, error) Stat(path string) (os.FileInfo, error) Cat(path string) (io.ReadCloser, error) Mkdir(path string) error Rm(path string) error Mv(from string, to string) error Save(path string, file io.Reader) error Touch(path string) error LoginForm() Form }也就是说:任何存储——只要实现这 10 个方法——就成为 Filestash 的一个后端。server/plugin/下 20 余个plg_backend_*目录(s3、ftp、sftp、webdav、gdrive、mysql、nfs、samba……)正是这套接口的 20 多种"真理实现"。
同样地,server/pkg/core/下还有整套组件接口,与 README 列举的"storage, authentication, authorisation, search, thumbnailing, apps, middleware, frontend changes"一一对应:
- authentication.go:
IAuthentication(Setup/EntryPoint/Callback),支撑本地账号、LDAP、WordPress、htpasswd、pass-through 等多种认证器; - authorisation.go:
IAuthorisation,对Ls/Cat/Stat/Mkdir/Rm/Mv/Save/Touch八类操作逐一切断放行权; - search.go:
ISearch(Query); - file.go:
IFile与IThumbnailer; - workflow.go:
ITrigger/IAction/ITriggerEvent; - 以及
IAuditPlugin、IMetadata、IDirectoryService、ILogger等。
编译期插件:Go 插件如何被注册
历史上(也仍是今天的主路径之一),插件用 Go 编写并编译进主二进制。仓库中这一机制非常直观:
- server/plugin/index.go 通过一堆blank import(
_ "...")把约 40 个 OSS 插件包引入主模块:认证类(htpasswd、ldap、local、passthrough、wordpress)、存储类(artifactory、backblaze、dav、dropbox、ftp、gdrive、git、ldap、local、mysql、nfs、nop、perkeep、psql、s3、samba、sftp、storj、tmp、url、webdav)、编辑器(codemirror、wopi)、handler(console、mcp、site)、图像处理(ascii、c)、安全(scanner、svg)、starter(http)、视频转码与若干 widget。每个插件包在自己的init()里向核心钩子(Hooks)注册实现; - embed.go 用
//go:embed server/plugin/index.go把这份"插件清单源码"作为字节数组打进二进制; - 启动时 registry.go 的
InitPluginList会解析这份内嵌 Go 源码的 import 表,按路径前缀把插件归类为 OSS / Enterprise / Custom / Apps 四组——即同一个index.go既是编译期的注册表,也是运行时的插件清单数据源。
这套设计的巧妙之处:加一个编译插件 = 在index.go加一行 blank import,核心零改动。
运行时插件:把 WASM 丢进插件目录
README 指出第二条更新的路径:运行时插件(runtime plugins)——一个 zip,丢进插件目录即可生效。zip 可以重塑前端,还可以携带实现同一套核心接口的WASM模块。这些 WASM 跑在一个"权限收紧"的 VM 中:未声明的网络主机一律不可达,没有任何"电话回家"的途径——安装插件不等于把一切信任交给它。
仓库提供了完整的可运行示例 plg_runtime_cookbook,其manifest.json精确说明了"权限收紧"的形态:
{ "author": "Filestash Pty Ltd", "version": "v0.0", "modules": [ { "type": "abi", "entrypoint": "endpoint.wasm" }, { "type": "abi", "entrypoint": "oninit.wasm" }, { "type": "abi", "entrypoint": "onchange.wasm" }, { "type": "abi", "entrypoint": "onquit.wasm" }, { "type": "abi", "entrypoint": "socket.wasm" }, { "type": "abi", "entrypoint": "filesystem.wasm" }, { "type": "abi", "entrypoint": "authentication.wasm" }, { "type": "abi", "entrypoint": "authorisation.wasm" } ], "permissions": [ "tcp://demo.filestash.app:443", "file:///{{ .TMP_PATH }}" ] }即:插件可声明 onInit/onChange/onQuit 生命周期、HTTP 端点、文件系统访问、认证/授权等模块,而网络权限是白名单制(tcp://host:port精确到端口)。WASM 宿主基于 go.mod 中的tetratelabs/wazero(纯 Go WASM 运行时),主机侧的适配器实现位于 server/pkg/extension/adapter/runtime(runtime.go、module.go、fs.go、socket.go、memory.go)。
README 给出了一段极简的 Rust 授权插件示例,值得完整保留——它展示了"代码即权限策略":
use filestash::*; #[derive(Default)] pub struct Plugin; impl Authorisation for Plugin { fn ls(&self, _ctx: &Context, path: &str) -> Decision { self.check(path) } fn cat(&self, _ctx: &Context, path: &str) -> Decision { ... } fn stat(&self, _ctx: &Context, path: &str) -> Decision { ... } } impl Plugin { fn check(&self, path: &str) -> Decision { if path.split("/").any(|segment| segment == "top_secret") { log::warn!("[TOPSECRET] access denied !!"); return Decision::Deny; } Decision::Allow } } register!(Plugin: Authorisation);仅这几行,就得到一个只读视图:任何名为top_secret的文件夹一律拒绝访问。Rust SDK 位于 server/pkg/extension/sdk/rust,cookbook 目录内还有permission_sock(含 socket 权限申请样例)等进阶示例。README 最后强调:代码只是"power-user 路径",常规授权场景完全不需要写代码,用管理界面即可配置。
启动流程:源码视角的运行序
理解了插件体系,再来看主程序如何把它们串起来。cmd/main.go 的Run函数给出了严格的启动序列:
InitLogger 初始化日志 InitConfig 初始化配置 extension.Discovery() 发现插件 extension.InitPluginList(embed.EmbedPluginList, extension.All()) 解析内嵌 index.go,登记插件清单 workflow.Init() 初始化工作流引擎 Hooks.Get.Starter() 校验 starter 插件存在(否则直接退出) Hooks.Get.Onload() 执行所有加载钩子 Hooks.Get.HttpEndpoint() 挂载各插件的 HTTP 路由 server.Build / PluginRoutes / CatchAll 构建核心路由 Hooks.Get.Starter()(withSignal(), router) 启动 HTTP 服务(带 SIGTERM/SIGINT 优雅退出) Hooks.Get.OnQuit() 退出钩子两点值得注意:其一,"starter 插件"是强制依赖——没有注册任何 starter(HTTP 启动器)进程直接报错退出,可见"连监听什么端口都是插件"贯彻得很彻底(仓库内置plg_starter_http、plg_starter_https、plg_starter_http2、plg_starter_tor四种);其二,withSignal()用context.WithCancel监听SIGTERM/SIGINT,为容器环境提供了干净的退出路径。
前端资源则由 embed.go 内嵌public/目录:非DEBUG=true时从二进制内嵌文件系统服务静态资源,因此最终产物是一个单二进制,无需外部 web 服务器。
构建与运行:Docker 实战
多阶段构建(docker/Dockerfile)
docker/Dockerfile 是了解构建环境依赖的权威资料,分三步:
- builder_prepare(alpine/git):
git clone --depth 1 --single-branch拉取源码(可用GIT_REPO/GIT_BRANCH构建参数覆盖); - builder_backend(golang:1.26-trixie):安装 C 库依赖——
libjpeg-dev、libtiff-dev、libpng-dev、libwebp-dev、libraw-dev、libheif-dev、libgif-dev、libvips-dev(图像处理)与libav*(FFmpeg 全家桶,视频缩略图/转码用),然后执行仓库根目录的make init && make build; - 运行镜像(debian:stable-slim):只拷贝
dist/产物,追加安装ffmpeg、libbrotli1、poppler-utils(PDF 文本化),创建非 root 用户filestash,工作目录/app,数据目录/app/data/state/,EXPOSE 8334。
根目录 Makefile 定义了构建命令,注意两个细节:
init: go get ./... go generate -x ./server/... # 生成 MIME 表、i18n 等资源 build: go build --tags "fts5" -o dist/filestash cmd/main.gofts5构建标签启用 SQLite 的 FTS5 全文检索扩展(配合 server/pkg/sqlite 的 cgo/nocgo 双实现),是默认搜索插件能用的前提;Windows 下产物自动带.exe后缀。
docker-compose:主服务 + WOPI 办公套件
docker/docker-compose.yml 给出了一套可直接运行的组合:
services: app: container_name: filestash image: machines/filestash:latest restart: always environment: - APPLICATION_URL= - CANARY=true - OFFICE_URL=http://wopi_server:9980 - OFFICE_FILESTASH_URL=http://app:8334 - OFFICE_REWRITE_URL=http://127.0.0.1:9980 ports: - "8334:8334" volumes: - filestash:/app/data/state/ wopi_server: container_name: filestash_wopi image: collabora/code:24.04.10.2.1 environment: - "extra_params=--o:ssl.enable=false" - aliasgroup1="https://.*:443" # 启动脚本另含一段替换 branding css 的 curl ports: - "9980:9980"其中OFFICE_*三个环境变量对应 plg_editor_wopi 插件:Filestash 把 docx/xlsx 等文件的在线编辑转发给 Collabora WOPI 服务。把application_url、存储后端等改为实际值后,8334端口即可访问管理后台。
上手路径与支持
README 的 Getting Started 一节将安装细节指向官方在线指南(本仓库不复制该文档),本地可用路径为:克隆仓库后按 Makefile 执行make(即init+build),或用 docker/Dockerfile 构建镜像。涉及插件时,两个入口值得常看:
- server/plugin:40+ 个内置插件源码,是学习"某个能力如何实现"的最佳教材;
- plg_runtime_cookbook:运行时(WASM)插件的完整示例集,含 onInit/onQuit/onChange/HTTP 端点/文件系统与 socket 权限申请。
支持渠道方面,README 提供:商业用户支持合同;个人用户可加入 IRC(libera.chat 的 #filestash 频道)或经由 Open Collective 捐赠。
小结
回到 README 开篇的哲学——"不是宇宙真理的东西都住在插件里"——现在可以给出源码级的验证:
- 真理:server/pkg/core 下十几个接口(
IBackend、IAuthentication、IAuthorisation、ISearch、ITrigger/IAction……); - 插件:server/plugin 下 40+ 个编译进二进制的 Go 包,通过 index.go 的 blank import 注册,并被 registry.go 在启动时解析为插件清单;
- 运行时扩展:WASM 模块 +
manifest.json权限白名单,由 wazero 在受限 VM 中执行; - 运行形态:单二进制(
go build --tags fts5),Docker 默认端口 8334,数据落在/app/data/state/。
这套架构的直接收益在 README 中表述得很直白:Web 客户端只是众多访问面之一,数据可以通过 API、SFTP/S3/FTP/WebDAV/MCP 网关被任意工具消费;而当你提出一个"深或怪"的需求时,回答永远是"写一个插件",而不是修改核心或 fork 仓库。
【免费下载链接】filestash:file_folder: Universal File Storage Client项目地址: https://gitcode.com/GitHub_Trending/fi/filestash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考