configuration changes, update docs

This commit is contained in:
Hay1tsme 2024-01-11 12:08:22 -05:00
parent 06e7288cad
commit fb36cc9b07
9 changed files with 199 additions and 110 deletions

View File

@ -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
)

View File

@ -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()

View File

@ -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(

View File

@ -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={}
))
))
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])

View File

@ -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 `""`
- `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)

View File

@ -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.
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.

View File

@ -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: ""

View File

@ -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;

View File

@ -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,
)