Avatar
醉后不知天在水

一些有关 OpenList 与 Hugging Face 的小巧思

3.8K 字
10 分钟
发布于
更新于
格物篇

正如文章标题所述,本文的核心在于“小巧思”。它并不打算做一个全能的避风港,而是更适合那些对 OpenList 需求不大、但又希望有一个轻量云端方案的用户。如果你也想在不折腾 VPS 的前提下,优雅地解决云端部署问题,或许这篇文章可以给你一些启发。

OpenList

OpenList

对 AList 有过一定了解,但没太关注社区消息的朋友,可能会好奇为什么会有 OpenList

其实起因很简单:自从前年 AList 项目被整体出售,随后又陷入了涉嫌投毒的风波,整个社区的信任基础受到了极大的冲击。

而 OpenList 由 AList 的原贡献者基于原项目 fork 开发而成,作为一个有韧性、长期治理、社区驱动的分支,旨在防御基于信任的开源攻击。

介绍以及具体的来龙去脉就不多说了,总之,如果我们仍然希望寻找一个纯粹、可靠、且更具社区属性的挂载方案,由衷建议原 AList 用户转向 OpenList。

为什么需要云端部署OpenList

说实话,在我的影音娱乐体系里,本地下载始终是绝对的主力。天翼云盘和夸克网盘的下载速度已经足够满足我的需求,这导致不管是 AList 时期,还是后来的 OpenList,这些软件对我而言只是个“备选项”。

我习惯于在 Windows 上挂着 Alist Helper,在 Android 上开着 AListLite,即用即走,轻量且本分。顺带一提,虽然这两款软件的名字仍然还带着“AList”,但现在都已经转向支持更纯粹的 OpenList 了

既然本地已经很香了,为什么突然想折腾一下云端部署?

起因是前段时间我闲来无事,重新捡起了 网易爆米花,这是一款类似 Infuse、VidHub 的播放软件,主打极其优雅的海报墙和刮削能力,旨在帮助用户打造一个完美的私人影视库。

但在使用过程中,我发现了一个尴尬的痛点:网易爆米花原生支持天翼云盘,却偏偏把夸克网盘挡在了门外。

如果想在软件里调取夸克的资源,目前唯一的办法就是通过 WebDAV 挂载。

于是我开始琢磨,要不要整一个 24 小时在线、且不占用本地资源的 WebDAV 服务端呢?

Hugging Face

Hugging Face

以下是 Gemini 对 Hugging Face(以下简称 HF) 的一些简单介绍:

  • HF 是全球顶尖的 AI 协作平台,被誉为 AI 界的 GitHub。它通过开源的 Transformers 库 统一了模型调用标准,并提供 Model Hub(模型库)、Datasets(数据集)和 Spaces(应用演示)三大核心能力。它打破了巨头的技术垄断,让开发者能像搭积木一样,轻松调用、分享和部署最前沿的自然语言处理、计算机视觉及音频模型,是当今生成式 AI 生态赖以生存的基础设施。

说实话,我对 HF 其实没有太深入的了解。最早关注到它,也是因为我在询问 Gemini “有哪些地方能在线免费部署 OpenList”时,Gemini 向我推荐了这里。

虽然如介绍所言,HF 在 AI 领域的名气似乎比较大,但我目前的视角很简单:HF 的 Spaces 能为我提供一个稳定、便捷且免费的容器化托管环境。

而在我实际尝试之后,我发现 HF 的 Spaces 确实已经足够满足我云端部署 OpenList 的需求。它不用我去折腾 VPS,也省去了折腾 VPS 所带来的那些维护问题,更不用我去烦恼各类云服务复杂的计费规则。

这种简单、纯粹的托管方式,能让我把 OpenList 顺利地部署到云端,作为一个 24 小时待命的 WebDAV 服务中心,随时响应网易爆米花或其他客户端的调用。

就目前而言,这大概是我能找到的,最省心也最贴合需求的云端部署方案了,何况它还是免费的。

使用 Hugging Face 部署 OpenList 会有什么问题?

既然是“白嫖” HF 的资源,自然会有一些条条框框的限制。在实际把 HF 里的 OpenList 当作 WebDAV 服务端使用的过程中,我遇到了几个比较棘手的问题:

  1. 无法自定义域名

HF 的免费 Space 是不支持自定义域名的。这意味着你只能使用类似 user-repo.hf.space 这种长串的二级域名。如果你想用自己的顶级域名来访问,官方给出的唯一方案是:升级到 PRO 订阅。

对于我这种追求“白嫖”到极致的人来说,为了一个 WebDAV 服务去按月交钱,显然不符合折腾的初衷。

  1. 自动休眠与配置归零

这是最头疼的一点。HF 的免费实例有一个“休眠机制”:如果 48 小时内没有流量访问,容器就会自动关机。而一旦它睡着,非持久化的文件系统就会重置。

于是,一个尴尬的死循环就出现了:

  • 为了省心,我把 OpenList 丢在云端
  • 结果我两天没看电影,容器休眠了
  • 等我第三天想看时,好不容易唤醒了它,却发现之前挂载的网盘账号、系统设置全丢了。

如果按照常规思路,每次看电影前都得去重配一遍,那这方案就不是“巧思”而是“受罪”了。

虽然 OpenList 后台自带备份与恢复功能,但每次唤醒都要登录后台、手动上传备份、等待恢复……这种割裂感完全违背了我的初衷:

“我只是想看一下电影,我有什么错!”(哈哈哈哈🤣)

我要的不是一个能“手动抢救”的备份,而是一个“随时可用、无需重复配置”的影视库。

部署 OpenList 到 Hugging Face

部署 OpenList 到 HF 其实也比较简单。

  1. 在 HF Space 里的项目里新建一个空间,名称乱取即可(来自L站佬友@黑猫警长的建议,避免HF封号警告)

  2. 创建Dockerfile

FROM openlistteam/openlist:latest
WORKDIR /opt/openlist
USER root
EXPOSE 5244

CMD sh -c "\
    ./openlist server & PID=\$!; \
    \
    echo '⏳ 正在启动服务...'; \
    i=0; while ! wget -q --spider http://127.0.0.1:5244/api/public/settings; do \
        sleep 2; i=\$((i+1)); \
        [ \$i -ge 15 ] && echo '❌ 服务启动超时' && kill \$PID && exit 1; \
    done; \
    \
    [ -z \"\$OPENLIST_ADMIN_PASSWORD\" ] && echo '❌ 未设置密码' && exit 1; \
    \
    echo '🔑 正在登录...'; \
    TOKEN=\$(wget -qO- --post-data=\"{\\\"username\\\":\\\"admin\\\",\\\"password\\\":\\\"\$OPENLIST_ADMIN_PASSWORD\\\"}\" \
          --header='Content-Type: application/json' http://127.0.0.1:5244/api/auth/login 2>/dev/null | grep -o '\"token\":\"[^\"]*\"' | cut -d'\"' -f4); \
    \
    if [ \${#TOKEN} -gt 20 ]; then \
        echo '🚀 登录成功'; \
        for n in 1 2 3 4 5 6 7 8 9 10; do \
            BODY=\$(printenv STORAGE_JSON_\$n); \
            [ -n \"\$BODY\" ] || continue; \
            \
            echo \"\$BODY\" > /tmp/p.json; \
            if grep -q '\"mount_path\"' /tmp/p.json; then \
                printf \"📦 配置 \$n: \"; \
                wget -qO- --post-file=/tmp/p.json \
                     --header=\"Content-Type: application/json\" \
                     --header=\"Authorization: \$TOKEN\" \
                     http://127.0.0.1:5244/api/admin/storage/create 2>/dev/null | grep -o '\"message\":\"[^\"]*\"' || echo '完成'; \
            fi; \
            rm -f /tmp/p.json; \
        done; \
        echo '✅ 所有初始化任务已完成!'; \
    else \
        echo '❌ 认证失败'; kill \$PID; exit 1; \
    fi; \
    wait \$PID"
  1. 在 Space 设置里创建 Secrets
环境变量名称环境变量介绍
OPENLIST_ADMIN_PASSWORD通过环境变量直接指定 OpenList 的管理员密码
STORAGE_JSON_1填入从 OpenList 后台导出的第一个存储配置 JSON 字符串
STORAGE_JSON_2如果有第二个网盘,填入此处(脚本目前支持设置到10)

解决配置归零

通过 Dockerfile 里的脚本以及 STORAGE_JSON 的环境变量,我们可以先解决最重要的问题:伴随自动休眠所导致的配置归零。

针对我的需求来说,我基本不需要频繁调整 OpenList 的配置。通常在首次启动后,我只需要设置好密码并挂载上网盘,剩下的就是等很久以后网盘 Cookie 过期了才去更新一下。

所以我并没有选择使用监控(如 UptimeRobot 等)每隔一段时间去“戳”一下容器以维持运行。这种保活手段在互联网上虽然很常用,但对我来说更偏向“治标不治本”的外部手段。

与其费劲心思不让它“入睡”,我更倾向于让 OpenList “忘记”它曾经“初始化”过,从而实现逻辑上的“持久化配置”。

