Compare commits

...

168 Commits

Author SHA1 Message Date
Jorik Schellekens
dbbdf1a4a9 Noop config if none is set 2019-09-06 10:25:55 +01:00
Jorik Schellekens
2f360de3b6 Key state is set in servename 2019-09-05 11:57:25 +01:00
Jorik Schellekens
d934837b48 Fix port in use 2019-09-05 11:48:23 +01:00
Jorik Schellekens
5d02267d85 Priv ports not working 2019-09-05 11:43:38 +01:00
Jorik Schellekens
1e5b073fd8 Remove debug output 2019-09-05 11:29:19 +01:00
Jorik Schellekens
0bc1f9c2d1 Database defaults 2019-09-05 10:55:31 +01:00
Jorik Schellekens
af3af0c8e4 Just say the config has been set up. 2019-09-05 10:55:19 +01:00
Jorik Schellekens
4798dd1f6b Update README.rst 2019-08-28 18:08:29 +01:00
Jorik Schellekens
cea9329e0b Black is still the new black 2019-08-28 18:05:39 +01:00
Jorik Schellekens
53b94eff58 Update state representation 2019-08-28 17:43:35 +01:00
Jorik Schellekens
997fe30087 Handle synapse start correctly 2019-08-28 17:41:16 +01:00
Jorik Schellekens
b18761b3e3 Handle synapse start failure correctly 2019-08-28 17:34:24 +01:00
Jorik Schellekens
d56e1bedad Generate extra files needed for startup like the config file. 2019-08-28 16:59:48 +01:00
Jorik Schellekens
b1c9c5a078 Fix config in use check 2019-08-28 16:31:12 +01:00
Jorik Schellekens
d4a52d31ef Extra support for configuring postgres 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ec743b9165 Mk data_dir if it doesn't exist 2019-08-28 15:59:54 +01:00
Jorik Schellekens
6472d30e6a Disable going back when setup is finished 2019-08-28 15:59:54 +01:00
Jorik Schellekens
0cfe4b3db9 Formatting 2019-08-28 15:59:54 +01:00
Jorik Schellekens
0a2c2086c8 Include a done screen 2019-08-28 15:59:54 +01:00
Jorik Schellekens
406a790cf5 Handle errors when synapse fails to start. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
eba2753781 Server throws errors on synctl error 2019-08-28 15:59:54 +01:00
Jorik Schellekens
27135d9a28 Input validation on servername 2019-08-28 15:59:54 +01:00
Jorik Schellekens
127e0b1cd7 Use the write method. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
2b08f5c6bd Error reporting for cert paths 2019-08-28 15:59:54 +01:00
Jorik Schellekens
8e1d22c6f7 Remove option to upload tls certs 2019-08-28 15:59:54 +01:00
Jorik Schellekens
2d56f81d05 We need to check that the pasth is abs and we don't need to validate 2019-08-28 15:59:54 +01:00
Jorik Schellekens
c36c0278b0 Not importing that from there 2019-08-28 15:59:54 +01:00
Jorik Schellekens
66bc80eddf Remove an endpoint that never existed 2019-08-28 15:59:54 +01:00
Jorik Schellekens
01d05e40a9 Remove use of servername endpoint 2019-08-28 15:59:54 +01:00
Jorik Schellekens
1cef415af7 Fix validation wrapper to work for oop.
This is not general enough to be honest.
2019-08-28 15:59:54 +01:00
Jorik Schellekens
13ea03ccd6 Cleanup and use new model 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ea54afb1c3 some better routing 2019-08-28 15:59:54 +01:00
Jorik Schellekens
39f758c07c It's an OO language. Use it like one. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
0690f7fc55 When I'm alone I count myslef. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
d63a58462f Make the model stateless. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
e5622a483c We're not using these 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ec76921ecb We're not doing the whole subconfig thing anymore. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
bbd4880371 Link front and backend to start synapse 2019-08-28 15:59:54 +01:00
Jorik Schellekens
0d73df5c10 Print something if the unforseen happens. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
b78ae32b4e Fix config generation 2019-08-28 15:59:54 +01:00
Jorik Schellekens
763c1d320c Remove unused api endpoints. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
0a5099cd24 Write out the configs. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
932aeca8c6 Quick cleanup! 2019-08-28 15:59:54 +01:00
Jorik Schellekens
e58f3ffe2f Use the nice new api 2019-08-28 15:59:54 +01:00
Jorik Schellekens
4b72b03d00 Don't need to specify those 2019-08-28 15:59:54 +01:00
Jorik Schellekens
69d0da4e32 Move args to generate_config 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ac47193ff0 Produce the remaining config 2019-08-28 15:59:54 +01:00
Jorik Schellekens
aed01c8b93 Tls config 2019-08-28 15:59:54 +01:00
Jorik Schellekens
84d04e9001 Don't need those either 2019-08-28 15:59:54 +01:00
Jorik Schellekens
d79788c953 Won't be needing those 2019-08-28 15:59:54 +01:00
Jorik Schellekens
22fcbbfe5e Started templating the output 2019-08-28 15:59:54 +01:00
Jorik Schellekens
e48e9ba41b How was this even remotely working? 2019-08-28 15:59:54 +01:00
Jorik Schellekens
438d7fa75a Fix config tls path bug 2019-08-28 15:59:54 +01:00
Jorik Schellekens
2f04eb2797 Fix port settings 2019-08-28 15:59:54 +01:00
Nad Chishtie
4ac5bd96e8 Tweak margin 2019-08-28 15:59:54 +01:00
Nad Chishtie
e023fd563f Add missing space 2019-08-28 15:59:54 +01:00
Nad Chishtie
70c04c54b1 Compile 2019-08-28 15:59:54 +01:00
Nad Chishtie
4d2f0090e9 Simplify select styles to fix cross browser bugs 2019-08-28 15:59:54 +01:00
Nad Chishtie
cc8fa83fcc Separate select & input @mixins 2019-08-28 15:59:54 +01:00
Jorik Schellekens
da19eb18be Fix database configuration 2019-08-28 15:59:54 +01:00
Nad Chishtie
58802ef961 Reorder & clean up CSS, remove extraneous classes and properties 2019-08-28 15:59:54 +01:00
Nad Chishtie
1629d91b68 Remove redundant comments 2019-08-28 15:59:54 +01:00
Nad Chishtie
9b0fc37f2d Use a sane system font stack 2019-08-28 15:59:54 +01:00
Nad Chishtie
9349614a16 Various WIP CSS tweaks 2019-08-28 15:59:54 +01:00
Nad Chishtie
20eb0de9b5 Polish delegation spacing 2019-08-28 15:59:54 +01:00
Nad Chishtie
4fe0c9eb1c Polish titles in setup complete 2019-08-28 15:59:54 +01:00
Nad Chishtie
82008faff8 Polish input selection 2019-08-28 15:59:54 +01:00
Nad Chishtie
02dc1cf13a Polish buttons that relate to input 2019-08-28 15:59:54 +01:00
Jorik Schellekens
45557de936 Weird indent 2019-08-28 15:59:54 +01:00
Jorik Schellekens
4bd7d5fa26 Disable continue in setup section 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ab805cd9ad Redundent whitespace 2019-08-28 15:59:54 +01:00
Jorik Schellekens
d48a316a55 Synapse port bug 2019-08-28 15:59:54 +01:00
Jorik Schellekens
d8a19849e8 Clickable 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ff59e6a85c Some little ui bugs 2019-08-28 15:59:54 +01:00
Jorik Schellekens
8b2acd1454 So much lint in my life 2019-08-28 15:59:54 +01:00
Jorik Schellekens
a35dcd33b1 Open servername ui by default 2019-08-28 15:59:54 +01:00
Jorik Schellekens
f56760604d Chevron 2019-08-28 15:59:54 +01:00
Jorik Schellekens
089645458f Only proceed if you've copied/downloaded 2019-08-28 15:59:54 +01:00
Jorik Schellekens
3f8100bda2 Llittle lin 2019-08-28 15:59:54 +01:00
Jorik Schellekens
6afcf0fc92 Nicer TLS button text 2019-08-28 15:59:54 +01:00
Jorik Schellekens
9e7a889418 Fix complete setup bugs 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ed3e3df538 Display port errors inline 2019-08-28 15:59:54 +01:00
Jorik Schellekens
eb537f087f Linting. Unfortunately we're using 4 spaces 2019-08-28 15:59:54 +01:00
Jorik Schellekens
e0452b89f2 Just a little lint 2019-08-28 15:59:54 +01:00
Jorik Schellekens
a26e54860b Smoothing out the UI intro 2019-08-28 15:59:54 +01:00
Jorik Schellekens
d01310d9e9 This removes a bug that would otherwise have happened
... in the future o.0....
2019-08-28 15:59:54 +01:00
Jorik Schellekens
6b0e658440 Get rid of annoying warning and unused prop 2019-08-28 15:59:54 +01:00
Jorik Schellekens
e03421b050 Nicer port text 2019-08-28 15:59:54 +01:00
Jorik Schellekens
e79d63c88d Cursor indications 2019-08-28 15:59:54 +01:00
Jorik Schellekens
2440c70630 Fix Delegation skip 2019-08-28 15:59:54 +01:00
Jorik Schellekens
25f8fba5a3 Setting port bug 2019-08-28 15:59:54 +01:00
Jorik Schellekens
bea99ee6a1 Move port entry into tab 2019-08-28 15:59:54 +01:00
Jorik Schellekens
96778c68d6 Implement state resets when going back. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
53fbf82f4e Remove useless console.logs 2019-08-28 15:59:54 +01:00
Jorik Schellekens
546ce01e0d Same console error 2019-08-28 15:59:54 +01:00
Jorik Schellekens
6bc3ff60c7 Fix errormessage in console. 2019-08-28 15:59:54 +01:00
Nad Chishtie
511f8b626b Add WIP CSS classes 2019-08-28 15:59:54 +01:00
Nad Chishtie
53ecf93da1 First cut of visual polish for installer. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
aa657705e4 Lint scss 2019-08-28 15:59:54 +01:00
Jorik Schellekens
659471703f I hope this doesn't break your design Nad. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
d6c296d184 Love a little less lint 2019-08-28 15:59:54 +01:00
Jorik Schellekens
5414d470f5 Linted and loaded. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
e34448600e Ignore yarn errors 2019-08-28 15:59:54 +01:00
Jorik Schellekens
153a340a6a Ignore config_dir 2019-08-28 15:59:54 +01:00
Jorik Schellekens
3cfe24a32c setup linting. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
c747fca2e8 Add some structure. 2019-08-28 15:59:54 +01:00
Jorik Schellekens
ce2b433bf7 Where did this come from? 2019-08-28 15:59:53 +01:00
Jorik Schellekens
07fe7906ad Highlight invalid ports. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
f618f8a44f Move webui to top level 2019-08-28 15:59:53 +01:00
Jorik Schellekens
893a346212 Remove cli controller 2019-08-28 15:59:53 +01:00
Jorik Schellekens
23a837c194 Complete all toggle actions. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
a41a655819 Set report_stats to false by default. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
2c7a57f9d9 Few more toggles. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
2d9f82e18a Toggle next 2019-08-28 15:59:53 +01:00
Jorik Schellekens
58964e4063 Move to scss. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
62e45eebbf Toggle a bit better. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
2a909302cf Cardify setup completion. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
a5fa7653fa Cardify Database 2019-08-28 15:59:53 +01:00
Jorik Schellekens
b6a8d989f4 Cardify port selection 2019-08-28 15:59:53 +01:00
Jorik Schellekens
a209536849 Remove going back logic for the time being. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
442da5fbdd TLS accordion entry 2019-08-28 15:59:53 +01:00
Jorik Schellekens
353543a780 Accordion stats, keys and delegation. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
6744c080d7 Changes in structure. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
63b9c8d3a6 Start moving to accordion. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
388dcb1e6c Missed some arguments. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
280d50cd4f Forgot to save 2019-08-28 15:59:53 +01:00
Jorik Schellekens
47ca664311 Missing import 2019-08-28 15:59:53 +01:00
Jorik Schellekens
4573aceeeb Restructure state. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
c9c7960390 Refactoring the reducers as prep for accordian style 2019-08-28 15:59:53 +01:00
Jorik Schellekens
7e8f993a84 Some refactoring 2019-08-28 15:59:53 +01:00
Jorik Schellekens
83198702f5 Clean up debug statements 2019-08-28 15:59:53 +01:00
Jorik Schellekens
e00008186b Setup the secret key and start synapse. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
fb592b6c98 Use python instead 2019-08-28 15:59:53 +01:00
Jorik Schellekens
1c9c8186b1 First try at starting synapse 2019-08-28 15:59:53 +01:00
Jorik Schellekens
c5ca02b3d0 Write out the yaml to synapse 2019-08-28 15:59:53 +01:00
Jorik Schellekens
eb10b04865 Relative paths. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
39641482ef My reflex is to write markdown. I forgot this was RST 2019-08-28 15:59:53 +01:00
Jorik Schellekens
eedd86766d Handle relative paths correctly! 2019-08-28 15:59:53 +01:00
Jorik Schellekens
3a8bebff88 That shouldn't be tracked. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
c2bf291a0d Yaml output. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
a8cc43dfac Finished templates, database config, and started converting options to synapse yaml. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
3ae448a183 Templates 2019-08-28 15:59:53 +01:00
Jorik Schellekens
b87c85a87d Startup instructions. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
3a9194e652 Port verification endpoint 2019-08-28 15:59:53 +01:00
Jorik Schellekens
3b3abcc273 Port selection 2019-08-28 15:59:53 +01:00
Jorik Schellekens
d599ab6249 Matrix branding 2019-08-28 15:59:53 +01:00
Jorik Schellekens
96c56177a8 I think that title makes more sense. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
a079c9bf0d UI for port selection. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
7fe1148f44 'not useing' tls is no longer an option. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
02845e7e3a Selecting ports for delegation. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
9a0ba7696e None is no longer a valid reverse proxy option. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
bb79dc1351 Missing import 2019-08-28 15:59:53 +01:00
Jorik Schellekens
2835f033b4 Delegation port selection. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
1481108afa Reverse proxy explenations. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
318b66fa2a Bad hack to make things format correctly 2019-08-28 15:59:53 +01:00
Jorik Schellekens
b5881cab45 Fix text for tlx and remove dud component. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
175ee86e6d Present the Reverse Proxy choice as a TLS config option. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
3242aa9af2 TLS ACME etc 2019-08-28 15:59:53 +01:00
Jorik Schellekens
9280882cb9 Cert endpoints. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
cd0cb18d94 more UI 2019-08-28 15:59:53 +01:00
Jorik Schellekens
e5765ac4af Consolidated servers to avoid CORS. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
2a34815d48 Add basic flow control 2019-08-28 15:59:53 +01:00
Jorik Schellekens
ced3cb281a Add endpoint to check if server has been setup. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
a00ce82e46 Some not too helpful docs. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
3cf68de825 Startup script 2019-08-28 15:59:53 +01:00
Jorik Schellekens
d7a8670cf5 Represent synapses config setup as a data model. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
4163c25449 Set up fronted as a react project. 2019-08-28 15:59:53 +01:00
Jorik Schellekens
d2d36126f7 Set up initial endpoints for backend server. 2019-08-28 15:59:53 +01:00
88 changed files with 5326 additions and 0 deletions

1
synapse_topology/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
config_dir

40
synapse_topology/__init__.py Executable file
View File

@@ -0,0 +1,40 @@
#! python
import argparse
import os.path as path
import sys
from synapse_topology.server import Server
from synapse_topology.model import Model
from twisted.internet import endpoints, reactor
from twisted.web.server import Site
from twisted.logger import (
eventsFromJSONLogFile,
textFileLogObserver,
globalLogPublisher,
)
globalLogPublisher.addObserver(textFileLogObserver(sys.stdout))
parser = argparse.ArgumentParser(description="Synapse configuration util")
parser.add_argument(
"config_dir",
metavar="CONFIG_DIR",
type=str,
help="Path the directory containing synapse's configuration files.",
)
args = parser.parse_args()
if not path.isdir(args.config_dir):
print("'{}' is not a directory.".format(args.config_dir))
exit(1)
model = Model(args.config_dir)
server = Server(model)
server.app.run("localhost", 8888)

View File

@@ -0,0 +1,18 @@
Backend
=======
::
Make sure you have synapse and klein installed in your pip env
```
./__init__.py config_dir
```
Frontend
========
Start the Backend and then
.. code:: bash
cd view/webui
yarn install
yarn watch

View File

@@ -0,0 +1,95 @@
import yaml
import subprocess
from os import mkdir
from os.path import abspath, join, exists, isdir
from synapse.config.homeserver import HomeServerConfig
from .constants import CONFIG_LOCK, CONFIG_LOCK_DATA, DATA_SUBDIR, SERVER_NAME
from .errors import BaseConfigInUseError, ConfigNotFoundError, ServernameNotSetError
from .config import create_config
from .util import is_subpath
from synapse.config import find_config_files
class Model:
"""
The Model brokers modification of the config file and signing keys in the config
directory.
"""
def __init__(self, config_dir):
self.config_dir = abspath(config_dir)
self.data_dir = abspath(join(self.config_dir, DATA_SUBDIR))
if not isdir(self.config_dir):
mkdir(self.config_dir)
if not isdir(self.data_dir):
mkdir(self.data_dir)
def get_config(self, config_path):
"""
Retrieves a config from the config directory. Any path can be provided
but it must be a subdirectory of self.config_dir
Args:
config_path (str): path to the config
Returns:
dict: the yaml parse of the config file
"""
conf_path = abspath(join(self.config_dir, config_path))
if not is_subpath(self.config_dir, conf_path):
raise FileNotFoundError()
with open(conf_path, "r") as f:
return yaml.safe_load(f)
def write_config(self, config):
"""
Given a config generates a templated config from synapse and writes it
out to the config dir. It will raise an exception if the config in
the config directory is in use.
Args:
config (dict): The configuration to template out.
"""
if self.config_in_use():
raise BaseConfigInUseError()
for conf_name, conf in create_config(
self.config_dir, self.data_dir, config
).items():
with open(abspath(join(self.config_dir, conf_name)), "w") as f:
f.write(conf)
def config_in_use(self):
"""
Checks if we set whether the config is in use. If it was set up by the system
but synapse wasn't launched yet we will have set this to False. However if
it's not present we assume someone else has set up synapse before so we assume
the config is in use.
"""
config = {}
config_files = find_config_files([self.config_dir])
for config_file in config_files:
with open(config_file) as stream:
config.update(yaml.safe_load(stream))
if not config:
return False
print(config.get(CONFIG_LOCK))
return config.get(CONFIG_LOCK, True)
def generate_secret_key(self, server_name):
if self.config_in_use():
raise BaseConfigInUseError()
signing_key_path = join(self.config_dir, server_name + ".signing.key")
subprocess.run(["generate_signing_key.py", "-o", signing_key_path])
with open(signing_key_path, "r") as f:
return f.read()

View File

@@ -0,0 +1,66 @@
from os.path import join
import yaml
from synapse.config.database import DatabaseConfig
from synapse.config.homeserver import HomeServerConfig
from synapse.config.logger import LoggingConfig
from synapse.config.server import ServerConfig
from synapse.config.tls import TlsConfig
def create_config(config_dir_path, data_dir_path, conf):
server_name = conf["server_name"]
del conf["server_name"]
server_config_in_use = conf["server_config_in_use"]
del conf["server_config_in_use"]
database_conf = conf["database"]
del conf["database"]
if database_conf["name"] == "sqlite3":
database_conf.setdefault(
"args", {"database": join(data_dir_path, "homeserver.db")}
)
base_configs = [ServerConfig, DatabaseConfig, TlsConfig]
# Generate configs for all the ones we didn't cover explicitely
uninitialized_configs = [
x for x in list(HomeServerConfig.__bases__) if x not in base_configs
]
class BaseConfig(*base_configs):
pass
class AdvancedConfig(*uninitialized_configs):
pass
config_args = {
"config_dir_path": config_dir_path,
"data_dir_path": data_dir_path,
"server_name": server_name,
**conf,
"database_conf": database_conf,
"generate_secrets": True,
}
base_config = BaseConfig()
advanced_config = AdvancedConfig()
base_config_text = base_config.generate_config(**config_args)
advanced_config_text = advanced_config.generate_config(**config_args)
config = {}
config.update(yaml.safe_load(base_config_text))
config.update(yaml.safe_load(advanced_config_text))
base_config.generate_missing_files(config, config_dir_path)
advanced_config.generate_missing_files(config, config_dir_path)
return {
"homeserver_basic_config.yaml": base_config_text
+ "\n\nserver_config_in_use: {}".format(server_config_in_use),
"homeserver_advanced_config.yaml": advanced_config_text,
}

View File

@@ -0,0 +1,22 @@
# Paths
DATA_SUBDIR = "data"
# Config options
SERVER_NAME = "server_name"
CONFIG_LOCK = "server_config_in_use"
SECRET_KEY = "macaroon_secret_key"
CONFIG_LOCK_DATA = """
## CONFIG LOCK ##
# Specifies whether synapse has been started with this config.
# If set to True the setup util will not go through the initialization
# phase which sets the server name and server keys.
{}: {{}}
""".format(
CONFIG_LOCK
)

View File

@@ -0,0 +1,14 @@
class ConfigNotFoundError(FileNotFoundError):
def __init__(self, config_name):
self.config_name = config_name
def get_config_name(self):
return self.config_name
class ServernameNotSetError(Exception):
pass
class BaseConfigInUseError(Exception):
pass

View File

@@ -0,0 +1,8 @@
from os.path import realpath, pardir, sep, relpath
def is_subpath(superpath, subpath):
subpath = realpath(subpath)
superpath = realpath(superpath)
relative = relpath(subpath, superpath)
return not relative.startswith(pardir + sep)

View File

@@ -0,0 +1,3 @@
# from .error_handlers import ErrorHandledServer as Server
from .error_handlers import ErrorHandledServer as Server

View File

View File

@@ -0,0 +1,47 @@
from jsonschema import ValidationError
from simplejson.errors import JSONDecodeError
from synapse_topology.model.errors import (
BaseConfigInUseError,
ConfigNotFoundError,
ServernameNotSetError,
)
from .server import Server
class ErrorHandledServer(Server):
app = Server.app
@app.handle_errors(ValidationError)
def validation_error(self, request, failure):
request.setResponseCode(400)
print("Invalid post schema {}".format(failure.getErrorMessage()))
return "Invalid post schema {}".format(failure.getErrorMessage())
@app.handle_errors(JSONDecodeError)
def json_decode_error(self, request, failure):
request.setResponseCode(400)
return "Invalid post json"
@app.handle_errors(ServernameNotSetError)
def not_initialised(self, request, failure):
request.setResponseCode(500)
return (
"Config file not setup, please initialise it using the /servername endpoint"
)
@app.handle_errors(ConfigNotFoundError)
def config_not_found(self, request, failure):
request.setResponseCode(404)
return "The config does not exist"
@app.handle_errors(BaseConfigInUseError)
def base_config_in_use(self, request, failure):
request.setResponseCode(409)
return "Sever name and keys already configured"
@app.handle_errors(Exception)
def handle_generic_error(self, request, failure):
print(failure)
request.setResponseCode(500)
return "Internal server error\n{}".format(failure)

View File

@@ -0,0 +1,55 @@
BASE_CONFIG_SCHEMA = {
"type": "object",
"properties": {
"server_name": {"type": "string", "minlength": 1},
"report_stats": {"type": "boolean"},
"log_config": {"type": "string", "minlength": 1},
"media_store_path": {"type": "string", "minlength": 1},
"uploads_path": {"type": "string", "minlength": 1},
"pid_file": {"type": "string", "minlength": 1},
"listeners": {"type": "array"},
"acme": {"type": "object"},
"database": {
"type": "object",
"properties": {
"name": {"type": "string", "minlength": 1},
"args": {"type": "object"},
},
"required": ["name"],
},
"tls_certificate_path": {"type": "string", "minlength": 1},
"tls_private_key_path": {"type": "string", "minlength": 1},
"server_config_in_use": {"type": "boolean"},
},
"required": ["server_name", "report_stats", "database"],
}
CERT_PATHS_SCHEMA = {
"type": "object",
"properties": {
"cert_path": {"type": "string", "minlength": 1},
"cert_key_path": {"type": "string", "minlength": 1},
},
"required": ["cert_path", "cert_key_path"],
}
CERTS_SCHEMA = {
"type": "object",
"properties": {
"cert": {"type": "string", "minlength": 1},
"cert_key": {"type": "string", "minlength": 1},
},
"required": ["cert", "cert_key"],
}
PORTS_SCHEMA = {
"type": "object",
"properties": {"ports": {"type": "array"}},
"required": ["ports"],
}
SECRET_KEY_SCHEMA = {
"type": "object",
"properties": {"server_name": {"type": "string", "minlength": "1"}},
"required": ["server_name"],
}

View File

@@ -0,0 +1,99 @@
from os.path import abspath, dirname, join, isabs
from canonicaljson import json
from twisted.web.static import File
from klein import Klein
from .utils import port_checker
from synapse_topology.model import constants
from .schemas import (
BASE_CONFIG_SCHEMA,
CERT_PATHS_SCHEMA,
CERTS_SCHEMA,
PORTS_SCHEMA,
SECRET_KEY_SCHEMA,
)
from .utils import validate_schema, log_body_if_fail
import subprocess
import sys
class Server:
app = Klein()
def __init__(self, model):
self.model = model
def server_webui(self, request):
client_path = abspath(join(dirname(abspath(__file__)), "../webui/dist/"))
print(client_path)
return File(client_path)
app.route("/topology_webui", branch=True)(server_webui)
app.route("/topology_webui/", branch=True)(server_webui)
@app.route("/setup", methods=["GET"])
def get_config_setup(self, request):
return json.dumps(
{
constants.CONFIG_LOCK: self.model.config_in_use(),
"config_dir": self.model.config_dir,
}
)
@app.route("/secretkey", methods=["POST"])
@validate_schema(SECRET_KEY_SCHEMA)
def get_secret_key(self, request, body):
return json.dumps(
{"secret_key": self.model.generate_secret_key(body["server_name"])}
)
@app.route("/config", methods=["GET"])
def get_config(self, request):
return str(self.model.get_config())
@app.route("/config", methods=["POST"])
@validate_schema(BASE_CONFIG_SCHEMA)
def set_config(self, request, body):
self.model.write_config(body)
@app.route("/testcertpaths", methods=["POST"])
def test_cert_paths(self, request):
body = json.loads(request.content.read())
result = {}
config_path = self.model.config_dir
for name, path in body.items():
if not isabs(path):
path = abspath(join(config_path, path))
try:
with open(path, "r"):
result[name] = {"invalid": False, "absolute_path": path}
except:
result[name] = {"invalid": True}
return json.dumps(result)
@app.route("/certs", methods=["POST"])
@validate_schema(CERTS_SCHEMA)
def upload_certs(self, request, body):
self.model.add_certs(**body)
@app.route("/ports", methods=["POST"])
@validate_schema(PORTS_SCHEMA)
def check_ports(self, request, body):
results = []
for port in body["ports"]:
results.append(port_checker(port))
return json.dumps({"ports": results})
@app.route("/start", methods=["POST"])
def start_synapse(self, request):
print("Starting synapse")
subprocess.check_output(["synctl", "start", self.model.config_dir])
@app.route("/favicon.ico")
def noop(self, request):
return

View File

@@ -0,0 +1,47 @@
from functools import wraps
from canonicaljson import json
from jsonschema import validate
from contextlib import closing
import socket
def validate_schema(schema):
def _wrap_validate(func):
@wraps(func)
def _do_validate(self, request):
body = json.loads(request.content.read())
print(body)
validate(instance=body, schema=schema)
return func(self, request, body)
return _do_validate
return _wrap_validate
def port_checker(port):
if port < 0 or 65535 < port:
return False
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
try:
sock.bind(("0.0.0.0", port))
sock.listen()
return True
except:
return False
def log_body_if_fail(func):
@wraps(func)
def _log_wrapper(self, request):
try:
return func(self, request)
except Exception:
body = json.loads(request.content.read())
print(body)
raise
return _log_wrapper

View File

@@ -0,0 +1,9 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread"
],
}

View File

@@ -0,0 +1,131 @@
const path = require('path');
// get the path of the js-sdk so we can extend the config
// eslint supports loading extended configs by module,
// but only if they come from a module that starts with eslint-config-
// So we load the filename directly (and it could be in node_modules/
// or or ../node_modules/ etc)
module.exports = {
parser: "babel-eslint",
plugins: [
"react",
"babel"
],
env: {
es6: true,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
}
},
rules: {
// rules we've always adhered to or now do
"max-len": ["error", {
code: 90,
ignoreComments: true,
}],
curly: ["error", "multi-line"],
"prefer-const": ["error"],
"comma-dangle": ["error", {
arrays: "always-multiline",
objects: "always-multiline",
imports: "always-multiline",
exports: "always-multiline",
functions: "always-multiline",
}],
// loosen jsdoc requirements a little
"require-jsdoc": ["error", {
require: {
FunctionDeclaration: false,
}
}],
"valid-jsdoc": ["error", {
requireParamDescription: false,
requireReturn: false,
requireReturnDescription: false,
}],
// rules we do not want from eslint-recommended
"no-console": ["off"],
"no-constant-condition": ["off"],
"no-empty": ["error", { "allowEmptyCatch": true }],
// rules we do not want from the google styleguide
"object-curly-spacing": ["off"],
"spaced-comment": ["off"],
"guard-for-in": ["off"],
// in principle we prefer single quotes, but life is too short
quotes: ["off"],
// rules we'd ideally like to adhere to, but the current
// code does not (in most cases because it's still ES5)
// we set these to warnings, and assert that the number
// of warnings doesn't exceed a given threshold
"no-var": ["warn"],
"brace-style": ["warn", "1tbs", { "allowSingleLine": true }],
"prefer-rest-params": ["warn"],
"prefer-spread": ["warn"],
"padded-blocks": ["warn"],
"no-extend-native": ["warn"],
"camelcase": ["warn"],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always",
}],
"arrow-parens": "off",
// eslint's built in no-invalid-this rule breaks with class properties
"no-invalid-this": "off",
// so we replace it with a version that is class property aware
"babel/no-invalid-this": "error",
// We appear to follow this most of the time, so let's enforce it instead
// of occasionally following it (or catching it in review)
"keyword-spacing": "error",
/** react **/
// This just uses the react plugin to help eslint known when
// variables have been used in JSX
"react/jsx-uses-vars": "error",
// Don't mark React as unused if we're using JSX
"react/jsx-uses-react": "error",
// bind or arrow function in props causes performance issues
// (but we currently use them in some places)
// It's disabled here, but we should using it sparingly.
"react/jsx-no-bind": "off",
"react/jsx-key": ["error"],
// Components in JSX should always be defined.
"react/jsx-no-undef": "error",
// Assert no spacing in JSX curly brackets
// <Element prop={ consideredError} prop={notConsideredError} />
//
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-curly-spacing.md
//
// Disabled for now - if anything we'd like to *enforce* spacing in JSX
// curly brackets for legibility, but in practice it's not clear that the
// consistency particularly improves legibility here. --Matthew
//
// "react/jsx-curly-spacing": ["error", {"when": "never", "children": {"when": "always"}}],
// Assert spacing before self-closing JSX tags, and no spacing before or
// after the closing slash, and no spacing after the opening bracket of
// the opening tag or closing tag.
//
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-tag-spacing.md
"react/jsx-tag-spacing": ["error"],
},
settings: {
flowtype: {
onlyFilesWithFlowAnnotation: true
},
},
};

