manage.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import os
  2. import io
  3. import sys
  4. import platform
  5. import shutil
  6. import time
  7. import subprocess
  8. import json
  9. import datetime
  10. import socket
  11. import re
  12. from threading import Thread
  13. from api.utils import shell_execute, docker, const
  14. from api.model.app import App
  15. from api.model.response import Response
  16. from api.utils.common_log import myLogger
  17. from redis import Redis
  18. from rq import Queue, Worker, Connection
  19. from rq.registry import StartedJobRegistry, FinishedJobRegistry, DeferredJobRegistry
  20. # 指定 Redis 容器的主机名和端口
  21. redis_conn = Redis(host='websoft9-redis', port=6379)
  22. # 使用指定的 Redis 连接创建 RQ 队列
  23. q = Queue(connection=redis_conn)
  24. # 获取所有app的信息
  25. def get_my_app():
  26. ret = Response(code=const.RETURN_FAIL, message="App query failed!")
  27. # get all info
  28. cmd = "docker compose ls -a --format json"
  29. output = shell_execute.execute_command_output_all(cmd)
  30. if int(output["code"]) == 0:
  31. output_list = json.loads(output["result"])
  32. installed_list, has_add = get_apps_from_compose(output_list)
  33. installing_list = get_apps_from_queue()
  34. app_list = installed_list + installing_list
  35. ret = Response(code=const.RETURN_SUCCESS, message="The app query is successful.", data=app_list)
  36. ret = ret.dict()
  37. return ret
  38. # 获取具体某个app的信息
  39. def get_app_detail(app_id):
  40. ret = {}
  41. ret['code'] = const.RETURN_FAIL
  42. ret['message'] = 'App query failed!'
  43. ret['data'] = None
  44. if docker.check_app_id(app_id):
  45. # get all info
  46. app_name = split_app_id(app_id)
  47. info, code = if_app_exits(app_id)
  48. if code:
  49. cmd = "docker compose ls -a --format json"
  50. output = shell_execute.execute_command_output_all(cmd)
  51. if int(output["code"]) == 0:
  52. output_list = json.loads(output["result"])
  53. app_list, has_add = get_apps_from_compose(output_list)
  54. list = get_apps_from_queue(app_list, has_add)
  55. flag = 0
  56. app_info = None
  57. for app in list:
  58. if app["app_id"] == app_id:
  59. list.clear()
  60. list.append(app)
  61. app_info = app
  62. flag = 1
  63. break
  64. if flag == 1:
  65. ret['code'] = const.RETURN_SUCCESS
  66. ret['message'] = "The app query is successful."
  67. ret['data'] = app_info
  68. else:
  69. ret['message'] = 'This app is not currently installed.'
  70. else:
  71. ret['message'] = "AppID is not legal!"
  72. return ret
  73. # 查询某个正在安装的app的 具体状态:waiting(等待安装)pulling(拉取镜像)initializing(初始化)running(正常运行)
  74. def install_app_process(app_id):
  75. ret = {}
  76. ret['code'] = const.RETURN_FAIL
  77. ret['message'] = ""
  78. ret['status'] = ""
  79. app_name = split_app_id(app_id)
  80. if docker.check_app_id(app_id):
  81. var_path = "/data/apps/" + app_name + "/variables.json"
  82. real_name = docker.read_var(var_path, 'name')
  83. info, code = if_app_exits(app_id)
  84. if code:
  85. status = docker.get_process_perc(app_name, real_name)
  86. ret["code"] = const.RETURN_SUCCESS
  87. ret['message'] = "This app is installing."
  88. ret['status'] = percentage
  89. else:
  90. ret['message'] = "This app is not currently installed."
  91. else:
  92. ret['message'] = "AppID is not legal!"
  93. ret = ret.dict()
  94. return ret
  95. def install_app(app_name, customer_app_name, app_version):
  96. myLogger.info_logger("Install app ...")
  97. ret = Response(code=const.RETURN_FAIL, message=" ")
  98. app_id = app_name + "_" + customer_app_name
  99. ret.code, ret.message = check_app(app_name, customer_app_name, app_version)
  100. if ret.code == const.RETURN_SUCCESS:
  101. ret.code, ret.message = prepare_app(app_name, customer_app_name)
  102. if ret.code == const.RETURN_SUCCESS:
  103. myLogger.info_logger("create job=" + app_id)
  104. # 根据请求创建新作业
  105. new_job = q.enqueue(install_app_delay, customer_app_name, app_version, job_id=app_id)
  106. ret.message = "The app is prepare to install, please check again in a few minutes."
  107. ret = ret.dict()
  108. return ret
  109. def start_app(app_id):
  110. ret = Response(code=const.RETURN_FAIL, message="")
  111. if docker.check_app_id(app_id):
  112. app_name = split_app_id(app_id)
  113. info, code = if_app_exits(app_id)
  114. if code:
  115. app_path = info.split()[-1].rsplit('/', 1)[0]
  116. docker.check_app_compose(app_path + '/.env')
  117. cmd = "docker compose -f " + app_path + "/docker-compose.yml start"
  118. output = shell_execute.execute_command_output_all(cmd)
  119. if int(output["code"]) == 0:
  120. ret.code = const.RETURN_SUCCESS
  121. ret.message = "The app starts successfully."
  122. else:
  123. ret.message = "The app failed to start!"
  124. else:
  125. ret.message = "This app is not currently installed."
  126. else:
  127. ret.message = "AppID is not legal!"
  128. ret = ret.dict()
  129. return ret
  130. def stop_app(app_id):
  131. ret = Response(code=const.RETURN_FAIL, message="")
  132. if docker.check_app_id(app_id):
  133. app_name = split_app_id(app_id)
  134. info, code = if_app_exits(app_id)
  135. if code:
  136. app_path = info.split()[-1].rsplit('/', 1)[0]
  137. cmd = "docker compose -f " + app_path + "/docker-compose.yml stop"
  138. output = shell_execute.execute_command_output_all(cmd)
  139. if int(output["code"]) == 0:
  140. ret.code = const.RETURN_SUCCESS
  141. ret.message = "The app stopped successfully."
  142. else:
  143. ret.message = "App stop failed!"
  144. else:
  145. ret.message = "This app is not currently installed."
  146. else:
  147. ret.message = 'AppID is not legal!'
  148. ret = ret.dict()
  149. return ret
  150. def restart_app(app_id):
  151. ret = Response(code=const.RETURN_FAIL, message="")
  152. if docker.check_app_id(app_id):
  153. app_name = split_app_id(app_id)
  154. info, code = if_app_exits(app_id)
  155. if code:
  156. app_path = info.split()[-1].rsplit('/', 1)[0]
  157. cmd = "docker compose -f " + app_path + "/docker-compose.yml restart"
  158. output = shell_execute.execute_command_output_all(cmd)
  159. if int(output["code"]) == 0:
  160. ret.code = const.RETURN_SUCCESS
  161. ret.message = "The app restarts successfully."
  162. else:
  163. ret.message = "App restart failed!"
  164. else:
  165. ret.message = "This app is not currently installed."
  166. else:
  167. ret.message = 'AppID is not legal!'
  168. ret = ret.dict()
  169. return ret
  170. def uninstall_app(app_id):
  171. ret = Response(code=const.RETURN_FAIL, message="")
  172. if docker.check_app_id(app_id):
  173. app_name = split_app_id(app_id)
  174. if_stopped = stop_app(app_id) # stop_app
  175. if if_stopped["code"] == 0:
  176. info, code = if_app_exits(app_id)
  177. app_path = info.split()[-1].rsplit('/', 1)[0]
  178. cmd = "docker compose -f " + app_path + "/docker-compose.yml down -v"
  179. lib_path = '/data/library/apps/' + app_name
  180. if app_path != lib_path:
  181. cmd = cmd + " && sudo rm -rf " + app_path
  182. output = shell_execute.execute_command_output_all(cmd)
  183. if int(output["code"]) == 0:
  184. ret.code = 0
  185. ret.message = "The app is deleted successfully"
  186. else:
  187. ret.message = "App deletion failed!"
  188. else:
  189. ret.message = if_stopped["message"]
  190. else:
  191. ret.message = 'AppID is not legal!'
  192. ret = ret.dict()
  193. return ret
  194. def check_app(app_name, customer_app_name, app_version):
  195. message = " "
  196. code = const.RETURN_FAIL
  197. if app_name == None or customer_app_name == None or app_version == None:
  198. message = "Please fill in the APP information completely!"
  199. elif not docker.check_app_directory(app_name):
  200. message = "Installing the app is not supported!"
  201. elif re.match('^[a-z0-9]+$', customer_app_name) == None:
  202. message = "App names must be lowercase letters and numbers!"
  203. elif docker.check_directory("/data/apps/" + customer_app_name):
  204. message = "The APP name is already in use, please specify a different name to reinstall."
  205. elif not docker.check_vm_resource(app_name):
  206. message = "System resources (memory, CPU, disk) are insufficient, and continuing to install may cause the app to not run or the server to be abnormal!"
  207. else:
  208. code = const.RETURN_SUCCESS
  209. return code, message
  210. def prepare_app(app_name, customer_app_name):
  211. library_path = "/data/library/apps/" + app_name
  212. install_path = "/data/apps/" + customer_app_name
  213. message = " "
  214. code = const.RETURN_SUCCESS
  215. output = shell_execute.execute_command_output_all("cp -r " + library_path + " " + install_path)
  216. if int(output["code"]) != 0:
  217. message = "creating" + customer_app_name + "directory failed!"
  218. code = const.RETURN_FAIL
  219. return code, message
  220. def install_app_delay(customer_app_name, app_version):
  221. myLogger.info_logger("start job=" + customer_app_name)
  222. # modify env
  223. env_path = "/data/apps/" + customer_app_name + "/.env"
  224. docker.modify_env(env_path, 'APP_NAME', customer_app_name)
  225. docker.modify_env(env_path, "APP_VERSION", app_version)
  226. # check port
  227. docker.check_app_compose(env_path)
  228. cmd = "cd /data/apps/" + customer_app_name + " && sudo docker compose pull && sudo docker compose up -d"
  229. shell_execute.execute_command_output_all(cmd)
  230. def if_app_exits(app_id):
  231. app_name = app_id.split('_')[1]
  232. real_name = app_id.split('_')[0]
  233. flag = False
  234. info = ""
  235. cmd = "docker compose ls -a | grep \'/" + app_name + "/\'"
  236. output = shell_execute.execute_command_output_all(cmd)
  237. if int(output["code"]) == 0:
  238. info = output["result"]
  239. app_path = info.split()[-1].rsplit('/', 1)[0]
  240. is_official = check_if_official_app(app_path + '/variables.json')
  241. if is_official:
  242. name = docker.read_var(app_path + '/variables.json', 'name')
  243. if name == real_name:
  244. flag = True
  245. elif real_name == app_name:
  246. flag = True
  247. myLogger.info_logger("APP info: " + info)
  248. return info, flag
  249. def split_app_id(app_id):
  250. return app_id.split("_")[1]
  251. def get_apps_from_compose(output_list):
  252. ip_result = shell_execute.execute_command_output_all("curl ifconfig.me")
  253. ip = ip_result["result"]
  254. app_list = []
  255. has_add = []
  256. for app_info in output_list:
  257. volume = app_info["ConfigFiles"] # volume
  258. app_path = volume.rsplit('/', 1)[0]
  259. app_name = volume.split('/')[-2]
  260. app_id = app_name + "_" + app_name # app_id
  261. real_name = ""
  262. trade_mark = ""
  263. port = 0
  264. url = ""
  265. admin_url = ""
  266. image_url = ""
  267. user_name = ""
  268. password = ""
  269. official_app = False
  270. if app_name in ['appmanage', 'nginxproxymanager', 'redis']:
  271. continue
  272. # get code
  273. case = app_info["Status"].split("(")[0] # case
  274. if case == "running":
  275. case_code = const.APP_RUNNING # case_code
  276. elif case == "exited":
  277. case = "stop"
  278. case_code = const.APP_STOP
  279. elif case == "created":
  280. case_code = const.APP_READY
  281. case = "installing"
  282. else:
  283. case_code = const.APP_ERROR
  284. var_path = app_path + "/variables.json"
  285. official_app = check_if_official_app(var_path)
  286. if official_app:
  287. real_name = docker.read_var(var_path, 'name')
  288. app_id = real_name + "_" + app_name # app_id
  289. # get trade_mark
  290. trade_mark = docker.read_var(var_path, 'trademark')
  291. image_url = get_Image_url(real_name)
  292. # get env info
  293. path = app_path + "/.env"
  294. # get port and url
  295. try:
  296. http_port = list(docker.read_env(
  297. path, "APP_HTTP_PORT").values())[0]
  298. port = int(http_port)
  299. easy_url = "http://" + ip + ":" + str(port)
  300. url = get_url(real_name, easy_url)
  301. admin_url = get_admin_url(real_name, url)
  302. except IndexError:
  303. try:
  304. db_port = list(docker.read_env(
  305. path, "APP_DB.*_PORT").values())[0]
  306. port = int(db_port)
  307. except IndexError:
  308. pass
  309. # get user_name
  310. try:
  311. user_name = list(docker.read_env(path, "APP_USER").values())[0]
  312. except IndexError:
  313. pass
  314. # get password
  315. try:
  316. password = list(docker.read_env(
  317. path, "POWER_PASSWORD").values())[0]
  318. except IndexError:
  319. pass
  320. has_add.append(app_name)
  321. app = App(app_id=app_id, name=real_name, customer_name=app_name, status_code=case_code, status=case, port=port,
  322. volume=volume, url=url,
  323. image_url=image_url, admin_url=admin_url, trade_mark=trade_mark, user_name=user_name,
  324. password=password, official_app=official_app)
  325. app_list.append(app.dict())
  326. return app_list, has_add
  327. def check_if_official_app(var_path):
  328. if docker.check_directory(var_path):
  329. if docker.read_var(var_path, 'name') != "" and docker.read_var(var_path, 'trademark') != "" and docker.read_var(
  330. var_path, 'requirements') != "":
  331. requirements = docker.read_var(var_path, 'requirements')
  332. try:
  333. cpu = requirements['cpu']
  334. mem = requirements['memory']
  335. return True
  336. except:
  337. return False
  338. else:
  339. return False
  340. def get_apps_from_queue():
  341. # 获取 StartedJobRegistry 实例
  342. registry = StartedJobRegistry(queue=q)
  343. finish = FinishedJobRegistry(queue=q)
  344. deferred = DeferredJobRegistry(queue=q)
  345. # 获取正在执行的作业 ID 列表
  346. run_job_ids = registry.get_job_ids()
  347. finish_job_ids = finish.get_job_ids()
  348. wait_job_ids = deferred.get_job_ids()
  349. myLogger.info_logger("waiting jobs' ID: " + wait_job_ids)
  350. myLogger.info_logger("Running jobs' ID: " + run_job_ids)
  351. myLogger.info_logger("Finished jobs' ID: " + finish_job_ids)
  352. installing_list = []
  353. for id in run_job_ids:
  354. app = get_installing_app(id, const.APP_READY, 'installing')
  355. installing_list.append(app)
  356. for id in wait_job_ids:
  357. app = get_installing_app(id, const.APP_WAIT, 'waiting')
  358. installing_list.append(app)
  359. return installing_list
  360. def get_installing_app(id, status_code, status):
  361. real_name = id.split('_')[0]
  362. app_name = id.split('_')[1]
  363. var_path = "/data/apps/" + app_name + "/variables.json"
  364. trade_mark = docker.read_var(var_path, 'trademark')
  365. real_name = docker.read_var(var_path, 'name')
  366. image_url = get_Image_url(real_name)
  367. app = App(app_id=real_name + "_" + app_name, name=real_name, customer_name=app_name,
  368. status_code=status_code, status=status, port=0, volume="",
  369. url="", image_url=image_url, admin_url="", trade_mark=trade_mark, user_name="",
  370. password="", official_app=True)
  371. return app
  372. def get_Image_url(app_name):
  373. image_url = "static/images/" + app_name + "-websoft9.png"
  374. return image_url
  375. def get_url(app_name, easy_url):
  376. url = easy_url
  377. if app_name == "joomla":
  378. url = easy_url + "/administrator"
  379. elif app_name == "other":
  380. url = easy_url + "/administrator"
  381. else:
  382. url = easy_url
  383. return url
  384. def get_admin_url(app_name, url):
  385. admin_url = ""
  386. if app_name == "wordpress":
  387. admin_url = url + "/wp-admin"
  388. elif app_name == "other":
  389. admin_url = url + "/admin"
  390. else:
  391. admin_url = ""
  392. return admin_url