我选择了一个更原生的方案:将核心的存储配置以 JSON 格式保存在 HF 的 Secrets 中。每次容器从休眠中被唤醒启动时,通过 Dockerfile 里的逻辑,全自动地把这些配置“喂”给 OpenList。

这意味着:即便容器文件系统重置了,只要它再次启动,就会瞬间从 Secrets 中读回所有网盘挂载信息,实现“原地复活”。

STORAGE_JSON 格式示例

  1. 导出原始配置

在 OpenList 管理后台点击 “备份” 后,你会得到一个 JSON 配置文件。请在该文件中定位到 storages 数组,我们要提取的就是其中每一个独立的存储对象,具体结构类似以下:

{
  "storages": [
  {
    "id": 1,
    "mount_path": "/189", 
    "order": 0,
    "driver": "189CloudPC",
    "cache_expiration": 30,
    "custom_cache_policies": "",
    "status": "work",
    "addition": "{\"login_type\":\"\",\"username\":\"xxx\",\"password\":\"xxx\",\"validate_code\":\"\",\"refresh_token\":\"xxx\",\"root_folder_id\":\"-11\",\"order_by\":\"filename\",\"order_direction\":\"asc\",\"type\":\"personal\",\"family_id\":\"xxx\",\"upload_method\":\"stream\",\"upload_thread\":\"3\",\"family_transfer\":false,\"rapid_upload\":false,\"no_use_ocr\":false}",
    "remark": "",
    "modified": "2026-03-03T12:07:21.141247771Z",
    "disabled": false,
    "disable_index": false,
    "enable_sign": false,
    "order_by": "",
    "order_direction": "",
    "extract_folder": "",
    "web_proxy": false,
    "webdav_policy": "302_redirect",
    "proxy_range": false,
    "down_proxy_url": "",
    "disable_proxy_sign": false
  },
  {
    "id": 2,
    "mount_path": "/quark",
    "order": 0,
    "driver": "Quark",
    "cache_expiration": 30,
    "custom_cache_policies": "",
    "status": "work",
    "addition": "{\"cookie\":\"ctoken\",\"root_folder_id\":\"0\",\"order_by\":\"none\",\"order_direction\":\"asc\",\"use_transcoding_address\":false,\"only_list_video_file\":false,\"AdditionVersion\":2}",
    "remark": "",
    "modified": "2026-03-03T12:07:26.143220307Z",
    "disabled": false,
    "disable_index": false,
    "enable_sign": false,
    "order_by": "",
    "order_direction": "",
    "extract_folder": "",
    "web_proxy": true,
    "webdav_policy": "native_proxy",
    "proxy_range": false,
    "down_proxy_url": "",
    "disable_proxy_sign": false
  }
  ]
}  
  1. 选取注入内容

从 storages 数组中,选择你想要自动挂载的项。

检查步骤:

  • { 开始,用 } 结束

  • 确保 { 之前和 } 之后没有从原数组里带出来的逗号

  • 检查对象内部最后一个字段后面是否有逗号。

  • 请勿完整复制 {"storages": [...]}

  • 推荐手动删除 "id": 1, 这样系统会自动分配 ID,避免冲突。

❌ 错误示例(末尾带有逗号)

{
  "id": 1,  /* 推荐手动删除 */
  "mount_path": "/189",
  /* 其他字段保持不变 */
  "disable_proxy_sign": false
},  /* <-- 这个末尾逗号会导致注入失败!*/

✅ 正确示例(无 ID 字段,干净的闭合)

{
  "mount_path": "/189",
  /* 其他字段保持不变 */
  "disable_proxy_sign": false
}
  1. 环境变量填写

在 HF 的 Settings -> Secrets 中:

变量名:STORAGE_JSON_1(第二个用 STORAGE_JSON_2,依此类推)。

变量值:直接粘贴截取的 { … } 内容。

为什么不将网盘信息放在仓库里?

原因在于当我尝试把 Space 设为 Private(私有),想直接将敏感信息存在仓库中时,发现这会带来一个尴尬的问题:访问权限的死锁。

在 HF 的机制下,Private Space 的访问是有门槛的:

  • 浏览器访问受限:如果你换个浏览器,或者在没登录 HF 账号的设备上打开,你会直接看到 401 错误或登录提醒,根本进不去 OpenList 的界面。

  • WebDAV 彻底瘫痪:这是最致命的。网易爆米花、Infuse 或者播放器是通过 WebDAV 协议去“撞”地址的。如果 Space 是私有的,HF 会在最外层加一把鉴权大锁,你的播放器根本拿不到数据,除非你在播放器端处理复杂的 HF 登录态(这几乎不可能)。

所以,为了让 WebDAV 能 24 小时随时随地调取资源,Space 必须设为 Public。

但 Public 意味着“裸奔”,仓库里的文件谁都能看。于是,我选择了“代码公开 + 存储配置存入 Secrets + 启动动态注入”这样的方案,来当成我唯一的标准答案:

  • 对外:它是 Public 的。播放器和 WebDAV 客户端可以顺着地址直接访问到 OpenList 的服务,无需通过 HF 的账号登录拦截。

  • 对内:它是加密的。即便别人顺着地址摸到我的代码仓库,他也只能看到一堆处理逻辑。核心的网盘 Token、账号和密码全部锁在只有我能看到的 Secrets 保险箱里。

通过 Cloudflare Workers 反代实现自定义域名

正如前面所述,HF 的免费 Space 是不支持自定义域名的。

如果你和我一样,希望在不额外支付 PRO 订阅的前提下,给 OpenList 套上一个体面的顶级域名(比如 dav.yourdomain.com)。

那么,就让我们祭出另一件“白嫖界”的神器:Cloudflare Workers。

通过 CF Workers,我们不仅能绕过 HF 的域名限制,给 OpenList 套上自己的顶级域名,还能顺便处理掉 WebDAV 常见的跨域和握手问题。

HF 域名在哪里查看

你可以在 HF 的 Space 页面里点击右上角三个点里的 “Embed this Space”里,找到你的 Space 域名。

HF 域名查看

为什么要折腾这一步?

  • 摆脱长尾域名(最主要的原因):那一串 *.hf.space 不仅难记,在某些网络环境下访问也不够顺畅。

  • 解决 WebDAV 握手难题:避免播放器(如网易爆米花、Infuse)在连接 WebDAV 时对跨域(CORS)和请求头有严格要求,从而导致直接直连 HF 有时会因为网关限制而挂载失败的问题。

  • 隐藏上游地址:所有的流量和请求都通过 CF 的边缘节点中转,既能加速,又能隐藏真实的 Space 运行地址。

核心代码实现

我在 CF Worker 中实现了一套逻辑,它不仅负责域名的“换壳”,更重要的是它会伪装请求头(让 HF 以为是直连访问),并注入全量的 WebDAV 跨域头:

export default {
  /**
   * @param {{ url: string | URL; headers: HeadersInit; method: any; body: any; }} request
   * @param {any} env
   */
  async fetch(request, env) {
    const upstream = '*.hf.space'; // 你的 HF 域名
    const url = new URL(request.url);
    
    // 替换域名
    url.host = upstream;
    url.protocol = 'https:'; // 强制 HTTPS

    // 复制原始请求的 Headers,防止只读限制
    const newHeaders = new Headers(request.headers);
    
    // 注入关键 Header,让 HF 以为是直连访问
    newHeaders.set('Host', upstream);
    newHeaders.set('Origin', `https://${upstream}`);
    newHeaders.set('Referer', `https://${upstream}`);
    
    // 构造新的请求
    const newRequest = new Request(url.toString(), {
      method: request.method,
      headers: newHeaders,
      body: request.body,
      redirect: 'follow'
    });

    try {
      const response = await fetch(newRequest);
      
      // 处理跨域,方便 Webdav 客户端握手
      const newResponseHeaders = new Headers(response.headers);
      newResponseHeaders.set('Access-Control-Allow-Origin', '*');
      newResponseHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK');
      newResponseHeaders.set('Access-Control-Allow-Headers', '*');

      return new Response(response.body, {
        status: response.status,
        statusText: response.statusText,
        headers: newResponseHeaders
      });
    } catch (e) {
      return new Response('Error: Upstream connection failed. ' + e.message, { status: 502 });
    }
  }
};

“小巧思”的价值

至此,整套方案终于严丝合缝地闭合了:

  • 成本 0 元:全程利用 HF 的容器额度和 CF Workers 的每日免费 10 万次请求额度,个人观影完全溢出。

  • 原地复活:通过环境变量注入,解决了 HF 自动休眠导致的“失忆”问题。

  • 访问体面:拥有了自定义域名,播放器刮削顺滑,海报墙秒开。

正如我在前言里说的,这套方案确实只是“小巧思”。它更适合那些对 OpenList 需求不大、但又希望有一个轻量云端方案的用户。如果你也想在不折腾 VPS 的前提下,优雅地解决云端部署问题,希望我的这段折腾经历能给你一点启发。

评论区