FileBrowser 局域网文件服务 — 部署记录与复现手册
本文档记录一次完整部署的全过程:环境探测、方案选择、每一步的实际命令与输出、
遇到的所有坑及解法、以及最终产物源码。目标是让另一个 AI(或人)拿到**任意一台
Windows 电脑**上,能照着复现出一模一样的服务。
- 部署日期:2026-09-04
- 本机:Windows 11 Pro 10.0.26200,主机名
masha-magicbook - 部署目录:
D:\cygwin64\home\masha\Documents\workspace\filebrowser - 服务:FileBrowser v2.63.23(官方 Windows amd64 单文件二进制)
- 监听:
0.0.0.0:8080,根目录D:\,中文界面
⚠️ 阅读顺序建议:先看 §1~§5 了解「怎么做」,再读 §8 踩坑记录。
§8 是本文档最有价值的部分——每一条都是实际撞出来的,跳过会重蹈覆辙。
1. 需求
用户的要求,逐条对应到后面的实现:
| 需求 | 实现 |
|---|---|
| 在当前目录部署一个 file browser 服务 | 用官方 filebrowser/filebrowser,不自己写 |
| 局域网中能用网页管理文件 | 监听 0.0.0.0:8080 |
| 管理范围:整个 D 盘 | 配置 --root "D:/" |
| 开启密码登录 | 建 admin 用户,--perm.admin |
2. 环境探测(第一步永远是这个)
不要假设环境。先摸底,结论直接决定方案。
for c in python python3 pip pip3 node npm docker git go; do
printf "%-8s " "$c"; command -v $c >/dev/null 2>&1 && $c --version 2>&1 | head -1 || echo "(未安装)"
done本机实测结果:
python Python 3.14.6
pip pip 26.1.2
node v24.16.0
npm 11.13.0
docker (未安装)
git git version 2.43.0.windows.1
go (未安装)结论:没有 Docker,没有 Go → 只能下载现成的预编译二进制。
2.1 拿局域网 IP
不要用 ipconfig,它的输出会被 bash 当成二进制文件:
$ ipconfig | grep -i "IPv4"
grep: (标准输入): 匹配到二进制文件改用 socket 探测(走 UDP 8.8.8.8 只为了拿本机出口网卡 IP,不真正发包):
python -c "import socket; s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM); \
s.connect(('8.8.8.8',80)); print('本机 IP:', s.getsockname()[0]); s.close()"本机输出:192.168.5.50
2.2 确认网络与包管理可用
timeout 15 pip download flask -d /tmp/pipcheck --no-deps -q && echo "pip 网络 OK"2.3 查 GitHub 最新发行版
WebFetch 到 github.com 被网络策略拦了(Unable to verify if domain github.com is safe to fetch),
改用 curl 打 GitHub API——这条路是通的:
curl -sSL -H "Accept: application/vnd.github+json" \
https://api.github.com/repos/filebrowser/filebrowser/releases/latest -o /tmp/fb_release.json解析出资产列表(注意资源命名是 <os>-<arch>-filebrowser.<ext>):
darwin-amd64-filebrowser.tar.gz ...
linux-amd64-filebrowser.tar.gz ...
windows-386-filebrowser.zip ...
windows-amd64-filebrowser.zip ... ← 要这个
windows-arm64-filebrowser.zip ...
filebrowser_2.63.23_checksums.txt ...本机 tag_name = v2.63.23,published_at = 2026-07-27T20:03:21Z,
运行 ./filebrowser.exe version 确认 File Browser v2.63.23/e8a388f8。
3. 方案选择
| 方案 | 可行 | 理由 |
|---|---|---|
| Docker 跑官方镜像 | ❌ | 没装 Docker |
go install github.com/... | ❌ | 没装 Go,装工具链太重 |
| 自己写 Flask/Node 文件管理器 | ❌ | 用户明确说不要自己写 |
| 下载官方 windows-amd64 二进制 | ✅ | FileBrowser 是单文件 Go 程序,官方 release 有 Windows 包,零依赖 |
FileBrowser 的特点决定了后续设计:
- 单文件:解压即用,36 MB 一个
filebrowser.exe - 配置存 Bolt DB 单文件
filebrowser.db,不需要装数据库 - 数据库文件被独占锁定 → 运行中无法改用户/配置(见 §8.7)
- 支持
-c配置文件(.filebrowser.{json,toml,yaml}),但多数设置只在 DB 里,
必须用config set写 - 默认监听
127.0.0.1:8080、root.——默认值只适合本机,局域网必须显式改
4. 下载与校验(一定要校验)
cd /tmp && rm -rf fb_dl && mkdir fb_dl && cd fb_dl
BASE="https://github.com/filebrowser/filebrowser/releases/download/v2.63.23"
curl -sSfL -o filebrowser_2.63.23_checksums.txt "$BASE/filebrowser_2.63.23_checksums.txt"
curl -sSfL --retry 3 -o windows-amd64-filebrowser.zip "$BASE/windows-amd64-filebrowser.zip"校验(官方 checksums 文件用的是 sha256sum 格式,注意文件名前有 *):
grep -i "windows-amd64" filebrowser_2.63.23_checksums.txt > expect.txt
sha256sum windows-amd64-filebrowser.zip > actual.txt
diff <(awk '{print $1}' expect.txt) <(awk '{print $1}' actual.txt) && echo "✅ 校验通过"本机实测:
fdb1d86dfafff8b3861867c7797ce786570013088678e03de17cfd9476c72384 windows-amd64-filebrowser.zip
fdb1d86dfafff8b3861867c7797ce786570013088678e03de17cfd9476c72384 *windows-amd64-filebrowser.zip
✅ SHA256 校验通过Windows Python 看不到 Cygwin 的
/tmp。Cygwin 的/tmp实际是D:\cygwin64\tmp,Python 会报FileNotFoundError: [Errno 2] No such file or directory: '/tmp/...'。
跨用时要cygpath -w转换:ZIP=$(cygpath -w /tmp/fb_dl/windows-amd64-filebrowser.zip) # → D:\cygwin64\tmp\...
5. 解压、初始化、创建用户
5.1 解压
本机没装 unzip,用 Python:
ZIP=$(cygpath -w /tmp/fb_dl/windows-amd64-filebrowser.zip)
DEST=$(cygpath -w "$PWD")
python -c "import zipfile; \
[print(i.filename, i.file_size) for i in zipfile.ZipFile(r'$ZIP').infolist()]"先列一遍内容(确认无嵌套目录、无路径穿越)再解压:
CHANGELOG.md 0.13 MB
LICENSE 0.01 MB
README.md 0.00 MB
filebrowser.exe 35.58 MBpython -c "import zipfile; \
[zipfile.ZipFile(r'$ZIP').extract(i, r'$DEST') for i in zipfile.ZipFile(r'$ZIP').infolist()]"Cygwin bash 执行.exe要用正斜杠路径,"D:\...\filebrowser.exe"会报未找到命令。用/home/masha/Documents/workspace/filebrowser/filebrowser.exe
或直接./filebrowser.exe。
5.2 初始化数据库
./filebrowser.exe config init -d filebrowser.db输出会打印一份完整默认配置,关键默认值(这些就是要改的):
Server:
Address: 127.0.0.1 ← 必须改成 0.0.0.0
Port: 8080
Root: . ← 必须改成目标盘
Log: stdout
Token Expiration Time: 2h
Exec Enabled: false ← 默认关命令执行,安全,保持
Defaults:
Locale: en ← 改成 zh-cn
Minimum Password Length: 125.3 写入配置
./filebrowser.exe config set -d filebrowser.db \
--address 0.0.0.0 \
--port 8080 \
--root "D:/" \
--locale zh-cn \
--tokenExpirationTime 12h回读确认(config set 会自己打印结果):
Server:
Root: D:/
Address: 0.0.0.0
Port: 8080
Token Expiration Time: 12h
Defaults:
Locale: zh-cn用"D:/"而不是"D:\"——正斜杠在 bash 里不用转义,且 Go 在 Windows 上两种都认。
5.4 创建管理员用户
PASS=$(python -c "
import secrets, string
keep = ''.join(c for c in (string.ascii_letters+string.digits) if c not in 'O0oI1l')
print(''.join(secrets.choice(keep) for _ in range(16)))
")
./filebrowser.exe users add admin "$PASS" --perm.admin -d filebrowser.db生成密码时去掉 O 0 o I 1 l,因为用户要在手机上手工输入。
长度 ≥12(minimumPasswordLength 默认 12,短了会被拒)。
验证:
./filebrowser.exe users ls -d filebrowser.dbID Username Scope Locale V. Mode S.Click Red. After C/M Admin ...
1 admin / zh-cn list false false true ...Scope 显示 / = 相对 root 的根,也就是 D:\。正确。
⚠️
--scope千万不要传绝对路径。传--scope "D:/"会得到:Error: failed to create user home dir: [/D:]: mkdir D:\D:: The filename, directory name, or volume label syntax is incorrect.root 已经是
D:/了,scope 必须留空(默认.)或传相对路径。
另外注意:config set用-r/--root,users add只有--scope,没有-r。
6. 启动服务
6.1 怎么让进程「活下来」
这是个关键设计点。三个候选:
| 方式 | 问题 | |
|---|---|---|
前台跑 ./filebrowser.exe | 关掉终端就没了 | |
nohup ... & / start /B | 进程仍在终端的进程组里,关窗口时收到 CTRL_CLOSE_EVENT 被一起杀掉 | |
| **`Popen(creationflags=DETACHED_PROCESS \ | CREATE_NEW_PROCESS_GROUP)`** | ✅ 完全脱离,关窗口不影响 |
所以启动逻辑写进 Python 脚本(见 §7),不用 .bat 直接跑 exe。
日志:FileBrowser 的 -l 支持 file,stdout 这类组合,但语法没文档。
不如直接让 stdout 追加到 filebrowser.out.log,稳。
6.2 验证服务
$ ./filebrowser.exe -d filebrowser.db # 首次启动
2026/09/04 22:33:09 Listening on [::]:8080netstat -ano 应看到两条(IPv4 + IPv6):
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING <pid>
TCP [::]:8080 [::]:0 LISTENING <pid>7. 控制脚本 filebrowser_ctl.py(完整源码)
设计要点:
- 系统工具走绝对路径——本机 PATH 里 Cygwin 的
/usr/bin排在system32前面,timeout、netstat会被解析成 GNU 版。见 §8.12 - 端口探测用 socket,不用
netstat,彻底不依赖 PATH - 输出前 reconfigure stdout 为 UTF-8,否则 Windows 控制台 GBK 编码打中文/emoji 会崩
start/stop幂等,重复执行不报错passwd自动「停服务 → 改密码 → 启动 → 真发一次登录请求验证」
#!/usr/bin/env python3
"""FileBrowser 服务控制脚本。
用法:
python filebrowser_ctl.py start 启动服务
python filebrowser_ctl.py stop 停止服务
python filebrowser_ctl.py restart 重启
python filebrowser_ctl.py status 查看状态
python filebrowser_ctl.py passwd [新密码] 改密码(服务会自动停/启)
说明: 这台机器的 PATH 里 Cygwin 的 /usr/bin 排在 system32 前面,
所以系统命令都走绝对路径调用,不依赖 PATH 解析。
"""
import csv
import io
import json
import os
import secrets
import socket
import string
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
# Windows 控制台默认 GBK,输出中文/emoji 会崩
for _s in (sys.stdout, sys.stderr):
if hasattr(_s, "reconfigure"):
_s.reconfigure(encoding="utf-8", errors="replace")
BASE = Path(__file__).resolve().parent
EXE = BASE / "filebrowser.exe"
DB = BASE / "filebrowser.db"
LOG = BASE / "filebrowser.out.log"
CRED = BASE / "CREDENTIALS.txt"
IMAGE = "filebrowser.exe"
PORT = 8080
# Cygwin 传进来的 PATH 对原生 Windows 程序不可用,系统工具一律走绝对路径
_SYSTEMROOT = Path(os.environ.get("SystemRoot") or os.environ.get("WINDIR") or r"C:\Windows")
TASKLIST = _SYSTEMROOT / "System32" / "tasklist.exe"
TASKKILL = _SYSTEMROOT / "System32" / "taskkill.exe"
DETACHED_PROCESS = 0x00000008
CREATE_NEW_PROCESS_GROUP = 0x00000200
# ---------------------------------------------------------------- 基础工具
def _run(cmd, **kw):
return subprocess.run([str(c) for c in cmd], capture_output=True, text=True, **kw)
def pids() -> list[int]:
"""按进程名查 PID。"""
r = _run([TASKLIST, "/FI", f"IMAGENAME eq {IMAGE}", "/FO", "CSV", "/NH"])
found = []
for row in csv.reader(io.StringIO(r.stdout)):
if row and row[0].strip('"').lower() == IMAGE.lower():
try:
found.append(int(row[1]))
except (IndexError, ValueError):
pass
return found
def port_open(port: int = PORT) -> bool:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
return s.connect_ex(("127.0.0.1", port)) == 0
def lan_ip() -> str:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except OSError:
return "本机IP"
def urls() -> str:
return f"http://localhost:{PORT} / http://{lan_ip()}:{PORT}"
def wait_port(port: int, timeout: float, *, expect: bool) -> bool:
deadline = time.time() + timeout
while time.time() < deadline:
if port_open(port) == expect:
return True
time.sleep(0.3)
return port_open(port) == expect
# ---------------------------------------------------------------- 子命令
def start() -> int:
if port_open():
print(f"FileBrowser 已在运行 (PID {pids() or '?'}), 端口 {PORT}")
return 0
if not EXE.exists():
sys.exit(f"找不到 {EXE}")
if not DB.exists():
sys.exit(f"找不到数据库 {DB}, 请先初始化配置")
with LOG.open("a", encoding="utf-8", errors="replace") as logf:
subprocess.Popen(
[str(EXE), "-d", str(DB)],
cwd=str(BASE),
stdin=subprocess.DEVNULL,
stdout=logf,
stderr=subprocess.STDOUT,
creationflags=DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
)
if not wait_port(PORT, timeout=15, expect=True):
print(f"启动超时, 请看日志 {LOG.name}:")
print(LOG.read_text(encoding="utf-8", errors="replace")[-2000:])
return 1
print(f"FileBrowser 已启动 PID {pids()}")
print(f" 本机访问: http://localhost:{PORT}")
print(f" 局域网访问: http://{lan_ip()}:{PORT}")
return 0
def stop() -> int:
ids = pids()
if not ids:
print("FileBrowser 本来就没在运行")
return 0
for pid in ids:
r = _run([TASKKILL, "/F", "/PID", str(pid)])
print(f" 终止 PID {pid}: {'成功' if r.returncode == 0 else '失败'}")
if not wait_port(PORT, timeout=10, expect=False):
print("端口还没释放, 请重试")
return 1
print(f"FileBrowser 已停止 (共 {len(ids)} 个进程)")
return 0
def restart() -> int:
stop()
return start()
def status() -> int:
ids = pids()
listening = port_open()
print(f"进程: {ids or '无'}")
print(f"端口 {PORT}: {'LISTENING' if listening else '未监听'}")
if listening:
print(f"访问地址: {urls()}")
if CRED.exists():
print(f"用户名: {CRED.read_text().splitlines()[0]}")
return 0 if listening else 1
# ---------------------------------------------------------------- 密码
def _generate(length: int = 16) -> str:
# 去掉易混淆字符,方便在外接设备上手工输入
alphabet = "".join(
c for c in (string.ascii_letters + string.digits) if c not in "O0oI1l"
)
return "".join(secrets.choice(alphabet) for _ in range(length))
def verify_login(username: str, password: str) -> str:
"""返回 ok / bad / offline。"""
body = json.dumps({"username": username, "password": password}).encode()
req = urllib.request.Request(
f"http://127.0.0.1:{PORT}/api/login",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return "ok" if resp.status == 200 else "bad"
except urllib.error.HTTPError as e:
print(f" 登录被拒: HTTP {e.code}")
return "bad"
except Exception as e:
print(f" 服务未响应: {e}")
return "offline"
def passwd(new_password: str | None = None) -> int:
"""改密码需要独占数据库文件, 所以先停服务再启动。"""
username = "admin"
password = new_password or _generate()
if len(password) < 12:
sys.exit("FileBrowser 要求密码至少 12 位")
was_running = bool(pids())
if was_running:
print("==> 停止服务以解锁数据库")
stop()
time.sleep(1)
r = _run([EXE, "users", "update", username, "--password", password, "-d", str(DB)])
if r.returncode != 0:
sys.exit(f"修改密码失败:\n{r.stderr}{r.stdout}")
CRED.write_bytes(f"{username}\n{password}\n".encode())
print(f"==> 密码已更新 ({len(password)} 位)")
if was_running:
print("==> 重新启动服务")
start()
time.sleep(1)
result = verify_login(username, password)
msg = {
"ok": "✅ 登录验证通过",
"offline": "⚠️ 服务未运行, 已改密码但未验证",
"bad": "❌ 登录验证失败",
}[result]
print(msg)
print()
print(f"用户名: {username}")
print(f"密 码: {password}")
print(f"凭据已写入 {CRED.name} (确认无误后可删除该文件)")
return 0 if result != "bad" else 1
# ---------------------------------------------------------------- 入口
def main() -> int:
cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
handlers = {
"start": start,
"stop": stop,
"restart": restart,
"status": status,
"passwd": lambda: passwd(sys.argv[2] if len(sys.argv) > 2 else None),
}
if cmd not in handlers:
sys.exit(__doc__.strip())
return handlers[cmd]()
if __name__ == "__main__":
raise SystemExit(main())8. .bat 双击包装
只有一行逻辑,作用是「进目录 + 调 Python」:
@echo off
cd /d "%~dp0"
"C:\Users\masha\AppData\Local\Python\bin\python.exe" filebrowser_ctl.py start %*start.bat / stop.bat / restart.bat / status.bat / passwd.bat 只有最后一个参数不同。
⚠️ .bat 必须存成 GBK + CRLF
这是本次最坑的地方,不要用编辑器/Write 工具直接存 UTF-8。用 Python 生成:
import pathlib, os
PY = r"C:\Users\masha\AppData\Local\Python\bin\python.exe"
def gbk(name, lines):
"""lines 是 str 列表, 写成 GBK + CRLF 的批处理。"""
data = ('\r\n'.join(lines) + '\r\n').encode('gbk')
pathlib.Path(name).write_bytes(data)
for name, cmd in [('start.bat','start'), ('stop.bat','stop'),
('restart.bat','restart'), ('status.bat','status'),
('passwd.bat','passwd')]:
gbk(name, [
'@echo off',
'cd /d "%~dp0"',
f'"{PY}" filebrowser_ctl.py {cmd} %*',
])三个条件缺一不可,缺一就会解析错乱(症状见 §8.10~8.11):
- GBK 编码(本机控制台代码页是 936)
- CRLF 行尾
- 无 BOM
9. 防火墙
netsh advfirewall firewall add rule name="FileBrowser (TCP 8080)" \
dir=in action=allow protocol=TCP localport=8080需要管理员权限,普通会话执行会返回:
请求的操作需要提升(作为管理员运行)。所以单独做了 install-firewall-rule.bat,用户右键「以管理员身份运行」。
脚本里用 net session >nul 2>&1 做提权自检(这是经典的 Windows 管理员检测手法)。
@echo off
cd /d "%~dp0"
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [错误] 本脚本需要管理员权限
echo 请关闭此窗口, 右键本文件, 选择 以管理员身份运行
pause
exit /b 1
)
netsh advfirewall firewall delete rule name="FileBrowser (TCP 8080)" >nul 2>&1
netsh advfirewall firewall add rule name="FileBrowser (TCP 8080)" dir=in action=allow protocol=TCP localport=8080
if %errorlevel% equ 0 (
echo [成功] 防火墙规则已添加, 局域网内可访问:
echo http://192.168.5.50:8080
echo.
echo 提示: 这是允许所有设备入站的宽规则
echo 若要限制为特定网段, 请到 高级安全 Windows 防火墙 中调整
) else (
echo [失败] 无法添加规则
)
pause规则不存在时的查证方式:
netsh advfirewall firewall show rule name="FileBrowser (TCP 8080)" verbose10. 验证清单(照这个顺序验,每步都有预期值)
1) 正确密码登录 -> HTTP 200,返回 JWT 字符串
2) 错误密码登录 -> HTTP 403 (防冒用)
3) 无 token 访问资源 -> HTTP 401 (认证生效)
4) GET /api/resources/. -> HTTP 200,23 个条目(D 盘根目录)
5) POST /api/resources/<新目录> -> HTTP 200
POST /api/resources/<dir>/f.txt -> HTTP 200
GET 读回 -> HTTP 200,内容一致
6) DELETE -> HTTP 204
7) 磁盘上确认目录已消失(`find / -maxdepth 1 -name "__fb_selftest*"` 应为空)要点:
- DELETE 返回 204 后再 GET 可能返回 200 而不是 404——这是 API 的兜底行为,
不代表删除失败。必须回磁盘确认,否则会把假阳性当通过。 - 本机
curl -I和-o /tmp/...会报curl: (23) client returned ERROR on write,
这是 Cygwin curl 的无害怪癖,HTTP 状态码仍然返回。别被它吓到,也别用它当失败信号。 - 用 Python
urllib做验证最干净。注意urlopen对 4xx 会抛HTTPError,
要except住才能断言"应该失败"的场景。
11. 踩坑记录(按发现顺序,全部实撞)
11.1 Cygwin 挂载点搞错
写了 /cygwin64/home/masha/...,偶尔成功偶尔 No such file or directory。
真相:/home/masha 才是挂载点(映射 D:\cygwin64\home\masha),/cygwin64/... 不是挂载点,靠偶发的路径自动转换才偶尔能用。
解法:pwd 看清楚,用 /home/masha/...。
11.2 并行 Bash 调用冲突
一次发两个 Bash 调用,其中一个的 cd 莫名失败。
解法:这台机器上一次只发一个 Bash 调用。
11.3 Cygwin bash 跑 .exe 要正斜杠
"D:\...\filebrowser.exe" version → 未找到命令。
解法:用 ./filebrowser.exe 或 /home/masha/.../filebrowser.exe。
11.4 Windows Python 看不到 /tmp
FileNotFoundError: '/tmp/fb_dl/...'。
解法:cygpath -w 转成 Windows 路径再交给 Python。
11.5 Windows 控制台 GBK 编码
Python 打印中文/emoji 直接 UnicodeEncodeError: 'gbk' codec can't encode character '✅'。
解法:脚本开头
for _s in (sys.stdout, sys.stderr):
if hasattr(_s, "reconfigure"):
_s.reconfigure(encoding="utf-8", errors="replace")11.6 users add --scope 传绝对路径
--scope "D:/" → Error: failed to create user home dir: [/D:]: mkdir D:\D:。
解法:scope 留空(默认 .),它相对 root 解析。
11.7 数据库被独占锁定
服务运行时执行 users update → Error: timeout。
Bolt DB 是文件锁,运行中不可写。
解法:先停服务再改用户/配置。passwd 子命令已自动处理。
11.8 taskkill 参数
taskkill //IM filebrowser.exe //F(Cygwin 双斜杠惯例)→ 无效参数/选项 - '//IM'。
解法:单斜杠 taskkill /IM ... /F,或干脆用 PowerShell Stop-Process -Name filebrowser -Force。
11.9 Cygwin 写文件偷偷加 CRLF
$ printf 'admin\n%s\n' "$PASS" > CREDENTIALS.txt
$ xxd CREDENTIALS.txt
41 64 6d 69 6e 0a 5a 76 7a 73 51 34 5a 55 36 4b 4b 56 66 6e 70 79 0d 0a
^^$(sed -n '2p' CREDENTIALS.txt) 取到的密码尾部带 \r(命令替换只剥 \n 不剥 \r)。
更阴险的是:\r 在 Python 源码里是行边界,塞进单引号字符串里会得到SyntaxError: unterminated string literal。
后果:当时用被污染的密码建了用户,导致登录一律 403,还以为是别的问题。
解法:凭据文件用 Path.write_bytes() 写,读取也用 Python 而非 sed。
11.10 .bat 存 UTF-8 → 解析器直接崩
中文字节被按 GBK 误读,连 ASCII 行都被拆碎:
'p0"' 不是内部或外部命令,也不是可运行的程序
'equ' 不是内部或外部命令
'.*LISTENING" >nul' 不是内部或外部命令
'-ano' 不是内部或外部命令(%~dp0、if %errorlevel% equ 0、netstat -ano 全被拆散)
解法:存 GBK。
11.11 .bat 用 LF 行尾 → 同样崩
修了编码之后仍然崩。原因是转码时用了 Path.read_text()——
它启用通用换行模式,把 CRLF 转成 LF,再 encode('gbk') 写回去就丢了行尾。
而 cmd.exe 要求 CRLF,LF 行尾会导致解析错乱。
解法:显式 '\r\n'.join(lines) + '\r\n',或 read_bytes() 处理。
顺带:chcp 65001写在.bat里没用。cmd 按初始代码页读文件,chcp只影响后续 echo 的显示,救不了已经被误读的字节。
11.12 PATH 被 Cygwin 污染
/usr/local/bin
/usr/bin ← Cygwin 在前
/c/Windows/system32 ← system32 在后cmd.exe 继承这个 PATH,于是:
timeout /t 2 >nul→timeout: 无效的时间间隔"/t"(调到了 GNU coreutils 版)- 原生 Windows 工具可能被解析到 Cygwin 版
解法:所有系统工具走绝对路径,用 os.environ["SystemRoot"]/System32/xxx.exe;
端口探测改用 Python socket,不碰 netstat。
另外:Windows Python 进程继承的也是这套 Cygwin 风格 PATH,shutil.which() 同样不可靠。
11.13 %~$PATH: 只能在 FOR/CALL 里用
echo timeout = %~$PATH:timeout → 非法的批处理替换...。
(诊断思路错,与部署无关,记下来免得再试。)
11.14 WebFetch 到 github.com 被拦
Unable to verify if domain github.com is safe to fetch。
解法:curl 打 https://api.github.com/...,这条路通。
11.15 ipconfig 输出被当二进制
grep: (标准输入): 匹配到二进制文件。
解法:用 Python socket 拿 IP。
12. 最终产物
filebrowser.exe 官方 Windows amd64 二进制 (36 MB)
filebrowser.db 配置 + 用户 (Bolt DB 单文件, 64 KB)
CREDENTIALS.txt 初始账号密码, 登录后可删
filebrowser.out.log 运行日志 (追加写入)
filebrowser_ctl.py 启停/状态/改密码 控制脚本
start/stop/restart/status/passwd.bat 双击包装 (GBK+CRLF)
install-firewall-rule.bat 防火墙放行, 需管理员
README.md 日常使用说明
DEPLOYMENT.md 本文档本机最终状态:
进程: [26616]
端口 8080: LISTENING
访问地址: http://localhost:8080 / http://192.168.5.50:8080
用户名: admin13. 附录:FileBrowser 关键知识点
13.1 配置优先级
Flags > 环境变量 (FB_ 前缀, 如 FB_DISABLEPREVIEWRESIZE)
> 配置文件 (.filebrowser.{json,toml,yaml}, 查 ./、$HOME/、/etc/filebrowser/)
> 数据库值
> 默认值注意:只有部分选项能放配置文件,其余只存在数据库里,
必须用 config set / config import 写。
13.2 数据库自动初始化
如果 -d 指定的数据库文件不存在,FileBrowser 进入 quick setup mode:
自动建库,并用 --username / --password 两个 flag 创建首个用户。
所以最简启动是:
./filebrowser.exe -a 0.0.0.0 -p 8080 -r "D:/" -d filebrowser.db \
--username admin --password 你的密码本文档选了显式 config init + config set + users add,因为更好控制、可审计。
13.3 常用命令
./filebrowser.exe config init # 初始化数据库
./filebrowser.exe config set # 改配置
./filebrowser.exe config cat # 打印当前配置
./filebrowser.exe config export # 导出配置
./filebrowser.exe config import # 导入配置
./filebrowser.exe users add # 加用户
./filebrowser.exe users update # 改用户
./filebrowser.exe users ls # 列用户
./filebrowser.exe users find # 查用户
./filebrowser.exe users rm # 删用户
./filebrowser.exe version全局 flag:-c/--config、-d/--database(默认 ./filebrowser.db)。
13.4 权限位
--perm.admin、--perm.login、--perm.new、--perm.rename、--perm.modify、--perm.delete、--perm.share、--perm.download、--perm.upload、--perm.archive、--perm.execute、--perm.pick、--perm.properties
默认 create/delete/rename/modify/share/download/execute 都是开的。
13.5 API 速查
POST /api/login body: {"username","password"} -> 返回 JWT 字符串
X-Auth: <jwt> 后续请求的认证头
GET /api/resources/<path> 列目录 (返回 items 数组)
POST /api/resources/<path> 建目录
DELETE /api/resources/<path> 删除GET /api/resources/. 返回的是对象不是数组,条目在 items 字段里:
{"items": [...], "numDirs": 23, "numFiles": 0, "sorting": {...},
"path": ".", "name": "", "isDir": true, "type": "directory"}遍历成 for it in data: 会遍历到键名字符串,报TypeError: string indices must be integers, not 'str'。要取 data["items"]。
13.6 其他有用默认值
minimumPasswordLength= 12(短了直接被拒)--disableExec= true(命令执行器默认关,保持关闭更安全)- TUS 上传分块 10 MB,重试 5 次
tokenExpirationTime默认 2hdisableTypeDetectionByHeader= false(会读文件头判断类型)- 支持
-l file,stdout这类日志组合
14. 安全注意事项(务必告知用户)
官方项目 2026-09-01 已归档,不再发版、不再修安全漏洞。启动时会打印:
NOTICE: File Browser is being wound down. NOTICE: The project is archived on 2026-09-01, after which there will be no NOTICE: further releases and no security fixes. Known unfixed issues are at NOTICE: https://github.com/filebrowser/filebrowser/security/advisories可信家用网络内使用没问题,绝不暴露公网。
- HTTP 明文,不是 HTTPS。配置里有
--cert/--key可上 TLS。 - CREDENTIALS.txt 是明文密码。数据库里存的是 bcrypt hash,只有这个文件是明文。
登录验证通过后就该删。 - 根目录给大了权限:
D:\+ admin 权限,网页里能看到$RECYCLE.BIN和System Volume Information。误删可从回收站还原,但操作要留意。 - 防火墙规则是"放行所有入站 8080"的宽规则。
要限网段请到「高级安全 Windows 防火墙」里改。 想开机自启可用计划任务(需管理员):
schtasks /Create /TN "FileBrowser" /SC ONLOGON ^ /TR "C:\Users\masha\AppData\Local\Python\bin\python.exe D:\cygwin64\home\masha\Documents\workspace\filebrowser\filebrowser_ctl.py start" /F删除:
schtasks /Delete /TN "FileBrowser" /F
15. 从零复现(浓缩版)
给别人看可以直接跳到这一节:
# 0. 装 Python(>=3.10); 确认 curl 可用; 查本机架构 x64/ARM64
# 1. 下载 + 校验
mkdir fb && cd fb
V=v2.63.23
B=https://github.com/filebrowser/filebrowser/releases/download/$V
curl -sSfL -o $V-checksums.txt $B/filebrowser_${V#v}_checksums.txt
curl -sSfL -o win.zip $B/windows-amd64-filebrowser.zip
sha256sum win.zip # 比对 checksums.txt 里的 fdb1d86dfafff8b3861867c7797ce786570013088678e03de17cfd9476c72384
# 2. 解压
python -c "import zipfile; zipfile.ZipFile('win.zip').extractall('.')"
# 3. 初始化 + 配置(把 D:/ 换成你要的根目录)
./filebrowser.exe config init -d filebrowser.db
./filebrowser.exe config set -d filebrowser.db \
-a 0.0.0.0 -p 8080 -r "D:/" --locale zh-cn --tokenExpirationTime 12h
# 4. 建用户(scope 千万别传绝对路径!)
./filebrowser.exe users add admin 你的至少12位密码 --perm.admin -d filebrowser.db
# 5. 放行防火墙(需管理员)
netsh advfirewall firewall add rule name="FileBrowser (TCP 8080)" \
dir=in action=allow protocol=TCP localport=8080
# 6. 启动 & 验证
./filebrowser.exe -d filebrowser.db
# 浏览器打开 http://localhost:8080 和 http://<局域网IP>:8080生产用法请补上 §7 的 filebrowser_ctl.py(它解决「关掉终端进程就死」和
「改密码要独占数据库」这两个真实问题),以及 §8 的 .bat 包装。
然后务必读一遍 §11 踩坑记录。