From 0068825cd5c33ee551fce6c5abe5bb98fd27d381 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 20 Mar 2026 16:24:02 +0800 Subject: [PATCH] cli(init): add --root option and generate .env from config.template into ASTRBOT_ROOT (auto-loaded by run) --- astrbot/cli/commands/cmd_init.py | 56 ++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/astrbot/cli/commands/cmd_init.py b/astrbot/cli/commands/cmd_init.py index 5fa761868..7ffb5b6f5 100644 --- a/astrbot/cli/commands/cmd_init.py +++ b/astrbot/cli/commands/cmd_init.py @@ -1,15 +1,16 @@ import asyncio import json import os +import re from pathlib import Path import click from filelock import FileLock, Timeout +from astrbot.cli.utils import check_dashboard 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, @@ -60,6 +61,50 @@ async def initialize_astrbot( ) click.echo(f"Created config file: {config_path}") + # Generate an .env for this instance from the bundled config.template (if available). + # The generated file will be written to ASTRBOT_ROOT/.env and will be automatically + # loaded by `astrbot run` (service-config/.env precedence applies). + ASTRBOT_ROOT = astrbot_root + env_file = ASTRBOT_ROOT / ".env" + if not env_file.exists(): + tmpl_candidates = [ + Path("/opt/astrbot/config.template"), + # project_root may point to the installed package directory; try it as well + 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") + # Determine instance name for template replacement (fallback to directory name) + instance_name = astrbot_root.name or "astrbot" + # Substitute ${VAR} and ${VAR:-default} for INSTANCE_NAME, PORT, ASTRBOT_ROOT + txt = re.sub(r"\$\{INSTANCE_NAME(:-[^}]*)?\}", instance_name, txt) + port_val = ( + os.environ.get("ASTRBOT_PORT") or os.environ.get("PORT") or "8000" + ) + txt = re.sub(r"\$\{PORT(:-[^}]*)?\}", str(port_val), txt) + txt = re.sub(r"\$\{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(0o644) + click.echo(f"Created environment file from template: {env_file}") + except Exception as e: + click.echo(f"Warning: failed to generate .env from template: {e!s}") + else: + click.echo("No config.template found; skipping .env generation") + if admin_password and not admin_username: raise click.ClickException( "--admin-password requires --admin-username to be provided" @@ -122,20 +167,27 @@ async def initialize_astrbot( type=str, help="Set dashboard admin password during initialization without prompting", ) +@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 = astrbot_paths.root + astrbot_root = Path(root) if root else astrbot_paths.root lock_file = astrbot_root / "astrbot.lock" lock = FileLock(lock_file, timeout=5)