diff --git a/docker-compose.yml b/docker-compose.yml index 1b79f784..4007b47f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,7 +48,7 @@ services: # Replace the domain with your domain. Proceed with caution, especially if you are using anonymous mode. # - KHOJ_NO_HTTPS=True # - KHOJ_DOMAIN=192.168.0.104 - command: --host="0.0.0.0" --port=42110 -vv --anonymous-mode + command: --host="0.0.0.0" --port=42110 -vv --anonymous-mode --non-interactive volumes: diff --git a/src/khoj/main.py b/src/khoj/main.py index 0a492e91..272c9bb8 100644 --- a/src/khoj/main.py +++ b/src/khoj/main.py @@ -131,7 +131,7 @@ def run(should_start_server=True): logger.info(f"📦 Initializing DB:\n{db_migrate_output.getvalue().strip()}") logger.debug(f"🌍 Initializing Web Client:\n{collectstatic_output.getvalue().strip()}") - initialization() + initialization(not args.non_interactive) # Create app directory, if it doesn't exist state.config_file.parent.mkdir(parents=True, exist_ok=True) diff --git a/src/khoj/utils/cli.py b/src/khoj/utils/cli.py index dd3388d9..2e2bb77d 100644 --- a/src/khoj/utils/cli.py +++ b/src/khoj/utils/cli.py @@ -50,6 +50,12 @@ def cli(args=None): default=False, help="Run Khoj in anonymous mode. This does not require any login for connecting users.", ) + parser.add_argument( + "--non-interactive", + action="store_true", + default=False, + help="Start Khoj in non-interactive mode. Assumes interactive shell unavailable for config. E.g when run via Docker.", + ) args, remaining_args = parser.parse_known_args(args) diff --git a/src/khoj/utils/initialization.py b/src/khoj/utils/initialization.py index 79948ccb..3791af3f 100644 --- a/src/khoj/utils/initialization.py +++ b/src/khoj/utils/initialization.py @@ -15,11 +15,16 @@ from khoj.utils.constants import default_offline_chat_model, default_online_chat logger = logging.getLogger(__name__) -def initialization(): +def initialization(interactive: bool = True): def _create_admin_user(): logger.info( "👩‍✈️ Setting up admin user. These credentials will allow you to configure your server at /server/admin." ) + if not interactive and (not os.getenv("KHOJ_ADMIN_EMAIL") or not os.getenv("KHOJ_ADMIN_PASSWORD")): + logger.error( + "🚨 Admin user cannot be created. Please set the KHOJ_ADMIN_EMAIL, KHOJ_ADMIN_PASSWORD environment variables or start server in interactive mode." + ) + exit(1) email_addr = os.getenv("KHOJ_ADMIN_EMAIL") or input("Email: ") password = os.getenv("KHOJ_ADMIN_PASSWORD") or input("Password: ") admin_user = KhojUser.objects.create_superuser(email=email_addr, username=email_addr, password=password) @@ -27,23 +32,26 @@ def initialization(): def _create_chat_configuration(): logger.info( - "🗣️ Configure chat models available to your server. You can always update these at /server/admin using the credentials of your admin account" + "🗣️ Configure chat models available to your server. You can always update these at /server/admin using your admin account" ) try: - use_offline_model = input("Use offline chat model? (y/n): ") + use_offline_model = "y" if not interactive else input("Use offline chat model? (y/n): ") if use_offline_model == "y": logger.info("🗣️ Setting up offline chat model") - offline_chat_model = input( - f"Enter the offline chat model you want to use. See HuggingFace for available GGUF models (default: {default_offline_chat_model}): " - ) + if interactive: + offline_chat_model = input( + f"Enter the offline chat model you want to use. See HuggingFace for available GGUF models (default: {default_offline_chat_model}): " + ) + else: + offline_chat_model = "" if offline_chat_model == "": ChatModelOptions.objects.create( chat_model=default_offline_chat_model, model_type=ChatModelOptions.ModelType.OFFLINE ) else: - default_max_tokens = model_to_prompt_size.get(offline_chat_model, 2000) + default_max_tokens = model_to_prompt_size.get(offline_chat_model, 4000) max_tokens = input( f"Enter the maximum number of tokens to use for the offline chat model (default {default_max_tokens}):" ) @@ -66,40 +74,56 @@ def initialization(): except ModuleNotFoundError as e: logger.warning("Offline models are not supported on this device.") - use_openai_model = input("Use OpenAI models? (y/n): ") + default_openai_api_key = os.getenv("OPENAI_API_KEY") + default_use_openai_model = {True: "y", False: "n"}[default_openai_api_key != None] + use_openai_model = default_use_openai_model if not interactive else input("Use OpenAI models? (y/n): ") if use_openai_model == "y": logger.info("🗣️ Setting up your OpenAI configuration") - api_key = input("Enter your OpenAI API key: ") + if interactive: + api_key = input(f"Enter your OpenAI API key (default: {default_openai_api_key}): ") + else: + api_key = default_openai_api_key OpenAIProcessorConversationConfig.objects.create(api_key=api_key) - openai_chat_model = input( - f"Enter the OpenAI chat model you want to use (default: {default_online_chat_model}): " - ) - openai_chat_model = openai_chat_model or default_online_chat_model - - default_max_tokens = model_to_prompt_size.get(openai_chat_model, 2000) - max_tokens = input( - f"Enter the maximum number of tokens to use for the OpenAI chat model (default: {default_max_tokens}): " - ) - max_tokens = max_tokens or default_max_tokens + if interactive: + openai_chat_model = input( + f"Enter the OpenAI chat model you want to use (default: {default_online_chat_model}): " + ) + openai_chat_model = openai_chat_model or default_online_chat_model + else: + openai_chat_model = default_online_chat_model + default_max_tokens = model_to_prompt_size.get(openai_chat_model, 10000) + if interactive: + max_tokens = input( + f"Enter the maximum number of tokens to use for the OpenAI chat model (default: {default_max_tokens}): " + ) + max_tokens = max_tokens or default_max_tokens + else: + max_tokens = default_max_tokens ChatModelOptions.objects.create( chat_model=openai_chat_model, model_type=ChatModelOptions.ModelType.OPENAI, max_prompt_size=max_tokens ) default_speech2text_model = "whisper-1" - openai_speech2text_model = input( - f"Enter the OpenAI speech to text model you want to use (default: {default_speech2text_model}): " - ) - openai_speech2text_model = openai_speech2text_model or default_speech2text_model + if interactive: + openai_speech2text_model = input( + f"Enter the OpenAI speech to text model you want to use (default: {default_speech2text_model}): " + ) + openai_speech2text_model = openai_speech2text_model or default_speech2text_model + else: + openai_speech2text_model = default_speech2text_model SpeechToTextModelOptions.objects.create( model_name=openai_speech2text_model, model_type=SpeechToTextModelOptions.ModelType.OPENAI ) default_text_to_image_model = "dall-e-3" - openai_text_to_image_model = input( - f"Enter the OpenAI text to image model you want to use (default: {default_text_to_image_model}): " - ) - openai_text_to_image_model = openai_text_to_image_model or default_text_to_image_model + if interactive: + openai_text_to_image_model = input( + f"Enter the OpenAI text to image model you want to use (default: {default_text_to_image_model}): " + ) + openai_text_to_image_model = openai_text_to_image_model or default_text_to_image_model + else: + openai_text_to_image_model = default_text_to_image_model TextToImageModelConfig.objects.create( model_name=openai_text_to_image_model, model_type=TextToImageModelConfig.ModelType.OPENAI ) @@ -107,7 +131,7 @@ def initialization(): if use_offline_model == "y" or use_openai_model == "y": logger.info("🗣️ Chat model configuration complete") - use_offline_speech2text_model = input("Use offline speech to text model? (y/n): ") + use_offline_speech2text_model = "n" if not interactive else input("Use offline speech to text model? (y/n): ") if use_offline_speech2text_model == "y": logger.info("🗣️ Setting up offline speech to text model") # Delete any existing speech to text model options. There can only be one.