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
File without changes
inpanel/mod/apt.py ADDED
@@ -0,0 +1,316 @@
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
+ '''Module for APT Repository Management'''
9
+
10
+ import os
11
+ import re
12
+ from pathlib import Path
13
+
14
+ from .. import base
15
+ from . import file
16
+ from ..apt import apt_sources
17
+
18
+ sources_list_path = '/etc/apt/sources.list'
19
+ sources_list_d_path = '/etc/apt/sources.list.d'
20
+
21
+
22
+ def get_list():
23
+ '''get sources list'''
24
+ res = []
25
+ if base.kernel_name in ('Linux'):
26
+ if Path(sources_list_path).exists():
27
+ res.append('sources.list')
28
+ if Path(sources_list_d_path).exists() and Path(sources_list_d_path).is_dir():
29
+ items = sorted(os.listdir(sources_list_d_path))
30
+ for item in items:
31
+ if item.endswith('.list'):
32
+ res.append('sources.list.d/' + item)
33
+ return res if len(res) > 0 else []
34
+ elif base.kernel_name in ('Darwin'):
35
+ return None
36
+ else:
37
+ return None
38
+
39
+
40
+ def item_exists(source):
41
+ '''check if source file exists'''
42
+ if source == 'sources.list':
43
+ return Path(sources_list_path).exists()
44
+ elif source.startswith('sources.list.d/'):
45
+ filename = source[len('sources.list.d/'):]
46
+ return Path(sources_list_d_path, filename).exists()
47
+ return False
48
+
49
+
50
+ def get_item(source):
51
+ '''get source config content'''
52
+ if not source:
53
+ return None
54
+
55
+ if source == 'sources.list':
56
+ filepath = sources_list_path
57
+ elif source.startswith('sources.list.d/'):
58
+ filename = source[len('sources.list.d/'):]
59
+ filepath = str(Path(sources_list_d_path) / filename)
60
+ else:
61
+ filepath = str(Path(sources_list_d_path) / source)
62
+
63
+ if Path(filepath).exists():
64
+ try:
65
+ with open(filepath, 'r', encoding='utf-8') as f:
66
+ content = f.read()
67
+ return {'content': content, 'path': filepath}
68
+ except Exception as e:
69
+ return None
70
+ return None
71
+
72
+
73
+ def set_item(source, data):
74
+ '''set source config content'''
75
+ if not source or not data:
76
+ return False
77
+
78
+ if source == 'sources.list':
79
+ filepath = sources_list_path
80
+ elif source.startswith('sources.list.d/'):
81
+ filename = source[len('sources.list.d/'):]
82
+ filepath = str(Path(sources_list_d_path) / filename)
83
+ else:
84
+ filepath = str(Path(sources_list_d_path) / source)
85
+
86
+ if Path(filepath).exists():
87
+ try:
88
+ with open(filepath, 'w', encoding='utf-8') as f:
89
+ f.write(data.get('content', ''))
90
+ return True
91
+ except Exception as e:
92
+ return False
93
+ else:
94
+ return False
95
+
96
+
97
+ def add_item(source, data):
98
+ '''add source config file'''
99
+ if not source or not data:
100
+ return False
101
+
102
+ if source == 'sources.list':
103
+ filepath = sources_list_path
104
+ if Path(filepath).exists():
105
+ return False
106
+ elif source.startswith('sources.list.d/'):
107
+ filename = source[len('sources.list.d/'):]
108
+ filepath = str(Path(sources_list_d_path) / filename)
109
+ else:
110
+ filepath = str(Path(sources_list_d_path) / source)
111
+
112
+ if Path(filepath).exists():
113
+ return False
114
+
115
+ try:
116
+ parent_dir = Path(filepath).parent
117
+ if not parent_dir.exists():
118
+ parent_dir.mkdir(parents=True, exist_ok=True)
119
+ with open(filepath, 'w', encoding='utf-8') as f:
120
+ f.write(data.get('content', ''))
121
+ return True
122
+ except Exception as e:
123
+ return False
124
+
125
+
126
+ def del_item(source):
127
+ '''delete source file'''
128
+ if not source:
129
+ return None
130
+
131
+ if source == 'sources.list':
132
+ filepath = sources_list_path
133
+ elif source.startswith('sources.list.d/'):
134
+ filename = source[len('sources.list.d/'):]
135
+ filepath = str(Path(sources_list_d_path) / filename)
136
+ else:
137
+ filepath = str(Path(sources_list_d_path) / source)
138
+
139
+ return file.delete(filepath)
140
+
141
+
142
+ def parse_sources(content):
143
+ '''parse sources.list content into structured data'''
144
+ sources = []
145
+ for line in content.split('\n'):
146
+ line = line.strip()
147
+ if not line or line.startswith('#'):
148
+ continue
149
+
150
+ match = re.match(r'^(deb|deb-src)\s+(\S+)\s+(\S+)(?:\s+(.+))?$', line)
151
+ if match:
152
+ source_type = match.group(1)
153
+ url = match.group(2)
154
+ distribution = match.group(3)
155
+ components = match.group(4).split() if match.group(4) else []
156
+
157
+ sources.append({
158
+ 'type': source_type,
159
+ 'url': url,
160
+ 'distribution': distribution,
161
+ 'components': components
162
+ })
163
+ return sources
164
+
165
+
166
+ def generate_sources(sources):
167
+ '''generate sources.list content from structured data'''
168
+ lines = []
169
+ for source in sources:
170
+ components_str = ' '.join(source.get('components', []))
171
+ if components_str:
172
+ line = f"{source['type']} {source['url']} {source['distribution']} {components_str}"
173
+ else:
174
+ line = f"{source['type']} {source['url']} {source['distribution']}"
175
+ lines.append(line)
176
+ return '\n'.join(lines) + '\n'
177
+
178
+
179
+ def get_repo_sury(version):
180
+ '''install sury php repository'''
181
+ cmds = []
182
+ cmds.append('apt-get install -y apt-transport-https lsb-release ca-certificates curl')
183
+ cmds.append('curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg')
184
+ cmds.append(f'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list')
185
+ cmds.append('apt-get update')
186
+ return cmds
187
+
188
+
189
+ def get_repo_nginx():
190
+ '''install nginx official repository'''
191
+ cmds = []
192
+ cmds.append('apt-get install -y apt-transport-https lsb-release ca-certificates curl')
193
+ cmds.append('curl -sSLo /usr/share/keyrings/nginx-archive-keyring.gpg https://nginx.org/keys/nginx_signing.key')
194
+ cmds.append('echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/debian/ $(lsb_release -sc) nginx" > /etc/apt/sources.list.d/nginx.list')
195
+ cmds.append('echo -e "Package: *\\nPin: origin nginx.org\\nPin: release o=nginx\\nPin-Priority: 900\\n" > /etc/apt/preferences.d/99nginx')
196
+ cmds.append('apt-get update')
197
+ return cmds
198
+
199
+
200
+ def get_repo_mariadb(mariadb_version):
201
+ '''install mariadb official repository'''
202
+ cmds = []
203
+ cmds.append('apt-get install -y apt-transport-https lsb-release ca-certificates curl')
204
+ cmds.append('curl -sSLo /usr/share/keyrings/mariadb-keyring.pgp https://mariadb.org/mariadb_release_signing_key.pgp')
205
+ cmds.append(f'echo "deb [signed-by=/usr/share/keyrings/mariadb-keyring.pgp] https://ftp.osuosl.org/pub/mariadb/repo/{mariadb_version}/debian $(lsb_release -sc) main" > /etc/apt/sources.list.d/mariadb.list')
206
+ cmds.append('apt-get update')
207
+ return cmds
208
+
209
+
210
+ def get_repo_nodesource(node_version):
211
+ '''install nodesource repository'''
212
+ cmds = []
213
+ cmds.append(f'curl -fsSL https://deb.nodesource.com/setup_{node_version}.x | bash -')
214
+ cmds.append('apt-get update')
215
+ return cmds
216
+
217
+
218
+ def get_repo_elastic(stack_version):
219
+ '''install elastic repository'''
220
+ cmds = []
221
+ cmds.append('apt-get install -y apt-transport-https lsb-release ca-certificates curl')
222
+ cmds.append('curl -sSLo /usr/share/keyrings/elasticsearch-keyring.gpg https://artifacts.elastic.co/GPG-KEY-elasticsearch')
223
+ cmds.append(f'echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/{stack_version}.x/apt stable main" > /etc/apt/sources.list.d/elastic.list')
224
+ cmds.append('apt-get update')
225
+ return cmds
226
+
227
+
228
+ def web_handler(context):
229
+ '''Handle web requests for APT repository management'''
230
+ action = context.get('action', '')
231
+ source = context.get('source', '')
232
+
233
+ if action == 'list':
234
+ items = get_list()
235
+ if items is None:
236
+ return {'code': -1, 'msg': '获取配置失败!'}
237
+ else:
238
+ return {'code': 0, 'msg': '', 'data': items}
239
+
240
+ elif action == 'item':
241
+ if not source:
242
+ return {'code': -1, 'msg': '配置文件不能为空!'}
243
+ data = get_item(source)
244
+ if data is None:
245
+ return {'code': -1, 'msg': '配置文件不存在!'}
246
+ else:
247
+ return {'code': 0, 'msg': '', 'data': data}
248
+
249
+ elif action == 'add':
250
+ if not source:
251
+ return {'code': -1, 'msg': '配置文件不能为空!'}
252
+
253
+ content = context.get('content', '')
254
+ if not content:
255
+ return {'code': -1, 'msg': '配置内容不能为空!'}
256
+
257
+ data = {'content': content}
258
+
259
+ if item_exists(source):
260
+ return {'code': -1, 'msg': '配置文件已存在!'}
261
+ if add_item(source, data) is True:
262
+ return {'code': 0, 'msg': '配置添加成功!'}
263
+ else:
264
+ return {'code': -1, 'msg': '配置添加失败!'}
265
+
266
+ elif action == 'edit':
267
+ if not source:
268
+ return {'code': -1, 'msg': '配置文件不能为空!'}
269
+
270
+ content = context.get('content', '')
271
+ if not content:
272
+ return {'code': -1, 'msg': '配置内容不能为空!'}
273
+
274
+ data = {'content': content}
275
+
276
+ if not item_exists(source):
277
+ return {'code': -1, 'msg': '配置文件不存在!'}
278
+ if set_item(source, data) is True:
279
+ return {'code': 0, 'msg': '配置修改成功!'}
280
+ else:
281
+ return {'code': -1, 'msg': '配置修改失败!'}
282
+
283
+ elif action == 'del':
284
+ if not source:
285
+ return {'code': -1, 'msg': '配置文件不能为空!'}
286
+ if not item_exists(source):
287
+ return {'code': -1, 'msg': '配置文件不存在!'}
288
+ if del_item(source) is True:
289
+ return {'code': 0, 'msg': '配置文件已移入回收站!'}
290
+ else:
291
+ return {'code': -1, 'msg': '删除失败!'}
292
+
293
+ elif action == 'parse':
294
+ content = context.get('content', '')
295
+ if not content:
296
+ return {'code': -1, 'msg': '内容不能为空!'}
297
+ sources = parse_sources(content)
298
+ return {'code': 0, 'msg': '', 'data': sources}
299
+
300
+ elif action == 'generate':
301
+ sources = context.get('sources', [])
302
+ content = generate_sources(sources)
303
+ return {'code': 0, 'msg': '', 'data': {'content': content}}
304
+
305
+ else:
306
+ return {'code': -1, 'msg': '未定义的操作!'}
307
+
308
+
309
+ if __name__ == '__main__':
310
+ l = get_list()
311
+ print(l)
312
+ if l:
313
+ i = l[0]
314
+ print(i)
315
+ c = get_item(i)
316
+ print(c)
@@ -0,0 +1,268 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (c) 2017-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 certificate Management.'''
10
+
11
+ from pathlib import Path
12
+ from shutil import copy
13
+ from subprocess import PIPE, Popen
14
+
15
+ from . import file
16
+ import os
17
+ from ..acme import ACME
18
+
19
+
20
+ class Certificate():
21
+
22
+ def __init__(self):
23
+ # self.path_current = os.path.dirname(os.path.abspath(__file__))
24
+ self.path_home = '/etc/inpanel/certs/'
25
+ self.path_acc = str(Path(self.path_home) / 'client.key')
26
+ self.path_crt = str(Path(self.path_home) / 'crt')
27
+ self.path_key = str(Path(self.path_home) / 'key')
28
+ self.path_csr = str(Path(self.path_home) / 'csr')
29
+ self.key_size = '4096'
30
+ self.acme = None
31
+
32
+ if not Path(self.path_home).exists():
33
+ Path(self.path_home).mkdir(parents=True, exist_ok=True)
34
+ if not Path(self.path_crt).exists():
35
+ Path(self.path_crt).mkdir(parents=True, exist_ok=True)
36
+ if not Path(self.path_key).exists():
37
+ Path(self.path_key).mkdir(parents=True, exist_ok=True)
38
+ if not Path(self.path_csr).exists():
39
+ Path(self.path_csr).mkdir(parents=True, exist_ok=True)
40
+
41
+ self.init_account()
42
+
43
+ def _cmd(self, cmd_list, stdin=None, cmd_input=None, err_msg="Command Line Error"):
44
+ proc = Popen(cmd_list, stdin=stdin, stdout=PIPE, stderr=PIPE)
45
+ out, err = proc.communicate(cmd_input)
46
+ out = out if out else err
47
+ if proc.returncode != 0:
48
+ raise IOError("{0}\n{1}".format(err_msg, err))
49
+ return out.decode('utf8')
50
+
51
+ def _check_key(self, key):
52
+ '''check the RSA key is ok'''
53
+ if not Path(key).exists():
54
+ return {'code': -1, 'msg': 'key_not_found'}
55
+ try:
56
+ out = self._cmd(['openssl', 'rsa', '-in', key, '-check', '-noout'])
57
+ if 'RSA key ok' in out:
58
+ return {'code': 0, 'msg': 'key_ok', 'data': 'ok'}
59
+ else:
60
+ return {'code': 0, 'msg': 'key_broken'}
61
+ except:
62
+ return {'code': -1, 'msg': 'key_check_error'}
63
+
64
+ def _check_csr(self, csr):
65
+ '''verify the CSR is ok'''
66
+ csr_path = Path(csr)
67
+ if not csr_path.exists() or not csr_path.is_file():
68
+ csr = str(Path(self.path_csr) / csr)
69
+ if not Path(csr).exists():
70
+ return {'code': -1, 'msg': 'csr_not_found'}
71
+ try:
72
+ out = self._cmd(
73
+ ['openssl', 'req', '-in', csr, '-verify', '-noout'])
74
+ if 'verify OK' in out:
75
+ return {'code': 0, 'msg': 'verify_success'}
76
+ else:
77
+ return {'code': -1, 'msg': 'verify_error'}
78
+ except:
79
+ return {'code': -1, 'msg': 'verify_error'}
80
+
81
+ def _generate_private_key(self, file_key=None, forced=False):
82
+ '''Generate private key'''
83
+ if file_key == None or file_key == '':
84
+ return {'code': -1, 'msg': 'need_key_name'}
85
+ ckk = self._check_key(file_key)
86
+ if ckk['code'] == 0 and ckk['data'] == 'ok' and forced == False:
87
+ return {'code': -1, 'msg': 'key_exists'}
88
+ try:
89
+ cmd_ = ['openssl', 'genrsa', '-out', file_key, self.key_size]
90
+ out = self._cmd(cmd_, err_msg='Generate private key error')
91
+ if out is None:
92
+ return {'code': -1, 'msg': 'key_generate_error'}
93
+ else:
94
+ return {'code': 0, 'msg': 'key_generate_success'}
95
+ except:
96
+ return {'code': -1, 'msg': 'key_generate_error'}
97
+
98
+ def init_account(self, file_key=None, forced=False):
99
+ '''Create a Let's Encrypt account private key'''
100
+ if file_key == None or file_key == '' or not file_key:
101
+ file_key = self.path_acc
102
+ return self._generate_private_key(file_key=file_key, forced=forced)
103
+
104
+ def create_domain_key(self, domain, forced=False):
105
+ '''Create a domain private key'''
106
+ key = str(Path(self.path_key) / (domain + '.key'))
107
+ return self._generate_private_key(file_key=key, forced=forced)
108
+
109
+ def generate_domain_csr(self, domain=[], custom_key=None, forced=False):
110
+ '''Generate a certificate signing request (CSR) for domains.'''
111
+ # openssl genrsa 4096 > github.com.key
112
+ if domain is None or len(domain) == 0:
113
+ return {'code': -1, 'msg': 'domain_error'}
114
+ if custom_key and Path(custom_key).is_file():
115
+ k = custom_key
116
+ else:
117
+ k = str(Path(self.path_key) / (domain[0] + '.key'))
118
+ if not Path(k).is_file():
119
+ return {'code': -1, 'msg': 'key_not_found'}
120
+ ckk = self._check_key(k)
121
+ if ckk['code'] == 0 and ckk['msg'] == 'key_broken':
122
+ return {'code': -1, 'msg': 'key_broken'}
123
+ c = str(Path(self.path_csr) / (domain[0] + '.csr'))
124
+ if Path(c).is_file() and forced == False:
125
+ return {'code': -1, 'msg': 'csr_exists'}
126
+
127
+ subj = '/CN=%s' % domain[0]
128
+ print(subj)
129
+ cmd = ['openssl', 'req', '-new', '-sha256', '-key', k, '-subj', subj]
130
+ conf_tmp = None
131
+ if len(domain) > 1:
132
+ san = ['DNS:%s' % domain[0]]
133
+ for item in domain[1:]:
134
+ san.append('DNS:%s' % item)
135
+ san = 'subjectAltName=%s' % (','.join(san))
136
+ opssl_conf = '/etc/pki/tls/openssl.cnf'
137
+ conf_tmp = str(Path(self.path_home) / Path(opssl_conf).name)
138
+ copy(opssl_conf, conf_tmp)
139
+ with open(conf_tmp, 'a', encoding='utf-8') as f:
140
+ f.writelines(['\n[SAN]', '\n%s' % san])
141
+ # config = '<(cat %s <(printf "[SAN]\\n%s"))' % (opssl_conf, san)
142
+ cmd.extend(['-reqexts', 'SAN', '-config', conf_tmp])
143
+ out = self._cmd(cmd, err_msg="Create csr error")
144
+ if conf_tmp is not None and Path(conf_tmp).exists():
145
+ os.remove(conf_tmp)
146
+ if out is None:
147
+ return {'code': -1, 'msg': 'csr_create_error'}
148
+ else:
149
+ with open(c, 'w', encoding='utf-8') as f:
150
+ f.write(out)
151
+ return {'code': 0, 'msg': 'csr_create_success', 'data': out}
152
+
153
+ def show_domain_csr(self, domain_csr=None, text=True, pubkey=False, subject=False):
154
+ if domain_csr and Path(domain_csr).exists() and Path(domain_csr).is_file():
155
+ csr = domain_csr
156
+ else:
157
+ csr = str(Path(self.path_csr) / domain_csr)
158
+ if not Path(csr).exists():
159
+ return {'code': -1, 'msg': 'csr_not_found'}
160
+
161
+ ckcsr = self._check_csr(csr)
162
+ if not ckcsr['code'] == 0:
163
+ return ckcsr
164
+ try:
165
+ cmd_ = ['openssl', 'req', '-in', csr]
166
+ if text == True and pubkey is not True and subject is not True:
167
+ cmd_.append('-text')
168
+ if pubkey is True:
169
+ cmd_.extend(['-noout', '-pubkey'])
170
+ if subject is True:
171
+ cmd_.extend(['-noout', '-subject'])
172
+ out = self._cmd(cmd_, err_msg='Generate private key error')
173
+ if out is None:
174
+ return {'code': -1, 'msg': 'csr_read_error'}
175
+ else:
176
+ return {'code': 0, 'msg': 'csr_read_success', 'data': out}
177
+ except:
178
+ return {'code': -1, 'msg': 'csr_read_error'}
179
+
180
+ def generate_domain_crt(self, domain):
181
+ '''getting a signed TLS certificate from Let's Encrypt'''
182
+ if domain is None:
183
+ return None
184
+ acc = self.path_acc
185
+ csr = str(Path(self.path_csr) / (domain + '.csr'))
186
+ crt = str(Path(self.path_crt) / (domain + '.crt'))
187
+ ckdir = '/var/www/%s/.well-known/acme-challenge' % domain
188
+ print(acc, csr, crt, ckdir)
189
+ if not Path(ckdir).exists():
190
+ Path(ckdir).mkdir(parents=True, exist_ok=True)
191
+ self.acme = ACME(acc, csr, ckdir)
192
+ signed_crt = self.acme.get_certificate()
193
+ if signed_crt is not None:
194
+ with open(crt, 'w', encoding='utf-8') as f:
195
+ f.write(signed_crt)
196
+
197
+ def revoke_domain_cert(self, domain=''):
198
+ print(domain)
199
+
200
+ def get_keys_list(self):
201
+ items = file.listfile(self.path_key)
202
+ if items is None:
203
+ return None
204
+ res = []
205
+ for i, item in enumerate(items):
206
+ path_item = Path(item)
207
+ if path_item.suffix == '.key':
208
+ res.append({
209
+ 'name': item,
210
+ 'ans': path_item.stem,
211
+ 'ext': path_item.suffix,
212
+ 'id': 'ididid',
213
+ 'size': self.key_size
214
+ })
215
+ return res
216
+
217
+ def get_crts_list(self):
218
+ items = file.listfile(self.path_crt)
219
+ if items is None:
220
+ return None
221
+ res = []
222
+ for i, item in enumerate(items):
223
+ path_item = Path(item)
224
+ if path_item.suffix == '.crt':
225
+ res.append({
226
+ 'name': item,
227
+ 'ans': path_item.stem,
228
+ 'ext': path_item.suffix,
229
+ 'id': 'ididid',
230
+ 'size': self.key_size
231
+ })
232
+ return res
233
+
234
+ def get_csrs_list(self):
235
+ items = file.listfile(self.path_csr)
236
+ if items is None:
237
+ return None
238
+ res = []
239
+ for i, item in enumerate(items):
240
+ path_item = Path(item)
241
+ if path_item.suffix == '.csr':
242
+ res.append({
243
+ 'name': item,
244
+ 'ext': path_item.suffix,
245
+ 'created': '2019-01-24',
246
+ 'size': self.key_size,
247
+ 'description': '说明'
248
+ })
249
+ return res
250
+
251
+ def get_host_list(self):
252
+ res = None
253
+ res = [{
254
+ 'domain': 'baokan.pub',
255
+ 'id': '9a6d6_7f1c1_e1fd1b154418d4d88d32153bec4b20ac',
256
+ 'size': 2048
257
+ }, {
258
+ 'domain': 'zhoubao.pub',
259
+ 'id': '9a6d6_7f1c1_e1fd1bfgj418d4d45632153bec4b20ac',
260
+ 'size': 2048
261
+ }]
262
+ return res
263
+
264
+ def get_config(self):
265
+ return dict()
266
+
267
+ def set_config(self):
268
+ return dict()