mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 01:40:15 +08:00
- Fixed '_GeneratorContextManager' error in pip_installer.py by using synchronous 'with' for constraints_file(). - Fixed 'CoroutineType' has no attribute 'is_file' in dashboard/routes/config.py by adding missing await. - Fixed undefined names (Group, ComponentTypes, File, Reply, At) in aiocqhttp_platform_adapter.py. - Added 'pytest-cov' for code coverage testing.
170 lines
5.2 KiB
Python
170 lines
5.2 KiB
Python
import asyncio
|
||
import json
|
||
import os
|
||
from pathlib import Path
|
||
|
||
import click
|
||
from filelock import FileLock, Timeout
|
||
|
||
from astrbot.core.config.default import DEFAULT_CONFIG
|
||
from astrbot.core.utils.astrbot_path import astrbot_paths
|
||
|
||
from ..utils import check_dashboard
|
||
from .cmd_conf import (
|
||
_validate_dashboard_password,
|
||
ensure_config_file,
|
||
prompt_dashboard_password,
|
||
set_dashboard_credentials,
|
||
)
|
||
|
||
|
||
async def initialize_astrbot(
|
||
astrbot_root: Path,
|
||
*,
|
||
yes: bool,
|
||
backend_only: bool,
|
||
admin_username: str | None,
|
||
admin_password: str | None,
|
||
) -> None:
|
||
"""Execute AstrBot initialization logic"""
|
||
dot_astrbot = astrbot_root / ".astrbot"
|
||
|
||
if not dot_astrbot.exists():
|
||
if yes or click.confirm(
|
||
f"Install AstrBot to this directory? {astrbot_root}",
|
||
default=True,
|
||
abort=True,
|
||
):
|
||
dot_astrbot.touch()
|
||
click.echo(f"Created {dot_astrbot}")
|
||
|
||
paths = {
|
||
"data": astrbot_root / "data",
|
||
"config": astrbot_root / "data" / "config",
|
||
"plugins": astrbot_root / "data" / "plugins",
|
||
"temp": astrbot_root / "data" / "temp",
|
||
"skills": astrbot_root / "data" / "skills",
|
||
}
|
||
|
||
for name, path in paths.items():
|
||
path.mkdir(parents=True, exist_ok=True)
|
||
click.echo(
|
||
f"{'Created' if not path.exists() else f'{name} Directory exists'}: {path}"
|
||
)
|
||
|
||
config_path = astrbot_root / "data" / "cmd_config.json"
|
||
if not config_path.exists():
|
||
config_path.write_text(
|
||
json.dumps(DEFAULT_CONFIG, ensure_ascii=False, indent=2),
|
||
encoding="utf-8-sig",
|
||
)
|
||
click.echo(f"Created config file: {config_path}")
|
||
|
||
if admin_password and not admin_username:
|
||
raise click.ClickException(
|
||
"--admin-password requires --admin-username to be provided"
|
||
)
|
||
|
||
if admin_username:
|
||
password_hash = (
|
||
_validate_dashboard_password(admin_password)
|
||
if admin_password is not None
|
||
else None
|
||
)
|
||
if password_hash is None:
|
||
if yes or os.environ.get("ASTRBOT_SYSTEMD") == "1":
|
||
raise click.ClickException(
|
||
"Non-interactive init requires --admin-password when --admin-username is set"
|
||
)
|
||
password_hash = prompt_dashboard_password("Dashboard admin password")
|
||
|
||
config = ensure_config_file()
|
||
set_dashboard_credentials(
|
||
config,
|
||
username=admin_username.strip(),
|
||
password_hash=password_hash,
|
||
)
|
||
config_path.write_text(
|
||
json.dumps(config, ensure_ascii=False, indent=2),
|
||
encoding="utf-8-sig",
|
||
)
|
||
click.echo(f"Configured dashboard admin username: {admin_username.strip()}")
|
||
|
||
if not backend_only and (
|
||
yes
|
||
or click.confirm(
|
||
"是否需要集成式 WebUI?(个人电脑推荐,服务器不推荐)",
|
||
default=True,
|
||
)
|
||
):
|
||
# 避免在 systemd 模式下因等待输入而阻塞
|
||
if os.environ.get("ASTRBOT_SYSTEMD") == "1":
|
||
click.echo("Systemd detected: Skipping dashboard check.")
|
||
else:
|
||
await check_dashboard(astrbot_root)
|
||
else:
|
||
click.echo("你可以使用在线面版(需支持配置后端)来控制。")
|
||
|
||
|
||
@click.command()
|
||
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompts")
|
||
@click.option("--backend-only", "-b", is_flag=True, help="Only initialize the backend")
|
||
@click.option("--backup", "-f", help="Initialize from backup file", type=str)
|
||
@click.option(
|
||
"-u",
|
||
"--admin-username",
|
||
type=str,
|
||
help="Set dashboard admin username during initialization",
|
||
)
|
||
@click.option(
|
||
"-p",
|
||
"--admin-password",
|
||
type=str,
|
||
help="Set dashboard admin password during initialization without prompting",
|
||
)
|
||
def init(
|
||
yes: bool,
|
||
backend_only: bool,
|
||
backup: str | None,
|
||
admin_username: str | None,
|
||
admin_password: str | None,
|
||
) -> None:
|
||
"""Initialize AstrBot"""
|
||
click.echo("Initializing AstrBot...")
|
||
|
||
if os.environ.get("ASTRBOT_SYSTEMD") == "1":
|
||
yes = True
|
||
|
||
astrbot_root = astrbot_paths.root
|
||
lock_file = astrbot_root / "astrbot.lock"
|
||
lock = FileLock(lock_file, timeout=5)
|
||
|
||
try:
|
||
with lock.acquire():
|
||
asyncio.run(
|
||
initialize_astrbot(
|
||
astrbot_root,
|
||
yes=yes,
|
||
backend_only=backend_only,
|
||
admin_username=admin_username,
|
||
admin_password=admin_password,
|
||
)
|
||
)
|
||
|
||
if backup:
|
||
from .cmd_bk import import_data_command
|
||
|
||
click.echo(f"Restoring from backup: {backup}")
|
||
click.get_current_context().invoke(
|
||
import_data_command, backup_file=backup, yes=True
|
||
)
|
||
|
||
click.echo("Done! You can now run 'astrbot run' to start AstrBot")
|
||
except Timeout:
|
||
raise click.ClickException(
|
||
"Cannot acquire lock file. Please check if another instance is running"
|
||
)
|
||
|
||
except Exception as e:
|
||
raise click.ClickException(f"Initialization failed: {e!s}")
|