feat: add startup reset password flag

This commit is contained in:
Weilong Liao
2026-06-17 00:32:37 +08:00
committed by GitHub
parent e84e94f39e
commit 4f5075e608
8 changed files with 512 additions and 3 deletions

View File

@@ -9,6 +9,8 @@ from filelock import FileLock, Timeout
from ..utils import check_astrbot_root, check_dashboard, get_astrbot_root
DASHBOARD_RESET_PASSWORD_ENV = "ASTRBOT_RESET_DASHBOARD_PASSWORD"
async def run_astrbot(astrbot_root: Path) -> None:
"""Run AstrBot"""
@@ -28,8 +30,13 @@ async def run_astrbot(astrbot_root: Path) -> None:
@click.option("--reload", "-r", is_flag=True, help="Auto-reload plugins")
@click.option("--port", "-p", help="AstrBot Dashboard port", required=False, type=str)
@click.option(
"--reset-password",
is_flag=True,
help="Reset dashboard initial password on startup",
)
@click.command()
def run(reload: bool, port: str) -> None:
def run(reload: bool, port: str | None, reset_password: bool) -> None:
"""Run AstrBot"""
try:
os.environ["ASTRBOT_CLI"] = "1"
@@ -50,6 +57,9 @@ def run(reload: bool, port: str) -> None:
click.echo("Plugin auto-reload enabled")
os.environ["ASTRBOT_RELOAD"] = "1"
if reset_password:
os.environ[DASHBOARD_RESET_PASSWORD_ENV] = "1"
lock_file = astrbot_root / "astrbot.lock"
lock = FileLock(lock_file, timeout=5)
with lock.acquire():

View File

