Files
kefu/im/deploy/xim_enable_ssl.py
T
2026-09-03 08:38:17 +08:00

131 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""Request, install, and verify BaoTa-managed HTTPS for xim.bchongw.com."""
import http.client
import json
import os
import socket
import ssl
import subprocess
import sys
import time
import urllib.request
from pathlib import Path
PANEL = Path('/www/server/panel')
DOMAIN = 'xim.bchongw.com'
PROJECT = 'xim_im'
EXPECTED_IP = '8.219.70.152'
GO_CONF = PANEL / ('vhost/nginx/go_' + PROJECT + '.conf')
def request(**values):
result = public.dict_obj()
for key, value in values.items():
setattr(result, key, value)
return result
def command(*args):
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
if result.returncode:
raise RuntimeError('{} failed: {}'.format(args[0], result.stderr.strip()))
return result.stdout.strip()
def fetch(url, expected):
last_error = None
for _ in range(40):
try:
with urllib.request.urlopen(url, timeout=8) as response:
body = response.read().decode('utf-8')
if response.status == 200 and expected in body:
return body
except Exception as error:
last_error = error
time.sleep(0.5)
raise RuntimeError('Public verification failed for {}: {}'.format(url, last_error))
def main():
if os.geteuid() != 0 or not PANEL.is_dir():
raise SystemExit('Run as root on the BaoTa host')
os.chdir(str(PANEL))
sys.path.insert(0, str(PANEL))
sys.path.insert(0, str(PANEL / 'class'))
global public
import public
import panelSite
from acme_v2 import acme_v2
from projectModel.goModel import main as GoProject
addresses = sorted(set(socket.gethostbyname_ex(DOMAIN)[2]))
if EXPECTED_IP not in addresses:
raise RuntimeError('DNS does not point to this server: ' + json.dumps(addresses))
go = GoProject()
project = go.get_project_find(PROJECT)
if not project or not go.get_project_run_state(project_name=PROJECT):
raise RuntimeError('BaoTa Go project is not running')
if DOMAIN + ':80' not in project['project_config']['domains']:
raise RuntimeError('Expected domain is not bound to the project')
fetch('http://' + DOMAIN + '/healthz', '"status":"ok"')
site_id = str(project['id'])
args = request(id=site_id, auth_to=site_id, auth_type='http',
auto_wildcard='0', domains=json.dumps([DOMAIN]),
siteName=PROJECT, ca='letsencrypt')
certificate = acme_v2().apply_cert_api(args)
if not certificate.get('status'):
raise RuntimeError('Certificate request failed: ' + str(certificate.get('msg')))
if 'private_key' not in certificate or 'cert' not in certificate:
raise RuntimeError('Certificate authority returned an incomplete result')
install = panelSite.panelSite().SetSSL(request(
siteName=PROJECT,
key=certificate['private_key'],
csr=certificate['cert'] + certificate.get('root', ''),
))
if not install.get('status'):
raise RuntimeError('Certificate install failed: ' + str(install.get('msg')))
redirect = panelSite.panelSite().HttpToHttps(request(siteName=PROJECT))
if not redirect.get('status'):
raise RuntimeError('HTTPS redirect enablement failed: ' + str(redirect.get('msg')))
command('/www/server/nginx/sbin/nginx', '-t')
command('/etc/init.d/nginx', 'reload')
fetch('https://' + DOMAIN + '/healthz', '"status":"ok"')
fetch('https://' + DOMAIN + '/admin/', '星遇社交运营中心')
connection = http.client.HTTPConnection(DOMAIN, 80, timeout=8)
connection.request('GET', '/healthz')
response = connection.getresponse()
response.read()
if response.status not in (301, 308):
raise RuntimeError('HTTP is not redirected to HTTPS: ' + str(response.status))
if not (response.getheader('Location') or '').startswith('https://' + DOMAIN):
raise RuntimeError('HTTP redirect target is incorrect')
context = ssl.create_default_context()
with socket.create_connection((DOMAIN, 443), timeout=8) as raw:
with context.wrap_socket(raw, server_hostname=DOMAIN) as secure:
peer = secure.getpeercert()
protocol = secure.version()
ssl_state = panelSite.panelSite().GetSSL(request(siteName=PROJECT))
if not ssl_state.get('status') or not ssl_state.get('httpTohttps'):
raise RuntimeError('BaoTa does not report SSL and forced HTTPS as enabled')
result = {
'status': 'HTTPS_DEPLOYMENT_OK',
'domain': DOMAIN,
'project': PROJECT,
'dns': addresses,
'tls_protocol': protocol,
'certificate_not_after': peer.get('notAfter'),
'http_redirect': response.status,
'nginx_config': str(GO_CONF),
}
print(json.dumps(result, ensure_ascii=False))
if __name__ == '__main__':
main()