1*6236dae4SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*6236dae4SAndroid Build Coastguard Worker# -*- coding: utf-8 -*- 3*6236dae4SAndroid Build Coastguard Worker#*************************************************************************** 4*6236dae4SAndroid Build Coastguard Worker# _ _ ____ _ 5*6236dae4SAndroid Build Coastguard Worker# Project ___| | | | _ \| | 6*6236dae4SAndroid Build Coastguard Worker# / __| | | | |_) | | 7*6236dae4SAndroid Build Coastguard Worker# | (__| |_| | _ <| |___ 8*6236dae4SAndroid Build Coastguard Worker# \___|\___/|_| \_\_____| 9*6236dae4SAndroid Build Coastguard Worker# 10*6236dae4SAndroid Build Coastguard Worker# Copyright (C) Daniel Stenberg, <[email protected]>, et al. 11*6236dae4SAndroid Build Coastguard Worker# 12*6236dae4SAndroid Build Coastguard Worker# This software is licensed as described in the file COPYING, which 13*6236dae4SAndroid Build Coastguard Worker# you should have received as part of this distribution. The terms 14*6236dae4SAndroid Build Coastguard Worker# are also available at https://curl.se/docs/copyright.html. 15*6236dae4SAndroid Build Coastguard Worker# 16*6236dae4SAndroid Build Coastguard Worker# You may opt to use, copy, modify, merge, publish, distribute and/or sell 17*6236dae4SAndroid Build Coastguard Worker# copies of the Software, and permit persons to whom the Software is 18*6236dae4SAndroid Build Coastguard Worker# furnished to do so, under the terms of the COPYING file. 19*6236dae4SAndroid Build Coastguard Worker# 20*6236dae4SAndroid Build Coastguard Worker# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21*6236dae4SAndroid Build Coastguard Worker# KIND, either express or implied. 22*6236dae4SAndroid Build Coastguard Worker# 23*6236dae4SAndroid Build Coastguard Worker# SPDX-License-Identifier: curl 24*6236dae4SAndroid Build Coastguard Worker# 25*6236dae4SAndroid Build Coastguard Worker########################################################################### 26*6236dae4SAndroid Build Coastguard Worker# 27*6236dae4SAndroid Build Coastguard Workerimport logging 28*6236dae4SAndroid Build Coastguard Workerimport os 29*6236dae4SAndroid Build Coastguard Workerimport subprocess 30*6236dae4SAndroid Build Coastguard Workerimport time 31*6236dae4SAndroid Build Coastguard Workerfrom datetime import timedelta, datetime 32*6236dae4SAndroid Build Coastguard Workerfrom json import JSONEncoder 33*6236dae4SAndroid Build Coastguard Worker 34*6236dae4SAndroid Build Coastguard Workerfrom .curl import CurlClient 35*6236dae4SAndroid Build Coastguard Workerfrom .env import Env 36*6236dae4SAndroid Build Coastguard Worker 37*6236dae4SAndroid Build Coastguard Worker 38*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__) 39*6236dae4SAndroid Build Coastguard Worker 40*6236dae4SAndroid Build Coastguard Worker 41*6236dae4SAndroid Build Coastguard Workerclass Caddy: 42*6236dae4SAndroid Build Coastguard Worker 43*6236dae4SAndroid Build Coastguard Worker def __init__(self, env: Env): 44*6236dae4SAndroid Build Coastguard Worker self.env = env 45*6236dae4SAndroid Build Coastguard Worker self._caddy = os.environ['CADDY'] if 'CADDY' in os.environ else env.caddy 46*6236dae4SAndroid Build Coastguard Worker self._caddy_dir = os.path.join(env.gen_dir, 'caddy') 47*6236dae4SAndroid Build Coastguard Worker self._docs_dir = os.path.join(self._caddy_dir, 'docs') 48*6236dae4SAndroid Build Coastguard Worker self._conf_file = os.path.join(self._caddy_dir, 'Caddyfile') 49*6236dae4SAndroid Build Coastguard Worker self._error_log = os.path.join(self._caddy_dir, 'caddy.log') 50*6236dae4SAndroid Build Coastguard Worker self._tmp_dir = os.path.join(self._caddy_dir, 'tmp') 51*6236dae4SAndroid Build Coastguard Worker self._process = None 52*6236dae4SAndroid Build Coastguard Worker self._rmf(self._error_log) 53*6236dae4SAndroid Build Coastguard Worker 54*6236dae4SAndroid Build Coastguard Worker @property 55*6236dae4SAndroid Build Coastguard Worker def docs_dir(self): 56*6236dae4SAndroid Build Coastguard Worker return self._docs_dir 57*6236dae4SAndroid Build Coastguard Worker 58*6236dae4SAndroid Build Coastguard Worker @property 59*6236dae4SAndroid Build Coastguard Worker def port(self) -> int: 60*6236dae4SAndroid Build Coastguard Worker return self.env.caddy_https_port 61*6236dae4SAndroid Build Coastguard Worker 62*6236dae4SAndroid Build Coastguard Worker def clear_logs(self): 63*6236dae4SAndroid Build Coastguard Worker self._rmf(self._error_log) 64*6236dae4SAndroid Build Coastguard Worker 65*6236dae4SAndroid Build Coastguard Worker def is_running(self): 66*6236dae4SAndroid Build Coastguard Worker if self._process: 67*6236dae4SAndroid Build Coastguard Worker self._process.poll() 68*6236dae4SAndroid Build Coastguard Worker return self._process.returncode is None 69*6236dae4SAndroid Build Coastguard Worker return False 70*6236dae4SAndroid Build Coastguard Worker 71*6236dae4SAndroid Build Coastguard Worker def start_if_needed(self): 72*6236dae4SAndroid Build Coastguard Worker if not self.is_running(): 73*6236dae4SAndroid Build Coastguard Worker return self.start() 74*6236dae4SAndroid Build Coastguard Worker return True 75*6236dae4SAndroid Build Coastguard Worker 76*6236dae4SAndroid Build Coastguard Worker def start(self, wait_live=True): 77*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 78*6236dae4SAndroid Build Coastguard Worker if self._process: 79*6236dae4SAndroid Build Coastguard Worker self.stop() 80*6236dae4SAndroid Build Coastguard Worker self._write_config() 81*6236dae4SAndroid Build Coastguard Worker args = [ 82*6236dae4SAndroid Build Coastguard Worker self._caddy, 'run' 83*6236dae4SAndroid Build Coastguard Worker ] 84*6236dae4SAndroid Build Coastguard Worker caddyerr = open(self._error_log, 'a') 85*6236dae4SAndroid Build Coastguard Worker self._process = subprocess.Popen(args=args, cwd=self._caddy_dir, stderr=caddyerr) 86*6236dae4SAndroid Build Coastguard Worker if self._process.returncode is not None: 87*6236dae4SAndroid Build Coastguard Worker return False 88*6236dae4SAndroid Build Coastguard Worker return not wait_live or self.wait_live(timeout=timedelta(seconds=5)) 89*6236dae4SAndroid Build Coastguard Worker 90*6236dae4SAndroid Build Coastguard Worker def stop_if_running(self): 91*6236dae4SAndroid Build Coastguard Worker if self.is_running(): 92*6236dae4SAndroid Build Coastguard Worker return self.stop() 93*6236dae4SAndroid Build Coastguard Worker return True 94*6236dae4SAndroid Build Coastguard Worker 95*6236dae4SAndroid Build Coastguard Worker def stop(self, wait_dead=True): 96*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 97*6236dae4SAndroid Build Coastguard Worker if self._process: 98*6236dae4SAndroid Build Coastguard Worker self._process.terminate() 99*6236dae4SAndroid Build Coastguard Worker self._process.wait(timeout=2) 100*6236dae4SAndroid Build Coastguard Worker self._process = None 101*6236dae4SAndroid Build Coastguard Worker return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5)) 102*6236dae4SAndroid Build Coastguard Worker return True 103*6236dae4SAndroid Build Coastguard Worker 104*6236dae4SAndroid Build Coastguard Worker def restart(self): 105*6236dae4SAndroid Build Coastguard Worker self.stop() 106*6236dae4SAndroid Build Coastguard Worker return self.start() 107*6236dae4SAndroid Build Coastguard Worker 108*6236dae4SAndroid Build Coastguard Worker def wait_dead(self, timeout: timedelta): 109*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 110*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 111*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 112*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.domain1}:{self.port}/' 113*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url) 114*6236dae4SAndroid Build Coastguard Worker if r.exit_code != 0: 115*6236dae4SAndroid Build Coastguard Worker return True 116*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for caddy to stop responding: {r}') 117*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 118*6236dae4SAndroid Build Coastguard Worker log.debug(f"Server still responding after {timeout}") 119*6236dae4SAndroid Build Coastguard Worker return False 120*6236dae4SAndroid Build Coastguard Worker 121*6236dae4SAndroid Build Coastguard Worker def wait_live(self, timeout: timedelta): 122*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 123*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 124*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 125*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.domain1}:{self.port}/' 126*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url) 127*6236dae4SAndroid Build Coastguard Worker if r.exit_code == 0: 128*6236dae4SAndroid Build Coastguard Worker return True 129*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 130*6236dae4SAndroid Build Coastguard Worker log.error(f"Caddy still not responding after {timeout}") 131*6236dae4SAndroid Build Coastguard Worker return False 132*6236dae4SAndroid Build Coastguard Worker 133*6236dae4SAndroid Build Coastguard Worker def _rmf(self, path): 134*6236dae4SAndroid Build Coastguard Worker if os.path.exists(path): 135*6236dae4SAndroid Build Coastguard Worker return os.remove(path) 136*6236dae4SAndroid Build Coastguard Worker 137*6236dae4SAndroid Build Coastguard Worker def _mkpath(self, path): 138*6236dae4SAndroid Build Coastguard Worker if not os.path.exists(path): 139*6236dae4SAndroid Build Coastguard Worker return os.makedirs(path) 140*6236dae4SAndroid Build Coastguard Worker 141*6236dae4SAndroid Build Coastguard Worker def _write_config(self): 142*6236dae4SAndroid Build Coastguard Worker domain1 = self.env.domain1 143*6236dae4SAndroid Build Coastguard Worker creds1 = self.env.get_credentials(domain1) 144*6236dae4SAndroid Build Coastguard Worker assert creds1 # convince pytype this isn't None 145*6236dae4SAndroid Build Coastguard Worker domain2 = self.env.domain2 146*6236dae4SAndroid Build Coastguard Worker creds2 = self.env.get_credentials(domain2) 147*6236dae4SAndroid Build Coastguard Worker assert creds2 # convince pytype this isn't None 148*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._docs_dir) 149*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 150*6236dae4SAndroid Build Coastguard Worker with open(os.path.join(self._docs_dir, 'data.json'), 'w') as fd: 151*6236dae4SAndroid Build Coastguard Worker data = { 152*6236dae4SAndroid Build Coastguard Worker 'server': f'{domain1}', 153*6236dae4SAndroid Build Coastguard Worker } 154*6236dae4SAndroid Build Coastguard Worker fd.write(JSONEncoder().encode(data)) 155*6236dae4SAndroid Build Coastguard Worker with open(self._conf_file, 'w') as fd: 156*6236dae4SAndroid Build Coastguard Worker conf = [ # base server config 157*6236dae4SAndroid Build Coastguard Worker '{', 158*6236dae4SAndroid Build Coastguard Worker f' http_port {self.env.caddy_http_port}', 159*6236dae4SAndroid Build Coastguard Worker f' https_port {self.env.caddy_https_port}', 160*6236dae4SAndroid Build Coastguard Worker f' servers :{self.env.caddy_https_port} {{', 161*6236dae4SAndroid Build Coastguard Worker ' protocols h3 h2 h1', 162*6236dae4SAndroid Build Coastguard Worker ' }', 163*6236dae4SAndroid Build Coastguard Worker '}', 164*6236dae4SAndroid Build Coastguard Worker f'{domain1}:{self.env.caddy_https_port} {{', 165*6236dae4SAndroid Build Coastguard Worker ' file_server * {', 166*6236dae4SAndroid Build Coastguard Worker f' root {self._docs_dir}', 167*6236dae4SAndroid Build Coastguard Worker ' }', 168*6236dae4SAndroid Build Coastguard Worker f' tls {creds1.cert_file} {creds1.pkey_file}', 169*6236dae4SAndroid Build Coastguard Worker '}', 170*6236dae4SAndroid Build Coastguard Worker f'{domain2} {{', 171*6236dae4SAndroid Build Coastguard Worker f' reverse_proxy /* http://localhost:{self.env.http_port} {{', 172*6236dae4SAndroid Build Coastguard Worker ' }', 173*6236dae4SAndroid Build Coastguard Worker f' tls {creds2.cert_file} {creds2.pkey_file}', 174*6236dae4SAndroid Build Coastguard Worker '}', 175*6236dae4SAndroid Build Coastguard Worker ] 176*6236dae4SAndroid Build Coastguard Worker fd.write("\n".join(conf)) 177