@@ -16,6 +16,7 @@ from .default import DEFAULT_CONFIG, DEFAULT_VALUE_MAP
ASTRBOT_CONFIG_PATH = os.path.join(get_astrbot_data_path(), "cmd_config.json")
DASHBOARD_INITIAL_PASSWORD_ENV = "ASTRBOT_DASHBOARD_INITIAL_PASSWORD"
DASHBOARD_RESET_PASSWORD_ENV = "ASTRBOT_RESET_DASHBOARD_PASSWORD"
logger = logging.getLogger("astrbot")
@@ -77,7 +78,11 @@ class AstrBotConfig(dict):
)
# 检查配置完整性,并插入
has_new = self.check_config_integrity(default_config, conf)
if (
reset_dashboard_password = self._consume_reset_dashboard_password_flag()
if reset_dashboard_password and "dashboard" in conf:
self._reset_generated_dashboard_password(conf)
has_new = True
elif (
"dashboard" in conf
and isinstance(conf["dashboard"], dict)
and not conf["dashboard"].get("pbkdf2_password")
@@ -118,6 +123,11 @@ class AstrBotConfig(dict):
True,
)
@staticmethod
def _consume_reset_dashboard_password_flag() -> bool:
raw_value = os.environ.pop(DASHBOARD_RESET_PASSWORD_ENV, "")
return raw_value.strip().lower() in {"1", "true", "yes", "on"}
@staticmethod
def _resolve_initial_dashboard_password() -> str:
env_password = os.environ.get(DASHBOARD_INITIAL_PASSWORD_ENV)

169
docs/en/use/cli.md Normal file
View File

@@ -0,0 +1,169 @@
# CLI Commands
The AstrBot CLI initializes instances, starts AstrBot, updates common config values, and manages plugins.
If you install AstrBot with `uv`:
```bash
uv tool install astrbot --python 3.12
```
`uv` creates the `astrbot` executable and puts it on `PATH`. You can inspect the path with:
::: code-group
```bash [Linux / macOS]
which astrbot
```
```powershell [Windows]
where.exe astrbot
```
:::
> [!TIP]
> Run the commands below from the AstrBot working directory.
## Quick Start
Initialize the directory once, then start AstrBot:
```bash
astrbot init
astrbot run
```
`astrbot init` creates the data directories and configuration files required by AstrBot. After initialization, use `astrbot run` for later starts.
## Top-Level Commands
| Command | Purpose |
| --- | --- |
| `astrbot init` | Initialize the current directory as an AstrBot working directory. |
| `astrbot run` | Start AstrBot in the foreground. |
| `astrbot conf` | Read or update common config values. |
| `astrbot password` | Change the WebUI login password interactively. |
| `astrbot plug` | Create, install, update, remove, or search plugins. |
| `astrbot help` | Show CLI help. |
| `astrbot --version` | Show the AstrBot CLI version. |
## Start AstrBot
```bash
astrbot run
```
Common options:
| Option | Purpose |
| --- | --- |
| `-p, --port <PORT>` | Set the WebUI port. |
| `-r, --reload` | Enable plugin auto-reload for plugin development. |
| `--reset-password` | Reset the WebUI initial password on startup and print the new password in startup logs. |
Examples:
```bash
astrbot run --port 6185
astrbot run --reload
astrbot run --reset-password
```
If you forget the WebUI login password, run this from the AstrBot working directory:
```bash
astrbot run --reset-password
```
AstrBot regenerates the initial password during startup and prints it in startup logs. After logging in, change the password in the WebUI immediately.
When starting directly from source, you can also run:
```bash
python main.py --reset-password
```
## Config
`astrbot conf` reads and updates common config values.
```bash
astrbot conf get
astrbot conf get dashboard.port
astrbot conf set dashboard.port 6185
```
Supported keys:
| Key | Description |
| --- | --- |
| `timezone` | Time zone, for example `Asia/Shanghai`. |
| `log_level` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`. |
| `dashboard.port` | WebUI port. |
| `dashboard.username` | WebUI username. |
| `dashboard.password` | WebUI password. |
| `callback_api_base` | Callback API base URL. Must start with `http://` or `https://`. |
Changing the dashboard password writes the current password hashes automatically:
```bash
astrbot conf set dashboard.password "new-password"
```
You can also use the dedicated interactive password command:
```bash
astrbot password
astrbot password --username admin
```
## Plugins
`astrbot plug` manages plugins under `data/plugins`.
| Command | Purpose |
| --- | --- |
| `astrbot plug list` | List installed plugins. |
| `astrbot plug list --all` | Also show uninstalled plugins. |
| `astrbot plug search <QUERY>` | Search plugins. |
| `astrbot plug install <NAME>` | Install a plugin. |
| `astrbot plug update [NAME]` | Update one plugin, or all updatable plugins if no name is given. |
| `astrbot plug remove <NAME>` | Remove an installed plugin. |
| `astrbot plug new <NAME>` | Create a new plugin from the template. |
Use a GitHub proxy when installing or updating plugins:
```bash
astrbot plug install example-plugin --proxy https://gh-proxy.example.com/
astrbot plug update --proxy https://gh-proxy.example.com/
```
Creating a new plugin asks for the author, description, version, and repository URL:
```bash
astrbot plug new my-plugin
```
## Help
Show general CLI help:
```bash
astrbot help
```
Show help for a specific command:
```bash
astrbot help run
astrbot run --help
astrbot help conf
astrbot plug --help
```
Show the version:
```bash
astrbot --version
```

169
docs/zh/use/cli.md Normal file
View File

@@ -0,0 +1,169 @@
# CLI 指令
AstrBot CLI 用于初始化实例、启动 AstrBot、修改常用配置和管理插件。
如果你使用 `uv` 安装:
```bash
uv tool install astrbot --python 3.12
```
`uv` 会生成 `astrbot` 可执行文件,并把它放到 `PATH` 中。可以用下面的命令确认路径:
::: code-group
```bash [Linux / macOS]
which astrbot
```
```powershell [Windows]
where.exe astrbot
```
:::
> [!TIP]
> 下面的命令都需要在 AstrBot 工作目录中执行。
## 快速开始
第一次部署时先初始化目录,再启动 AstrBot
```bash
astrbot init
astrbot run
```
`astrbot init` 会在当前目录创建 AstrBot 所需的数据目录和配置文件。初始化完成后,后续启动只需要执行 `astrbot run`。
## 顶层指令
| 指令 | 用途 |
| --- | --- |
| `astrbot init` | 初始化当前目录为 AstrBot 工作目录。 |
| `astrbot run` | 在前台启动 AstrBot。 |
| `astrbot conf` | 查看或修改常用配置项。 |
| `astrbot password` | 交互式修改 WebUI 登录密码。 |
| `astrbot plug` | 创建、安装、更新、删除或搜索插件。 |
| `astrbot help` | 查看 CLI 帮助。 |
| `astrbot --version` | 查看 AstrBot CLI 版本。 |
## 启动 AstrBot
```bash
astrbot run
```
常用选项:
| 选项 | 用途 |
| --- | --- |
| `-p, --port <PORT>` | 指定 WebUI 端口。 |
| `-r, --reload` | 启用插件自动重载,适合插件开发调试。 |
| `--reset-password` | 启动时重置 WebUI 初始密码,并在启动日志中打印新密码。 |
示例:
```bash
astrbot run --port 6185
astrbot run --reload
astrbot run --reset-password
```
如果你忘记了 WebUI 登录密码,可以在 AstrBot 工作目录中执行:
```bash
astrbot run --reset-password
```
AstrBot 会在启动时重新生成初始密码,并在启动日志中打印。登录后请立即在 WebUI 中修改密码。
使用源码方式直接启动时,也可以执行:
```bash
python main.py --reset-password
```
## 配置
`astrbot conf` 用于查看和修改常用配置项。
```bash
astrbot conf get
astrbot conf get dashboard.port
astrbot conf set dashboard.port 6185
```
支持的配置项:
| 配置项 | 说明 |
| --- | --- |
| `timezone` | 时区,例如 `Asia/Shanghai`。 |
| `log_level` | 日志等级:`DEBUG`、`INFO`、`WARNING`、`ERROR`、`CRITICAL`。 |
| `dashboard.port` | WebUI 端口。 |
| `dashboard.username` | WebUI 用户名。 |
| `dashboard.password` | WebUI 密码。 |
| `callback_api_base` | 回调 API 基础地址,需要以 `http://` 或 `https://` 开头。 |
修改密码时会自动写入新版密码哈希:
```bash
astrbot conf set dashboard.password "new-password"
```
也可以使用专门的交互式密码指令:
```bash
astrbot password
astrbot password --username admin
```
## 插件
`astrbot plug` 用于管理 `data/plugins` 下的插件。
| 指令 | 用途 |
| --- | --- |
| `astrbot plug list` | 查看已安装插件。 |
| `astrbot plug list --all` | 同时显示未安装插件。 |
| `astrbot plug search <QUERY>` | 搜索插件。 |
| `astrbot plug install <NAME>` | 安装插件。 |
| `astrbot plug update [NAME]` | 更新指定插件;不传名称时更新所有可更新插件。 |
| `astrbot plug remove <NAME>` | 删除已安装插件。 |
| `astrbot plug new <NAME>` | 基于模板创建新插件。 |
安装或更新插件时可以使用 GitHub 代理:
```bash
astrbot plug install example-plugin --proxy https://gh-proxy.example.com/
astrbot plug update --proxy https://gh-proxy.example.com/
```
创建新插件会交互式询问作者、描述、版本和仓库地址:
```bash
astrbot plug new my-plugin
```
## 帮助
查看全部 CLI 帮助:
```bash
astrbot help
```
查看指定指令帮助:
```bash
astrbot help run
astrbot run --help
astrbot help conf
astrbot plug --help
```
查看版本:
```bash
astrbot --version
```

30
main.py
View File

@@ -9,6 +9,28 @@ import runtime_bootstrap
runtime_bootstrap.initialize_runtime_bootstrap()
DASHBOARD_RESET_PASSWORD_ENV = "ASTRBOT_RESET_DASHBOARD_PASSWORD"
def _apply_startup_env_flags(argv: list[str]) -> None:
"""Apply startup flags that must take effect before core imports.
Args:
argv: Command-line arguments excluding the executable name.
"""
if "-h" in argv or "--help" in argv:
return
startup_parser = argparse.ArgumentParser(add_help=False)
startup_parser.add_argument("--reset-password", action="store_true")
startup_args, _ = startup_parser.parse_known_args(argv)
if startup_args.reset_password:
os.environ[DASHBOARD_RESET_PASSWORD_ENV] = "1"
_apply_startup_env_flags(sys.argv[1:])
from astrbot.core import LogBroker, LogManager, db_helper, logger # noqa: E402
from astrbot.core.config.default import VERSION # noqa: E402
from astrbot.core.initial_loader import InitialLoader # noqa: E402
@@ -141,6 +163,14 @@ if __name__ == "__main__":
help="Specify the directory path for WebUI static files",
default=None,
)
parser.add_argument(
"--reset-password",
action="store_true",
help=(
"Reset the dashboard initial password on startup and print it in "
"startup logs"
),
)
args = parser.parse_args()
check_env()

