diff --git a/README.md b/README.md
index 16c405a..9ca2f70 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,7 @@ cd web && npm run build && rm -rf internal/httpapi/dist && cp -r web/dist intern
```bash
sudo dpkg -i webui4frpc-0.1.4-linux-amd64.deb # 或 linux-arm64.deb
-sudo nano /etc/webui4frpc/webui4frpc.env # 改掉默认密码
+sudo nano /etc/webui4frpc/config.json # 改掉默认密码(见「从配置文件启动」)
sudo systemctl restart webui4frpc
```
@@ -96,7 +96,7 @@ sudo systemctl restart webui4frpc
```bash
sudo rpm -i webui4frpc-0.1.4-linux-x86_64.rpm
-sudo vi /etc/webui4frpc/webui4frpc.env
+sudo vi /etc/webui4frpc/config.json
sudo systemctl enable --now webui4frpc
```
@@ -152,12 +152,19 @@ sha256sum -c SHA256SUMS --ignore-missing
./webui4frpc -addr 127.0.0.1:7500 -user admin -password admin123 -workdir ./data
```
+或者用配置文件(推荐,口令不必出现在命令行与进程列表里):
+
+```bash
+./webui4frpc -config ./config.json
+```
+
打开 ,输入账号密码进入画布。首次使用可到「设置」页一键安装 frpc(或手动指定路径),然后回到画布创建 local / remote 并连线、保存。
### 参数
| 参数 | 默认 | 说明 |
| --- | --- | --- |
+| `-config` | `/config.json`(或 `$W4F_CONFIG`) | 配置文件路径;文件不存在不算错误 |
| `-addr` | `127.0.0.1:7500` | 监听地址 |
| `-user` / `-password` | `admin` / `admin` | Basic 认证(自动同步为 system 管理员账号) |
| `-workdir` | `./webui-frpc` | 数据目录(db / 配置 / 日志 / bin) |
@@ -168,6 +175,104 @@ sha256sum -c SHA256SUMS --ignore-missing
环境变量 `W4F_HOST` 可覆盖节点对外可达的主机名(Docker DNS / 通配监听场景用)。
+## 从配置文件启动
+
+除了命令行 flag,也可以用 JSON 配置文件启动。**推荐用它承载口令**:命令行参数会出现在
+`ps` 输出、systemd 单元文件与 shell history 里,配置文件可以把口令放到只 root 可读的地方。
+
+### 优先级
+
+```
+命令行 flag > 配置文件 > 内置默认值
+```
+
+- **只有显式传入的 flag 才会覆盖配置文件**。实现上用 `flag.Visit` 判断某个 flag 是否真的
+ 被写在命令行上 —— Go 的 `flag` 包会先把默认值塞进变量,若直接读变量,默认值会无条件
+ 压过配置文件。这是这套逻辑里唯一容易出错的地方,已有回归测试守着
+ (`internal/appconfig` 的 `TestResolveFileBeatsFlagDefaults`)。
+- 配置文件里**没写**或留空的字段,才回落到内置默认值。
+- `-peer` 特殊:显式传了 `-peer` 就用命令行的列表**整体替换**配置文件里的 `peers`,
+ 而不是追加 —— 否则在命令行上删掉的对端会被配置文件复活。
+- `-join-key` 的内置默认是 `$W4F_JOIN_KEY`;未显式传 flag 时,配置文件里的 `join_key`
+ 只在环境变量为空时才生效。
+- `-config` 未传时的查找顺序:`$W4F_CONFIG` → `/config.json`。
+ 注意 `workdir` 用的是 **flag 值**(显式或默认):配置文件必须先能被找到,才谈得上
+ 读取它里面的 `workdir`。
+- 文件**不存在不算错误**(回到旧的纯 flag 行为);文件存在但 JSON 解析失败、出现未知键
+ (例如把 `password` 拼成 `passwd`)、或引用了未定义的环境变量,都会**立即报错退出**,
+ 不会静默忽略 —— 静默回落到 `admin/admin` 才是真正的安全问题。
+
+### 字段
+
+字段与 flag 一一对应,全部可选:
+
+| 字段 | 对应 flag | 说明 |
+| --- | --- | --- |
+| `addr` | `-addr` | 监听地址 |
+| `user` | `-user` | Basic 认证用户名 |
+| `password` | `-password` | Basic 认证口令(推荐用 `${VAR}` 或 `file:`) |
+| `workdir` | `-workdir` | 数据目录 |
+| `bin` | `-bin` | 初始 frpc 二进制路径 |
+| `frpc` | `-frpc` | worker 默认 frpc 路径 |
+| `peers` | `-peer` | 集群对端 `host:port` 数组 |
+| `join_key` | `-join-key` | 集群准入密钥(也接受 `joinKey` 写法) |
+
+### 口令的两种写法
+
+所有字符串字段都支持这两种展开形式,不只是 `password`:
+
+```json
+{ "password": "${W4F_PASSWORD}" }
+```
+
+```json
+{ "password": "file:/etc/webui4frpc/secret/password" }
+```
+
+- `${VAR}` 取环境变量;变量**未定义或是空值**会报错退出(而不是展开成空口令)。
+ 想写字面量 `$` 用 `$$`。
+- `file:/path` 只取文件的**第一行**(去掉行尾换行),适合 `chmod 600` 的 secrets 文件。
+ 文件读不到会报错退出。
+- 展开后的值**不会**出现在任何日志里:启动横幅只打印「字段来源」与脱敏摘要
+ (`password=`、`join-key=`),`Values` 的 `String()` 也做了脱敏,
+ 所以连手滑 `log.Printf("%v", ...)` 都不会漏。
+
+### 示例
+
+```json
+{
+ "addr": "0.0.0.0:7500",
+ "user": "jianf",
+ "password": "file:/etc/webui4frpc/secret/password",
+ "workdir": "/var/lib/webui4frpc",
+ "frpc": "/usr/local/bin/frpc",
+ "peers": ["10.0.0.2:7500", "10.0.0.3:7500"],
+ "join_key": "${W4F_JOIN_KEY}"
+}
+```
+
+对应的 systemd 单元不再需要把口令写进 `ExecStart`:
+
+```ini
+[Service]
+User=webui4frpc
+Group=webui4frpc
+EnvironmentFile=-/etc/webui4frpc/webui4frpc.env
+ExecStart=/usr/bin/webui4frpc -config /etc/webui4frpc/config.json
+ReadWritePaths=/var/lib/webui4frpc
+```
+
+`packaging/common/` 里带了一份可直接抄的单元模板与 `config.json` 示例。
+
+### 关于「没配口令」
+
+配置文件里没写 `password`、命令行也没给时,**行为与旧版一致**(回落到内置默认值),
+但启动日志会打一行明确警告:
+
+```
+WARNING: no password configured (neither -password nor the config file); using the built-in default — change it before exposing this node
+```
+
### 负载均衡参数
每节点独立生效;集群页的开关**优先于**这里的默认值(在页面上选过就以页面为准,重启不丢)。