mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 01:40:15 +08:00
- Move logo and print_logo to new astrbot/cli/banner.py module to avoid circular imports - Add is_interactive() helper to detect TTY - Show ASCII logo in run/init commands only in interactive mode - Show WeChat QR code ASCII art only in interactive mode; non-interactive mode shows just the QR link
223 lines
8.0 KiB
Python
223 lines
8.0 KiB
Python
import asyncio
|
||
import json
|
||
import os
|
||
import re
|
||
from pathlib import Path
|
||
|
||
import click
|
||
from filelock import FileLock, Timeout
|
||
|
||
from astrbot.cli.utils import DashboardManager
|
||
from astrbot.core.config.default import DEFAULT_CONFIG
|
||
from astrbot.core.utils.astrbot_path import astrbot_paths
|
||
|
||
from .cmd_conf import ensure_config_file, 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"""
|
||
from astrbot.cli.banner import print_logo
|
||
|
||
click.echo("=" * 60)
|
||
click.echo("AstrBot 初始化向导")
|
||
click.echo("=" * 60)
|
||
print_logo()
|
||
click.echo()
|
||
|
||
dot_astrbot = astrbot_root / ".astrbot"
|
||
if not dot_astrbot.exists():
|
||
if yes or click.confirm(
|
||
f"确定要将 AstrBot 安装到以下目录吗?\n {astrbot_root}",
|
||
default=True,
|
||
abort=True,
|
||
):
|
||
dot_astrbot.touch()
|
||
click.echo(f"[OK] 已创建: {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)
|
||
status = "Created" if not path.exists() else "Exists"
|
||
click.echo(f" [{status}] {name.title()}: {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"[OK] 配置文件已创建: {config_path}")
|
||
ASTRBOT_ROOT = astrbot_root
|
||
env_file = ASTRBOT_ROOT / ".env"
|
||
if not env_file.exists():
|
||
tmpl_candidates = [
|
||
Path("/opt/astrbot/config.template"),
|
||
getattr(astrbot_paths, "project_root", Path.cwd()) / "config.template",
|
||
Path.cwd() / "config.template",
|
||
]
|
||
tmpl = None
|
||
for t in tmpl_candidates:
|
||
try:
|
||
if t.exists():
|
||
tmpl = t
|
||
break
|
||
except Exception:
|
||
continue
|
||
if tmpl is not None:
|
||
try:
|
||
txt = tmpl.read_text(encoding="utf-8")
|
||
instance_name = astrbot_root.name or "astrbot"
|
||
txt = re.sub("\\$\\{INSTANCE_NAME(:-[^}]*)?\\}", instance_name, txt)
|
||
port_val = (
|
||
os.environ.get("ASTRBOT_PORT") or os.environ.get("PORT") or "8000"
|
||
)
|
||
txt = re.sub("\\$\\{PORT(:-[^}]*)?\\}", str(port_val), txt)
|
||
txt = re.sub("\\$\\{ASTRBOT_ROOT(:-[^}]*)?\\}", str(ASTRBOT_ROOT), txt)
|
||
header = f"# Generated from config.template by astrbot init for instance: {instance_name}\n# This file will be auto-loaded by 'astrbot run'\n\n"
|
||
env_file.write_text(header + txt, encoding="utf-8")
|
||
env_file.chmod(420)
|
||
click.echo(f"[OK] 环境变量文件已创建: {env_file}")
|
||
except Exception as e:
|
||
click.echo(f"[警告] 无法从模板生成 .env 文件: {e!s}")
|
||
else:
|
||
click.echo("[提示] 未找到 config.template 文件,跳过 .env 生成")
|
||
if admin_password is not None:
|
||
raise click.ClickException(
|
||
"--admin-password is no longer supported during init. Run 'astrbot conf admin' after initialization.",
|
||
)
|
||
effective_admin_username = (
|
||
admin_username.strip()
|
||
if admin_username
|
||
else str(DEFAULT_CONFIG["dashboard"]["username"])
|
||
)
|
||
if admin_username:
|
||
config = ensure_config_file()
|
||
set_dashboard_credentials(
|
||
config,
|
||
username=effective_admin_username,
|
||
password_hash=None,
|
||
)
|
||
config_path.write_text(
|
||
json.dumps(config, ensure_ascii=False, indent=2),
|
||
encoding="utf-8-sig",
|
||
)
|
||
click.echo(f"[OK] Dashboard admin 用户名已设置为: {effective_admin_username}")
|
||
click.echo()
|
||
click.echo("!" * 60)
|
||
click.echo("重要提示:")
|
||
click.echo(" 1. Dashboard 密码尚未设置!首次登录前必须先设置密码")
|
||
click.echo(" 2. 设置命令: astrbot conf admin")
|
||
click.echo(" 3. 登录地址: http://localhost:6185 或 http://服务器IP:6185")
|
||
click.echo("!" * 60)
|
||
click.echo()
|
||
if not backend_only and (
|
||
yes
|
||
or click.confirm(
|
||
"是否需要集成式 WebUI?(个人电脑推荐,服务器推荐使用后端模式)",
|
||
default=True,
|
||
)
|
||
):
|
||
await DashboardManager().ensure_installed(astrbot_root)
|
||
else:
|
||
click.echo()
|
||
click.echo("[提示] 你选择了后端模式,可以使用以下方式管理 AstrBot:")
|
||
click.echo(" - 使用在线 Dashboard: 在浏览器中访问远程服务器的 WebUI")
|
||
click.echo(" - 使用 CLI 命令: astrbot conf / astrbot plug 等")
|
||
click.echo()
|
||
click.echo("!" * 60)
|
||
click.echo("安全提示:")
|
||
click.echo(" HTTPS 前端只能安全连接 localhost 的 HTTP 后端")
|
||
click.echo(" 不支持远程 + HTTP 后端(不安全)")
|
||
click.echo(" 如需远程访问,请使用 HTTPS 后端或通过反向代理")
|
||
click.echo("!" * 60)
|
||
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="Deprecated. Run `astrbot conf admin` after initialization.",
|
||
)
|
||
@click.option(
|
||
"--root",
|
||
help="ASTRBOT root directory to initialize (overrides ASTRBOT_ROOT env)",
|
||
type=str,
|
||
)
|
||
def init(
|
||
yes: bool,
|
||
backend_only: bool,
|
||
backup: str | None,
|
||
admin_username: str | None,
|
||
admin_password: str | None,
|
||
root: str | None = None,
|
||
) -> None:
|
||
"""Initialize AstrBot"""
|
||
click.echo("Initializing AstrBot...")
|
||
if os.environ.get("ASTRBOT_SYSTEMD") == "1":
|
||
yes = True
|
||
from astrbot.core.utils.astrbot_path import astrbot_paths
|
||
|
||
astrbot_root = Path(root) if root else 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()
|
||
click.echo("=" * 60)
|
||
click.echo("初始化完成!")
|
||
click.echo("=" * 60)
|
||
click.echo()
|
||
click.echo("启动 AstrBot:")
|
||
click.echo(" 完整模式(含 Dashboard): astrbot run")
|
||
click.echo(" 仅后端模式: astrbot run --backend-only")
|
||
click.echo()
|
||
click.echo("首次使用前请先设置管理员密码:")
|
||
click.echo(" astrbot conf admin")
|
||
click.echo()
|
||
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}")
|