43
tests/test_cli_run.py Normal file
View File

@@ -0,0 +1,43 @@
import os
import sys
from click.testing import CliRunner
from astrbot.cli.commands import cmd_run
def test_run_reset_password_sets_startup_env(monkeypatch, tmp_path):
(tmp_path / ".astrbot").touch()
monkeypatch.chdir(tmp_path)
monkeypatch.delenv(cmd_run.DASHBOARD_RESET_PASSWORD_ENV, raising=False)
original_env = {
"ASTRBOT_CLI": os.environ.get("ASTRBOT_CLI"),
"ASTRBOT_ROOT": os.environ.get("ASTRBOT_ROOT"),
cmd_run.DASHBOARD_RESET_PASSWORD_ENV: os.environ.get(
cmd_run.DASHBOARD_RESET_PASSWORD_ENV
),
}
original_sys_path = list(sys.path)
called = False
async def fake_run_astrbot(astrbot_root):
nonlocal called
called = True
assert astrbot_root == tmp_path
assert os.environ[cmd_run.DASHBOARD_RESET_PASSWORD_ENV] == "1"
monkeypatch.setattr(cmd_run, "run_astrbot", fake_run_astrbot)
try:
result = CliRunner().invoke(cmd_run.run, ["--reset-password"])
finally:
for key, value in original_env.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
sys.path[:] = original_sys_path
assert result.exit_code == 0, result.output
assert called is True

