Nebula 1.0.0 的重点不是“又一个对象存储客户端”,而是把多云对象存储管理做成一个统一桌面入口。首个正式版先接入阿里云 OSS,覆盖浏览、上传、下载、分享和传输管理这些高频动作;底层用 Rust + Tauri 做跨平台桌面应用,目标是原生、轻量、打开就能用。
对象存储终于不用总在控制台里点来点去
云对象存储的日常操作很碎:找 bucket、进目录、上传一批文件、下载日志包、临时分享一个对象、看传输是否失败。控制台能做,但每家云的交互不一样,页面层级也重。命令行适合自动化,却不适合临时浏览和批量拖拽。
Nebula 的产品方向很清晰:像本地文件管理器一样管理云上的对象。这个定位对开发者、运维和内容团队都实用,因为对象存储本质上已经承担了很多“远端文件系统”的工作:构建产物、备份、静态资源、数据导出、日志归档,都可能放在 bucket 里。
1.0.0 先把阿里云 OSS 做到“日常够用”,这比一次性接很多厂商更稳。对象存储看起来都叫 bucket/object,但权限模型、签名、分片上传、临时链接、区域 endpoint 都有差异。先把一个 provider 的体验打磨完整,再扩展多云,工程上更容易收敛。
Rust + Tauri 的取舍:轻量桌面壳,重活放在后端
Nebula 基于 Rust + Tauri,这个技术组合适合这类工具型桌面应用:
- UI 可以用 Web 技术快速迭代,适合做文件列表、传输队列、弹窗和状态反馈。
- 本地能力由 Rust 侧承接,例如文件读写、并发传输、凭证处理、系统通知。
- 相比把完整浏览器运行时一起打包的方案,Tauri 通常更轻,启动和安装体积更友好。
这里真正关键的是边界:桌面界面不应该知道“阿里云 OSS 如何签名上传”,它只需要发出“列目录、上传、下载、生成分享链接”这些意图。云厂商差异应该被压到 provider 层里。
这也解释了摘要里提到的 StorageProvider 接口:后续厂商只要实现同一个接口,界面无需改动。对一个多云管理器来说,这不是小细节,而是能否长期扩展的核心结构。
可以这样理解 StorageProvider:统一动作,不统一实现
下面是一个可改造的 Rust 伪项目示例,用来说明多云对象存储客户端可以怎样抽象 provider。注意:这不是 Nebula 官方接口,只是基于摘要中“实现 StorageProvider 接口即可加入”的思路做的最小化实践模型。
把下面内容保存为 main.rs,本机有 Rust 的话可以直接运行:
use std::error::Error;
#[derive(Debug, Clone)]
struct ObjectEntry {
key: String,
size: u64,
is_dir: bool,
}
trait StorageProvider {
fn name(&self) -> &'static str;
fn list(&self, bucket: &str, prefix: &str) -> Result<Vec<ObjectEntry>, Box<dyn Error>>;
fn upload(&self, bucket: &str, key: &str, local_path: &str) -> Result<(), Box<dyn Error>>;
fn download(&self, bucket: &str, key: &str, local_path: &str) -> Result<(), Box<dyn Error>>;
fn share_url(&self, bucket: &str, key: &str, expires_seconds: u64) -> Result<String, Box<dyn Error>>;
}
struct AliyunOssProvider {
endpoint: String,
}
impl StorageProvider for AliyunOssProvider {
fn name(&self) -> &'static str {
"aliyun-oss"
}
fn list(&self, bucket: &str, prefix: &str) -> Result<Vec<ObjectEntry>, Box<dyn Error>> {
println!("LIST oss://{}/{} via {}", bucket, prefix, self.endpoint);
Ok(vec![
ObjectEntry { key: format!("{}app.log", prefix), size: 2048, is_dir: false },
ObjectEntry { key: format!("{}images/", prefix), size: 0, is_dir: true },
])
}
fn upload(&self, bucket: &str, key: &str, local_path: &str) -> Result<(), Box<dyn Error>> {
println!("UPLOAD {} -> oss://{}/{}", local_path, bucket, key);
Ok(())
}
fn download(&self, bucket: &str, key: &str, local_path: &str) -> Result<(), Box<dyn Error>> {
println!("DOWNLOAD oss://{}/{} -> {}", bucket, key, local_path);
Ok(())
}
fn share_url(&self, bucket: &str, key: &str, expires_seconds: u64) -> Result<String, Box<dyn Error>> {
Ok(format!(
"https://{}.{} /{}?expires={}",
bucket,
self.endpoint,
key,
expires_seconds
).replace(" ", ""))
}
}
fn print_files(provider: &dyn StorageProvider) -> Result<(), Box<dyn Error>> {
println!("provider: {}", provider.name());
for object in provider.list("demo-bucket", "logs/")? {
let kind = if object.is_dir { "dir " } else { "file" };
println!("{} {:>8} {}", kind, object.size, object.key);
}
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
let provider = AliyunOssProvider {
endpoint: "oss-cn-hangzhou.aliyuncs.com".to_string(),
};
print_files(&provider)?;
provider.upload("demo-bucket", "logs/app.log", "./app.log")?;
provider.download("demo-bucket", "logs/app.log", "./downloaded-app.log")?;
println!("share: {}", provider.share_url("demo-bucket", "logs/app.log", 3600)?);
Ok(())
}
运行:
rustc main.rs && ./main
这段代码的重点不是模拟 OSS SDK,而是展示 UI 层最想要的稳定契约:列对象、上传、下载、分享。将来接入 S3、腾讯云 COS、华为云 OBS 或 MinIO 时,界面仍然调用同一组动作,provider 内部处理各自的 endpoint、鉴权、分页、签名和错误码。
真正落地时,要盯住几个边界
桌面对象存储工具很容易做成“能跑”,但要做到日常可靠,需要关注这些细节:
- 凭证存储:AccessKey 不应该明文散落在配置文件里,最好接入系统 keychain 或至少做本地加密。
- 大文件传输:上传下载要支持队列、暂停、重试、失败原因展示;对象存储网络抖动很常见。
- 分页和前缀:bucket 里可能有百万级对象,列表必须分页加载,不能一次拉爆内存。
- 删除和覆盖:对象存储没有传统文件系统那种“撤销”体验,危险操作需要明确确认。
- 分享链接:临时 URL 要清楚展示过期时间,避免误把长期公开访问当成短期协作。
Nebula 1.0.0 选择从阿里云 OSS 起步,是一个务实切入点。团队或个人现在可以把它当作 OSS 日常管理器试用;如果你的环境已经有多家云,后续就要重点观察它的 StorageProvider 扩展是否足够稳定,尤其是权限、传输队列和错误处理是否能跨 provider 保持一致。
采用建议
如果你平时只是偶尔上传静态文件,云厂商控制台已经够用;如果你每天都在 bucket 里查文件、传日志、发临时链接,一个桌面管理器会明显减少上下文切换。
更稳妥的试用方式是:先接一个非生产 bucket,验证浏览、上传、下载、分享和失败重试;再评估团队凭证管理规范,最后再让它进入生产对象存储的日常流程。对象存储工具越顺手,越要把权限边界设清楚。