Files
t0saki 7ee826e165 feat(init): TUI-style setup wizard with separate embedding/VLM config (#2952)
* feat(init): TUI-style setup wizard with separate embedding/VLM config

Rework `openviking-server init` into an arrow-key TUI (rich chrome +
termios raw-mode menus, with a numbered-input fallback for non-TTY).

- Two-step main flow: Step 1 embedding, Step 2 VLM, so mixing cloud and
  local (one online, one local) is visible in the main flow rather than
  hidden in submenus.
- Recommended all-Ollama one-shot config (embedding + VLM + query
  planner) sized by available RAM.
- Refreshed VLM presets (qwen3.6:27b / qwen3.6:35b).
- Show current configuration at every level: overview panel, menu-item
  "(now: ...)" descriptions, and per-section "Current: ..." lines.
- Seed every prompt default from the existing config; offer to keep
  existing secrets (masked); show an old -> new diff before saving.
- Server & auth step now covers host + port, and switching to a
  non-local binding requires (and can generate) a root_api_key.
- Left/right arrow navigation: <- back a step, -> confirm.
- Auto-offer init on a bare `openviking-server` when no config exists;
  disk-space check before pulling models; post-save doctor validation
  with optional server start.

* fix(init): preserve existing config in wizard update flow

Address Copilot review feedback on the setup wizard:

- Cloud embedding update now keeps a non-default api_base (proxy /
  OpenAI-compatible endpoint) when the provider is unchanged, instead
  of always resetting to the provider preset default.
- Custom OpenAI-compatible VLM branch seeds API base and model from the
  existing config and routes key collection through _prompt_vlm_api_key,
  so keep-existing-key / env-var reuse applies there too.
- server_bootstrap init auto-offer now catches OSError alongside
  EOFError, matching the wizard's own prompt helpers, so non-standard
  pipe/terminal contexts fall through silently instead of crashing.
2026-07-02 17:24:35 +08:00

109 lines
3.7 KiB
Python

# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
# SPDX-License-Identifier: AGPL-3.0
"""Lightweight entry point for openviking-server.
This module lives outside the ``openviking`` package so that importing it
does NOT trigger ``openviking/__init__.py`` (which eagerly imports clients
and initialises the config singleton via module-level loggers).
The real bootstrap logic stays in ``openviking.server.bootstrap``; we just
pre-parse ``--config`` and set the environment variable before that module
is ever imported.
Subcommands ``init`` and ``doctor`` are handled here directly (they don't
need a running server).
"""
import os
import sys
from openviking_cli.utils.config import OPENVIKING_CONFIG_ENV
def _config_missing() -> bool:
"""True when no ov.conf resolves through the standard four-level chain."""
from openviking_cli.utils.config.config_loader import resolve_config_path
from openviking_cli.utils.config.consts import DEFAULT_OV_CONF
return resolve_config_path(None, OPENVIKING_CONFIG_ENV, DEFAULT_OV_CONF) is None
def _maybe_offer_init() -> None:
"""On an interactive TTY with no config, offer to run the setup wizard.
Falls through silently in non-interactive contexts (Docker, CI) so the
server keeps its existing missing-config behavior there.
"""
try:
interactive = sys.stdin.isatty() and sys.stdout.isatty()
except (AttributeError, ValueError):
interactive = False
if not interactive or not _config_missing():
return
print("No OpenViking configuration found.")
try:
answer = input("Run interactive setup now? [Y/n]: ").strip().lower()
except (EOFError, OSError):
return
if answer not in ("", "y", "yes"):
return
from openviking_cli.setup_wizard import main as init_main
code = init_main()
if code != 0 or _config_missing():
sys.exit(code or 1)
def main():
"""Bootstrap the server while binding a stable execution-level log trace ID."""
# Pre-parse --config from sys.argv before any openviking imports,
# so the env var is visible when the config singleton first initialises.
# This is done for all subcommands (init, doctor, server) to ensure
# consistent behavior.
for i, arg in enumerate(sys.argv):
if arg == "--config" and i + 1 < len(sys.argv):
os.environ[OPENVIKING_CONFIG_ENV] = sys.argv[i + 1]
break
if arg.startswith("--config="):
os.environ[OPENVIKING_CONFIG_ENV] = arg.split("=", 1)[1]
break
# Import after config pre-parse to avoid early config singleton initialization via
# module-level loggers.
from openviking_cli.utils.logger import bind_log_execution_trace # noqa: PLC0415
# Intercept subcommands that don't need the server.
if len(sys.argv) > 1 and sys.argv[1] == "init":
from openviking_cli.setup_wizard import main as init_main
with bind_log_execution_trace():
sys.exit(init_main())
if len(sys.argv) > 1 and sys.argv[1] == "doctor":
from openviking_cli.doctor import main as doctor_main
with bind_log_execution_trace():
sys.exit(doctor_main())
# `openviking-server ingest ...` runs the local-log ingestion CLI (client-side).
if len(sys.argv) > 1 and sys.argv[1] == "ingest":
from openviking.ingest.cli import app as ingest_app
with bind_log_execution_trace():
ingest_app(args=sys.argv[2:], prog_name="openviking-server ingest")
return
if not any(arg in ("-h", "--help", "--version") for arg in sys.argv[1:]):
_maybe_offer_init()
from openviking.server.bootstrap import main as _real_main
with bind_log_execution_trace():
_real_main()
if __name__ == "__main__":
main()