View File

@@ -10,7 +10,12 @@ from unittest import mock
import pytest
from astrbot.core.utils.io import should_use_bundled_dashboard_dist
from main import check_dashboard_files, check_env
from main import (
DASHBOARD_RESET_PASSWORD_ENV,
_apply_startup_env_flags,
check_dashboard_files,
check_env,
)
class _version_info:
@@ -62,6 +67,30 @@ def test_check_env(monkeypatch):
check_env()
def test_apply_startup_env_flags_sets_reset_password_env(monkeypatch):
monkeypatch.delenv(DASHBOARD_RESET_PASSWORD_ENV, raising=False)
_apply_startup_env_flags(["--webui-dir", "/tmp/webui", "--reset-password"])
assert os.environ[DASHBOARD_RESET_PASSWORD_ENV] == "1"
def test_apply_startup_env_flags_ignores_unrelated_args(monkeypatch):
monkeypatch.delenv(DASHBOARD_RESET_PASSWORD_ENV, raising=False)
_apply_startup_env_flags(["--webui-dir", "/tmp/webui"])
assert DASHBOARD_RESET_PASSWORD_ENV not in os.environ
def test_apply_startup_env_flags_does_not_reset_for_help(monkeypatch):
monkeypatch.delenv(DASHBOARD_RESET_PASSWORD_ENV, raising=False)
_apply_startup_env_flags(["--reset-password", "--help"])
assert DASHBOARD_RESET_PASSWORD_ENV not in os.environ
def test_check_env_appends_user_site_packages_after_runtime_paths(monkeypatch):
astrbot_root = "/tmp/astrbot-root"
site_packages_path = "/tmp/astrbot-site-packages"

View File

@@ -10,6 +10,8 @@ from astrbot.core.config.default import DEFAULT_VALUE_MAP
from astrbot.core.config.i18n_utils import ConfigMetadataI18n
from astrbot.core.utils.auth_password import (
DEFAULT_DASHBOARD_PASSWORD,
hash_dashboard_password,
hash_md5_dashboard_password,
validate_dashboard_password,
verify_dashboard_password,
)
@@ -320,6 +322,53 @@ class TestAstrBotConfigLoad:
config["dashboard"]["password"], generated_password
)
def test_reset_dashboard_password_env_rotates_existing_password(
self, temp_config_path, monkeypatch
):
"""Test startup reset flag rotates an already configured dashboard password."""
old_password = "OldPassword123"
default_config = {
"dashboard": {
"username": "astrbot",
"password": "",
"pbkdf2_password": "",
},
}
with open(temp_config_path, "w", encoding="utf-8") as f:
json.dump(
{
"dashboard": {
"username": "astrbot",
"password": hash_md5_dashboard_password(old_password),
"pbkdf2_password": hash_dashboard_password(old_password),
"password_change_required": False,
"password_storage_upgraded": True,
}
},
f,
)
monkeypatch.setenv("ASTRBOT_RESET_DASHBOARD_PASSWORD", "1")
config = AstrBotConfig(
config_path=temp_config_path,
default_config=default_config,
)
generated_password = getattr(config, "_generated_dashboard_password", None)
assert isinstance(generated_password, str)
assert config["dashboard"]["password_change_required"] is True
assert config["dashboard"]["password_storage_upgraded"] is True
assert "ASTRBOT_RESET_DASHBOARD_PASSWORD" not in os.environ
assert verify_dashboard_password(
config["dashboard"]["pbkdf2_password"], generated_password
)
assert not verify_dashboard_password(
config["dashboard"]["pbkdf2_password"], old_password
)
assert verify_dashboard_password(
config["dashboard"]["password"], generated_password
)
def test_legacy_astrbot_user_without_change_flag_keeps_legacy_password(
self, temp_config_path
):