3
synapse_topology/webui/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
yarn-error.log
dist

View File

@@ -0,0 +1,23 @@
module.exports = {
"extends": "stylelint-config-standard",
"plugins": [
"stylelint-scss",
],
"rules": {
"indentation": 4,
"comment-empty-line-before": null,
"declaration-empty-line-before": null,
"length-zero-no-unit": null,
"rule-empty-line-before": null,
"color-hex-length": null,
"max-empty-lines": null,
"number-no-trailing-zeros": null,
"number-leading-zero": null,
"selector-list-comma-newline-after": null,
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": [true, {
// https://github.com/vector-im/riot-web/issues/10544
"ignoreAtRules": ["define-mixin"],
}],
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
{
"name": "synapse_topology_webui",
"version": "0.0.0",
"description": "A simple webui for initialising the synapse startup",
"main": "index.js",
"author": "Jorik Schellekens (matrix.org)",
"license": "Apache-2.0",
"private": true,
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/node": "^7.5.5",
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.5.5",
"babel-eslint": "^10.0.2",
"babel-loader": "^8.0.6",
"css-loader": "^3.2.0",
"eslint": "^6.1.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-react": "^7.14.3",
"file-loader": "^4.1.0",
"html-webpack-plugin": "^3.2.0",
"html-webpack-tags-plugin": "^2.0.13",
"less": "^3.9.0",
"node-sass": "^4.12.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"redux-devtools-extension": "^2.13.8",
"sass-loader": "^7.2.0",
"style-loader": "^0.23.1",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.7.2"
},
"scripts": {
"build": "webpack -p --progress --config webpack.config.babel.js",
"dev-build": "webpack --progress -d --config webpack.config.babel.js",
"watch": "webpack --progress -d --config webpack.config.babel.js --watch"
},
"dependencies": {
"fetch-absolute": "^1.0.0",
"react-bootstrap": "^1.0.0-beta.11",
"react-icons": "^3.7.0",
"react-localize-redux": "^3.5.3",
"react-redux": "^7.1.0",
"redux": "^4.0.4",
"redux-thunk": "^2.3.0",
"yaml": "^1.6.0"
}
}

View File

@@ -0,0 +1,46 @@
Digitized data copyright (c) 2010 Google Corporation
with Reserved Font Arimo, Tinos and Cousine.
Copyright (c) 2012 Red Hat, Inc.
with Reserved Font Name Liberation.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,5 @@
<svg width="75" height="32" xmlns="http://www.w3.org/2000/svg">
<g fill="#2D2D2D" fillRule="nonzero">
<path d="M.936.732V31.25H3.13v.732H.095V0h3.034v.732zM9.386 10.407v1.544h.044a4.461 4.461 0 0 1 1.487-1.368c.58-.323 1.245-.485 1.993-.485.72 0 1.377.14 1.972.42.595.279 1.047.771 1.355 1.477.338-.5.796-.941 1.377-1.323.58-.383 1.266-.574 2.06-.574.602 0 1.16.074 1.674.22.514.148.954.383 1.322.707.366.323.653.746.859 1.268.205.522.308 1.15.308 1.887v7.633H20.71v-6.464c0-.383-.015-.743-.044-1.082a2.305 2.305 0 0 0-.242-.882 1.473 1.473 0 0 0-.584-.596c-.257-.146-.606-.22-1.047-.22-.44 0-.796.085-1.068.253-.272.17-.485.39-.639.662a2.654 2.654 0 0 0-.308.927 7.074 7.074 0 0 0-.078 1.048v6.354h-3.128v-6.398c0-.338-.007-.673-.021-1.004a2.825 2.825 0 0 0-.188-.916 1.411 1.411 0 0 0-.55-.673c-.258-.168-.636-.253-1.135-.253a2.33 2.33 0 0 0-.584.1 1.94 1.94 0 0 0-.705.374c-.228.184-.422.449-.584.794-.161.346-.242.798-.242 1.357v6.619H6.434V10.407h2.952zM25.842 12.084a3.751 3.751 0 0 1 1.233-1.17 5.37 5.37 0 0 1 1.685-.629 9.579 9.579 0 0 1 1.884-.187c.573 0 1.153.04 1.74.121.588.081 1.124.24 1.609.475.484.235.88.562 1.19.981.308.42.462.975.462 1.666v5.934c0 .516.03 1.008.088 1.478.058.471.161.824.308 1.06H32.87a4.435 4.435 0 0 1-.22-1.104c-.5.515-1.087.876-1.762 1.081a7.084 7.084 0 0 1-2.071.31c-.544 0-1.05-.067-1.52-.2a3.472 3.472 0 0 1-1.234-.617 2.87 2.87 0 0 1-.826-1.059c-.199-.426-.298-.934-.298-1.522 0-.647.114-1.18.342-1.6.227-.419.52-.753.881-1.004.36-.25.771-.437 1.234-.562.462-.125.929-.224 1.399-.298.47-.073.932-.132 1.387-.176.456-.044.86-.11 1.212-.199.353-.088.631-.217.837-.386.206-.169.301-.415.287-.74 0-.337-.055-.606-.166-.804a1.217 1.217 0 0 0-.44-.464 1.737 1.737 0 0 0-.639-.22 5.292 5.292 0 0 0-.782-.055c-.617 0-1.101.132-1.454.397-.352.264-.558.706-.617 1.323h-3.128c.044-.735.227-1.345.55-1.83zm6.179 4.423a5.095 5.095 0 0 1-.639.165 9.68 9.68 0 0 1-.716.11c-.25.03-.5.067-.749.11a5.616 5.616 0 0 0-.694.177 2.057 2.057 0 0 0-.594.298c-.17.125-.305.284-.408.474-.103.192-.154.434-.154.728 0 .28.051.515.154.706.103.192.242.342.419.453.176.11.381.187.617.231.234.044.477.066.726.066.617 0 1.094-.102 1.432-.309.338-.205.587-.452.75-.739.16-.286.26-.576.297-.87.036-.295.055-.53.055-.707v-1.17a1.4 1.4 0 0 1-.496.277zM43.884 10.407v2.096h-2.291v5.647c0 .53.088.883.264 1.059.176.177.529.265 1.057.265.177 0 .345-.007.507-.022.161-.015.316-.037.463-.066v2.426a7.49 7.49 0 0 1-.882.089 21.67 21.67 0 0 1-.947.022c-.484 0-.944-.034-1.377-.1a3.233 3.233 0 0 1-1.145-.386 2.04 2.04 0 0 1-.782-.816c-.191-.353-.287-.816-.287-1.39v-6.728H36.57v-2.096h1.894v-3.42h3.129v3.42h2.29zM48.355 10.407v2.118h.044a3.907 3.907 0 0 1 1.454-1.754 4.213 4.213 0 0 1 1.036-.497 3.734 3.734 0 0 1 1.145-.176c.206 0 .433.037.683.11v2.912a5.862 5.862 0 0 0-.528-.077 5.566 5.566 0 0 0-.595-.033c-.573 0-1.058.096-1.454.287a2.52 2.52 0 0 0-.958.783 3.143 3.143 0 0 0-.518 1.158 6.32 6.32 0 0 0-.154 1.434v5.14h-3.128V10.407h2.973zM54.039 8.642V6.06h3.128v2.582H54.04zm3.128 1.765v11.405H54.04V10.407h3.128zM58.797 10.407h3.569l2.005 2.978 1.982-2.978h3.459l-3.745 5.339 4.208 6.067h-3.57l-2.378-3.596-2.38 3.596h-3.502l4.097-6.001zM74.094 31.25V.732H71.9V0h3.035v31.982H71.9v-.732z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,24 @@
export const DELEGATION_TYPES = {
LOCAL: "local",
WELL_KNOWN: ".well_known",
DNS: "DNS SRV",
};
export const REVERSE_PROXY_TYPES = {
CADDY: "CADDY",
APACHE: "APACHE",
HAPROXY: "HAPROXY",
NGINX: "NGINX",
OTHER: "OTHER",
};
export const TLS_TYPES = {
ACME: "ACME",
TLS: "TLS",
REVERSE_PROXY: "REVERSE_PROXY",
};
export const DATABASE_TYPES = {
SQLITE3: "sqlite3",
POSTGRES: "psycopg2",
};

View File

@@ -0,0 +1,329 @@
import {
ADVANCE_UI,
BACK_UI,
SET_SERVERNAME,
SET_STATS,
BASE_CONFIG_CHECKED,
FAIL,
SET_SECRET_KEY,
GETTING_SECRET_KEY,
SET_DELEGATION,
SET_DELEGATION_SERVERNAME,
SET_DELEGATION_PORTS,
SET_REVERSE_PROXY,
SET_TLS,
TESTING_TLS_CERT_PATHS,
SET_TLS_CERT_PATHS,
SET_TLS_CERT_PATHS_VALIDITY,
SET_TLS_CERT_FILES,
UPLOADING_TLS_CERT_PATHS,
TESTING_SYNAPSE_PORTS,
SET_SYNAPSE_PORTS,
SET_SYNAPSE_PORTS_FREE,
SET_DATABASE,
SET_CONFIG_DIR,
SYNAPSE_START_FAILED,
} from './types';
import {
getServerSetup,
getSecretkey,
postCertPaths,
postCerts,
testPorts,
postConfig,
startSynapse,
} from '../api';
import { CONFIG_LOCK, CONFIG_DIR } from '../api/constants';
import { baseConfigToSynapseConfig } from '../utils/yaml';
export const startup = () => {
return dispatch => {
getServerSetup().then(
result => {
dispatch(start(result[CONFIG_LOCK]));
dispatch(setConfigDir(result[CONFIG_DIR]));
},
error => dispatch(fail(error)),
)
};
};
const setConfigDir = dir => ({
type: SET_CONFIG_DIR,
configDir: dir,
});
export const generateSecretKeys = serverName => {
return dispatch => {
dispatch(getSecretKey(serverName))
};
};
export const setTlsCertPaths = (certPath, certKeyPath, callback) => {
return dispatch => {
dispatch(testingTlsCertPaths(true));
postCertPaths(certPath, certKeyPath)
.then(
result => dispatch(checkTlsCertPathValidity(result, callback)),
error => dispatch(fail(error)),
);
};
};
const setTlsCerts = (certPath, certKeyPath) => ({
type: SET_TLS_CERT_PATHS,
certPath: certPath,
certKeyPath: certKeyPath,
});
const testingTlsCertPaths = testing => ({
type: TESTING_TLS_CERT_PATHS,
testing,
});
const checkTlsCertPathValidity =
({ cert_path: certPath, cert_key_path: certKeyPath }, callback) => {
return dispatch => {
dispatch(testingTlsCertPaths(false));
dispatch(setTlsCerts(certPath.absolute_path, certKeyPath.absolute_path))
dispatch(setCertPathValidity({ certPath, certKeyPath }));
if (!certPath.invalid && !certKeyPath.invalid) {
dispatch(advanceUI());
callback();
};
};
};
export const uploadTlsCertFiles = (tlsCertFile, tlsCertKeyFile) =>
dispatch => {
dispatch(setTlsCertFiles(tlsCertFile, tlsCertKeyFile));
dispatch(uploadingTlsCertFiles(true));
postCerts(tlsCertFile, tlsCertKeyFile)
.then(
result => {
dispatch(uploadingTlsCertFiles(false));
dispatch(advanceUI())
},
error => dispatch(fail(error)),
)
};
const uploadingTlsCertFiles = uploading => ({
type: UPLOADING_TLS_CERT_PATHS,
uploading,
});
export const setTlsCertFiles = (tlsCertFile, tlsCertKeyFile) => ({
type: SET_TLS_CERT_FILES,
tlsCertFile,
tlsCertKeyFile,
})
const setCertPathValidity = ({ certPath, certKeyPath }) => ({
type: SET_TLS_CERT_PATHS_VALIDITY,
certPathInvalid: certPath.invalid,
certKeyPathInvalid: certKeyPath.invalid,
});
export const gettingSecretKeys = () => ({
type: GETTING_SECRET_KEY,
});
export const getSecretKey = serverName => {
return dispatch => {
getSecretkey(serverName).then(
result => dispatch(setSecretKey(result)),
error => dispatch(fail(error)),
)
};
};
export const setSecretKey = key => ({
type: SET_SECRET_KEY,
key,
});
export const start = setupDone => ({
type: BASE_CONFIG_CHECKED,
setupDone,
});
export const fail = reason => ({
type: FAIL,
reason,
});
export const advanceUI = option => ({
type: ADVANCE_UI,
option,
});
export const setServername = servername => ({
type: SET_SERVERNAME,
servername,
});
export const setStats = consent => ({
type: SET_STATS,
consent,
});
export const setDelegation = delegationType => ({
type: SET_DELEGATION,
delegationType,
});
export const setDelegationServername = servername => ({
type: SET_DELEGATION_SERVERNAME,
servername,
});
export const setDelegationPorts = (federationPort, clientPort) => ({
type: SET_DELEGATION_PORTS,
federationPort,
clientPort,
});
export const setReverseProxy = proxyType => ({
type: SET_REVERSE_PROXY,
proxyType,
});
export const setTls = tlsType => ({
type: SET_TLS,
tlsType,
});
export const setSynapsePorts = (federationPort, clientPort, callback) => {
const fedPortPriv = federationPort < 1024;
const clientPortPriv = clientPort < 1024;
return dispatch => {
dispatch(testingSynapsePorts(true));
dispatch({
type: SET_SYNAPSE_PORTS,
federationPort,
clientPort,
})
testPorts([federationPort, clientPort])
.then(
results => dispatch(updatePortsFree(
fedPortPriv ? true : results.ports[0],
clientPortPriv ? true : results.ports[1],
callback,
)),
error => dispatch(fail(error)),
)
}
};
export const updatePortsFree =
(synapseFederationPortFree, synapseClientPortFree, callback) => {
return dispatch => {
dispatch(testingSynapsePorts(false));
dispatch({
type: SET_SYNAPSE_PORTS_FREE,
synapseFederationPortFree,
synapseClientPortFree,
});
if (synapseFederationPortFree && synapseClientPortFree) {
callback();
dispatch(advanceUI());
}
}
}
export const testingSynapsePorts = verifying => ({
type: TESTING_SYNAPSE_PORTS,
verifying,
})
export const setDatabase = databaseConfig => ({
type: SET_DATABASE,
databaseConfig,
})
export const writeConfig = (callback) => {
return (dispatch, getState) => {
postConfig(baseConfigToSynapseConfig(getState().baseConfig))
.then(
res => startSynapse().then(
res => {
if (res.ok) {
dispatch(advanceUI());
callback();
} else {
dispatch(synapseStartFailed());
}
},
error => {
fail(error);
dispatch(synapseStartFailed());
}
),
error => {
dispatch(fail(error));
dispatch(synapseStartStartFailed())
}
)
}
}
export const synapseStartFailed = () => ({
type: SYNAPSE_START_FAILED,
})
export const resetUI = (ui) => ({
type: BACK_UI,
ui,
})

View File

@@ -0,0 +1,25 @@
export const ADVANCE_UI = 'ADVANCE_UI';
export const BACK_UI = 'BACK_UI';
export const SET_SERVERNAME = 'SET_SERVERNAME';
export const SET_STATS = 'SET_STATS';
export const BASE_CONFIG_CHECKED = 'BASE_CONFIG_CHECKED';
export const FAIL = 'NETWORK_FAIL';
export const SET_SECRET_KEY = 'SET_SECRET_KEY';
export const GETTING_SECRET_KEY = 'GETTING_SECRET_KEY';
export const SET_DELEGATION = 'SET_DELEGATION';
export const SET_DELEGATION_SERVERNAME = 'SET_DELEGATION_SERVERNAME';
export const SET_DELEGATION_PORTS = 'SET_DELEGATION_PORTS';
export const SET_REVERSE_PROXY = 'SET_REVERSE_PROXY';
export const TESTING_TLS_CERT_PATHS = 'TESTING_TLS_CERT_PATHS';
export const UPLOADING_TLS_CERT_PATHS = 'UPLOADING_TLS_CERT_PATHS';
export const SET_TLS = 'SET_TLS';
export const SET_TLS_CERT_PATHS = 'SET_TLS_CERT_PATHS';
export const SET_TLS_CERT_PATHS_VALIDITY = 'SET_TLS_CERT_PATHS_VALIDITY';
export const SET_TLS_CERT_FILES = 'SET_TLS_CERT_FILES';
export const TESTING_SYNAPSE_PORTS = 'TESTING_SYNAPSE_PORTS';
export const SET_SYNAPSE_PORTS = 'SET_SYNAPSE_PORTS';
export const SET_SYNAPSE_PORTS_FREE = 'SET_SYNAPSE_PORTS_IN_USE';
export const SET_DATABASE = 'SET_DATABASE';
export const SET_CONFIG_DIR = 'SET_CONFIG_DIR';
export const WRITE_CONFIG = 'WRITE_CONFIG';
export const SYNAPSE_START_FAILED = 'SYNAPSE_START_FAILED';

View File

@@ -0,0 +1,11 @@
export const API_URL = "http://localhost:8888/";
export const SERVER_NAME = "/servername";
export const SECRET_KEY = "/secretkey";
export const CONFIG = "/config";
export const CONFIG_SOMETHING = "/config_something";
export const SETUP_CHECK = "/setup";
export const CERT_PATHS = "/testcertpaths";
export const TEST_PORTS = "/ports";
export const CONFIG_LOCK = "server_config_in_use";
export const CONFIG_DIR = "config_dir";
export const START = "/start";

View File

@@ -0,0 +1,91 @@
import fetchAbsolute from 'fetch-absolute';
import {
API_URL,
CONFIG,
SECRET_KEY,
SERVER_NAME,
SETUP_CHECK,
CERT_PATHS,
TEST_PORTS,
START,
} from './constants';
const fetchAbs = fetchAbsolute(fetch)(API_URL)
export const getServerName = () =>
fetchAbs(SERVER_NAME)
.then(res => res.json())
export const postCertPaths = (certPath, certKeyPath) =>
fetchAbs(
CERT_PATHS,
{
method: 'POST',
body: JSON.stringify({
// eslint-disable-next-line camelcase
cert_path: certPath,
// eslint-disable-next-line camelcase
cert_key_path: certKeyPath,
}),
},
).then(res => res.json())
export const postCerts = (cert, certKey) =>
fetchAbs(
CERT_PATHS,
{
method: 'POST',
body: JSON.stringify({
cert,
// eslint-disable-next-line camelcase
cert_key: certKey,
}),
},
)
export const testPorts = (ports) =>
fetchAbs(
TEST_PORTS,
{
method: 'POST',
body: JSON.stringify({
ports,
}),
},
).then(res => res.json())
export const getSecretkey = serverName =>
fetchAbs(
SECRET_KEY,
{
method: 'POST',
body: JSON.stringify({
server_name: serverName,
})
}
)
.then(res => res.json())
.then(json => json.secret_key)
export const postConfig = (config) =>
fetchAbs(
CONFIG,
{
method: 'POST',
body: JSON.stringify(config),
},
);
// Checks if the server's base config has been setup.
export const getServerSetup = () => fetchAbs(SETUP_CHECK)
.then(res => res.json())
export const startSynapse = () => fetchAbs(
START,
{
method: 'POST',
}
)

View File

@@ -0,0 +1,24 @@
import React from 'react';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import { reset } from 'ansi-colors';
import Chevron from './Chevron';
export default ({ active, open, children, eventKey, as, reset }) => {
const clickable = active & !open;
const toggle = useAccordionToggle(eventKey);
const decoratedOnClick = () => {
if (clickable) {
toggle();
reset();
}
}
const As = as;
return <div className={clickable ? "active-card-header" : "inactive-card-header"}>
<As onClick={decoratedOnClick}> {children} <Chevron open={open} /></As>
</div>
}

View File

@@ -0,0 +1,39 @@
/* eslint-disable max-len */
import React from 'react';
import ContentWrapper from '../containers/ContentWrapper';
import ButtonDisplay from './ButtonDisplay';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import { nextUI } from '../reducers/setup-ui-reducer';
export default ({ started, servername, onClick }) => {
const toggle = useAccordionToggle(nextUI())
const wrappedOnClick = () => {
onClick();
toggle();
}
const prompt = servername ? "Configuring " + servername : "Let's configure your Synapse server."
return <ContentWrapper>
<div className='baseintro'>
<svg width="113" height="48" xmlns="http://www.w3.org/2000/svg">
<g fill="#2D2D2D" fillRule="nonzero">
<path d="M1.404 1.098v45.776h3.29v1.099H.142V0h4.552v1.098zM14.079 15.61v2.317h.065c.617-.882 1.36-1.567 2.231-2.052.87-.485 1.867-.728 2.99-.728 1.08 0 2.066.21 2.958.629.892.42 1.57 1.158 2.032 2.217.507-.75 1.195-1.412 2.066-1.986.87-.573 1.9-.86 3.09-.86.903 0 1.74.11 2.511.33.77.222 1.432.575 1.982 1.06.55.485.98 1.12 1.29 1.902.307.784.462 1.727.462 2.83v11.45h-4.693v-9.696c0-.574-.022-1.114-.066-1.622-.044-.507-.165-.948-.363-1.323a2.21 2.21 0 0 0-.876-.894c-.385-.22-.909-.33-1.57-.33-.66 0-1.195.127-1.602.38a2.758 2.758 0 0 0-.958.993 3.981 3.981 0 0 0-.463 1.39 10.611 10.611 0 0 0-.116 1.572v9.53h-4.692v-9.597c0-.507-.011-1.009-.033-1.505a4.238 4.238 0 0 0-.28-1.374 2.117 2.117 0 0 0-.827-1.01c-.386-.252-.953-.38-1.702-.38-.22 0-.512.05-.875.15-.364.099-.717.287-1.058.562-.342.276-.633.673-.876 1.191-.242.519-.363 1.197-.363 2.035v9.928H9.65V15.61h4.428zM38.763 18.125a5.627 5.627 0 0 1 1.85-1.754 8.056 8.056 0 0 1 2.528-.943 14.368 14.368 0 0 1 2.825-.281c.86 0 1.73.06 2.61.182.881.121 1.686.359 2.413.711a4.757 4.757 0 0 1 1.784 1.473c.463.628.694 1.461.694 2.498v8.902c0 .773.044 1.511.132 2.217.088.706.242 1.236.463 1.588h-4.758a6.888 6.888 0 0 1-.33-1.655 6.117 6.117 0 0 1-2.644 1.623 10.625 10.625 0 0 1-3.107.463c-.815 0-1.575-.1-2.28-.298a5.207 5.207 0 0 1-1.85-.927 4.304 4.304 0 0 1-1.24-1.588c-.297-.64-.446-1.401-.446-2.283 0-.97.17-1.77.513-2.4a4.28 4.28 0 0 1 1.322-1.505 5.913 5.913 0 0 1 1.85-.844 19.932 19.932 0 0 1 2.098-.447c.705-.11 1.4-.198 2.082-.264a12.534 12.534 0 0 0 1.818-.298c.529-.133.947-.325 1.255-.58.308-.253.452-.622.43-1.108 0-.507-.083-.91-.248-1.208a1.825 1.825 0 0 0-.66-.695 2.606 2.606 0 0 0-.959-.331 7.938 7.938 0 0 0-1.173-.083c-.925 0-1.652.199-2.18.596-.53.397-.838 1.06-.926 1.986h-4.692c.065-1.103.34-2.02.826-2.747zm9.268 6.635c-.297.1-.617.182-.958.248-.341.067-.7.122-1.074.166-.374.044-.749.1-1.123.165-.353.066-.7.155-1.04.265-.343.11-.64.26-.893.447a2.125 2.125 0 0 0-.612.711c-.154.287-.231.651-.231 1.092 0 .419.077.773.231 1.059.154.287.364.513.628.678.265.166.573.282.926.348.352.066.715.099 1.09.099.925 0 1.64-.154 2.148-.463.506-.309.88-.679 1.123-1.11.242-.43.391-.864.447-1.306.054-.44.082-.794.082-1.059v-1.754a2.101 2.101 0 0 1-.744.414zM65.825 15.61v3.144H62.39v8.472c0 .793.132 1.323.397 1.588.263.264.793.397 1.586.397.264 0 .517-.011.76-.033a6.52 6.52 0 0 0 .693-.1v3.64c-.396.067-.837.11-1.321.133-.485.021-.959.033-1.421.033-.727 0-1.416-.05-2.065-.15a4.85 4.85 0 0 1-1.719-.578 3.06 3.06 0 0 1-1.173-1.224c-.286-.53-.43-1.225-.43-2.086V18.754h-2.841V15.61h2.842v-5.13h4.692v5.13h3.436zM72.533 15.61v3.177h.066c.22-.53.517-1.02.892-1.473a5.86 5.86 0 0 1 1.289-1.158 6.32 6.32 0 0 1 1.553-.745 5.601 5.601 0 0 1 1.719-.265c.308 0 .649.056 1.024.166v4.369a8.793 8.793 0 0 0-.793-.116 8.35 8.35 0 0 0-.893-.05c-.859 0-1.586.144-2.18.43a3.779 3.779 0 0 0-1.438 1.175 4.714 4.714 0 0 0-.776 1.737 9.48 9.48 0 0 0-.232 2.151v7.71h-4.692V15.61h4.461zM81.058 12.962V9.091h4.693v3.871h-4.693zm4.693 2.648v17.109h-4.693V15.61h4.693zM88.196 15.61h5.353l3.007 4.467 2.974-4.467h5.188L99.1 23.618l6.311 9.1h-5.353l-3.57-5.393-3.568 5.394h-5.254l6.146-9.001zM111.14 46.874V1.098h-3.289V0h4.552v47.973h-4.552v-1.099z" />
</g>
</svg>
<h1>Setting up Synapse</h1>
<p>{prompt}</p>
{
!started ?
<ButtonDisplay><button onClick={wrappedOnClick}>Get Started</button></ButtonDisplay>
: undefined
}
</div>
</ContentWrapper>
}

View File

@@ -0,0 +1,5 @@
import React from 'react';
import style from '../../scss/main.scss';
export default ({ children }) => <div className={style.buttonDisplay}>{children}</div>

View File

@@ -0,0 +1,7 @@
import React from 'react';
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
export default ({ open }) =>
<span className="chevron">
{open ? <FaChevronDown /> : <FaChevronRight />}
</span>

View File

@@ -0,0 +1,94 @@
import React, { useState } from 'react';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import ReverseProxySampleConfig from '../containers/ReverseProxySampleConfig'
import DelegationSampleConfig from '../containers/DelegationSampleConfig';
import AccordionToggle from '../containers/AccordionToggle';
import InlineError from '../components/InlineError';
import { TLS_TYPES, DELEGATION_TYPES } from '../actions/constants';
import { COMPLETE_UI } from '../reducers/ui-constants';
import { nextUI } from '../reducers/setup-ui-reducer';
export default ({
tlsType,
delegationType,
synapseStartFailed,
configDir,
onClick,
}) => {
const toggle = useAccordionToggle(nextUI(COMPLETE_UI));
const decoratedOnClick = () => {
onClick(toggle);
}
const [revProxyDownloaded, setRevProxyDownloaded] = useState(false);
const [delegationDownloaded, setDelegationDownloaded] = useState(false);
const revProxyBody = <Card.Body>
<ReverseProxySampleConfig onClick={() => setRevProxyDownloaded(true)} />
<button
disabled={!revProxyDownloaded}
onClick={() => setBody(body + 1)}
>Next</button>
</Card.Body >
const delegationBody = <Card.Body>
<DelegationSampleConfig onClick={() => setDelegationDownloaded(true)} />
<button
disabled={!delegationDownloaded}
onClick={() => setBody(body + 1)}
>Next</button>
</Card.Body>
const finishedBody = <Card.Body>
<InlineError error={synapseStartFailed ? "Couldn't start synapse." : undefined}>
<button onClick={decoratedOnClick}>Start Synapse</button>
</InlineError>
<hr />
<p>
In future use <a href="https://manpages.debian.org/testing/matrix-synapse/synctl.1.en.html">
synctl</a> to start and stop synapse. Use the following to start synapse again:
</p>
<pre>
<code>
synctl start {configDir}
</code>
</pre>
</Card.Body>
const show = [];
const [body, setBody] = useState(0);
if (tlsType == TLS_TYPES.REVERSE_PROXY) {
show.push(revProxyBody);
}
if (delegationType != DELEGATION_TYPES.LOCAL) {
show.push(delegationBody)
}
show.push(finishedBody)
return <Card>
<AccordionToggle as={Card.Header} eventKey={COMPLETE_UI}>
Setup Complete
</AccordionToggle>
<Accordion.Collapse eventKey={COMPLETE_UI}>
{show[body]}
</Accordion.Collapse>
</Card>
}

View File

@@ -0,0 +1,13 @@
import React, { useState } from 'react';
import ContentWrapper from '../containers/ContentWrapper';
export default () => {
return <ContentWrapper>
<h1>Config selection</h1>
<p>The base config has already been setup.</p>
<p>If you want to start the installation from scratch please delete the
config yaml.</p>
</ContentWrapper>;
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
import style from '../../scss/main.scss';
export default ({ children }) => {
return <div className={style.contentWrapper}>{children}</div>
}

View File

@@ -0,0 +1,113 @@
import React, { useState } from 'react';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import {
DATABASE_TYPES,
} from '../actions/constants'
import { DATABASE_UI } from '../reducers/ui-constants';
import AccordionToggle from '../containers/AccordionToggle';
import { nextUI } from '../reducers/setup-ui-reducer';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
export default ({
onClick,
}) => {
const defaultDatabase = DATABASE_TYPES.POSTGRES;
const [database, setDatabase] = useState(defaultDatabase)
const hostDefault = "localhost"
const [databaseHost, setHost] = useState(hostDefault);
const [postgresDatabase, setPostgresDatabase] = useState();
const [databaseUsername, setUser] = useState();
const [databasePassword, setPassword] = useState();
const toggle = useAccordionToggle(nextUI(DATABASE_UI));
return <Card>
<AccordionToggle as={Card.Header} eventKey={DATABASE_UI}>
Database
</AccordionToggle>
<Accordion.Collapse eventKey={DATABASE_UI}>
<Card.Body>
<p>Synapse can use either SQLite3 or Postgres as it's database.</p>
<p>Postgres is recommended.</p>
<Tabs defaultActiveKey={defaultDatabase} onSelect={k => setDatabase(k)}>
<Tab eventKey={DATABASE_TYPES.POSTGRES} title={"Postgres"}>
This will connect to the given Postgres database via {DATABASE_TYPES.POSTGRES}
<p>
Host
</p>
<input
type="text"
onChange={e => setHost(e.target.value ? e.target.value : hostDefault)}
autoFocus
placeholder="localhost"
/>
<p>
Database name
</p>
<input
type="text"
onChange={e => setPostgresDatabase(e.target.value)}
autoFocus
placeholder="unspecified"
/>
<p>
User name
</p>
<input
type="text"
onChange={e => setUser(e.target.value)}
autoFocus
placeholder="unspecified"
/>
<p>
Password
</p>
<input
type="text"
onChange={e => setPassword(e.target.value)}
autoFocus
placeholder="unspecified"
/>
<button
className='inputButton'
disabled={databaseHost ? undefined : true}
onClick={() => {
toggle();
onClick({
databaseType: DATABASE_TYPES.POSTGRES,
databaseHost,
database: postgresDatabase,
databaseUsername,
databasePassword,
})
}}
>Use Postgres</button>
</Tab>
<Tab eventKey={DATABASE_TYPES.SQLITE3} title={DATABASE_TYPES.SQLITE3}>
<button
className='inputButton'
onClick={() => {
toggle();
onClick({
databaseType: DATABASE_TYPES.SQLITE3
});
}}
>Use {DATABASE_TYPES.SQLITE3}</button>
</Tab>
</Tabs>
</Card.Body>
</Accordion.Collapse>
</Card >
}

View File

@@ -0,0 +1,165 @@
/* eslint-disable max-len */
import React, { useState } from 'react';
import style from '../../scss/main.scss';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import { DELEGATION_TYPES } from '../actions/constants';
import { DELEGATION_OPTIONS_UI } from '../reducers/ui-constants';
import AccordionToggle from '../containers/AccordionToggle';
import { nextUI } from '../reducers/setup-ui-reducer';
import InlineError from './InlineError';
export default ({ servername, skip, onClick }) => {
const defaultType = DELEGATION_TYPES.DNS;
const [type, setType] = useState(defaultType);
const [delegatedServername, setDelegatedServerName] = useState("");
const [fedPort, setFedPort] = useState("");
const [clientPort, setClientPort] = useState("");
const [clientPortValid, setClientPortValid] = useState(true)
const [fedPortValid, setFedPortValid] = useState(true)
const updateValidity = (port, setValid) => setValid(
!port ||
(!isNaN(port) && 0 < port && port <= 65535),
)
const onFederationChange = event => {
const val = event.target.value;
setFedPort(val);
updateValidity(val, setFedPortValid);
}
const onClientChange = event => {
const val = event.target.value;
setClientPort(val);
updateValidity(val, setClientPortValid);
}
const toggle = useAccordionToggle(nextUI(DELEGATION_OPTIONS_UI));
const portSelection = <div>
<p>Please enter the domain name of the server synapse is installed on.</p>
<input
type="text"
onChange={e => setDelegatedServerName(e.target.value)}
autoFocus
placeholder="Enter server name"
value={delegatedServername}
/>
<p>
Homeserver Port
</p>
<InlineError error={fedPortValid ? undefined : "Invalid port"}>
<input
type="text"
onChange={onFederationChange}
className={fedPortValid ? undefined : "invalid"}
autoFocus
placeholder="Use Default 8448"
value={fedPort}
/>
</InlineError>
<p>
Client Port
</p>
<InlineError error={clientPortValid ? undefined : "Invalid port"}>
<input
type="text"
onChange={onClientChange}
className={clientPortValid ? undefined : "invalid"}
autoFocus
placeholder="Use Default 443"
value={clientPort}
/>
</InlineError>
<button disabled={delegatedServername && clientPortValid && fedPortValid ? undefined : true}
onClick={() => {
toggle();
onClick(type, delegatedServername, fedPort, clientPort)
}}
>
Use {type}
</button>
</div >
return <Card>
<AccordionToggle as={Card.Header} eventKey={DELEGATION_OPTIONS_UI}>
Delegation (optional)
</AccordionToggle>
<Accordion.Collapse eventKey={DELEGATION_OPTIONS_UI}>
<Card.Body>
<p>
If you'd like your synapse to be hosted on a different server to the
one known on the network by '{servername}' you can use delegation.&nbsp;
<a
href="https://github.com/matrix-org/synapse/blob/master/docs/federate.md"
target="_blank"
>
Learn more
</a>
</p>
<p>
If you're not sure if you need delegation, we recommending skipping this step.
</p>
<button onClick={() => {
toggle();
skip();
}}>
Skip
</button>
<hr />
<p>
Other federation servers will connect to {servername}:8448 over the network.
</p>
<p>
There are two forms of delegation:
</p>
<Tabs defaultActiveKey={defaultType} onSelect={k => setType(k)}>
<Tab eventKey={DELEGATION_TYPES.DNS} title={DELEGATION_TYPES.DNS}>
<p>
You will need access to {servername}'s domain zone DNS records.
This method also requires the synapse install's server to provide
a valid TLS cert for {servername}
</p>
<p>
You will need to add an SRV record to {servername}'s DNS zone. (Once
again, we'll print the SRV record out for you later.)
</p>
{portSelection}
</Tab>
<Tab eventKey={DELEGATION_TYPES.WELL_KNOWN} title={DELEGATION_TYPES.WELL_KNOWN}>
<p>
{servername} provides the url
https://{servername}/.well-known/matrix/server which gives
federating servers information about how to contact the actual
server hosting the synapse install. (Don't worry! We'll print out
the .well-known file for you later.)
</p>
{portSelection}
</Tab>
</Tabs>
</Card.Body>
</Accordion.Collapse>
</Card>
}

View File

@@ -0,0 +1,63 @@
/* eslint-disable max-len */
import React from 'react';
import ContentWrapper from '../containers/ContentWrapper';
import ButtonDisplay from './ButtonDisplay';
import DownloadOrCopy from './DownloadOrCopy';
import { DELEGATION_TYPES } from '../actions/constants';
export default ({
delegationType,
serverConfig,
clientConfig,
serverConfigFileName,
clientConfigFileName,
serverName,
onClick,
}) => {
if (delegationType == DELEGATION_TYPES.DNS) {
return <ContentWrapper>
<h1 className='setupCompleteTitle'>Configure Delegation</h1>
<p>
You will need to add the following SRV record to your DNS zone.
</p>
<pre>
<code>
{clientConfig}
</code>
</pre>
<DownloadOrCopy content={clientConfig} fileName={clientConfigFileName} onClick={onClick} />
</ContentWrapper>
} else {
return <ContentWrapper>
<h1 className='setupCompleteTitle'>Configure delegation</h1>
<p>
The delegation configuration needs to take place outside the installer.
</p>
<p>
You'll need to host the following at https://{serverName}/.well-known/matrix/server
</p>
<pre>
<code>
{serverConfig}
</code>
</pre>
<DownloadOrCopy content={serverConfig} fileName={serverConfigFileName} onClick={onClick} />
<p>
You'll also need to host the following at https://{serverName}/.well-known/matrix/client
</p>
<pre>
<code>
{clientConfig}
</code>
</pre>
<DownloadOrCopy content={clientConfig} fileName={clientConfigFileName} onClick={onClick} />
</ContentWrapper>;
}
}

View File

@@ -0,0 +1,27 @@
import React, { useState } from 'react';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import AccordionToggle from '../containers/AccordionToggle';
import { DONE_UI } from '../reducers/ui-constants';
export default ({ configDir }) => {
return <Card>
<AccordionToggle as={Card.Header} eventKey={DONE_UI} >
Done
</AccordionToggle>
<Accordion.Collapse eventKey={DONE_UI}>
<Card.Body>
<p>
Synapse is running!
</p>
<p>
There are many settings to play with in the yaml files in <code>{configDir}</code>.
</p>
</Card.Body>
</Accordion.Collapse>
</Card>;
}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import ButtonDisplay from './ButtonDisplay';
const download = (filename, text) => {
const e = document.createElement('a');
e.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
e.setAttribute('download', filename);
e.style.display = 'none';
document.body.appendChild(e);
e.click();
document.body.removeChild(e);
}
export default ({ content, fileName, onClick = () => undefined }) => {
const downloadOnClick = () => {
download(fileName, content);
onClick();
}
const copyOnClick = () => {
navigator.clipboard.writeText(content);
onClick();
}
return <ButtonDisplay>
<div className='buttonGroup'>
<button onClick={downloadOnClick}>Download</button>
<span className='or'>or</span>
<button onClick={copyOnClick}>Copy</button>
</div>
</ButtonDisplay>
}

View File

@@ -0,0 +1,12 @@
import React from 'react';
import ContentWrapper from '../containers/ContentWrapper';
export default () => {
return <ContentWrapper>
<h1>Damn!</h1>
<p>Has the config server been started?</p>
</ContentWrapper>;
}

View File

@@ -0,0 +1,72 @@
import React, { useState } from 'react';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import ButtonDisplay from './ButtonDisplay';
import DownloadOrCopy from './DownloadOrCopy';
import { KEY_EXPORT_UI } from '../reducers/ui-constants';
import AccordionToggle from '../containers/AccordionToggle';
import { nextUI } from '../reducers/setup-ui-reducer';
export default ({ secretKeyLoaded, secretKey, onClick }) => {
const [downloadedOrCopied, setDownloadedOrCopied] = useState(false);
const toggle = useAccordionToggle(nextUI(KEY_EXPORT_UI));
const decoratedOnClick = () => {
setDownloadedOrCopied(false);
toggle();
onClick();
}
let body;
if (!secretKeyLoaded) {
body = <Card.Body><p>Generating secret key</p></Card.Body>
} else {
body = <Card.Body>
<p>
Your server uses a secret key to identify itself to other servers. Keep
a copy of it to retain ownership of the server name in case the server
is inaccessible:
</p>
<pre><code>{secretKey}</code></pre>
<p>
Keep a copy of this key somewhere safe by downloading or copying
the key to your clipboard to continue.
</p>
<DownloadOrCopy
content={secretKey}
fileName="secret_key.txt"
onClick={() => setDownloadedOrCopied(true)} />
<div className='blockWrapper'>
<ButtonDisplay>
<button
onClick={decoratedOnClick}
disabled={!downloadedOrCopied}
>Next</button>
</ButtonDisplay>
</div>
</Card.Body>
}
return <Card>
<AccordionToggle as={Card.Header} eventKey={KEY_EXPORT_UI}>
Secret Key
</AccordionToggle>
<Accordion.Collapse eventKey={KEY_EXPORT_UI}>
{body}
</Accordion.Collapse>
</Card>
}

View File

@@ -0,0 +1,12 @@
import React from 'react';
export default ({ error, children }) => {
return <div
className="inlineError"
error={error ? "true" : "false"}>
{children}
{error ? <span>{error}</span> : undefined}
</div>
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
import ContentWrapper from '../containers/ContentWrapper';
export default () => {
return <ContentWrapper>
<h1>loading..</h1>
</ContentWrapper>;
}

View File

@@ -0,0 +1,144 @@
/* eslint-disable max-len */
import React, { useState } from 'react';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import { PORT_SELECTION_UI } from '../reducers/ui-constants';
import AccordionToggle from '../containers/AccordionToggle';
import ContentWrapper from '../containers/ContentWrapper';
import { nextUI } from '../reducers/setup-ui-reducer';
import InlineError from './InlineError';
export default ({
servername,
verifyingPorts,
fedPortInUse,
clientPortInUse,
canChangePorts,
defaultFedPort,
defaultClientPort,
onClick,
}) => {
if (verifyingPorts) {
return <ContentWrapper><h1>Verifying ports.</h1></ContentWrapper>
}
const [fedPort, setFedPort] = useState();
const [clientPort, setClientPort] = useState();
const [clientPortValid, setClientPortValid] = useState(true)
const [fedPortValid, setFedPortValid] = useState(true)
const updateValidity = (port, setValid) => setValid(
!isNaN(port) && 0 < port && port <= 65535,
)
const onFederationChange = event => {
const val = event.target.value ? event.target.value : defaultFedPort;
setFedPort(val);
updateValidity(val, setFedPortValid);
}
const onClientChange = event => {
const val = event.target.value ? event.target.value : defaultClientPort;
setClientPort(val);
updateValidity(val, setClientPortValid);
}
const toggle = useAccordionToggle(nextUI(PORT_SELECTION_UI));
const fedPortError = fedPortInUse ?
"This port is in use" :
!fedPortValid ? "Invalid port" :
undefined;
const clientPortError = clientPortInUse ?
"This port is in use" :
!clientPortValid ? "Invalid port" :
undefined;
const fedPortPriv = fedPort ? fedPort < 1024 : defaultFedPort < 1024
const clientPortPriv = clientPort ? clientPort < 1024 : defaultClientPort < 1024
return <Card>
<AccordionToggle as={Card.Header} eventKey={PORT_SELECTION_UI}>
{servername ? servername + "'s ports" : "Ports"}
</AccordionToggle>
<Accordion.Collapse eventKey={PORT_SELECTION_UI}>
<Card.Body>
<p>
Synapse will be listening on the following ports on localhost.
</p>
{
canChangePorts ?
<p>
Since you're using a reverse proxy you can change these to anything you
like as long as synapse can bind to them. We recommend not using privileged
ports within the range 0 to 1024.
</p>
:
<p>
Since you're not using a reverse proxy synapse will have to listen on
these ports. If any of these ports are already in use (we'll test them when
you click the button) you will either need to reconfigure the ports used on
localhost, setup up delegation or use a reverse proxy.
</p>
}
<p>
We will check that the ports are not in use.
</p>
<p>
Note: we can't check whether privileged ports are in use. If you've
set a privileged port <b>we will skip the check for that port</b>.
</p>
<h6>Federation Port</h6>
<InlineError error={fedPortError}>
<input
type="text"
onChange={onFederationChange}
disabled={canChangePorts ? undefined : true}
autoFocus
placeholder={defaultFedPort}
/>
</InlineError>
{fedPortPriv ? <p>This is a privileged port.</p> : undefined}
<h6>Client Port</h6>
<InlineError error={clientPortError}>
<input
type="text"
onChange={onClientChange}
disabled={canChangePorts ? undefined : true}
autoFocus
placeholder={defaultClientPort}
/>
</InlineError>
{clientPortPriv ? <p>This is a privileged port.</p> : undefined}
<div>
<button
disabled={clientPortValid && fedPortValid ? undefined : true}
onClick={() => onClick(
fedPort ? parseInt(fedPort) : defaultFedPort,
clientPort ? parseInt(clientPort) : defaultClientPort,
toggle,
)}
>Verify These Ports</button>
</div>
</Card.Body>
</Accordion.Collapse>
</Card>
}

View File

@@ -0,0 +1,36 @@
import React from 'react';
import ContentWrapper from '../containers/ContentWrapper';
import ButtonDisplay from './ButtonDisplay';
import DownloadOrCopy from './DownloadOrCopy';
import { REVERSE_PROXY_TYPES } from '../actions/constants';
export default ({ proxyType, sampleConfig, fileName, onClick }) => {
return <ContentWrapper>
<h1 className='setupCompleteTitle'>Configure the ReverseProxy</h1>
<p>
It's time for you to setup the reverse proxy outside of this installer.
</p>
{
proxyType == REVERSE_PROXY_TYPES.OTHER ?
<p>
Here's a sample config for Apache. Since you chose 'other'
for your reverse proxy. You'll have to figure it out
for yourself. We believe in you.
</p>
:
<p>
We can't do it for you
but here's the sample configuration for your {proxyType} proxy.
</p>
}
<pre>
<code>
{sampleConfig}
</code>
</pre>
<DownloadOrCopy content={sampleConfig} fileName={fileName} onClick={onClick} />
</ContentWrapper>;
}

View File

@@ -0,0 +1,67 @@
import React, { useState } from 'react';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import { SERVER_NAME_UI } from '../reducers/ui-constants';
import AccordionToggle from '../containers/AccordionToggle';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import { nextUI } from '../reducers/setup-ui-reducer';
import InlineError from './InlineError';
export default ({ onClick }) => {
const [servername, setServerName] = useState("");
const [serverNameValid, setServerNameValid] = useState(true);
const validator = /^[0-9a-zA-Z.-]+$/;
const onChange = event => {
setServerName(event.target.value);
setServerNameValid(validator.test(event.target.value));
};
const toggle = useAccordionToggle(nextUI(SERVER_NAME_UI));
const decoratedOnClick = () => {
onClick(servername);
toggle();
}
return <Card>
<AccordionToggle as={Card.Header} eventKey={SERVER_NAME_UI} >
Name your server
</AccordionToggle>
<Accordion.Collapse eventKey={SERVER_NAME_UI}>
<Card.Body>
<p>
Your server name usually matches your domain. For example, the
matrix.org server is simply called `matrix.org`.
</p>
<p>
Your server name will be used to establish User IDs (e.g.
`@user:server.name`) and Room Aliases (e.g. `#room:server.name`).
</p>
<InlineError error={!serverNameValid ? "The servername may only be alphanumeric characters" : undefined}>
<input
type="text"
onChange={onChange}
autoFocus
placeholder="Enter server name"
/>
</InlineError>
<div>
<button
disabled={servername && serverNameValid ? undefined : true}
onClick={decoratedOnClick}
>
Next
</button>
</div>
</Card.Body>
</Accordion.Collapse>
</Card>;
}

View File

@@ -0,0 +1,48 @@
import React, { useState } from 'react';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import { STATS_REPORT_UI } from '../reducers/ui-constants';
import AccordionToggle from '../containers/AccordionToggle';
import { nextUI } from '../reducers/setup-ui-reducer';
export default ({ onClick }) => {
const [consent, setConsent] = useState(false);
const toggle = useAccordionToggle(nextUI(STATS_REPORT_UI));
const decoratedOnClick = () => {
toggle();
onClick(consent);
}
return <Card>
<AccordionToggle as={Card.Header} eventKey={STATS_REPORT_UI}>
Anonymous Statistics
</AccordionToggle>
<Accordion.Collapse eventKey={STATS_REPORT_UI}>
<Card.Body>
<p>
Would you like to report anonymous statistics to matrix.org?
Your server will send anonymised, aggregated statistics to matrix.org
on user usage so we can measure the health of the Matrix ecosystem.
</p>
<label>
<input
type="checkbox"
onChange={event => setConsent(event.target.checked)}
/>
Yes, send anonymous statistics
</label>
<div className='blockWrapper'>
<button onClick={decoratedOnClick}>Next</button>
</div>
</Card.Body>
</Accordion.Collapse>
</Card>
}

View File

@@ -0,0 +1,164 @@
/* eslint-disable max-len */
import React, { useState } from 'react';
import style from '../../scss/main.scss';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import useAccordionToggle from 'react-bootstrap/useAccordionToggle';
import { TLS_UI } from '../reducers/ui-constants';
import { TLS_TYPES, REVERSE_PROXY_TYPES } from '../actions/constants';
import AccordionToggle from '../containers/AccordionToggle';
import { nextUI } from '../reducers/setup-ui-reducer';
import InlineError from './InlineError';
const tlsLink = "https://en.wikipedia.org/wiki/Transport_Layer_Security";
const apacheLink = "http://httpd.apache.org/";
const caddyLink = "https://caddyserver.com/";
const haproxyLink = "http://www.haproxy.org/";
const nginxLink = "https://www.nginx.com/";
export default ({
testingCertPaths,
certPathInvalid,
certKeyPathInvalid,
onClickCertPath,
onClickACME,
onClickReverseProxy,
}) => {
const defaultType = TLS_TYPES.REVERSE_PROXY;
const [certPath, setCertPath] = useState("");
const [certKeyPath, setCertKeyPath] = useState("");
const defaultValue = REVERSE_PROXY_TYPES.NGINX;
const [reverseProxy, setReverseProxy] = useState(defaultValue);
const toggle = useAccordionToggle(nextUI(TLS_UI));
return <Card>
<AccordionToggle as={Card.Header} eventKey={TLS_UI}>
TLS
</AccordionToggle>
<Accordion.Collapse eventKey={TLS_UI}>
<Card.Body>
<p>
Synapse uses TLS to ensure communication between homeservers is
secure. To use TLS, youll need a TLS certificate. Synapse supports
ACME, providing your own certificates, or reverse proxy handling TLS
certificates.
</p>
<Tabs defaultActiveKey={defaultType}>
<Tab eventKey={TLS_TYPES.REVERSE_PROXY} title="Reverse Proxy">
<p>
It is recommended to run Synapse behind a reverse proxy such
as <a target="_blank" href={apacheLink}>Apache</a>, <a target="_blank" href={caddyLink}>Caddy</a>, <a target="_blank" href={haproxyLink}>HAProxy</a>, or <a target="_blank" href={nginxLink}>NGiNX</a>.
</p>
<p>
The main benefit to this is that the reverse proxy can listen on
the privileged port 443 (which clients like Riot expect to connect
to) on behalf of synapse. The incoming traffic is then forwarded
to Synapse on a non privileged port.
</p>
<p>
You need root to listen on ports 0 to 1024 inclusive and running
synapse with root privileges is <b>strongly discouraged</b>.
Reverse proxies are more secure, run with root and pass things on
like nobody's business.
</p>
<p>
(Note: you can also have synapse use a non privileged port by
using one of the delegation methods mentioned earlier.)
</p>
<p>
If you choose to use a Reverse Proxy we'll provide you with
configuration templates later.
</p>
<p>More information about Reverse Proxies{' '}
<a href="https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst">
in the docs</a>.
</p>
<p>
Please choose the reverse proxy you're using. This is just so we can provide
you with a template later, if you already know how you're going to set yours
up don't worry too much about this.
</p>
<select defaultValue={defaultValue} onChange={e => setReverseProxy(e.target.value)} >
<option value={REVERSE_PROXY_TYPES.APACHE}>Apache</option>
<option value={REVERSE_PROXY_TYPES.CADDY}>Caddy</option>
<option value={REVERSE_PROXY_TYPES.HAPROXY}>HAProxy</option>
<option value={REVERSE_PROXY_TYPES.NGINX}>NGiNX</option>
<option value={REVERSE_PROXY_TYPES.OTHER}>Some other Reverse Proxy</option>
</select>
<div>
<button onClick={() => {
toggle();
onClickReverseProxy(reverseProxy)
}}>
Use a reverse proxy with TLS
</button>
</div>
</Tab>
<Tab eventKey={TLS_TYPES.ACME} title="ACME">
<p>
ACME is a protocol that allows TLS certificates to be requested
automagically. Synapse supports ACME by requesting certs from
Let's Encrypt, which is one of the easiest ways to manage your
certificates.
</p>
<p>
If you wish to use ACME you will need access to port 80 which
usually requires root privileges. Do not run Synapse as root. Use
a Reverse Proxy or Authbind
</p>
<button onClick={() => {
toggle();
onClickACME()
}}>Use ACME</button>
</Tab>
<Tab eventKey={TLS_TYPES.TLS} title="Provide your own TLS certs">
<p>
Specify a path to or upload TLS certs for the domain.
</p>
<InlineError error={certPathInvalid ? "The file doesn't exist or can't be accessed." : undefined}>
<input
className={certPathInvalid ? style.invalidInput : undefined}
type="text"
placeholder="/path/to/your/cert.pem"
value={certPath ? certPath : undefined}
onChange={e => setCertPath(e.target.value)}
/>
</InlineError>
<p>Please enter path to the cert's key</p>
<InlineError error={certKeyPathInvalid ? "The file doesn't exist or can't be accessed." : undefined}>
<input
className={certKeyPathInvalid ? style.invalidInput : undefined}
type="text"
placeholder="/path/to/your/cert/key.tls.key"
value={certKeyPath ? certKeyPath : undefined}
onChange={e => setCertKeyPath(e.target.value)}
/>
</InlineError>
<button
className="inputButton"
disabled={certPath && certKeyPath ? undefined : true}
onClick={() => onClickCertPath(certPath, certKeyPath, toggle)}
>Use TLS Path</button>
</Tab>
</Tabs>
</Card.Body>
</Accordion.Collapse>
</Card>
}

View File

@@ -0,0 +1,108 @@
import React from 'react';
import style from '../../scss/main.scss';
import Accordion from 'react-bootstrap/Accordion';
import Card from 'react-bootstrap/Card';
import {
SERVER_NAME_UI,
STATS_REPORT_UI,
KEY_EXPORT_UI,
DELEGATION_OPTIONS_UI,
TLS_UI,
PORT_SELECTION_UI,
REVERSE_PROXY_TEMPLATE_UI,
LOADING_UI,
ERROR_UI,
DELEGATION_TEMPLATE_UI,
DATABASE_UI,
COMPLETE_UI,
SETUP_ORDER,
DONE_UI,
} from '../reducers/ui-constants';
import Error from './Error';
import Loading from './Loading';
import BaseIntro from '../containers/BaseIntro';
import ServerName from '../containers/ServerName';
import StatsReporter from '../containers/StatsReporter';
import ExportKeys from '../containers/ExportKeys';
import DelegationOptions from '../containers/DelegationOptions';
import TLS from '../containers/TLS';
import PortSelection from '../containers/PortSelection';
import ReverseProxySampleConfig from '../containers/ReverseProxySampleConfig';
import DelegationSampleConfig from '../containers/DelegationSampleConfig';
import Database from '../containers/Database';
import ConfigCompleted from './ConfigCompleted';
import CompleteSetup from '../containers/CompleteSetup';
import ContentWrapper from '../containers/ContentWrapper';
import Done from '../containers/Done';
const blockMapping = uiBlock => {
switch (uiBlock) {
case LOADING_UI:
return <Loading key={uiBlock} />
case ERROR_UI:
return <Error key={uiBlock} />
case SERVER_NAME_UI:
return <ServerName key={uiBlock} />
case STATS_REPORT_UI:
return <StatsReporter key={uiBlock} />
case KEY_EXPORT_UI:
return <ExportKeys key={uiBlock} />
case DELEGATION_OPTIONS_UI:
return <DelegationOptions key={uiBlock} />
case TLS_UI:
return <TLS key={uiBlock} />
case PORT_SELECTION_UI:
return <PortSelection key={uiBlock} />
case REVERSE_PROXY_TEMPLATE_UI:
return <ReverseProxySampleConfig key={uiBlock} />
case DELEGATION_TEMPLATE_UI:
return <DelegationSampleConfig key={uiBlock} />
case DATABASE_UI:
return <Database key={uiBlock} />
case COMPLETE_UI:
return <CompleteSetup key={uiBlock} />
case DONE_UI:
return <Done key={uiBlock} />
default:
return <h1>how did i get here?</h1>
}
}
export default ({ setupUI, configUI, baseConfig }) => {
if (!baseConfig.baseConfigChecked) {
return <Loading />
}
if (baseConfig.setupDone) {
console.log(`switching to ui ${configUI}`);
return <ConfigCompleted></ConfigCompleted>
}
if (!baseConfig.setupDone) {
return <ContentWrapper>
<Accordion>
<BaseIntro />
<div style={setupUI.activeBlocks.length ? undefined : { display: "none" }}>
{SETUP_ORDER.map(blockMapping)}
</div>
</Accordion>
</ContentWrapper>
}
}

View File

@@ -0,0 +1,22 @@
import { connect } from 'react-redux';
import AccordionToggle from '../components/AccordionToggle';
import { resetUI } from '../actions';
import { DONE_UI } from '../reducers/ui-constants';
const mapStateToProps = (state, { eventKey, as, children }) => ({
active: state.setupUI.activeBlocks.includes(eventKey) && state.setupUI.activeBlocks[state.setupUI.activeBlocks.length - 1] != DONE_UI,
open: state.setupUI.activeBlocks[state.setupUI.activeBlocks.length - 1] == eventKey,
eventKey,
as,
children,
});
const mapDispathToProps = (dispatch, { eventKey }) => ({
reset: () => dispatch(resetUI(eventKey)),
});
export default connect(
mapStateToProps,
mapDispathToProps,
)(AccordionToggle);

View File

@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import BaseIntro from '../components/BaseIntro';
import { advanceUI } from '../actions';
const mapStateToProps = (state, ownProps) => ({
started: Boolean(state.setupUI.activeBlocks.length),
servername: state.baseConfig.servername,
});
const mapDispathToProps = (dispatch) => ({
onClick: () => dispatch(advanceUI()),
});
export default connect(
mapStateToProps,
mapDispathToProps,
)(BaseIntro);

View File

@@ -0,0 +1,21 @@
import { connect } from 'react-redux';
import CompleteSetup from '../components/CompleteSetup';
import { writeConfig } from '../actions';
const mapStateToProps = (state) => ({
tlsType: state.baseConfig.tls,
synapseStartFailed: state.baseConfig.synapseStartFailed,
delegationType: state.baseConfig.delegationType,
configDir: state.baseConfig.configDir,
});
const mapDispatchToProps = (dispatch) => ({
onClick: (callback) => dispatch(writeConfig(callback)),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(CompleteSetup);

View File

@@ -0,0 +1,15 @@
import { connect } from 'react-redux';
import ContentWrapper from '../components/ContentWrapper';
const mapStateToProps = (state, { children }) => ({
children,
});
const mapDispatchToProps = (dispatch) => ({
});
export default connect(
mapStateToProps,
)(ContentWrapper);

View File

@@ -0,0 +1,22 @@
import { connect } from 'react-redux';
import Database from '../components/Database';
import { setDatabase, advanceUI } from '../actions';
const mapStateToProps = (state) => {
};
const mapDispatchToProps = (dispatch) => ({
onClick: databaseConfig => {
dispatch(setDatabase(databaseConfig));
dispatch(advanceUI());
},
});
export default connect(
null,
mapDispatchToProps,
)(Database);

View File

@@ -0,0 +1,44 @@
import { connect } from 'react-redux';
import DelegationOptions from '../components/DelegationOptions';
import {
setDelegation,
advanceUI,
setDelegationServername,
setDelegationPorts,
} from '../actions';
import { DELEGATION_TYPES } from '../actions/constants';
const mapStateToProps = (state, { children }) => {
return {
servername: state.baseConfig.servername,
}
}
const mapDispatchToProps = (dispatch) => ({
onClick: (type, servername, fedPort, clientPort) => {
dispatch(advanceUI());
dispatch(setDelegation(type));
dispatch(setDelegationServername(servername));
dispatch(setDelegationPorts(fedPort, clientPort));
},
skip: () => {
dispatch(advanceUI());
dispatch(setDelegation(DELEGATION_TYPES.LOCAL));
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(DelegationOptions);

View File

@@ -0,0 +1,74 @@
import { connect } from 'react-redux';
import DelegationSampleConfig from '../components/DelegationSampleConfig';
import { advanceUI } from '../actions';
import DNSConfig from '../templates/dns-srv';
import FedWellKnownConfig from '../templates/federation-well-known'
import ClientWellKnownConfig from '../templates/client-well-known'
import { DELEGATION_TYPES } from '../actions/constants';
// synapseServerName: state.baseConfig.delegationServerName ? state.baseConfig.delegationServerName : state.baseConfig.servername,
const serverConfig = state => {
if (state.delegationType == DELEGATION_TYPES.DNS) {
return undefined;
} else {
return FedWellKnownConfig({
synapseServerName: state.delegationServerName,
delegationSynapsePort: state.delegationFederationPort ?
state.delegationFederationPort :
8448,
});
}
}
const clientConfig = state => {
if (state.delegationType == DELEGATION_TYPES.WELL_KNOWN) {
return ClientWellKnownConfig({
synapseServerName: state.delegationServerName,
delegationClientPort: state.delegationClientPort ?
state.delegationClientPort :
443,
});
} else {
return DNSConfig({
serverName: state.servername,
synapseServerName: state.delegationServerName,
delegationClientPort: state.delegationClientPort ?
state.delegationClientPort :
443,
})
}
}
const mapStateToProps = state => ({
delegationType: state.baseConfig.delegationType,
serverConfig: serverConfig(state.baseConfig),
clientConfig: clientConfig(state.baseConfig),
serverConfigFileName: `${state.baseConfig.servername}_delegation.conf`,
clientConfigFileName: `${state.baseConfig.servername}_client_delegation.conf`,
serverName: state.baseConfig.servername,
});
const mapDispatchToProps = (dispatch, { onClick }) => ({
onClick,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(DelegationSampleConfig);

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import Done from '../components/Done';
const mapStateToProps = (state, ownProps) => ({
configDir: state.baseConfig.configDir,
});
const mapDispathToProps = (dispatch) => ({
});
export default connect(
mapStateToProps,
mapDispathToProps,
)(Done);

View File

@@ -0,0 +1,25 @@
import { connect } from 'react-redux';
import ExportKeys from '../components/ExportKeys';
import { advanceUI } from '../actions';
const mapStateToProps = state => {
const secretKeyLoaded = state.baseConfig.secretKeyLoaded;
const secretKey = state.baseConfig.secretKey;
return {
secretKeyLoaded,
secretKey,
}
};
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(advanceUI()),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ExportKeys);

View File

@@ -0,0 +1,59 @@
import { connect } from 'react-redux';
import PortSelection from '../components/PortSelection';
import { setSynapsePorts } from '../actions';
import { TLS_TYPES } from '../actions/constants';
const defaultFedPort = state => {
if (state.tls == TLS_TYPES.REVERSE_PROXY) {
return 8008;
}
return state.delegationFederationPort ? state.delegationFederationPort : 8448;
}
const defaultClientPort = state => {
if (state.tls == TLS_TYPES.REVERSE_PROXY) {
return 8008;
}
return state.delegationFederationPort ?
state.delegationFederationPort :
443;
}
const mapStateToProps = ({ baseConfig }) => ({
servername: baseConfig.servername,
verifyingPorts: baseConfig.verifyingPorts,
fedPortInUse: baseConfig.synapseFederationPortFree != undefined ?
!baseConfig.synapseFederationPortFree :
false,
clientPortInUse: baseConfig.synapseClientPortFree != undefined ?
!baseConfig.synapseClientPortFree :
false,
canChangePorts: baseConfig.tls == TLS_TYPES.REVERSE_PROXY,
defaultFedPort: defaultFedPort(baseConfig),
defaultClientPort: defaultClientPort(baseConfig),
});
const mapDispathToProps = (dispatch) => ({
onClick: (fedPort, clientPort, callback) => {
dispatch(setSynapsePorts(fedPort, clientPort, callback));
},
});
export default connect(
mapStateToProps,
mapDispathToProps,
)(PortSelection);

View File

@@ -0,0 +1,58 @@
import { connect } from 'react-redux';
import ReverseProxySampleConfig from '../components/ReverseProxySampleConfig';
import { REVERSE_PROXY_TYPES } from '../actions/constants';
import apacheConfig from '../templates/apache';
import caddyConfig from '../templates/caddy';
import haproxyConfig from '../templates/haproxy';
import nginxConfig from '../templates/nginx';
const sampleConfig = reverseProxyType => {
switch (reverseProxyType) {
case REVERSE_PROXY_TYPES.APACHE:
return apacheConfig;
case REVERSE_PROXY_TYPES.CADDY:
return caddyConfig;
case REVERSE_PROXY_TYPES.HAPROXY:
return haproxyConfig;
case REVERSE_PROXY_TYPES.NGINX:
return nginxConfig;
case REVERSE_PROXY_TYPES.OTHER:
return otherConfig;
default:
return () => { }
}
}
const mapStateToProps = state => ({
proxyType: state.baseConfig.reverseProxy,
sampleConfig: sampleConfig(state.baseConfig.reverseProxy)({
delegationFedPort: state.baseConfig.delegationFederationPort ?
state.baseConfig.delegationFederationPort :
8448,
delegationClientPort: state.baseConfig.delegationClientPort ?
state.baseConfig.delegationClientPort :
443,
fedPort: state.baseConfig.synapseFederationPort,
clientPort: state.baseConfig.synapseClientPort,
synapseServerName: state.baseConfig.delegationServerName ?
state.baseConfig.delegationServerName :
state.baseConfig.servername,
}),
fileName: "synapse_reverse_proxy.conf",
});
const mapDispatchToProps = (dispatch, { onClick }) => ({
onClick,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ReverseProxySampleConfig);

View File

@@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import ServerName from '../components/ServerName';
import { advanceUI, setServername, generateSecretKeys } from '../actions';
const mapStateToProps = (state, ownProps) => ({
});
const mapDispathToProps = (dispatch) => ({
onClick: servername => {
dispatch(advanceUI());
dispatch(setServername(servername));
dispatch(generateSecretKeys(servername));
},
});
export default connect(
null,
mapDispathToProps,
)(ServerName);

View File

@@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import StatsReporter from '../components/StatsReporter';
import { advanceUI, setStats } from '../actions';
const mapStateToProps = (state, ownProps) => ({
});
const mapDispathToProps = (dispatch) => ({
onClick: consent => {
dispatch(advanceUI());
dispatch(setStats(consent));
},
});
export default connect(
null,
mapDispathToProps,
)(StatsReporter);

View File

@@ -0,0 +1,52 @@
import { connect } from 'react-redux';
import TLS from '../components/TLS';
import {
advanceUI,
setTls,
setTlsCertPaths,
setTlsCertFiles,
setReverseProxy,
} from '../actions';
import { TLS_TYPES } from '../actions/constants';
const mapStateToProps = (state, ownProps) => ({
testingCertPaths: state.baseConfig.testingCertPaths,
uploadingCertPaths: state.baseConfig.uploadingCerts,
certPathInvalid: state.baseConfig.certPathInvalid,
certKeyPathInvalid: state.baseConfig.certKeyPathInvalid,
});
const mapDispathToProps = (dispatch) => ({
onClickACME: () => {
dispatch(advanceUI(TLS_TYPES.ACME));
dispatch(setTls(TLS_TYPES.ACME));
},
onClickReverseProxy: proxyType => {
dispatch(advanceUI());
dispatch(setTls(TLS_TYPES.REVERSE_PROXY))
dispatch(setReverseProxy(proxyType))
},
onClickCertPath: (certPath, certKeyPath, callback) => {
dispatch(setTlsCertPaths(certPath, certKeyPath, callback));
},
onClickCertUpload: (tlsCertFile, tlsKeyFile, callback) => {
dispatch(setTlsCertFiles(tlsCertFile, tlsKeyFile));
callback();
},
});
export default connect(
mapStateToProps,
mapDispathToProps,
)(TLS)

View File

@@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import UI from '../components/UI';
const mapStateToProps = ({ setupDone, setupUI, configUI, baseConfig }) => ({
setupDone,
setupUI,
configUI,
baseConfig,
})
const mapDispathToProps = (dispatch, ownProps) => ({
})
export default connect(
mapStateToProps,
)(UI)

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import UI from './containers/UI';
import { startup } from './actions';
const store = createStore(
rootReducer,
applyMiddleware(thunk),
//+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
store.dispatch(startup());
render(
<Provider store={store}>
{/* <img className={style.logo} src={logo} /> */}
<UI />
</Provider>,
document.body,
);

View File

@@ -0,0 +1,147 @@
import {
SET_SERVERNAME,
SET_STATS,
SET_SECRET_KEY,
GETTING_SECRET_KEY,
SET_DELEGATION,
SET_DELEGATION_PORTS,
SET_DELEGATION_SERVERNAME,
SET_REVERSE_PROXY,
SET_TLS,
TESTING_TLS_CERT_PATHS,
SET_TLS_CERT_PATHS,
SET_TLS_CERT_PATHS_VALIDITY,
SET_TLS_CERT_FILES,
UPLOADING_TLS_CERT_PATHS,
TESTING_SYNAPSE_PORTS,
SET_SYNAPSE_PORTS,
SET_SYNAPSE_PORTS_FREE,
SET_DATABASE,
SET_CONFIG_DIR,
BASE_CONFIG_CHECKED,
SYNAPSE_START_FAILED,
} from "../actions/types";
export default (state, action) => {
switch (action.type) {
case BASE_CONFIG_CHECKED:
return {
...state,
baseConfigChecked: true,
setupDone: action.setupDone,
}
case SET_SERVERNAME:
return {
...state,
servername: action.servername,
}
case SET_STATS:
return {
...state,
reportStats: action.consent,
}
case GETTING_SECRET_KEY:
return {
...state,
secretKeyLoaded: false,
}
case SET_SECRET_KEY:
return {
...state,
secretKeyLoaded: true,
secretKey: action.key,
};
case SET_DELEGATION:
return {
...state,
delegationType: action.delegationType,
}
case SET_DELEGATION_PORTS:
return {
...state,
delegationFederationPort: action.federationPort,
delegationClientPort: action.clientPort,
}
case SET_DELEGATION_SERVERNAME:
return {
...state,
delegationServername: action.servername,
}
case SET_REVERSE_PROXY:
return {
...state,
reverseProxy: action.proxyType,
}
case SET_TLS:
return {
...state,
tls: action.tlsType,
}
case TESTING_TLS_CERT_PATHS:
return {
...state,
testingCertPaths: action.testing,
}
case SET_TLS_CERT_PATHS_VALIDITY:
return {
...state,
certPathInvalid: action.certPathInvalid,
certKeyPathInvalid: action.certKeyPathInvalid,
}
case SET_TLS_CERT_PATHS:
return {
...state,
tlsCertPath: action.certPath,
tlsCertKeyPath: action.certKeyPath,
}
case SET_TLS_CERT_FILES:
return {
...state,
tlsCertFile: action.tlsCertFile,
tlsCertKeyFile: action.tlsCerKeyFile,
}
case UPLOADING_TLS_CERT_PATHS:
return {
...state,
uploadingCerts: action.uploading,
}
case TESTING_SYNAPSE_PORTS:
return {
...state,
verifyingports: action.verifying,
}
case SET_SYNAPSE_PORTS:
return {
...state,
synapseFederationPort: action.federationPort,
synapseClientPort: action.clientPort,
}
case SET_SYNAPSE_PORTS_FREE:
return {
...state,
synapseFederationPortFree: action.synapseFederationPortFree,
synapseClientPortFree: action.synapseClientPortFree,
}
case SET_DATABASE:
return {
...state,
...action.databaseConfig,
}
case SET_CONFIG_DIR:
return {
...state,
configDir: action.configDir,
}
case SYNAPSE_START_FAILED:
return {
...state,
synapseStartFailed: true,
}
default:
return state;
}
};

View File

@@ -0,0 +1,21 @@
const ADVANCED_CONFIG_UI_COMPONENTS = {
CONFIG_SELECTION_UI: "config_selection_ui",
};
export default ({ configUI, baseConfig }, action) => {
if (!baseConfig.baseConfigChecked) {
return configUI;
};
if (!baseConfig.setupDone) {
return configUI;
};
return configUI;
}

View File

@@ -0,0 +1,75 @@
import baseConfigReducer from './base-config-reducer';
import configUIReducer from './config-ui-reducer';
import setupUIReducer from './setup-ui-reducer';
import { uiStateMapping } from './state';
import { BACK_UI } from '../actions/types';
export default (state = {
setupUI: {
activeBlocks: [],
},
configUI: {
},
baseConfig: {
baseConfigChecked: false,
},
}, action) => {
const setupUI = setupUIReducer(state, action);
const rState = {
configUI: configUIReducer(state, action),
setupUI,
baseConfig: filterBaseConfig(
baseConfigReducer(state.baseConfig, action),
action,
setupUI.activeBlocks.slice(0, setupUI.activeBlocks.length - 1),
),
}
console.log(action);
console.log(rState);
return rState;
};
const filterBaseConfig = (baseConfig, action, activeBlocks) => {
if (action.type == BACK_UI) {
return filterObj(
baseConfig,
Object.values(
filterObj(
uiStateMapping,
[...activeBlocks, "base"]),
).flat(),
);
} else {
return baseConfig;
}
}
const filterObj = (object, filterList) => {
return Object.keys(object)
.filter(key => filterList.includes(key))
.reduce((obj, key) => {
obj[key] = object[key];
return obj;
},
{},
);
}

View File

@@ -0,0 +1,57 @@
import { ADVANCE_UI, BACK_UI, BASE_CONFIG_CHECKED } from '../actions/types';
import {
SETUP_ORDER,
} from './ui-constants';
const newActiveBlocks = activeBlocks => {
return SETUP_ORDER.slice(0, activeBlocks.length + 1)
}
export default ({ setupUI, baseConfig }, action) => {
if (!baseConfig.baseConfigChecked) {
return setupUI;
}
if (baseConfig.setupDone) {
return setupUI;
}
switch (action.type) {
case ADVANCE_UI:
return {
activeBlocks: newActiveBlocks(setupUI.activeBlocks),
};
case BACK_UI:
return {
activeBlocks: resetUI(setupUI.activeBlocks, action.ui),
};
default:
return setupUI;
}
}
export const nextUI = current => SETUP_ORDER[SETUP_ORDER.lastIndexOf(current) + 1]
export const resetUI = (activeBlocks, destinationBlock) => {
const indexOfDest = SETUP_ORDER.indexOf(destinationBlock);
if (indexOfDest >= activeBlocks.length) {
// The index is in the future
return activeBlocks;
}
return activeBlocks.slice(0, indexOfDest + 1);
}

View File

@@ -0,0 +1,129 @@
import {
SERVER_NAME_UI,
STATS_REPORT_UI,
KEY_EXPORT_UI,
DELEGATION_OPTIONS_UI,
PORT_SELECTION_UI,
DATABASE_UI,
COMPLETE_UI,
TLS_UI,
} from './ui-constants';
const setupUI = "setupUI";
const activeBlocks = "activeBlocks";
const configUI = "configUI";
const baseConfig = "baseConfig";
const setupDone = "setupDone";
const baseConfigChecked = "baseConfigChecked";
const servername = "servername";
const reportStats = "reportStats";
const secretKeyLoaded = "secretKeyLoaded";
const secretKey = "secretKey";
const delegationType = "delegationType";
const delegationServerName = "delegationServerName";
const delegationFederationPort = "delegationFederationPort";
const delegationClientPort = "delegationClientPort";
const reverseProxy = "reverseProxy";
const tls = "tls";
const testingCertPaths = "testingCertPaths";
const uploadingCerts = "uploadingCerts";
const certPathInvalid = "certPathInvalid";
const certKeyPathInvalid = "certKeyPathInvalid";
const tlsCertPath = "tlsCertPath";
const tlsCertKeyPath = "tlsCertKeyPath";
const tlsCertFile = "tlsCertFile";
const tlsCertKeyFile = "tlsCertKeyFile";
const tlsPath = "tlsPath";
const verifyingPorts = "verifyingPorts";
const synapseFederationPortFree = "synapseFederationPortFree";
const synapseClientPortFree = "synapseClientPortFree";
const synapseFederationPort = "synapseFederationPort";
const synapseClientPort = "synapseClientPort";
const databaseConf = "databaseConf";
const configDir = "configDir";
const state = {
[setupUI]: {
[activeBlocks]: ["block1"],
},
[configUI]: {
},
[baseConfig]: {
[setupDone]: true,
[baseConfigChecked]: false,
[configDir]: "sadfasdf",
[servername]: "server_name",
[reportStats]: false,
[secretKeyLoaded]: false,
[secretKey]: "asdfsadf",
[delegationType]: "local|well_known|DNS_SRV",
[delegationServerName]: "name",
[delegationFederationPort]: "\"\"|325",
[delegationClientPort]: "\"\"|325",
[reverseProxy]: "nginx|caddy|apache|haproxy|other|none",
[tls]: "acme|tls|reverseproxy",
[testingCertPaths]: true,
[uploadingCerts]: true,
[certPathInvalid]: true,
[certKeyPathInvalid]: true,
[tlsCertPath]: "sadfaf",
[tlsCertKeyPath]: "sdfasdf",
[tlsCertFile]: "sadfa;dlf;sad;fkla;sdlfjkas;dlfkjas;dflkja;sdfkljadf ------",
[tlsCertKeyFile]: "sadfa;dlf;sad;fkla;sdlfjkas;dlfkjas;dflkja;sdfkljadf ------",
[verifyingPorts]: true,
[synapseFederationPortFree]: true,
[synapseClientPortFree]: true,
[synapseFederationPort]: 1234,
[synapseClientPort]: 1234,
[databaseConf]: "{dadtabaseType, databaseUsername, databasePassword, database}",
},
}
export const uiStateMapping = {
base: [
setupDone,
baseConfigChecked,
configDir,
],
[SERVER_NAME_UI]: [
servername,
secretKeyLoaded,
secretKey,
],
[STATS_REPORT_UI]: [
reportStats,
],
[KEY_EXPORT_UI]: [
],
[DELEGATION_OPTIONS_UI]: [
delegationType,
delegationServerName,
delegationClientPort,
delegationFederationPort,
],
[TLS_UI]: [
tls,
reverseProxy,
testingCertPaths,
uploadingCerts,
certPathInvalid,
certKeyPathInvalid,
tlsCertPath,
tlsCertKeyPath,
tlsCertFile,
tlsCertKeyFile,
],
[PORT_SELECTION_UI]: [
verifyingPorts,
synapseClientPort,
synapseFederationPort,
synapseClientPortFree,
synapseFederationPortFree,
],
[DATABASE_UI]: [
databaseConf,
],
[COMPLETE_UI]: [
],
}

View File

@@ -0,0 +1,35 @@
// Setup
export const SERVER_NAME_UI = "server_name_ui";
export const STATS_REPORT_UI = "stats_report_ui";
export const KEY_EXPORT_UI = "key_export_ui";
export const DELEGATION_OPTIONS_UI = "delegation_options_ui";
export const TLS_UI = "tls_ui";
export const PORT_SELECTION_UI = "port_selection_ui";
export const REVERSE_PROXY_TEMPLATE_UI = "reverse_proxy_tamplate_ui";
export const DELEGATION_TEMPLATE_UI = "delegation_tamplate_ui";
export const DATABASE_UI = "database_ui";
export const COMPLETE_UI = "complete_ui";
export const DONE_UI = "done_ui";
// Setup order
export const SETUP_ORDER = [
SERVER_NAME_UI,
STATS_REPORT_UI,
KEY_EXPORT_UI,
DELEGATION_OPTIONS_UI,
TLS_UI,
PORT_SELECTION_UI,
DATABASE_UI,
COMPLETE_UI,
DONE_UI,
];
// Config
export const CONFIG_SELECTION_UI = "config_selection_ui";
// Loading screen:
export const LOADING_UI = "loading_ui";
// Error screen:
export const ERROR_UI = "error_ui";

View File

@@ -0,0 +1,25 @@
export default ({
delegationFedPort,
delegationClientPort,
fedPort,
clientPort,
synapseServerName,
}) => `
<VirtualHost *:${delegationClientPort}>
SSLEngine on
ServerName ${synapseServerName};
AllowEncodedSlashes NoDecode
ProxyPass /_matrix http://127.0.0.1:${clientPort}/_matrix nocanon
ProxyPassReverse /_matrix http://127.0.0.1:${clientPort}/_matrix
</VirtualHost>
<VirtualHost *:${delegationFedPort}>
SSLEngine on
ServerName ${synapseServerName};
AllowEncodedSlashes NoDecode
ProxyPass /_matrix http://127.0.0.1:${fedPort}/_matrix nocanon
ProxyPassReverse /_matrix http://127.0.0.1:${fedPort}/_matrix
</VirtualHost>
`

View File

@@ -0,0 +1,17 @@
export default ({
delegationFedPort,
delegationClientPort,
fedPort,
clientPort,
synapseServerName,
}) => `${synapseServerName}:${delegationClientPort} {
proxy /_matrix http://localhost:${clientPort} {
transparent
}
}
${synapseServerName}:${delegationFedPort} {
proxy / http://localhost:${fedPort} {
transparent
}
}`

View File

@@ -0,0 +1,13 @@
/* eslint-disable max-len */
export default ({
synapseServerName,
delegationClientPort,
}) => `{
"m.homeserver": {
"base_url": "https://${synapseServerName}${delegationClientPort ? `:${delegationClientPort}` : ""}"
},
}`
// TODO: Maybe include this?
// "m.identity_server": {
// "base_url": "https://identity.example.com"
// },

View File

@@ -0,0 +1,9 @@
/* eslint-disable max-len */
export default ({
delegationFedPort,
delegationClientPort,
fedPort,
clientPort,
serverName,
synapseServerName,
}) => `_matrix._tcp.${serverName} 3600 IN SRV 10 5 ${delegationClientPort} ${synapseServerName}`

View File

@@ -0,0 +1,6 @@
export default ({
synapseServerName,
delegationSynapsePort,
}) => `{
"m.server": "${synapseServerName}:${delegationSynapsePort}"
}`

View File

@@ -0,0 +1,51 @@
/* eslint-disable max-len */
export default ({
delegationFedPort,
delegationClientPort,
fedPort,
clientPort,
synapseServerName,
}) => {
if (fedPort == clientPort) {
return `frontend https
bind :::${delegationClientPort} v4v6 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2,http/1.1
# Matrix client traffic
acl matrix-host hdr(host) -i ${synapseServerName}
acl matrix-path path_beg /_matrix
use_backend matrix if matrix-host matrix-path
frontend matrix-federation
bind :::${delegationFedPort} v4v6 ssl crt /etc/ssl/haproxy/<your_tls_cert>.pem alpn h2,http/1.1
default_backend matrix
backend matrix
server matrix 127.0.0.1:${fedPort}
`
} else {
return `frontend https
bind:::${delegationClientPort} v4v6 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2, http / 1.1
# Matrix client traffic
acl matrix-host hdr(host) -i ${synapseServerName}
acl matrix-path path_beg /_matrix
use_backend matrix-client if matrix-host matrix-path
frontend matrix - federation
bind::: ${delegationFedPort} v4v6 ssl crt /etc/ssl/haproxy/<your_tls_cert>.pem alpn h2,http/1.1
default_backend matrix
backend matrix
server matrix 127.0.0.1:${fedPort}
backend matrix-client 127.0.0.1:${clientPort}`
}
}

View File

@@ -0,0 +1,27 @@
import React from 'react';
export default ({
delegationFedPort,
delegationClientPort,
fedPort,
clientPort,
synapseServerName,
}) => `listen {delegationClientPort} ssl;
listen [::]:${delegationClientPort} ssl;
server_name ${synapseServerName};
location /_matrix {
proxy_pass http://localhost:${clientPort};
proxy_set_header X-Forwarded-For $remote_addr;
}
}
server {
listen ${delegationFedPort} ssl default_server;
listen [::]:${delegationFedPort} ssl default_server;
server_name ${synapseServerName};
location / {
proxy_pass http://localhost:${fedPort};
proxy_set_header X-Forwarded-For $remote_addr;
}
}`

View File

@@ -0,0 +1,154 @@
/* eslint-disable camelcase */
import { TLS_TYPES, REVERSE_PROXY_TYPES, DATABASE_TYPES } from '../actions/constants';
import { CONFIG_LOCK } from '../api/constants';
const listeners = config => {
const listeners = [];
if (config.tls == TLS_TYPES.REVERSE_PROXY) {
listeners.push({
port: config.synapseFederationPort,
tls: false,
bind_addresses: ['::1', '127.0.0.1'],
type: "http",
x_forwarded: true,
resources: [{
names: ["federation"],
compress: false,
}],
});
} else {
listeners.push({
port: config.synapseFederationPort,
tls: true,
type: "http",
resources: [{
names: ["federation"],
}],
});
}
if (config.synapseClientPort == config.synapseFederationPort) {
listeners[0].resources[0].names.push("client");
} else if (config.tls == TLS_TYPES.REVERSE_PROXY) {
listeners.push({
port: config.synapseClientPort,
tls: false,
bind_addresses: ['::1', '127.0.0.1'],
type: "http",
x_forwarded: true,
resources: [{
names: ["client"],
compress: false,
}],
});
} else {
listeners.push({
port: config.synapseClientPort,
tls: true,
type: "http",
resources: [{
names: ["client"],
}],
});
}
return { listeners: listeners };
}
const tlsPaths = config => {
if (config.tls == TLS_TYPES.TLS) {
return {
tls_certificate_path: config.tlsCertPath,
tls_private_key_path: config.tlsCertKeyPath,
}
} else if (config.tls == TLS_TYPES.ACME) {
return {
tls_certificate_path:
config.configDir + "/" + config.servername + ".tls.cert",
tls_private_key_path:
config.configDir + "/" + config.servername + ".tls.key",
}
} else {
return {}
}
}
const acme = config => {
if (config.tls == TLS_TYPES.ACME) {
return {
acme_domain: config.delegationServerName ?
config.delegationServerName :
config.servername,
}
} else {
return {}
}
}
const database = config => {
if (config.databaseType == DATABASE_TYPES.SQLITE3) {
return {
database: {
name: config.databaseType,
}
}
} else {
return {
database: {
name: config.databaseType,
args: {
user: config.databaseUsername,
password: config.databasePassword,
host: config.databaseHost,
database: config.database,
}
}
}
}
}
export const baseConfigToSynapseConfig = config => {
const conf = {
server_name: config.servername,
report_stats: config.reportStats,
...database(config),
...listeners(config),
...tlsPaths(config),
...acme(config),
[CONFIG_LOCK]: true,
}
console.log(conf)
return conf
}

View File

@@ -0,0 +1,38 @@
@mixin rippler {
position: relative;
overflow: hidden;
transform: translate3d(0, 0, 0);
&:after {
content: "";
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
background-image: radial-gradient(circle, #fff 10%, transparent 10.01%);
background-repeat: no-repeat;
background-position: 50%;
transform: scale(10, 10);
opacity: 0;
transition: transform .5s, opacity 1s;
}
&:active:after {
transform: scale(0, 0);
opacity: .3;
transition: 0s;
}
}
@mixin dropshadowed {
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover {
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,243 @@
@import './themes.scss';
@import './animations.scss';
@import './bootstrap.min.css';
@mixin theme {
@include dark;
}
// Type selectors (Parents)
html {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 1.6rem;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
@include theme;
background-color: $primary;
color: $font;
margin: auto;
width: 48rem;
max-width: 90%;
text-align: center;
}
// Type selectors (Children)
a {
@include theme;
color: $link;
text-decoration: none;
font-size: 0.6rem;
line-height: 0.8rem;
}
a:hover {
color: $link;
text-decoration: none;
}
button {
@include rippler;
border-radius: 5rem;
font-size: 0.8rem;
padding: 0.4rem 1.2rem;
color: $font;
background-color: $tertiary;
border: none;
display: inline-block;
text-transform: capitalize;
font-style: bold;
color: $primary;
}
button:hover {
background-color: $link;
}
button[disabled] {
background-color: darken($secondary, 8%);
color: lighten($font, 50%);
}
h1 {
@include theme;
font-size: 1rem;
margin: 1rem;
}
input {
padding: 0.4rem;
font-size: 0.6rem;
border-style: none;
background: white;
border-radius: 4rem;
color: $font;
padding: 0.4rem 1.2rem;
margin-bottom: 1rem;
}
input[type=checkbox] {
margin-right: 0.4rem;
}
label {
font-size: 0.6rem;
font-weight: 600;
}
p {
font-size: 0.6rem;
line-height: 0.8rem;
}
pre {
background: white;
border-radius: 0.4rem;
color: $font;
padding: 1rem;
font-size: 0.6rem;
text-align: left;
text-decoration: none;
}
select {
font-size: 0.7rem;
margin-bottom: 1.3rem;
&:after {
content: ;
transform: rotate(90deg);
position: absolute;
}
}
// Class selectors
.active-card-header {
cursor: pointer;
}
.baseintro {
padding: 2rem 1rem;
}
.blockwrapper {
display: block;
}
.buttonDisplay {
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.buttonGroup {
margin-top: 1rem;
margin-bottom: 1rem;
}
.card {
text-align: left;
border-style: none;
background: $secondary;
margin-bottom: 8px;
}
.card-header {
font-size: 0.8rem;
border-style: none;
}
.card-header-tabs {
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
}
.chevron {
float:right;
}
.inactive-card-header {
cursor: not-allowed;
}
.inputButton {
margin-left: 0.6rem;
font-size: 0.6rem;
}
.inlineError {
color: $error;
font-size: 0.6rem;
line-height: 0.8rem;
span {
margin-left: 1rem;
}
}
.invalid {
border-color: $error;
}
.invalidInput {
border-color: red;
}
.keyDisplay {
word-wrap: break-word;
}
.logo {
position: absolute;
top:0;
right: 0;
margin: 0.5rem;
}
.nav-tabs .nav-link.active {
border-color: transparent;
background-color: $smoke;
}
.nav-tabs .nav-link:hover {
border-color: transparent;
background-color: $smoke;
}
.or {
font-size: 0.6rem;
margin-left: 0.4rem;
margin-right: 0.4rem;
}
.redButton {
background-color: red;
}
.servername {
position: absolute;
margin-top: 0.5rem;
margin-left: 0.5rem;
color: darken(silver, 20%);
}
.setupCompleteTitle {
margin-left: 0;
margin-top: 0;
}
.tab-content {
background-color: $smoke;
padding: 1rem;
}

View File

@@ -0,0 +1,10 @@
@mixin dark {
$primary: #ffffff !global;
$secondary: #f4f4f4 !global;
$tertiary: #3b444b !global;
$smoke: #ececec !global;
$font: #333 !global;
$highlight: #4aeff0 !global;
$link: #0098d4 !global;
$error: #e32017 !global;
}

View File

@@ -0,0 +1,73 @@
import 'webpack';
import path from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import HtmlWebpackTagsPlugin from 'html-webpack-tags-plugin';
export default {
entry: ['./src/js/index.jsx', './src/scss/main.scss', './src/scss/bootstrap.min.css'],
watch: true,
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
resolve: {
extensions: ['.js', '.jsx', '.css'],
},
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
},
],
},
{
test: /\.css$/,
use: [
{
loader: 'file-loader',
options: {
name: 'css/[name].css',
},
},
],
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
hash: true,
filename: __dirname + '/dist/index.html',
title: 'Topology - The synapse configuration tool',
}),
new HtmlWebpackTagsPlugin({
tags: ['css/bootstrap.min.css'],
}),
],
};