Merge branch 'develop' of github.com:matrix-org/synapse into matrix-org-hotfixes
commit
6d1a3e2bdd
|
@ -0,0 +1,21 @@
|
||||||
|
# Configuration file used for testing the 'synapse_port_db' script.
|
||||||
|
# Tells the script to connect to the postgresql database that will be available in the
|
||||||
|
# CI's Docker setup at the point where this file is considered.
|
||||||
|
server_name: "test"
|
||||||
|
|
||||||
|
signing_key_path: "/src/.buildkite/test.signing.key"
|
||||||
|
|
||||||
|
report_stats: false
|
||||||
|
|
||||||
|
database:
|
||||||
|
name: "psycopg2"
|
||||||
|
args:
|
||||||
|
user: postgres
|
||||||
|
host: postgres
|
||||||
|
password: postgres
|
||||||
|
database: synapse
|
||||||
|
|
||||||
|
# Suppress the key server warning.
|
||||||
|
trusted_key_servers:
|
||||||
|
- server_name: "matrix.org"
|
||||||
|
suppress_key_server_warning: true
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2019 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.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from synapse.storage.engines import create_engine
|
||||||
|
|
||||||
|
logger = logging.getLogger("create_postgres_db")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Create a PostgresEngine.
|
||||||
|
db_engine = create_engine({"name": "psycopg2", "args": {}})
|
||||||
|
|
||||||
|
# Connect to postgres to create the base database.
|
||||||
|
# We use "postgres" as a database because it's bound to exist and the "synapse" one
|
||||||
|
# doesn't exist yet.
|
||||||
|
db_conn = db_engine.module.connect(
|
||||||
|
user="postgres", host="postgres", password="postgres", dbname="postgres"
|
||||||
|
)
|
||||||
|
db_conn.autocommit = True
|
||||||
|
cur = db_conn.cursor()
|
||||||
|
cur.execute("CREATE DATABASE synapse;")
|
||||||
|
cur.close()
|
||||||
|
db_conn.close()
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Test script for 'synapse_port_db', which creates a virtualenv, installs Synapse along
|
||||||
|
# with additional dependencies needed for the test (such as coverage or the PostgreSQL
|
||||||
|
# driver), update the schema of the test SQLite database and run background updates on it,
|
||||||
|
# create an empty test database in PostgreSQL, then run the 'synapse_port_db' script to
|
||||||
|
# test porting the SQLite database to the PostgreSQL database (with coverage).
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
cd `dirname $0`/../..
|
||||||
|
|
||||||
|
echo "--- Install dependencies"
|
||||||
|
|
||||||
|
# Install dependencies for this test.
|
||||||
|
pip install psycopg2 coverage coverage-enable-subprocess
|
||||||
|
|
||||||
|
# Install Synapse itself. This won't update any libraries.
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
echo "--- Generate the signing key"
|
||||||
|
|
||||||
|
# Generate the server's signing key.
|
||||||
|
python -m synapse.app.homeserver --generate-keys -c .buildkite/sqlite-config.yaml
|
||||||
|
|
||||||
|
echo "--- Prepare the databases"
|
||||||
|
|
||||||
|
# Make sure the SQLite3 database is using the latest schema and has no pending background update.
|
||||||
|
scripts-dev/update_database --database-config .buildkite/sqlite-config.yaml
|
||||||
|
|
||||||
|
# Create the PostgreSQL database.
|
||||||
|
./.buildkite/scripts/create_postgres_db.py
|
||||||
|
|
||||||
|
echo "+++ Run synapse_port_db"
|
||||||
|
|
||||||
|
# Run the script
|
||||||
|
coverage run scripts/synapse_port_db --sqlite-database .buildkite/test_db.db --postgres-config .buildkite/postgres-config.yaml
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Configuration file used for testing the 'synapse_port_db' script.
|
||||||
|
# Tells the 'update_database' script to connect to the test SQLite database to upgrade its
|
||||||
|
# schema and run background updates on it.
|
||||||
|
server_name: "test"
|
||||||
|
|
||||||
|
signing_key_path: "/src/.buildkite/test.signing.key"
|
||||||
|
|
||||||
|
report_stats: false
|
||||||
|
|
||||||
|
database:
|
||||||
|
name: "sqlite3"
|
||||||
|
args:
|
||||||
|
database: ".buildkite/test_db.db"
|
||||||
|
|
||||||
|
# Suppress the key server warning.
|
||||||
|
trusted_key_servers:
|
||||||
|
- server_name: "matrix.org"
|
||||||
|
suppress_key_server_warning: true
|
Binary file not shown.
|
@ -5,3 +5,4 @@
|
||||||
* [ ] Pull request is based on the develop branch
|
* [ ] Pull request is based on the develop branch
|
||||||
* [ ] Pull request includes a [changelog file](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#changelog)
|
* [ ] Pull request includes a [changelog file](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#changelog)
|
||||||
* [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
* [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
||||||
|
* [ ] Code style is correct (run the [linters](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#code-style))
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
Synapse 1.5.1 (2019-11-06)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Limit the length of data returned by url previews, to prevent DoS attacks. ([\#6331](https://github.com/matrix-org/synapse/issues/6331), [\#6334](https://github.com/matrix-org/synapse/issues/6334))
|
||||||
|
|
||||||
|
|
||||||
Synapse 1.5.0 (2019-10-29)
|
Synapse 1.5.0 (2019-10-29)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|
|
@ -58,10 +58,29 @@ All Matrix projects have a well-defined code-style - and sometimes we've even
|
||||||
got as far as documenting it... For instance, synapse's code style doc lives
|
got as far as documenting it... For instance, synapse's code style doc lives
|
||||||
at https://github.com/matrix-org/synapse/tree/master/docs/code_style.md.
|
at https://github.com/matrix-org/synapse/tree/master/docs/code_style.md.
|
||||||
|
|
||||||
|
To facilitate meeting these criteria you can run ``scripts-dev/lint.sh``
|
||||||
|
locally. Since this runs the tools listed in the above document, you'll need
|
||||||
|
python 3.6 and to install each tool. **Note that the script does not just
|
||||||
|
test/check, but also reformats code, so you may wish to ensure any new code is
|
||||||
|
committed first**. By default this script checks all files and can take some
|
||||||
|
time; if you alter only certain files, you might wish to specify paths as
|
||||||
|
arguments to reduce the run-time.
|
||||||
|
|
||||||
Please ensure your changes match the cosmetic style of the existing project,
|
Please ensure your changes match the cosmetic style of the existing project,
|
||||||
and **never** mix cosmetic and functional changes in the same commit, as it
|
and **never** mix cosmetic and functional changes in the same commit, as it
|
||||||
makes it horribly hard to review otherwise.
|
makes it horribly hard to review otherwise.
|
||||||
|
|
||||||
|
Before doing a commit, ensure the changes you've made don't produce
|
||||||
|
linting errors. You can do this by running the linters as follows. Ensure to
|
||||||
|
commit any files that were corrected.
|
||||||
|
|
||||||
|
::
|
||||||
|
# Install the dependencies
|
||||||
|
pip install -U black flake8 isort
|
||||||
|
|
||||||
|
# Run the linter script
|
||||||
|
./scripts-dev/lint.sh
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
|
|
18
INSTALL.md
18
INSTALL.md
|
@ -36,7 +36,7 @@ that your email address is probably `user@example.com` rather than
|
||||||
System requirements:
|
System requirements:
|
||||||
|
|
||||||
- POSIX-compliant system (tested on Linux & OS X)
|
- POSIX-compliant system (tested on Linux & OS X)
|
||||||
- Python 3.5, 3.6, or 3.7
|
- Python 3.5, 3.6, 3.7 or 3.8.
|
||||||
- At least 1GB of free RAM if you want to join large public rooms like #matrix:matrix.org
|
- At least 1GB of free RAM if you want to join large public rooms like #matrix:matrix.org
|
||||||
|
|
||||||
Synapse is written in Python but some of the libraries it uses are written in
|
Synapse is written in Python but some of the libraries it uses are written in
|
||||||
|
@ -413,16 +413,18 @@ For a more detailed guide to configuring your server for federation, see
|
||||||
|
|
||||||
## Email
|
## Email
|
||||||
|
|
||||||
It is desirable for Synapse to have the capability to send email. For example,
|
It is desirable for Synapse to have the capability to send email. This allows
|
||||||
this is required to support the 'password reset' feature.
|
Synapse to send password reset emails, send verifications when an email address
|
||||||
|
is added to a user's account, and send email notifications to users when they
|
||||||
|
receive new messages.
|
||||||
|
|
||||||
To configure an SMTP server for Synapse, modify the configuration section
|
To configure an SMTP server for Synapse, modify the configuration section
|
||||||
headed ``email``, and be sure to have at least the ``smtp_host``, ``smtp_port``
|
headed `email`, and be sure to have at least the `smtp_host`, `smtp_port`
|
||||||
and ``notif_from`` fields filled out. You may also need to set ``smtp_user``,
|
and `notif_from` fields filled out. You may also need to set `smtp_user`,
|
||||||
``smtp_pass``, and ``require_transport_security``.
|
`smtp_pass`, and `require_transport_security`.
|
||||||
|
|
||||||
If Synapse is not configured with an SMTP server, password reset via email will
|
If email is not configured, password reset, registration and notifications via
|
||||||
be disabled by default.
|
email will be disabled.
|
||||||
|
|
||||||
## Registering a user
|
## Registering a user
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add federation support for cross-signing.
|
|
@ -0,0 +1 @@
|
||||||
|
Add a CI job to test the `synapse_port_db` script.
|
|
@ -0,0 +1 @@
|
||||||
|
Contributor documentation now mentions script to run linters.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix LruCache callback deduplication for Python 3.8. Contributed by @V02460.
|
|
@ -0,0 +1 @@
|
||||||
|
Convert EventContext to an attrs.
|
|
@ -0,0 +1 @@
|
||||||
|
Increase default room version from 4 to 5, thereby enforcing server key validity period checks.
|
|
@ -0,0 +1 @@
|
||||||
|
Remove a room from a server's public rooms list on room upgrade.
|
|
@ -0,0 +1 @@
|
||||||
|
Remove a room from a server's public rooms list on room upgrade.
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for outbound http proxying via http_proxy/HTTPS_PROXY env vars.
|
|
@ -0,0 +1 @@
|
||||||
|
Move `persist_events` out from main data store.
|
|
@ -0,0 +1 @@
|
||||||
|
Delete keys from key backup when deleting backup versions.
|
|
@ -0,0 +1 @@
|
||||||
|
Make notification of cross-signing signatures work with workers.
|
|
@ -0,0 +1 @@
|
||||||
|
Modify CAPTCHA_SETUP.md to update the terms `private key` and `public key` to `secret key` and `site key` respectively. Contributed by Yash Jipkate.
|
|
@ -0,0 +1 @@
|
||||||
|
Expose some homeserver functionality to spam checkers.
|
|
@ -0,0 +1 @@
|
||||||
|
Change cache descriptors to always return deferreds.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix incorrect comment regarding the functionality of an `if` statement.
|
|
@ -0,0 +1 @@
|
||||||
|
Update CI to run `isort` over the `scripts` and `scripts-dev` directories.
|
|
@ -0,0 +1 @@
|
||||||
|
Replace every instance of `logger.warn` method with `logger.warning` as the former is deprecated.
|
|
@ -0,0 +1 @@
|
||||||
|
Update `INSTALL.md` Email section to talk about `account_threepid_delegates`.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix a small typo in `account_threepid_delegates` configuration option.
|
|
@ -0,0 +1 @@
|
||||||
|
Port replication http server endpoints to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Port room rest handlers to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Add a CI job to test the `synapse_port_db` script.
|
|
@ -0,0 +1 @@
|
||||||
|
Remove redundant CLI parameters on CI's `flake8` step.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix exception when remote servers attempt to join a room that they're not allowed to join.
|
|
@ -0,0 +1 @@
|
||||||
|
Port `federation_server.py` to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Port receipt and read markers to async/wait.
|
|
@ -0,0 +1 @@
|
||||||
|
Prevent errors from appearing on Synapse startup if `git` is not installed.
|
|
@ -0,0 +1 @@
|
||||||
|
Change cache descriptors to always return deferreds.
|
|
@ -0,0 +1 @@
|
||||||
|
Split out state storage into separate data store.
|
|
@ -0,0 +1 @@
|
||||||
|
Split out state storage into separate data store.
|
|
@ -0,0 +1 @@
|
||||||
|
Refactor EventContext for clarity.
|
|
@ -0,0 +1 @@
|
||||||
|
Move `persist_events` out from main data store.
|
|
@ -0,0 +1 @@
|
||||||
|
Implement label-based filtering on `/sync` and `/messages` ([MSC2326](https://github.com/matrix-org/matrix-doc/pull/2326)).
|
|
@ -0,0 +1 @@
|
||||||
|
Update the version of black used to 19.10b0.
|
|
@ -0,0 +1 @@
|
||||||
|
Add some documentation about worker replication.
|
|
@ -0,0 +1 @@
|
||||||
|
Appservice requests will no longer contain a double slash prefix when the appservice url provided ends in a slash.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix `/purge_room` admin API.
|
|
@ -0,0 +1 @@
|
||||||
|
Implement label-based filtering on `/sync` and `/messages` ([MSC2326](https://github.com/matrix-org/matrix-doc/pull/2326)).
|
|
@ -0,0 +1 @@
|
||||||
|
Document the use of `lint.sh` for code style enforcement & extend it to run on specified paths only.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix the `hidden` field in the `devices` table for SQLite versions prior to 3.23.0.
|
|
@ -0,0 +1 @@
|
||||||
|
Replace every instance of `logger.warn` method with `logger.warning` as the former is deprecated.
|
|
@ -0,0 +1 @@
|
||||||
|
Add optional python dependencies and dependant binary libraries to snapcraft packaging.
|
|
@ -0,0 +1 @@
|
||||||
|
Remove the dependency on psutil and replace functionality with the stdlib `resource` module.
|
|
@ -0,0 +1 @@
|
||||||
|
Improve documentation for EventContext fields.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix bug which casued rejected events to be persisted with the wrong room state.
|
|
@ -0,0 +1 @@
|
||||||
|
Add some checks that we aren't using state from rejected events.
|
|
@ -1 +0,0 @@
|
||||||
Limit the length of data returned by url previews, to prevent DoS attacks.
|
|
|
@ -1 +0,0 @@
|
||||||
Limit the length of data returned by url previews, to prevent DoS attacks.
|
|
|
@ -0,0 +1 @@
|
||||||
|
Remove the dependency on psutil and replace functionality with the stdlib `resource` module.
|
|
@ -0,0 +1 @@
|
||||||
|
Prevent the server taking a long time to start up when guest registration is enabled.
|
|
@ -0,0 +1 @@
|
||||||
|
Implement label-based filtering on `/sync` and `/messages` ([MSC2326](https://github.com/matrix-org/matrix-doc/pull/2326)).
|
|
@ -0,0 +1 @@
|
||||||
|
Add continuous integration for python 3.8.
|
|
@ -0,0 +1 @@
|
||||||
|
Correct spacing/case of various instances of the word "homeserver".
|
|
@ -0,0 +1 @@
|
||||||
|
Fix bug where upgrading a guest account to a full user would fail when account validity is enabled.
|
|
@ -0,0 +1 @@
|
||||||
|
Temporarily blacklist the failing unit test PurgeRoomTestCase.test_purge_room.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix `to_device` stream ID getting reset every time Synapse restarts, which had the potential to cause unable to decrypt errors.
|
|
@ -78,7 +78,7 @@ class InputOutput(object):
|
||||||
m = re.match("^join (\S+)$", line)
|
m = re.match("^join (\S+)$", line)
|
||||||
if m:
|
if m:
|
||||||
# The `sender` wants to join a room.
|
# The `sender` wants to join a room.
|
||||||
room_name, = m.groups()
|
(room_name,) = m.groups()
|
||||||
self.print_line("%s joining %s" % (self.user, room_name))
|
self.print_line("%s joining %s" % (self.user, room_name))
|
||||||
self.server.join_room(room_name, self.user, self.user)
|
self.server.join_room(room_name, self.user, self.user)
|
||||||
# self.print_line("OK.")
|
# self.print_line("OK.")
|
||||||
|
@ -105,7 +105,7 @@ class InputOutput(object):
|
||||||
m = re.match("^backfill (\S+)$", line)
|
m = re.match("^backfill (\S+)$", line)
|
||||||
if m:
|
if m:
|
||||||
# we want to backfill a room
|
# we want to backfill a room
|
||||||
room_name, = m.groups()
|
(room_name,) = m.groups()
|
||||||
self.print_line("backfill %s" % room_name)
|
self.print_line("backfill %s" % room_name)
|
||||||
self.server.backfill(room_name)
|
self.server.backfill(room_name)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
matrix-synapse-py3 (1.5.1) stable; urgency=medium
|
||||||
|
|
||||||
|
* New synapse release 1.5.1.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Wed, 06 Nov 2019 10:02:14 +0000
|
||||||
|
|
||||||
matrix-synapse-py3 (1.5.0) stable; urgency=medium
|
matrix-synapse-py3 (1.5.0) stable; urgency=medium
|
||||||
|
|
||||||
* New synapse release 1.5.0.
|
* New synapse release 1.5.0.
|
||||||
|
|
|
@ -101,7 +101,7 @@ is suitable for local testing, but for any practical use, you will either need
|
||||||
to use a reverse proxy, or configure Synapse to expose an HTTPS port.
|
to use a reverse proxy, or configure Synapse to expose an HTTPS port.
|
||||||
|
|
||||||
For documentation on using a reverse proxy, see
|
For documentation on using a reverse proxy, see
|
||||||
https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst.
|
https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.md.
|
||||||
|
|
||||||
For more information on enabling TLS support in synapse itself, see
|
For more information on enabling TLS support in synapse itself, see
|
||||||
https://github.com/matrix-org/synapse/blob/master/INSTALL.md#tls-certificates. Of
|
https://github.com/matrix-org/synapse/blob/master/INSTALL.md#tls-certificates. Of
|
||||||
|
|
|
@ -217,8 +217,9 @@ def main(args, environ):
|
||||||
# backwards-compatibility generate-a-config-on-the-fly mode
|
# backwards-compatibility generate-a-config-on-the-fly mode
|
||||||
if "SYNAPSE_CONFIG_PATH" in environ:
|
if "SYNAPSE_CONFIG_PATH" in environ:
|
||||||
error(
|
error(
|
||||||
"SYNAPSE_SERVER_NAME and SYNAPSE_CONFIG_PATH are mutually exclusive "
|
"SYNAPSE_SERVER_NAME can only be combined with SYNAPSE_CONFIG_PATH "
|
||||||
"except in `generate` or `migrate_config` mode."
|
"in `generate` or `migrate_config` mode. To start synapse using a "
|
||||||
|
"config file, unset the SYNAPSE_SERVER_NAME environment variable."
|
||||||
)
|
)
|
||||||
|
|
||||||
config_path = "/compiled/homeserver.yaml"
|
config_path = "/compiled/homeserver.yaml"
|
||||||
|
|
|
@ -4,7 +4,7 @@ The captcha mechanism used is Google's ReCaptcha. This requires API keys from Go
|
||||||
|
|
||||||
## Getting keys
|
## Getting keys
|
||||||
|
|
||||||
Requires a public/private key pair from:
|
Requires a site/secret key pair from:
|
||||||
|
|
||||||
<https://developers.google.com/recaptcha/>
|
<https://developers.google.com/recaptcha/>
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ Must be a reCAPTCHA v2 key using the "I'm not a robot" Checkbox option
|
||||||
The keys are a config option on the home server config. If they are not
|
The keys are a config option on the home server config. If they are not
|
||||||
visible, you can generate them via `--generate-config`. Set the following value:
|
visible, you can generate them via `--generate-config`. Set the following value:
|
||||||
|
|
||||||
recaptcha_public_key: YOUR_PUBLIC_KEY
|
recaptcha_public_key: YOUR_SITE_KEY
|
||||||
recaptcha_private_key: YOUR_PRIVATE_KEY
|
recaptcha_private_key: YOUR_SECRET_KEY
|
||||||
|
|
||||||
In addition, you MUST enable captchas via:
|
In addition, you MUST enable captchas via:
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ pid_file: DATADIR/homeserver.pid
|
||||||
# For example, for room version 1, default_room_version should be set
|
# For example, for room version 1, default_room_version should be set
|
||||||
# to "1".
|
# to "1".
|
||||||
#
|
#
|
||||||
#default_room_version: "4"
|
#default_room_version: "5"
|
||||||
|
|
||||||
# The GC threshold parameters to pass to `gc.set_threshold`, if defined
|
# The GC threshold parameters to pass to `gc.set_threshold`, if defined
|
||||||
#
|
#
|
||||||
|
@ -287,7 +287,7 @@ listeners:
|
||||||
# Used by phonehome stats to group together related servers.
|
# Used by phonehome stats to group together related servers.
|
||||||
#server_context: context
|
#server_context: context
|
||||||
|
|
||||||
# Resource-constrained Homeserver Settings
|
# Resource-constrained homeserver Settings
|
||||||
#
|
#
|
||||||
# If limit_remote_rooms.enabled is True, the room complexity will be
|
# If limit_remote_rooms.enabled is True, the room complexity will be
|
||||||
# checked before a user joins a new remote room. If it is above
|
# checked before a user joins a new remote room. If it is above
|
||||||
|
@ -743,11 +743,11 @@ uploads_path: "DATADIR/uploads"
|
||||||
## Captcha ##
|
## Captcha ##
|
||||||
# See docs/CAPTCHA_SETUP for full details of configuring this.
|
# See docs/CAPTCHA_SETUP for full details of configuring this.
|
||||||
|
|
||||||
# This Home Server's ReCAPTCHA public key.
|
# This homeserver's ReCAPTCHA public key.
|
||||||
#
|
#
|
||||||
#recaptcha_public_key: "YOUR_PUBLIC_KEY"
|
#recaptcha_public_key: "YOUR_PUBLIC_KEY"
|
||||||
|
|
||||||
# This Home Server's ReCAPTCHA private key.
|
# This homeserver's ReCAPTCHA private key.
|
||||||
#
|
#
|
||||||
#recaptcha_private_key: "YOUR_PRIVATE_KEY"
|
#recaptcha_private_key: "YOUR_PRIVATE_KEY"
|
||||||
|
|
||||||
|
@ -955,7 +955,7 @@ uploads_path: "DATADIR/uploads"
|
||||||
# If a delegate is specified, the config option public_baseurl must also be filled out.
|
# If a delegate is specified, the config option public_baseurl must also be filled out.
|
||||||
#
|
#
|
||||||
account_threepid_delegates:
|
account_threepid_delegates:
|
||||||
#email: https://example.com # Delegate email sending to example.org
|
#email: https://example.com # Delegate email sending to example.com
|
||||||
#msisdn: http://localhost:8090 # Delegate SMS sending to this local process
|
#msisdn: http://localhost:8090 # Delegate SMS sending to this local process
|
||||||
|
|
||||||
# Users who register on this homeserver will automatically be joined
|
# Users who register on this homeserver will automatically be joined
|
||||||
|
@ -1270,7 +1270,7 @@ password_config:
|
||||||
# smtp_user: "exampleusername"
|
# smtp_user: "exampleusername"
|
||||||
# smtp_pass: "examplepassword"
|
# smtp_pass: "examplepassword"
|
||||||
# require_transport_security: false
|
# require_transport_security: false
|
||||||
# notif_from: "Your Friendly %(app)s Home Server <noreply@example.com>"
|
# notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
||||||
# app_name: Matrix
|
# app_name: Matrix
|
||||||
#
|
#
|
||||||
# # Enable email notifications by default
|
# # Enable email notifications by default
|
||||||
|
|
|
@ -199,7 +199,20 @@ client (C):
|
||||||
|
|
||||||
#### REPLICATE (C)
|
#### REPLICATE (C)
|
||||||
|
|
||||||
Asks the server to replicate a given stream
|
Asks the server to replicate a given stream. The syntax is:
|
||||||
|
|
||||||
|
```
|
||||||
|
REPLICATE <stream_name> <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `<token>` may be either:
|
||||||
|
* a numeric stream_id to stream updates since (exclusive)
|
||||||
|
* `NOW` to stream all subsequent updates.
|
||||||
|
|
||||||
|
The `<stream_name>` is the name of a replication stream to subscribe
|
||||||
|
to (see [here](../synapse/replication/tcp/streams/_base.py) for a list
|
||||||
|
of streams). It can also be `ALL` to subscribe to all known streams,
|
||||||
|
in which case the `<token>` must be set to `NOW`.
|
||||||
|
|
||||||
#### USER_SYNC (C)
|
#### USER_SYNC (C)
|
||||||
|
|
||||||
|
|
11
mypy.ini
11
mypy.ini
|
@ -1,8 +1,11 @@
|
||||||
[mypy]
|
[mypy]
|
||||||
namespace_packages=True
|
namespace_packages = True
|
||||||
plugins=mypy_zope:plugin
|
plugins = mypy_zope:plugin
|
||||||
follow_imports=skip
|
follow_imports = normal
|
||||||
mypy_path=stubs
|
check_untyped_defs = True
|
||||||
|
show_error_codes = True
|
||||||
|
show_traceback = True
|
||||||
|
mypy_path = stubs
|
||||||
|
|
||||||
[mypy-zope]
|
[mypy-zope]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
|
@ -20,11 +20,13 @@ from concurrent.futures import ThreadPoolExecutor
|
||||||
DISTS = (
|
DISTS = (
|
||||||
"debian:stretch",
|
"debian:stretch",
|
||||||
"debian:buster",
|
"debian:buster",
|
||||||
|
"debian:bullseye",
|
||||||
"debian:sid",
|
"debian:sid",
|
||||||
"ubuntu:xenial",
|
"ubuntu:xenial",
|
||||||
"ubuntu:bionic",
|
"ubuntu:bionic",
|
||||||
"ubuntu:cosmic",
|
"ubuntu:cosmic",
|
||||||
"ubuntu:disco",
|
"ubuntu:disco",
|
||||||
|
"ubuntu:eoan",
|
||||||
)
|
)
|
||||||
|
|
||||||
DESC = '''\
|
DESC = '''\
|
||||||
|
|
|
@ -7,7 +7,15 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
isort -y -rc synapse tests scripts-dev scripts
|
if [ $# -ge 1 ]
|
||||||
flake8 synapse tests
|
then
|
||||||
python3 -m black synapse tests scripts-dev scripts
|
files=$*
|
||||||
|
else
|
||||||
|
files="synapse tests scripts-dev scripts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Linting these locations: $files"
|
||||||
|
isort -y -rc $files
|
||||||
|
flake8 $files
|
||||||
|
python3 -m black $files
|
||||||
./scripts-dev/config-lint.sh
|
./scripts-dev/config-lint.sh
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2019 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.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from twisted.internet import defer, reactor
|
||||||
|
|
||||||
|
from synapse.config.homeserver import HomeServerConfig
|
||||||
|
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
|
from synapse.server import HomeServer
|
||||||
|
from synapse.storage import DataStore
|
||||||
|
from synapse.storage.engines import create_engine
|
||||||
|
from synapse.storage.prepare_database import prepare_database
|
||||||
|
|
||||||
|
logger = logging.getLogger("update_database")
|
||||||
|
|
||||||
|
|
||||||
|
class MockHomeserver(HomeServer):
|
||||||
|
DATASTORE_CLASS = DataStore
|
||||||
|
|
||||||
|
def __init__(self, config, database_engine, db_conn, **kwargs):
|
||||||
|
super(MockHomeserver, self).__init__(
|
||||||
|
config.server_name,
|
||||||
|
reactor=reactor,
|
||||||
|
config=config,
|
||||||
|
database_engine=database_engine,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
self.database_engine = database_engine
|
||||||
|
self.db_conn = db_conn
|
||||||
|
|
||||||
|
def get_db_conn(self):
|
||||||
|
return self.db_conn
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=(
|
||||||
|
"Updates a synapse database to the latest schema and runs background updates"
|
||||||
|
" on it."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
parser.add_argument("-v", action='store_true')
|
||||||
|
parser.add_argument(
|
||||||
|
"--database-config",
|
||||||
|
type=argparse.FileType('r'),
|
||||||
|
required=True,
|
||||||
|
help="A database config file for either a SQLite3 database or a PostgreSQL one.",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
logging_config = {
|
||||||
|
"level": logging.DEBUG if args.v else logging.INFO,
|
||||||
|
"format": "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s",
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.basicConfig(**logging_config)
|
||||||
|
|
||||||
|
# Load, process and sanity-check the config.
|
||||||
|
hs_config = yaml.safe_load(args.database_config)
|
||||||
|
|
||||||
|
if "database" not in hs_config:
|
||||||
|
sys.stderr.write("The configuration file must have a 'database' section.\n")
|
||||||
|
sys.exit(4)
|
||||||
|
|
||||||
|
config = HomeServerConfig()
|
||||||
|
config.parse_config_dict(hs_config, "", "")
|
||||||
|
|
||||||
|
# Create the database engine and a connection to it.
|
||||||
|
database_engine = create_engine(config.database_config)
|
||||||
|
db_conn = database_engine.module.connect(
|
||||||
|
**{
|
||||||
|
k: v
|
||||||
|
for k, v in config.database_config.get("args", {}).items()
|
||||||
|
if not k.startswith("cp_")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update the database to the latest schema.
|
||||||
|
prepare_database(db_conn, database_engine, config=config)
|
||||||
|
db_conn.commit()
|
||||||
|
|
||||||
|
# Instantiate and initialise the homeserver object.
|
||||||
|
hs = MockHomeserver(
|
||||||
|
config,
|
||||||
|
database_engine,
|
||||||
|
db_conn,
|
||||||
|
db_config=config.database_config,
|
||||||
|
)
|
||||||
|
# setup instantiates the store within the homeserver object.
|
||||||
|
hs.setup()
|
||||||
|
store = hs.get_datastore()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def run_background_updates():
|
||||||
|
yield store.run_background_updates(sleep=False)
|
||||||
|
# Stop the reactor to exit the script once every background update is run.
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Apply all background updates on the database.
|
||||||
|
reactor.callWhenRunning(lambda: run_as_background_process(
|
||||||
|
"background_updates", run_background_updates
|
||||||
|
))
|
||||||
|
|
||||||
|
reactor.run()
|
|
@ -72,7 +72,7 @@ def move_media(origin_server, file_id, src_paths, dest_paths):
|
||||||
# check that the original exists
|
# check that the original exists
|
||||||
original_file = src_paths.remote_media_filepath(origin_server, file_id)
|
original_file = src_paths.remote_media_filepath(origin_server, file_id)
|
||||||
if not os.path.exists(original_file):
|
if not os.path.exists(original_file):
|
||||||
logger.warn(
|
logger.warning(
|
||||||
"Original for %s/%s (%s) does not exist",
|
"Original for %s/%s (%s) does not exist",
|
||||||
origin_server,
|
origin_server,
|
||||||
file_id,
|
file_id,
|
||||||
|
|
|
@ -157,7 +157,7 @@ class Store(
|
||||||
)
|
)
|
||||||
except self.database_engine.module.DatabaseError as e:
|
except self.database_engine.module.DatabaseError as e:
|
||||||
if self.database_engine.is_deadlock(e):
|
if self.database_engine.is_deadlock(e):
|
||||||
logger.warn("[TXN DEADLOCK] {%s} %d/%d", desc, i, N)
|
logger.warning("[TXN DEADLOCK] {%s} %d/%d", desc, i, N)
|
||||||
if i < N:
|
if i < N:
|
||||||
i += 1
|
i += 1
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
|
@ -432,7 +432,7 @@ class Porter(object):
|
||||||
for row in rows:
|
for row in rows:
|
||||||
d = dict(zip(headers, row))
|
d = dict(zip(headers, row))
|
||||||
if "\0" in d['value']:
|
if "\0" in d['value']:
|
||||||
logger.warn('dropping search row %s', d)
|
logger.warning('dropping search row %s', d)
|
||||||
else:
|
else:
|
||||||
rows_dict.append(d)
|
rows_dict.append(d)
|
||||||
|
|
||||||
|
@ -647,7 +647,7 @@ class Porter(object):
|
||||||
if isinstance(col, bytes):
|
if isinstance(col, bytes):
|
||||||
return bytearray(col)
|
return bytearray(col)
|
||||||
elif isinstance(col, string_types) and "\0" in col:
|
elif isinstance(col, string_types) and "\0" in col:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
"DROPPING ROW: NUL value in table %s col %s: %r",
|
"DROPPING ROW: NUL value in table %s col %s: %r",
|
||||||
table,
|
table,
|
||||||
headers[j],
|
headers[j],
|
||||||
|
|
|
@ -20,3 +20,23 @@ parts:
|
||||||
source: .
|
source: .
|
||||||
plugin: python
|
plugin: python
|
||||||
python-version: python3
|
python-version: python3
|
||||||
|
python-packages:
|
||||||
|
- '.[all]'
|
||||||
|
build-packages:
|
||||||
|
- libffi-dev
|
||||||
|
- libturbojpeg0-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libxslt1-dev
|
||||||
|
- libpq-dev
|
||||||
|
- zlib1g-dev
|
||||||
|
stage-packages:
|
||||||
|
- libasn1-8-heimdal
|
||||||
|
- libgssapi3-heimdal
|
||||||
|
- libhcrypto4-heimdal
|
||||||
|
- libheimbase1-heimdal
|
||||||
|
- libheimntlm0-heimdal
|
||||||
|
- libhx509-5-heimdal
|
||||||
|
- libkrb5-26-heimdal
|
||||||
|
- libldap-2.4-2
|
||||||
|
- libpq5
|
||||||
|
- libsasl2-2
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
""" This is a reference implementation of a Matrix home server.
|
""" This is a reference implementation of a Matrix homeserver.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -36,7 +36,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
__version__ = "1.5.0"
|
__version__ = "1.5.1"
|
||||||
|
|
||||||
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
|
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
|
||||||
# We import here so that we don't have to install a bunch of deps when
|
# We import here so that we don't have to install a bunch of deps when
|
||||||
|
|
|
@ -144,8 +144,8 @@ def main():
|
||||||
logging.captureWarnings(True)
|
logging.captureWarnings(True)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Used to register new users with a given home server when"
|
description="Used to register new users with a given homeserver when"
|
||||||
" registration has been disabled. The home server must be"
|
" registration has been disabled. The homeserver must be"
|
||||||
" configured with the 'registration_shared_secret' option"
|
" configured with the 'registration_shared_secret' option"
|
||||||
" set."
|
" set."
|
||||||
)
|
)
|
||||||
|
@ -202,7 +202,7 @@ def main():
|
||||||
"server_url",
|
"server_url",
|
||||||
default="https://localhost:8448",
|
default="https://localhost:8448",
|
||||||
nargs="?",
|
nargs="?",
|
||||||
help="URL to use to talk to the home server. Defaults to "
|
help="URL to use to talk to the homeserver. Defaults to "
|
||||||
" 'https://localhost:8448'.",
|
" 'https://localhost:8448'.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -497,7 +497,7 @@ class Auth(object):
|
||||||
token = self.get_access_token_from_request(request)
|
token = self.get_access_token_from_request(request)
|
||||||
service = self.store.get_app_service_by_token(token)
|
service = self.store.get_app_service_by_token(token)
|
||||||
if not service:
|
if not service:
|
||||||
logger.warn("Unrecognised appservice access token.")
|
logger.warning("Unrecognised appservice access token.")
|
||||||
raise InvalidClientTokenError()
|
raise InvalidClientTokenError()
|
||||||
request.authenticated_entity = service.sender
|
request.authenticated_entity = service.sender
|
||||||
return defer.succeed(service)
|
return defer.succeed(service)
|
||||||
|
|
|
@ -138,3 +138,10 @@ class LimitBlockingTypes(object):
|
||||||
|
|
||||||
MONTHLY_ACTIVE_USER = "monthly_active_user"
|
MONTHLY_ACTIVE_USER = "monthly_active_user"
|
||||||
HS_DISABLED = "hs_disabled"
|
HS_DISABLED = "hs_disabled"
|
||||||
|
|
||||||
|
|
||||||
|
class EventContentFields(object):
|
||||||
|
"""Fields found in events' content, regardless of type."""
|
||||||
|
|
||||||
|
# Labels for the event, cf https://github.com/matrix-org/matrix-doc/pull/2326
|
||||||
|
LABELS = "org.matrix.labels"
|
||||||
|
|
|
@ -457,7 +457,7 @@ def cs_error(msg, code=Codes.UNKNOWN, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
class FederationError(RuntimeError):
|
class FederationError(RuntimeError):
|
||||||
""" This class is used to inform remote home servers about erroneous
|
""" This class is used to inform remote homeservers about erroneous
|
||||||
PDUs they sent us.
|
PDUs they sent us.
|
||||||
|
|
||||||
FATAL: The remote server could not interpret the source event.
|
FATAL: The remote server could not interpret the source event.
|
||||||
|
|
|
@ -20,6 +20,7 @@ from jsonschema import FormatChecker
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.api.constants import EventContentFields
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.storage.presence import UserPresenceState
|
from synapse.storage.presence import UserPresenceState
|
||||||
from synapse.types import RoomID, UserID
|
from synapse.types import RoomID, UserID
|
||||||
|
@ -66,6 +67,10 @@ ROOM_EVENT_FILTER_SCHEMA = {
|
||||||
"contains_url": {"type": "boolean"},
|
"contains_url": {"type": "boolean"},
|
||||||
"lazy_load_members": {"type": "boolean"},
|
"lazy_load_members": {"type": "boolean"},
|
||||||
"include_redundant_members": {"type": "boolean"},
|
"include_redundant_members": {"type": "boolean"},
|
||||||
|
# Include or exclude events with the provided labels.
|
||||||
|
# cf https://github.com/matrix-org/matrix-doc/pull/2326
|
||||||
|
"org.matrix.labels": {"type": "array", "items": {"type": "string"}},
|
||||||
|
"org.matrix.not_labels": {"type": "array", "items": {"type": "string"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +264,9 @@ class Filter(object):
|
||||||
|
|
||||||
self.contains_url = self.filter_json.get("contains_url", None)
|
self.contains_url = self.filter_json.get("contains_url", None)
|
||||||
|
|
||||||
|
self.labels = self.filter_json.get("org.matrix.labels", None)
|
||||||
|
self.not_labels = self.filter_json.get("org.matrix.not_labels", [])
|
||||||
|
|
||||||
def filters_all_types(self):
|
def filters_all_types(self):
|
||||||
return "*" in self.not_types
|
return "*" in self.not_types
|
||||||
|
|
||||||
|
@ -282,6 +290,7 @@ class Filter(object):
|
||||||
room_id = None
|
room_id = None
|
||||||
ev_type = "m.presence"
|
ev_type = "m.presence"
|
||||||
contains_url = False
|
contains_url = False
|
||||||
|
labels = []
|
||||||
else:
|
else:
|
||||||
sender = event.get("sender", None)
|
sender = event.get("sender", None)
|
||||||
if not sender:
|
if not sender:
|
||||||
|
@ -300,10 +309,11 @@ class Filter(object):
|
||||||
content = event.get("content", {})
|
content = event.get("content", {})
|
||||||
# check if there is a string url field in the content for filtering purposes
|
# check if there is a string url field in the content for filtering purposes
|
||||||
contains_url = isinstance(content.get("url"), text_type)
|
contains_url = isinstance(content.get("url"), text_type)
|
||||||
|
labels = content.get(EventContentFields.LABELS, [])
|
||||||
|
|
||||||
return self.check_fields(room_id, sender, ev_type, contains_url)
|
return self.check_fields(room_id, sender, ev_type, labels, contains_url)
|
||||||
|
|
||||||
def check_fields(self, room_id, sender, event_type, contains_url):
|
def check_fields(self, room_id, sender, event_type, labels, contains_url):
|
||||||
"""Checks whether the filter matches the given event fields.
|
"""Checks whether the filter matches the given event fields.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -313,6 +323,7 @@ class Filter(object):
|
||||||
"rooms": lambda v: room_id == v,
|
"rooms": lambda v: room_id == v,
|
||||||
"senders": lambda v: sender == v,
|
"senders": lambda v: sender == v,
|
||||||
"types": lambda v: _matches_wildcard(event_type, v),
|
"types": lambda v: _matches_wildcard(event_type, v),
|
||||||
|
"labels": lambda v: v in labels,
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, match_func in literal_keys.items():
|
for name, match_func in literal_keys.items():
|
||||||
|
|
|
@ -44,6 +44,8 @@ def check_bind_error(e, address, bind_addresses):
|
||||||
bind_addresses (list): Addresses on which the service listens.
|
bind_addresses (list): Addresses on which the service listens.
|
||||||
"""
|
"""
|
||||||
if address == "0.0.0.0" and "::" in bind_addresses:
|
if address == "0.0.0.0" and "::" in bind_addresses:
|
||||||
logger.warn("Failed to listen on 0.0.0.0, continuing because listening on [::]")
|
logger.warning(
|
||||||
|
"Failed to listen on 0.0.0.0, continuing because listening on [::]"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
|
@ -94,7 +94,7 @@ class AppserviceServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -103,7 +103,7 @@ class AppserviceServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ class ClientReaderServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -162,7 +162,7 @@ class ClientReaderServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ class EventCreatorServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -156,7 +156,7 @@ class EventCreatorServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ class FederationReaderServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -141,7 +141,7 @@ class FederationReaderServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ class FederationSenderServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -132,7 +132,7 @@ class FederationSenderServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -204,7 +204,7 @@ class FrontendProxyServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -213,7 +213,7 @@ class FrontendProxyServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,13 @@ from __future__ import print_function
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
|
import resource
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
import psutil
|
|
||||||
from prometheus_client import Gauge
|
from prometheus_client import Gauge
|
||||||
|
|
||||||
from twisted.application import service
|
from twisted.application import service
|
||||||
|
@ -282,7 +283,7 @@ class SynapseHomeServer(HomeServer):
|
||||||
reactor.addSystemEventTrigger("before", "shutdown", s.stopListening)
|
reactor.addSystemEventTrigger("before", "shutdown", s.stopListening)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -291,7 +292,7 @@ class SynapseHomeServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
def run_startup_checks(self, db_conn, database_engine):
|
def run_startup_checks(self, db_conn, database_engine):
|
||||||
all_users_native = are_all_users_on_domain(
|
all_users_native = are_all_users_on_domain(
|
||||||
|
@ -471,6 +472,87 @@ class SynapseService(service.Service):
|
||||||
return self._port.stopListening()
|
return self._port.stopListening()
|
||||||
|
|
||||||
|
|
||||||
|
# Contains the list of processes we will be monitoring
|
||||||
|
# currently either 0 or 1
|
||||||
|
_stats_process = []
|
||||||
|
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def phone_stats_home(hs, stats, stats_process=_stats_process):
|
||||||
|
logger.info("Gathering stats for reporting")
|
||||||
|
now = int(hs.get_clock().time())
|
||||||
|
uptime = int(now - hs.start_time)
|
||||||
|
if uptime < 0:
|
||||||
|
uptime = 0
|
||||||
|
|
||||||
|
stats["homeserver"] = hs.config.server_name
|
||||||
|
stats["server_context"] = hs.config.server_context
|
||||||
|
stats["timestamp"] = now
|
||||||
|
stats["uptime_seconds"] = uptime
|
||||||
|
version = sys.version_info
|
||||||
|
stats["python_version"] = "{}.{}.{}".format(
|
||||||
|
version.major, version.minor, version.micro
|
||||||
|
)
|
||||||
|
stats["total_users"] = yield hs.get_datastore().count_all_users()
|
||||||
|
|
||||||
|
total_nonbridged_users = yield hs.get_datastore().count_nonbridged_users()
|
||||||
|
stats["total_nonbridged_users"] = total_nonbridged_users
|
||||||
|
|
||||||
|
daily_user_type_results = yield hs.get_datastore().count_daily_user_type()
|
||||||
|
for name, count in iteritems(daily_user_type_results):
|
||||||
|
stats["daily_user_type_" + name] = count
|
||||||
|
|
||||||
|
room_count = yield hs.get_datastore().get_room_count()
|
||||||
|
stats["total_room_count"] = room_count
|
||||||
|
|
||||||
|
stats["daily_active_users"] = yield hs.get_datastore().count_daily_users()
|
||||||
|
stats["monthly_active_users"] = yield hs.get_datastore().count_monthly_users()
|
||||||
|
stats["daily_active_rooms"] = yield hs.get_datastore().count_daily_active_rooms()
|
||||||
|
stats["daily_messages"] = yield hs.get_datastore().count_daily_messages()
|
||||||
|
|
||||||
|
r30_results = yield hs.get_datastore().count_r30_users()
|
||||||
|
for name, count in iteritems(r30_results):
|
||||||
|
stats["r30_users_" + name] = count
|
||||||
|
|
||||||
|
daily_sent_messages = yield hs.get_datastore().count_daily_sent_messages()
|
||||||
|
stats["daily_sent_messages"] = daily_sent_messages
|
||||||
|
stats["cache_factor"] = CACHE_SIZE_FACTOR
|
||||||
|
stats["event_cache_size"] = hs.config.event_cache_size
|
||||||
|
|
||||||
|
#
|
||||||
|
# Performance statistics
|
||||||
|
#
|
||||||
|
old = stats_process[0]
|
||||||
|
new = (now, resource.getrusage(resource.RUSAGE_SELF))
|
||||||
|
stats_process[0] = new
|
||||||
|
|
||||||
|
# Get RSS in bytes
|
||||||
|
stats["memory_rss"] = new[1].ru_maxrss
|
||||||
|
|
||||||
|
# Get CPU time in % of a single core, not % of all cores
|
||||||
|
used_cpu_time = (new[1].ru_utime + new[1].ru_stime) - (
|
||||||
|
old[1].ru_utime + old[1].ru_stime
|
||||||
|
)
|
||||||
|
if used_cpu_time == 0 or new[0] == old[0]:
|
||||||
|
stats["cpu_average"] = 0
|
||||||
|
else:
|
||||||
|
stats["cpu_average"] = math.floor(used_cpu_time / (new[0] - old[0]) * 100)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Database version
|
||||||
|
#
|
||||||
|
|
||||||
|
stats["database_engine"] = hs.get_datastore().database_engine_name
|
||||||
|
stats["database_server_version"] = hs.get_datastore().get_server_version()
|
||||||
|
logger.info("Reporting stats to %s: %s" % (hs.config.report_stats_endpoint, stats))
|
||||||
|
try:
|
||||||
|
yield hs.get_proxied_http_client().put_json(
|
||||||
|
hs.config.report_stats_endpoint, stats
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Error reporting stats: %s", e)
|
||||||
|
|
||||||
|
|
||||||
def run(hs):
|
def run(hs):
|
||||||
PROFILE_SYNAPSE = False
|
PROFILE_SYNAPSE = False
|
||||||
if PROFILE_SYNAPSE:
|
if PROFILE_SYNAPSE:
|
||||||
|
@ -497,91 +579,19 @@ def run(hs):
|
||||||
reactor.run = profile(reactor.run)
|
reactor.run = profile(reactor.run)
|
||||||
|
|
||||||
clock = hs.get_clock()
|
clock = hs.get_clock()
|
||||||
start_time = clock.time()
|
|
||||||
|
|
||||||
stats = {}
|
stats = {}
|
||||||
|
|
||||||
# Contains the list of processes we will be monitoring
|
def performance_stats_init():
|
||||||
# currently either 0 or 1
|
_stats_process.clear()
|
||||||
stats_process = []
|
_stats_process.append(
|
||||||
|
(int(hs.get_clock().time(), resource.getrusage(resource.RUSAGE_SELF)))
|
||||||
|
)
|
||||||
|
|
||||||
def start_phone_stats_home():
|
def start_phone_stats_home():
|
||||||
return run_as_background_process("phone_stats_home", phone_stats_home)
|
return run_as_background_process(
|
||||||
|
"phone_stats_home", phone_stats_home, hs, stats
|
||||||
@defer.inlineCallbacks
|
|
||||||
def phone_stats_home():
|
|
||||||
logger.info("Gathering stats for reporting")
|
|
||||||
now = int(hs.get_clock().time())
|
|
||||||
uptime = int(now - start_time)
|
|
||||||
if uptime < 0:
|
|
||||||
uptime = 0
|
|
||||||
|
|
||||||
stats["homeserver"] = hs.config.server_name
|
|
||||||
stats["server_context"] = hs.config.server_context
|
|
||||||
stats["timestamp"] = now
|
|
||||||
stats["uptime_seconds"] = uptime
|
|
||||||
version = sys.version_info
|
|
||||||
stats["python_version"] = "{}.{}.{}".format(
|
|
||||||
version.major, version.minor, version.micro
|
|
||||||
)
|
)
|
||||||
stats["total_users"] = yield hs.get_datastore().count_all_users()
|
|
||||||
|
|
||||||
total_nonbridged_users = yield hs.get_datastore().count_nonbridged_users()
|
|
||||||
stats["total_nonbridged_users"] = total_nonbridged_users
|
|
||||||
|
|
||||||
daily_user_type_results = yield hs.get_datastore().count_daily_user_type()
|
|
||||||
for name, count in iteritems(daily_user_type_results):
|
|
||||||
stats["daily_user_type_" + name] = count
|
|
||||||
|
|
||||||
room_count = yield hs.get_datastore().get_room_count()
|
|
||||||
stats["total_room_count"] = room_count
|
|
||||||
|
|
||||||
stats["daily_active_users"] = yield hs.get_datastore().count_daily_users()
|
|
||||||
stats["monthly_active_users"] = yield hs.get_datastore().count_monthly_users()
|
|
||||||
stats[
|
|
||||||
"daily_active_rooms"
|
|
||||||
] = yield hs.get_datastore().count_daily_active_rooms()
|
|
||||||
stats["daily_messages"] = yield hs.get_datastore().count_daily_messages()
|
|
||||||
|
|
||||||
r30_results = yield hs.get_datastore().count_r30_users()
|
|
||||||
for name, count in iteritems(r30_results):
|
|
||||||
stats["r30_users_" + name] = count
|
|
||||||
|
|
||||||
daily_sent_messages = yield hs.get_datastore().count_daily_sent_messages()
|
|
||||||
stats["daily_sent_messages"] = daily_sent_messages
|
|
||||||
stats["cache_factor"] = CACHE_SIZE_FACTOR
|
|
||||||
stats["event_cache_size"] = hs.config.event_cache_size
|
|
||||||
|
|
||||||
if len(stats_process) > 0:
|
|
||||||
stats["memory_rss"] = 0
|
|
||||||
stats["cpu_average"] = 0
|
|
||||||
for process in stats_process:
|
|
||||||
stats["memory_rss"] += process.memory_info().rss
|
|
||||||
stats["cpu_average"] += int(process.cpu_percent(interval=None))
|
|
||||||
|
|
||||||
stats["database_engine"] = hs.get_datastore().database_engine_name
|
|
||||||
stats["database_server_version"] = hs.get_datastore().get_server_version()
|
|
||||||
logger.info(
|
|
||||||
"Reporting stats to %s: %s" % (hs.config.report_stats_endpoint, stats)
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
yield hs.get_simple_http_client().put_json(
|
|
||||||
hs.config.report_stats_endpoint, stats
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warn("Error reporting stats: %s", e)
|
|
||||||
|
|
||||||
def performance_stats_init():
|
|
||||||
try:
|
|
||||||
process = psutil.Process()
|
|
||||||
# Ensure we can fetch both, and make the initial request for cpu_percent
|
|
||||||
# so the next request will use this as the initial point.
|
|
||||||
process.memory_info().rss
|
|
||||||
process.cpu_percent(interval=None)
|
|
||||||
logger.info("report_stats can use psutil")
|
|
||||||
stats_process.append(process)
|
|
||||||
except (AttributeError):
|
|
||||||
logger.warning("Unable to read memory/cpu stats. Disabling reporting.")
|
|
||||||
|
|
||||||
def generate_user_daily_visit_stats():
|
def generate_user_daily_visit_stats():
|
||||||
return run_as_background_process(
|
return run_as_background_process(
|
||||||
|
|
|
@ -120,7 +120,7 @@ class MediaRepositoryServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -129,7 +129,7 @@ class MediaRepositoryServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ class PusherServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -123,7 +123,7 @@ class PusherServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -326,7 +326,7 @@ class SynchrotronServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -335,7 +335,7 @@ class SynchrotronServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ class UserDirectoryServer(HomeServer):
|
||||||
)
|
)
|
||||||
elif listener["type"] == "metrics":
|
elif listener["type"] == "metrics":
|
||||||
if not self.get_config().enable_metrics:
|
if not self.get_config().enable_metrics:
|
||||||
logger.warn(
|
logger.warning(
|
||||||
(
|
(
|
||||||
"Metrics listener configured, but "
|
"Metrics listener configured, but "
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
|
@ -159,7 +159,7 @@ class UserDirectoryServer(HomeServer):
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
_base.listen_metrics(listener["bind_addresses"], listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warning("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
self.get_tcp_replication().start_replication(self)
|
self.get_tcp_replication().start_replication(self)
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,9 @@ class ApplicationService(object):
|
||||||
ip_range_whitelist=None,
|
ip_range_whitelist=None,
|
||||||
):
|
):
|
||||||
self.token = token
|
self.token = token
|
||||||
self.url = url
|
self.url = (
|
||||||
|
url.rstrip("/") if isinstance(url, str) else None
|
||||||
|
) # url must not end with a slash
|
||||||
self.hs_token = hs_token
|
self.hs_token = hs_token
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
self.server_name = hostname
|
self.server_name = hostname
|
||||||
|
|
|
@ -35,11 +35,11 @@ class CaptchaConfig(Config):
|
||||||
## Captcha ##
|
## Captcha ##
|
||||||
# See docs/CAPTCHA_SETUP for full details of configuring this.
|
# See docs/CAPTCHA_SETUP for full details of configuring this.
|
||||||
|
|
||||||
# This Home Server's ReCAPTCHA public key.
|
# This homeserver's ReCAPTCHA public key.
|
||||||
#
|
#
|
||||||
#recaptcha_public_key: "YOUR_PUBLIC_KEY"
|
#recaptcha_public_key: "YOUR_PUBLIC_KEY"
|
||||||
|
|
||||||
# This Home Server's ReCAPTCHA private key.
|
# This homeserver's ReCAPTCHA private key.
|
||||||
#
|
#
|
||||||
#recaptcha_private_key: "YOUR_PRIVATE_KEY"
|
#recaptcha_private_key: "YOUR_PRIVATE_KEY"
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue