Filestash 配置参数详解
Filestash 配置体系实测详解:/api/config 与 /admin/api/config 双端点、全量参数 Schema 清单、在线修改四参数行为验证、全量替换清空连接的事故回放与还原基线(v0.6.20261022,2026-10-22 实测)。
Filestash 配置参数详解
Filestash 的全部行为都由一份集中式配置驱动:管理员密码、存储后端连接、界面偏好、日志级别、共享策略、Office 协同……都落在同一份配置里。与多数软件"改配置文件 + 重启"不同,Filestash 的配置可以通过管理 API 在线修改并立即生效(无需重启进程)。本文是 Filestash 专栏的配置专题:完整给出配置体系的两个 API 端点、落盘文件位置、全部参数的 Schema 清单与字段语义,并对四个有代表性的参数做"在线修改 → 行为验证 → 还原"的全流程实测,包括一次真实的"全量替换把存储后端连接清空"的事故回放。全部命令与回显均来自真实服务器执行记录(版本 v0.6.20261022,实测日期 2026-10-22)。
相关阅读:《Ubuntu 22.04 安装 Filestash 指南》完成了安装、systemd 托管与初始化(设置管理员密码、添加存储后端);本文在其基础上展开"配置到底有哪些、怎么改、改了会发生什么"。
一、环境信息说明
| 项目 | 值 |
|---|---|
| 演示服务器 | filestash-server(10.89.57.2),独立 Ubuntu 22.04 服务器 |
| 操作系统 | Ubuntu 22.04.5 LTS(x86_64) |
| 服务端口 | 8334 |
| Filestash 版本 | v0.6.20261022(构建 commit 244e8b74ebdf60cdbd650a24c1f22569d839a814,从 /api/config 的 version 字段读取) |
| 配置落盘文件 | /opt/filestash/data/state/config/config.json |
| 管理员账号 | admin / q1w2e3r4(本文所有管理操作均以此身份登录) |
| 存储后端 A | SFTP 服务器 10.89.57.3(OpenSSH,账号 root / q1w2e3r4) |
| 存储后端 B | WebDAV 服务器 10.89.57.4:8080(wsgidav,账号 admin / q1w2e3r4) |
| 连接标签 | connections 中已配置 {type: sftp, label: SFTP 服务器} 与 {type: webdav, label: WebDAV 服务器} |
版本与 commit 的取证方式(/api/config 无需认证,响应中直接带有版本信息):
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | grep -E '"(version|license|name)"'
"license": "agpl",
"name": "Filestash",
"version": "244e8b74ebdf60cdbd650a24c1f22569d839a814",
二、配置体系总览:两个端点 + 一个落盘文件
Filestash 的配置管理由三部分组成:
| 组成 | 地址/路径 | 谁在用 | 说明 |
|---|---|---|---|
| 前台配置端点 | GET /api/config | Web 前端、脚本 | 公开只读,无需认证。返回前端需要的配置子集 + 内置 MIME 注册表(约 308 个扩展名映射)+ 版本号 |
| 管理配置端点 | GET/POST /admin/api/config | 管理后台 /admin/、运维脚本 | 需要管理员会话(见第三节)。GET 返回全量参数 Schema;POST 提交修改 |
| 配置落盘文件 | /opt/filestash/data/state/config/config.json | Filestash 进程 | 唯一事实源。通过 API 提交的修改立即写入该文件并即时生效,无需重启 |
三者关系:管理员在 Web 后台 /admin/ 界面上做的每一次修改,底层就是一次 POST /admin/api/config;POST 成功后配置写入 config.json 并立刻影响运行中的进程。因此"改配置"与"脚本化改配置"走的是同一条路,本文以 API 视角完整实测。
/api/config(前台端点)返回内容节选——注意它的键名是"前端视角"的扁平命名(如 default_view、enable_share),与后台 Schema 的层级命名(general.filepage_default_view、features.share.enable)存在对应关系:
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | head -20
{
"result": {
"auth": [],
"connections": [
{"label": "SFTP 服务器", "type": "sftp"},
{"label": "WebDAV 服务器", "type": "webdav"}
],
"default_sort": "type",
"default_view": "grid",
"display_hidden": false,
"editor": "emacs",
"enable_chromecast": true,
"enable_search": true,
"enable_share": true,
"enable_tags": true,
"license": "agpl",
"logout": "",
...(mime 注册表,约 308 个扩展名映射)...
"name": "Filestash",
"open_mode": "single_click",
"origin": "",
"refresh_after_upload": false,
"share_default_access": "editor",
"share_redirect": "",
"thumbnailer": ["image/jpeg", "image/png", "image/gif"],
"upload_button": false,
"upload_chunk_size": 0,
"upload_pool_size": 0,
"version": "244e8b74ebdf60cdbd650a24c1f22569d839a814"
},
"status": "ok"
}
字段对应关系(实测验证,见第八节):default_view ← general.filepage_default_view,display_hidden ← general.display_hidden,enable_share ← features.share.enable,share_default_access ← features.share.default_access,name ← general.name,connections ← 配置里的 connections 数组。
三、管理会话与访问控制
3.1 未登录访问管理端点:403
/admin/api/config 受管理员口令保护。未携带会话直接访问,返回 403:
$ curl -s -D- http://127.0.0.1:8334/admin/api/config
HTTP/1.1 403 Forbidden
Content-Type: text/plain
...
同一时刻服务端日志(/opt/filestash/data/state/log/access.log)中的对应记录:
2026/10/22 04:50:14 HTTP 403 GET 0.0ms /admin/api/config
3.2 建立管理会话
用管理员密码 POST /admin/api/session 建立会话(Cookie 保持):
$ curl -s -c /tmp/admin.cookie -H 'X-Requested-With: XmlHttpRequest' \
-H 'Content-Type: application/json' \
-d '{"password": "q1w2e3r4"}' \
http://127.0.0.1:8334/admin/api/session
{"status": "ok", "result": true}
日志中的对应记录(注意耗时约 1.5 秒——这是服务端做 bcrypt 口令校验的成本,属正常现象而非卡顿):
2026/10/22 04:44:34 HTTP 200 POST 1538.7ms /admin/api/session
会话建立后,同一 Cookie 连接即可 GET /admin/api/config。管理 API 还要求请求头 X-Requested-With: XmlHttpRequest(与前台 API 一致),缺失时同样会被拒绝。
四、配置项 Schema 结构详解
GET /admin/api/config 返回的不是"当前值"那么简单,而是全量参数的 Schema 表单:每个参数都带标签、类型、描述、默认值与当前值。这是理解 Filestash 配置的钥匙——官方把配置做成了"自描述表单",Web 管理后台就是拿这份 Schema 渲染出来的。
4.1 参数节点的公共字段
每个叶子参数节点都包含以下字段(以 general.display_hidden 为例):
"display_hidden": {
"label": "display_hidden",
"type": "boolean",
"description": "Should files starting with a dot be visible by default?",
"readonly": false,
"default": false,
"value": null,
"required": false
}
| 字段 | 含义 | 实测备注 |
|---|---|---|
label | 参数名 | Web 后台的表单标签 |
type | 数据类型 | 见 4.2 类型表 |
description | 官方描述(英文原文) | 最权威的参数说明来源 |
placeholder | 输入提示 | 部分参数带有默认值提示,如 Default: 8334 |
readonly | 是否只读 | constant 节点下的参数为 true |
default | 默认值 | 未显式配置时生效的值 |
value | 当前值 | null 表示未显式设置、跟随 default |
required | 是否必填 | general.secret_key 为 true |
options | 可选项列表 | 仅 select 类型有,如 filepage_default_view 的 [list, grid] |
pattern | 值的正则约束 | secret_key 为 [a-zA-Z0-9]{16} |
id | 跨层引用 ID | 被 enable 型开关的 target 引用(见 4.3) |
target | 联动目标列表 | 仅 enable 型开关有(见 4.3) |
value: null 的语义是本文最重要的一个 Schema 结论:null 不代表"空",而是"跟随默认值"。落盘文件里对应键也是 null(见第九节还原基线)。修改参数 = 把 value 从 null 改成显式值;还原参数 = 把它改回 null。
4.2 参数类型一览(9 种)
| type | 含义 | 实例 |
|---|---|---|
text | 单行文本 | general.name、general.host |
number | 数字 | general.port、general.cookie_timeout |
boolean | 布尔开关 | general.display_hidden、log.telemetry |
select | 单选(带 options) | log.level(DEBUG/INFO/WARNING/ERROR)、general.editor(base/emacs/vim) |
password | 密码/密钥 | general.secret_key、email.password |
long_text | 多行文本 | general.custom_css |
enable | 功能总开关(带 target 联动) | features.share.enable、log.enable |
hidden | 隐藏字段(不在后台表单渲染) | middleware.identity_provider.type/params |
bcrypt | bcrypt 口令哈希 | auth.admin |
4.3 enable 型开关的 target 联动机制
enable 型参数是"功能总开关",其 target 字段列出被它管辖的子参数,被管辖的子参数用 id 标识。以日志为例:
"log": {
"enable": {
"label": "enable", "type": "enable",
"target": ["log_level"], // 管辖 id 为 log_level 的参数
"default": true, "value": null, ...
},
"level": {
"id": "log_level", // 被 log.enable 的 target 引用
"label": "level", "type": "select",
"options": ["DEBUG", "INFO", "WARNING", "ERROR"],
"default": "INFO", "value": null, ...
},
...
}
同样的结构出现在 features.workflow.enable(target: workflow_workers / workflow_max_queue)、features.office.enable(target: office_server / filestash_server / rewrite_discovery_url)、features.site.enable(target: site_autoindex / site_cors_allow_origins)、features.video.enable_transcoder(target: transcoding_blacklist_format / transcoding_video_encoder)。工程含义:关掉总开关后,其 target 子参数即失效,无需逐个清理。
五、general 参数全表(20 项)
以下全部字段取自 GET /admin/api/config 实测返回(2026-10-22,v0.6.20261022)。"默认值"列即 Schema 的 default;"当前值"列为实测环境初始状态(null = 跟随默认)。
| 参数 | 类型 | 默认值 | 官方描述(译注) |
|---|---|---|---|
| general.name | text | Filestash | 界面上显示的站点名称 |
| general.port | number | 8334 | 服务监听端口 |
| general.host | text | null | 用户访问本服务所用的主机名(影响对外链接生成) |
| general.secret_key | password(required) | 首启随机生成 16 位 | 加解密密钥。修改会使既有用户会话与共享链接全部失效 |
| general.force_ssl | boolean | null | 启用 HSTS(Strict Transport Security) |
| general.editor | select | emacs | 编辑器键位:base / emacs / vim |
| general.logout | text | ""(空串) | 点击退出按钮后的跳转 URL |
| general.display_hidden | boolean | false | 是否默认显示点开头的隐藏文件 |
| general.refresh_after_upload | boolean | false | 上传完成后刷新目录列表 |
| general.open_mode | select | single_click | 文件/文件夹打开方式:single_click / double_click |
| general.upload_button | boolean | false | 在任何设备上显示上传按钮 |
| general.upload_pool_size | number | 15 | 并行上传文件数上限 |
| general.upload_chunk_size | number | 0 | 上传分块大小(MB) |
| general.buffer_size | select | medium | 传输 I/O 缓冲:small / medium / large(大缓冲提升高带宽网络吞吐,占用更多内存) |
| general.filepage_default_view | select | grid | 文件页默认布局:list / grid |
| general.filepage_default_sort | select | type | 文件页默认排序:type / date / name |
| general.cookie_timeout | number | 10080 | 认证 Cookie 有效期(分钟),默认 60×24×7 = 一周 |
| general.extended_session | boolean | false | 会话中存储额外认证数据 |
| general.custom_css | long_text | "" | 站点自定义 CSS |
| general.license | text | null | 商业许可密钥(联系 support@filestash.app 获取) |
重点解读三条:
- general.secret_key 是整个系统的信任根:用户会话 Cookie 与共享链接 URL 都用它加密/签名。描述原文明确警告 "Update this settings will invalidate existing user sessions and shared links, use with caution!"(修改此项将使现有用户会话与共享链接失效,谨慎操作)。改它等于全员登出 + 全部共享链接作废,变更窗口要规划好。
- general.host 与共享链接/回调有关:填错会导致生成的对外链接不可达;反代场景下应填用户实际访问的域名。
- general.cookie_timeout 控制登录态寿命:默认 10080 分钟(一周)。对安全敏感的部署建议按需调小。
六、features 参数组全表(11 组 28 项)
6.1 share(共享)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| features.share.enable | enable | true | 启用/禁用共享功能(target 无子参数;实测仅影响前端入口,见 8.4) |
| features.share.default_access | select | editor | 共享链接默认权限:editor / viewer |
| features.share.redirect | text | null | 共享链接重定向模板,如 https://example.com?full_path={{path}} |
6.2 protection(防护)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| features.protection.enable | enable | true | 针对扫描器的主动防护 |
| features.protection.zip_timeout | number | 60 | 打包下载/解压 zip 的超时(秒) |
| features.protection.disable_svg | boolean | true | 禁用 SVG 展示(防 XSS,placeholder 注明 Default: true) |
| features.protection.disable_csp | boolean | false | 关闭内容安全策略(除非完全信任存储内容,否则不应开启) |
| features.protection.iframe | text | "" | 允许 iframe 嵌入本站的域名列表,如 https://example.com |
| features.protection.signature | text | "" | 强制对认证 URL 参数验签 |
| features.protection.enable_chromecast | boolean | true | 允许投屏到 Chromecast(浏览器需访问 Google 服务器下载 SDK) |
6.3 其余功能组
| 组 | 参数(类型/默认值) | 说明 |
|---|---|---|
| api | features.api.enable(boolean / true) | 启用/禁用 REST API |
| collaborative | features.collaborative.enable(enable / true) | 协同编辑 |
| workflow | features.workflow.enable(enable / true);workers(number / 1);max_queue(number / 5) | 工作流引擎总开关;并行 worker 数;每工作流最大排队任务数 |
| office | features.office.enable(enable / false);office_server(text / http://127.0.0.1:9980);filestash_server(text / http://app:8334);rewrite_discovery_url(text / "") | WOPI Office 协同:Office 服务器地址(Collabora 默认 9980);Office 服务器回访 Filestash 的地址;浏览器侧 Discovery URL 重写(反代/编排部署用) |
| mcp | features.mcp.enable(enable / false) | Model Context Protocol 网关(供 AI Agent 访问文件) |
| site | features.site.enable(enable / false);autoindex(boolean / false);cors_allow_origins(text / null) | 通过共享链接建站,站点挂在 /public/{shareID}/;无 index 文件时是否自动列目录;CORS 允许来源(* 或逗号分隔列表) |
| search | features.search.explore_timeout(number / 1500) | 全文搜索未启用时,递归探索目录的时限(毫秒,默认 1500ms) |
| video | features.video.enable_transcoder(enable / true);encoder(select / libx264,可选 libx264 / h264_vaapi / h264_nvenc / h264_v4l2m2m);blacklist_format(text / "") | 按需 HLS 转码(依赖 ffmpeg,未装则自动禁用并打 WARN 日志);硬件编码器选择;不转码的格式黑名单 |
| server | features.server.console_enable(boolean / false) | 内置交互式 Web 控制台,开启后位于 /admin/tty/(用户名 admin,密码同管理后台) |
七、log / email / auth / middleware / constant
7.1 log(日志)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| log.enable | enable | true | 日志总开关(target: log_level) |
| log.level | select | INFO | 日志级别:DEBUG / INFO / WARNING / ERROR。实测 DEBUG 级别的两个可见变化见 8.3 |
| log.telemetry | boolean | false | 匿名遥测(官方描述明确:不与任何第三方共享,仅用于改进软件) |
日志的落盘位置与逐行格式解读(HTTP 请求行、AUDIT 审计行、SYST 系统行的完整分析)见本专栏下一篇《Filestash 运维管理与 API 实操详解》。
7.2 email(SMTP 邮件,5 项)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| email.server | text | smtp.gmail.com | SMTP 服务器地址 |
| email.port | number | 587 | SMTP 端口 |
| email.username | text | null | SMTP 认证用户名 |
| email.password | password | null | SMTP 认证密码 |
| email.from | text | null | 发件人地址(收件方可见) |
配置后用于共享链接的邮件验证等通知场景(共享访问页存在 email 验证步骤)。
7.3 auth / middleware / constant
"auth": {
"admin": {
"label": "admin", "type": "bcrypt",
"description": "Password of the admin section.",
"default": "", "value": "$2b$10$hJXK4pqyc9DGDizo6Rp7/Ojc8Tqds5KDSCgGqL/zUDsBE9cUxdCpK", ...
}
}
- auth.admin(bcrypt 类型):管理后台口令,存的是 bcrypt 哈希(
$2b$10$...,cost=10——这也解释了 3.2 节登录耗时约 1.5 秒)。安装初始化时设置,后续可在后台更换。 - middleware.identity_provider / middleware.attribute_mapping(hidden 类型,各含 type/params 或 related_backend/params 子字段):认证与属性映射插件的挂载点。默认未配置任何插件(
/healthz首启报的middleware.identity_provider[type=][params=0]即指这里),安装篇采用的就是"无认证插件 + 本地连接列表"的最简形态。 - constant(readonly=true,运行时常量,不可改):
"constant": {
"user": {"type": "boolean", "readonly": true, "value": "filestash"},
"license": {"type": "text", "readonly": true, "value": "agpl"}
}
constant.user 是运行进程的操作系统账号(安装篇创建的 filestash 系统用户),constant.license 是当前许可形态(agpl)。两者只读,用于后台页面展示。
八、配置修改实操与四个参数的行为验证
8.1 修改流程:GET Schema → 提交全量 → 验证
POST /admin/api/config 的请求体是整份配置的"压平-展开"形态:把 Schema 树里的每个叶子节点取 value 组成同构 JSON 提交。实测采用的压平规则(与安装篇初始化脚本一致):
import json, urllib.request, urllib.error, http.cookiejar
cj = http.cookiejar.CookieJar()
op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
H = {"Content-Type": "application/json", "X-Requested-With": "XmlHttpRequest", "User-Agent": "Mozilla/5.0"}
BASE = "http://127.0.0.1:8334"
def call(method, url, payload=None, extra=None):
data = json.dumps(payload).encode() if payload is not None else None
hh = dict(H); hh.update(extra or {})
try:
r = op.open(urllib.request.Request(url, data=data, headers=hh, method=method))
return r.status, json.loads(r.read().decode())
except urllib.error.HTTPError as e:
return e.code, e.read().decode().strip()[:200]
def flatten(o):
"""取每个叶子节点的 value;number 类型转 int"""
out = {}
for k, v in o.items():
if isinstance(v, dict) and all(kk in v for kk in ("label", "type", "default", "value")):
val = v["value"]
if v["type"] == "number":
try: val = int(val)
except Exception: val = 0
out[k] = val
elif isinstance(v, dict): out[k] = flatten(v)
else: out[k] = v
return out
def admin_post(changes):
"""changes 形如 {"general.display_hidden": True};按点分路径嵌套写入"""
st, adm = call("GET", BASE+"/admin/api/config")
b = flatten(adm["result"])
b["connections"] = [{"type": "sftp", "label": "SFTP 服务器"},
{"type": "webdav", "label": "WebDAV 服务器"}] # 关键!见 8.2
for dotted, v in changes.items():
parts = dotted.split("."); node = b
for p in parts[:-1]: node = node[p]
node[parts[-1]] = v
return call("POST", BASE+"/admin/api/config", b)
三个实测要点:
- number 类型必须转 int:Schema 里 number 的
value可能是字符串或 0,直接提交字符串会被拒绝或落成意外值,压平时统一int()。 - 修改要基于"最新一次 GET"的压平结果:先 GET 再改再 POST,避免用过期快照覆盖别人的修改。
connections必须手工带上——这是本次实测踩到的最大的坑,单独成节。
8.2 事故回放:全量替换把存储后端连接清空
POST /admin/api/config 的语义是全量替换而非增量合并:请求体里没有的键,按"还原默认"处理。而压平 Schema 得到的 body 里根本没有 connections 键(4.1 节的 Schema 树不回显连接列表)。实测中第一次提交修改时没有手工补上 connections,后果如下:
## 修改提交成功(但 body 里没有 connections)
2026/10/22 04:52:42 HTTP 200 POST 0.7ms /admin/api/config
## 立即查看前台配置:connections 变成了空数组
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | grep -A1 connections
"connections": [],
## 已登录的 SFTP 会话随即失效,文件列表接口返回 403
2026/10/22 04:52:42 SYST DEBUG middleware::session::start action=extractBackend error="Not Allowed"
2026/10/22 04:52:42 HTTP 403 GET 0.0ms /api/files/ls?path=/srv/share/
修复方式:重新 POST /admin/api/config,body 中补上 connections 数组(8.1 脚本第 27-28 行),提交后连接立即恢复、原 SFTP 会话继续可用。
教训:脚本化修改配置时,POST body 必须包含 connections、secret_key、auth.admin 这三类"不在 Schema 表单里回显、但参与全量替换"的数据。8.1 的 admin_post() 已把它们固定在流程里。
8.3 log.level:INFO → DEBUG 的两个可见变化
把 log.level 从默认 INFO 改为 DEBUG 并立即还原(期间发起 4 个请求):
>>> admin_post({"log.level": "DEBUG"})
(200, {'status': 'ok'})
>>> admin_post({"log.level": None})
(200, {'status': 'ok'})
观察 access.log(tail -n 8,时间均为 04:59:46):前 4 行是 DEBUG 生效期间的请求,每行末尾都追加了一段 32 位随机串 trace=<...>;后 4 行恢复 INFO 后不再携带:
2026/10/22 04:59:46 HTTP 200 GET 0.7ms /admin/api/config
2026/10/22 04:59:46 HTTP 200 POST 1.1ms /admin/api/config trace=58sl838M4OgNd7sJMraOqAriiVPvu2wa
2026/10/22 04:59:46 HTTP 200 GET 0.1ms /healthz
2026/10/22 04:59:46 HTTP 200 GET 0.1ms /api/config trace=krSKhWAgKa3XvhCjPRvB1dAmLsfxGKk1
2026/10/22 04:59:46 HTTP 200 GET 0.3ms /admin/api/config trace=Zwq6V8BHjZIltBO1Y32AczsvB2eyWkrN
2026/10/22 04:59:46 HTTP 200 POST 0.7ms /admin/api/config
2026/10/22 04:59:46 HTTP 200 GET 0.1ms /healthz
2026/10/22 04:59:46 HTTP 200 GET 0.1ms /api/config
第二个变化:DEBUG 级别下,内部中间件的异常细节会写入日志(INFO 级别不出现)。8.2 事故窗口里的这行就是 DEBUG 级别的产物:
2026/10/22 04:52:42 SYST DEBUG middleware::session::start action=extractBackend error="Not Allowed"
运维结论:排障时临时把 log.level 切到 DEBUG,可以用 trace 串把一次请求的全部日志行串起来(trace 同时出现在请求行与异常行);排查完记得还原(还原写法是把 value 提交回 null,即跟随默认 INFO)。
8.4 features.share.enable:只关前端入口,API 不设防
先建立共享(用 SFTP 会话),再禁用共享功能,对比行为。创建共享的 API 是 PUT /api/share/<自定义ID>(注意不是 /api/files/share,后者实测返回 404——路径探测记录见 8.5):
## 1. 创建共享(viewer 角色:只读)
$ curl -s -X PUT -H 'X-Requested-With: XmlHttpRequest' -H 'Content-Type: application/json' \
-H "Authorization: Bearer <SFTP会话令牌>" \
-d '{"path": "/srv/share/docs/", "can_read": true, "can_write": false, "can_upload": false}' \
"http://127.0.0.1:8334/api/share/c1tst001"
{"status": "ok"}
## 2. 列出该路径的共享
$ curl -s -H "Authorization: Bearer <SFTP会话令牌>" \
"http://127.0.0.1:8334/api/share?path=/srv/share/docs/"
{"status": "ok", "results": [{"id": "c1tst001", "path": "/", "can_read": true, "can_write": false, "can_upload": false}]}
## 3. 共享链接页面可访问(HTTP 200)
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8334/s/c1tst001
200
现在禁用共享并干净地重测(专门核实了落盘值确为 false,排除提交残留干扰):
>>> admin_post({"features.share.enable": False})
(200, {'status': 'ok'})
$ grep -A4 '"share"' /opt/filestash/data/state/config/config.json
"share": {"enable": false, "default_access": null, "redirect": null}
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | grep enable_share
"enable_share": false,
前端标记 enable_share 已下发为 false(Web 界面的"分享"按钮随之隐藏)。但 API 层的行为完全不变:
## 禁用状态下创建新共享:仍然成功
$ curl -s -X PUT ... -d '{"path": "/srv/share/docs/", "can_read": true, ...}' \
"http://127.0.0.1:8334/api/share/c1dis001"
{"status": "ok"}
## 禁用状态下访问已有共享链接:仍然 200
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8334/s/c1tst001
200
实测结论(v0.6.20261022):features.share.enable=false 只是"前端开关"——隐藏 Web 界面的分享入口;已建共享的创建/访问 API 照常工作。若目标是"服务端彻底禁用共享",仅靠这个参数不够,需要叠加反向代理规则拦截 /api/share 与 /s/ 路径,或用授权插件收敛权限。测试后已将共享删除、参数还原:
$ curl -s -X DELETE -H "Authorization: Bearer <SFTP会话令牌>" http://127.0.0.1:8334/api/share/c1dis001
{"status": "ok"}
>>> admin_post({"features.share.enable": True})
(200, {'status': 'ok'})
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | grep enable_share
"enable_share": true,
8.5 display_hidden 与 filepage_default_view:改了立即生效
general.display_hidden:置 true 后,/api/config 的 display_hidden 立即翻转,且落盘文件同步写入(无需重启):
>>> admin_post({"general.display_hidden": True})
(200, {'status': 'ok'})
$ grep -n 'display_hidden' /opt/filestash/data/state/config/config.json
10: "display_hidden": true,
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | grep display_hidden
"display_hidden": true,
一个需要澄清的行为细节:SFTP 后端的 ls 接口无论该参数真假,都会返回点开头文件(实测 display_hidden=false 时 .c1hidden.txt 也在 results 里):
$ curl -s -H "Authorization: Bearer <SFTP会话令牌>" \
"http://127.0.0.1:8334/api/files/ls?path=/srv/share/" | python3 -m json.tool
{
"status": "ok",
"results": [
{"name": "sample.bin", ...},
{"name": ".c1hidden.txt", ...},
{"name": "docs", ...},
{"name": "b1-verify", ...}
]
}
即 display_hidden 只控制 Web 界面是否默认展示隐藏文件,过滤发生在前端渲染层,不是服务端接口行为。
general.filepage_default_view:从默认 grid 改为 list 后,/api/config 的 default_view 字段立即从 grid 变为 list(Web 界面文件页随之默认用列表布局展示):
>>> admin_post({"general.filepage_default_view": "list"})
(200, {'status': 'ok'})
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | grep default_view
"default_view": "list",
以上两个参数实测后均已还原为 null(见第九节),落盘文件回到 null 基线。
九、配置还原与落盘基线
把 8.3/8.4/8.5 修改过的四个参数(display_hidden、filepage_default_view、log.level、share.enable)全部提交回 null,即还原为"跟随默认值":
>>> admin_post({"general.display_hidden": None, "general.filepage_default_view": None,
... "log.level": None, "features.share.enable": True})
(200, {'status': 'ok'})
$ curl -s http://127.0.0.1:8334/api/config | python3 -m json.tool | grep -E '"(display_hidden|default_view|enable_share)"'
"default_view": "grid",
"display_hidden": false,
"enable_share": true,
还原后的落盘基线(config.json 全文,节略 mime 等无关段):所有未显式设置的键都是 null,密钥、口令哈希、connections 保留原值——这就是"出厂默认 + 必要初始化"的标准形态:
$ cat /opt/filestash/data/state/config/config.json
{
"general": {
"name": null,
"port": 0,
"host": null,
"secret_key": "iUIfIl67pLEWX98S",
"force_ssl": null,
"editor": null,
"logout": null,
"display_hidden": null,
"refresh_after_upload": null,
"open_mode": null,
"upload_button": null,
"upload_pool_size": 0,
"upload_chunk_size": 0,
"buffer_size": null,
"filepage_default_view": null,
"filepage_default_sort": null,
"cookie_timeout": 0,
"extended_session": null,
"custom_css": null,
"license": null
},
"features": {
"api": {"enable": null},
"share": {"enable": null, "default_access": null, "redirect": null},
...(protection/collaborative/workflow/office/mcp/site/search/video/server,同构,均为 null/0)...
},
"log": {"enable": null, "level": null, "telemetry": null},
"email": {"server": null, "port": 0, "username": null, "password": null, "from": null},
"auth": {"admin": "$2b$10$hJXK4pqyc9DGDizo6Rp7/Ojc8Tqds5KDSCgGqL/zUDsBE9cUxdCpK"},
"middleware": {"identity_provider": {"type": null, "params": null},
"attribute_mapping": {"related_backend": null, "params": null}},
"constant": {"user": "filestash", "license": "agpl"},
"connections": [
{"type": "sftp", "label": "SFTP 服务器"},
{"type": "webdav", "label": "WebDAV 服务器"}
]
}
基线管理的三点建议:
- 改前备份:
cp config.json config.json.bak.$(date +%s)。虽然 API 能逐项还原 null,但一份改动前的落盘快照永远是最后的退路。 - secret_key 不可轻易轮换:如 4.4 所述会使全部会话与共享链接失效,只能安排在维护窗口。
- 还原 = 提交 null,而不是"删键":请求体里删掉某键与显式提交 null 等价(全量替换语义),但显式 null 语义更清晰,不易误伤其他键。
十、附:共享链接 API 速查
8.4 实测中顺带确认了共享功能的完整 API 面(详细的行为与参数说明见本专栏《Filestash 运维管理与 API 实操详解》):
| 操作 | 方法与路径 | 说明 |
|---|---|---|
| 创建/更新共享 | PUT /api/share/<ID> | body:{"path": "...", "can_read": true, "can_write": false, "can_upload": false},可选 url/password/expire/users;ID 为自定义短串(Web 端用 7 位随机串) |
| 列出路径已有共享 | GET /api/share?path=<路径> | 返回 results 数组,元素含 id/path/can_read/can_write/can_upload |
| 删除共享 | DELETE /api/share/<ID> | 返回 {"status": "ok"} |
| 访问共享 | GET /s/<ID> | 共享落地页(HTTP 200,口令/邮件验证时先走 proof 流程) |
| 共享口令验证 | GET /api/share/<ID>/proof | 带 - 参数校验口令/邮件验证码(前端分享页调用) |
注:老版本文档/社区资料中的
POST /api/files/share在本版本实测返回 404 Not Found;PUT/DELETE该路径则返回 405 Method Not Allowed。以本文实测的/api/share系列为准。
至此 Filestash 配置体系的四个维度——端点、Schema、修改语义、落盘形态——都有了实测答案。下一篇《Filestash 运维管理与 API 实操详解》将以运维视角收尾:日志逐行解读、健康检查全形态、共享与打包下载等核心 API 的实操全集。