-
-
Notifications
You must be signed in to change notification settings - Fork 69
Expand file tree
/
Copy pathpatch_localhost.py
More file actions
294 lines (271 loc) · 11.8 KB
/
Copy pathpatch_localhost.py
File metadata and controls
294 lines (271 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import os
def replace_in_file(path, old, new):
if not os.path.exists(path):
print(f'SKIP (not found): {path}')
return False
with open(path, 'r') as f:
content = f.read()
if old not in content:
print(f'PATTERN NOT FOUND: {path}')
return False
content = content.replace(old, new)
with open(path, 'w') as f:
f.write(content)
print(f'Patched: {path}')
return True
# 1. site_domain_service.py - add panel_origin helper
replace_in_file(
'/opt/serverkit/backend/app/services/site_domain_service.py',
""" @classmethod
def server_ip(cls):
\"\"\"Public IP that wildcard/custom A-records should point at (Phase 3).\"\"\"
return SystemSettings.get('server_public_ip') or current_app.config.get('SERVER_PUBLIC_IP') or None
@classmethod
def https_enabled(cls):""",
""" @classmethod
def server_ip(cls):
\"\"\"Public IP that wildcard/custom A-records should point at (Phase 3).\"\"\"
return SystemSettings.get('server_public_ip') or current_app.config.get('SERVER_PUBLIC_IP') or None
@classmethod
def panel_origin(cls):
\"\"\"Canonical public origin of the ServerKit panel, or None when no
canonical domain is configured.
Uses the persisted canonical_domain / canonical_https_enabled settings.
Falls back to PUBLIC_URL / SERVERKIT_PUBLIC_URL env vars, then to the
sites base domain. Returns None if nothing usable is configured.
\"\"\"
domain = SystemSettings.get('canonical_domain')
if domain:
https = bool(SystemSettings.get('canonical_https_enabled', False))
return f'https://{domain}' if https else f'http://{domain}'
url = current_app.config.get('PUBLIC_URL') or current_app.config.get('SERVERKIT_PUBLIC_URL')
if url:
return url.rstrip('/')
base = cls.base_domain()
if base:
return f'https://{base}' if cls.https_enabled() else f'http://{base}'
return None
@classmethod
def https_enabled(cls):"""
)
# 2. git_service.py - Gitea URL
replace_in_file(
'/opt/serverkit/backend/app/services/git_service.py',
""" return {
'installed': True,
'running': running,
'http_port': app.port or config.get('http_port'),
'ssh_port': config.get('ssh_port'),
'url_path': '/gitea', # Slug-based URL path
'url': f\"http://localhost:{app.port}\" if app.port else None, # Legacy port-based URL
'app_id': app.id,
'version': config.get('version', '1.21')
}""",
""" # Prefer the panel's canonical origin so the link works through a domain
# and Cloudflare; fall back to the local port only when no domain is set.
from app.services.site_domain_service import SiteDomainService
panel_origin = SiteDomainService.panel_origin()
if panel_origin:
public_url = f\"{panel_origin}/gitea\"
elif app.port:
public_url = f\"http://localhost:{app.port}\"
else:
public_url = None
return {
'installed': True,
'running': running,
'http_port': app.port or config.get('http_port'),
'ssh_port': config.get('ssh_port'),
'url_path': '/gitea',
'url': public_url,
'app_id': app.id,
'version': config.get('version', '1.21')
}"""
)
# 3. wordpress_service.py - standalone WordPress status URL
replace_in_file(
'/opt/serverkit/backend/app/services/wordpress_service.py',
""" return {
'installed': True,
'running': running,
'http_port': app.port or config.get('http_port'),
'url_path': '/wordpress',
'url': f\"http://localhost:{app.port}\" if app.port else None,
'app_id': app.id,
'version': config.get('version', '6.4')
}""",
""" # Prefer the panel's canonical origin so the link works through a domain
# and Cloudflare; fall back to the local port only when no domain is set.
from app.services.site_domain_service import SiteDomainService
panel_origin = SiteDomainService.panel_origin()
if panel_origin:
public_url = f\"{panel_origin}/wordpress\"
elif app.port:
public_url = f\"http://localhost:{app.port}\"
else:
public_url = None
return {
'installed': True,
'running': running,
'http_port': app.port or config.get('http_port'),
'url_path': '/wordpress',
'url': public_url,
'app_id': app.id,
'version': config.get('version', '6.4')
}"""
)
# 4. wordpress_service.py - _canonical_site_url fallback
replace_in_file(
'/opt/serverkit/backend/app/services/wordpress_service.py',
""" @classmethod
def _canonical_site_url(cls, app) -> str:
\"\"\"The URL WordPress serves under — its primary domain if one exists,
else the legacy localhost:<port> address. Used to build correct
search-replace pairs when cloning.\"\"\"
from app.models.domain import Domain
d = (Domain.query.filter_by(application_id=app.id, is_primary=True).first()
or Domain.query.filter_by(application_id=app.id).first())
if d:
return f'http://{d.name}'
if app.port:
return f'http://localhost:{app.port}'
return None""",
""" @classmethod
def _canonical_site_url(cls, app) -> str:
\"\"\"The URL WordPress serves under — its primary domain if one exists,
else the panel's canonical origin, else the legacy localhost:<port>
address. Used to build correct search-replace pairs when cloning.\"\"\"
from app.models.domain import Domain
from app.services.site_domain_service import SiteDomainService
d = (Domain.query.filter_by(application_id=app.id, is_primary=True).first()
or Domain.query.filter_by(application_id=app.id).first())
if d:
scheme = 'https' if d.ssl_enabled else 'http'
return f'{scheme}://{d.name}'
panel_origin = SiteDomainService.panel_origin()
if panel_origin:
return panel_origin
if app.port:
return f'http://localhost:{app.port}'
return None"""
)
# 5. roundcube_service.py - install signature + URL
replace_in_file(
'/opt/serverkit/backend/app/services/roundcube_service.py',
""" @classmethod
def install(cls, imap_host: str = 'host.docker.internal',
smtp_host: str = 'host.docker.internal') -> Dict:""",
""" @classmethod
def install(cls, imap_host: str = 'host.docker.internal',
smtp_host: str = 'host.docker.internal',
domain: str = None) -> Dict:"""
)
replace_in_file(
'/opt/serverkit/backend/app/services/roundcube_service.py',
""" return {
'success': True,
'message': 'Roundcube installed successfully',
'port': cls.HOST_PORT,
'url': f'http://localhost:{cls.HOST_PORT}',
}""",
""" # Build a public URL: prefer a supplied domain, then auto-generate
# webmail.<panel_domain>, and only fall back to localhost:port.
from app.services.site_domain_service import SiteDomainService
public_url = None
warning = None
if domain:
public_url = f'http://{domain}'
proxy_res = cls.configure_nginx_proxy(domain)
if not proxy_res.get('success'):
warning = proxy_res.get('error')
else:
panel_origin = SiteDomainService.panel_origin()
panel_host = None
if panel_origin:
from urllib.parse import urlparse
panel_host = urlparse(panel_origin).hostname
if panel_host:
webmail_domain = f'webmail.{panel_host}'
public_url = f'http://{webmail_domain}'
proxy_res = cls.configure_nginx_proxy(webmail_domain)
if not proxy_res.get('success'):
warning = proxy_res.get('error')
if not public_url:
public_url = f'http://localhost:{cls.HOST_PORT}'
result = {
'success': True,
'message': 'Roundcube installed successfully',
'port': cls.HOST_PORT,
'url': public_url,
}
if warning:
result['warning'] = warning
return result"""
)
# 6. email.py - pass domain to roundcube install
replace_in_file(
'/opt/serverkit/backend/app/api/email.py',
""" result = RoundcubeService.install(
imap_host=data.get('imap_host', 'host.docker.internal'),
smtp_host=data.get('smtp_host', 'host.docker.internal'),
)""",
""" result = RoundcubeService.install(
imap_host=data.get('imap_host', 'host.docker.internal'),
smtp_host=data.get('smtp_host', 'host.docker.internal'),
domain=(data.get('domain') or '').strip() or None,
)"""
)
# 7. python_service.py - Django ALLOWED_HOSTS
replace_in_file(
'/opt/serverkit/backend/app/services/python_service.py',
""" # Create .env file
env_content = f'''DEBUG=False
SECRET_KEY={secrets.token_hex(32)}
ALLOWED_HOSTS=localhost,127.0.0.1
'''
with open(os.path.join(app_path, '.env'), 'w') as f:
f.write(env_content)""",
""" # Build ALLOWED_HOSTS: local addresses plus the panel domain and its
# subdomains so Django accepts requests behind nginx on a real domain.
from app.services.site_domain_service import SiteDomainService
allowed_hosts = ['localhost', '127.0.0.1']
panel_origin = SiteDomainService.panel_origin()
if panel_origin:
from urllib.parse import urlparse
panel_host = urlparse(panel_origin).hostname
if panel_host:
allowed_hosts.append(panel_host)
# Permit subdomains of the panel domain (e.g. app.serverkit.example.com)
if '.' in panel_host:
allowed_hosts.append('.' + panel_host)
allowed_hosts_str = ','.join(allowed_hosts)
# Create .env file
env_content = f'''DEBUG=False
SECRET_KEY={secrets.token_hex(32)}
ALLOWED_HOSTS={allowed_hosts_str}
'''
with open(os.path.join(app_path, '.env'), 'w') as f:
f.write(env_content)"""
)
# 8. environment_domain_service.py - fallback
replace_in_file(
'/opt/serverkit/backend/app/services/environment_domain_service.py',
""" if not production_domain:
# Fallback for sites without a domain
slug = cls.slugify(branch_name) if branch_name else env_type
return f'{env_type}-{slug}.localhost'""",
""" if not production_domain:
# Fallback for sites without a domain: use the managed-sites base domain
# or the panel domain instead of a useless .localhost address.
from app.services.site_domain_service import SiteDomainService
base = SiteDomainService.base_domain() or SiteDomainService.panel_origin()
if base:
from urllib.parse import urlparse
base_host = urlparse(base).hostname or base
slug = cls.slugify(branch_name) if branch_name else env_type
return f'{env_type}-{slug}.{base_host}'
# Last resort only when absolutely no domain is configured
slug = cls.slugify(branch_name) if branch_name else env_type
return f'{env_type}-{slug}.localhost'"""
)
print('All backend patches applied.')