We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Support config fragments for docker image
**Description:**

I'm using the very nice docker image of synapse and would like to follow the default configuration and only override specific parts of it. It seems that synapse supports config fragments in a directory (which the debian package seems to be [using](https://github.com/matrix-org/synapse/blob/master/debian/matrix-synapse.service#L10) ), but the docker image runs [start.py](https://github.com/matrix-org/synapse/blob/master/docker/start.py#L250) which doesn't have the relevant flags and doesn't allow passing them in.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `docker/start.py`
Content:
```
1 #!/usr/local/bin/python
2 
3 import codecs
4 import glob
5 import os
6 import subprocess
7 import sys
8 
9 import jinja2
10 
11 
12 # Utility functions
13 def log(txt):
14     print(txt, file=sys.stderr)
15 
16 
17 def error(txt):
18     log(txt)
19     sys.exit(2)
20 
21 
22 def convert(src, dst, environ):
23     """Generate a file from a template
24 
25     Args:
26         src (str): path to input file
27         dst (str): path to file to write
28         environ (dict): environment dictionary, for replacement mappings.
29     """
30     with open(src) as infile:
31         template = infile.read()
32     rendered = jinja2.Template(template).render(**environ)
33     with open(dst, "w") as outfile:
34         outfile.write(rendered)
35 
36 
37 def generate_config_from_template(config_dir, config_path, environ, ownership):
38     """Generate a homeserver.yaml from environment variables
39 
40     Args:
41         config_dir (str): where to put generated config files
42         config_path (str): where to put the main config file
43         environ (dict): environment dictionary
44         ownership (str|None): "<user>:<group>" string which will be used to set
45             ownership of the generated configs. If None, ownership will not change.
46     """
47     for v in ("SYNAPSE_SERVER_NAME", "SYNAPSE_REPORT_STATS"):
48         if v not in environ:
49             error(
50                 "Environment variable '%s' is mandatory when generating a config file."
51                 % (v,)
52             )
53 
54     # populate some params from data files (if they exist, else create new ones)
55     environ = environ.copy()
56     secrets = {
57         "registration": "SYNAPSE_REGISTRATION_SHARED_SECRET",
58         "macaroon": "SYNAPSE_MACAROON_SECRET_KEY",
59     }
60 
61     for name, secret in secrets.items():
62         if secret not in environ:
63             filename = "/data/%s.%s.key" % (environ["SYNAPSE_SERVER_NAME"], name)
64 
65             # if the file already exists, load in the existing value; otherwise,
66             # generate a new secret and write it to a file
67 
68             if os.path.exists(filename):
69                 log("Reading %s from %s" % (secret, filename))
70                 with open(filename) as handle:
71                     value = handle.read()
72             else:
73                 log("Generating a random secret for {}".format(secret))
74                 value = codecs.encode(os.urandom(32), "hex").decode()
75                 with open(filename, "w") as handle:
76                     handle.write(value)
77             environ[secret] = value
78 
79     environ["SYNAPSE_APPSERVICES"] = glob.glob("/data/appservices/*.yaml")
80     if not os.path.exists(config_dir):
81         os.mkdir(config_dir)
82 
83     # Convert SYNAPSE_NO_TLS to boolean if exists
84     if "SYNAPSE_NO_TLS" in environ:
85         tlsanswerstring = str.lower(environ["SYNAPSE_NO_TLS"])
86         if tlsanswerstring in ("true", "on", "1", "yes"):
87             environ["SYNAPSE_NO_TLS"] = True
88         else:
89             if tlsanswerstring in ("false", "off", "0", "no"):
90                 environ["SYNAPSE_NO_TLS"] = False
91             else:
92                 error(
93                     'Environment variable "SYNAPSE_NO_TLS" found but value "'
94                     + tlsanswerstring
95                     + '" unrecognized; exiting.'
96                 )
97 
98     if "SYNAPSE_LOG_CONFIG" not in environ:
99         environ["SYNAPSE_LOG_CONFIG"] = config_dir + "/log.config"
100 
101     log("Generating synapse config file " + config_path)
102     convert("/conf/homeserver.yaml", config_path, environ)
103 
104     log_config_file = environ["SYNAPSE_LOG_CONFIG"]
105     log("Generating log config file " + log_config_file)
106     convert("/conf/log.config", log_config_file, environ)
107 
108     # Hopefully we already have a signing key, but generate one if not.
109     args = [
110         "python",
111         "-m",
112         "synapse.app.homeserver",
113         "--config-path",
114         config_path,
115         # tell synapse to put generated keys in /data rather than /compiled
116         "--keys-directory",
117         config_dir,
118         "--generate-keys",
119     ]
120 
121     if ownership is not None:
122         subprocess.check_output(["chown", "-R", ownership, "/data"])
123         args = ["gosu", ownership] + args
124 
125     subprocess.check_output(args)
126 
127 
128 def run_generate_config(environ, ownership):
129     """Run synapse with a --generate-config param to generate a template config file
130 
131     Args:
132         environ (dict): env var dict
133         ownership (str|None): "userid:groupid" arg for chmod. If None, ownership will not change.
134 
135     Never returns.
136     """
137     for v in ("SYNAPSE_SERVER_NAME", "SYNAPSE_REPORT_STATS"):
138         if v not in environ:
139             error("Environment variable '%s' is mandatory in `generate` mode." % (v,))
140 
141     server_name = environ["SYNAPSE_SERVER_NAME"]
142     config_dir = environ.get("SYNAPSE_CONFIG_DIR", "/data")
143     config_path = environ.get("SYNAPSE_CONFIG_PATH", config_dir + "/homeserver.yaml")
144     data_dir = environ.get("SYNAPSE_DATA_DIR", "/data")
145 
146     # create a suitable log config from our template
147     log_config_file = "%s/%s.log.config" % (config_dir, server_name)
148     if not os.path.exists(log_config_file):
149         log("Creating log config %s" % (log_config_file,))
150         convert("/conf/log.config", log_config_file, environ)
151 
152     args = [
153         "python",
154         "-m",
155         "synapse.app.homeserver",
156         "--server-name",
157         server_name,
158         "--report-stats",
159         environ["SYNAPSE_REPORT_STATS"],
160         "--config-path",
161         config_path,
162         "--config-directory",
163         config_dir,
164         "--data-directory",
165         data_dir,
166         "--generate-config",
167         "--open-private-ports",
168     ]
169     # log("running %s" % (args, ))
170 
171     if ownership is not None:
172         # make sure that synapse has perms to write to the data dir.
173         subprocess.check_output(["chown", ownership, data_dir])
174 
175         args = ["gosu", ownership] + args
176         os.execv("/usr/sbin/gosu", args)
177     else:
178         os.execv("/usr/local/bin/python", args)
179 
180 
181 def main(args, environ):
182     mode = args[1] if len(args) > 1 else None
183     desired_uid = int(environ.get("UID", "991"))
184     desired_gid = int(environ.get("GID", "991"))
185     synapse_worker = environ.get("SYNAPSE_WORKER", "synapse.app.homeserver")
186     if (desired_uid == os.getuid()) and (desired_gid == os.getgid()):
187         ownership = None
188     else:
189         ownership = "{}:{}".format(desired_uid, desired_gid)
190 
191     if ownership is None:
192         log("Will not perform chmod/gosu as UserID already matches request")
193 
194     # In generate mode, generate a configuration and missing keys, then exit
195     if mode == "generate":
196         return run_generate_config(environ, ownership)
197 
198     if mode == "migrate_config":
199         # generate a config based on environment vars.
200         config_dir = environ.get("SYNAPSE_CONFIG_DIR", "/data")
201         config_path = environ.get(
202             "SYNAPSE_CONFIG_PATH", config_dir + "/homeserver.yaml"
203         )
204         return generate_config_from_template(
205             config_dir, config_path, environ, ownership
206         )
207 
208     if mode is not None:
209         error("Unknown execution mode '%s'" % (mode,))
210 
211     config_dir = environ.get("SYNAPSE_CONFIG_DIR", "/data")
212     config_path = environ.get("SYNAPSE_CONFIG_PATH", config_dir + "/homeserver.yaml")
213 
214     if not os.path.exists(config_path):
215         if "SYNAPSE_SERVER_NAME" in environ:
216             error(
217                 """\
218 Config file '%s' does not exist.
219 
220 The synapse docker image no longer supports generating a config file on-the-fly
221 based on environment variables. You can migrate to a static config file by
222 running with 'migrate_config'. See the README for more details.
223 """
224                 % (config_path,)
225             )
226 
227         error(
228             "Config file '%s' does not exist. You should either create a new "
229             "config file by running with the `generate` argument (and then edit "
230             "the resulting file before restarting) or specify the path to an "
231             "existing config file with the SYNAPSE_CONFIG_PATH variable."
232             % (config_path,)
233         )
234 
235     log("Starting synapse with config file " + config_path)
236 
237     args = ["python", "-m", synapse_worker, "--config-path", config_path]
238     if ownership is not None:
239         args = ["gosu", ownership] + args
240         os.execv("/usr/sbin/gosu", args)
241     else:
242         os.execv("/usr/local/bin/python", args)
243 
244 
245 if __name__ == "__main__":
246     main(sys.argv, os.environ)
247
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/docker/start.py b/docker/start.py
--- a/docker/start.py
+++ b/docker/start.py
@@ -179,7 +179,7 @@
 
 
 def main(args, environ):
-    mode = args[1] if len(args) > 1 else None
+    mode = args[1] if len(args) > 1 else "run"
     desired_uid = int(environ.get("UID", "991"))
     desired_gid = int(environ.get("GID", "991"))
     synapse_worker = environ.get("SYNAPSE_WORKER", "synapse.app.homeserver")
@@ -205,36 +205,47 @@
             config_dir, config_path, environ, ownership
         )
 
-    if mode is not None:
+    if mode != "run":
         error("Unknown execution mode '%s'" % (mode,))
 
-    config_dir = environ.get("SYNAPSE_CONFIG_DIR", "/data")
-    config_path = environ.get("SYNAPSE_CONFIG_PATH", config_dir + "/homeserver.yaml")
+    args = args[2:]
 
-    if not os.path.exists(config_path):
-        if "SYNAPSE_SERVER_NAME" in environ:
-            error(
-                """\
+    if "-m" not in args:
+        args = ["-m", synapse_worker] + args
+
+    # if there are no config files passed to synapse, try adding the default file
+    if not any(p.startswith("--config-path") or p.startswith("-c") for p in args):
+        config_dir = environ.get("SYNAPSE_CONFIG_DIR", "/data")
+        config_path = environ.get(
+            "SYNAPSE_CONFIG_PATH", config_dir + "/homeserver.yaml"
+        )
+
+        if not os.path.exists(config_path):
+            if "SYNAPSE_SERVER_NAME" in environ:
+                error(
+                    """\
 Config file '%s' does not exist.
 
 The synapse docker image no longer supports generating a config file on-the-fly
 based on environment variables. You can migrate to a static config file by
 running with 'migrate_config'. See the README for more details.
 """
+                    % (config_path,)
+                )
+
+            error(
+                "Config file '%s' does not exist. You should either create a new "
+                "config file by running with the `generate` argument (and then edit "
+                "the resulting file before restarting) or specify the path to an "
+                "existing config file with the SYNAPSE_CONFIG_PATH variable."
                 % (config_path,)
             )
 
-        error(
-            "Config file '%s' does not exist. You should either create a new "
-            "config file by running with the `generate` argument (and then edit "
-            "the resulting file before restarting) or specify the path to an "
-            "existing config file with the SYNAPSE_CONFIG_PATH variable."
-            % (config_path,)
-        )
+        args += ["--config-path", config_path]
 
-    log("Starting synapse with config file " + config_path)
+    log("Starting synapse with args " + " ".join(args))
 
-    args = ["python", "-m", synapse_worker, "--config-path", config_path]
+    args = ["python"] + args
     if ownership is not None:
         args = ["gosu", ownership] + args
         os.execv("/usr/sbin/gosu", args)

{"golden_diff": "diff --git a/docker/start.py b/docker/start.py\n--- a/docker/start.py\n+++ b/docker/start.py\n@@ -179,7 +179,7 @@\n \n \n def main(args, environ):\n-    mode = args[1] if len(args) > 1 else None\n+    mode = args[1] if len(args) > 1 else \"run\"\n     desired_uid = int(environ.get(\"UID\", \"991\"))\n     desired_gid = int(environ.get(\"GID\", \"991\"))\n     synapse_worker = environ.get(\"SYNAPSE_WORKER\", \"synapse.app.homeserver\")\n@@ -205,36 +205,47 @@\n             config_dir, config_path, environ, ownership\n         )\n \n-    if mode is not None:\n+    if mode != \"run\":\n         error(\"Unknown execution mode '%s'\" % (mode,))\n \n-    config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n-    config_path = environ.get(\"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\")\n+    args = args[2:]\n \n-    if not os.path.exists(config_path):\n-        if \"SYNAPSE_SERVER_NAME\" in environ:\n-            error(\n-                \"\"\"\\\n+    if \"-m\" not in args:\n+        args = [\"-m\", synapse_worker] + args\n+\n+    # if there are no config files passed to synapse, try adding the default file\n+    if not any(p.startswith(\"--config-path\") or p.startswith(\"-c\") for p in args):\n+        config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n+        config_path = environ.get(\n+            \"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\"\n+        )\n+\n+        if not os.path.exists(config_path):\n+            if \"SYNAPSE_SERVER_NAME\" in environ:\n+                error(\n+                    \"\"\"\\\n Config file '%s' does not exist.\n \n The synapse docker image no longer supports generating a config file on-the-fly\n based on environment variables. You can migrate to a static config file by\n running with 'migrate_config'. See the README for more details.\n \"\"\"\n+                    % (config_path,)\n+                )\n+\n+            error(\n+                \"Config file '%s' does not exist. You should either create a new \"\n+                \"config file by running with the `generate` argument (and then edit \"\n+                \"the resulting file before restarting) or specify the path to an \"\n+                \"existing config file with the SYNAPSE_CONFIG_PATH variable.\"\n                 % (config_path,)\n             )\n \n-        error(\n-            \"Config file '%s' does not exist. You should either create a new \"\n-            \"config file by running with the `generate` argument (and then edit \"\n-            \"the resulting file before restarting) or specify the path to an \"\n-            \"existing config file with the SYNAPSE_CONFIG_PATH variable.\"\n-            % (config_path,)\n-        )\n+        args += [\"--config-path\", config_path]\n \n-    log(\"Starting synapse with config file \" + config_path)\n+    log(\"Starting synapse with args \" + \" \".join(args))\n \n-    args = [\"python\", \"-m\", synapse_worker, \"--config-path\", config_path]\n+    args = [\"python\"] + args\n     if ownership is not None:\n         args = [\"gosu\", ownership] + args\n         os.execv(\"/usr/sbin/gosu\", args)\n", "issue": "Support config fragments for docker image\n**Description:**\r\n\r\nI'm using the very nice docker image of synapse and would like to follow the default configuration and only override specific parts of it. It seems that synapse supports config fragments in a directory (which the debian package seems to be [using](https://github.com/matrix-org/synapse/blob/master/debian/matrix-synapse.service#L10) ), but the docker image runs [start.py](https://github.com/matrix-org/synapse/blob/master/docker/start.py#L250) which doesn't have the relevant flags and doesn't allow passing them in.\n", "before_files": [{"content": "#!/usr/local/bin/python\n\nimport codecs\nimport glob\nimport os\nimport subprocess\nimport sys\n\nimport jinja2\n\n\n# Utility functions\ndef log(txt):\n    print(txt, file=sys.stderr)\n\n\ndef error(txt):\n    log(txt)\n    sys.exit(2)\n\n\ndef convert(src, dst, environ):\n    \"\"\"Generate a file from a template\n\n    Args:\n        src (str): path to input file\n        dst (str): path to file to write\n        environ (dict): environment dictionary, for replacement mappings.\n    \"\"\"\n    with open(src) as infile:\n        template = infile.read()\n    rendered = jinja2.Template(template).render(**environ)\n    with open(dst, \"w\") as outfile:\n        outfile.write(rendered)\n\n\ndef generate_config_from_template(config_dir, config_path, environ, ownership):\n    \"\"\"Generate a homeserver.yaml from environment variables\n\n    Args:\n        config_dir (str): where to put generated config files\n        config_path (str): where to put the main config file\n        environ (dict): environment dictionary\n        ownership (str|None): \"<user>:<group>\" string which will be used to set\n            ownership of the generated configs. If None, ownership will not change.\n    \"\"\"\n    for v in (\"SYNAPSE_SERVER_NAME\", \"SYNAPSE_REPORT_STATS\"):\n        if v not in environ:\n            error(\n                \"Environment variable '%s' is mandatory when generating a config file.\"\n                % (v,)\n            )\n\n    # populate some params from data files (if they exist, else create new ones)\n    environ = environ.copy()\n    secrets = {\n        \"registration\": \"SYNAPSE_REGISTRATION_SHARED_SECRET\",\n        \"macaroon\": \"SYNAPSE_MACAROON_SECRET_KEY\",\n    }\n\n    for name, secret in secrets.items():\n        if secret not in environ:\n            filename = \"/data/%s.%s.key\" % (environ[\"SYNAPSE_SERVER_NAME\"], name)\n\n            # if the file already exists, load in the existing value; otherwise,\n            # generate a new secret and write it to a file\n\n            if os.path.exists(filename):\n                log(\"Reading %s from %s\" % (secret, filename))\n                with open(filename) as handle:\n                    value = handle.read()\n            else:\n                log(\"Generating a random secret for {}\".format(secret))\n                value = codecs.encode(os.urandom(32), \"hex\").decode()\n                with open(filename, \"w\") as handle:\n                    handle.write(value)\n            environ[secret] = value\n\n    environ[\"SYNAPSE_APPSERVICES\"] = glob.glob(\"/data/appservices/*.yaml\")\n    if not os.path.exists(config_dir):\n        os.mkdir(config_dir)\n\n    # Convert SYNAPSE_NO_TLS to boolean if exists\n    if \"SYNAPSE_NO_TLS\" in environ:\n        tlsanswerstring = str.lower(environ[\"SYNAPSE_NO_TLS\"])\n        if tlsanswerstring in (\"true\", \"on\", \"1\", \"yes\"):\n            environ[\"SYNAPSE_NO_TLS\"] = True\n        else:\n            if tlsanswerstring in (\"false\", \"off\", \"0\", \"no\"):\n                environ[\"SYNAPSE_NO_TLS\"] = False\n            else:\n                error(\n                    'Environment variable \"SYNAPSE_NO_TLS\" found but value \"'\n                    + tlsanswerstring\n                    + '\" unrecognized; exiting.'\n                )\n\n    if \"SYNAPSE_LOG_CONFIG\" not in environ:\n        environ[\"SYNAPSE_LOG_CONFIG\"] = config_dir + \"/log.config\"\n\n    log(\"Generating synapse config file \" + config_path)\n    convert(\"/conf/homeserver.yaml\", config_path, environ)\n\n    log_config_file = environ[\"SYNAPSE_LOG_CONFIG\"]\n    log(\"Generating log config file \" + log_config_file)\n    convert(\"/conf/log.config\", log_config_file, environ)\n\n    # Hopefully we already have a signing key, but generate one if not.\n    args = [\n        \"python\",\n        \"-m\",\n        \"synapse.app.homeserver\",\n        \"--config-path\",\n        config_path,\n        # tell synapse to put generated keys in /data rather than /compiled\n        \"--keys-directory\",\n        config_dir,\n        \"--generate-keys\",\n    ]\n\n    if ownership is not None:\n        subprocess.check_output([\"chown\", \"-R\", ownership, \"/data\"])\n        args = [\"gosu\", ownership] + args\n\n    subprocess.check_output(args)\n\n\ndef run_generate_config(environ, ownership):\n    \"\"\"Run synapse with a --generate-config param to generate a template config file\n\n    Args:\n        environ (dict): env var dict\n        ownership (str|None): \"userid:groupid\" arg for chmod. If None, ownership will not change.\n\n    Never returns.\n    \"\"\"\n    for v in (\"SYNAPSE_SERVER_NAME\", \"SYNAPSE_REPORT_STATS\"):\n        if v not in environ:\n            error(\"Environment variable '%s' is mandatory in `generate` mode.\" % (v,))\n\n    server_name = environ[\"SYNAPSE_SERVER_NAME\"]\n    config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n    config_path = environ.get(\"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\")\n    data_dir = environ.get(\"SYNAPSE_DATA_DIR\", \"/data\")\n\n    # create a suitable log config from our template\n    log_config_file = \"%s/%s.log.config\" % (config_dir, server_name)\n    if not os.path.exists(log_config_file):\n        log(\"Creating log config %s\" % (log_config_file,))\n        convert(\"/conf/log.config\", log_config_file, environ)\n\n    args = [\n        \"python\",\n        \"-m\",\n        \"synapse.app.homeserver\",\n        \"--server-name\",\n        server_name,\n        \"--report-stats\",\n        environ[\"SYNAPSE_REPORT_STATS\"],\n        \"--config-path\",\n        config_path,\n        \"--config-directory\",\n        config_dir,\n        \"--data-directory\",\n        data_dir,\n        \"--generate-config\",\n        \"--open-private-ports\",\n    ]\n    # log(\"running %s\" % (args, ))\n\n    if ownership is not None:\n        # make sure that synapse has perms to write to the data dir.\n        subprocess.check_output([\"chown\", ownership, data_dir])\n\n        args = [\"gosu\", ownership] + args\n        os.execv(\"/usr/sbin/gosu\", args)\n    else:\n        os.execv(\"/usr/local/bin/python\", args)\n\n\ndef main(args, environ):\n    mode = args[1] if len(args) > 1 else None\n    desired_uid = int(environ.get(\"UID\", \"991\"))\n    desired_gid = int(environ.get(\"GID\", \"991\"))\n    synapse_worker = environ.get(\"SYNAPSE_WORKER\", \"synapse.app.homeserver\")\n    if (desired_uid == os.getuid()) and (desired_gid == os.getgid()):\n        ownership = None\n    else:\n        ownership = \"{}:{}\".format(desired_uid, desired_gid)\n\n    if ownership is None:\n        log(\"Will not perform chmod/gosu as UserID already matches request\")\n\n    # In generate mode, generate a configuration and missing keys, then exit\n    if mode == \"generate\":\n        return run_generate_config(environ, ownership)\n\n    if mode == \"migrate_config\":\n        # generate a config based on environment vars.\n        config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n        config_path = environ.get(\n            \"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\"\n        )\n        return generate_config_from_template(\n            config_dir, config_path, environ, ownership\n        )\n\n    if mode is not None:\n        error(\"Unknown execution mode '%s'\" % (mode,))\n\n    config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n    config_path = environ.get(\"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\")\n\n    if not os.path.exists(config_path):\n        if \"SYNAPSE_SERVER_NAME\" in environ:\n            error(\n                \"\"\"\\\nConfig file '%s' does not exist.\n\nThe synapse docker image no longer supports generating a config file on-the-fly\nbased on environment variables. You can migrate to a static config file by\nrunning with 'migrate_config'. See the README for more details.\n\"\"\"\n                % (config_path,)\n            )\n\n        error(\n            \"Config file '%s' does not exist. You should either create a new \"\n            \"config file by running with the `generate` argument (and then edit \"\n            \"the resulting file before restarting) or specify the path to an \"\n            \"existing config file with the SYNAPSE_CONFIG_PATH variable.\"\n            % (config_path,)\n        )\n\n    log(\"Starting synapse with config file \" + config_path)\n\n    args = [\"python\", \"-m\", synapse_worker, \"--config-path\", config_path]\n    if ownership is not None:\n        args = [\"gosu\", ownership] + args\n        os.execv(\"/usr/sbin/gosu\", args)\n    else:\n        os.execv(\"/usr/local/bin/python\", args)\n\n\nif __name__ == \"__main__\":\n    main(sys.argv, os.environ)\n", "path": "docker/start.py"}], "after_files": [{"content": "#!/usr/local/bin/python\n\nimport codecs\nimport glob\nimport os\nimport subprocess\nimport sys\n\nimport jinja2\n\n\n# Utility functions\ndef log(txt):\n    print(txt, file=sys.stderr)\n\n\ndef error(txt):\n    log(txt)\n    sys.exit(2)\n\n\ndef convert(src, dst, environ):\n    \"\"\"Generate a file from a template\n\n    Args:\n        src (str): path to input file\n        dst (str): path to file to write\n        environ (dict): environment dictionary, for replacement mappings.\n    \"\"\"\n    with open(src) as infile:\n        template = infile.read()\n    rendered = jinja2.Template(template).render(**environ)\n    with open(dst, \"w\") as outfile:\n        outfile.write(rendered)\n\n\ndef generate_config_from_template(config_dir, config_path, environ, ownership):\n    \"\"\"Generate a homeserver.yaml from environment variables\n\n    Args:\n        config_dir (str): where to put generated config files\n        config_path (str): where to put the main config file\n        environ (dict): environment dictionary\n        ownership (str|None): \"<user>:<group>\" string which will be used to set\n            ownership of the generated configs. If None, ownership will not change.\n    \"\"\"\n    for v in (\"SYNAPSE_SERVER_NAME\", \"SYNAPSE_REPORT_STATS\"):\n        if v not in environ:\n            error(\n                \"Environment variable '%s' is mandatory when generating a config file.\"\n                % (v,)\n            )\n\n    # populate some params from data files (if they exist, else create new ones)\n    environ = environ.copy()\n    secrets = {\n        \"registration\": \"SYNAPSE_REGISTRATION_SHARED_SECRET\",\n        \"macaroon\": \"SYNAPSE_MACAROON_SECRET_KEY\",\n    }\n\n    for name, secret in secrets.items():\n        if secret not in environ:\n            filename = \"/data/%s.%s.key\" % (environ[\"SYNAPSE_SERVER_NAME\"], name)\n\n            # if the file already exists, load in the existing value; otherwise,\n            # generate a new secret and write it to a file\n\n            if os.path.exists(filename):\n                log(\"Reading %s from %s\" % (secret, filename))\n                with open(filename) as handle:\n                    value = handle.read()\n            else:\n                log(\"Generating a random secret for {}\".format(secret))\n                value = codecs.encode(os.urandom(32), \"hex\").decode()\n                with open(filename, \"w\") as handle:\n                    handle.write(value)\n            environ[secret] = value\n\n    environ[\"SYNAPSE_APPSERVICES\"] = glob.glob(\"/data/appservices/*.yaml\")\n    if not os.path.exists(config_dir):\n        os.mkdir(config_dir)\n\n    # Convert SYNAPSE_NO_TLS to boolean if exists\n    if \"SYNAPSE_NO_TLS\" in environ:\n        tlsanswerstring = str.lower(environ[\"SYNAPSE_NO_TLS\"])\n        if tlsanswerstring in (\"true\", \"on\", \"1\", \"yes\"):\n            environ[\"SYNAPSE_NO_TLS\"] = True\n        else:\n            if tlsanswerstring in (\"false\", \"off\", \"0\", \"no\"):\n                environ[\"SYNAPSE_NO_TLS\"] = False\n            else:\n                error(\n                    'Environment variable \"SYNAPSE_NO_TLS\" found but value \"'\n                    + tlsanswerstring\n                    + '\" unrecognized; exiting.'\n                )\n\n    if \"SYNAPSE_LOG_CONFIG\" not in environ:\n        environ[\"SYNAPSE_LOG_CONFIG\"] = config_dir + \"/log.config\"\n\n    log(\"Generating synapse config file \" + config_path)\n    convert(\"/conf/homeserver.yaml\", config_path, environ)\n\n    log_config_file = environ[\"SYNAPSE_LOG_CONFIG\"]\n    log(\"Generating log config file \" + log_config_file)\n    convert(\"/conf/log.config\", log_config_file, environ)\n\n    # Hopefully we already have a signing key, but generate one if not.\n    args = [\n        \"python\",\n        \"-m\",\n        \"synapse.app.homeserver\",\n        \"--config-path\",\n        config_path,\n        # tell synapse to put generated keys in /data rather than /compiled\n        \"--keys-directory\",\n        config_dir,\n        \"--generate-keys\",\n    ]\n\n    if ownership is not None:\n        subprocess.check_output([\"chown\", \"-R\", ownership, \"/data\"])\n        args = [\"gosu\", ownership] + args\n\n    subprocess.check_output(args)\n\n\ndef run_generate_config(environ, ownership):\n    \"\"\"Run synapse with a --generate-config param to generate a template config file\n\n    Args:\n        environ (dict): env var dict\n        ownership (str|None): \"userid:groupid\" arg for chmod. If None, ownership will not change.\n\n    Never returns.\n    \"\"\"\n    for v in (\"SYNAPSE_SERVER_NAME\", \"SYNAPSE_REPORT_STATS\"):\n        if v not in environ:\n            error(\"Environment variable '%s' is mandatory in `generate` mode.\" % (v,))\n\n    server_name = environ[\"SYNAPSE_SERVER_NAME\"]\n    config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n    config_path = environ.get(\"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\")\n    data_dir = environ.get(\"SYNAPSE_DATA_DIR\", \"/data\")\n\n    # create a suitable log config from our template\n    log_config_file = \"%s/%s.log.config\" % (config_dir, server_name)\n    if not os.path.exists(log_config_file):\n        log(\"Creating log config %s\" % (log_config_file,))\n        convert(\"/conf/log.config\", log_config_file, environ)\n\n    args = [\n        \"python\",\n        \"-m\",\n        \"synapse.app.homeserver\",\n        \"--server-name\",\n        server_name,\n        \"--report-stats\",\n        environ[\"SYNAPSE_REPORT_STATS\"],\n        \"--config-path\",\n        config_path,\n        \"--config-directory\",\n        config_dir,\n        \"--data-directory\",\n        data_dir,\n        \"--generate-config\",\n        \"--open-private-ports\",\n    ]\n    # log(\"running %s\" % (args, ))\n\n    if ownership is not None:\n        # make sure that synapse has perms to write to the data dir.\n        subprocess.check_output([\"chown\", ownership, data_dir])\n\n        args = [\"gosu\", ownership] + args\n        os.execv(\"/usr/sbin/gosu\", args)\n    else:\n        os.execv(\"/usr/local/bin/python\", args)\n\n\ndef main(args, environ):\n    mode = args[1] if len(args) > 1 else \"run\"\n    desired_uid = int(environ.get(\"UID\", \"991\"))\n    desired_gid = int(environ.get(\"GID\", \"991\"))\n    synapse_worker = environ.get(\"SYNAPSE_WORKER\", \"synapse.app.homeserver\")\n    if (desired_uid == os.getuid()) and (desired_gid == os.getgid()):\n        ownership = None\n    else:\n        ownership = \"{}:{}\".format(desired_uid, desired_gid)\n\n    if ownership is None:\n        log(\"Will not perform chmod/gosu as UserID already matches request\")\n\n    # In generate mode, generate a configuration and missing keys, then exit\n    if mode == \"generate\":\n        return run_generate_config(environ, ownership)\n\n    if mode == \"migrate_config\":\n        # generate a config based on environment vars.\n        config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n        config_path = environ.get(\n            \"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\"\n        )\n        return generate_config_from_template(\n            config_dir, config_path, environ, ownership\n        )\n\n    if mode != \"run\":\n        error(\"Unknown execution mode '%s'\" % (mode,))\n\n    args = args[2:]\n\n    if \"-m\" not in args:\n        args = [\"-m\", synapse_worker] + args\n\n    # if there are no config files passed to synapse, try adding the default file\n    if not any(p.startswith(\"--config-path\") or p.startswith(\"-c\") for p in args):\n        config_dir = environ.get(\"SYNAPSE_CONFIG_DIR\", \"/data\")\n        config_path = environ.get(\n            \"SYNAPSE_CONFIG_PATH\", config_dir + \"/homeserver.yaml\"\n        )\n\n        if not os.path.exists(config_path):\n            if \"SYNAPSE_SERVER_NAME\" in environ:\n                error(\n                    \"\"\"\\\nConfig file '%s' does not exist.\n\nThe synapse docker image no longer supports generating a config file on-the-fly\nbased on environment variables. You can migrate to a static config file by\nrunning with 'migrate_config'. See the README for more details.\n\"\"\"\n                    % (config_path,)\n                )\n\n            error(\n                \"Config file '%s' does not exist. You should either create a new \"\n                \"config file by running with the `generate` argument (and then edit \"\n                \"the resulting file before restarting) or specify the path to an \"\n                \"existing config file with the SYNAPSE_CONFIG_PATH variable.\"\n                % (config_path,)\n            )\n\n        args += [\"--config-path\", config_path]\n\n    log(\"Starting synapse with args \" + \" \".join(args))\n\n    args = [\"python\"] + args\n    if ownership is not None:\n        args = [\"gosu\", ownership] + args\n        os.execv(\"/usr/sbin/gosu\", args)\n    else:\n        os.execv(\"/usr/local/bin/python\", args)\n\n\nif __name__ == \"__main__\":\n    main(sys.argv, os.environ)\n", "path": "docker/start.py"}]}