Skip to content

Repository files navigation

fnos-file-tool —— 飞牛 fnOS 文件创建器

tests license: MIT python dependencies

仓库地址 https://github.com/Jacob9102/fnos-file-tool · 问题反馈 https://github.com/Jacob9102/fnos-file-tool/issues


在飞牛 NAS 上创建 / 编辑 / 管理文件的工具,只依赖 Python 标准库,一份源码两种交付:

交付形态 打开方式 鉴权 目录范围
fpk 原生应用 飞牛桌面图标,桌面窗口内嵌打开 飞牛登录 + 应用账号密码 只允许你在「应用设置 → 访问权限」里授权的目录
Docker 服务 浏览器访问端口 应用账号密码 由 compose 的挂载决定

能力:23 种文件格式(含空白 Excel / Word / PPT / PDF)、多级建目录并修正权限、批量创建、 上传下载、在线编辑、重命名/复制/移动/删除、chmod/chown,全部在网页里完成。

适用场景:飞牛的文件管理器或其他应用不让你新建文件(共享文件夹根目录权限是 000、目录属主是 root、 应用数据目录只读等),但你确实需要在那里落一个文件(配置文件、.strm.nfo、说明文档、密钥文件…)。


一、为什么飞牛上"建不了文件"

这不是玄学,实测根因有三条:

