InPanel 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. inpanel/__init__.py +29 -0
  2. inpanel/acme.py +289 -0
  3. inpanel/aliyuncs.py +269 -0
  4. inpanel/app.py +514 -0
  5. inpanel/apt.py +223 -0
  6. inpanel/base.py +200 -0
  7. inpanel/config.py +139 -0
  8. inpanel/data/config.ini +29 -0
  9. inpanel/data/history +5 -0
  10. inpanel/data/runlogs.ini +12 -0
  11. inpanel/data/update_info.ini +4 -0
  12. inpanel/dnf.py +225 -0
  13. inpanel/lib/__init__.py +9 -0
  14. inpanel/lib/async_process.py +98 -0
  15. inpanel/lib/filelock.py +82 -0
  16. inpanel/lib/magic.py +233 -0
  17. inpanel/lib/pxssh.py +310 -0
  18. inpanel/lib/pyDes.py +867 -0
  19. inpanel/mod/__init__.py +0 -0
  20. inpanel/mod/apt.py +316 -0
  21. inpanel/mod/certificate.py +268 -0
  22. inpanel/mod/config.py +372 -0
  23. inpanel/mod/cron.py +315 -0
  24. inpanel/mod/disk.py +322 -0
  25. inpanel/mod/dnf.py +239 -0
  26. inpanel/mod/file.py +722 -0
  27. inpanel/mod/firewall.py +199 -0
  28. inpanel/mod/firewall_base.py +137 -0
  29. inpanel/mod/firewall_firewalld.py +170 -0
  30. inpanel/mod/firewall_iptables.py +176 -0
  31. inpanel/mod/firewall_macos.py +213 -0
  32. inpanel/mod/firewall_ufw.py +128 -0
  33. inpanel/mod/ftp.py +231 -0
  34. inpanel/mod/httpd.py +1205 -0
  35. inpanel/mod/lighttpd.py +26 -0
  36. inpanel/mod/login.py +98 -0
  37. inpanel/mod/mysql.py +577 -0
  38. inpanel/mod/named.py +26 -0
  39. inpanel/mod/nginx.py +2327 -0
  40. inpanel/mod/package.py +33 -0
  41. inpanel/mod/package_apt.py +62 -0
  42. inpanel/mod/package_base.py +77 -0
  43. inpanel/mod/package_dnf.py +62 -0
  44. inpanel/mod/package_map.py +366 -0
  45. inpanel/mod/package_yum.py +64 -0
  46. inpanel/mod/php.py +289 -0
  47. inpanel/mod/process.py +291 -0
  48. inpanel/mod/proftpd.py +26 -0
  49. inpanel/mod/pureftpd.py +26 -0
  50. inpanel/mod/query.py +108 -0
  51. inpanel/mod/server.py +1319 -0
  52. inpanel/mod/service.py +262 -0
  53. inpanel/mod/setting.py +228 -0
  54. inpanel/mod/shell.py +78 -0
  55. inpanel/mod/ssh.py +280 -0
  56. inpanel/mod/system.py +220 -0
  57. inpanel/mod/task.py +1337 -0
  58. inpanel/mod/ufw.py +14 -0
  59. inpanel/mod/user.py +306 -0
  60. inpanel/mod/vsftpd.py +67 -0
  61. inpanel/mod/yum.py +243 -0
  62. inpanel/plugins/__init__.py +10 -0
  63. inpanel/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
  64. inpanel/plugins/acme/static/index.html +10 -0
  65. inpanel/plugins/acme/static/index.js +13 -0
  66. inpanel/plugins/shadowsocks-libev/__pycache__/info.cpython-313.pyc +0 -0
  67. inpanel/plugins/shadowsocks-libev/info.py +73 -0
  68. inpanel/plugins/shadowsocks-libev/static/index.html +70 -0
  69. inpanel/plugins/shadowsocks-libev/static/index.js +18 -0
  70. inpanel/public/css/global.css +138 -0
  71. inpanel/public/favicon.ico +0 -0
  72. inpanel/public/images/alipay.jpg +0 -0
  73. inpanel/public/images/loading.gif +0 -0
  74. inpanel/public/images/weixin.jpg +0 -0
  75. inpanel/public/index.html +94 -0
  76. inpanel/public/js/controllers/controllers.js +566 -0
  77. inpanel/public/js/controllers/database.js +590 -0
  78. inpanel/public/js/controllers/ecs.js +624 -0
  79. inpanel/public/js/controllers/file.js +940 -0
  80. inpanel/public/js/controllers/plugins.js +105 -0
  81. inpanel/public/js/controllers/service.js +1460 -0
  82. inpanel/public/js/controllers/site.js +1241 -0
  83. inpanel/public/js/controllers/utils.js +2319 -0
  84. inpanel/public/js/core.js +221 -0
  85. inpanel/public/js/directives.js +784 -0
  86. inpanel/public/js/filters.js +395 -0
  87. inpanel/public/js/services.js +257 -0
  88. inpanel/public/lib/angular/1.0.2/angular-resource.min.js +10 -0
  89. inpanel/public/lib/angular/1.0.2/angular.min.js +158 -0
  90. inpanel/public/lib/bootstrap/css/bootstrap-theme.css +587 -0
  91. inpanel/public/lib/bootstrap/css/bootstrap-theme.css.map +1 -0
  92. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css +6 -0
  93. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css.map +1 -0
  94. inpanel/public/lib/bootstrap/css/bootstrap.css +6757 -0
  95. inpanel/public/lib/bootstrap/css/bootstrap.css.map +1 -0
  96. inpanel/public/lib/bootstrap/css/bootstrap.min.css +6 -0
  97. inpanel/public/lib/bootstrap/css/bootstrap.min.css.map +1 -0
  98. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  99. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  100. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  101. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  102. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  103. inpanel/public/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
  104. inpanel/public/lib/bootstrap/img/glyphicons-halflings.png +0 -0
  105. inpanel/public/lib/bootstrap/js/bootstrap.js +2377 -0
  106. inpanel/public/lib/bootstrap/js/bootstrap.min.js +7 -0
  107. inpanel/public/lib/bootstrap/js/npm.js +13 -0
  108. inpanel/public/lib/codemirror/codemirror-compressed.js +1 -0
  109. inpanel/public/lib/codemirror/codemirror.css +173 -0
  110. inpanel/public/lib/jquery/jquery-1.12.4.min.js +5 -0
  111. inpanel/public/lib/md5/md5.js +379 -0
  112. inpanel/public/lib/md5/md5.min.js +1 -0
  113. inpanel/public/partials/database/index.html +172 -0
  114. inpanel/public/partials/database/mysql/dbedit.html +462 -0
  115. inpanel/public/partials/database/mysql/dbnew.html +348 -0
  116. inpanel/public/partials/database/mysql/useredit.html +310 -0
  117. inpanel/public/partials/database/mysql/usernew.html +97 -0
  118. inpanel/public/partials/directives/message.html +20 -0
  119. inpanel/public/partials/directives/navbar.html +25 -0
  120. inpanel/public/partials/directives/selector.html +57 -0
  121. inpanel/public/partials/directives/srvbase.html +72 -0
  122. inpanel/public/partials/directives/srvext.html +34 -0
  123. inpanel/public/partials/directives/srvfile.html +23 -0
  124. inpanel/public/partials/directives/srvinstall.html +34 -0
  125. inpanel/public/partials/directives/srvlog.html +23 -0
  126. inpanel/public/partials/directives/srvminiop.html +28 -0
  127. inpanel/public/partials/directives/srvuninstall.html +38 -0
  128. inpanel/public/partials/directives/srvupdate.html +30 -0
  129. inpanel/public/partials/ecs/account.html +156 -0
  130. inpanel/public/partials/ecs/ecs.html +198 -0
  131. inpanel/public/partials/ecs/index.html +40 -0
  132. inpanel/public/partials/ecs/setings.html +459 -0
  133. inpanel/public/partials/file/file.html +501 -0
  134. inpanel/public/partials/file/trash.html +95 -0
  135. inpanel/public/partials/ftp.html +36 -0
  136. inpanel/public/partials/log.html +83 -0
  137. inpanel/public/partials/login.html +79 -0
  138. inpanel/public/partials/logout.html +22 -0
  139. inpanel/public/partials/main.html +534 -0
  140. inpanel/public/partials/plugins/index.html +55 -0
  141. inpanel/public/partials/plugins/plugins.html +13 -0
  142. inpanel/public/partials/secure.html +13 -0
  143. inpanel/public/partials/service/apache.html +147 -0
  144. inpanel/public/partials/service/cron.html +71 -0
  145. inpanel/public/partials/service/index.html +293 -0
  146. inpanel/public/partials/service/iptables.html +40 -0
  147. inpanel/public/partials/service/lighttpd.html +63 -0
  148. inpanel/public/partials/service/mariadb.html +113 -0
  149. inpanel/public/partials/service/memcache.html +36 -0
  150. inpanel/public/partials/service/minio.html +44 -0
  151. inpanel/public/partials/service/mongodb.html +44 -0
  152. inpanel/public/partials/service/mysql.html +113 -0
  153. inpanel/public/partials/service/named.html +61 -0
  154. inpanel/public/partials/service/nginx.html +296 -0
  155. inpanel/public/partials/service/ntp.html +40 -0
  156. inpanel/public/partials/service/php.html +250 -0
  157. inpanel/public/partials/service/proftpd.html +59 -0
  158. inpanel/public/partials/service/pureftpd.html +59 -0
  159. inpanel/public/partials/service/redis.html +40 -0
  160. inpanel/public/partials/service/samba.html +58 -0
  161. inpanel/public/partials/service/sendmail.html +40 -0
  162. inpanel/public/partials/service/ssh.html +186 -0
  163. inpanel/public/partials/service/tomcat.html +65 -0
  164. inpanel/public/partials/service/vsftpd.html +195 -0
  165. inpanel/public/partials/setting.html +299 -0
  166. inpanel/public/partials/site/apache/site.html +277 -0
  167. inpanel/public/partials/site/index.html +344 -0
  168. inpanel/public/partials/site/nginx/site.html +558 -0
  169. inpanel/public/partials/sorry.html +19 -0
  170. inpanel/public/partials/storage/autofm.html +103 -0
  171. inpanel/public/partials/storage/backup.html +42 -0
  172. inpanel/public/partials/storage/index.html +360 -0
  173. inpanel/public/partials/storage/migrate.html +93 -0
  174. inpanel/public/partials/storage/movedata.html +98 -0
  175. inpanel/public/partials/storage/remote.html +77 -0
  176. inpanel/public/partials/utils/cron.html +486 -0
  177. inpanel/public/partials/utils/firewall.html +266 -0
  178. inpanel/public/partials/utils/index.html +193 -0
  179. inpanel/public/partials/utils/network.html +118 -0
  180. inpanel/public/partials/utils/process.html +239 -0
  181. inpanel/public/partials/utils/repository.html +373 -0
  182. inpanel/public/partials/utils/shell.html +28 -0
  183. inpanel/public/partials/utils/ssl.html +319 -0
  184. inpanel/public/partials/utils/time.html +109 -0
  185. inpanel/public/partials/utils/user.html +359 -0
  186. inpanel/public/robots.txt +2 -0
  187. inpanel/remote.py +109 -0
  188. inpanel/templates/file/preview.html +34 -0
  189. inpanel/templates/index.html +93 -0
  190. inpanel/utils.py +282 -0
  191. inpanel/web.py +1811 -0
  192. inpanel/yum.py +756 -0
  193. inpanel-1.2.1.dist-info/METADATA +399 -0
  194. inpanel-1.2.1.dist-info/RECORD +198 -0
  195. inpanel-1.2.1.dist-info/WHEEL +5 -0
  196. inpanel-1.2.1.dist-info/entry_points.txt +2 -0
  197. inpanel-1.2.1.dist-info/licenses/LICENSE +30 -0
  198. inpanel-1.2.1.dist-info/top_level.txt +1 -0
inpanel/mod/task.py ADDED
@@ -0,0 +1,1337 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (c) 2020-2026 Jackson Dou
4
+ # All rights reserved.
5
+ #
6
+ # InPanel is distributed under the terms of the (new) BSD License.
7
+ # The full license can be found in 'LICENSE'.
8
+
9
+ """Module for Task Management."""
10
+
11
+ import time
12
+ from datetime import datetime
13
+ from functools import partial
14
+ from pathlib import Path
15
+ from shlex import quote
16
+ from subprocess import PIPE, Popen
17
+
18
+ import tornado.escape
19
+ import tornado.httpclient
20
+
21
+ from .. import remote
22
+ from ..base import app_api, os_versint
23
+ from . import disk
24
+ from . import shell
25
+ from . import server
26
+ from . import yum
27
+ from . import mysql
28
+ from . import file
29
+ from . import ssh
30
+ from . import ftp
31
+
32
+
33
+ class TaskManager:
34
+ jobs = {}
35
+ locks = {}
36
+
37
+ def __init__(self, settings, config):
38
+ self.settings = settings
39
+ self.config = config
40
+
41
+ def _lock_job(self, lockname):
42
+ if lockname in self.locks:
43
+ return False
44
+ self.locks[lockname] = True
45
+ return True
46
+
47
+ def _unlock_job(self, lockname):
48
+ if lockname not in self.locks:
49
+ return False
50
+ del self.locks[lockname]
51
+ return True
52
+
53
+ def _start_job(self, jobname):
54
+ if jobname in self.jobs and self.jobs[jobname]['status'] == 'running':
55
+ return False
56
+ self.jobs[jobname] = {'status': 'running', 'msg': ''}
57
+ return True
58
+
59
+ def _update_job(self, jobname, code, msg):
60
+ self.jobs[jobname]['code'] = code
61
+ self.jobs[jobname]['msg'] = msg
62
+ return True
63
+
64
+ def _get_job(self, jobname):
65
+ if jobname not in self.jobs:
66
+ return {'status': 'none', 'code': -1, 'msg': ''}
67
+ return self.jobs[jobname]
68
+
69
+ def _finish_job(self, jobname, code, msg, data=None):
70
+ self.jobs[jobname]['status'] = 'finish'
71
+ self.jobs[jobname]['code'] = code
72
+ self.jobs[jobname]['msg'] = msg
73
+ if data:
74
+ self.jobs[jobname]['data'] = data
75
+
76
+ def _call(self, callback):
77
+ tornado.ioloop.IOLoop.instance().add_callback(callback)
78
+
79
+ async def update(self):
80
+ if not self._start_job('update'):
81
+ return
82
+
83
+ root_path = self.settings['root_path']
84
+ data_path = self.settings['data_path']
85
+
86
+ if Path('%s/../.git' % root_path).exists():
87
+ self._finish_job('update', 0, '升级成功!')
88
+ return
89
+
90
+ http_client = tornado.httpclient.AsyncHTTPClient()
91
+ response = await http_client.fetch(app_api['latest'])
92
+ if response.error:
93
+ self._update_job('update', -1, '获取版本信息失败!')
94
+ return
95
+ versioninfo = tornado.escape.json_decode(response.body.decode('utf-8'))
96
+ downloadurl = versioninfo['download']
97
+ initscript = '%s/scripts/init.d/%s/inpanel' % (root_path, self.settings['os_name'])
98
+ binscript = '%s/scripts/bin/inpanel' % root_path
99
+ steps = [
100
+ {
101
+ 'desc': '正在备份当前配置文件...',
102
+ 'cmd': '/bin/cp -f %s/config.ini /tmp/inpanel_config.ini' % data_path,
103
+ }, {
104
+ 'desc': '正在下载安装包...',
105
+ 'cmd': 'wget -q "%s" -O %s/inpanel.tar.gz' % (downloadurl, data_path),
106
+ }, {
107
+ 'desc': '正在创建解压目录...',
108
+ 'cmd': 'mkdir -p %s/inpanel' % data_path,
109
+ }, {
110
+ 'desc': '正在解压安装包...',
111
+ 'cmd': 'tar zxmf %s/inpanel.tar.gz -C %s/inpanel --strip-components 1' % (data_path, data_path),
112
+ }, {
113
+ 'desc': '正在删除旧版本...',
114
+ 'cmd': 'find %s -mindepth 1 -maxdepth 1 -path %s -prune -o -exec rm -rf {} \\\\;' % (root_path, data_path),
115
+ }, {
116
+ 'desc': '正在复制新版本...',
117
+ 'cmd': 'find %s/inpanel -mindepth 1 -maxdepth 1 -exec cp -r {} %s \\\\;' % (data_path, root_path),
118
+ }, {
119
+ 'desc': '正在删除旧的服务脚本...',
120
+ 'cmd': 'rm -f /etc/init.d/inpanel /usr/bin/inpanel',
121
+ }, {
122
+ 'desc': '正在安装新的服务脚本...',
123
+ 'cmd': 'ln -s %s /etc/init.d/inpanel' % initscript
124
+ }, {
125
+ 'desc': '正在安装新的命令脚本...',
126
+ 'cmd': 'ln -s %s /usr/bin/inpanel' % binscript
127
+ }, {
128
+ 'desc': '正在更改脚本权限...',
129
+ 'cmd': 'chmod +x /usr/bin/inpanel /etc/init.d/inpanel %s/config.py %s/server.py' % (root_path, root_path),
130
+ }, {
131
+ 'desc': '正在删除安装临时文件...',
132
+ 'cmd': 'rm -rf %s/inpanel %s/inpanel.tar.gz' % (data_path, data_path)
133
+ }, {
134
+ 'desc': '正在恢复旧的配置文件...',
135
+ 'cmd': '/bin/cp -f /tmp/inpanel_config.ini %s/config.ini' % data_path
136
+ }, {
137
+ 'desc': '正在删除旧的配置文件...',
138
+ 'cmd': 'rm -f /tmp/inpanel_config.ini'
139
+ }
140
+ ]
141
+ for step in steps:
142
+ desc = step['desc']
143
+ cmd = step['cmd']
144
+ self._update_job('update', 2, desc)
145
+ result, output = await shell.async_command(cmd)
146
+ if result != 0:
147
+ self._update_job('update', -1, desc + '失败!')
148
+ break
149
+
150
+ if result == 0:
151
+ code = 0
152
+ msg = '升级成功!请刷新页面重新登录。'
153
+ else:
154
+ code = -1
155
+ msg = '升级失败!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
156
+
157
+ self._finish_job('update', code, msg)
158
+
159
+ async def service(self, action, service, name):
160
+ jobname = f'service_{action}_{service}'
161
+ if not self._start_job(jobname):
162
+ return
163
+
164
+ action_str = {'start': '启动', 'stop': '停止', 'restart': '重启'}
165
+ self._update_job(jobname, 2, f'正在{action_str[action]} {name} 服务...')
166
+
167
+ if action == 'start' and service in ('sendmail',) and self.settings['os_name'] in ('redhat', 'centos') and os_versint == 5:
168
+ hostname = server.ServerInfo.hostname()
169
+ hostname_found = False
170
+ dot_found = False
171
+ lines = []
172
+ with open('/etc/hosts', encoding='utf-8') as f:
173
+ for line in f:
174
+ if not line.startswith('#') and not hostname_found:
175
+ fields = line.strip().split()
176
+ if hostname in fields:
177
+ hostname_found = True
178
+ dot_found = any(field for field in fields[1:] if '.' in field)
179
+ if not dot_found:
180
+ line = '%s %s.localdomain\n' % (line.strip(), hostname)
181
+ lines.append(line)
182
+ if not dot_found:
183
+ with open('/etc/hosts', 'w', encoding='utf-8') as f:
184
+ f.writelines(lines)
185
+ if os_versint < 7:
186
+ cmd = f'/etc/init.d/{service} {action}'
187
+ else:
188
+ cmd = f'/bin/systemctl {action} {service}.service'
189
+ result, output = await shell.async_command(cmd)
190
+ if result == 0:
191
+ code = 0
192
+ msg = f'{name} 服务{action_str[action]}成功!'
193
+ else:
194
+ code = -1
195
+ txt = output.strip().replace('\n', '<br>')
196
+ msg = f'{name} 服务{action_str[action]}失败!<p style="margin:10px">{txt}</p>'
197
+
198
+ self._finish_job(jobname, code, msg)
199
+
200
+ async def datetime(self, newdatetime):
201
+ jobname = 'datetime'
202
+ if not self._start_job(jobname):
203
+ return
204
+
205
+ self._update_job(jobname, 2, '正在设置系统时间...')
206
+
207
+ cmd = 'date -s %s' % (quote(newdatetime),)
208
+ result, output = await shell.async_command(cmd)
209
+ if result == 0:
210
+ code = 0
211
+ msg = '系统时间设置成功!'
212
+ else:
213
+ code = -1
214
+ txt = output.strip().replace('\n', '<br>')
215
+ msg = f'系统时间设置失败!<p style="margin:10px">{txt}</p>'
216
+
217
+ self._finish_job(jobname, code, msg)
218
+
219
+ async def ntpdate(self, server):
220
+ jobname = 'ntpdate_%s' % server
221
+ if not self._start_job(jobname):
222
+ return
223
+
224
+ self._update_job(jobname, 2, '正在从 %s 同步时间...' % server)
225
+ cmd = 'ntpdate -u %s' % server
226
+ result, output = await shell.async_command(cmd)
227
+ if result == 0:
228
+ code = 0
229
+ offset = output.split(' offset ')[-1].split()[0]
230
+ msg = '同步时间成功!(时间偏差 %s 秒)' % str(offset)
231
+ else:
232
+ code = -1
233
+ if 'no server suitable' in output:
234
+ msg = '同步时间失败!没有找到合适同步服务器。'
235
+ elif 'no servers can be used' in output:
236
+ msg = '同步时间失败!没有找到同步服务器的地址'
237
+ else:
238
+ msg = '同步时间失败!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
239
+
240
+ self._finish_job(jobname, code, msg)
241
+
242
+ async def swapon(self, action, devname):
243
+ jobname = f'swapon_{action}_{devname}'
244
+ if not self._start_job(jobname):
245
+ return
246
+
247
+ action_str = {'on': '启用', 'off': '停用'}
248
+ self._update_job(jobname, 2, f'正在{action_str[action]} {devname}...')
249
+
250
+ if action == 'on':
251
+ cmd = f'swapon /dev/{devname}'
252
+ else:
253
+ cmd = f'swapoff /dev/{devname}'
254
+
255
+ result, output = await shell.async_command(cmd)
256
+ if result == 0:
257
+ code = 0
258
+ msg = f'{action_str[action]} {devname} 成功!'
259
+ else:
260
+ code = -1
261
+ txt = output.strip().replace('\n', '<br>')
262
+ msg = f'{action_str[action]} {devname} 失败!<p style="margin:10px">{txt}</p>'
263
+
264
+ self._finish_job(jobname, code, msg)
265
+
266
+ async def mount(self, action, devname, mountpoint, fstype):
267
+ jobname = 'mount_%s_%s' % (action, devname)
268
+ if not self._start_job(jobname):
269
+ return
270
+
271
+ action_str = {'mount': '挂载', 'umount': '卸载'}
272
+ self._update_job(jobname, 2, '正在%s %s 到 %s...' % (action_str[action], devname, mountpoint))
273
+
274
+ if action == 'mount':
275
+ disk.fstab(devname, {
276
+ 'devname': devname,
277
+ 'mount': mountpoint,
278
+ 'fstype': fstype,
279
+ })
280
+ cmd = 'mount -t %s /dev/%s %s' % (fstype, devname, mountpoint)
281
+ else:
282
+ cmd = 'umount /dev/%s' % (devname)
283
+
284
+ result, output = await shell.async_command(cmd)
285
+ if result == 0:
286
+ code = 0
287
+ msg = '%s %s 成功!' % (action_str[action], devname)
288
+ else:
289
+ code = -1
290
+ msg = '%s %s 失败!<p style="margin:10px">%s</p>' % (action_str[action], devname, output.strip().replace('\n', '<br>'))
291
+
292
+ self._finish_job(jobname, code, msg)
293
+
294
+ async def format(self, devname, fstype):
295
+ jobname = 'format_%s' % devname
296
+ if not self._start_job(jobname):
297
+ return
298
+
299
+ self._update_job(jobname, 2, '正在格式化 %s,可能需要较长时间,请耐心等候...' % devname)
300
+
301
+ if fstype in ('ext2', 'ext3', 'ext4'):
302
+ cmd = 'mkfs.%s -F /dev/%s' % (fstype, devname)
303
+ elif fstype in ('xfs', 'reiserfs', 'btrfs'):
304
+ cmd = 'mkfs.%s -f /dev/%s' % (fstype, devname)
305
+ elif fstype == 'swap':
306
+ cmd = 'mkswap -f /dev/%s' % devname
307
+ else:
308
+ cmd = 'mkfs.%s /dev/%s' % (fstype, devname)
309
+ result, output = await shell.async_command(cmd)
310
+ if result == 0:
311
+ code = 0
312
+ msg = '%s 格式化成功!' % devname
313
+ else:
314
+ code = -1
315
+ msg = '%s 格式化失败!<p style="margin:10px">%s</p>' % (devname, output.strip().replace('\n', '<br>'))
316
+
317
+ self._finish_job(jobname, code, msg)
318
+
319
+ async def yum_repolist(self):
320
+ jobname = 'yum_repolist'
321
+ if not self._start_job(jobname):
322
+ return
323
+ if not self._lock_job('yum'):
324
+ self._finish_job(jobname, -1, '已有一个YUM进程在运行,读取软件源列表失败。')
325
+ return
326
+
327
+ self._update_job(jobname, 2, '正在获取软件源列表...')
328
+
329
+ cmd = 'yum repolist --disableplugin=fastestmirror'
330
+ result, output = await shell.async_command(cmd)
331
+ data = []
332
+ if result == 0:
333
+ code = 0
334
+ msg = '获取软件源列表成功!'
335
+ lines = output.split('\n')
336
+ for line in lines:
337
+ if not line:
338
+ continue
339
+ repo = line.split()[0]
340
+ if repo in yum.yum_repolist:
341
+ data.append(repo)
342
+ else:
343
+ code = -1
344
+ msg = '获取软件源列表失败!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
345
+
346
+ self._finish_job(jobname, code, msg, data)
347
+ self._unlock_job('yum')
348
+
349
+ async def yum_installrepo(self, repo):
350
+ jobname = 'yum_installrepo_%s' % repo
351
+ if not self._start_job(jobname):
352
+ return
353
+
354
+ if repo not in yum.yum_repolist:
355
+ self._finish_job(jobname, -1, '不可识别的软件源!')
356
+ self._unlock_job('yum')
357
+ return
358
+
359
+ self._update_job(jobname, 2, '正在安装软件源 %s...' % repo)
360
+
361
+ arch = self.settings['arch']
362
+
363
+ cmds = []
364
+ if repo == 'base':
365
+ for cmd in yum.get_repo_release(os_versint, self.settings['os_name'], self.settings['arch']):
366
+ cmds.append(cmd)
367
+
368
+ elif repo in ('epel', 'CentALT'):
369
+ for cmd in yum.get_repo_epel(os_versint, self.settings['os_name'], self.settings['arch']):
370
+ cmds.append(cmd)
371
+
372
+ elif repo == 'ius':
373
+ result, output = await shell.async_command(yum.yum_repoinstallcmds['ius'])
374
+ if result != 0:
375
+ error = True
376
+
377
+ elif repo == '10gen':
378
+ with open('/etc/yum.repos.d/10gen.repo', 'w', encoding='utf-8') as f:
379
+ f.write(yum.yum_repostr['10gen'][self.settings['arch']])
380
+
381
+ elif repo == 'mariadb':
382
+ with open('/etc/yum.repos.d/mariadb.repo', 'w', encoding='utf-8') as f:
383
+ f.write(yum.yum_repostr['mariadb'][self.settings['arch']])
384
+
385
+ elif repo == 'atomic':
386
+ result, output = await shell.async_command(yum.yum_repoinstallcmds['atomic'])
387
+ if result != 0:
388
+ error = True
389
+
390
+ error = False
391
+ for cmd in cmds:
392
+ result, output = await shell.async_command(cmd)
393
+ if result != 0 and not 'already installed' in output:
394
+ error = True
395
+ break
396
+
397
+ if repo == 'CentALT':
398
+ repofile = '/etc/yum.repos.d/centalt.repo'
399
+ if Path(repofile).is_file():
400
+ lines = []
401
+ baseurl_found = False
402
+ with open(repofile, encoding='utf-8') as f:
403
+ for line in f:
404
+ if line.startswith('baseurl='):
405
+ baseurl_found = True
406
+ line = '#%s' % line
407
+ lines.append(line)
408
+ lines.append(line)
409
+ if baseurl_found:
410
+ with open(repofile, 'w', encoding='utf-8') as f:
411
+ f.writelines(lines)
412
+
413
+ if not error:
414
+ code = 0
415
+ msg = '软件源 %s 安装成功!' % repo
416
+ else:
417
+ code = -1
418
+ msg = '软件源 %s 安装失败!<p style="margin:10px">%s</p>' % (repo, output.strip().replace('\n', '<br>'))
419
+
420
+ self._finish_job(jobname, code, msg)
421
+
422
+ async def yum_info(self, pkg, repo, option):
423
+ jobname = 'yum_info_%s' % pkg
424
+ if not self._start_job(jobname):
425
+ return
426
+ if not self._lock_job('yum'):
427
+ self._finish_job(jobname, -1, '已有一个YUM进程在运行,读取软件包信息失败。')
428
+ return
429
+
430
+ self._update_job(jobname, 2, '正在获取软件版本信息...')
431
+
432
+ if repo == '*':
433
+ repo = ''
434
+ arch = self.settings['arch']
435
+ if pkg in yum.yum_pkg_noarchitecture:
436
+ arch = 'noarch'
437
+ if option == 'install':
438
+ cmds = ['yum info %s %s.%s --showduplicates --disableplugin=fastestmirror'
439
+ % (repo, alias, arch) for alias in yum.yum_pkg_alias[pkg]]
440
+ else:
441
+ cmds = ['yum info %s.%s --disableplugin=fastestmirror' % (pkg, arch)]
442
+
443
+ data = []
444
+ matched = False
445
+ await shell.async_command('LANG="en_US.UTF-8"')
446
+ for cmd in cmds:
447
+ result, output = await shell.async_command(cmd)
448
+ if result == 0:
449
+ matched = True
450
+ lines = output.split('\n')
451
+ for line in lines:
452
+ if any(line.startswith(word)
453
+ for word in ('Name', 'Version', 'Release', 'Size', 'Repo', 'From repo', 'Summary', 'URL', 'License')):
454
+ fields = line.strip().split(':', 1)
455
+ if len(fields) != 2:
456
+ continue
457
+ field_name = fields[0].strip().lower().replace(' ', '_')
458
+ field_value = fields[1].strip()
459
+ if field_name == 'name':
460
+ data.append({})
461
+ if field_name == 'repo':
462
+ data[-1][field_name] = field_value.split('/')[0]
463
+ else:
464
+ data[-1][field_name] = field_value
465
+
466
+ if matched:
467
+ code = 0
468
+ msg = '获取软件版本信息成功!'
469
+ data = [pkg for pkg in data if pkg['repo'] in yum.yum_repolist + ('installed',)]
470
+ if option == 'update' and len(data) == 1:
471
+ msg = '没有找到可用的新版本!'
472
+ else:
473
+ code = -1
474
+ msg = '获取软件版本信息失败!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
475
+
476
+ self._finish_job(jobname, code, msg, data)
477
+ self._unlock_job('yum')
478
+
479
+ async def yum_install(self, repo, pkg, version, release, ext):
480
+ jobname = 'yum_install_%s_%s_%s_%s_%s' % (repo, pkg, ext, version, release)
481
+ jobname = jobname.strip('_').replace('__', '_')
482
+ if not self._start_job(jobname):
483
+ return
484
+ if not self._lock_job('yum'):
485
+ self._finish_job(jobname, -1, '已有一个YUM进程在运行,安装失败。')
486
+ return
487
+
488
+ if ext:
489
+ self._update_job(jobname, 2, '正在下载并安装扩展包,请耐心等候...')
490
+ else:
491
+ self._update_job(jobname, 2, '正在下载并安装软件包,请耐心等候...')
492
+
493
+ arch = self.settings['arch']
494
+ if pkg in yum.yum_pkg_noarchitecture:
495
+ arch = 'noarch'
496
+
497
+ if ext:
498
+ if version:
499
+ if release:
500
+ pkgs = ['%s-%s-%s.%s' % (ext, version, release, arch)]
501
+ else:
502
+ pkgs = ['%s-%s.%s' % (ext, version, arch)]
503
+ else:
504
+ pkgs = ['%s.%s' % (ext, arch)]
505
+ else:
506
+ if version:
507
+ if release:
508
+ pkgs = ['%s-%s-%s.%s' % (p, version, release, arch)
509
+ for p, pinfo in yum.yum_pkg_relatives[pkg].items() if pinfo['default']]
510
+ else:
511
+ pkgs = ['%s-%s.%s' % (p, version, arch)
512
+ for p, pinfo in yum.yum_pkg_relatives[pkg].items() if pinfo['default']]
513
+ else:
514
+ pkgs = ['%s.%s' % (p, arch)
515
+ for p, pinfo in yum.yum_pkg_relatives[pkg].items() if pinfo['default']]
516
+ repos = [repo, ]
517
+ if repo in ('CentALT', 'ius', 'atomic', '10gen', 'mariadb'):
518
+ repos.extend(['base', 'updates', 'epel'])
519
+ exclude_repos = [r for r in yum.yum_repolist if r not in repos]
520
+
521
+ endinstall = False
522
+ hasconflict = False
523
+ conflicts_backups = []
524
+ while not endinstall:
525
+ cmd = 'yum install -y %s --disablerepo=%s' % (' '.join(pkgs), ','.join(exclude_repos))
526
+ result, output = await shell.async_command(cmd)
527
+ pkg_ext = ext and ext or pkg
528
+
529
+ pkgstr = version and '%s v%s-%s' % (pkg_ext, version, release) or (pkg_ext)
530
+ if result == 0:
531
+ if hasconflict:
532
+ cmd = 'yum install -y %s' % (' '.join(conflicts_backups),)
533
+ result, output = await shell.async_command(cmd)
534
+ endinstall = True
535
+ code = 0
536
+ msg = '%s 安装成功!' % pkgstr
537
+ else:
538
+ clines = output.split('\n')
539
+ for cline in clines:
540
+ if cline.startswith('Error:') and ' conflicts with ' in cline:
541
+ hasconflict = True
542
+ conflict_pkg = cline.split(' conflicts with ', 1)[1]
543
+ self._update_job(jobname, 2, '检测到软件冲突,正在卸载处理冲突...')
544
+ tcmd = 'yum erase -y %s' % conflict_pkg
545
+ result, output = await shell.async_command(tcmd)
546
+ if result == 0:
547
+ lines = output.split('\n')
548
+ conflicts_backups = []
549
+ linestart = False
550
+ for line in lines:
551
+ if not linestart:
552
+ if not line.startswith('Removing for dependencies:'):
553
+ continue
554
+ linestart = True
555
+ if not line.strip():
556
+ break
557
+ fields = line.split()
558
+ conflicts_backups.append('%s-%s' % (fields[0], fields[2]))
559
+ else:
560
+ endinstall = True
561
+ break
562
+ elif 'conflicts between' in cline:
563
+ pass
564
+ if not hasconflict:
565
+ endinstall = True
566
+ if endinstall:
567
+ code = -1
568
+ msg = '%s 安装失败!<p style="margin:10px">%s</p>' % (pkgstr, output.strip().replace('\n', '<br>'))
569
+
570
+ self._finish_job(jobname, code, msg)
571
+ self._unlock_job('yum')
572
+
573
+ async def yum_uninstall(self, repo, pkg, version, release, ext):
574
+ jobname = 'yum_uninstall_%s_%s_%s_%s' % (pkg, ext, version, release)
575
+ jobname = jobname.strip('_').replace('__', '_')
576
+ if not self._start_job(jobname):
577
+ return
578
+ if not self._lock_job('yum'):
579
+ self._finish_job(jobname, -1, '已有一个YUM进程在运行,卸载失败。')
580
+ return
581
+
582
+ if ext:
583
+ self._update_job(jobname, 2, '正在卸载扩展包...')
584
+ else:
585
+ self._update_job(jobname, 2, '正在卸载软件包...')
586
+
587
+ arch = self.settings['arch']
588
+ if pkg in yum.yum_pkg_noarchitecture:
589
+ arch = 'noarch'
590
+
591
+ if ext:
592
+ pkgs = ['%s-%s-%s.%s' % (ext, version, release, arch)]
593
+ else:
594
+ pkgs = ['%s-%s-%s.%s' % (p, version, release, arch)
595
+ for p, pinfo in yum.yum_pkg_relatives[pkg].items()
596
+ if 'base' in pinfo and pinfo['base']]
597
+ cmd = 'yum erase -y %s' % (' '.join(pkgs),)
598
+ result, output = await shell.async_command(cmd)
599
+ pkg_ext = ext and ext or pkg
600
+ if result == 0:
601
+ code = 0
602
+ msg = '%s v%s-%s 卸载成功!' % (pkg_ext, version, release)
603
+ else:
604
+ code = -1
605
+ msg = '%s v%s-%s 卸载失败!<p style="margin:10px">%s</p>' % \
606
+ (pkg_ext, version, release, output.strip().replace('\n', '<br>'))
607
+
608
+ self._finish_job(jobname, code, msg)
609
+ self._unlock_job('yum')
610
+
611
+ async def yum_update(self, repo, pkg, version, release, ext):
612
+ jobname = 'yum_update_%s_%s_%s_%s_%s' % (repo, pkg, ext, version, release)
613
+ jobname = jobname.strip('_').replace('__', '_')
614
+ if not self._start_job(jobname):
615
+ return
616
+ if not self._lock_job('yum'):
617
+ self._finish_job(jobname, -1, '已有一个YUM进程在运行,更新失败。')
618
+ return
619
+
620
+ if ext:
621
+ self._update_job(jobname, 2, '正在下载并升级扩展包,请耐心等候...')
622
+ else:
623
+ self._update_job(jobname, 2, '正在下载并升级软件包,请耐心等候...')
624
+
625
+ pkg_ext = ext and ext or pkg
626
+
627
+ arch = self.settings['arch']
628
+ if pkg_ext in yum.yum_pkg_noarchitecture:
629
+ arch = 'noarch'
630
+
631
+ cmd = 'yum update -y %s-%s-%s.%s' % (pkg_ext, version, release, arch)
632
+ result, output = await shell.async_command(cmd)
633
+ if result == 0:
634
+ code = 0
635
+ msg = '成功升级 %s 到版本 v%s-%s!' % (pkg_ext, version, release)
636
+ else:
637
+ code = -1
638
+ msg = '%s 升级到版本 v%s-%s 失败!<p style="margin:10px">%s</p>' % \
639
+ (pkg_ext, version, release, output.strip().replace('\n', '<br>'))
640
+
641
+ self._finish_job(jobname, code, msg)
642
+ self._unlock_job('yum')
643
+
644
+ async def yum_ext_info(self, pkg):
645
+ jobname = 'yum_ext_info_%s' % pkg
646
+ if not self._start_job(jobname):
647
+ return
648
+ if not self._lock_job('yum'):
649
+ self._finish_job(jobname, -1, '已有一个YUM进程在运行,获取扩展信息失败。')
650
+ return
651
+
652
+ self._update_job(jobname, 2, '正在收集扩展信息...')
653
+
654
+ exts = [k for k, v in yum.yum_pkg_relatives[pkg].items() if 'isext' in v and v['isext']]
655
+ cmd = 'yum info %s --disableplugin=fastestmirror' % (' '.join(['%s.%s' % (ext, self.settings['arch']) for ext in exts]))
656
+
657
+ data = []
658
+ matched = False
659
+ await shell.async_command('LANG="en_US.UTF-8"')
660
+ result, output = await shell.async_command(cmd)
661
+ if result == 0:
662
+ matched = True
663
+ lines = output.split('\n')
664
+ for line in lines:
665
+ if any(line.startswith(word)
666
+ for word in ('Name', 'Version', 'Release', 'Size',
667
+ 'Repo', 'From repo')):
668
+ fields = line.strip().split(':', 1)
669
+ if len(fields) != 2:
670
+ continue
671
+ field_name = fields[0].strip().lower().replace(' ', '_')
672
+ field_value = fields[1].strip()
673
+ if field_name == 'name':
674
+ data.append({})
675
+ data[-1][field_name] = field_value
676
+ if matched:
677
+ code = 0
678
+ msg = '获取扩展信息成功!'
679
+ else:
680
+ code = -1
681
+ msg = '获取扩展信息失败!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
682
+
683
+ self._finish_job(jobname, code, msg, data)
684
+ self._unlock_job('yum')
685
+
686
+ async def copy(self, srcpath, despath):
687
+ jobname = 'copy_%s_%s' % (srcpath, despath)
688
+
689
+ if not self._start_job(jobname):
690
+ return
691
+
692
+ self._update_job(jobname, 2, '正在复制 %s 到 %s...' % (srcpath, despath))
693
+
694
+ cmd = 'cp -rf %s %s' % (quote(srcpath), quote(despath))
695
+ result, output = await shell.async_command(cmd)
696
+
697
+ if result == 0:
698
+ code = 0
699
+ msg = '复制 %s 到 %s 完成!' % (srcpath, despath)
700
+ else:
701
+ code = -1
702
+ msg = '复制 %s 到 %s 失败!<p style="margin:10px">%s</p>' % (srcpath, despath, output.strip().replace('\n', '<br>'))
703
+
704
+ self._finish_job(jobname, code, msg)
705
+
706
+ async def move(self, srcpath, despath):
707
+ jobname = 'move_%s_%s' % (srcpath, despath)
708
+ if not self._start_job(jobname):
709
+ return
710
+
711
+ self._update_job(jobname, 2, '正在移动 %s 到 %s...' % (srcpath, despath))
712
+
713
+ despath_exists = Path(despath).exists()
714
+
715
+ if despath_exists:
716
+ if not Path(srcpath).exists():
717
+ self._finish_job(jobname, -1, '不可识别的源!')
718
+ return
719
+ cmd = 'cp -rf %s/* %s' % (quote(srcpath), quote(despath))
720
+ else:
721
+ cmd = 'mv %s %s' % (quote(srcpath), quote(despath))
722
+ result, output = await shell.async_command(cmd)
723
+ if result == 0:
724
+ code = 0
725
+ msg = '移动 %s 到 %s 完成!' % (srcpath, despath)
726
+ else:
727
+ code = -1
728
+ msg = '移动 %s 到 %s 失败!<p style="margin:10px">%s</p>' % (srcpath, despath, output.strip().replace('\n', '<br>'))
729
+
730
+ if despath_exists and code == 0:
731
+ cmd = 'rm -rf %s' % (quote(srcpath),)
732
+ result, output = await shell.async_command(cmd)
733
+ if result == 0:
734
+ code = 0
735
+ msg = '移动 %s 到 %s 完成!' % (srcpath, despath)
736
+ else:
737
+ code = -1
738
+ msg = '移动 %s 到 %s 失败!<p style="margin:10px">%s</p>' % (srcpath, despath, output.strip().replace('\n', '<br>'))
739
+
740
+ self._finish_job(jobname, code, msg)
741
+
742
+ async def remove(self, paths):
743
+ jobname = 'remove_%s' % ','.join(paths)
744
+ if not self._start_job(jobname):
745
+ return
746
+
747
+ for path in paths:
748
+ self._update_job(jobname, 2, '正在删除 %s...' % path)
749
+ cmd = 'rm -rf %s' % (quote(path))
750
+ result, output = await shell.async_command(cmd)
751
+
752
+ if result == 0:
753
+ code = 0
754
+ msg = '删除 %s 成功!' % path
755
+ else:
756
+ code = -1
757
+ msg = '删除 %s 失败!<p style="margin:10px">%s</p>' % (path, output.strip().replace('\n', '<br>'))
758
+
759
+ self._finish_job(jobname, code, msg)
760
+
761
+ async def compress(self, zippath, paths):
762
+ jobname = 'compress_%s_%s' % (zippath, ','.join(paths))
763
+ if not self._start_job(jobname):
764
+ return
765
+ self._update_job(jobname, 2, '正在压缩生成 %s...' % zippath)
766
+
767
+ basepath = str(Path(zippath).parent) + '/'
768
+ path = ' '.join([quote(item.replace(basepath, '')) for item in paths])
769
+ if zippath.endswith('.tar.gz') or zippath.endswith('.tgz'):
770
+ cmd = 'tar zcf %s -C %s %s' % (quote(zippath), quote(basepath), path)
771
+ elif zippath.endswith('.tar.bz2'):
772
+ cmd = 'tar jcf %s -C %s %s' % (quote(zippath), quote(basepath), path)
773
+ elif zippath.endswith('.zip'):
774
+ if not Path('/usr/bin/zip').exists():
775
+ self._update_job(jobname, 2, '正在安装 zip...')
776
+ if self.settings['os_name'] in ('centos', 'redhat'):
777
+ cmd = 'yum install -y zip unzip'
778
+ result, output = await shell.async_command(cmd)
779
+ if result == 0:
780
+ self._update_job(jobname, 0, 'zip 安装成功!')
781
+ else:
782
+ self._update_job(jobname, -1, 'zip 安装失败!')
783
+ return
784
+ cmd = 'cd %s; zip -rq9 %s %s' % (quote(basepath), quote(zippath), path)
785
+ elif zippath.endswith('.gz'):
786
+ path = ' '.join([quote(item) for item in paths])
787
+ cmd = 'gzip -f %s' % path
788
+ else:
789
+ self._finish_job(jobname, -1, '不支持的类型!')
790
+ return
791
+
792
+ result, output = await shell.async_command(cmd)
793
+ if result == 0:
794
+ code = 0
795
+ msg = '压缩到 %s 成功!' % zippath
796
+ else:
797
+ code = -1
798
+ msg = '压缩失败!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
799
+
800
+ self._finish_job(jobname, code, msg)
801
+
802
+ async def decompress(self, zippath, despath):
803
+ jobname = 'decompress_%s_%s' % (zippath, despath)
804
+ if not self._start_job(jobname):
805
+ return
806
+
807
+ self._update_job(jobname, 2, '正在解压 %s...' % zippath)
808
+ if zippath.endswith('.tar.gz') or zippath.endswith('.tgz'):
809
+ cmd = 'tar zxf %s -C %s' % (quote(zippath), quote(despath))
810
+ elif zippath.endswith('.tar.bz2'):
811
+ cmd = 'tar jxf %s -C %s' % (quote(zippath), quote(despath))
812
+ elif zippath.endswith('.zip'):
813
+ if not Path('/usr/bin/unzip').is_file():
814
+ self._update_job(jobname, 2, '正在安装 unzip...')
815
+ if self.settings['os_name'] in ('centos', 'redhat'):
816
+ cmd = 'yum install -y zip unzip'
817
+ result, output = await shell.async_command(cmd)
818
+ if result == 0:
819
+ self._update_job(jobname, 0, 'unzip 安装成功!')
820
+ else:
821
+ self._update_job(jobname, -1, 'unzip 安装失败!')
822
+ return
823
+ cmd = 'unzip -q -o %s -d %s' % (quote(zippath), quote(despath))
824
+ elif zippath.endswith('.gz'):
825
+ cmd = 'gunzip -f %s' % quote(zippath)
826
+ else:
827
+ self._finish_job(jobname, -1, '不支持的类型!')
828
+ return
829
+
830
+ result, output = await shell.async_command(cmd)
831
+ if result == 0:
832
+ code = 0
833
+ msg = '解压 %s 成功!' % zippath
834
+ else:
835
+ code = -1
836
+ msg = '解压 %s 失败!<p style="margin:10px">%s</p>' % (zippath, output.strip().replace('\n', '<br>'))
837
+
838
+ self._finish_job(jobname, code, msg)
839
+
840
+ async def chown(self, paths, user, group, option):
841
+ jobname = 'chown_%s' % ','.join(paths)
842
+ if not self._start_job(jobname):
843
+ return
844
+
845
+ self._update_job(jobname, 2, '正在设置用户和用户组...')
846
+
847
+ for path in paths:
848
+ result = await shell.async_task(file.chown, path, user, group, option == '-R')
849
+ if result == True:
850
+ code = 0
851
+ msg = '设置用户和用户组成功!'
852
+ else:
853
+ code = -1
854
+ msg = '设置 %s 的用户和用户组时失败!' % path
855
+ break
856
+
857
+ self._finish_job(jobname, code, msg)
858
+
859
+ async def chmod(self, paths, perms, option):
860
+ jobname = 'chmod_%s' % ','.join(paths)
861
+ if not self._start_job(jobname):
862
+ return
863
+
864
+ self._update_job(jobname, 2, '正在设置权限...')
865
+
866
+ try:
867
+ perms = int(perms, 8)
868
+ except:
869
+ self._finish_job(jobname, -1, '权限值输入有误!')
870
+ return
871
+
872
+ for path in paths:
873
+ result = await shell.async_task(file.chmod, path, perms, option == '-R')
874
+ if result == True:
875
+ code = 0
876
+ msg = '权限修改成功!'
877
+ else:
878
+ code = -1
879
+ msg = f'修改 {path} 的权限时失败!'
880
+ break
881
+
882
+ self._finish_job(jobname, code, msg)
883
+
884
+ async def wget(self, url, path):
885
+ jobname = 'wget_%s' % tornado.escape.url_escape(url)
886
+ if not self._start_job(jobname):
887
+ return
888
+
889
+ self._update_job(jobname, 2, f'正在下载 {url}...')
890
+
891
+ if Path(path).is_dir():
892
+ cmd = 'wget -q "%s" --directory-prefix=%s' % (quote(url), quote(path))
893
+ else:
894
+ cmd = 'wget -q "%s" -O %s' % (quote(url), quote(path))
895
+ result, output = await shell.async_command(cmd)
896
+ if result == 0:
897
+ code = 0
898
+ msg = '下载成功!'
899
+ else:
900
+ code = -1
901
+ msg = '下载失败!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
902
+
903
+ self._finish_job(jobname, code, msg)
904
+
905
+ async def mysql_fupdatepwd(self, password):
906
+ jobname = 'mysql_fupdatepwd'
907
+ if not self._start_job(jobname):
908
+ return
909
+
910
+ self._update_job(jobname, 2, '正在检测 MySQL 服务状态...')
911
+ cmd = 'service mysqld status'
912
+ result, output = await shell.async_command(cmd)
913
+ isstopped = 'stopped' in output
914
+
915
+ if not isstopped:
916
+ self._update_job(jobname, 2, '正在停止 MySQL 服务...')
917
+ cmd = 'service mysqld stop'
918
+ result, output = await shell.async_command(cmd)
919
+ if result != 0:
920
+ self._finish_job(jobname, -1, '停止 MySQL 服务时出错!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>'))
921
+ return
922
+
923
+ self._update_job(jobname, 2, '正在启用 MySQL 恢复模式...')
924
+ manually = False
925
+ cmd = 'service mysqld startsos'
926
+ result, output = await shell.async_command(cmd)
927
+ if result != 0:
928
+ manually = True
929
+ cmd = 'mysqld_safe --skip-grant-tables --skip-networking'
930
+ p = Popen(cmd, stdout=PIPE, stderr=STDOUT, close_fds=True, shell=True)
931
+ if not p:
932
+ self._finish_job(jobname, -1, '启用 MySQL 恢复模式时出错!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>'))
933
+ return
934
+
935
+ if manually:
936
+ time.sleep(2)
937
+
938
+ error = False
939
+ self._update_job(jobname, 2, '正在强制重置 root 密码...')
940
+ if not mysql.fupdatepwd(password):
941
+ error = True
942
+
943
+ if manually:
944
+ result = await shell.async_task(mysql.shutdown, password)
945
+ if result:
946
+ self._update_job(jobname, 0, '成功停止 MySQL 服务!')
947
+ else:
948
+ self._update_job(jobname, -1, '停止 MySQL 服务失败!')
949
+ p.terminate()
950
+ p.wait()
951
+
952
+ msg = ''
953
+ if not isstopped:
954
+ if error:
955
+ msg = '重置 root 密码时发生错误!正在重启 MySQL 服务...'
956
+ self._update_job(jobname, -1, msg)
957
+ else:
958
+ self._update_job(jobname, 2, '正在重启 MySQL 服务...')
959
+ if manually:
960
+ cmd = 'service mysqld start'
961
+ else:
962
+ cmd = 'service mysqld restart'
963
+ else:
964
+ if error:
965
+ msg = '重置 root 密码时发生错误!正在停止 MySQL 服务...'
966
+ self._update_job(jobname, -1, msg)
967
+ else:
968
+ self._update_job(jobname, 2, '正在停止 MySQL 服务...')
969
+ if manually:
970
+ cmd = ''
971
+ else:
972
+ cmd = 'service mysqld stop'
973
+
974
+ if not cmd:
975
+ if error:
976
+ code = -1
977
+ msg = '%sOK' % msg
978
+ else:
979
+ code = 0
980
+ msg = 'root 密码重置成功!'
981
+ else:
982
+ result, output = await shell.async_command(cmd)
983
+ if result == 0:
984
+ if error:
985
+ code = -1
986
+ msg = '%sOK' % msg
987
+ else:
988
+ code = 0
989
+ msg = 'root 密码重置成功!'
990
+ else:
991
+ if error:
992
+ code = -1
993
+ msg = '%sOK' % msg
994
+ else:
995
+ code = -1
996
+ msg = 'root 密码重置成功,但在操作服务时出错!<p style="margin:10px">%s</p>' % output.strip().replace('\n', '<br>')
997
+
998
+ self._finish_job(jobname, code, msg)
999
+
1000
+ async def mysql_databases(self, password):
1001
+ jobname = 'mysql_databases'
1002
+ if not self._start_job(jobname):
1003
+ return
1004
+
1005
+ self._update_job(jobname, 2, '正在获取数据库列表...')
1006
+ dbs = []
1007
+ dbs = await shell.async_task(mysql.show_databases, password)
1008
+ if dbs:
1009
+ code = 0
1010
+ msg = '获取数据库列表成功!'
1011
+ else:
1012
+ code = -1
1013
+ msg = '获取数据库列表失败!'
1014
+
1015
+ self._finish_job(jobname, code, msg, dbs)
1016
+
1017
+ async def mysql_users(self, password, dbname=None):
1018
+ if not dbname:
1019
+ jobname = 'mysql_users'
1020
+ else:
1021
+ jobname = 'mysql_users_%s' % dbname
1022
+ if not self._start_job(jobname):
1023
+ return
1024
+
1025
+ if not dbname:
1026
+ self._update_job(jobname, 2, '正在获取用户列表...')
1027
+ else:
1028
+ self._update_job(jobname, 2, f'正在获取数据库 {dbname} 的用户列表...')
1029
+
1030
+ users = []
1031
+ users = await shell.async_task(mysql.show_users, password, dbname)
1032
+ if users:
1033
+ code = 0
1034
+ msg = '获取用户列表成功!'
1035
+ else:
1036
+ code = -1
1037
+ msg = '获取用户列表失败!'
1038
+
1039
+ self._finish_job(jobname, code, msg, users)
1040
+
1041
+ async def mysql_dbinfo(self, password, dbname):
1042
+ jobname = 'mysql_dbinfo_%s' % dbname
1043
+ if not self._start_job(jobname):
1044
+ return
1045
+
1046
+ self._update_job(jobname, 2, '正在获取数据库 %s 的信息...' % dbname)
1047
+ dbinfo = False
1048
+ dbinfo = await shell.async_task(mysql.show_database, password, dbname)
1049
+ if dbinfo:
1050
+ code = 0
1051
+ msg = '获取数据库 %s 的信息成功!' % dbname
1052
+ else:
1053
+ code = -1
1054
+ msg = '获取数据库 %s 的信息失败!' % dbname
1055
+
1056
+ self._finish_job(jobname, code, msg, dbinfo)
1057
+
1058
+ async def mysql_rename(self, password, dbname, newname):
1059
+ jobname = 'mysql_rename_%s' % dbname
1060
+ if not self._start_job(jobname):
1061
+ return
1062
+
1063
+ self._update_job(jobname, 2, f'正在重命名 {dbname}...')
1064
+ result = await shell.async_task(mysql.rename_database, password, dbname, newname)
1065
+ if result == True:
1066
+ code = 0
1067
+ msg = f'{dbname} 重命名成功!'
1068
+ else:
1069
+ code = -1
1070
+ msg = f'{dbname} 重命名失败!'
1071
+
1072
+ self._finish_job(jobname, code, msg)
1073
+
1074
+ async def mysql_create(self, password, dbname, collation):
1075
+ jobname = f'mysql_create_{dbname}'
1076
+ if not self._start_job(jobname):
1077
+ return
1078
+
1079
+ self._update_job(jobname, 2, f'正在创建 {dbname}...')
1080
+ result = await shell.async_task(mysql.create_database, password, dbname, collation=collation)
1081
+ if result == True:
1082
+ code = 0
1083
+ msg = f'{dbname} 创建成功!'
1084
+ else:
1085
+ code = -1
1086
+ msg = f'{dbname} 创建失败!'
1087
+
1088
+ self._finish_job(jobname, code, msg)
1089
+
1090
+ async def mysql_export(self, password, dbname, path):
1091
+ jobname = f'mysql_export_{dbname}'
1092
+ if not self._start_job(jobname):
1093
+ return
1094
+
1095
+ self._update_job(jobname, 2, f'正在导出 {dbname}...')
1096
+ result = await shell.async_task(mysql.export_database, password, dbname, path)
1097
+ if result == True:
1098
+ code = 0
1099
+ msg = f'{dbname} 导出成功!'
1100
+ else:
1101
+ code = -1
1102
+ msg = f'{dbname} 导出失败!'
1103
+
1104
+ self._finish_job(jobname, code, msg)
1105
+
1106
+ async def mysql_drop(self, password, dbname):
1107
+ jobname = f'mysql_drop_{dbname}'
1108
+ if not self._start_job(jobname):
1109
+ return
1110
+
1111
+ self._update_job(jobname, 2, f'正在删除 {dbname}...')
1112
+ result = await shell.async_task(mysql.drop_database, password, dbname)
1113
+ if result == True:
1114
+ code = 0
1115
+ msg = f'{dbname} 删除成功!'
1116
+ else:
1117
+ code = -1
1118
+ msg = f'{dbname} 删除失败!'
1119
+
1120
+ self._finish_job(jobname, code, msg)
1121
+
1122
+ async def mysql_createuser(self, password, user, host, pwd=None):
1123
+ username = f'{user}@{host}'
1124
+ jobname = f'mysql_createuser_{username}'
1125
+ if not self._start_job(jobname):
1126
+ return
1127
+
1128
+ self._update_job(jobname, 2, f'正在添加用户 {username}...')
1129
+ result = await shell.async_task(mysql.create_user, password, user, host, pwd)
1130
+ if result is True:
1131
+ code = 0
1132
+ msg = f'用户 {username} 添加成功!'
1133
+ else:
1134
+ code = -1
1135
+ msg = f'用户 {username} 添加失败!'
1136
+
1137
+ self._finish_job(jobname, code, msg)
1138
+
1139
+ async def mysql_userprivs(self, password, user, host):
1140
+ username = f'{user}@{host}'
1141
+ jobname = f'mysql_userprivs_{username}'
1142
+ if not self._start_job(jobname):
1143
+ return
1144
+
1145
+ self._update_job(jobname, 2, f'正在获取用户 {username} 的权限...')
1146
+
1147
+ privs = {'global': {}, 'bydb': {}}
1148
+ globalprivs = await shell.async_task(mysql.show_user_globalprivs, password, user, host)
1149
+ if globalprivs != False:
1150
+ code = 0
1151
+ msg = f'获取用户 {username} 的全局权限成功!'
1152
+ privs['global'] = globalprivs
1153
+ else:
1154
+ code = -1
1155
+ msg = f'获取用户 {username} 的全局权限失败!'
1156
+ privs = False
1157
+
1158
+ if privs:
1159
+ dbprivs = await shell.async_task(mysql.show_user_dbprivs, password, user, host)
1160
+ if dbprivs != False:
1161
+ code = 0
1162
+ msg = f'获取用户 {username} 的数据库权限成功!'
1163
+ privs['bydb'] = dbprivs
1164
+ else:
1165
+ code = -1
1166
+ msg = f'获取用户 {username} 的数据库权限失败!'
1167
+ privs = False
1168
+
1169
+ self._finish_job(jobname, code, msg, privs)
1170
+
1171
+ async def mysql_updateuserprivs(self, password, user, host, privs, dbname=None):
1172
+ username = f'{user}@{host}'
1173
+ if dbname:
1174
+ jobname = f'mysql_updateuserprivs_{username}_{dbname}'
1175
+ else:
1176
+ jobname = f'mysql_updateuserprivs_{username}'
1177
+ if not self._start_job(jobname):
1178
+ return
1179
+
1180
+ if dbname:
1181
+ self._update_job(jobname, 2, f'正在更新用户 {username} 在数据库 {dbname} 中的权限...')
1182
+ else:
1183
+ self._update_job(jobname, 2, f'正在更新用户 {username} 的权限...')
1184
+
1185
+ rt = await shell.async_task(mysql.update_user_privs, password, user, host, privs, dbname)
1186
+ if rt != False:
1187
+ code = 0
1188
+ msg = f'用户 {username} 的权限更新成功!'
1189
+ else:
1190
+ code = -1
1191
+ msg = f'用户 {username} 的权限更新失败!'
1192
+
1193
+ self._finish_job(jobname, code, msg)
1194
+
1195
+ async def mysql_setuserpassword(self, password, user, host, pwd):
1196
+ username = f'{user}@{host}'
1197
+ jobname = f'mysql_setuserpassword_{username}'
1198
+ if not self._start_job(jobname):
1199
+ return
1200
+
1201
+ self._update_job(jobname, 2, f'正在更新用户 {username} 的密码...')
1202
+
1203
+ rt = await shell.async_task(mysql.set_user_password, password, user, host, pwd)
1204
+ if rt != False:
1205
+ code = 0
1206
+ msg = f'用户 {username} 的密码更新成功 !'
1207
+ else:
1208
+ code = -1
1209
+ msg = f'用户 {username} 的密码更新失败 !'
1210
+
1211
+ self._finish_job(jobname, code, msg)
1212
+
1213
+ async def mysql_dropuser(self, password, user, host):
1214
+ username = f'{user}@{host}'
1215
+ jobname = f'mysql_dropuser_{username}'
1216
+ if not self._start_job(jobname):
1217
+ return
1218
+
1219
+ self._update_job(jobname, 2, f'正在删除用户 {username}...')
1220
+
1221
+ rt = await shell.async_task(mysql.drop_user, password, user, host)
1222
+ if rt != False:
1223
+ code = 0
1224
+ msg = f'用户 {username} 删除成功 !'
1225
+ else:
1226
+ code = -1
1227
+ msg = f'用户 {username} 删除失败 !'
1228
+
1229
+ self._finish_job(jobname, code, msg)
1230
+
1231
+ async def ssh_genkey(self, path, password=''):
1232
+ jobname = 'ssh_genkey'
1233
+ if not self._start_job(jobname):
1234
+ return
1235
+
1236
+ self._update_job(jobname, 2, '正在生成密钥对...')
1237
+
1238
+ rt = await shell.async_task(ssh.genkey, path, password)
1239
+ if rt != False:
1240
+ code = 0
1241
+ msg = '密钥对生成成功 !'
1242
+ else:
1243
+ code = -1
1244
+ msg = '密钥对生成失败 !'
1245
+
1246
+ self._finish_job(jobname, code, msg)
1247
+
1248
+ async def ssh_chpasswd(self, path, oldpassword, newpassword=''):
1249
+ jobname = 'ssh_chpasswd'
1250
+ if not self._start_job(jobname):
1251
+ return
1252
+
1253
+ self._update_job(jobname, 2, '正在修改私钥密码...')
1254
+
1255
+ rt = await shell.async_task(ssh.chpasswd, path, oldpassword, newpassword)
1256
+ if rt != False:
1257
+ code = 0
1258
+ msg = '私钥密码修改成功!'
1259
+ else:
1260
+ code = -1
1261
+ msg = '私钥密码修改失败!'
1262
+
1263
+ self._finish_job(jobname, code, msg)
1264
+
1265
+ async def inpanel_install(self, ssh_ip, ssh_port, ssh_user, ssh_password, instance_name, accessnet, accessport=None, accesskey=None):
1266
+ jobname = f'remote_inpanel_install_{ssh_ip}'
1267
+ if not self._start_job(jobname):
1268
+ return
1269
+
1270
+ self._update_job(jobname, 2, f'正在将 InPanel 安装到 {ssh_ip}...')
1271
+
1272
+ result = await shell.async_task(remote.inpanel_install, ssh_ip, ssh_port, ssh_user, ssh_password, accesskey=accesskey, inpanel_port=accessport)
1273
+ if result == True:
1274
+ code = 0
1275
+ msg = 'InPanel 安装成功!'
1276
+ self.config.set('inpanel', instance_name, f'{accesskey}|{accessnet}|{accessport}')
1277
+ else:
1278
+ code = -1
1279
+ msg = 'InPanel 安装过程中发生错误!'
1280
+
1281
+ self._finish_job(jobname, code, msg)
1282
+
1283
+ async def inpanel_uninstall(self, ssh_ip, ssh_port, ssh_user, ssh_password, instance_name):
1284
+ jobname = f'remote_inpanel_uninstall_{ssh_ip}'
1285
+ if not self._start_job(jobname):
1286
+ return
1287
+
1288
+ self._update_job(jobname, 2, f'正在卸载 {ssh_ip} 上的 InPanel...')
1289
+ result = await shell.async_task(remote.inpanel_uninstall, ssh_ip, ssh_port, ssh_user, ssh_password)
1290
+ if result == True:
1291
+ code = 0
1292
+ msg = 'InPanel 卸载成功!'
1293
+ try:
1294
+ self.config.remove_option('inpanel', instance_name)
1295
+ except:
1296
+ pass
1297
+ else:
1298
+ code = -1
1299
+ msg = 'InPanel 卸载过程中发生错误!'
1300
+
1301
+ self._finish_job(jobname, code, msg)
1302
+
1303
+ async def inpanel_config(self, ssh_ip, ssh_port, ssh_user, ssh_password, accesskey=None):
1304
+ jobname = f'remote_inpanel_config_{ssh_ip}'
1305
+ if not self._start_job(jobname):
1306
+ return
1307
+
1308
+ self._update_job(jobname, 2, f'正在更新 {ssh_ip} 上的 InPanel 配置...')
1309
+
1310
+ result = await shell.async_task(remote.inpanel_config, ssh_ip, ssh_port, ssh_user, ssh_password, accesskey=accesskey)
1311
+ if result == True:
1312
+ code = 0
1313
+ msg = 'InPanel 配置更新成功!'
1314
+ else:
1315
+ code = -1
1316
+ msg = 'InPanel 配置更新过程中发生错误!'
1317
+
1318
+ self._finish_job(jobname, code, msg)
1319
+
1320
+ async def uploadtoftp(self, address, account, password, source, target):
1321
+ jobname = f'upload_to_ftp_{address}_{source}_{target}'
1322
+ if not self._start_job(jobname):
1323
+ return
1324
+
1325
+ if not Path(source).exists():
1326
+ self._finish_job(jobname, -1, '传输失败!文件不存在')
1327
+ return
1328
+
1329
+ self._update_job(jobname, 2, f'正在传输文件 {source}...')
1330
+ result = await shell.async_task(ftp.uploadtoftp, address, account, password, source, target)
1331
+ if result == True:
1332
+ code = 0
1333
+ msg = f'文件 {source} 已成功传输到 {address} 服务器!'
1334
+ else:
1335
+ code = -1
1336
+ msg = '文件传输失败!'
1337
+ self._finish_job(jobname, code, msg)