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.
- inpanel/__init__.py +29 -0
- inpanel/acme.py +289 -0
- inpanel/aliyuncs.py +269 -0
- inpanel/app.py +514 -0
- inpanel/apt.py +223 -0
- inpanel/base.py +200 -0
- inpanel/config.py +139 -0
- inpanel/data/config.ini +29 -0
- inpanel/data/history +5 -0
- inpanel/data/runlogs.ini +12 -0
- inpanel/data/update_info.ini +4 -0
- inpanel/dnf.py +225 -0
- inpanel/lib/__init__.py +9 -0
- inpanel/lib/async_process.py +98 -0
- inpanel/lib/filelock.py +82 -0
- inpanel/lib/magic.py +233 -0
- inpanel/lib/pxssh.py +310 -0
- inpanel/lib/pyDes.py +867 -0
- inpanel/mod/__init__.py +0 -0
- inpanel/mod/apt.py +316 -0
- inpanel/mod/certificate.py +268 -0
- inpanel/mod/config.py +372 -0
- inpanel/mod/cron.py +315 -0
- inpanel/mod/disk.py +322 -0
- inpanel/mod/dnf.py +239 -0
- inpanel/mod/file.py +722 -0
- inpanel/mod/firewall.py +199 -0
- inpanel/mod/firewall_base.py +137 -0
- inpanel/mod/firewall_firewalld.py +170 -0
- inpanel/mod/firewall_iptables.py +176 -0
- inpanel/mod/firewall_macos.py +213 -0
- inpanel/mod/firewall_ufw.py +128 -0
- inpanel/mod/ftp.py +231 -0
- inpanel/mod/httpd.py +1205 -0
- inpanel/mod/lighttpd.py +26 -0
- inpanel/mod/login.py +98 -0
- inpanel/mod/mysql.py +577 -0
- inpanel/mod/named.py +26 -0
- inpanel/mod/nginx.py +2327 -0
- inpanel/mod/package.py +33 -0
- inpanel/mod/package_apt.py +62 -0
- inpanel/mod/package_base.py +77 -0
- inpanel/mod/package_dnf.py +62 -0
- inpanel/mod/package_map.py +366 -0
- inpanel/mod/package_yum.py +64 -0
- inpanel/mod/php.py +289 -0
- inpanel/mod/process.py +291 -0
- inpanel/mod/proftpd.py +26 -0
- inpanel/mod/pureftpd.py +26 -0
- inpanel/mod/query.py +108 -0
- inpanel/mod/server.py +1319 -0
- inpanel/mod/service.py +262 -0
- inpanel/mod/setting.py +228 -0
- inpanel/mod/shell.py +78 -0
- inpanel/mod/ssh.py +280 -0
- inpanel/mod/system.py +220 -0
- inpanel/mod/task.py +1337 -0
- inpanel/mod/ufw.py +14 -0
- inpanel/mod/user.py +306 -0
- inpanel/mod/vsftpd.py +67 -0
- inpanel/mod/yum.py +243 -0
- inpanel/plugins/__init__.py +10 -0
- inpanel/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
- inpanel/plugins/acme/static/index.html +10 -0
- inpanel/plugins/acme/static/index.js +13 -0
- inpanel/plugins/shadowsocks-libev/__pycache__/info.cpython-313.pyc +0 -0
- inpanel/plugins/shadowsocks-libev/info.py +73 -0
- inpanel/plugins/shadowsocks-libev/static/index.html +70 -0
- inpanel/plugins/shadowsocks-libev/static/index.js +18 -0
- inpanel/public/css/global.css +138 -0
- inpanel/public/favicon.ico +0 -0
- inpanel/public/images/alipay.jpg +0 -0
- inpanel/public/images/loading.gif +0 -0
- inpanel/public/images/weixin.jpg +0 -0
- inpanel/public/index.html +94 -0
- inpanel/public/js/controllers/controllers.js +566 -0
- inpanel/public/js/controllers/database.js +590 -0
- inpanel/public/js/controllers/ecs.js +624 -0
- inpanel/public/js/controllers/file.js +940 -0
- inpanel/public/js/controllers/plugins.js +105 -0
- inpanel/public/js/controllers/service.js +1460 -0
- inpanel/public/js/controllers/site.js +1241 -0
- inpanel/public/js/controllers/utils.js +2319 -0
- inpanel/public/js/core.js +221 -0
- inpanel/public/js/directives.js +784 -0
- inpanel/public/js/filters.js +395 -0
- inpanel/public/js/services.js +257 -0
- inpanel/public/lib/angular/1.0.2/angular-resource.min.js +10 -0
- inpanel/public/lib/angular/1.0.2/angular.min.js +158 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.css +587 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css +6 -0
- inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap.css +6757 -0
- inpanel/public/lib/bootstrap/css/bootstrap.css.map +1 -0
- inpanel/public/lib/bootstrap/css/bootstrap.min.css +6 -0
- inpanel/public/lib/bootstrap/css/bootstrap.min.css.map +1 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- inpanel/public/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
- inpanel/public/lib/bootstrap/img/glyphicons-halflings.png +0 -0
- inpanel/public/lib/bootstrap/js/bootstrap.js +2377 -0
- inpanel/public/lib/bootstrap/js/bootstrap.min.js +7 -0
- inpanel/public/lib/bootstrap/js/npm.js +13 -0
- inpanel/public/lib/codemirror/codemirror-compressed.js +1 -0
- inpanel/public/lib/codemirror/codemirror.css +173 -0
- inpanel/public/lib/jquery/jquery-1.12.4.min.js +5 -0
- inpanel/public/lib/md5/md5.js +379 -0
- inpanel/public/lib/md5/md5.min.js +1 -0
- inpanel/public/partials/database/index.html +172 -0
- inpanel/public/partials/database/mysql/dbedit.html +462 -0
- inpanel/public/partials/database/mysql/dbnew.html +348 -0
- inpanel/public/partials/database/mysql/useredit.html +310 -0
- inpanel/public/partials/database/mysql/usernew.html +97 -0
- inpanel/public/partials/directives/message.html +20 -0
- inpanel/public/partials/directives/navbar.html +25 -0
- inpanel/public/partials/directives/selector.html +57 -0
- inpanel/public/partials/directives/srvbase.html +72 -0
- inpanel/public/partials/directives/srvext.html +34 -0
- inpanel/public/partials/directives/srvfile.html +23 -0
- inpanel/public/partials/directives/srvinstall.html +34 -0
- inpanel/public/partials/directives/srvlog.html +23 -0
- inpanel/public/partials/directives/srvminiop.html +28 -0
- inpanel/public/partials/directives/srvuninstall.html +38 -0
- inpanel/public/partials/directives/srvupdate.html +30 -0
- inpanel/public/partials/ecs/account.html +156 -0
- inpanel/public/partials/ecs/ecs.html +198 -0
- inpanel/public/partials/ecs/index.html +40 -0
- inpanel/public/partials/ecs/setings.html +459 -0
- inpanel/public/partials/file/file.html +501 -0
- inpanel/public/partials/file/trash.html +95 -0
- inpanel/public/partials/ftp.html +36 -0
- inpanel/public/partials/log.html +83 -0
- inpanel/public/partials/login.html +79 -0
- inpanel/public/partials/logout.html +22 -0
- inpanel/public/partials/main.html +534 -0
- inpanel/public/partials/plugins/index.html +55 -0
- inpanel/public/partials/plugins/plugins.html +13 -0
- inpanel/public/partials/secure.html +13 -0
- inpanel/public/partials/service/apache.html +147 -0
- inpanel/public/partials/service/cron.html +71 -0
- inpanel/public/partials/service/index.html +293 -0
- inpanel/public/partials/service/iptables.html +40 -0
- inpanel/public/partials/service/lighttpd.html +63 -0
- inpanel/public/partials/service/mariadb.html +113 -0
- inpanel/public/partials/service/memcache.html +36 -0
- inpanel/public/partials/service/minio.html +44 -0
- inpanel/public/partials/service/mongodb.html +44 -0
- inpanel/public/partials/service/mysql.html +113 -0
- inpanel/public/partials/service/named.html +61 -0
- inpanel/public/partials/service/nginx.html +296 -0
- inpanel/public/partials/service/ntp.html +40 -0
- inpanel/public/partials/service/php.html +250 -0
- inpanel/public/partials/service/proftpd.html +59 -0
- inpanel/public/partials/service/pureftpd.html +59 -0
- inpanel/public/partials/service/redis.html +40 -0
- inpanel/public/partials/service/samba.html +58 -0
- inpanel/public/partials/service/sendmail.html +40 -0
- inpanel/public/partials/service/ssh.html +186 -0
- inpanel/public/partials/service/tomcat.html +65 -0
- inpanel/public/partials/service/vsftpd.html +195 -0
- inpanel/public/partials/setting.html +299 -0
- inpanel/public/partials/site/apache/site.html +277 -0
- inpanel/public/partials/site/index.html +344 -0
- inpanel/public/partials/site/nginx/site.html +558 -0
- inpanel/public/partials/sorry.html +19 -0
- inpanel/public/partials/storage/autofm.html +103 -0
- inpanel/public/partials/storage/backup.html +42 -0
- inpanel/public/partials/storage/index.html +360 -0
- inpanel/public/partials/storage/migrate.html +93 -0
- inpanel/public/partials/storage/movedata.html +98 -0
- inpanel/public/partials/storage/remote.html +77 -0
- inpanel/public/partials/utils/cron.html +486 -0
- inpanel/public/partials/utils/firewall.html +266 -0
- inpanel/public/partials/utils/index.html +193 -0
- inpanel/public/partials/utils/network.html +118 -0
- inpanel/public/partials/utils/process.html +239 -0
- inpanel/public/partials/utils/repository.html +373 -0
- inpanel/public/partials/utils/shell.html +28 -0
- inpanel/public/partials/utils/ssl.html +319 -0
- inpanel/public/partials/utils/time.html +109 -0
- inpanel/public/partials/utils/user.html +359 -0
- inpanel/public/robots.txt +2 -0
- inpanel/remote.py +109 -0
- inpanel/templates/file/preview.html +34 -0
- inpanel/templates/index.html +93 -0
- inpanel/utils.py +282 -0
- inpanel/web.py +1811 -0
- inpanel/yum.py +756 -0
- inpanel-1.2.1.dist-info/METADATA +399 -0
- inpanel-1.2.1.dist-info/RECORD +198 -0
- inpanel-1.2.1.dist-info/WHEEL +5 -0
- inpanel-1.2.1.dist-info/entry_points.txt +2 -0
- inpanel-1.2.1.dist-info/licenses/LICENSE +30 -0
- inpanel-1.2.1.dist-info/top_level.txt +1 -0
inpanel/mod/__init__.py
ADDED
|
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()
|