现象 实测结果 本工具的解决方式
普通用户写共享目录被拒绝 touch /vol1/1000/x.txtPermission denied(共享根目录权限是 d--------- 容器以 root 运行 + bind mount 挂载 /vol1,直接绕过用户态权限
目录属主是 root / 其他用户 实测:trimafs 按目录本地权限元数据授权,目录属主对其中 root 创建的文件仍有完整读/写/删权限,所以工具建的文件在飞牛里立刻可用,通常不需要 chown 仍提供 chown 接口,需要归属普通用户时可批量改
新建目录会被落成 000 同一句 mkdir:容器 /tmp 上得 755,而 /vol1 共享目录上得 000 创建时对每一级目录显式 chmod,否则建出来的目录在飞牛里根本进不去

第 3 条是踩坑重点:很多"能在飞牛上建文件"的脚本只 mkdirchmod,结果目录是 000, 在飞牛文件管理器里既看不见也进不去,看起来就像"没建成功"。


二、能做什么

  • 新建文件(可选格式、可带初始内容、可指定权限),父目录不存在自动创建并修正权限
  • 文件格式下拉框:23 种格式。文本类(txt/json/html/sh…)预填该格式的默认内容; 二进制类(xlsx/docx/pptx/pdf/zip/png/gif/jpg)直接生成空白标准文件,其"初始内容"框会被禁用
  • 扩展名自动补全:选了格式就不用在文件名里手打后缀(readme + txt → readme.txt); 名称自带扩展名(含 .env 这类点文件)时以名称为准,另有无扩展名档位用于 Dockerfile / LICENSE
  • 新建文件夹,支持 a/b/c 多级一次创建,每一级都正确 chmod
  • 批量创建:一次粘贴几十个文件名 / 目录名,也可统一指定格式
  • 上传 / 下载:任意大小、任意类型(原始二进制流)
  • 在线编辑:网页里直接改文本内容,Ctrl+S 保存(≤ MAX_EDIT_MB
  • ✅ 重命名、复制、移动、删除(目录可递归)
  • 改权限 chmod(递归)、改属主 chown(递归,且不会破坏原权限位)
  • 账号密码登录:首次打开设置账号,之后每次打开需登录,登录后可修改用户名/密码
  • 目录授权:只允许操作飞牛「应用设置 → 访问权限」里授权过的目录,未授权的存储空间完全不可见
  • ✅ 挂载根目录白名单 + 会话鉴权;未登录时所有文件接口一律拒绝

关于"空白文件"为什么不能是 0 字节

新建一个 0 字节的 x.xlsx,Excel 打开时会直接报"文件格式无效"。所以二进制格式是真正构造出来的最小合法文件 (只用标准库:zipfile 组装 OOXML、zlib+struct 造 PNG、手写 PDF 交叉引用表):

格式 生成内容 已用真库验证
xlsx 含 1 个空白工作表 Sheet1 的工作簿 openpyxl.load_workbook 打开成功,max_row=1, max_column=1
docx 含 1 个空段落的 Word 文档 python-docx 打开成功,正文为空
pptx 1 页空白幻灯片(含母版/版式/主题,缺一 PowerPoint 就打不开) python-pptx 打开成功,1 页
pdf A4 单页空白 PDF(xref 偏移按实际字节计算) 交叉引用偏移校验通过
zip 合法空压缩包(22 字节 EOCD) zipfile 打开成功,包内为空
png / gif / jpg 1×1 空白图片 Pillow 解码成功,尺寸 (1,1)

三、快速部署

方式 A:SSH + 一键脚本(推荐)

在飞牛的 SSH 里拉取代码(或点 GitHub 页面上的 Code → Download ZIP 再传到飞牛):

cd /vol1/1000/docker
git clone https://github.com/Jacob9102/fnos-file-tool.git
# 没装 git 就下载 ZIP: https://codeload.github.com/Jacob9102/fnos-file-tool/zip/refs/heads/main
cd fnos-file-tool
# 先改 docker-compose.yml 里 volumes 的挂载目录(改成你真正要管理的目录)
bash deploy.sh

方式 B:只用 docker compose

docker compose up -d --build

方式 C:飞牛 Docker 界面

  1. 先在 SSH 里构建镜像(界面里构建需要能读取构建上下文):
    cd /vol1/1000/docker/fnos-file-tool && docker build -t fnos-file-tool:1.1.0 .
  2. 打开飞牛 Docker → 项目 → 新增,粘贴 docker-compose.yml 内容(把 build: . 那行删掉,保留 image: fnos-file-tool:1.1.0),把各卷的宿主路径改成你的实际路径,部署即可。
  3. 访问 http://<飞牛IP>:8787/,首次打开会让你设置账号密码,设置完即登录。

端口冲突时改 docker-compose.ymlports 的左侧数字,例如 "8790:8787"


四、目录结构

fnos-file-tool/
├── app.py                    # 后端:单文件,仅标准库(HTTP/路由/鉴权/格式生成/生命周期)
├── static/
│   ├── index.html            # 前端:单页,无 CDN、无构建步骤,离线可用
│   └── uitest.html           # 界面自测页:真实浏览器里自动点击每一个按钮
├── Dockerfile                # Docker 版:python:3.12-alpine + 非 root 降权启动(可选)
├── docker-compose.yml        # 部署配置(端口 / 挂载 / 开关)
├── deploy.sh                 # 一键部署(构建 + 启动 + 就绪检查)
├── entrypoint.sh             # 容器入口,支持 PUID/PGID 降权
├── build-fpk.sh              # 构建飞牛应用包:同步源码 + 生成图标 + 调用官方 fnpack
├── tools/make_icons.py       # 图标生成(构建期用 Pillow,运行时不依赖)
├── fpk/                      # 飞牛应用包工程
│   ├── manifest              # 应用元信息(键值格式,非 JSON)
│   ├── ICON.PNG / ICON_256.PNG
│   ├── app/ui/config         # 桌面入口:type=iframe + 统一网关
│   ├── app/ui/images/        # 桌面图标(按尺寸命名,{0} 会被替换)
│   ├── cmd/                  # 生命周期:main / install_* / uninstall_* / upgrade_* / config_*
│   ├── config/{privilege,resource}
│   └── wizard/               # 安装向导目录(安装器要求存在)
├── tests/
│   ├── smoke.sh              # 接口层 73 项
│   ├── auth.sh               # 账号与登录 29 项
│   ├── fpk-sim.sh            # 模拟飞牛环境 35 项(生命周期 + 网关 + 授权目录边界)
│   └── validate_formats.py   # 空白 Office / 图片文件合法性 39 项
├── LICENSE                   # MIT
└── README.md

fpk/app/server/app.pyfpk/app/ui/index.htmlfpk/LICENSEfnos-file-tool.fpk 都是 构建产物(已被 .gitignore 忽略),由 build-fpk.sh 从上面的源码重新同步生成, 仓库里不会出现两份会各自漂移的副本。

五、配置项(环境变量)

变量 默认 说明
PORT 8787 容器内监听端口
ROOTS vol1=/mnt/vol1,... 白名单根目录,标签=容器内路径,逗号分隔;路径必须与 volumes 挂载点一致
ALLOW_ANY_ROOT false true 时不做根目录限制(危险,等于容器内全盘可写)
ALLOW_DELETE true 设为 0 关闭删除接口
ALLOW_CHMOD true 设为 0 关闭改权限接口
MAX_EDIT_MB 4 在线编辑的最大文件体积
MAX_UPLOAD_MB 2048 单次上传上限
ACCESS_LOG 0 设为 1 时逐条打印请求日志(排查问题用)
PUID / PGID 0 / 0 默认 root(可写任意挂载目录)。设为 1000 等则降权运行,创建的文件直接归属该用户

只挂载你需要的目录

    environment:
      ROOTS: "share=/mnt/share"
    volumes:
      - /vol1/1000/share:/mnt/share:rw        # 只放行这一个目录

比直接挂整个 /vol1~/vol4 安全得多:容器内只有 /mnt/share 可访问, 其他路径会被白名单直接拒绝(返回 403),实测已验证。

要加第二个目录就照抄一行,并在 ROOTS 里补上对应标签:

      ROOTS: "share=/mnt/share,文档=/mnt/docs"
    volumes:
      - /vol1/1000/share:/mnt/share:rw
      - /vol1/1000/文档:/mnt/docs:rw

六、HTTP 接口

所有接口返回 JSON,成功恒为 {"ok": true, ...},失败为 {"ok": false, "error": "..."}。 除下面几个接口外,所有接口都需要登录(会话 Cookie):

方法 路径 说明
GET /api/auth/status 查询是否已设置账号、是否已登录(免鉴权,前端据此决定显示「首次设置 / 登录 / 主界面」)
POST /api/auth/setup 首次设置账号 {username, password},仅在尚未设置时可用(用户名 2–32 字符,密码 ≥6 位)
POST /api/auth/login 登录 {username, password},成功后下发会话 Cookie(失败 5 次锁定 5 分钟)
POST /api/auth/logout 退出登录
POST /api/auth/password 改用户名/密码 {currentPassword, username?, newPassword?}
GET /api/meta 服务信息、挂载根目录、运行身份(uid/gid)、formats 格式清单与 default_format免鉴权,前端不写死格式表)
GET /api/settings 读当前生效的开关、已授权目录、运行模式(网关/TCP)与账号文件位置
POST /api/settings 改开关 {allow_delete?, allow_chmod?, require_admin?, max_edit_mb?, max_upload_mb?},写回 settings.json 即时生效(目录授权不在此处改)
GET /api/list?path= 列目录
GET /api/read?path= 读文件(文本返回 UTF-8,二进制返回 base64)
GET /api/download?path= 下载原始文件(目录会返回 400,请先压缩)
POST /api/mkdir {path, name, mode?} 建目录,name 支持 a/b/c 多级
POST /api/touch {path, name, format?, content?, mode?, overwrite?} 建文件(见下方格式说明)
POST /api/batch {path, names:[...], format?, content?, overwrite?} 批量建(以 / 结尾建目录,单次 ≤ 500 项)
PUT /api/write {path, content, mode?} 覆盖写入文本(≤ MAX_EDIT_MB
POST /api/upload?path=&name= 请求体为原始二进制,写入 path/name(≤ MAX_UPLOAD_MB
POST /api/rename {path, newName}
POST /api/copy / /api/move {path, target, name?}
POST /api/delete {path, recursive?}
POST /api/chmod {path, mode, recursive?}
POST /api/chown {path, uid, gid?, recursive?, mode?, keepMode?}

格式与命名规则(POST /api/touch

formatGET /api/metaformats[].key(如 txt / xlsx / none),默认 txt。规则:

  1. 名称自带扩展名时以名称为准{"name":"自定义.zzz","format":"txt"} → 就是 自定义.zzz
  2. 名称没有扩展名时补上所选格式的扩展名{"name":"readme","format":"xlsx"}readme.xlsx
  3. 点文件不补.env / .gitignore 视为用户就是要这个名字。
  4. 要完全无扩展名:用 format: "none"DockerfileLICENSE 等)。
  5. 二进制格式不接受手填内容:带 content 会返回 400;不填内容则生成空白标准文件。
  6. 文本格式不填内容时写入该格式的默认模板(如 json → {}、html → 网页骨架、sh → shebang 且权限 755)。
  7. 名称里可以带子目录:{"name":"报告/2026/汇总","format":"xlsx"} 会连目录一起建好并修正权限。
# 建一个空白 Excel(不用手打 .xlsx 后缀,也不填内容)
curl -s -b $JAR -c $JAR -H 'Content-Type: application/json' \
  -X POST "$API/touch" -d '{"path":"/mnt/share","name":"月度汇总","format":"xlsx"}'

# 建一个空白 Word / PPT / PDF / ZIP / 图片同理,把 format 换成 docx / pptx / pdf / zip / png

# 文本格式:不填内容会用该格式的默认模板
curl -s -b $JAR -c $JAR -H 'Content-Type: application/json' \
  -X POST "$API/touch" -d '{"path":"/mnt/share","name":"配置","format":"json"}'

# 批量建 3 个空白表格
curl -s -b $JAR -c $JAR -H 'Content-Type: application/json' \
  -X POST "$API/batch" \
  -d '{"path":"/mnt/share","names":["一月报表","二月报表","三月报表"],"format":"xlsx"}'

命令行示例

URL 里的路径必须做百分号编码(浏览器会自动处理)。用 curl 手写中文路径时请用 --data-urlencode 或 python 的 quote,否则原始 UTF-8 字节可能撞上 HTTP 解析器保留字符而报 400。

JAR=/tmp/fnos-cookies.txt

# 先登录拿到会话 Cookie;首次使用把 auth/login 换成 auth/setup
curl -s -c $JAR -H 'Content-Type: application/json' \
  -X POST "$API/auth/login" -d '{"username":"你的账号","password":"你的密码"}'
API='http://127.0.0.1:8787/api'

# 建一个文件(JSON 请求体里中文没问题)
curl -s -b $JAR -c $JAR -H 'Content-Type: application/json' \
  -X POST "$API/touch" \
  -d '{"path":"/mnt/vol1/1000","name":"说明.txt","content":"你好 fnOS\n","mode":"664"}'

# 读中文文件名(用 python 做 URL 编码)
P=$(python3 -c "import urllib.parse;print(urllib.parse.quote('/mnt/vol1/1000/说明.txt'))")
curl -s -b $JAR -c $JAR "$API/read?path=$P"

# 建多级目录并指定权限
curl -s -b $JAR -c $JAR -H 'Content-Type: application/json' \
  -X POST "$API/mkdir" -d '{"path":"/mnt/vol1/1000","name":"a/b/c","mode":"775"}'

# 把某目录及子项属主改回飞牛的普通用户
curl -s -b $JAR -c $JAR -H 'Content-Type: application/json' \
  -X POST "$API/chown" -d '{"path":"/mnt/vol1/1000/文档","uid":1000,"gid":1001,"recursive":true}'

七、安全说明(请务必看)

  1. 首次打开务必设置账号密码,并只把端口留在内网。 本服务以 root 运行、能写你挂载的所有目录,把端口暴露到公网等于把 NAS 文件系统开放出去。
  2. 不要挂载整个存储池,只挂需要操作的目录。
  3. 不要把端口暴露到公网。需要外网访问时请走飞牛自带的远程访问 / 反向代理,并叠加 HTTPS 与更强的鉴权。
  4. 容器固定以 root 运行是故意设计(否则无法写入飞牛的受限目录);如果你不需要这个能力, 在 compose 里设 PUID/PGID 为普通用户,或者用 ALLOW_DELETE=0 / ALLOW_CHMOD=0 关闭危险接口。
  5. 所有路径都会做 realpath 归一化并校验是否在 ROOTS 白名单内,防止 ../ 与符号链接逃逸(有测试覆盖)。

八、测试

五套测试,全部可在飞牛本机跑(不需要联网);合计 204 项(格式校验未装可选库时为 196 项):

bash tests/smoke.sh                 # 73 项:登录 + 接口层(增删改查 / 上传下载字节一致 / 权限 / 安全边界)
bash tests/auth.sh                  # 29 项:首次设置 / 登录 / 退出 / 改密 / 密码存储 / 会话
bash tests/fpk-sim.sh               # 35 项:模拟飞牛应用环境跑生命周期与统一网关
python3 tests/validate_formats.py   # 39 项(装可选库后)/ 31 项(仅标准库结构校验)

界面层(真实浏览器自动点击,含登录流程):

http://<飞牛IP>:8787/uitest.html?run=1&user=uitest&pass=uitest123456

validate_formats.py 装了 openpyxl / python-docx / python-pptx / Pillow 时做深度校验 (真库打开生成的 xlsx/docx/pptx、Pillow 解码图片、核对 PDF 交叉引用偏移),没装则退化为结构校验。

本项目的实测环境

项目
系统 飞牛 fnOS 1.2.0604(底层 Debian GNU/Linux 12)
Docker 28.5.2 / Compose v5.2.0
存储池 /vol1~/vol4(trimafs)
实测结论 1 宿主普通用户 touch /vol1/1000/x.txtPermission denied;应用内同一路径 → 成功
实测结论 2 共享目录的 d--------- 是真实 mode(getfacl 无 ACL 条目),但 trimafs 按目录本地权限元数据授权:uid 1000 能读/改/删 root 在其中创建的文件,非属主 uid 921 被拒
实测结论 3 同一句 mkdir:容器 /tmp 上得 755/vol4/1000/... 共享目录上得 000
已修复的坑 6 个真实 bug(keep-alive 请求体污染、新建目录落成 000、网关前缀剥除等)的定位与修法见代码注释与提交历史

九、常见问题

Q:忘记账号密码了怎么办? 删除账号文件后重启应用即回到「首次设置」。Docker 版在 ./data/auth.json, fpk 版在 …/@appconf/fnos-file-tool/auth.json

Q:在飞牛文件管理器里看不到刚建的文件? 先点飞牛的刷新。实测中文件是立刻可见且可编辑的;若确实看不到,可能是目录权限问题, 用 /api/chmod 把中间各级目录改成 755

Q:创建出来的文件属主是 root,飞牛里改不动? 实测结论:不用改。 trimafs 按目录本地权限元数据授权,目录属主(如 uid 1000)对其中 root 创建的文件 仍然可以读、追加、删除(已实测通过)。若你的环境确实不行,两条路:调用 /api/chown 改成 1000:1001; 或把 compose 里 PUID/PGID 设为 1000/1001 重新部署 —— 但注意降权后,容器将失去写入本就对普通用户 不可写目录的能力(那正是本工具存在的理由),需自行权衡。

Q:docker compose up 报端口被占用?docker-compose.ymlports: - "8787:8787" 的左侧数字,或用 ss -ltnp | grep 8787 看谁在用。

Q:构建镜像时卡在 apk add 容器内的包源需要外网。若 NAS 上网受限,可把 Dockerfile 的 python:3.12-alpine3.20 换成你本地已有的 镜像(例如 python:3.12-alpine),或给 Docker 配置代理后重试。

Q:为什么不用 Nginx + PHP 之类的现成文件管理器? 也可以。本工具的定位是「最小、无依赖、专治飞牛写权限」,代码量小到可以自己审一遍,镜像 60MB,秒级启动。


十、打包成飞牛应用(.fpk,桌面内嵌窗口打开)

除了 Docker 版,本项目还能打包成飞牛的 原生应用包 .fpk,安装后在飞牛桌面上是一个图标, 点击后在桌面窗口内嵌打开(不另开浏览器窗口),走飞牛统一网关,不占用任何端口。

10.1 为什么是"内嵌窗口"

飞牛的桌面入口由 app/ui/config 声明,其中 type 决定打开方式:

type 行为
iframe 在飞牛桌面窗口内打开(本项目使用)
url 在浏览器标签页 / 外部 Web 视图中打开

本项目的 fpk/app/ui/config

{
    ".url": {
        "fnos-file-tool.main": {
            "title": "文件创建器",
            "icon": "images/icon_{0}.png",
            "type": "iframe",
            "protocol": "",
            "gatewayPrefix": "/app/fnos-file-tool",
            "gatewaySocket": "app.sock",
            "url": "/app/fnos-file-tool",
            "allUsers": false,
            "control": { "accessPerm": "readonly" }
        }
    }
}

用的是统一网关模式:应用只监听 ${TRIM_APPDEST}/app.sock(Unix Socket,权限 0770), 飞牛校验登录态后把 /app/fnos-file-tool/... 的请求转发进来。这样:

  • 不占端口、不把服务暴露到局域网,鉴权交给应用账号 + 飞牛网关;
  • 复用飞牛当前域名和 NAS 登录态;
  • protocol / port 在网关入口下不参与路由,因此无需声明端口。

因为请求带前缀,服务端会把前缀剥掉后再路由,并在 HTML 里注入 <base href="/app/fnos-file-tool/">, 前端全部用相对地址调接口 —— 同一套前端在 Docker 直连(<base href="/">)和网关下都能正常工作。

10.2 目录结构(fpk 工程)

fnos-file-tool/
├── app.py / static/ …        ← 开发版源码(Docker 版与 fpk 版共用一份)
├── build-fpk.sh              ← 同步源码 + 生成图标 + 调官方 fnpack 打包
├── tools/make_icons.py       ← 图标生成(构建期用 Pillow,运行时不依赖)
└── fpk/                      ← fpk 工程
    ├── manifest              ← 应用元信息(键值格式,不是 JSON)
    ├── ICON.PNG / ICON_256.PNG
    ├── LICENSE
    ├── app/
    │   ├── server/app.py     ← 由 build-fpk.sh 从 ../app.py 同步
    │   └── ui/
    │       ├── config        ← 桌面入口(type=iframe + 网关)
    │       ├── index.html    ← 由 build-fpk.sh 从 ../static/index.html 同步
    │       └── images/icon_{0,64,128,256}.png
    ├── cmd/                  ← 生命周期脚本:main / install_* / uninstall_* / upgrade_* / config_*
    ├── config/privilege      ← run-as: root(读写任意目录的前提)
    ├── config/resource       ← data-share 为空(不额外创建共享目录)
    └── wizard/               ← 安装向导目录(安装器要求存在)

10.3 构建

bash build-fpk.sh          # 产出 fnos-file-tool.fpk

脚本会用飞牛自带的 /usr/local/bin/fnpack(v1.2.4)打包;普通用户执行时自动通过 nsenter 借宿主命名空间调用它。不要手工拼 tar:fpk 内外层结构(app.tgz 不带 app/ 前缀) 必须与安装器期望一致,fnpack 还会自动写入 checksum

10.4 安装

# 方式一:命令行
appcenter-cli install-fpk /path/to/fnos-file-tool.fpk
appcenter-cli start fnos-file-tool
appcenter-cli status fnos-file-tool

# 方式二:飞牛桌面 → 应用中心 → 手动安装(若被禁用先执行 appcenter-cli manual-install enable)

装好后飞牛桌面出现「文件创建器」图标,点击即在桌面窗口内打开。

10.5 两种运行模式的差异

Docker 版 fpk 原生版
监听 TCP 端口(默认 8787) Unix Socket ${TRIM_APPDEST}/app.sock
入口 浏览器直接访问 飞牛桌面内嵌窗口(iframe)
鉴权 应用账号密码 应用账号密码(可叠加飞牛管理员校验)
可访问目录 由 volumes 挂载决定 自动发现 /vol1/volN(+ 系统授权目录)
运行身份 容器内 root 应用进程 root(config/privilege
运行依赖 Docker 系统 Python 3.8+(飞牛自带 python3,或商店的 python312)

模式由环境变量决定:GATEWAY_SOCKET 存在 → 网关模式(cmd/main 会显式导出); 否则走 TCP。LISTEN_MODE=tcp|socket 可强制指定。

10.6 首次使用:设置账号 + 授权目录

装好后在飞牛桌面点开「文件创建器」,会先看到首次使用:设置账号,设置用户名与密码(≥6 位)即完成注册并自动登录。

然后必须授权目录,否则界面里什么都看不到(这是刻意的安全边界):

飞牛桌面 → 应用中心 → 已安装 → 文件创建器 → 设置(齿轮) → 访问权限 / 授权目录 → 添加目录

保存后 config_callback 会自动重启应用加载新的授权目录(授权目录是通过环境变量 TRIM_DATA_ACCESSIBLE_PATHS 注入的,只在进程启动时读取一次)。之后刷新页面即可看到目录。

不确定授权有没有生效?登录后点右上角 ⚙ 设置,「已授权目录」里会列出应用实际收到的路径, 还能看到原始的 TRIM_DATA_ACCESSIBLE_PATHS 值,方便核对。

10.7 配置:${TRIM_PKGETC}/settings.json

登录后可在应用内 ⚙ 设置 里直接修改开关(会写回这个文件):

{
  "require_admin": false,      // 是否额外要求飞牛管理员身份(默认 false,以应用账号为准)
  "allow_delete": true,        // 是否允许删除
  "allow_chmod": true,         // 是否允许改权限/属主
  "max_edit_mb": 4,            // 在线编辑上限
  "max_upload_mb": 2048        // 上传上限
}

目录授权不在这里配置,必须走飞牛的「应用设置 → 访问权限」,确保授权边界只由系统设置决定。 账号文件为同一个目录下的 auth.json(PBKDF2-SHA256 加盐,权限 600); 忘记密码时可删除该文件后重启应用,重新走首次设置。

10.8 安全模型

  • 登录是唯一入口:未登录时所有文件接口返回 401(未设置账号时返回 409)。 密码用 hashlib.pbkdf2_hmac 加盐存储(20 万次迭代),会话是 HMAC 签名的 Cookie(HttpOnly,7 天)。 登录失败 5 次锁定 5 分钟。
  • 目录授权是硬边界:只放行飞牛授权目录,/vol1 整个存储空间、/etc/tmp 等一律 403, 连浏览都不允许。所有路径仍做 realpath 归一化,.. 与符号链接逃逸均被拒绝。
  • 网关=边界:Socket 权限 0770,只有飞牛网关能连;服务不对外暴露任何端口。
  • 可选叠加飞牛管理员校验:设置里打开 require_admin 后,非管理员的飞牛账号即使知道应用密码也会被 403。

10.9 验证

bash tests/fpk-sim.sh    # 35 项:模拟飞牛环境(生命周期 + 网关 + 登录 + 授权目录边界)
bash tests/auth.sh       # 29 项:账号与登录(首次设置/登录/退出/改密/限流/持久化)
bash tests/smoke.sh      # 73 项:登录 + 接口层(Docker 模式)
python3 tests/validate_formats.py   # 39 项:空白 Office / 图片文件的合法性

界面自测(真实浏览器自动点击,含登录流程):

http://<飞牛IP>:8787/uitest.html?run=1&user=uitest&pass=uitest123456

fpk-sim.sh 会造一套 target/var/etc/cmd,用 TRIM_* 环境变量跑 cmd/main start|status|restart|stop,通过 Unix Socket 带网关前缀访问接口,验证: socket 权限 0770、重复 start 不产生双进程、<base> 注入、未设置账号时全部拒绝登录后可读写未授权路径(/vol1、/etc、/tmp)一律 403、退出后 401、 改密与登录限流、设置持久化、新增授权目录后 config_callback 重启加载生效

10.10 上架前建议确认

当前值 说明
maintainer / distributor Jacob9102 已按 GitHub 用户名填写;改署名时同步改 LICENSE 版权行
os_min_version 未设置 建议在真机验证后按实际兼容版本填写
LICENSE MIT(版权行 Jacob9102 已填写
图标 脚本生成的蓝色文稿+加号 换设计图后重跑 tools/make_icons.py
platform all 纯 Python 标准库,无架构相关二进制

十一、仓库信息

仓库 https://github.com/Jacob9102/fnos-file-tool
问题反馈 / 功能建议 https://github.com/Jacob9102/fnos-file-tool/issues
许可证 MIT(见 LICENSE
维护者 Jacob9102
CI 每次 push / PR 自动跑 tests/ 里的测试,见 .github/workflows/tests.yml

欢迎提 Issue 与 PR(修 bug、加文件格式、补测试都行)。提交前建议先在本机跑一遍:

bash tests/smoke.sh && bash tests/auth.sh && bash tests/fpk-sim.sh && python3 tests/validate_formats.py

About

在飞牛 fnOS / 任意 Linux NAS 上创建、编辑、管理文件的轻量 Web 工具:纯 Python 标准库、零第三方依赖,支持 23 种文件格式(含空白 Excel / Word / PPT / PDF)、批量创建、上传下载、在线编辑、chmod/chown,两种交付形态(fpk 原生应用:桌面内嵌窗口 + 统一网关;Docker:浏览器直连)。

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages