diff --git a/changelog.d/4315.feature b/changelog.d/4315.feature
new file mode 100644
index 0000000000..23e82fd02d
--- /dev/null
+++ b/changelog.d/4315.feature
@@ -0,0 +1 @@
+Add a script to generate a clean config file
diff --git a/scripts/generate_config b/scripts/generate_config
new file mode 100755
index 0000000000..61c5f049e8
--- /dev/null
+++ b/scripts/generate_config
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+import argparse
+import sys
+
+from synapse.config.homeserver import HomeServerConfig
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--config-dir",
+ default="CONFDIR",
+
+ help="The path where the config files are kept. Used to create filenames for "
+ "things like the log config and the signing key. Default: %(default)s",
+ )
+
+ parser.add_argument(
+ "--data-dir",
+ default="DATADIR",
+ help="The path where the data files are kept. Used to create filenames for "
+ "things like the database and media store. Default: %(default)s",
+ )
+
+ parser.add_argument(
+ "--server-name",
+ default="SERVERNAME",
+ help="The server name. Used to initialise the server_name config param, but also "
+ "used in the names of some of the config files. Default: %(default)s",
+ )
+
+ parser.add_argument(
+ "--report-stats",
+ action="store",
+ help="Whether the generated config reports anonymized usage statistics",
+ choices=["yes", "no"],
+ )
+
+ parser.add_argument(
+ "--generate-secrets",
+ action="store_true",
+ help="Enable generation of new secrets for things like the macaroon_secret_key."
+ "By default, these parameters will be left unset."
+ )
+
+ parser.add_argument(
+ "-o", "--output-file",
+ type=argparse.FileType('w'),
+ default=sys.stdout,
+ help="File to write the configuration to. Default: stdout",
+ )
+
+ args = parser.parse_args()
+
+ report_stats = args.report_stats
+ if report_stats is not None:
+ report_stats = report_stats == "yes"
+
+ conf = HomeServerConfig().generate_config(
+ config_dir_path=args.config_dir,
+ data_dir_path=args.data_dir,
+ server_name=args.server_name,
+ generate_secrets=args.generate_secrets,
+ report_stats=report_stats,
+ )
+
+ args.output_file.write(conf)
diff --git a/synapse/config/_base.py b/synapse/config/_base.py
index 14dae65ea0..fd2d6d52ef 100644
--- a/synapse/config/_base.py
+++ b/synapse/config/_base.py
@@ -135,10 +135,6 @@ class Config(object):
return file_stream.read()
@staticmethod
- def default_path(name):
- return os.path.abspath(os.path.join(os.path.curdir, name))
-
- @staticmethod
def read_config_file(file_path):
with open(file_path) as file_stream:
return yaml.load(file_stream)
@@ -151,8 +147,39 @@ class Config(object):
return results
def generate_config(
- self, config_dir_path, server_name, is_generating_file, report_stats=None
+ self,
+ config_dir_path,
+ data_dir_path,
+ server_name,
+ generate_secrets=False,
+ report_stats=None,
):
+ """Build a default configuration file
+
+ This is used both when the user explicitly asks us to generate a config file
+ (eg with --generate_config), and before loading the config at runtime (to give
+ a base which the config files override)
+
+ Args:
+ config_dir_path (str): The path where the config files are kept. Used to
+ create filenames for things like the log config and the signing key.
+
+ data_dir_path (str): The path where the data files are kept. Used to create
+ filenames for things like the database and media store.
+
+ server_name (str): The server name. Used to initialise the server_name
+ config param, but also used in the names of some of the config files.
+
+ generate_secrets (bool): True if we should generate new secrets for things
+ like the macaroon_secret_key. If False, these parameters will be left
+ unset.
+
+ report_stats (bool|None): Initial setting for the report_stats setting.
+ If None, report_stats will be left unset.
+
+ Returns:
+ str: the yaml config file
+ """
default_config = "# vim:ft=yaml\n"
default_config += "\n\n".join(
@@ -160,15 +187,14 @@ class Config(object):
for conf in self.invoke_all(
"default_config",
config_dir_path=config_dir_path,
+ data_dir_path=data_dir_path,
server_name=server_name,
- is_generating_file=is_generating_file,
+ generate_secrets=generate_secrets,
report_stats=report_stats,
)
)
- config = yaml.load(default_config)
-
- return default_config, config
+ return default_config
@classmethod
def load_config(cls, description, argv):
@@ -274,12 +300,14 @@ class Config(object):
if not cls.path_exists(config_dir_path):
os.makedirs(config_dir_path)
with open(config_path, "w") as config_file:
- config_str, config = obj.generate_config(
+ config_str = obj.generate_config(
config_dir_path=config_dir_path,
+ data_dir_path=os.getcwd(),
server_name=server_name,
report_stats=(config_args.report_stats == "yes"),
- is_generating_file=True,
+ generate_secrets=True,
)
+ config = yaml.load(config_str)
obj.invoke_all("generate_files", config)
config_file.write(config_str)
print(
@@ -350,11 +378,13 @@ class Config(object):
raise ConfigError(MISSING_SERVER_NAME)
server_name = specified_config["server_name"]
- _, config = self.generate_config(
+ config_string = self.generate_config(
config_dir_path=config_dir_path,
+ data_dir_path=os.getcwd(),
server_name=server_name,
- is_generating_file=False,
+ generate_secrets=False,
)
+ config = yaml.load(config_string)
config.pop("log_config")
config.update(specified_config)
diff --git a/synapse/config/database.py b/synapse/config/database.py
index e915d9d09b..c8890147a6 100644
--- a/synapse/config/database.py
+++ b/synapse/config/database.py
@@ -12,6 +12,7 @@
# 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.
+import os
from ._base import Config
@@ -45,8 +46,8 @@ class DatabaseConfig(Config):
self.set_databasepath(config.get("database_path"))
- def default_config(self, **kwargs):
- database_path = self.abspath("homeserver.db")
+ def default_config(self, data_dir_path, **kwargs):
+ database_path = os.path.join(data_dir_path, "homeserver.db")
return """\
# Database configuration
database:
diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py
index 9d740c7a71..5aad062c36 100644
--- a/synapse/config/homeserver.py
+++ b/synapse/config/homeserver.py
@@ -53,10 +53,3 @@ class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
ServerNoticesConfig, RoomDirectoryConfig,
):
pass
-
-
-if __name__ == '__main__':
- import sys
- sys.stdout.write(
- HomeServerConfig().generate_config(sys.argv[1], sys.argv[2], True)[0]
- )
diff --git a/synapse/config/key.py b/synapse/config/key.py
index 279c47bb48..53f48fe2dd 100644
--- a/synapse/config/key.py
+++ b/synapse/config/key.py
@@ -66,26 +66,35 @@ class KeyConfig(Config):
# falsification of values
self.form_secret = config.get("form_secret", None)
- def default_config(self, config_dir_path, server_name, is_generating_file=False,
+ def default_config(self, config_dir_path, server_name, generate_secrets=False,
**kwargs):
base_key_name = os.path.join(config_dir_path, server_name)
- if is_generating_file:
- macaroon_secret_key = random_string_with_symbols(50)
- form_secret = '"%s"' % random_string_with_symbols(50)
+ if generate_secrets:
+ macaroon_secret_key = 'macaroon_secret_key: "%s"' % (
+ random_string_with_symbols(50),
+ )
+ form_secret = 'form_secret: "%s"' % random_string_with_symbols(50)
else:
- macaroon_secret_key = None
- form_secret = 'null'
+ macaroon_secret_key = "# macaroon_secret_key: <PRIVATE STRING>"
+ form_secret = "# form_secret: <PRIVATE STRING>"
return """\
- macaroon_secret_key: "%(macaroon_secret_key)s"
+ # a secret which is used to sign access tokens. If none is specified,
+ # the registration_shared_secret is used, if one is given; otherwise,
+ # a secret key is derived from the signing key.
+ #
+ # Note that changing this will invalidate any active access tokens, so
+ # all clients will have to log back in.
+ %(macaroon_secret_key)s
# Used to enable access token expiration.
expire_access_token: False
# a secret which is used to calculate HMACs for form values, to stop
- # falsification of values
- form_secret: %(form_secret)s
+ # falsification of values. Must be specified for the User Consent
+ # forms to work.
+ %(form_secret)s
## Signing Keys ##
diff --git a/synapse/config/logger.py b/synapse/config/logger.py
index 7081868963..f87efecbf8 100644
--- a/synapse/config/logger.py
+++ b/synapse/config/logger.py
@@ -80,9 +80,7 @@ class LoggingConfig(Config):
self.log_file = self.abspath(config.get("log_file"))
def default_config(self, config_dir_path, server_name, **kwargs):
- log_config = self.abspath(
- os.path.join(config_dir_path, server_name + ".log.config")
- )
+ log_config = os.path.join(config_dir_path, server_name + ".log.config")
return """
# A yaml python logging config file
log_config: "%(log_config)s"
diff --git a/synapse/config/metrics.py b/synapse/config/metrics.py
index 61155c99d0..718c43ae03 100644
--- a/synapse/config/metrics.py
+++ b/synapse/config/metrics.py
@@ -24,10 +24,16 @@ class MetricsConfig(Config):
self.metrics_bind_host = config.get("metrics_bind_host", "127.0.0.1")
def default_config(self, report_stats=None, **kwargs):
- suffix = "" if report_stats is None else "report_stats: %(report_stats)s\n"
- return ("""\
+ res = """\
## Metrics ###
# Enable collection and rendering of performance metrics
enable_metrics: False
- """ + suffix) % locals()
+ """
+
+ if report_stats is None:
+ res += "# report_stats: true|false\n"
+ else:
+ res += "report_stats: %s\n" % ('true' if report_stats else 'false')
+
+ return res
diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index e365f0c30b..6c2b543b8c 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -50,8 +50,13 @@ class RegistrationConfig(Config):
raise ConfigError('Invalid auto_join_rooms entry %s' % (room_alias,))
self.autocreate_auto_join_rooms = config.get("autocreate_auto_join_rooms", True)
- def default_config(self, **kwargs):
- registration_shared_secret = random_string_with_symbols(50)
+ def default_config(self, generate_secrets=False, **kwargs):
+ if generate_secrets:
+ registration_shared_secret = 'registration_shared_secret: "%s"' % (
+ random_string_with_symbols(50),
+ )
+ else:
+ registration_shared_secret = '# registration_shared_secret: <PRIVATE STRING>'
return """\
## Registration ##
@@ -78,7 +83,7 @@ class RegistrationConfig(Config):
# If set, allows registration by anyone who also has the shared
# secret, even if registration is otherwise disabled.
- registration_shared_secret: "%(registration_shared_secret)s"
+ %(registration_shared_secret)s
# Set the number of bcrypt rounds used to generate password hash.
# Larger numbers increase the work factor needed to generate the hash.
diff --git a/synapse/config/repository.py b/synapse/config/repository.py
index 06c62ab62c..76e3340a91 100644
--- a/synapse/config/repository.py
+++ b/synapse/config/repository.py
@@ -12,7 +12,7 @@
# 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.
-
+import os
from collections import namedtuple
from synapse.util.module_loader import load_module
@@ -175,9 +175,9 @@ class ContentRepositoryConfig(Config):
"url_preview_url_blacklist", ()
)
- def default_config(self, **kwargs):
- media_store = self.default_path("media_store")
- uploads_path = self.default_path("uploads")
+ def default_config(self, data_dir_path, **kwargs):
+ media_store = os.path.join(data_dir_path, "media_store")
+ uploads_path = os.path.join(data_dir_path, "uploads")
return r"""
# Directory where uploaded images and attachments are stored.
media_store_path: "%(media_store)s"
diff --git a/synapse/config/server.py b/synapse/config/server.py
index fb4585a2d4..120c2b81fc 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -15,6 +15,7 @@
# limitations under the License.
import logging
+import os.path
from synapse.http.endpoint import parse_and_validate_server_name
@@ -203,7 +204,7 @@ class ServerConfig(Config):
]
})
- def default_config(self, server_name, **kwargs):
+ def default_config(self, server_name, data_dir_path, **kwargs):
_, bind_port = parse_and_validate_server_name(server_name)
if bind_port is not None:
unsecure_port = bind_port - 400
@@ -211,7 +212,7 @@ class ServerConfig(Config):
bind_port = 8448
unsecure_port = 8008
- pid_file = self.abspath("homeserver.pid")
+ pid_file = os.path.join(data_dir_path, "homeserver.pid")
return """\
## Server ##
|