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/ssh.py ADDED
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright (c) 2017-2026 Jackson Dou
5
+ # All rights reserved.
6
+ #
7
+ # InPanel is distributed under the terms of The New BSD License.
8
+ # The full license can be found in 'LICENSE'.
9
+
10
+ '''Module for ssh Management'''
11
+
12
+ import os
13
+ import re
14
+ import shlex
15
+ from pathlib import Path
16
+
17
+ import pexpect
18
+
19
+ SSHCFG = '/etc/ssh/sshd_config'
20
+
21
+
22
+ def loadconfig(cfgfile=None, detail=False):
23
+ """Read config file and parse config item to dict.
24
+ """
25
+ if not cfgfile:
26
+ cfgfile = SSHCFG
27
+
28
+ settings = {}
29
+ with open(cfgfile, encoding='utf-8') as f:
30
+ for line_i, line in enumerate(f):
31
+ line = line.strip()
32
+ if not line or line.startswith('# '):
33
+ continue
34
+
35
+ # detect if it's commented
36
+ if line.startswith('#'):
37
+ line = line.strip('#')
38
+ commented = True
39
+ if not detail:
40
+ continue
41
+ else:
42
+ commented = False
43
+
44
+ fs = re.split(r'\s+', line, 1)
45
+ if len(fs) != 2:
46
+ continue
47
+
48
+ item = fs[0].strip()
49
+ value = fs[1].strip()
50
+
51
+ if item in settings:
52
+ if detail:
53
+ count = settings[item]['count']+1
54
+ if not commented:
55
+ settings[item] = detail and {
56
+ 'file': cfgfile,
57
+ 'line': line_i,
58
+ 'value': value,
59
+ 'commented': commented,
60
+ } or value
61
+ else:
62
+ count = 1
63
+ settings[item] = detail and {
64
+ 'file': cfgfile,
65
+ 'line': line_i,
66
+ 'value': fs[1].strip(),
67
+ 'commented': commented,
68
+ } or value
69
+ if detail:
70
+ settings[item]['count'] = count
71
+
72
+ return settings
73
+
74
+
75
+ def cfg_get(item, detail=False, config=None):
76
+ """Get value of a config item.
77
+ """
78
+ if not config:
79
+ config = loadconfig(detail=detail)
80
+ if item in config:
81
+ return config[item]
82
+ else:
83
+ return None
84
+
85
+
86
+ def cfg_set(item, value, commented=False, config=None):
87
+ """Set value of a config item.
88
+ """
89
+ cfgfile = SSHCFG
90
+ v = cfg_get(item, detail=True, config=config)
91
+
92
+ if v:
93
+ # detect if value change
94
+ if v['commented'] == commented and v['value'] == value:
95
+ return True
96
+
97
+ # empty value should be commented
98
+ if value == '':
99
+ commented = True
100
+
101
+ # replace item in line
102
+ lines = []
103
+ with open(v['file'], encoding='utf-8') as f:
104
+ for line_i, line in enumerate(f):
105
+ if line_i == v['line']:
106
+ if not v['commented']:
107
+ if commented:
108
+ if v['count'] > 1:
109
+ # delete this line, just ignore it
110
+ pass
111
+ else:
112
+ # comment this line
113
+ lines.append('#%s %s\n' % (item, value))
114
+ else:
115
+ lines.append('%s %s\n' % (item, value))
116
+ else:
117
+ if commented:
118
+ # do not allow change comment value
119
+ lines.append(line)
120
+ pass
121
+ else:
122
+ # append a new line after comment line
123
+ lines.append(line)
124
+ lines.append('%s %s\n' % (item, value))
125
+ else:
126
+ lines.append(line)
127
+ with open(v['file'], 'w', encoding='utf-8') as f:
128
+ f.write(''.join(lines))
129
+ else:
130
+ # append to the end of file
131
+ with open(inifile, 'a', encoding='utf-8') as f:
132
+ f.write('\n%s%s = %s\n' % (commented and '#' or '', item, value))
133
+
134
+ return True
135
+
136
+
137
+ def genkey(path, password=''):
138
+ """Generate a ssh key pair.
139
+ """
140
+ cmd = shlex.split('ssh-keygen -t rsa')
141
+ child = pexpect.spawn(cmd[0], cmd[1:])
142
+ i = child.expect(['Enter file in which to save the key', pexpect.EOF])
143
+ if i == 1:
144
+ if child.isalive():
145
+ child.wait()
146
+ return False
147
+
148
+ child.sendline(path)
149
+ i = child.expect(['Overwrite', 'Enter passphrase', pexpect.EOF])
150
+ if i == 0:
151
+ child.sendline('y')
152
+ i = child.expect(['Enter passphrase', pexpect.EOF])
153
+ if i == 1:
154
+ if child.isalive():
155
+ child.wait()
156
+ return False
157
+ elif i == 2:
158
+ if child.isalive():
159
+ child.wait()
160
+ return False
161
+
162
+ child.sendline(password)
163
+ i = child.expect(['Enter same passphrase', pexpect.EOF])
164
+ if i == 1:
165
+ if child.isalive():
166
+ child.wait()
167
+ return False
168
+
169
+ child.sendline(password)
170
+ child.expect(pexpect.EOF)
171
+
172
+ if child.isalive():
173
+ return child.wait() == 0
174
+ return True
175
+
176
+
177
+ def chpasswd(path, oldpassword, newpassword):
178
+ """Change password of a private key.
179
+ """
180
+ if len(newpassword) != 0 and not len(newpassword) > 4:
181
+ return False
182
+
183
+ cmd = shlex.split('ssh-keygen -p')
184
+ child = pexpect.spawn(cmd[0], cmd[1:])
185
+ i = child.expect(['Enter file in which the key is', pexpect.EOF])
186
+ if i == 1:
187
+ if child.isalive():
188
+ child.wait()
189
+ return False
190
+
191
+ child.sendline(path)
192
+ i = child.expect(
193
+ ['Enter old passphrase', 'Enter new passphrase', pexpect.EOF])
194
+ if i == 0:
195
+ child.sendline(oldpassword)
196
+ i = child.expect(
197
+ ['Enter new passphrase', 'Bad passphrase', pexpect.EOF])
198
+ if i != 0:
199
+ if child.isalive():
200
+ child.wait()
201
+ return False
202
+ elif i == 2:
203
+ if child.isalive():
204
+ child.wait()
205
+ return False
206
+
207
+ child.sendline(newpassword)
208
+ i = child.expect(['Enter same passphrase again', pexpect.EOF])
209
+ if i == 1:
210
+ if child.isalive():
211
+ child.wait()
212
+ return False
213
+
214
+ child.sendline(newpassword)
215
+ child.expect(pexpect.EOF)
216
+
217
+ if child.isalive():
218
+ return child.wait() == 0
219
+ return True
220
+
221
+
222
+ def web_handler(context):
223
+ action = context.get_argument('action', '')
224
+
225
+ if action == 'getsettings':
226
+ port = cfg_get('Port')
227
+ enable_pwdauth = cfg_get('PasswordAuthentication') == 'yes'
228
+ enable_pubkauth = cfg_get('PubkeyAuthentication') == 'yes'
229
+ subsystem = cfg_get('Subsystem')
230
+ enable_sftp = subsystem and 'sftp' in subsystem
231
+ pubkey_path = '/root/.ssh/sshkey_inpanel.pub'
232
+ prvkey_path = '/root/.ssh/sshkey_inpanel'
233
+ context.write({'code': 0, 'msg': '获取 SSH 服务配置信息成功!', 'data': {
234
+ 'port': port,
235
+ 'enable_pwdauth': enable_pwdauth,
236
+ 'enable_pubkauth': enable_pubkauth,
237
+ 'enable_sftp': enable_sftp,
238
+ 'pubkey': Path(pubkey_path).is_file() and pubkey_path or '',
239
+ 'prvkey': Path(prvkey_path).is_file() and prvkey_path or '',
240
+ }})
241
+
242
+ elif action == 'savesettings':
243
+ if context.config.get('runtime', 'mode') == 'demo':
244
+ context.write({'code': -1, 'msg': '演示模式不允许修改 SSH 服务设置!'})
245
+ return
246
+
247
+ port = context.get_argument('port', '')
248
+ if port:
249
+ cfg_set('Port', port)
250
+ enable_pwdauth = context.get_argument('enable_pwdauth', '')
251
+ if enable_pwdauth:
252
+ cfg_set('PasswordAuthentication', enable_pwdauth=='on' and 'yes' or 'no')
253
+ enable_pubkauth = context.get_argument('enable_pubkauth', '')
254
+ if enable_pubkauth:
255
+ if enable_pubkauth == 'on':
256
+ pubkey_path = context.get_argument('pubkey', '')
257
+ if not Path(pubkey_path).is_file():
258
+ context.write({'code': -1, 'msg': '公钥文件不存在!'})
259
+ return
260
+ cfg_set('PubkeyAuthentication', enable_pubkauth=='on' and 'yes' or 'no')
261
+ cfg_set('AuthorizedKeysFile', pubkey_path)
262
+
263
+ enable_sftp = context.get_argument('enable_sftp', '')
264
+ if enable_sftp:
265
+ cfg_set('Subsystem', 'sftp /usr/libexec/openssh/sftp-server', enable_sftp!='on')
266
+ context.write({'code': 0, 'msg': 'SSH 服务配置保存成功!'})
267
+
268
+
269
+ if __name__ == '__main__':
270
+ import pprint
271
+ pp = pprint.PrettyPrinter(indent=4)
272
+
273
+ # pp.pprint(loadconfig())
274
+ #print cfg_get('Port')
275
+ #print cfg_get('Subsystem', detail=True)
276
+ #print cfg_set('Protocol', '2', commented=False)
277
+ #print cfg_set('Subsystem', 'sftp\t/usr/libexec/openssh/sftp-server', commented=True)
278
+
279
+ #print genkey('/root/.ssh/sshkey_inpanel')
280
+ #print chpasswd('/root/.ssh/sshkey_inpanel', '', 'aaaaaa')
inpanel/mod/system.py ADDED
@@ -0,0 +1,220 @@
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 System Detection and Information'''
9
+
10
+ import os
11
+ import shutil
12
+ from platform import mac_ver, platform, uname, win32_ver
13
+ from typing import Dict, Optional, Tuple
14
+
15
+
16
+ def get_os_release() -> Dict[str, str]:
17
+ """读取 /etc/os-release 文件,返回系统信息字典"""
18
+ info = {}
19
+ try:
20
+ with open("/etc/os-release", "r") as f:
21
+ for line in f:
22
+ if "=" in line:
23
+ k, v = line.strip().split("=", 1)
24
+ info[k] = v.strip('"')
25
+ except FileNotFoundError:
26
+ pass
27
+ return info
28
+
29
+
30
+ _os_release = get_os_release()
31
+
32
+ OS_ID = _os_release.get("ID", "")
33
+ OS_ID_LIKE = _os_release.get("ID_LIKE", "")
34
+ OS_VERSION_ID = _os_release.get("VERSION_ID", "")
35
+ OS_PRETTY_NAME = _os_release.get("PRETTY_NAME", "")
36
+ OS_NAME = _os_release.get("NAME", "")
37
+
38
+
39
+ def get_os_family() -> str:
40
+ """获取操作系统家族:rhel 或 debian 或 darwin 或 windows"""
41
+ if OS_ID in ("almalinux", "rocky", "centos", "rhel", "fedora", "ol", "scientific", "centos-stream"):
42
+ return "rhel"
43
+ if OS_ID in ("debian", "ubuntu", "linuxmint", "pop", "elementary", "kali"):
44
+ return "debian"
45
+ if OS_ID_LIKE:
46
+ if "rhel" in OS_ID_LIKE:
47
+ return "rhel"
48
+ if "debian" in OS_ID_LIKE:
49
+ return "debian"
50
+ kernel_name, _, _, _, _, _ = uname()
51
+ if kernel_name == "Darwin":
52
+ return "darwin"
53
+ if kernel_name == "Windows":
54
+ return "windows"
55
+ return "unknown"
56
+
57
+
58
+ def get_os_id() -> str:
59
+ """获取操作系统 ID"""
60
+ return OS_ID
61
+
62
+
63
+ def get_os_version() -> str:
64
+ """获取操作系统版本号(完整)"""
65
+ return OS_VERSION_ID
66
+
67
+
68
+ def get_os_version_major() -> int:
69
+ """获取操作系统主版本号(整数)"""
70
+ if OS_VERSION_ID:
71
+ try:
72
+ return int(OS_VERSION_ID.split(".")[0])
73
+ except (ValueError, IndexError):
74
+ pass
75
+ return 0
76
+
77
+
78
+ def get_os_version_full() -> Tuple[int, int]:
79
+ """获取操作系统版本号(主版本,次版本)"""
80
+ major = 0
81
+ minor = 0
82
+ if OS_VERSION_ID:
83
+ parts = OS_VERSION_ID.split(".")
84
+ try:
85
+ major = int(parts[0])
86
+ if len(parts) > 1:
87
+ minor = int(parts[1])
88
+ except ValueError:
89
+ pass
90
+ return (major, minor)
91
+
92
+
93
+ def get_os_title() -> str:
94
+ """获取操作系统全称"""
95
+ if OS_PRETTY_NAME:
96
+ return OS_PRETTY_NAME
97
+ if OS_NAME:
98
+ if OS_VERSION_ID:
99
+ return f"{OS_NAME} {OS_VERSION_ID}"
100
+ return OS_NAME
101
+ kernel_name, _, _, _, _, _ = uname()
102
+ if kernel_name == "Darwin":
103
+ os_version, _, _ = mac_ver()
104
+ return f"macOS {os_version}"
105
+ if kernel_name == "Windows":
106
+ os_version, _, _ = win32_ver()
107
+ return f"Windows {os_version}"
108
+ return "Unknown"
109
+
110
+
111
+ def get_os_name() -> str:
112
+ """获取操作系统发行名称"""
113
+ if OS_ID:
114
+ return OS_ID.capitalize()
115
+ kernel_name, _, _, _, _, _ = uname()
116
+ if kernel_name == "Darwin":
117
+ return "macOS"
118
+ if kernel_name == "Windows":
119
+ return "Windows"
120
+ return "Unknown"
121
+
122
+
123
+ def is_rhel_family() -> bool:
124
+ """是否为 RHEL 家族系统"""
125
+ return get_os_family() == "rhel"
126
+
127
+
128
+ def is_debian_family() -> bool:
129
+ """是否为 Debian 家族系统"""
130
+ return get_os_family() == "debian"
131
+
132
+
133
+ def is_darwin() -> bool:
134
+ """是否为 macOS 系统"""
135
+ return get_os_family() == "darwin"
136
+
137
+
138
+ def is_windows() -> bool:
139
+ """是否为 Windows 系统"""
140
+ return get_os_family() == "windows"
141
+
142
+
143
+ def compare_version(version_str: str) -> int:
144
+ """
145
+ 比较当前系统版本与指定版本
146
+ 返回值: -1 (小于), 0 (等于), 1 (大于)
147
+ """
148
+ current_major, current_minor = get_os_version_full()
149
+ parts = version_str.split(".")
150
+ try:
151
+ major = int(parts[0])
152
+ minor = int(parts[1]) if len(parts) > 1 else 0
153
+ except ValueError:
154
+ return 0
155
+
156
+ if current_major > major:
157
+ return 1
158
+ elif current_major < major:
159
+ return -1
160
+ else:
161
+ if current_minor > minor:
162
+ return 1
163
+ elif current_minor < minor:
164
+ return -1
165
+ return 0
166
+
167
+
168
+ def has_package_manager() -> bool:
169
+ """检查系统是否有包管理器"""
170
+ return any([
171
+ shutil.which("yum"),
172
+ shutil.which("dnf"),
173
+ shutil.which("apt"),
174
+ shutil.which("zypper"),
175
+ shutil.which("pacman"),
176
+ ])
177
+
178
+
179
+ def get_system_info() -> Dict[str, str]:
180
+ """返回完整的系统信息字典"""
181
+ kernel_name, hostname, kernel_release, kernel_version, machine, processor = uname()
182
+ return {
183
+ 'os_title': get_os_title(),
184
+ 'os_name': get_os_name(),
185
+ 'os_version': get_os_version(),
186
+ 'os_versint': str(get_os_version_major()),
187
+ 'os_platform': platform(),
188
+ 'os_family': get_os_family(),
189
+ 'os_id': get_os_id(),
190
+ 'hostname': hostname,
191
+ 'kernel_name': kernel_name,
192
+ 'kernel_release': kernel_release,
193
+ 'kernel_version': kernel_version,
194
+ 'machine': machine,
195
+ 'processor': processor,
196
+ }
197
+
198
+
199
+ __all__ = [
200
+ 'get_os_release',
201
+ 'OS_ID',
202
+ 'OS_ID_LIKE',
203
+ 'OS_VERSION_ID',
204
+ 'OS_PRETTY_NAME',
205
+ 'OS_NAME',
206
+ 'get_os_family',
207
+ 'get_os_id',
208
+ 'get_os_version',
209
+ 'get_os_version_major',
210
+ 'get_os_version_full',
211
+ 'get_os_title',
212
+ 'get_os_name',
213
+ 'is_rhel_family',
214
+ 'is_debian_family',
215
+ 'is_darwin',
216
+ 'is_windows',
217
+ 'compare_version',
218
+ 'has_package_manager',
219
+ 'get_system_info',
220
+ ]