commands.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import datetime
  2. import logging
  3. import random
  4. import string
  5. import click
  6. from flask import current_app
  7. from werkzeug.exceptions import NotFound
  8. from core.index.index import IndexBuilder
  9. from libs.password import password_pattern, valid_password, hash_password
  10. from libs.helper import email as email_validate
  11. from extensions.ext_database import db
  12. from libs.rsa import generate_key_pair
  13. from models.account import InvitationCode, Tenant
  14. from models.dataset import Dataset
  15. from models.model import Account
  16. import secrets
  17. import base64
  18. from models.provider import Provider, ProviderName
  19. from services.provider_service import ProviderService
  20. @click.command('reset-password', help='Reset the account password.')
  21. @click.option('--email', prompt=True, help='The email address of the account whose password you need to reset')
  22. @click.option('--new-password', prompt=True, help='the new password.')
  23. @click.option('--password-confirm', prompt=True, help='the new password confirm.')
  24. def reset_password(email, new_password, password_confirm):
  25. if str(new_password).strip() != str(password_confirm).strip():
  26. click.echo(click.style('sorry. The two passwords do not match.', fg='red'))
  27. return
  28. account = db.session.query(Account). \
  29. filter(Account.email == email). \
  30. one_or_none()
  31. if not account:
  32. click.echo(click.style('sorry. the account: [{}] not exist .'.format(email), fg='red'))
  33. return
  34. try:
  35. valid_password(new_password)
  36. except:
  37. click.echo(
  38. click.style('sorry. The passwords must match {} '.format(password_pattern), fg='red'))
  39. return
  40. # generate password salt
  41. salt = secrets.token_bytes(16)
  42. base64_salt = base64.b64encode(salt).decode()
  43. # encrypt password with salt
  44. password_hashed = hash_password(new_password, salt)
  45. base64_password_hashed = base64.b64encode(password_hashed).decode()
  46. account.password = base64_password_hashed
  47. account.password_salt = base64_salt
  48. db.session.commit()
  49. click.echo(click.style('Congratulations!, password has been reset.', fg='green'))
  50. @click.command('reset-email', help='Reset the account email.')
  51. @click.option('--email', prompt=True, help='The old email address of the account whose email you need to reset')
  52. @click.option('--new-email', prompt=True, help='the new email.')
  53. @click.option('--email-confirm', prompt=True, help='the new email confirm.')
  54. def reset_email(email, new_email, email_confirm):
  55. if str(new_email).strip() != str(email_confirm).strip():
  56. click.echo(click.style('Sorry, new email and confirm email do not match.', fg='red'))
  57. return
  58. account = db.session.query(Account). \
  59. filter(Account.email == email). \
  60. one_or_none()
  61. if not account:
  62. click.echo(click.style('sorry. the account: [{}] not exist .'.format(email), fg='red'))
  63. return
  64. try:
  65. email_validate(new_email)
  66. except:
  67. click.echo(
  68. click.style('sorry. {} is not a valid email. '.format(email), fg='red'))
  69. return
  70. account.email = new_email
  71. db.session.commit()
  72. click.echo(click.style('Congratulations!, email has been reset.', fg='green'))
  73. @click.command('reset-encrypt-key-pair', help='Reset the asymmetric key pair of workspace for encrypt LLM credentials. '
  74. 'After the reset, all LLM credentials will become invalid, '
  75. 'requiring re-entry.'
  76. 'Only support SELF_HOSTED mode.')
  77. @click.confirmation_option(prompt=click.style('Are you sure you want to reset encrypt key pair?'
  78. ' this operation cannot be rolled back!', fg='red'))
  79. def reset_encrypt_key_pair():
  80. if current_app.config['EDITION'] != 'SELF_HOSTED':
  81. click.echo(click.style('Sorry, only support SELF_HOSTED mode.', fg='red'))
  82. return
  83. tenant = db.session.query(Tenant).first()
  84. if not tenant:
  85. click.echo(click.style('Sorry, no workspace found. Please enter /install to initialize.', fg='red'))
  86. return
  87. tenant.encrypt_public_key = generate_key_pair(tenant.id)
  88. db.session.query(Provider).filter(Provider.provider_type == 'custom').delete()
  89. db.session.commit()
  90. click.echo(click.style('Congratulations! '
  91. 'the asymmetric key pair of workspace {} has been reset.'.format(tenant.id), fg='green'))
  92. @click.command('generate-invitation-codes', help='Generate invitation codes.')
  93. @click.option('--batch', help='The batch of invitation codes.')
  94. @click.option('--count', prompt=True, help='Invitation codes count.')
  95. def generate_invitation_codes(batch, count):
  96. if not batch:
  97. now = datetime.datetime.now()
  98. batch = now.strftime('%Y%m%d%H%M%S')
  99. if not count or int(count) <= 0:
  100. click.echo(click.style('sorry. the count must be greater than 0.', fg='red'))
  101. return
  102. count = int(count)
  103. click.echo('Start generate {} invitation codes for batch {}.'.format(count, batch))
  104. codes = ''
  105. for i in range(count):
  106. code = generate_invitation_code()
  107. invitation_code = InvitationCode(
  108. code=code,
  109. batch=batch
  110. )
  111. db.session.add(invitation_code)
  112. click.echo(code)
  113. codes += code + "\n"
  114. db.session.commit()
  115. filename = 'storage/invitation-codes-{}.txt'.format(batch)
  116. with open(filename, 'w') as f:
  117. f.write(codes)
  118. click.echo(click.style(
  119. 'Congratulations! Generated {} invitation codes for batch {} and saved to the file \'{}\''.format(count, batch,
  120. filename),
  121. fg='green'))
  122. def generate_invitation_code():
  123. code = generate_upper_string()
  124. while db.session.query(InvitationCode).filter(InvitationCode.code == code).count() > 0:
  125. code = generate_upper_string()
  126. return code
  127. def generate_upper_string():
  128. letters_digits = string.ascii_uppercase + string.digits
  129. result = ""
  130. for i in range(8):
  131. result += random.choice(letters_digits)
  132. return result
  133. @click.command('recreate-all-dataset-indexes', help='Recreate all dataset indexes.')
  134. def recreate_all_dataset_indexes():
  135. click.echo(click.style('Start recreate all dataset indexes.', fg='green'))
  136. recreate_count = 0
  137. page = 1
  138. while True:
  139. try:
  140. datasets = db.session.query(Dataset).filter(Dataset.indexing_technique == 'high_quality')\
  141. .order_by(Dataset.created_at.desc()).paginate(page=page, per_page=50)
  142. except NotFound:
  143. break
  144. page += 1
  145. for dataset in datasets:
  146. try:
  147. click.echo('Recreating dataset index: {}'.format(dataset.id))
  148. index = IndexBuilder.get_index(dataset, 'high_quality')
  149. if index and index._is_origin():
  150. index.recreate_dataset(dataset)
  151. recreate_count += 1
  152. else:
  153. click.echo('passed.')
  154. except Exception as e:
  155. click.echo(click.style('Recreate dataset index error: {} {}'.format(e.__class__.__name__, str(e)), fg='red'))
  156. continue
  157. click.echo(click.style('Congratulations! Recreate {} dataset indexes.'.format(recreate_count), fg='green'))
  158. @click.command('sync-anthropic-hosted-providers', help='Sync anthropic hosted providers.')
  159. def sync_anthropic_hosted_providers():
  160. click.echo(click.style('Start sync anthropic hosted providers.', fg='green'))
  161. count = 0
  162. page = 1
  163. while True:
  164. try:
  165. tenants = db.session.query(Tenant).order_by(Tenant.created_at.desc()).paginate(page=page, per_page=50)
  166. except NotFound:
  167. break
  168. page += 1
  169. for tenant in tenants:
  170. try:
  171. click.echo('Syncing tenant anthropic hosted provider: {}'.format(tenant.id))
  172. ProviderService.create_system_provider(
  173. tenant,
  174. ProviderName.ANTHROPIC.value,
  175. current_app.config['ANTHROPIC_HOSTED_QUOTA_LIMIT'],
  176. True
  177. )
  178. count += 1
  179. except Exception as e:
  180. click.echo(click.style('Sync tenant anthropic hosted provider error: {} {}'.format(e.__class__.__name__, str(e)), fg='red'))
  181. continue
  182. click.echo(click.style('Congratulations! Synced {} anthropic hosted providers.'.format(count), fg='green'))
  183. def register_commands(app):
  184. app.cli.add_command(reset_password)
  185. app.cli.add_command(reset_email)
  186. app.cli.add_command(generate_invitation_codes)
  187. app.cli.add_command(reset_encrypt_key_pair)
  188. app.cli.add_command(recreate_all_dataset_indexes)
  189. app.cli.add_command(sync_anthropic_hosted_providers)