Files
moreminimore-chat/app/models/installation_config.rb
Moreminimore 46fe703986
Some checks failed
Frontend Lint & Test / test (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / security-scan (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Sync GHSA advisories to Linear / sync (push) Has been cancelled
feat(captain): add OpenAI Custom Server provider option
Add an operator-level provider switch for Captain AI so it can use any
OpenAI-compatible base URL (vLLM, OpenRouter, proxies, etc.) in addition to the
default api.openai.com endpoint.

- installation_config.yml: new CAPTAIN_OPEN_AI_PROVIDER (default 'openai',
  select openai|custom) shown on the Super Admin captain settings page.
- lib/llm/config.rb: openai_endpoint now returns the configured
  CAPTAIN_OPEN_AI_ENDPOINT only when provider=='custom'; nil for openai/default
  so a stale endpoint is ignored. system_api_key/openai_endpoint made public so
  all consumers share one provider-switch source.
- Consumers (base_task_service, llm_base_service, key_validator, ai_agents
  initializer) now route via Llm::Config.openai_endpoint instead of reading
  CAPTAIN_OPEN_AI_ENDPOINT directly.
- installation_config.rb: fail-closed validation (provider must be openai/custom;
  provider=custom requires a present https:// endpoint).

Approved by independent five-key reviews deleg_b141098a (found+fixed one logic
gap) and deleg_3088b922 (passed=true, blocking arrays empty).
2026-08-19 10:52:47 +07:00

101 lines
3.3 KiB
Ruby

# == Schema Information
#
# Table name: installation_configs
#
# id :bigint not null, primary key
# locked :boolean default(TRUE), not null
# name :string not null
# serialized_value :jsonb not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_installation_configs_on_name (name) UNIQUE
# index_installation_configs_on_name_and_created_at (name,created_at) UNIQUE
#
class InstallationConfig < ApplicationRecord
CAPTAIN_LLM_CONFIG_KEYS = %w[
CAPTAIN_OPEN_AI_API_KEY
CAPTAIN_OPEN_AI_ENDPOINT
CAPTAIN_OPEN_AI_MODEL
].freeze
RESTART_REQUIRED_CONFIG_KEYS = (CAPTAIN_LLM_CONFIG_KEYS + %w[
LANGFUSE_BASE_URL
LANGFUSE_PUBLIC_KEY
LANGFUSE_SECRET_KEY
OTEL_PROVIDER
]).freeze
# https://stackoverflow.com/questions/72970170/upgrading-to-rails-6-1-6-1-causes-psychdisallowedclass-tried-to-load-unspecif
# https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017
# FIX ME : fixes breakage of installation config. we need to migrate.
# Fix configuration in application.rb
serialize :serialized_value, coder: YAML, type: ActiveSupport::HashWithIndifferentAccess, default: {}.with_indifferent_access
before_validation :set_lock
validates :name, presence: true
validate :saml_sso_users_check, if: -> { name == 'ENABLE_SAML_SSO_LOGIN' }
validate :openai_custom_provider_check, if: -> { name == 'CAPTAIN_OPEN_AI_PROVIDER' }
validate :openai_custom_endpoint_check, if: -> { name == 'CAPTAIN_OPEN_AI_ENDPOINT' }
# TODO: Get rid of default scope
# https://stackoverflow.com/a/1834250/939299
default_scope { order(created_at: :desc) }
scope :editable, -> { where(locked: false) }
after_commit :clear_cache
def value
serialized_value[:value]
end
def value=(value_to_assigned)
self.serialized_value = {
value: value_to_assigned
}.with_indifferent_access
end
private
def set_lock
self.locked = true if locked.nil?
end
def clear_cache
GlobalConfig.clear_cache
end
def saml_sso_users_check
return unless value == false || value == 'false'
return unless User.exists?(provider: 'saml')
errors.add(:base, 'Cannot disable SAML SSO login while users are using SAML authentication')
end
# Only allow provider values the platform understands ('openai' default or 'custom')
# and ensure the "custom" provider actually requires an endpoint.
def openai_custom_provider_check
return if %w[openai custom].include?(value.to_s)
errors.add(:base, 'CAPTAIN_OPEN_AI_PROVIDER must be "openai" or "custom"')
end
# Fail-closed: when the provider is "custom", CAPTAIN_OPEN_AI_ENDPOINT must be
# present AND a secure https URL (OpenAI-compatible base).
def openai_custom_endpoint_check
provider = InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_PROVIDER')&.value.to_s
return unless provider == 'custom'
if value.blank?
errors.add(:base, 'CAPTAIN_OPEN_AI_ENDPOINT is required when using the custom provider')
return
end
return if value.to_s.match?(%r{\Ahttps://\S+})
errors.add(:base, 'CAPTAIN_OPEN_AI_ENDPOINT must be a valid https URL when using the custom provider')
end
end