diff --git a/core/allnet.py b/core/allnet.py index aa82071..8b47dca 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -123,13 +123,8 @@ class AllnetServlet: ) self.logger.initialized = True - plugins = Utils.get_all_titles() - - if len(plugins) == 0: - self.logger.error("No games detected!") - self.logger.info( - f"Serving {len(TitleServlet.title_registry)} game codes" + f"Ready on port {self.config.allnet.port if self.config.allnet.standalone else self.config.server.port}" ) async def handle_poweron(self, request: Request): @@ -894,10 +889,30 @@ if not access(cfg.server.log_dir, W_OK): exit(1) billing = BillingServlet(cfg, cfg_dir) -app = Starlette( +app_billing = Starlette( cfg.server.is_develop, [ Route("/request", billing.handle_billing_request, methods=["POST"]), Route("/request/", billing.handle_billing_request, methods=["POST"]), ] ) + +allnet = AllnetServlet(cfg, cfg_dir) +route_lst = [ + Route("/sys/servlet/PowerOn", allnet.handle_poweron, methods=["GET", "POST"]), + Route("/sys/servlet/DownloadOrder", allnet.handle_dlorder, methods=["GET", "POST"]), + Route("/sys/servlet/LoaderStateRecorder", allnet.handle_loaderstaterecorder, methods=["GET", "POST"]), + Route("/sys/servlet/Alive", allnet.handle_alive, methods=["GET", "POST"]), + Route("/naomitest.html", allnet.handle_naomitest), +] + +if cfg.allnet.allow_online_updates: + route_lst += [ + Route("/report-api/Report", allnet.handle_dlorder_report, methods=["POST"]), + Route("/dl/ini/{file:str}", allnet.handle_dlorder_ini), + ] + +app_allnet = Starlette( + cfg.server.is_develop, + route_lst +) diff --git a/core/app.py b/core/app.py index 0a59112..080e92e 100644 --- a/core/app.py +++ b/core/app.py @@ -52,21 +52,17 @@ logger.info(f"Artemis starting in {'develop' if cfg.server.is_develop else 'prod title = TitleServlet(cfg, cfg_dir) # This has to be loaded first to load plugins mucha = MuchaServlet(cfg, cfg_dir) -allnet = AllnetServlet(cfg, cfg_dir) route_lst: List[Route] = [ # Allnet - Route("/sys/servlet/PowerOn", allnet.handle_poweron, methods=["GET", "POST"]), - Route("/sys/servlet/DownloadOrder", allnet.handle_dlorder, methods=["GET", "POST"]), - Route("/sys/servlet/LoaderStateRecorder", allnet.handle_loaderstaterecorder, methods=["GET", "POST"]), - Route("/sys/servlet/Alive", allnet.handle_alive, methods=["GET", "POST"]), - Route("/report-api/Report", allnet.handle_dlorder_report, methods=["POST"]), - Route("/dl/ini/{file:str}", allnet.handle_dlorder_ini), - Route("/naomitest.html", allnet.handle_naomitest), + # Mucha Route("/mucha_front/boardauth.do", mucha.handle_boardauth, methods=["POST"]), Route("/mucha_front/updatacheck.do", mucha.handle_updatecheck, methods=["POST"]), Route("/mucha_front/downloadstate.do", mucha.handle_dlstate, methods=["POST"]), + # General + Route("/", dummy_rt), + Route("/robots.txt", FrontendServlet.robots) ] if not cfg.billing.standalone: @@ -76,14 +72,21 @@ if not cfg.billing.standalone: Route("/request/", billing.handle_billing_request, methods=["POST"]), ] -if not cfg.frontend.standalone and cfg.frontend.secret: - frontend = FrontendServlet(cfg, cfg_dir) - route_lst += frontend.get_routes() -else: - if not cfg.frontend.secret: - logger.error("Frontend secret not specified, cannot start frontend!") - route_lst.append(Route("/", dummy_rt)) - route_lst.append(Route("/robots.txt", FrontendServlet.robots)) +if not cfg.allnet.standalone: + allnet = AllnetServlet(cfg, cfg_dir) + route_lst += [ + Route("/sys/servlet/PowerOn", allnet.handle_poweron, methods=["GET", "POST"]), + Route("/sys/servlet/DownloadOrder", allnet.handle_dlorder, methods=["GET", "POST"]), + Route("/sys/servlet/LoaderStateRecorder", allnet.handle_loaderstaterecorder, methods=["GET", "POST"]), + Route("/sys/servlet/Alive", allnet.handle_alive, methods=["GET", "POST"]), + Route("/naomitest.html", allnet.handle_naomitest), + ] + + if cfg.allnet.allow_online_updates: + route_lst += [ + Route("/report-api/Report", allnet.handle_dlorder_report, methods=["POST"]), + Route("/dl/ini/{file:str}", allnet.handle_dlorder_ini), + ] for code, game in title.title_registry.items(): route_lst += game.get_routes() diff --git a/core/config.py b/core/config.py index e5d0f35..8e0e79d 100644 --- a/core/config.py +++ b/core/config.py @@ -29,7 +29,7 @@ class ServerConfig: Port the game will listen on """ return CoreConfig.get_config_field( - self.__config, "core", "server", "port", default=8080 + self.__config, "core", "server", "port", default=80 ) @property @@ -127,13 +127,13 @@ class TitleConfig: @property def reboot_start_time(self) -> str: return CoreConfig.get_config_field( - self.__config, "core", "title", "reboot_start_time", default="04:00" + self.__config, "core", "title", "reboot_start_time", default="" ) @property def reboot_end_time(self) -> str: return CoreConfig.get_config_field( - self.__config, "core", "title", "reboot_end_time", default="05:00" + self.__config, "core", "title", "reboot_end_time", default="" ) class DatabaseConfig: @@ -207,9 +207,15 @@ class FrontendConfig: self.__config = parent_config @property - def standalone(self) -> int: + def enable(self) -> bool: return CoreConfig.get_config_field( - self.__config, "core", "frontend", "standalone", default=True + self.__config, "core", "frontend", "enable", default=False + ) + + @property + def port(self) -> int: + return CoreConfig.get_config_field( + self.__config, "core", "frontend", "port", default=8080 ) @property @@ -230,6 +236,18 @@ class AllnetConfig: def __init__(self, parent_config: "CoreConfig") -> None: self.__config = parent_config + @property + def standalone(self) -> bool: + return CoreConfig.get_config_field( + self.__config, "core", "allnet", "standalone", default=False + ) + + @property + def port(self) -> int: + return CoreConfig.get_config_field( + self.__config, "core", "allnet", "port", default=80 + ) + @property def loglevel(self) -> int: return CoreConfig.str_to_loglevel( @@ -257,7 +275,7 @@ class BillingConfig: @property def standalone(self) -> bool: return CoreConfig.get_config_field( - self.__config, "core", "billing", "standalone", default=True + self.__config, "core", "billing", "standalone", default=False ) @property @@ -361,6 +379,19 @@ class CoreConfig(dict): return logging.DEBUG else: return logging.INFO + + @classmethod + def loglevel_to_str(cls, level: int) -> str: + if level == logging.ERROR: + return "error" + elif level == logging.WARN: + return "warn" + elif level == logging.INFO: + return "info" + elif level == logging.DEBUG: + return "debug" + else: + return "notset" @classmethod def get_config_field( diff --git a/core/frontend.py b/core/frontend.py index 17b058f..cbf2fe0 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -3,15 +3,17 @@ from typing import Any, Dict, List, Union, Optional from starlette.requests import Request from starlette.routing import Route, Mount from starlette.responses import Response, PlainTextResponse, RedirectResponse +from starlette.applications import Starlette from logging.handlers import TimedRotatingFileHandler import jinja2 import bcrypt import re import jwt +import yaml from base64 import b64decode from enum import Enum -from urllib import parse from datetime import datetime, timezone +from os import path, environ, mkdir, W_OK, access from core import CoreConfig, Utils from core.data import Data @@ -725,4 +727,21 @@ class FE_Machine(FE_Base): title=f"{self.core_config.server.name} | Machine", sesh=vars(usr_sesh), arcade={} - )) \ No newline at end of file + )) + +cfg_dir = environ.get("DIANA_CFG_DIR", "config") +cfg: CoreConfig = CoreConfig() +if path.exists(f"{cfg_dir}/core.yaml"): + cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml"))) + +if not path.exists(cfg.server.log_dir): + mkdir(cfg.server.log_dir) + +if not access(cfg.server.log_dir, W_OK): + print( + f"Log directory {cfg.server.log_dir} NOT writable, please check permissions" + ) + exit(1) + +fe = FrontendServlet(cfg, cfg_dir) +app = Starlette(cfg.server.is_develop, fe.get_routes(), on_startup=[fe.startup]) diff --git a/docs/config.md b/docs/config.md index 81fb43d..07398d1 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,23 +1,24 @@ # ARTEMiS Configuration ## Server - `listen_address`: IP Address or hostname that the server will listen for connections on. Set to 127.0.0.1 for local only, or 0.0.0.0 for all interfaces. Default `127.0.0.1` +- `hostname`: Hostname that gets sent to clients to tell them where to connect. Games must be able to connect to your server via the hostname or IP you spcify here. Note that most games will reject `localhost` or `127.0.0.1`. Default `localhost` +- `port`: Port that the server will listen for connections on. Default `80` +- `ssl_key`: Location of the ssl server key for the secure title server. Ignored if you don't use SSL. Default `cert/title.key` +- `ssl_cert`: Location of the ssl server certificate for the secure title server. Must not be a self-signed SSL. Ignored if you don't use SSL. Default `cert/title.pem` - `allow_user_registration`: Allows users to register in-game via the AimeDB `register` function. Disable to be able to control who can use cards on your server. Default `True` - `allow_unregistered_serials`: Allows games that do not have registered keychips to connect and authenticate. Disable to restrict who can connect to your server. Recomended to disable for production setups. Default `True` - `name`: Name for the server, used by some games in their default MOTDs. Default `ARTEMiS` - `is_develop`: Flags that the server is a development instance without a proxy standing in front of it. Setting to `False` tells the server not to listen for SSL, because the proxy should be handling all SSL-related things, among other things. Default `True` -- `threading`: Flags that `reactor.run` should be called via the `Thread` standard library. May provide a speed boost, but removes the ability to kill the server via `Ctrl + C`. Default: `False` -- `check_arcade_ip`: Checks IPs against the `arcade` table in the database, if one is defined. Default `False` -- `strict_ip_checking`: Rejects clients if there is no IP in the `arcade` table for the respective arcade +- `is_using_proxy`: Flags that you'll be using some other software, such as nginx, to proxy requests, and to send `proxy_port` or `proxy_port_ssl` to games instead of `port`. Default `False` +- `proxy_port`: Which port your front-facing proxy will be listening on. Ignored if `is_using_proxy` is `False` or if set to `0`. Default `0` +- `proxy_port`: Which port your front-facing proxy will be listening for ssl connections on. Ignored if `is_using_proxy` is `False` or if set to `0`. Default `0` - `log_dir`: Directory to store logs. Server MUST have read and write permissions to this directory or you will have issues. Default `logs` +- `check_arcade_ip`: Checks IPs against the `arcade` table in the database, if one is defined. Default `False` +- `strict_ip_checking`: Rejects clients if there is no IP in the `arcade` table for the respective arcade. Default `False` ## Title - `loglevel`: Logging level for the title server. Default `info` -- `hostname`: Hostname that gets sent to clients to tell them where to connect. Games must be able to connect to your server via the hostname or IP you spcify here. Note that most games will reject `localhost` or `127.0.0.1`. Default `localhost` -- `port`: Port that the title server will listen for connections on. Set to 0 to use the Allnet handler to reduce the port footprint. Default `8080` -- `port_ssl`: Port that the secure title server will listen for connections on. Set to 0 to use the Allnet handler to reduce the port footprint. Default `0` -- `ssl_key`: Location of the ssl server key for the secure title server. Ignored if `port_ssl` is set to `0` or `is_develop` set to `False`. Default `cert/title.key` -- `ssl_cert`: Location of the ssl server certificate for the secure title server. Must not be a self-signed SSL. Ignored if `port_ssl` is set to `0` or `is_develop` is set to `False`. Default `cert/title.pem` -- `reboot_start_time`: 24 hour JST time that clients will see as the start of maintenance period. Leave blank for no maintenance time. Default: "" -- `reboot_end_time`: 24 hour JST time that clients will see as the end of maintenance period. Leave blank for no maintenance time. Default: "" +- `reboot_start_time`: 24 hour JST time that clients will see as the start of maintenance period, ex `04:00`. Leave blank for no maintenance time. Default: `""` +- `reboot_end_time`: 24 hour JST time that clients will see as the end of maintenance period, ex `05:00`. Leave blank for no maintenance time. Default: `""` ## Database - `host`: Host of the database. Default `localhost` - `username`: Username of the account the server should connect to the database with. Default `aime` @@ -26,23 +27,29 @@ - `port`: Port the database server is listening on. Default `3306` - `protocol`: Protocol used in the connection string, e.i `mysql` would result in `mysql://...`. Default `mysql` - `sha2_password`: Weather or not the password in the connection string should be hashed via SHA2. Default `False` -- `loglevel`: Logging level for the database. Default `warn` -- `user_table_autoincrement_start`: What the `aime_user` table ID autoincrememnt should start with. Default `10000` +- `loglevel`: Logging level for the database. Default `info` - `memcached_host`: Host of the memcached server. Default `localhost` ## Frontend -- `enable`: Weather or not the frontend should be enabled. Default `False` -- `port`: Port the frontend should listen for connections on. Default `8090` +- `enable`: Weather or not the frontend should run. Default `False` +- `port`: Port the frontend should listen on. Default `8080` - `loglevel`: Logging level for the frontend server. Default `info` +- `secret`: Base64-encoded JWT secret for session cookies, generated by you. Default `""` ## Allnet +- `standalone`: Weather allnet should be launched as a standalone service on it's own port. +- `port`: Port the billing server should listen for connections on. Games are hardcoded to ask for port `80` so only change if you have a proxy redirecting properly. Default `80` - `loglevel`: Logging level for the allnet server. Default `info` -- `port`: Port the allnet server should listen for connections on. Games are hardcoded to ask for port `80` so only change if you have a proxy redirecting properly. Default `80` - `allow_online_updates`: Allow allnet to distribute online updates via DownloadOrders. This system is currently non-functional, so leave it disabled. Default `False` +- `update_cfg_folder`: Folder where delivery INI files will be checked for. Ignored if `allow_online_updates` is `False`. Default `""` ## Billing -- `port`: Port the billing server should listen for connections on. Games are hardcoded to ask for port `8443` so only change if you have a proxy redirecting properly. Set to 0 to use the allnet handler to reduce the number of ports the server eats up. Default `8443` -- `ssl_key`: Location of the ssl server key for the billing server. Ignored if `port` is set to `0` or `is_develop` set to `False`. Default `cert/server.key` -- `ssl_cert`: Location of the ssl server certificate for the billing server. Must match the CA distributed to users or the billing server will not connect. Ignored if `port` is set to `0` or `is_develop` is set to `False`. Default `cert/server.pem` +- `standalone`: Weather or not the billing server should be launched as a standalone service on it's own port. Setting this to `True` requires that you have `ssl_key` and `ssl_cert` set. Default `False` +- `loglevel`: Logging level for the billing server. Default `info` +- `port`: Port the billing server should listen for connections on. Games are hardcoded to ask for port `8443` so only change if you have a proxy redirecting properly. Ignored if `standalone` is `False`. Default `8443` +- `ssl_key`: Location of the ssl server key for the billing server. Ignored if `standalone` is `False`. Default `cert/server.key` +- `ssl_cert`: Location of the ssl server certificate for the billing server. Ignored if `standalone` is `False`. Must match the CA distributed to users or the billing server will not connect. Default `cert/server.pem` - `signing_key`: Location of the RSA Private key used to sign billing requests. Must match the public key distributed to users or the billing server will not connect. Default `cert/billing.key` ## Aimedb - `loglevel`: Logging level for the aimedb server. Default `info` - `port`: Port the aimedb server should listen for connections on. Games are hardcoded to ask for port `22345` so only change if you have a proxy redirecting properly. Default `22345` -- `key`: Key to encrypt/decrypt aimedb requests and responses. MUST be set or the server will not start. If set incorrectly, your server will not properly handle aimedb requests. Default `""` \ No newline at end of file +- `key`: Key to encrypt/decrypt aimedb requests and responses. MUST be set or the server will not start. If set incorrectly, your server will not properly handle aimedb requests. Default `""` +- `id_secret`: Base64-encoded JWT secret for Sega Auth IDs. Leaving this blank disables this feature. Default `""` +- `id_lifetime_seconds`: Number of secons a JWT generated should be valid for. Default `86400` (1 day) diff --git a/docs/prod.md b/docs/prod.md index de79b99..31efe8c 100644 --- a/docs/prod.md +++ b/docs/prod.md @@ -1,41 +1,30 @@ # ARTEMiS Production mode -Production mode is a configuration option that changes how the server listens to be more friendly to a production environment. This mode assumes that a proxy (for this guide, nginx) is standing in front of the server to handle port mapping and TLS. In order to activate production mode, simply change `is_develop` to `False` in `core.yaml`. Next time you start the server, you should see "Starting server in production mode". +ARTEMiS is designed to run in one of two ways. Developmen/local mode, which assumes you're just trying to set up something to save your scores and make the games work, and have patched your games to disable SSL and cert checks and encryption and the like, and production mode. In production mode, artemis assumes you have a proxy server, such as nginx or apache, standing in front of artemis doing HTTPS and port management. This document will cover how to properly set up a production instance of ARTEMiS. + +## ARTEMiS configuration +Step 1 is to edit your artemis configuration. Some recomended changes: +### `server` +- `listen_address` -> `127.0.0.1` +- `is_develop` -> `False` +- `is_using_proxy` -> `True` +- `port` -> The port nginx will send proxied requests to. If you're using the example config, set this to 8080. +- `proxy_port` -> The port your proxy will be accepting title server connections on. If you're using the example config, set this to 80. +- `proxy_port_ssl` -> The port your proxy will be accepting secure title server connections on. If you're using the example config, set this to 443. +- `allow_unregistered_serials` -> `False` +### `billing` +- `standalone` -> `False` +### `frontend` +- `enable` -> `True` if you want the frontend +- `port` -> `8080` if you're using the default nginx config + +If you plan to serve artemis behind a VPN, these additional settings are also recomended +- `check_arcade_ip` -> `True` +- `strict_ip_checking` -> `True` ## Nginx Configuration -### Port forwarding -Artemis requires that the following ports be forwarded to allow internet traffic to access the server. This will not change regardless of what you set in the config, as many of these ports are hard-coded in the games. -`tcp:80` all.net, non-ssl titles -`tcp:8443` billing -`tcp:22345` aimedb -`tcp:443` frontend, SSL titles +For most cases, the config in `example_config` will suffice. It makes the following assumptions +- ARTEMiS is running on port 8080 +- Billing is set to not be standalone +- You're not using cloudflare in front of your frontend -### A note about external proxy services (cloudflare, etc) -Due to the way that artemis functions, it is currently not possible to put the server behind something like Cloudflare. Cloudflare only proxies web traffic on the standard ports (80, 443) and, as shown above, this does not work with artemis. Server administrators should seek other means to protect their network (VPS hosting, VPN, etc) - -### SSL Certificates -You will need to generate SSL certificates for some games. The certificates vary in security and validity requirements. Please see the general guide below -- General Title: The certificate for the general title server should be valid, not self-signed and match the CN that the game will be reaching out to (e.i if your games are reaching out to titles.hostname.here, your ssl certificate should be valid for titles.hostname.here, or *.hostname.here) -- CXB: Same requires as the title server. It must not be self-signed, and CN must match. Recomended to get a wildcard cert if possible, and use it for both Title and CXB -- Pokken: Pokken can be self-signed, and the CN doesn't have to match, but it MUST use 2048-bit RSA. Due to the games age, andthing stronger then that will be rejected. - -### Port mappings -An example config is provided in the `config` folder called `nginx_example.conf`. It is set up for the following: -`naominet.jp:tcp:80` -> `localhost:tcp:8000` for allnet -`ib.naominet.jp:ssl:8443` -> `localhost:tcp:8444` for the billing server -`your.hostname.here:ssl:443` -> `localhost:tcp:8080` for the SSL title server -`your.hostname.here:tcp:80` -> `localhost:tcp:8080` for the non-SSL title server -`cxb.hostname.here:ssl:443` -> `localhost:tcp:8080` for crossbeats (appends /SDCA/104/ to the request) -`pokken.hostname.here:ssl:443` -> `localhost:tcp:8080` for pokken -`frontend.hostname.here:ssl:443` -> `localhost:tcp:8090` for the frontend, includes https redirection - -If you're using this as a guide, be sure to replace your.hostname.here with the hostname you specified in core.yaml under `titles->hostname`. Do *not* change naominet.jp, or allnet/billing will fail. Also remember to specifiy certificate paths correctly, as in the example they are simply placeholders. - -### Multi-service ports -It is possible to use nginx to redirect billing and title server requests to the same port that all.net uses. By setting `port` to 0 under billing and title server, you can change the nginx config to serve the following (entries not shown here should be the same) -`ib.naominet.jp:ssl:8443` -> `localhost:tcp:8000` for the billing server -`your.hostname.here:ssl:443` -> `localhost:tcp:8000` for the SSL title server -`your.hostname.here:tcp:80` -> `localhost:tcp:8000` for the non-SSL title server -`cxb.hostname.here:ssl:443` -> `localhost:tcp:8000` for crossbeats (appends /SDCA/104/ to the request) -`pokken.hostname.here:ssl:443` -> `localhost:tcp:8000` for pokken - -This will allow you to only use 3 ports locally, but you will still need to forward the same internet-facing ports as before. \ No newline at end of file +If this describes you, your only configuration needs are to edit the `server_name` and `certificate_*` directives. Otherwise, please see nginx configuration documentation to configure it to best suit your setup. diff --git a/example_config/core.yaml b/example_config/core.yaml index 464da8b..51417a8 100644 --- a/example_config/core.yaml +++ b/example_config/core.yaml @@ -1,7 +1,7 @@ server: listen_address: "127.0.0.1" hostname: "localhost" - port: 8080 + port: 80 ssl_key: "cert/title.key" ssl_cert: "cert/title.crt" allow_user_registration: True @@ -34,10 +34,13 @@ database: frontend: standalone: True + port: 8080 loglevel: "info" secret: "" allnet: + standalone: False + port: 80 loglevel: "info" allow_online_updates: False update_cfg_folder: "" diff --git a/example_config/nginx_example.conf b/example_config/nginx_example.conf index 1790b84..fb77fdb 100644 --- a/example_config/nginx_example.conf +++ b/example_config/nginx_example.conf @@ -69,12 +69,12 @@ server { location / { return 301 https://$host$request_uri; - # If you don't want https redirection, comment the line above and uncomment the line below + # If you don't want https redirection, or are using something like cloudflare to manage HTTPS, comment out the line above and uncomment the line below # proxy_pass http://localhost:8080/; } } -# Frontend HTTPS. Comment out if you on't intend to use the frontend +# Frontend HTTPS. Comment out if you on't intend to use the frontend, or have cloudflare or something managing https for you. server { listen 443 ssl; server_name frontend.hostname.here; diff --git a/index.py b/index.py index c820f22..fbd68d0 100644 --- a/index.py +++ b/index.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import argparse import yaml -from os import path, mkdir, access, W_OK, environ +from os import path, environ import uvicorn import logging import asyncio @@ -33,31 +33,53 @@ async def launch_main(cfg: CoreConfig, ssl: bool) -> None: async def launch_billing(cfg: CoreConfig) -> None: server_cfg = uvicorn.Config( - "core.allnet:app", - host=cfg.server.listen_address, - port=cfg.billing.port, - reload=cfg.server.is_develop, - log_level="info" if cfg.server.is_develop else "critical", - ssl_version=3, - ssl_certfile=cfg.billing.ssl_cert, - ssl_keyfile=cfg.billing.ssl_key - ) + "core.allnet:app_billing", + host=cfg.server.listen_address, + port=cfg.billing.port, + reload=cfg.server.is_develop, + log_level="info" if cfg.server.is_develop else "critical", + ssl_version=3, + ssl_certfile=cfg.billing.ssl_cert, + ssl_keyfile=cfg.billing.ssl_key + ) server = uvicorn.Server(server_cfg) - if cfg.billing.standalone: - await server.serve() - else: - while True: - pass + await server.serve() + +async def launch_frontend(cfg: CoreConfig) -> None: + server_cfg = uvicorn.Config( + "core.frontend:app", + host=cfg.server.listen_address, + port=cfg.frontend.port, + reload=cfg.server.is_develop, + log_level="info" if cfg.server.is_develop else "critical", + ) + server = uvicorn.Server(server_cfg) + await server.serve() + +async def launch_allnet(cfg: CoreConfig) -> None: + server_cfg = uvicorn.Config( + "core.allnet:app_allnet", + host=cfg.server.listen_address, + port=cfg.allnet.port, + reload=cfg.server.is_develop, + log_level="info" if cfg.server.is_develop else "critical", + ) + server = uvicorn.Server(server_cfg) + await server.serve() async def launcher(cfg: CoreConfig, ssl: bool) -> None: AimedbServlette(cfg).start() + task_list = [asyncio.create_task(launch_main(cfg, ssl))] + if cfg.billing.standalone: + task_list.append(asyncio.create_task(launch_billing(cfg))) + if cfg.frontend.enable: + task_list.append(asyncio.create_task(launch_frontend(cfg))) + if cfg.allnet.standalone: + task_list.append(asyncio.create_task(launch_allnet(cfg))) + done, pending = await asyncio.wait( - [ - asyncio.create_task(launch_main(cfg, ssl)), - asyncio.create_task(launch_billing(cfg)), - - ], + task_list, return_when=asyncio.FIRST_COMPLETED, )