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/httpd.py ADDED
@@ -0,0 +1,1205 @@
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 Apache HTTP Server Management.'''
10
+
11
+
12
+ import re
13
+ from glob import glob
14
+ from io import StringIO
15
+ from json import loads
16
+ from pathlib import Path
17
+ from string import punctuation
18
+
19
+ from ..base import COMMENTFLAG, GENBY, config_path
20
+ from .config import Config
21
+ from ..utils import is_valid_domain, is_valid_ip, is_valid_ipv4, is_valid_ipv6
22
+
23
+ # load httpd module config
24
+ httpd_config = Config(str(Path(config_path) / 'module/httpd.ini'))
25
+ # print('httpd_config', httpd_config.get_config())
26
+ base_path = httpd_config.get('base', 'path')
27
+ base_config = httpd_config.get('base', 'config')
28
+ base_servers = httpd_config.get('base', 'servers')
29
+
30
+ if Path(base_path).exists():
31
+ HTTPDCONF = base_path
32
+ else:
33
+ HTTPDCONF = '/etc/httpd/'
34
+
35
+ if Path(base_config).exists():
36
+ APACHECONF = base_config
37
+ else:
38
+ APACHECONF = '/etc/httpd/conf/httpd.conf'
39
+
40
+ if Path(base_servers).exists():
41
+ SERVERCONF = base_servers
42
+ else:
43
+ SERVERCONF = '/etc/httpd/conf.d/'
44
+
45
+
46
+ CONFIGS = {
47
+ 'servertokens': 'os',
48
+ 'serverroot': '/etc/httpd',
49
+ 'timeout': 60,
50
+ 'defaulttype': 'text/plain',
51
+ 'documentroot': '/var/www/html',
52
+ 'directoryindex': 'index.html index.htm index.php',
53
+ 'adddefaultcharset': 'utf-8',
54
+ 'listen': 80,
55
+ 'serveradmin': 'root@localhost',
56
+ 'servername': 'www@localhost',
57
+ 'namevirtualhost': '*:80',
58
+ 'keepalive': 'off',
59
+ 'maxkeepaliverequests': 100,
60
+ 'keepalivetimeout': 15,
61
+ 'usecanonicalname': 'off',
62
+ 'accessfilename': '.htaccess',
63
+ 'typesconfig': '/etc/mime.types',
64
+ 'errorlog': 'logs/error_log',
65
+ 'loglevel': 'debug', # info, notice, warn, error, crit, alert, emerg
66
+ 'serversignature': 'on',
67
+ 'indexoptions': 'fancyindexing versionsort namewidth=* htmltable charset=utf-8',
68
+ 'alias': 'alias',
69
+ 'addlanguage': '',
70
+ 'loadmodule': '',
71
+ 'scriptalias': '',
72
+ 'addtype': '',
73
+ 'addicon': '',
74
+ 'addiconbytype': '',
75
+ 'include': '',
76
+ 'aaa': '',
77
+ 'bbb': ''
78
+ }
79
+ DIRECTIVES = {
80
+ 'acceptfilter': ('server'),
81
+ 'acceptpathinfo': ('server', 'virtualhost', 'directory', 'htaccess'),
82
+ 'accessfilename': ('server', 'virtualhost'),
83
+ 'adddefaultcharset': ('server', 'virtualhost', 'directory', 'htaccess'),
84
+ 'allowencodedslashes': ('server', 'virtualhost'),
85
+ 'allowoverride': ('directory'),
86
+ 'allowoverridelist': ('directory'),
87
+ 'cgimapextension': ('directory', 'htaccess'),
88
+ 'cgipassauth': ('directory', 'htaccess'),
89
+ 'cgivar': ('directory', 'htaccess'),
90
+ 'contentdigest': ('server', 'virtualhost', 'directory', 'htaccess'),
91
+ 'defaultruntimedir': ('server'),
92
+ 'defaulttype': ('server', 'virtualhost', 'directory', 'htaccess'),
93
+ 'define': ('server', 'virtualhost', 'directory'),
94
+ 'directory': ('server', 'virtualhost'),
95
+ 'directorymatch': ('server', 'virtualhost'),
96
+ 'documentroot': ('server', 'virtualhost'),
97
+ 'else': ('server', 'virtualhost', 'directory', 'htaccess'),
98
+ 'elseif': ('server', 'virtualhost', 'directory', 'htaccess'),
99
+ 'enablemmap': ('server', 'virtualhost', 'directory', 'htaccess'),
100
+ 'enablesendfile': ('server', 'virtualhost', 'directory', 'htaccess'),
101
+ 'error': ('server', 'virtualhost', 'directory', 'htaccess'),
102
+ 'errordocument': ('server', 'virtualhost', 'directory', 'htaccess'),
103
+ 'errorlog': ('server', 'virtualhost'),
104
+ 'errorlogformat': ('server', 'virtualhost'),
105
+ 'extendedstatus': ('server'),
106
+ 'fileetag': ('server', 'virtualhost', 'directory', 'htaccess'),
107
+ 'files': ('server', 'virtualhost', 'directory', 'htaccess'),
108
+ 'filesmatch': ('server', 'virtualhost', 'directory', 'htaccess'),
109
+ 'forcetype': ('directory', 'htaccess'),
110
+ 'gprofdir': ('server', 'virtualhost'),
111
+ 'hostnamelookups': ('server', 'virtualhost', 'directory'),
112
+ 'httpprotocoloptions': ('server', 'virtualhost'),
113
+ 'if': ('server', 'virtualhost', 'directory', 'htaccess'),
114
+ 'ifdefine': ('server', 'virtualhost', 'directory', 'htaccess'),
115
+ 'ifdirective': ('server', 'virtualhost', 'directory', 'htaccess'),
116
+ 'iffile': ('server', 'virtualhost', 'directory', 'htaccess'),
117
+ 'ifmodule': ('server', 'virtualhost', 'directory', 'htaccess'),
118
+ 'ifsection': ('server', 'virtualhost', 'directory', 'htaccess'),
119
+ 'include': ('server', 'virtualhost', 'directory'),
120
+ 'includeoptional': ('server', 'virtualhost', 'directory'),
121
+ 'keepalive': ('server', 'virtualhost'),
122
+ 'keepalivetimeout': ('server', 'virtualhost'),
123
+ 'limit': ('directory', 'htaccess'),
124
+ 'limitexcept': ('directory', 'htaccess'),
125
+ 'limitinternalrecursion': ('server', 'virtualhost'),
126
+ 'limitrequestbody': ('server', 'virtualhost', 'directory', 'htaccess'),
127
+ 'limitrequestfields': ('server', 'virtualhost'),
128
+ 'limitrequestfieldsize': ('server', 'virtualhost'),
129
+ 'limitrequestline': ('server', 'virtualhost'),
130
+ 'limitxmlrequestbody': ('server', 'virtualhost', 'directory', 'htaccess'),
131
+ 'location': ('server', 'virtualhost'),
132
+ 'locationmatch': ('server', 'virtualhost'),
133
+ 'loglevel': ('server', 'virtualhost', 'directory'),
134
+ 'maxkeepaliverequests': ('server', 'virtualhost'),
135
+ 'maxrangeoverlaps': ('server', 'virtualhost', 'directory'),
136
+ 'maxrangereversals': ('server', 'virtualhost', 'directory'),
137
+ 'maxranges': ('server', 'virtualhost', 'directory'),
138
+ 'mergeslashes': ('server', 'virtualhost'),
139
+ 'mergetrailers': ('server', 'virtualhost'),
140
+ 'mutex': ('server'),
141
+ 'namevirtualhost': ('server'),
142
+ 'options': ('server', 'virtualhost', 'directory', 'htaccess'),
143
+ 'protocol': ('server', 'virtualhost'),
144
+ 'protocols': ('server', 'virtualhost'),
145
+ 'protocolshonororder': ('server', 'virtualhost'),
146
+ 'qualifyredirecturl': ('server', 'virtualhost', 'directory'),
147
+ 'regexdefaultoptions': ('server'),
148
+ 'registerhttpmethod': ('server'),
149
+ 'rlimitcpu': ('server', 'virtualhost', 'directory', 'htaccess'),
150
+ 'rlimitmem': ('server', 'virtualhost', 'directory', 'htaccess'),
151
+ 'rlimitnproc': ('server', 'virtualhost', 'directory', 'htaccess'),
152
+ 'scriptinterpretersource': ('server', 'virtualhost', 'directory', 'htaccess'),
153
+ 'seerequesttail': ('server'),
154
+ 'serveradmin': ('server', 'virtualhost'),
155
+ 'serveralias': ('virtualhost'),
156
+ 'servername': ('server', 'virtualhost'),
157
+ 'serverpath': ('virtualhost'),
158
+ 'serverroot': ('server'),
159
+ 'serversignature': ('server', 'virtualhost', 'directory', 'htaccess'),
160
+ 'servertokens': ('server'),
161
+ 'sethandler': ('server', 'virtualhost', 'directory', 'htaccess'),
162
+ 'setinputfilter': ('server', 'virtualhost', 'directory', 'htaccess'),
163
+ 'setoutputfilter': ('server', 'virtualhost', 'directory', 'htaccess'),
164
+ 'timeout': ('server', 'virtualhost'),
165
+ 'traceenable': ('server', 'virtualhost'),
166
+ 'undefine': ('server'),
167
+ 'usecanonicalname': ('server', 'virtualhost', 'directory'),
168
+ 'usecanonicalphysicalport': ('server', 'virtualhost', 'directory'),
169
+ 'virtualhost': ('server'),
170
+ 'aaa': (),
171
+ 'bbb': ()
172
+ }
173
+
174
+ GZIP = r'''<IfModule mod_deflate.c>
175
+ DeflateCompressionLevel 6
176
+ AddOutputFilterByType DEFLATE text/plain
177
+ AddOutputFilterByType DEFLATE text/html
178
+ AddOutputFilterByType DEFLATE text/php
179
+ AddOutputFilterByType DEFLATE text/xml
180
+ AddOutputFilterByType DEFLATE text/css
181
+ AddOutputFilterByType DEFLATE text/javascript
182
+ AddOutputFilterByType DEFLATE application/xhtml+xml
183
+ AddOutputFilterByType DEFLATE application/xml
184
+ AddOutputFilterByType DEFLATE application/rss+xml
185
+ AddOutputFilterByType DEFLATE application/atom_xml
186
+ AddOutputFilterByType DEFLATE application/javascript
187
+ AddOutputFilterByType DEFLATE application/x-javascript
188
+ AddOutputFilterByType DEFLATE application/x-httpd-php
189
+ AddOutputFilterByType DEFLATE application/x-font-ttf
190
+ AddOutputFilterByType DEFLATE image/svg+xml
191
+ AddOutputFilterByType DEFLATE image/gif image/png image/jpe image/swf image/jpeg image/bmp
192
+ # Don’t compress images and other
193
+ BrowserMatch ^Mozilla/4 gzip-only-text/html
194
+ BrowserMatch ^Mozilla/4\.0[678] no-gzip
195
+ BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
196
+ SetEnvIfNoCase Request_URI .(?:html|htm)$ no-gzip dont-varySetEnvIfNoCase
197
+ #SetEnvIfNoCase Request_URI .(?:gif|jpe?g|png)$ no-gzip dont-vary
198
+ SetEnvIfNoCase Request_URI .(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary
199
+ SetEnvIfNoCase Request_URI .(?:pdf|doc)$ no-gzip dont-vary
200
+ </IfModule>'''
201
+
202
+ def web_handler(context):
203
+ '''for web server'''
204
+ action = context.get_argument('action', '')
205
+ if action == 'getservers':
206
+ sites = getservers()
207
+ context.write({'code': 0, 'msg': '', 'data': sites})
208
+
209
+ elif action in ('enableserver', 'disableserver', 'deleteserver'):
210
+ ip = context.get_argument('ip', '')
211
+ port = context.get_argument('port', '')
212
+ name = context.get_argument('server_name', '')
213
+ handler = getattr(locals(), action)
214
+ opstr = {
215
+ 'enableserver': '启用',
216
+ 'disableserver': '停用',
217
+ 'deleteserver': '删除',
218
+ }
219
+ if handler(name, ip, port):
220
+ context.write({'code': 0, 'msg': f'站点 {name}:{port} {opstr[action]}成功!'})
221
+ else:
222
+ context.write({'code': -1, 'msg': f'站点 {name}:{port} {opstr[action]}失败!'})
223
+
224
+ elif action == 'get_settings':
225
+ # items = context.get_argument('items', '')
226
+ # items = items.split(',')
227
+ config = loadconfig()
228
+ context.write({'code': 0, 'msg': '', 'data': config})
229
+
230
+ elif action == 'getserver':
231
+ ip = context.get_argument('ip', '')
232
+ port = context.get_argument('port', '')
233
+ name = context.get_argument('name', '')
234
+ serverinfo = getserver(ip, port, name)
235
+ if serverinfo:
236
+ context.write({'code': 0, 'msg': '站点信息读取成功!', 'data': serverinfo})
237
+ else:
238
+ context.write({'code': -1, 'msg': '站点不存在!'})
239
+
240
+ elif action in ('addserver', 'updateserver'):
241
+ setting = loads(context.get_argument('setting', '')) or {}
242
+
243
+ ip = setting.get('ip', '')
244
+ if ip not in ('', '*', '0.0.0.0') and not is_valid_ip(ip):
245
+ context.write({'code': -1, 'msg': f'{ip} 不是有效的IP地址!'})
246
+ return
247
+
248
+ port = int(setting.get('port', 0))
249
+ if port <= 0 or port > 65535:
250
+ context.write({'code': -1, 'msg': f'{setting.get("port")} 不是有效的端口号!'})
251
+ return
252
+
253
+ servername = setting.get('servername')
254
+ # print('servername', servername)
255
+ if not is_valid_domain(servername):
256
+ context.write({'code': -1, 'msg': f'{servername} 不是有效的域名!'})
257
+ return
258
+
259
+ documentroot = setting.get('documentroot', '')
260
+ if not documentroot:
261
+ context.write({'code': -1, 'msg': f'{documentroot} 不是有效的目录!'})
262
+ return
263
+ autocreate = setting.get('autocreate')
264
+ if not Path(documentroot).exists():
265
+ if autocreate:
266
+ try:
267
+ mkdir(documentroot)
268
+ except:
269
+ context.write({'code': -1, 'msg': f'站点目录 {documentroot} 创建失败!'})
270
+ return
271
+ else:
272
+ context.write({'code': -1, 'msg': f'站点目录 {documentroot} 不存在!'})
273
+ return
274
+
275
+ directoryindex = setting.get('directoryindex')
276
+ serveralias = setting.get('serveralias')
277
+ serveradmin = setting.get('serveradmin')
278
+ errorlog = setting.get('errorlog')
279
+ customlog = setting.get('customlog')
280
+ directory = setting.get('directory')
281
+
282
+ version = context.get_argument('version', '') # apache version
283
+ for diret in directory:
284
+ if 'path' in diret and diret['path']:
285
+ if not Path(diret['path']).exists() and 'autocreate' in diret and diret['autocreate']:
286
+ try:
287
+ mkdir(diret['path'])
288
+ except:
289
+ context.write({'code': -1, 'msg': '路径 %s 创建失败!' % diret['path']})
290
+ return
291
+ else:
292
+ context.write({'code': -1, 'msg': '请选择路径!'})
293
+ return
294
+ if action == 'addserver':
295
+ if not addserver(servername, ip, port, serveralias=serveralias, serveradmin=serveradmin, documentroot=documentroot, directoryindex=directoryindex, directory=directory,
296
+ errorlog=errorlog, customlog=customlog, version=version):
297
+ context.write({'code': -1, 'msg': '新站点添加失败!请检查站点域名是否重复。', 'data': setting})
298
+ else:
299
+ context.write({'code': 0, 'msg': '新站点添加成功!', 'data': setting})
300
+ else:
301
+ c_ip = context.get_argument('ip', '')
302
+ c_port = context.get_argument('port', '')
303
+ c_name = context.get_argument('name', '')
304
+ if not updateserver(c_name, c_ip, c_port, serveralias=serveralias, serveradmin=serveradmin, documentroot=documentroot, directoryindex=directoryindex, directory=directory,
305
+ errorlog=errorlog, customlog=customlog, version=version):
306
+ context.write({'code': -1, 'msg': '站点设置更新失败!请检查配置信息(如域名是否重复?)', 'data': setting})
307
+ else:
308
+ context.write({'code': 0, 'msg': '站点设置更新成功!', 'data': setting})
309
+
310
+
311
+ def loadconfig(conf=None, getlineinfo=False):
312
+ '''Load Apache config and return a dict.
313
+ '''
314
+ if not conf:
315
+ conf = APACHECONF
316
+ if not Path(conf).is_file():
317
+ return False
318
+ return _loadconfig(conf, getlineinfo)
319
+
320
+
321
+ def _loadconfig(conf, getlineinfo, config=None, context_stack=None):
322
+ '''parse Apache httpd.conf and include configs'''
323
+ if config is None:
324
+ configs = {}
325
+ else:
326
+ configs = config
327
+
328
+ result = {}
329
+ directorys = {} # 附加信息
330
+
331
+ RE_VH_START = re.compile(r'<VirtualHost(\s+)(\S+)>')
332
+ RE_VH_CLOSE = re.compile(r'</VirtualHost>')
333
+ RE_DT_START = re.compile(r'<Directory(\s+)(\S+)>')
334
+ RE_DT_CLOSE = re.compile(r'</Directory>')
335
+ with open(conf, 'r', encoding='utf-8') as f:
336
+ id_v = 0
337
+ enable = False
338
+ vhost = []
339
+ id_d = 0
340
+ enable_d = False
341
+ v_dirs = {}
342
+ result_d = {}
343
+ for line_i, line in enumerate(f):
344
+ out = line.strip()
345
+
346
+ line_disabled = False
347
+ if out.startswith(COMMENTFLAG):
348
+ # deal with our speical comment string
349
+ while out.startswith(COMMENTFLAG):
350
+ out = out[3:]
351
+ out = out.strip()
352
+ line_disabled = True
353
+
354
+ if not out or out.startswith('#'):
355
+ continue
356
+
357
+ # deal with comment and detect inpanel flag in comment
358
+ gen_by_inpanel = False
359
+ fields = out.split('#', 1)
360
+ if len(fields) > 1 and fields[1].strip() == GENBY:
361
+ gen_by_inpanel = True
362
+
363
+ out = fields[0].strip()
364
+ line = out.strip(';').lstrip('<').lstrip('</').rstrip('>')
365
+ fields = line.split()
366
+ key = fields[0].lower()
367
+ value = ' '.join(fields[1:]).strip(';')
368
+ # print(key, value, line)
369
+
370
+ match = RE_VH_START.search(out)
371
+ if match:
372
+ id_d = 0
373
+ v_dirs = {}
374
+ result_d[id_v] = []
375
+ directorys[id_v] = []
376
+ name_port = match.groups()[1].strip('"').strip('\'')
377
+ # print(name_port)
378
+ port = name_port.split(':')[-1]
379
+ ip = name_port[0:-(len(port) + 1)]
380
+ ip = ip.lstrip('[').rstrip(']') # for IPv6
381
+ vhost = [ip, port, gen_by_inpanel, line_disabled]
382
+ enable = True
383
+ enable_d = False
384
+ continue
385
+
386
+ # start of Directory in VirtualHost
387
+ match_d = RE_DT_START.search(out)
388
+ if enable is True and match_d:
389
+ v_dirs = {}
390
+ path = match_d.groups()[1].strip().strip('"')
391
+ v_dirs[id_d] = []
392
+ v_dirs[id_d].append('path ' + path)
393
+ enable_d = True
394
+ continue
395
+
396
+ # end of Directory in VirtualHost
397
+ # if '</Directory>' in out:
398
+ if enable_d is True and RE_DT_CLOSE.search(out):
399
+ result_d[id_v].append(v_dirs[id_d])
400
+ id_d += 1
401
+ enable_d = False
402
+ v_dirs = {}
403
+ continue
404
+
405
+ # merge of Directory in VirtualHost
406
+ if enable_d:
407
+ v_dirs[id_d].append(out)
408
+ continue
409
+
410
+ # end of VirtualHost
411
+ if RE_VH_CLOSE.search(out):
412
+ enable_d = False
413
+ if len(vhost) > 0:
414
+ result[id_v] = vhost
415
+ if id_v in result_d:
416
+ d = _parse_directory(result_d[id_v])
417
+ directorys[id_v] = d
418
+ else:
419
+ directorys[id_v] = []
420
+ id_v += 1
421
+ enable = False
422
+ continue
423
+
424
+ # merge of VirtualHost
425
+ if enable:
426
+ # print('merge', out)
427
+ vhost.append(out)
428
+ continue
429
+
430
+ if key in CONFIGS:
431
+ if key in ('indexoptions', 'directoryindex'):
432
+ configs[key] = ' '.join(str(n) for n in fields[1:])
433
+ elif key == 'addicon':
434
+ if key in configs:
435
+ configs[key].append({'path': fields[1], 'exts': fields[2:]})
436
+ else:
437
+ configs[key] = [{'path': fields[1], 'exts': fields[2:]}]
438
+ elif key == 'addiconbytype':
439
+ configs[key] = ' '.join(str(n) for n in fields[1:])
440
+ elif key == 'alias':
441
+ if key in configs:
442
+ configs[key].append({'alias': fields[1], 'origin': fields[2].strip('"')})
443
+ else:
444
+ configs[key] = [{'alias': fields[1], 'origin': fields[2].strip('"')}]
445
+ elif key == ('addlanguage', 'loadmodule'):
446
+ if key in configs:
447
+ configs[key].append(fields[1:])
448
+ else:
449
+ configs[key] = [fields[1:]]
450
+ elif key == 'listen':
451
+ port = fields[1].split(':')[-1]
452
+ ip = fields[1][0:-(len(port) + 1)].lstrip('[').rstrip(']')
453
+ if key in configs:
454
+ configs[key].append({'port': port, 'ip': ip})
455
+ else:
456
+ configs[key] = [{'port': port, 'ip': ip}]
457
+ elif key == 'include':
458
+ include_file = fields[1] if fields[1].startswith('/') else str(Path(HTTPDCONF) / fields[1])
459
+ include_files = glob(include_file)
460
+ # order by domain name, excluding tld
461
+ # getdm = lambda x: x.split('/')[-1].split('.')[-3::-1]
462
+ # include_files = sorted(include_files, lambda x,y: cmp(getdm(x), getdm(y)))
463
+ for subconf in include_files:
464
+ if Path(subconf).is_file():
465
+ # print(subconf, getlineinfo, configs, context_stack)
466
+ # print('configs', configs)
467
+ _loadconfig(subconf, getlineinfo, configs, context_stack)
468
+ else:
469
+ configs[key] = fields[1].strip(punctuation)
470
+
471
+ # print('directorys', directorys)
472
+ if 'virtualhost' not in configs:
473
+ configs['virtualhost'] = []
474
+
475
+ if len(result) > 0:
476
+ # print('result', result)
477
+ for i in result.items():
478
+ if directorys[i]:
479
+ server = {'directory': directorys[i]}
480
+ else:
481
+ server = {}
482
+ for j, line in enumerate(result[i]):
483
+ if j == 0:
484
+ server['ip'] = result[i][0]
485
+ elif j == 1:
486
+ server['port'] = result[i][1]
487
+ elif j == 2:
488
+ server['_inpanel'] = result[i][2]
489
+ elif j == 3:
490
+ server['disabled'] = result[i][3]
491
+ server['status'] = 'off' if result[i][3] else 'on'
492
+ else:
493
+ fields = line.split()
494
+ key = fields[0].lower()
495
+ if key == 'directoryindex':
496
+ server['directoryindex'] = ' '.join(str(n) for n in fields[1:])
497
+ elif key == 'serveralias':
498
+ server['serveralias'] = fields[1:]
499
+ elif key == 'customlog':
500
+ server['customlog'] = ' '.join(fields[1:])
501
+ elif key in ('serveradmin', 'servername', 'documentroot', 'errorlog', 'options', 'location', 'suexecusergroup'):
502
+ server[key] = fields[1].strip(' ').strip('"')
503
+
504
+ configs['virtualhost'].append(server)
505
+
506
+ # configs['gzip'] = _context_check_gzip(LoadModule, IfModule)
507
+ return configs
508
+
509
+
510
+ def _parse_directory(directory):
511
+ '''read and detect directory config
512
+ '''
513
+ if not directory:
514
+ return []
515
+
516
+ results = []
517
+ for r in directory:
518
+ drct = {
519
+ 'path': '',
520
+ 'allow': [],
521
+ 'deny': []
522
+ }
523
+ for line in r:
524
+ line = line.split()
525
+ key = line[0].lower()
526
+ if key == 'path':
527
+ drct['path'] = line[1].strip()
528
+ elif key == 'order':
529
+ drct['order'] = (','.join(str(n) for n in line[1:])).lower()
530
+ elif key in ('allow', 'deny'):
531
+ # drct[key].append(line[2].strip()) # only multi-line IPs
532
+ drct[key].extend([i for i in line[2:]]) # support multi-line IPs and multiple IPs per line
533
+ elif key in ('allowoverride'):
534
+ drct[key] = line[1].strip()
535
+ elif key == 'options':
536
+ options = (' '.join(str(n) for n in line[1:])).lower()
537
+ if '-indexes' in options:
538
+ drct['indexes'] = '-'
539
+ elif 'indexes' in options or '+indexes' in options:
540
+ drct['indexes'] = '+'
541
+ if '-followsymlinks' in options:
542
+ drct['followsymlinks'] = '-'
543
+ elif 'followsymlinks' in options or '+followsymlinks' in options:
544
+ drct['followsymlinks'] = '+'
545
+ if '-execcgi' in options:
546
+ drct['execcgi'] = '-'
547
+ elif 'execcgi' in options or '+execcgi' in options:
548
+ drct['execcgi'] = '+'
549
+ order = drct.get('order', '')
550
+ if order in ('allow,deny', 'deny,allow'):
551
+ drct['allow_enable'] = True
552
+ drct['deny_enable'] = True
553
+ results.append(drct)
554
+ # print(results)
555
+ return results
556
+
557
+
558
+ def getservers(config=None):
559
+ '''Get servers from apache configuration files.
560
+ '''
561
+ if not config:
562
+ config = loadconfig()
563
+ return _context_getservers(config=config)
564
+
565
+
566
+ def getserver(ip, port, server_name, config=None):
567
+ '''Get server setting from Apache configuration files.
568
+ '''
569
+ if config is None or ('_isdirty' in config and config['_isdirty']):
570
+ config = loadconfig()
571
+ server = _context_getserver(ip, port, server_name, config=config)
572
+ return server[0] if len(server) >= 1 else None
573
+
574
+
575
+ def virtual_host_config(site, key, val, port=80):
576
+ '''
577
+ site: abc.com
578
+ key: VirtualHost or DocumentRoot or ServerAdmin or Directory
579
+ val: /var/www
580
+ '''
581
+ keys = ['VirtualHost', 'DocumentRoot', 'ServerAdmin', 'Directory']
582
+ if key not in keys or not site or not val:
583
+ return False
584
+
585
+ conf = SERVERCONF + site + '.conf'
586
+ try:
587
+ stat(conf)
588
+ except OSError:
589
+ print('site config file not exist')
590
+ return False
591
+
592
+ old_conf = open(conf, encoding='utf-8').read()
593
+
594
+ with open(conf + '.bak', 'w', encoding='utf-8') as f:
595
+ # backup
596
+ f.write(old_conf)
597
+
598
+ if key == 'VirtualHost':
599
+ conf = SERVERCONF + val + '.conf'
600
+ val = str(val) + ':' + str(port)
601
+ # os.renames(SERVERCONF + site + '.conf', conf + '.bak')
602
+
603
+ # make new config
604
+ new_conf = re.sub(key + ' "(.*?)"',
605
+ key + ' "' + val + '"',
606
+ old_conf)
607
+
608
+ # save new config file
609
+ with open(conf, 'w', encoding='utf-8') as f:
610
+ f.write(new_conf)
611
+ # delete old site config file
612
+ if key == 'VirtualHost' and stat(conf) and stat(SERVERCONF + site + '.conf.bak'):
613
+ remove(SERVERCONF + site + '.conf')
614
+ return True
615
+
616
+
617
+ # https://blog.csdn.net/brucemj/article/details/37933519
618
+ # https://oomake.com/question/266681
619
+
620
+
621
+ def replace_docroot(vhost, new_docroot):
622
+ '''yield new lines of an httpd.conf file where docroot lines matching
623
+ the specified vhost are replaced with the new_docroot
624
+ '''
625
+ vhost_start = re.compile(r'<VirtualHost\s+(.*?)>')
626
+ vhost_end = re.compile(r'</VirtualHost>')
627
+ docroot_re = re.compile(r'(DocumentRoot\s+)(\S+)')
628
+ file = open(HTTPDCONF + vhost + '.conf', encoding='utf-8').read()
629
+ conf_file = StringIO(file)
630
+ in_vhost = False
631
+ curr_vhost = None
632
+ for line in conf_file:
633
+ # 起始行查找host
634
+ vhost_start_match = vhost_start.search(line)
635
+ if vhost_start_match:
636
+ curr_vhost = vhost_start_match.groups()[0]
637
+ in_vhost = True
638
+ print(curr_vhost, vhost)
639
+ if in_vhost and (curr_vhost == vhost):
640
+ docroot_match = docroot_re.search(line)
641
+ if docroot_match:
642
+ sub_line = docroot_re.sub(r'\1%s' % new_docroot, line)
643
+ line = sub_line
644
+ vhost_end_match = vhost_end.search(line)
645
+ if vhost_end_match:
646
+ in_vhost = False
647
+ yield line
648
+
649
+
650
+ def _context_check_gzip(LoadModule, IfModule):
651
+ mod_status = (
652
+ ['ext_filter_module', 'modules/mod_ext_filter.so'] in LoadModule and
653
+ ['deflate_module', 'modules/mod_deflate.so'] in LoadModule and
654
+ ['headers_module', 'modules/mod_headers.so'] in LoadModule
655
+ )
656
+ # check deflate config in IfModule
657
+ mod_deflate = True
658
+ return mod_status and mod_deflate
659
+
660
+ def _context_gethttp(config=None):
661
+ """Get http context config.
662
+ """
663
+ if config is None or ('_isdirty' in config and config['_isdirty']):
664
+ config = loadconfig(APACHECONF, True)
665
+ print('config', config)
666
+ return config['_'][0]
667
+
668
+ def _context_getservers(disabled=None, config=None, getlineinfo=True):
669
+ '''Get server context configs.
670
+ '''
671
+
672
+ if config is None or ('_isdirty' in config and config['_isdirty']):
673
+ config = loadconfig(APACHECONF, getlineinfo)
674
+ servers = []
675
+ if 'virtualhost' in config and len(config['virtualhost']) > 0:
676
+ servers = config['virtualhost']
677
+ if disabled == None or not getlineinfo:
678
+ return servers
679
+ else:
680
+ return [server for server in servers if server['disabled'] == disabled]
681
+
682
+ def _context_getserver(ip, port, servername, config=None, disabled=None, getlineinfo=True):
683
+ '''Get a server context config by ip:port and servername.
684
+
685
+ If disabled is None, return all servers
686
+ If disabled is True, return only disabled servers
687
+ If disabled is False, return only normal servers
688
+ '''
689
+ if config is None or ('_isdirty' in config and config['_isdirty']):
690
+ config = loadconfig(APACHECONF, getlineinfo)
691
+ cnfservers = _context_getservers(disabled=disabled, config=config, getlineinfo=getlineinfo)
692
+ # print(cnfservers)
693
+ if not ip or ip in ('*', '0.0.0.0'):
694
+ ip = '*'
695
+ if is_valid_ipv6(ip) and not is_valid_ipv4(ip):
696
+ ip = '[' + ip + ']'
697
+ port = str(port)
698
+ return [s for s in cnfservers if ip == s['ip'] and port == s['port'] and s['servername'] == servername or 'serveralias' in s and servername in s['serveralias']]
699
+ # if getlineinfo:
700
+ # s_names = ' '.join([v['value'] for v in s['server_name']]).split()
701
+ # listens = [v['value'].split()[0] for v in s['listen']]
702
+ # else:
703
+ # s_names = ' '.join([v for v in s['server_name']]).split()
704
+ # listens = [v.split()[0] for v in s['listen']]
705
+ # find_listen = ip and ['%s:%s' % (ip, port)] or [port, '*:%s' % port, '0.0.0.0:%s' % port]
706
+ # if server_name == s_names and ip == s_ip and port == s_port: # any([i in listens for i in find_listen]):
707
+ # return s
708
+ # return False
709
+
710
+
711
+ def _context_getupstreams(server_name, config=None, disabled=None, getlineinfo=True):
712
+ """Get upstream list related to a server.
713
+ """
714
+ if config is None or ('_isdirty' in config and config['_isdirty']):
715
+ config = loadconfig(APACHECONF, getlineinfo)
716
+ upstreams = http_get('upstream', config)
717
+ if not upstreams: return False
718
+ if getlineinfo:
719
+ upstreams = [upstream for upstream in upstreams
720
+ if upstream['_param']['value'].startswith('backend_of_%s_' % server_name)]
721
+ else:
722
+ upstreams = [upstream for upstream in upstreams
723
+ if upstream['_param'].startswith('backend_of_%s_' % server_name)]
724
+
725
+ if disabled == None or not getlineinfo:
726
+ return upstreams
727
+ else:
728
+ return [upstream for upstream in upstreams
729
+ if upstream['_param']['disabled']==disabled]
730
+
731
+ def _comment(filepath, start, end):
732
+ """Commend some lines in the file.
733
+ """
734
+ if not Path(filepath).is_file():
735
+ return False
736
+ data = []
737
+ with open(filepath, encoding='utf-8') as f:
738
+ for i, line in enumerate(f):
739
+ if i >= start and i <= end:
740
+ if not line.startswith(COMMENTFLAG):
741
+ data.append(COMMENTFLAG)
742
+ data.append(line)
743
+ with open(filepath, 'w', encoding='utf-8') as f:
744
+ f.write(''.join(data))
745
+ return True
746
+
747
+ def _uncomment(filepath, start, end):
748
+ """Uncommend some lines in the file.
749
+ """
750
+ if not Path(filepath).is_file():
751
+ return False
752
+ data = []
753
+ with open(filepath, encoding='utf-8') as f:
754
+ for i, line in enumerate(f):
755
+ if i >= start and i <= end:
756
+ while line.startswith(COMMENTFLAG):
757
+ line = line[3:]
758
+ data.append(line)
759
+ with open(filepath, 'w', encoding='utf-8') as f:
760
+ f.write(''.join(data))
761
+ return True
762
+
763
+ def _delete(filepath, start, end, delete_emptyfile=True):
764
+ """Delete some lines in the file.
765
+
766
+ If delete_emptyfile is set to True, then the empty file will
767
+ be deleted from file system.
768
+ """
769
+ if not Path(filepath).is_file():
770
+ return False
771
+ data = []
772
+ with open(filepath, encoding='utf-8') as f:
773
+ for i, line in enumerate(f):
774
+ if i >= start and i <= end:
775
+ continue
776
+ data.append(line)
777
+ with open(filepath, 'w', encoding='utf-8') as f:
778
+ f.write(''.join(data))
779
+ if delete_emptyfile:
780
+ if ''.join(data).strip() == '':
781
+ unlink(filepath)
782
+ return True
783
+
784
+ def _getcontextrange(context, config):
785
+ """Return the range of the input context, including the file path.
786
+
787
+ Return format:
788
+ [filepath, line_start, line_end]
789
+ """
790
+ file_i = context['_range']['begin']['file']
791
+ filepath = config['_files'][file_i]
792
+ line_start = context['_range']['begin']['line'][0]
793
+ line_end = context['_range']['end']['line'][0]
794
+ return [filepath, line_start, line_end]
795
+
796
+ def _context_commentserver(ip, port, server_name, config=None):
797
+ """Comment a context using InPanel's special comment string '#v#'
798
+ """
799
+ if config is None or ('_isdirty' in config and config['_isdirty']):
800
+ config = loadconfig(APACHECONF, True)
801
+ scontext = _context_getserver(ip, port, server_name, config=config)
802
+ if not scontext: return False
803
+ filepath, line_start, line_end = _getcontextrange(scontext, config)
804
+ return _comment(filepath, line_start, line_end)
805
+
806
+ def _context_uncommentserver(ip, port, server_name, config=None):
807
+ """Uncomment a InPanel's special-commented context.
808
+ """
809
+ if config is None or ('_isdirty' in config and config['_isdirty']):
810
+ config = loadconfig(APACHECONF, True)
811
+ scontext = _context_getserver(ip, port, server_name, config=config)
812
+ if not scontext: return False
813
+ filepath, line_start, line_end = _getcontextrange(scontext, config)
814
+ return _uncomment(filepath, line_start, line_end)
815
+
816
+ def _context_deleteserver(ip, port, server_name, config=None, disabled=None):
817
+ """Delete a server context.
818
+ """
819
+ if config is None or ('_isdirty' in config and config['_isdirty']):
820
+ config = loadconfig(APACHECONF, True)
821
+ scontext = _context_getserver(ip, port, server_name, config=config, disabled=disabled)
822
+ if not scontext: return False
823
+ filepath, line_start, line_end = _getcontextrange(scontext, config)
824
+ config['_isdirty'] = True
825
+ return _delete(filepath, line_start, line_end)
826
+
827
+ def _context_commentupstreams(server_name, config=None):
828
+ """Comment upstreams by server names.
829
+ """
830
+ if config is None or ('_isdirty' in config and config['_isdirty']):
831
+ config = loadconfig(APACHECONF, True)
832
+ upstreams = _context_getupstreams(server_name, config=config)
833
+ if not upstreams: return True
834
+ for upstream in upstreams:
835
+ filepath, line_start, line_end = _getcontextrange(upstream, config)
836
+ if not _comment(filepath, line_start, line_end): return False
837
+ return True
838
+
839
+ def _context_uncommentupstreams(server_name, config=None):
840
+ """Uncomment upstreams by server names.
841
+ """
842
+ if config is None or ('_isdirty' in config and config['_isdirty']):
843
+ config = loadconfig(APACHECONF, True)
844
+ upstreams = _context_getupstreams(server_name, config=config)
845
+ if not upstreams: return True
846
+ for upstream in upstreams:
847
+ filepath, line_start, line_end = _getcontextrange(upstream, config)
848
+ if not _uncomment(filepath, line_start, line_end): return False
849
+ return True
850
+
851
+ def _context_deleteupstreams(server_name, config=None, disabled=None):
852
+ """Delete upstreams by server name.
853
+ """
854
+ while True:
855
+ # we need to reload config after delete one upstream
856
+ if config is None or ('_isdirty' in config and config['_isdirty']):
857
+ config = loadconfig(APACHECONF, True)
858
+ upstreams = _context_getupstreams(server_name, config=config, disabled=disabled)
859
+ if not upstreams: return True
860
+ for upstream in upstreams:
861
+ filepath, line_start, line_end = _getcontextrange(upstream, config)
862
+ config['_isdirty'] = True
863
+ if not _delete(filepath, line_start, line_end): return False
864
+ break # only delete the first one
865
+ return True
866
+
867
+
868
+ def _replace(positions, lines):
869
+ """Replace the lines specified in list positions to new lines.
870
+
871
+ Structure of positions:
872
+ [
873
+ (filepath, line_start, line_count),
874
+ ...
875
+ ]
876
+ Parameter positions can not be empty.
877
+
878
+ * If the new lines is empty, old lines with be deleted.
879
+ * If the new lines is less than the old lines, the rest old lines
880
+ will also be deleted.
881
+ * If the new lines is more than the old lines, then new value will
882
+ append after the last line of the old lines.
883
+ """
884
+ # merge line positions by filepath
885
+ # struct: {'/path/to/file': [3, 4, 10, ...], ...}
886
+ files = {}
887
+ for pos in positions:
888
+ filepath, line_start, line_count = pos
889
+ if not filepath in files:
890
+ files[filepath] = []
891
+ for i in range(line_count):
892
+ files[filepath].append(line_start+i)
893
+ # replace line by line
894
+ for filepath, line_nums in files.items():
895
+ flines = []
896
+ with open(filepath, encoding='utf-8') as f:
897
+ for i, fline in enumerate(f):
898
+ if i in line_nums:
899
+ if len(lines) > 0:
900
+ # replace with a new line
901
+ line = lines[0]
902
+ lines = lines[1:]
903
+ # detect the space at the start of the old line
904
+ # this aim to keep the indent of the line
905
+ space = ''
906
+ for c in fline:
907
+ if c not in (' ', '\t'):
908
+ break
909
+ space += c
910
+ flines.append(''.join([space, line, '\n']))
911
+ else:
912
+ # no more new line, delete the old line
913
+ continue
914
+ else:
915
+ if i > line_nums[-1] and len(lines) > 0:
916
+ # exceed the last old line, insert the rest new lines here
917
+ # detect the indent of the last line
918
+ space = ''
919
+ if len(flines)>0: # last line exists
920
+ for c in flines[-1]:
921
+ if c not in (' ', '\t'):
922
+ break
923
+ space += c
924
+ for line in lines:
925
+ flines.append(''.join([space, line, '\n']))
926
+ lines = []
927
+ flines.append(fline)
928
+ # write back to file
929
+ with open(filepath, 'w', encoding='utf-8') as f:
930
+ f.write(''.join(flines))
931
+
932
+
933
+ def _insert(filepath, line_start, lines):
934
+ """Insert the lines to the specified position.
935
+ """
936
+ flines = []
937
+ with open(filepath, encoding='utf-8') as f:
938
+ for i, fline in enumerate(f):
939
+ if i == line_start:
940
+ # detect the indent of the last not empty line
941
+ space = ''
942
+ flines_len = len(flines)
943
+ if flines_len>0: # last line exists
944
+ line_i = -1
945
+ while flines[line_i].strip() == '' and -line_i <= flines_len:
946
+ line_i -= 1
947
+ for c in flines[line_i]:
948
+ if c not in (' ', '\t'):
949
+ break
950
+ space += c
951
+ if flines[line_i].strip().endswith('{'):
952
+ space += ' '
953
+ for line in lines:
954
+ flines.append(''.join([space, line, '\n']))
955
+ flines.append(fline)
956
+ # write back to file
957
+ with open(filepath, 'w', encoding='utf-8') as f:
958
+ f.write(''.join(flines))
959
+
960
+
961
+ def http_get(directive, config=None):
962
+ """Get directive values in http context.
963
+ """
964
+ if config is None or ('_isdirty' in config and config['_isdirty']):
965
+ config = loadconfig(APACHECONF)
966
+ hcontext = _context_gethttp(config)
967
+ if directive in hcontext:
968
+ return hcontext[directive]
969
+ else:
970
+ return None
971
+
972
+
973
+ def http_getfirst(directive, config=None):
974
+ """Get the first value of the directive in http context.
975
+ """
976
+ values = http_get(directive, config)
977
+ if values:
978
+ return values[0]
979
+ return None
980
+
981
+
982
+ def http_set(directive, values, config=None):
983
+ """Set a directive in http context.
984
+
985
+ If directive exists, the value will be replace in place.
986
+ If directive not exists, new directive will be created at the beginning of http context.
987
+
988
+ Parameter values can be a list or a string.
989
+ If values is set to empty list or None or empty string, then the directive will be deleted.
990
+ """
991
+ if config is None or ('_isdirty' in config and config['_isdirty']):
992
+ config = loadconfig(APACHECONF, True)
993
+ hcontext = _context_gethttp(config)
994
+
995
+ if not values:
996
+ values = []
997
+ elif isinstance(values, str):
998
+ values = [values]
999
+ values = ['%s %s;' % (directive, v) for v in values]
1000
+
1001
+ if directive in hcontext:
1002
+ # update or delete value
1003
+ dvalues = hcontext[directive]
1004
+ lines = [(config['_files'][dvalue['file']], dvalue['line'][0], dvalue['line'][1]) for dvalue in dvalues]
1005
+ _replace(lines, values)
1006
+ else:
1007
+ # add directive to the beginning of http context
1008
+ # some directive like proxy_cache_path should be declare before use the resource,
1009
+ # so we should insert it at the beginning
1010
+ begin = hcontext['_range']['begin']
1011
+ _insert(config['_files'][begin['file']], begin['line'][0]+begin['line'][1], values)
1012
+
1013
+ config['_isdirty'] = True
1014
+
1015
+
1016
+ def disableserver(ip, port, servername):
1017
+ """Disable a server.
1018
+ """
1019
+ _context_commentupstreams(servername)
1020
+ return _context_commentserver(ip, port, servername)
1021
+
1022
+
1023
+ def enableserver(ip, port, servername):
1024
+ """Enable a server.
1025
+ """
1026
+ _context_uncommentupstreams(servername)
1027
+ return _context_uncommentserver(ip, port, servername)
1028
+
1029
+
1030
+ def deleteserver(server_name, ip, port):
1031
+ '''Delete a server.'''
1032
+ print(SERVERCONF + server_name + '.conf')
1033
+ try:
1034
+ unlink(SERVERCONF + server_name + '.conf')
1035
+ return True
1036
+ except:
1037
+ return False
1038
+
1039
+
1040
+ def servername_exists(ip, port, server_name, config=None):
1041
+ '''Check if the server_name at given ip:port is already exists.
1042
+ '''
1043
+ return _context_getserver(ip, port, server_name, config=config) and True or False
1044
+
1045
+
1046
+ def addserver(serveraname, ip, port, serveralias=None, serveradmin=None, documentroot=None, directoryindex=None, directory=None, errorlog=None, customlog=None, version=None):
1047
+ '''Add a new VirtualHost.'''
1048
+ if not is_valid_domain(serveraname):
1049
+ return False
1050
+ ip = ip or '*'
1051
+ if is_valid_ipv6(ip) and not is_valid_ipv4(ip):
1052
+ ip = '[' + ip + ']'
1053
+ if port:
1054
+ port = str(port) or '80'
1055
+ else:
1056
+ return False
1057
+ if not documentroot:
1058
+ return False
1059
+ # check if any of servername - ip - port pair already exists
1060
+ if servername_exists(ip, port, serveraname):
1061
+ return False
1062
+
1063
+ # start generate the config string
1064
+ servercfg = [f'<VirtualHost {ip}:{port}> # {GENBY}']
1065
+ servercfg.append(f' ServerName {serveraname}')
1066
+ servercfg.append(f' DocumentRoot {documentroot}')
1067
+ if serveradmin:
1068
+ servercfg.append(f' ServerAdmin {serveradmin}')
1069
+ if serveralias:
1070
+ servercfg.append(f' ServerAlias {" ".join(serveralias)}')
1071
+ if directoryindex:
1072
+ servercfg.append(f' DirectoryIndex {" ".join(directoryindex.split())}')
1073
+ else:
1074
+ servercfg.append(' DirectoryIndex index.html index.htm index.php')
1075
+ if errorlog:
1076
+ servercfg.append(f' ErrorLog {errorlog}')
1077
+ if customlog:
1078
+ servercfg.append(f' CustomLog {customlog}')
1079
+ if directory and len(directory) > 0:
1080
+ d = _extend_directory(directory)
1081
+ servercfg.extend([' %s' % i for i in d])
1082
+ # end of server context
1083
+ servercfg.append('</VirtualHost>')
1084
+
1085
+ #print '\n'.join(servercfg)
1086
+ configfile = str(Path(SERVERCONF) / (serveraname + '.conf'))
1087
+ configfile_exists = Path(configfile).exists()
1088
+
1089
+ # check if need to add a new line at the end of the file to
1090
+ # avoid first line go to the same former } line
1091
+ if configfile_exists:
1092
+ with open(configfile, encoding='utf-8') as f:
1093
+ f.seek(-1, 2)
1094
+ if f.read(1) != '\n':
1095
+ servercfg.insert(0, '')
1096
+ with open(configfile, configfile_exists and 'a' or 'w', encoding='utf-8') as f:
1097
+ f.write('\n'.join(servercfg))
1098
+ return True
1099
+
1100
+
1101
+ def _extend_directory(directory):
1102
+ '''extend Directory config to line for build configfile.
1103
+ Usage
1104
+ ----------
1105
+ > d = _extend_directory(directory)\n
1106
+ > servercfg.extend(d) # for Apache Config\n
1107
+ > servercfg.extend([' %s' % i for i in d]) # for VirtualHost Config
1108
+ '''
1109
+ drct_cfg = []
1110
+ for drct in directory:
1111
+ if 'path' in drct and drct['path']:
1112
+ drct_cfg.append('<Directory %s>' % drct['path'])
1113
+ else:
1114
+ continue
1115
+ options = []
1116
+ if drct.get('indexes'): # '+' or '-'
1117
+ options.append(str(drct['indexes']) + 'indexes')
1118
+ if drct.get('followsymlinks'):
1119
+ options.append(str(drct['followsymlinks']) + 'followsymlinks')
1120
+ if drct.get('execcgi'):
1121
+ options.append(str(drct['execcgi']) + 'execcgi')
1122
+ if len(options) > 0:
1123
+ drct_cfg.append(' Options ' + ' '.join(options))
1124
+ if drct.get('order'):
1125
+ drct_cfg.append(' Order ' + drct['order'])
1126
+ if len(drct.get('allow', [])) > 0:
1127
+ drct_cfg.extend([' allow from %s' % i for i in drct['allow']])
1128
+ if len(drct.get('deny', [])) > 0:
1129
+ drct_cfg.extend([' deny from %s' % i for i in drct['deny']])
1130
+ drct_cfg.append('</Directory>')
1131
+ return drct_cfg
1132
+
1133
+
1134
+ def updateserver(old_name, old_ip, old_port, serveraname, ip, port, serveralias=None, serveradmin=None, documentroot=None, directoryindex=None, directory=None, errorlog=None, customlog=None, version=None):
1135
+ '''Update an existing server.
1136
+
1137
+ If the old config is not in the right place, we would automatically delete it and
1138
+ create the new config to coresponding config file under /etc/httpd/conf.d/.
1139
+ '''
1140
+ # compare the old context and the new context
1141
+ # to check if the ip:port/server_name change and conflict status
1142
+ config = loadconfig(APACHECONF, True)
1143
+ oldscontext = _context_getserver(old_ip, old_port, old_name, config)
1144
+ if not oldscontext:
1145
+ return False
1146
+ scontext = _context_getserver(ip, port, serveraname)
1147
+ # server context found, but not equals to the old
1148
+ # this means conflict occur
1149
+ if scontext and scontext != oldscontext:
1150
+ return False
1151
+
1152
+ if __name__ == '__main__':
1153
+ HTTPDCONF = '/Users/douzhenjiang/Projects/inpanel/test'
1154
+ APACHECONF = '/Users/douzhenjiang/Projects/inpanel/test/httpd.conf'
1155
+ SERVERCONF = '/Users/douzhenjiang/Projects/inpanel/test/conf.d/'
1156
+ print(locals())
1157
+ # tmp = loadconfig()
1158
+ # print('config', tmp)
1159
+ # print(dumps(tmp))
1160
+ # virtual_host_config('aaa.com', 'DocumentRoot', '/v/asfs34535')
1161
+ # virtual_host_config('aaa.com', 'ServerAdmin', '4567896543')
1162
+ # virtual_host_config('aaa.com', 'VirtualHost', 'bbb.com', 567)
1163
+
1164
+ # for line in replace_docroot('aaa.com', 'docroot'):
1165
+ # print(line)
1166
+
1167
+ # aaa = '/Users/douzhenjiang/Projects/inpanel/test/aaa.com.conf'
1168
+ # tmp1 = loadconfig()
1169
+ # print(dumps(tmp1))
1170
+ # s= tmp1['virtualhost']
1171
+ # # s = scontext['virtualhost']
1172
+ # s= [i for i in s if i['port']=='870' and i['ip']=='1.1.1.1' and i['servername']=='inpanel.org']
1173
+ # print(s)
1174
+ # s = getserver('1.1.1.1', '80', 'aaaaaaaa.aa')
1175
+ # s = servername_exists('1.1.1.1', 80, 'aaaaaaaa.aa')
1176
+ # print(s)
1177
+ # s = addserver('aaaaaaaa.aa', '1.1.1.1', 801, documentroot='/Users/douzhenjiang/Projects/inpanel/test')
1178
+ # s = getserver('1.1.1.1', 80, 'aaaaaaaa.aa')
1179
+ # s = dumps(s)
1180
+ # print(s)
1181
+ # for i in s:
1182
+ # print('port' in i)
1183
+ # # if i.port=='80':
1184
+ # # print(i)
1185
+ # # break
1186
+ # print([i for i in s if i['port']=='870' and i['ip']=='1.1.1.1' and i['servername']=='inpanel.org'])
1187
+
1188
+ # print dumps(_context_getservers(disabled=None))
1189
+
1190
+ # path = str(Path(SERVERCONF, clist[i])
1191
+ # print os.path.splitext('/Users/douzhenjiang/Projects/inpanel/test/aaa.com')
1192
+ # SERVERCONF = '/Users/douzhenjiang/Projects/inpanel/test'
1193
+ # print(servername_exists('1.1.1.1', 80, 'inpanel.org'))
1194
+ # addserver('inpanel.org', '1.1.1.1', 80,
1195
+ # serveralias=['cc.com', 'bb.com'],
1196
+ # serveradmin='root',
1197
+ # documentroot='/var/www/inpanel.org',
1198
+ # directoryindex='index.html index.php',
1199
+ # directory=[{'path': '/var/www/inpanel.org/abc',
1200
+ # 'options': 'aadsfsdf asdfdsf'}],
1201
+ # errorlog='abc/aaa.log',
1202
+ # customlog='abc/aaa_error.log',
1203
+ # version=None)
1204
+ # print(enableserver('*', 80, 'zhoukan.pub'))
1205
+