summary refs log tree commit diff
path: root/docker/configure_workers_and_start.py
diff options
context:
space:
mode:
authorAndrew Morgan <andrew@amorgan.xyz>2020-12-15 19:15:55 +0000
committerAndrew Morgan <andrew@amorgan.xyz>2020-12-15 19:15:55 +0000
commit62ac8b9c0db62ba42934b432b10fa9d87ee68434 (patch)
tree94b779016fd1591a9a2e1d5f68448a03f3d27823 /docker/configure_workers_and_start.py
parentmajor wip (diff)
downloadsynapse-62ac8b9c0db62ba42934b432b10fa9d87ee68434.tar.xz
Get Synapse main and worker process startup working!
Diffstat (limited to 'docker/configure_workers_and_start.py')
-rwxr-xr-xdocker/configure_workers_and_start.py298
1 files changed, 298 insertions, 0 deletions
diff --git a/docker/configure_workers_and_start.py b/docker/configure_workers_and_start.py
new file mode 100755

index 0000000000..57ae5cac80 --- /dev/null +++ b/docker/configure_workers_and_start.py
@@ -0,0 +1,298 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright 2020 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script reads environment variables and generates a shared Synapse worker, +# nginx and supervisord configs depending on the workers requested + +import os +import sys +import subprocess +import jinja2 + + +# Utility functions +def log(txt): + print(txt) + + +def error(txt): + log(txt) + sys.exit(2) + + +def convert(src, dst, environ): + """Generate a file from a template + + Args: + src (str): path to input file + dst (str): path to file to write + environ (dict): environment dictionary, for replacement mappings. + """ + with open(src) as infile: + template = infile.read() + rendered = jinja2.Template(template, autoescape=True).render(**environ) + with open(dst, "w") as outfile: + outfile.write(rendered) + + +def generate_base_homeserver_config(): + """Starts Synapse and generates a basic homeserver config, which will later be + modified for worker support. + + Raises: CalledProcessError if calling start.py return a non-zero exit code. + """ + # start.py already does this for us, so just call that. + # note that this script is copied in in the official, monolith dockerfile + subprocess.check_output(["/usr/local/bin/python", "/start.py", "generate"]) + + +def generate_worker_files(environ, config_path: str, data_dir: str): + """Read the desired list of workers from environment variables and generate + shared homeserver, nginx and supervisord configs. + + Args: + environ: _Environ[str] + config_path: Where to output the generated Synapse main worker config file. + data_dir: The location of the synapse data directory. Where log and + user-facing config files live. + """ + # Note that yaml cares about indentation, so care should be taken to insert lines + # into files at the correct indentation below. + + # The contents of a Synapse config file that will be added alongside the generated + # config when running the main Synapse process. + # It is intended mainly for disabling functionality when certain workers are spun up. + homeserver_config = """ +redis: + enabled: true + +# TODO: remove before prod +suppress_key_server_warning: true + """ + + # The supervisord config + supervisord_config = """ +[supervisord] +nodaemon=true + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +priority=900 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +username=www-data +autorestart=true + +[program:synapse_main] +command=/usr/local/bin/python -m synapse.app.homeserver \ + --config-path="%s" \ + --config-path=/conf/workers/shared.yaml + +# Log startup failures to supervisord's stdout/err +# Regular synapse logs will still go in the configured data directory +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=unexpected +exitcodes=0 + + """ % (config_path,) + + # An nginx site config. Will live in /etc/nginx/conf.d + nginx_config_template_header = """ +server { + listen 80; + listen [::]:80; + + # For the federation port + listen 8448 default_server; + listen [::]:8448 default_server; + + server_name localhost; + """ + nginx_config_body = "" # to modify below + nginx_config_template_end = """ + # Send all other traffic to the main process + location ~* ^(\/_matrix|\/_synapse) { + proxy_pass http://localhost:8008; + proxy_set_header X-Forwarded-For $remote_addr; + + # TODO: Can we move this to the default nginx.conf so all locations are + # affected? + # + # Nginx by default only allows file uploads up to 1M in size + # Increase client_max_body_size to match max_upload_size defined in homeserver.yaml + client_max_body_size 50M; + } +} + """ + + # Read desired worker configuration from environment + if "SYNAPSE_WORKERS" not in environ: + error("Environment variable 'SYNAPSE_WORKERS' is mandatory.") + + worker_types = environ.get("SYNAPSE_WORKERS") + worker_types = worker_types.split(",") + + for worker_type in worker_types: + worker_type = worker_type.strip() + + if worker_type == "pusher": + # Disable push handling from the main process + homeserver_config += """ +start_pushers: false + """ + + # Enable the pusher worker in supervisord + supervisord_config += """ +[program:synapse_pusher] +command=/usr/local/bin/python -m synapse.app.pusher \ + --config-path="%s" \ + --config-path=/conf/workers/shared.yaml \ + --config-path=/conf/workers/pusher.yaml +autorestart=unexpected +exitcodes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + """ % (config_path,) + + # This worker does not handle any REST endpoints + + elif worker_type == "appservice": + # Disable appservice traffic sending from the main process + homeserver_config += """ + notify_appservices: false + """ + + # Enable the pusher worker in supervisord + supervisord_config += """ +[program:synapse_appservice] +command=/usr/local/bin/python -m synapse.app.appservice \ + --config-path="%s" \ + --config-path=/conf/workers/shared.yaml \ + --config-path=/conf/workers/appservice.yaml +autorestart=unexpected +exitcodes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + """ % (config_path,) + + # This worker does not handle any REST endpoints + + elif worker_type == "user_dir": + # Disable user directory updates on the main process + homeserver_config += """ +update_user_directory: false + """ + + # Enable the user directory worker in supervisord + supervisord_config += """ +[program:synapse_user_dir] +command=/usr/local/bin/python -m synapse.app.user_dir \ + --config-path="%s" \ + --config-path=/conf/workers/shared.yaml \ + --config-path=/conf/workers/user_dir.yaml +autorestart=unexpected +exitcodes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + """ % (config_path,) + + # Route user directory requests to this worker + nginx_config_body += """ + location ~* ^/_matrix/client/(api/v1|r0|unstable)/user_directory/search$ { + proxy_pass http://localhost:8010; + proxy_set_header X-Forwarded-For $remote_addr; + } + """ + + # Write out the config files + + # Shared homeserver config + print(homeserver_config) + with open("/conf/workers/shared.yaml", "w") as f: + f.write(homeserver_config) + + # Nginx config + print() + print(nginx_config_template_header) + print(nginx_config_body) + print(nginx_config_template_end) + with open("/etc/nginx/conf.d/matrix-synapse.conf", "w") as f: + f.write(nginx_config_template_header) + f.write(nginx_config_body) + f.write(nginx_config_template_end) + + # Supervisord config + print() + print(supervisord_config) + with open("/etc/supervisor/conf.d/supervisord.conf", "w") as f: + f.write(supervisord_config) + + # Generate worker log config files from the templates. + # The templates are mainly there so that we can inject some environment variable + # values into them. + log_config_template_dir = "/conf/workers/log_config_templates/" + log_config_dir = "/conf/workers/" + for log_config_filename in os.listdir(log_config_template_dir): + template_path = log_config_template_dir + log_config_filename + out_path = log_config_dir + log_config_filename + + convert(template_path, out_path, environ) + + # Ensure the logging directory exists + log_dir = data_dir + "/logs" + if not os.path.exists(log_dir): + os.mkdir(log_dir) + + +def start_supervisord(): + """Starts up supervisord which then starts and monitors all other necessary processes + + Raises: CalledProcessError if calling start.py return a non-zero exit code. + """ + subprocess.check_output(["/usr/bin/supervisord"]) + + +def main(args, environ): + config_dir = environ.get("SYNAPSE_CONFIG_DIR", "/data") + config_path = environ.get("SYNAPSE_CONFIG_PATH", config_dir + "/homeserver.yaml") + data_dir = environ.get("SYNAPSE_DATA_DIR", "/data") + + # Generate the base homeserver config if one does not yet exist + if not os.path.exists(config_path): + log("Generating base homeserver config") + generate_base_homeserver_config() + + # Always regenerate all other config files + generate_worker_files(environ, config_path, data_dir) + + # Start supervisord, which will start Synapse, all of the configured worker + # processes, redis, nginx etc. according to the config we created above. + start_supervisord() + + +if __name__ == "__main__": + main(sys.argv, os.environ)