Module jumpscale.servers.threebot.threebot
Expand source code
from jumpscale.loader import j
import imp
import os
import sys
import toml
import shutil
import gevent
import signal
from urllib.parse import urlparse
from gevent.pywsgi import WSGIServer
from jumpscale.core.base import Base, fields
from jumpscale import packages as pkgnamespace
from jumpscale.sals.nginx.nginx import LocationType, PORTS
from jumpscale.sals.nginx.nginx import LocationType, PORTS, AcmeServer
from jumpscale.servers.appserver import StripPathMiddleware, apply_main_middlewares
GEDIS = "gedis"
GEDIS_HTTP = "gedis_http"
GEDIS_HTTP_HOST = "127.0.0.1"
GEDIS_HTTP_PORT = 8000
SERVICE_MANAGER = "service_manager"
CHATFLOW_SERVER_HOST = "127.0.0.1"
CHATFLOW_SERVER_PORT = 31000
DEFAULT_PACKAGES = {
    "auth": {"path": os.path.dirname(j.packages.auth.__file__), "giturl": ""},
    "chatflows": {"path": os.path.dirname(j.packages.chatflows.__file__), "giturl": ""},
    "admin": {"path": os.path.dirname(j.packages.admin.__file__), "giturl": ""},
    "weblibs": {"path": os.path.dirname(j.packages.weblibs.__file__), "giturl": ""},
    "backup": {"path": os.path.dirname(j.packages.backup.__file__), "giturl": ""},
}
DOWNLOADED_PACKAGES_PATH = j.sals.fs.join_paths(j.core.dirs.VARDIR, "downloaded_packages")
class NginxPackageConfig:
    def __init__(self, package):
        self.package = package
        self.nginx = j.sals.nginx.get("main")
    @property
    def default_config(self):
        default_server = {
            "name": "default",
            "ports": self.package.config.get("ports"),
            "locations": self.package.config.get("locations", []),
            "domain": self.package.default_domain,
            "letsencryptemail": self.package.default_email,
            "acme_server_type": self.package.default_acme_server_type,
            "acme_server_url": self.package.default_acme_server_url,
        }
        is_auth = self.package.config.get("is_auth", True)
        is_admin = self.package.config.get("is_admin", True)
        is_package_authorized = self.package.config.get("is_package_authorized", False)
        for static_dir in self.package.static_dirs:
            path_url = j.data.text.removeprefix(static_dir.get("path_url"), "/")
            default_server["locations"].append(
                {
                    "is_auth": static_dir.get("is_auth", is_auth),
                    "is_admin": static_dir.get("is_admin", is_admin),
                    "is_package_authorized": static_dir.get("is_package_authorized", is_package_authorized),
                    "type": "static",
                    "name": static_dir.get("name"),
                    "spa": static_dir.get("spa"),
                    "index": static_dir.get("index"),
                    "path_url": j.sals.fs.join_paths(self.package.base_url, path_url),
                    "path_location": self.package.resolve_staticdir_location(static_dir),
                    "force_https": self.package.config.get("force_https", True),
                }
            )
        for bottle_server in self.package.bottle_servers:
            path_url = j.data.text.removeprefix(bottle_server.get("path_url"), "/")
            if hasattr(bottle_server, "standalone") and bottle_server.standalone:
                default_server["locations"].append(
                    {
                        "is_auth": bottle_server.get("is_auth", is_auth),
                        "is_admin": bottle_server.get("is_admin", is_admin),
                        "is_package_authorized": bottle_server.get("is_package_authorized", is_package_authorized),
                        "type": "proxy",
                        "name": bottle_server.get("name"),
                        "host": bottle_server.get("host"),
                        "port": bottle_server.get("port"),
                        "path_url": j.sals.fs.join_paths(self.package.base_url,),
                        "path_dest": bottle_server.get("path_dest"),
                        "websocket": bottle_server.get("websocket"),
                        "force_https": self.package.config.get("force_https", True),
                    }
                )
            else:
                path_url = j.data.text.removeprefix(bottle_server.get("path_url"), "/")
                default_server["locations"].append(
                    {
                        "is_auth": bottle_server.get("is_auth", is_auth),
                        "is_admin": bottle_server.get("is_admin", is_admin),
                        "is_package_authorized": bottle_server.get("is_package_authorized", is_package_authorized),
                        "type": "proxy",
                        "name": bottle_server.get("name"),
                        "host": "0.0.0.0",
                        "port": 31000,
                        "path_url": j.sals.fs.join_paths(self.package.base_url, path_url),
                        "path_dest": f"/{self.package.name}{bottle_server.get('path_dest')}",
                        "websocket": bottle_server.get("websocket"),
                        "force_https": self.package.config.get("force_https", True),
                    }
                )
        if self.package.actors_dir:
            default_server["locations"].append(
                {
                    "is_auth": is_auth,
                    "is_admin": is_admin,
                    "is_package_authorized": is_package_authorized,
                    "type": "proxy",
                    "name": "actors",
                    "host": GEDIS_HTTP_HOST,
                    "port": GEDIS_HTTP_PORT,
                    "path_url": j.sals.fs.join_paths(self.package.base_url, "actors"),
                    "path_dest": self.package.base_url,
                    "force_https": self.package.config.get("force_https", True),
                }
            )
        if self.package.chats_dir:
            default_server["locations"].append(
                {
                    "is_auth": is_auth,
                    "is_admin": is_admin,
                    "is_package_authorized": is_package_authorized,
                    "type": "proxy",
                    "name": "chats",
                    "host": CHATFLOW_SERVER_HOST,
                    "port": CHATFLOW_SERVER_PORT,
                    "path_url": j.sals.fs.join_paths(self.package.base_url, "chats"),
                    "path_dest": f"/chatflows{self.package.base_url}/chats",  # TODO: temperoary fix for auth package
                    "force_https": self.package.config.get("force_https", True),
                }
            )
        return [default_server]
    def apply(self, write_config=True):
        default_ports = [PORTS.HTTP, PORTS.HTTPS]
        servers = self.default_config + self.package.config.get("servers", [])
        for server in servers:
            ports = server.get("ports", default_ports) or default_ports
            for port in ports:
                server_name = server.get("name")
                if server_name != "default":
                    server_name = f"{self.package.name}_{server_name}"
                website = self.nginx.get_website(server_name, port=port)
                website.ssl = server.get("ssl", port == PORTS.HTTPS)
                website.includes = server.get("includes", [])
                website.domain = server.get("domain", self.default_config[0].get("domain"))
                website.letsencryptemail = server.get(
                    "letsencryptemail", self.default_config[0].get("letsencryptemail")
                )
                website.acme_server_type = server.get(
                    "acme_server_type", self.default_config[0].get("acme_server_type")
                )
                website.acme_server_url = server.get("acme_server_url", self.default_config[0].get("acme_server_url"))
                if server.get("key_path"):
                    website.key_path = server["key_path"]
                if server.get("cert_path"):
                    website.cert_path = server["cert_path"]
                if server.get("fullchain_path"):
                    website.fullchain_path = server["fullchain_path"]
                for location in server.get("locations", []):
                    loc = None
                    location_name = location.get("name")
                    location_name = f"{self.package.name}_{location_name}"
                    location_type = location.get("type", "static")
                    if location_type == "static":
                        loc = website.get_static_location(location_name)
                        loc.spa = location.get("spa", False)
                        loc.index = location.get("index")
                        loc.path_location = location.get("path_location")
                    elif location_type == "proxy":
                        loc = website.get_proxy_location(location_name)
                        loc.scheme = location.get("scheme", "http")
                        loc.host = location.get("host")
                        loc.port = location.get("port", PORTS.HTTP)
                        loc.path_dest = location.get("path_dest", "")
                        loc.websocket = location.get("websocket", False)
                        loc.proxy_buffering = location.get("proxy_buffering", "")
                        loc.proxy_buffers = location.get("proxy_buffers")
                        loc.proxy_buffer_size = location.get("proxy_buffer_size")
                    elif location_type == "custom":
                        loc = website.get_custom_location(location_name)
                        loc.custom_config = location.get("custom_config")
                    if loc:
                        loc.location_type = location_type
                        path_url = location.get("path_url", "/")
                        if loc.location_type == LocationType.PROXY:
                            # proxy location needs / (as we append slash to the backend server too)
                            # and nginx always redirects to the same location with slash
                            # this way, requests will go to backend servers without double slashes...etc
                            if not path_url.endswith("/"):
                                path_url += "/"
                        else:
                            # for other locations, slash is not required
                            if path_url != "/":
                                path_url = path_url.rstrip("/")
                        loc.path_url = path_url
                        loc.force_https = location.get("force_https")
                        loc.is_auth = location.get("is_auth", False)
                        loc.is_admin = location.get("is_admin", False)
                        loc.is_package_authorized = location.get("is_package_authorized", False)
                        loc.package_name = self.package.name
                if write_config:
                    website.configure(generate_certificates=self.nginx.cert)
class Package:
    def __init__(
        self,
        path,
        default_domain,
        default_email,
        giturl="",
        kwargs=None,
        admins=None,
        default_acme_server_type=AcmeServer.LETSENCRYPT,
        default_acme_server_url=None,
    ):
        self.path = path
        self.giturl = giturl
        self._config = None
        self.name = j.sals.fs.basename(path.rstrip("/"))
        self.nginx_config = NginxPackageConfig(self)
        self._module = None
        self.default_domain = default_domain
        self.default_email = default_email
        self.default_acme_server_type = default_acme_server_type
        self.default_acme_server_url = default_acme_server_url
        self.kwargs = kwargs or {}
        self.admins = admins or []
    def _load_files(self, dir_path):
        for file_path in j.sals.fs.walk_files(dir_path, recursive=False):
            file_name = j.sals.fs.basename(file_path)
            if file_name.endswith(".py"):
                name = f"{self.name}_{file_name[:-3]}"
                yield dict(name=name, path=file_path)
    def load_config(self):
        return toml.load(self.package_config_path)
    @property
    def package_config_path(self):
        return j.sals.fs.join_paths(self.path, "package.toml")
    @property
    def package_module_path(self):
        return j.sals.fs.join_paths(self.path, "package.py")
    @property
    def module(self):
        if self._module is None:
            package_file_path = j.sals.fs.join_paths(self.path, "package.py")
            if j.sals.fs.exists(package_file_path):
                module = imp.load_source(self.name, package_file_path)
                if not hasattr(module, self.name):
                    raise j.exceptions.Halt(f"missing class ({self.name}) in the package file")
                self._module = getattr(module, self.name)()
        return self._module
    @property
    def base_url(self):
        return j.sals.fs.join_paths("/", self.name)
    @property
    def config(self):
        if not self._config:
            self._config = self.load_config()
        return self._config
    @property
    def ui_name(self):
        return self.config.get("ui_name", self.name)
    @property
    def actors_dir(self):
        actors_dir = j.sals.fs.join_paths(self.path, self.config.get("actors_dir", "actors"))
        if j.sals.fs.exists(actors_dir):
            return actors_dir
    @property
    def chats_dir(self):
        chats_dir = j.sals.fs.join_paths(self.path, self.config.get("chats_dir", "chats"))
        if j.sals.fs.exists(chats_dir):
            return chats_dir
    @property
    def services_dir(self):
        services_dir = j.sals.fs.join_paths(self.path, self.config.get("services_dir", "services"))
        if j.sals.fs.exists(services_dir):
            return services_dir
    @property
    def static_dirs(self):
        return self.config.get("static_dirs", [])
    @property
    def bottle_servers(self):
        return self.config.get("bottle_servers", [])
    @property
    def actors(self):
        return self._load_files(self.actors_dir)
    @property
    def services(self):
        return self._load_files(self.services_dir)
    def resolve_staticdir_location(self, static_dir):
        """Resolves path for static location in case we need it
        absoulute or not
        static_dir.absolute_path true it will return the path directly
        if false will be relative to the path
        Args:
            static_dir (str): package.toml static dirs category
        Returns:
            str: package path
        """
        path_location = static_dir.get("path_location")
        absolute_path = static_dir.get("absolute_path", False)
        if absolute_path:
            return j.sals.fs.expanduser(path_location)
        return j.sals.fs.expanduser(j.sals.fs.join_paths(self.path, path_location))
    def get_bottle_server(self, file_path, host, port):
        module = imp.load_source(file_path[:-3], file_path)
        return WSGIServer((host, port), StripPathMiddleware(module.app))
    def get_package_bottle_app(self, file_path):
        module = imp.load_source(file_path[:-3], file_path)
        return module.app
    def preinstall(self):
        if self.module and hasattr(self.module, "preinstall"):
            self.module.preinstall()
    def install(self, **kwargs):
        if self.module and hasattr(self.module, "install"):
            self.module.install(**kwargs)
    def uninstall(self):
        if self.module and hasattr(self.module, "uninstall"):
            self.module.uninstall()
    def start(self):
        if self.module and hasattr(self.module, "start"):
            self.module.start(**self.kwargs)
    def stop(self):
        if self.module and hasattr(self.module, "stop"):
            self.module.stop()
    def restart(self):
        if self.module:
            self.module.stop()
            self.module.start()
    def exists(self):
        return j.sals.fs.exists(self.package_config_path)
    def is_valid(self):
        # more constraints, but for now let's say it's not ok if the main files don't exist
        return self.exists() and not self.is_excluded()
    def is_excluded(self):
        return self.config.get("excluded", False) == True
class PackageManager(Base):
    packages = fields.Typed(dict, default=DEFAULT_PACKAGES.copy())
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._threebot = None
    @property
    def threebot(self):
        if self._threebot is None:
            self._threebot = j.servers.threebot.get()
        return self._threebot
    def get(self, package_name):
        if package_name in self.packages:
            package_path = self.packages[package_name]["path"]
            package_giturl = self.packages[package_name]["giturl"]
            package_kwargs = self.packages[package_name].get("kwargs", {})
            package_admins = self.packages[package_name].get("admins", [])
            return Package(
                path=package_path,
                default_domain=self.threebot.domain,
                default_email=self.threebot.email,
                default_acme_server_type=self.threebot.acme_server_type,
                default_acme_server_url=self.threebot.acme_server_url,
                giturl=package_giturl,
                kwargs=package_kwargs,
                admins=package_admins,
            )
    def get_packages(self):
        all_packages = []
        # Add installed packages including outer packages
        for pkg in self.packages:
            package = self.get(pkg)
            if package and package.is_valid():
                if j.sals.fs.exists(package.path):
                    chatflows = True if package.chats_dir else False
                    all_packages.append(
                        {
                            "name": pkg,
                            "path": package.path,
                            "giturl": package.giturl,
                            "system_package": pkg in DEFAULT_PACKAGES.keys(),
                            "installed": True,
                            "frontend": package.config.get("frontend", False),
                            "chatflows": chatflows,
                            "ui_name": package.ui_name,
                        }
                    )
                else:
                    j.logger.error(f"path {package.path} for {pkg} doesn't exist anymore")
            else:
                j.logger.error("pkg {pkg} is in self.packages but it's None")
        # Add uninstalled sdk packages under j.packages
        for path in set(pkgnamespace.__path__):
            for pkg in os.listdir(path):
                pkg_path = j.sals.fs.join_paths(path, pkg)
                pkgtoml_path = j.sals.fs.join_paths(pkg_path, "package.toml")
                ui_name = pkg
                excluded = False
                with open(pkgtoml_path) as f:
                    conf = j.data.serializers.toml.loads(f.read())
                    ui_name = conf.get("ui_name", pkg)
                    excluded = conf.get("excluded", False)
                if pkg not in self.packages and j.sals.fs.exists(pkgtoml_path) and not excluded:
                    all_packages.append(
                        {
                            "name": pkg,
                            "path": j.sals.fs.dirname(getattr(j.packages, pkg).__file__),
                            "giturl": "",
                            "system_package": pkg in DEFAULT_PACKAGES.keys(),
                            "installed": False,
                            "ui_name": ui_name,
                        }
                    )
        return all_packages
    def list_all(self):
        return list(self.packages.keys())
    def add(self, path: str = None, giturl: str = None, **kwargs):
        # first check if public repo
        # TODO: Check if package already exists
        if not any([path, giturl]) or all([path, giturl]):
            raise j.exceptions.Value("either path or giturl is required")
        pkg_name = ""
        if giturl:
            url = urlparse(giturl)
            url_parts = url.path.lstrip("/").split("/")
            if len(url_parts) == 2:
                pkg_name = url_parts[1].strip("/")
                j.logger.debug(
                    f"user didn't pass a URL containing branch {giturl}, try to guess (master, main, development) in order"
                )
                if j.tools.http.get(f"{giturl}/tree/master").status_code == 200:
                    url_parts.extend(["tree", "master"])
                elif j.tools.http.get(f"{giturl}/tree/main").status_code == 200:
                    url_parts.extend(["tree", "main"])
                elif j.tools.http.get(f"{giturl}/tree/development").status_code == 200:
                    url_parts.extend(["tree", "development"])
                else:
                    raise j.exceptions.Value(f"couldn't guess the branch for {giturl}")
            else:
                pkg_name = url_parts[-1].strip("/")
            if len(url_parts) < 4:
                raise j.exceptions.Value(f"invalid git URL {giturl}")
            org, repo, _, branch = url_parts[:4]
            repo_dir = f"{org}_{repo}_{pkg_name}_{branch}"
            repo_path = j.sals.fs.join_paths(DOWNLOADED_PACKAGES_PATH, repo_dir)
            repo_url = f"{url.scheme}://{url.hostname}/{org}/{repo}"
            # delete repo dir if exists
            j.sals.fs.rmtree(repo_path)
            j.tools.git.clone_repo(url=repo_url, dest=repo_path, branch_or_tag=branch)
            toml_paths = list(
                j.sals.fs.walk(repo_path, "*", filter_fun=lambda x: str(x).endswith(f"{pkg_name}/package.toml"))
            )
            if not toml_paths:
                raise j.exceptions.Value(f"couldn't find {pkg_name}/package.toml in {repo_path}")
            path_for_package_toml = toml_paths[0]
            package_path = j.sals.fs.parent(path_for_package_toml)
            path = package_path
        admins = kwargs.pop("admins", [])
        package = Package(
            path=path,
            default_domain=self.threebot.domain,
            default_email=self.threebot.email,
            giturl=giturl,
            kwargs=kwargs,
            admins=admins,
        )
        # TODO: adding under the same name if same path and same giturl should be fine, no?
        # if package.name in self.packages:
        #     raise j.exceptions.Value(f"Package with name {package.name} already exists")
        # execute package install method
        package.install(**kwargs)
        # install package if threebot is started
        if self.threebot.started:
            self.install(package)
            self.threebot.nginx.reload()
        self.packages[package.name] = {
            "name": package.name,
            "path": package.path,
            "giturl": package.giturl,
            "kwargs": package.kwargs,
            "admins": package.admins,
            "ui_name": package.ui_name,
        }
        self.save()
        # Return updated package info
        return {package.name: self.packages[package.name]}
    def delete(self, package_name):
        if package_name in DEFAULT_PACKAGES:
            raise j.exceptions.Value("cannot delete default packages")
        package = self.get(package_name)
        if not package:
            raise j.exceptions.NotFound(f"{package_name} package not found")
        # remove bottle servers
        rack_servers = list(self.threebot.rack._servers)
        for bottle_server in rack_servers:
            if bottle_server.startswith(f"{package_name}_"):
                self.threebot.rack.remove(bottle_server)
        # stop background services
        if package.services_dir:
            for service in package.services:
                self.threebot.services.stop_service(service["name"])
        if self.threebot.started:
            # unregister gedis actors
            gedis_actors = list(self.threebot.gedis._loaded_actors.keys())
            for actor in gedis_actors:
                if actor.startswith(f"{package_name}_"):
                    self.threebot.gedis._system_actor.unregister_actor(actor)
            # unload chats
            try:
                if package.chats_dir:
                    self.threebot.chatbot.unload(package.chats_dir)
            except Exception as e:
                j.logger.warning(f"Couldn't unload the chats of package {package_name}, this is the exception {str(e)}")
            # reload nginx
            self.threebot.nginx.reload()
        # execute package uninstall method
        package.uninstall()
        self.packages.pop(package_name)
        self.save()
    def install(self, package):
        """install and apply package configrations
        Args:
            package ([package object]): get package object using [self.get(package_name)]
        Returns:
            [dict]: [package info]
        """
        sys.path.append(package.path + "/../")  # TODO to be changed
        package.preinstall()
        for static_dir in package.static_dirs:
            path = package.resolve_staticdir_location(static_dir)
            if not j.sals.fs.exists(path):
                raise j.exceptions.NotFound(f"Cannot find static dir {path}")
        # add bottle servers
        # we first merge all apps of a package into a single app
        # then mount this app on threebot main app
        # this will work with multiple non-standalone apps
        package_app = j.servers.appserver.make_main_app()
        for bottle_server in package.bottle_servers:
            path = j.sals.fs.join_paths(package.path, bottle_server["file_path"])
            if not j.sals.fs.exists(path):
                raise j.exceptions.NotFound(f"Cannot find bottle server path {path}")
            standalone = bottle_server.get("standalone", False)
            if standalone:
                bottle_wsgi_server = package.get_bottle_server(path, bottle_server["host"], bottle_server["port"])
                self.threebot.rack.add(f"{package.name}_{bottle_server['name']}", bottle_wsgi_server)
            else:
                bottle_app = package.get_package_bottle_app(path)
                package_app.merge(bottle_app)
        if package_app.routes:
            j.logger.info(f"registering {package.name} package app")
            self.threebot.mainapp.mount(f"/{package.name}", package_app)
        # register gedis actors
        if package.actors_dir:
            for actor in package.actors:
                self.threebot.gedis._system_actor.register_actor(actor["name"], actor["path"], force_reload=True)
        # add chatflows actors
        if package.chats_dir:
            self.threebot.chatbot.load(package.chats_dir)
        # start background services
        if package.services_dir:
            for service in package.services:
                self.threebot.services.add_service(service["name"], service["path"])
        j.logger.info(f"starting rack")
        # start servers
        self.threebot.rack.start()
        j.logger.info(f"applying nginx config")
        # apply nginx configuration
        package.nginx_config.apply()
        j.logger.info(f"starting package")
        # execute package start method
        package.start()
        j.logger.info(f"reloading gedis")
        self.threebot.gedis_http.client.reload()
        j.logger.info(f"reloading nginx")
        self.threebot.nginx.reload()
    def reload(self, package_name):
        if self.threebot.started:
            package = self.get(package_name)
            if not package:
                raise j.exceptions.NotFound(f"{package_name} package not found")
            if package.services_dir:
                for service in package.services:
                    self.threebot.services.stop_service(service["name"])
            self.install(package)
            self.threebot.nginx.reload()
            self.save()
        else:
            raise j.exceptions.Runtime("Can't reload package. Threebot server is not started")
        # Return updated package info
        return {package.name: self.packages[package.name]}
    def _install_all(self):
        """Install and apply all the packages configurations
        This method shall not be called directly from the shell,
        it must be called only from the code on the running Gedis server
        """
        all_packages = self.list_all()
        for package in all_packages:
            if package not in DEFAULT_PACKAGES:
                j.logger.info(f"Configuring package {package}")
                pkg = self.get(package)
                if not pkg:
                    j.logger.error(f"can't get package {package}")
                else:
                    if pkg.path and pkg.is_valid():
                        self.install(pkg)
                    else:
                        j.logger.error(f"package {package} was installed before but {pkg.path} doesn't exist anymore.")
    def scan_packages_paths_in_dir(self, path):
        """Scans all packages in a path in any level and returns list of package paths
        Args:
            path (str): root path that has packages on some levels
        Returns:
            List[str]: list of all packages available under the path
        """
        filterfun = lambda x: str(x).endswith("package.toml")
        pkgtoml_paths = j.sals.fs.walk(path, filter_fun=filterfun)
        pkgs_paths = list(map(lambda x: x.replace("/package.toml", ""), pkgtoml_paths))
        return pkgs_paths
    def scan_packages_in_dir(self, path):
        """Gets a dict from packages names to packages paths existing under a path that may have jumpscale packages at any level.
        Args:
            path (str): root path that has packages on some levels
        Returns:
            Dict[package_name, package_path]: dict of all packages available under the path
        """
        pkgname_to_path = {}
        for p in self.scan_packages_paths_in_dir(path):
            basename = j.sals.fs.basename(p).strip()
            if basename:
                pkgname_to_path[basename] = p
        return pkgname_to_path
class ThreebotServer(Base):
    _package_manager = fields.Factory(PackageManager)
    domain = fields.String()
    email = fields.String()
    acme_server_type = fields.Enum(AcmeServer)
    acme_server_url = fields.URL()
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._rack = None
        self._gedis = None
        self._db = None
        self._gedis_http = None
        self._services = None
        self._packages = None
        self._started = False
        self._nginx = None
        self._redis = None
        self.rack.add(GEDIS, self.gedis)
        self.rack.add(GEDIS_HTTP, self.gedis_http.gevent_server)
        self.rack.add(SERVICE_MANAGER, self.services)
    def is_running(self):
        nginx_running = self.nginx.is_running()
        redis_running = self.redis.cmd.is_running() or j.sals.nettools.wait_connection_test(
            "127.0.0.1", 6379, timeout=1
        )
        gedis_running = j.sals.nettools.wait_connection_test("127.0.0.1", 16000, timeout=1)
        return nginx_running and redis_running and gedis_running
    @property
    def started(self):
        return self._started
    @property
    def nginx(self):
        if self._nginx is None:
            self._nginx = j.tools.nginx.get("default")
        return self._nginx
    @property
    def redis(self):
        if self._redis is None:
            self._redis = j.tools.redis.get("default")
        return self._redis
    @property
    def db(self):
        if self._db is None:
            self._db = j.core.db
        return self._db
    @property
    def rack(self):
        if self._rack is None:
            self._rack = j.servers.rack
        return self._rack
    @property
    def gedis(self):
        if self._gedis is None:
            self._gedis = j.servers.gedis.get("threebot")
        return self._gedis
    @property
    def gedis_http(self):
        if self._gedis_http is None:
            self._gedis_http = j.servers.gedis_http.get("threebot")
        return self._gedis_http
    @property
    def services(self):
        if self._services is None:
            self._services = j.tools.servicemanager.get("threebot")
        return self._services
    @property
    def chatbot(self):
        return self.gedis._loaded_actors.get("chatflows_chatbot")
    @property
    def packages(self):
        if self._packages is None:
            self._packages = self._package_manager.get(self.instance_name)
        return self._packages
    def check_dependencies(self):
        install_msg = "Visit https://github.com/threefoldtech/js-sdk/blob/development/docs/wiki/quick_start.md for installation guide"
        if not self.nginx.installed:
            raise j.exceptions.NotFound(f"nginx is not installed.\n{install_msg}")
        ret = shutil.which("certbot")
        if not ret:
            raise j.exceptions.NotFound(f"certbot is not installed.\n{install_msg}")
        rc, out, err = j.sals.process.execute("certbot plugins")
        if "* nginx" not in out:
            raise j.exceptions.NotFound(f"python-certbot-nginx is not installed.\n{install_msg}")
        if not self.redis.installed:
            raise j.exceptions.NotFound(f"redis is not installed.\n{install_msg}")
        ret = shutil.which("tmux")
        if not ret:
            raise j.exceptions.NotFound(f"tmux is not installed.\n{install_msg}")
        ret = shutil.which("git")
        if not ret:
            raise j.exceptions.NotFound(f"git is not installed.\n{install_msg}")
    def start(self, wait: bool = False, cert: bool = True):
        # start default servers in the rack
        # handle signals
        for signal_type in (signal.SIGTERM, signal.SIGINT, signal.SIGKILL):
            gevent.signal_handler(signal_type, self.stop)
        # mark app as started
        if self.is_running():
            return
        self.check_dependencies()
        self.redis.start()
        self.nginx.start()
        j.sals.nginx.get(self.nginx.server_name).cert = cert
        self.mainapp = j.servers.appserver.make_main_app()
        self.rack.start()
        j.logger.register(f"threebot_{self.instance_name}")
        # add default packages
        for package_name in DEFAULT_PACKAGES:
            j.logger.info(f"Configuring package {package_name}")
            try:
                package = self.packages.get(package_name)
                self.packages.install(package)
            except Exception as e:
                self.stop()
                raise j.core.exceptions.Runtime(
                    f"Error happened during getting or installing {package_name} package, the detailed error is {str(e)}"
                ) from e
        # install all package
        j.logger.info("Adding packages")
        self.packages._install_all()
        j.logger.info("jsappserver")
        self.jsappserver = WSGIServer(("localhost", 31000), apply_main_middlewares(self.mainapp))
        j.logger.info("rack add")
        self.rack.add(f"appserver", self.jsappserver)
        j.logger.info("Reloading nginx")
        self.nginx.reload()
        # mark server as started
        self._started = True
        j.logger.info(f"routes: {self.mainapp.routes}")
        j.logger.info(f"Threebot is running at http://localhost:{PORTS.HTTP} and https://localhost:{PORTS.HTTPS}")
        self.rack.start(wait=wait)  # to keep the server running
    def stop(self):
        server_packages = self.packages.list_all()
        for package_name in server_packages:
            package = self.packages.get(package_name)
            package.stop()
        self.nginx.stop()
        # mark app as stopped, do this before stopping redis
        j.logger.unregister()
        self.rack.stop()
        self.redis.stop()
        self._started = False
Classes
class NginxPackageConfig (package)- 
Expand source code
class NginxPackageConfig: def __init__(self, package): self.package = package self.nginx = j.sals.nginx.get("main") @property def default_config(self): default_server = { "name": "default", "ports": self.package.config.get("ports"), "locations": self.package.config.get("locations", []), "domain": self.package.default_domain, "letsencryptemail": self.package.default_email, "acme_server_type": self.package.default_acme_server_type, "acme_server_url": self.package.default_acme_server_url, } is_auth = self.package.config.get("is_auth", True) is_admin = self.package.config.get("is_admin", True) is_package_authorized = self.package.config.get("is_package_authorized", False) for static_dir in self.package.static_dirs: path_url = j.data.text.removeprefix(static_dir.get("path_url"), "/") default_server["locations"].append( { "is_auth": static_dir.get("is_auth", is_auth), "is_admin": static_dir.get("is_admin", is_admin), "is_package_authorized": static_dir.get("is_package_authorized", is_package_authorized), "type": "static", "name": static_dir.get("name"), "spa": static_dir.get("spa"), "index": static_dir.get("index"), "path_url": j.sals.fs.join_paths(self.package.base_url, path_url), "path_location": self.package.resolve_staticdir_location(static_dir), "force_https": self.package.config.get("force_https", True), } ) for bottle_server in self.package.bottle_servers: path_url = j.data.text.removeprefix(bottle_server.get("path_url"), "/") if hasattr(bottle_server, "standalone") and bottle_server.standalone: default_server["locations"].append( { "is_auth": bottle_server.get("is_auth", is_auth), "is_admin": bottle_server.get("is_admin", is_admin), "is_package_authorized": bottle_server.get("is_package_authorized", is_package_authorized), "type": "proxy", "name": bottle_server.get("name"), "host": bottle_server.get("host"), "port": bottle_server.get("port"), "path_url": j.sals.fs.join_paths(self.package.base_url,), "path_dest": bottle_server.get("path_dest"), "websocket": bottle_server.get("websocket"), "force_https": self.package.config.get("force_https", True), } ) else: path_url = j.data.text.removeprefix(bottle_server.get("path_url"), "/") default_server["locations"].append( { "is_auth": bottle_server.get("is_auth", is_auth), "is_admin": bottle_server.get("is_admin", is_admin), "is_package_authorized": bottle_server.get("is_package_authorized", is_package_authorized), "type": "proxy", "name": bottle_server.get("name"), "host": "0.0.0.0", "port": 31000, "path_url": j.sals.fs.join_paths(self.package.base_url, path_url), "path_dest": f"/{self.package.name}{bottle_server.get('path_dest')}", "websocket": bottle_server.get("websocket"), "force_https": self.package.config.get("force_https", True), } ) if self.package.actors_dir: default_server["locations"].append( { "is_auth": is_auth, "is_admin": is_admin, "is_package_authorized": is_package_authorized, "type": "proxy", "name": "actors", "host": GEDIS_HTTP_HOST, "port": GEDIS_HTTP_PORT, "path_url": j.sals.fs.join_paths(self.package.base_url, "actors"), "path_dest": self.package.base_url, "force_https": self.package.config.get("force_https", True), } ) if self.package.chats_dir: default_server["locations"].append( { "is_auth": is_auth, "is_admin": is_admin, "is_package_authorized": is_package_authorized, "type": "proxy", "name": "chats", "host": CHATFLOW_SERVER_HOST, "port": CHATFLOW_SERVER_PORT, "path_url": j.sals.fs.join_paths(self.package.base_url, "chats"), "path_dest": f"/chatflows{self.package.base_url}/chats", # TODO: temperoary fix for auth package "force_https": self.package.config.get("force_https", True), } ) return [default_server] def apply(self, write_config=True): default_ports = [PORTS.HTTP, PORTS.HTTPS] servers = self.default_config + self.package.config.get("servers", []) for server in servers: ports = server.get("ports", default_ports) or default_ports for port in ports: server_name = server.get("name") if server_name != "default": server_name = f"{self.package.name}_{server_name}" website = self.nginx.get_website(server_name, port=port) website.ssl = server.get("ssl", port == PORTS.HTTPS) website.includes = server.get("includes", []) website.domain = server.get("domain", self.default_config[0].get("domain")) website.letsencryptemail = server.get( "letsencryptemail", self.default_config[0].get("letsencryptemail") ) website.acme_server_type = server.get( "acme_server_type", self.default_config[0].get("acme_server_type") ) website.acme_server_url = server.get("acme_server_url", self.default_config[0].get("acme_server_url")) if server.get("key_path"): website.key_path = server["key_path"] if server.get("cert_path"): website.cert_path = server["cert_path"] if server.get("fullchain_path"): website.fullchain_path = server["fullchain_path"] for location in server.get("locations", []): loc = None location_name = location.get("name") location_name = f"{self.package.name}_{location_name}" location_type = location.get("type", "static") if location_type == "static": loc = website.get_static_location(location_name) loc.spa = location.get("spa", False) loc.index = location.get("index") loc.path_location = location.get("path_location") elif location_type == "proxy": loc = website.get_proxy_location(location_name) loc.scheme = location.get("scheme", "http") loc.host = location.get("host") loc.port = location.get("port", PORTS.HTTP) loc.path_dest = location.get("path_dest", "") loc.websocket = location.get("websocket", False) loc.proxy_buffering = location.get("proxy_buffering", "") loc.proxy_buffers = location.get("proxy_buffers") loc.proxy_buffer_size = location.get("proxy_buffer_size") elif location_type == "custom": loc = website.get_custom_location(location_name) loc.custom_config = location.get("custom_config") if loc: loc.location_type = location_type path_url = location.get("path_url", "/") if loc.location_type == LocationType.PROXY: # proxy location needs / (as we append slash to the backend server too) # and nginx always redirects to the same location with slash # this way, requests will go to backend servers without double slashes...etc if not path_url.endswith("/"): path_url += "/" else: # for other locations, slash is not required if path_url != "/": path_url = path_url.rstrip("/") loc.path_url = path_url loc.force_https = location.get("force_https") loc.is_auth = location.get("is_auth", False) loc.is_admin = location.get("is_admin", False) loc.is_package_authorized = location.get("is_package_authorized", False) loc.package_name = self.package.name if write_config: website.configure(generate_certificates=self.nginx.cert)Instance variables
var default_config- 
Expand source code
@property def default_config(self): default_server = { "name": "default", "ports": self.package.config.get("ports"), "locations": self.package.config.get("locations", []), "domain": self.package.default_domain, "letsencryptemail": self.package.default_email, "acme_server_type": self.package.default_acme_server_type, "acme_server_url": self.package.default_acme_server_url, } is_auth = self.package.config.get("is_auth", True) is_admin = self.package.config.get("is_admin", True) is_package_authorized = self.package.config.get("is_package_authorized", False) for static_dir in self.package.static_dirs: path_url = j.data.text.removeprefix(static_dir.get("path_url"), "/") default_server["locations"].append( { "is_auth": static_dir.get("is_auth", is_auth), "is_admin": static_dir.get("is_admin", is_admin), "is_package_authorized": static_dir.get("is_package_authorized", is_package_authorized), "type": "static", "name": static_dir.get("name"), "spa": static_dir.get("spa"), "index": static_dir.get("index"), "path_url": j.sals.fs.join_paths(self.package.base_url, path_url), "path_location": self.package.resolve_staticdir_location(static_dir), "force_https": self.package.config.get("force_https", True), } ) for bottle_server in self.package.bottle_servers: path_url = j.data.text.removeprefix(bottle_server.get("path_url"), "/") if hasattr(bottle_server, "standalone") and bottle_server.standalone: default_server["locations"].append( { "is_auth": bottle_server.get("is_auth", is_auth), "is_admin": bottle_server.get("is_admin", is_admin), "is_package_authorized": bottle_server.get("is_package_authorized", is_package_authorized), "type": "proxy", "name": bottle_server.get("name"), "host": bottle_server.get("host"), "port": bottle_server.get("port"), "path_url": j.sals.fs.join_paths(self.package.base_url,), "path_dest": bottle_server.get("path_dest"), "websocket": bottle_server.get("websocket"), "force_https": self.package.config.get("force_https", True), } ) else: path_url = j.data.text.removeprefix(bottle_server.get("path_url"), "/") default_server["locations"].append( { "is_auth": bottle_server.get("is_auth", is_auth), "is_admin": bottle_server.get("is_admin", is_admin), "is_package_authorized": bottle_server.get("is_package_authorized", is_package_authorized), "type": "proxy", "name": bottle_server.get("name"), "host": "0.0.0.0", "port": 31000, "path_url": j.sals.fs.join_paths(self.package.base_url, path_url), "path_dest": f"/{self.package.name}{bottle_server.get('path_dest')}", "websocket": bottle_server.get("websocket"), "force_https": self.package.config.get("force_https", True), } ) if self.package.actors_dir: default_server["locations"].append( { "is_auth": is_auth, "is_admin": is_admin, "is_package_authorized": is_package_authorized, "type": "proxy", "name": "actors", "host": GEDIS_HTTP_HOST, "port": GEDIS_HTTP_PORT, "path_url": j.sals.fs.join_paths(self.package.base_url, "actors"), "path_dest": self.package.base_url, "force_https": self.package.config.get("force_https", True), } ) if self.package.chats_dir: default_server["locations"].append( { "is_auth": is_auth, "is_admin": is_admin, "is_package_authorized": is_package_authorized, "type": "proxy", "name": "chats", "host": CHATFLOW_SERVER_HOST, "port": CHATFLOW_SERVER_PORT, "path_url": j.sals.fs.join_paths(self.package.base_url, "chats"), "path_dest": f"/chatflows{self.package.base_url}/chats", # TODO: temperoary fix for auth package "force_https": self.package.config.get("force_https", True), } ) return [default_server] 
Methods
def apply(self, write_config=True)- 
Expand source code
def apply(self, write_config=True): default_ports = [PORTS.HTTP, PORTS.HTTPS] servers = self.default_config + self.package.config.get("servers", []) for server in servers: ports = server.get("ports", default_ports) or default_ports for port in ports: server_name = server.get("name") if server_name != "default": server_name = f"{self.package.name}_{server_name}" website = self.nginx.get_website(server_name, port=port) website.ssl = server.get("ssl", port == PORTS.HTTPS) website.includes = server.get("includes", []) website.domain = server.get("domain", self.default_config[0].get("domain")) website.letsencryptemail = server.get( "letsencryptemail", self.default_config[0].get("letsencryptemail") ) website.acme_server_type = server.get( "acme_server_type", self.default_config[0].get("acme_server_type") ) website.acme_server_url = server.get("acme_server_url", self.default_config[0].get("acme_server_url")) if server.get("key_path"): website.key_path = server["key_path"] if server.get("cert_path"): website.cert_path = server["cert_path"] if server.get("fullchain_path"): website.fullchain_path = server["fullchain_path"] for location in server.get("locations", []): loc = None location_name = location.get("name") location_name = f"{self.package.name}_{location_name}" location_type = location.get("type", "static") if location_type == "static": loc = website.get_static_location(location_name) loc.spa = location.get("spa", False) loc.index = location.get("index") loc.path_location = location.get("path_location") elif location_type == "proxy": loc = website.get_proxy_location(location_name) loc.scheme = location.get("scheme", "http") loc.host = location.get("host") loc.port = location.get("port", PORTS.HTTP) loc.path_dest = location.get("path_dest", "") loc.websocket = location.get("websocket", False) loc.proxy_buffering = location.get("proxy_buffering", "") loc.proxy_buffers = location.get("proxy_buffers") loc.proxy_buffer_size = location.get("proxy_buffer_size") elif location_type == "custom": loc = website.get_custom_location(location_name) loc.custom_config = location.get("custom_config") if loc: loc.location_type = location_type path_url = location.get("path_url", "/") if loc.location_type == LocationType.PROXY: # proxy location needs / (as we append slash to the backend server too) # and nginx always redirects to the same location with slash # this way, requests will go to backend servers without double slashes...etc if not path_url.endswith("/"): path_url += "/" else: # for other locations, slash is not required if path_url != "/": path_url = path_url.rstrip("/") loc.path_url = path_url loc.force_https = location.get("force_https") loc.is_auth = location.get("is_auth", False) loc.is_admin = location.get("is_admin", False) loc.is_package_authorized = location.get("is_package_authorized", False) loc.package_name = self.package.name if write_config: website.configure(generate_certificates=self.nginx.cert) 
 class Package (path, default_domain, default_email, giturl='', kwargs=None, admins=None, default_acme_server_type=AcmeServer.LETSENCRYPT, default_acme_server_url=None)- 
Expand source code
class Package: def __init__( self, path, default_domain, default_email, giturl="", kwargs=None, admins=None, default_acme_server_type=AcmeServer.LETSENCRYPT, default_acme_server_url=None, ): self.path = path self.giturl = giturl self._config = None self.name = j.sals.fs.basename(path.rstrip("/")) self.nginx_config = NginxPackageConfig(self) self._module = None self.default_domain = default_domain self.default_email = default_email self.default_acme_server_type = default_acme_server_type self.default_acme_server_url = default_acme_server_url self.kwargs = kwargs or {} self.admins = admins or [] def _load_files(self, dir_path): for file_path in j.sals.fs.walk_files(dir_path, recursive=False): file_name = j.sals.fs.basename(file_path) if file_name.endswith(".py"): name = f"{self.name}_{file_name[:-3]}" yield dict(name=name, path=file_path) def load_config(self): return toml.load(self.package_config_path) @property def package_config_path(self): return j.sals.fs.join_paths(self.path, "package.toml") @property def package_module_path(self): return j.sals.fs.join_paths(self.path, "package.py") @property def module(self): if self._module is None: package_file_path = j.sals.fs.join_paths(self.path, "package.py") if j.sals.fs.exists(package_file_path): module = imp.load_source(self.name, package_file_path) if not hasattr(module, self.name): raise j.exceptions.Halt(f"missing class ({self.name}) in the package file") self._module = getattr(module, self.name)() return self._module @property def base_url(self): return j.sals.fs.join_paths("/", self.name) @property def config(self): if not self._config: self._config = self.load_config() return self._config @property def ui_name(self): return self.config.get("ui_name", self.name) @property def actors_dir(self): actors_dir = j.sals.fs.join_paths(self.path, self.config.get("actors_dir", "actors")) if j.sals.fs.exists(actors_dir): return actors_dir @property def chats_dir(self): chats_dir = j.sals.fs.join_paths(self.path, self.config.get("chats_dir", "chats")) if j.sals.fs.exists(chats_dir): return chats_dir @property def services_dir(self): services_dir = j.sals.fs.join_paths(self.path, self.config.get("services_dir", "services")) if j.sals.fs.exists(services_dir): return services_dir @property def static_dirs(self): return self.config.get("static_dirs", []) @property def bottle_servers(self): return self.config.get("bottle_servers", []) @property def actors(self): return self._load_files(self.actors_dir) @property def services(self): return self._load_files(self.services_dir) def resolve_staticdir_location(self, static_dir): """Resolves path for static location in case we need it absoulute or not static_dir.absolute_path true it will return the path directly if false will be relative to the path Args: static_dir (str): package.toml static dirs category Returns: str: package path """ path_location = static_dir.get("path_location") absolute_path = static_dir.get("absolute_path", False) if absolute_path: return j.sals.fs.expanduser(path_location) return j.sals.fs.expanduser(j.sals.fs.join_paths(self.path, path_location)) def get_bottle_server(self, file_path, host, port): module = imp.load_source(file_path[:-3], file_path) return WSGIServer((host, port), StripPathMiddleware(module.app)) def get_package_bottle_app(self, file_path): module = imp.load_source(file_path[:-3], file_path) return module.app def preinstall(self): if self.module and hasattr(self.module, "preinstall"): self.module.preinstall() def install(self, **kwargs): if self.module and hasattr(self.module, "install"): self.module.install(**kwargs) def uninstall(self): if self.module and hasattr(self.module, "uninstall"): self.module.uninstall() def start(self): if self.module and hasattr(self.module, "start"): self.module.start(**self.kwargs) def stop(self): if self.module and hasattr(self.module, "stop"): self.module.stop() def restart(self): if self.module: self.module.stop() self.module.start() def exists(self): return j.sals.fs.exists(self.package_config_path) def is_valid(self): # more constraints, but for now let's say it's not ok if the main files don't exist return self.exists() and not self.is_excluded() def is_excluded(self): return self.config.get("excluded", False) == TrueInstance variables
var actors- 
Expand source code
@property def actors(self): return self._load_files(self.actors_dir) var actors_dir- 
Expand source code
@property def actors_dir(self): actors_dir = j.sals.fs.join_paths(self.path, self.config.get("actors_dir", "actors")) if j.sals.fs.exists(actors_dir): return actors_dir var base_url- 
Expand source code
@property def base_url(self): return j.sals.fs.join_paths("/", self.name) var bottle_servers- 
Expand source code
@property def bottle_servers(self): return self.config.get("bottle_servers", []) var chats_dir- 
Expand source code
@property def chats_dir(self): chats_dir = j.sals.fs.join_paths(self.path, self.config.get("chats_dir", "chats")) if j.sals.fs.exists(chats_dir): return chats_dir var config- 
Expand source code
@property def config(self): if not self._config: self._config = self.load_config() return self._config var module- 
Expand source code
@property def module(self): if self._module is None: package_file_path = j.sals.fs.join_paths(self.path, "package.py") if j.sals.fs.exists(package_file_path): module = imp.load_source(self.name, package_file_path) if not hasattr(module, self.name): raise j.exceptions.Halt(f"missing class ({self.name}) in the package file") self._module = getattr(module, self.name)() return self._module var package_config_path- 
Expand source code
@property def package_config_path(self): return j.sals.fs.join_paths(self.path, "package.toml") var package_module_path- 
Expand source code
@property def package_module_path(self): return j.sals.fs.join_paths(self.path, "package.py") var services- 
Expand source code
@property def services(self): return self._load_files(self.services_dir) var services_dir- 
Expand source code
@property def services_dir(self): services_dir = j.sals.fs.join_paths(self.path, self.config.get("services_dir", "services")) if j.sals.fs.exists(services_dir): return services_dir var static_dirs- 
Expand source code
@property def static_dirs(self): return self.config.get("static_dirs", []) var ui_name- 
Expand source code
@property def ui_name(self): return self.config.get("ui_name", self.name) 
Methods
def exists(self)- 
Expand source code
def exists(self): return j.sals.fs.exists(self.package_config_path) def get_bottle_server(self, file_path, host, port)- 
Expand source code
def get_bottle_server(self, file_path, host, port): module = imp.load_source(file_path[:-3], file_path) return WSGIServer((host, port), StripPathMiddleware(module.app)) def get_package_bottle_app(self, file_path)- 
Expand source code
def get_package_bottle_app(self, file_path): module = imp.load_source(file_path[:-3], file_path) return module.app def install(self, **kwargs)- 
Expand source code
def install(self, **kwargs): if self.module and hasattr(self.module, "install"): self.module.install(**kwargs) def is_excluded(self)- 
Expand source code
def is_excluded(self): return self.config.get("excluded", False) == True def is_valid(self)- 
Expand source code
def is_valid(self): # more constraints, but for now let's say it's not ok if the main files don't exist return self.exists() and not self.is_excluded() def load_config(self)- 
Expand source code
def load_config(self): return toml.load(self.package_config_path) def preinstall(self)- 
Expand source code
def preinstall(self): if self.module and hasattr(self.module, "preinstall"): self.module.preinstall() def resolve_staticdir_location(self, static_dir)- 
Resolves path for static location in case we need it absoulute or not
static_dir.absolute_path true it will return the path directly if false will be relative to the path
Args
static_dir:str- package.toml static dirs category
 
Returns
str- package path
 
Expand source code
def resolve_staticdir_location(self, static_dir): """Resolves path for static location in case we need it absoulute or not static_dir.absolute_path true it will return the path directly if false will be relative to the path Args: static_dir (str): package.toml static dirs category Returns: str: package path """ path_location = static_dir.get("path_location") absolute_path = static_dir.get("absolute_path", False) if absolute_path: return j.sals.fs.expanduser(path_location) return j.sals.fs.expanduser(j.sals.fs.join_paths(self.path, path_location)) def restart(self)- 
Expand source code
def restart(self): if self.module: self.module.stop() self.module.start() def start(self)- 
Expand source code
def start(self): if self.module and hasattr(self.module, "start"): self.module.start(**self.kwargs) def stop(self)- 
Expand source code
def stop(self): if self.module and hasattr(self.module, "stop"): self.module.stop() def uninstall(self)- 
Expand source code
def uninstall(self): if self.module and hasattr(self.module, "uninstall"): self.module.uninstall() 
 class PackageManager (*args, **kwargs)- 
A simple attribute-based namespace.
SimpleNamespace(**kwargs)
base class implementation for any class with fields which supports getting/setting raw data for any instance fields.
any instance can have an optional name and a parent.
class Person(Base): name = fields.String() age = fields.Float() p = Person(name="ahmed", age="19") print(p.name, p.age)Args
parent_:Base, optional- parent instance. Defaults to None.
 instance_name_:str, optional- instance name. Defaults to None.
 **values- any given field values to initiate the instance with
 
Expand source code
class PackageManager(Base): packages = fields.Typed(dict, default=DEFAULT_PACKAGES.copy()) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._threebot = None @property def threebot(self): if self._threebot is None: self._threebot = j.servers.threebot.get() return self._threebot def get(self, package_name): if package_name in self.packages: package_path = self.packages[package_name]["path"] package_giturl = self.packages[package_name]["giturl"] package_kwargs = self.packages[package_name].get("kwargs", {}) package_admins = self.packages[package_name].get("admins", []) return Package( path=package_path, default_domain=self.threebot.domain, default_email=self.threebot.email, default_acme_server_type=self.threebot.acme_server_type, default_acme_server_url=self.threebot.acme_server_url, giturl=package_giturl, kwargs=package_kwargs, admins=package_admins, ) def get_packages(self): all_packages = [] # Add installed packages including outer packages for pkg in self.packages: package = self.get(pkg) if package and package.is_valid(): if j.sals.fs.exists(package.path): chatflows = True if package.chats_dir else False all_packages.append( { "name": pkg, "path": package.path, "giturl": package.giturl, "system_package": pkg in DEFAULT_PACKAGES.keys(), "installed": True, "frontend": package.config.get("frontend", False), "chatflows": chatflows, "ui_name": package.ui_name, } ) else: j.logger.error(f"path {package.path} for {pkg} doesn't exist anymore") else: j.logger.error("pkg {pkg} is in self.packages but it's None") # Add uninstalled sdk packages under j.packages for path in set(pkgnamespace.__path__): for pkg in os.listdir(path): pkg_path = j.sals.fs.join_paths(path, pkg) pkgtoml_path = j.sals.fs.join_paths(pkg_path, "package.toml") ui_name = pkg excluded = False with open(pkgtoml_path) as f: conf = j.data.serializers.toml.loads(f.read()) ui_name = conf.get("ui_name", pkg) excluded = conf.get("excluded", False) if pkg not in self.packages and j.sals.fs.exists(pkgtoml_path) and not excluded: all_packages.append( { "name": pkg, "path": j.sals.fs.dirname(getattr(j.packages, pkg).__file__), "giturl": "", "system_package": pkg in DEFAULT_PACKAGES.keys(), "installed": False, "ui_name": ui_name, } ) return all_packages def list_all(self): return list(self.packages.keys()) def add(self, path: str = None, giturl: str = None, **kwargs): # first check if public repo # TODO: Check if package already exists if not any([path, giturl]) or all([path, giturl]): raise j.exceptions.Value("either path or giturl is required") pkg_name = "" if giturl: url = urlparse(giturl) url_parts = url.path.lstrip("/").split("/") if len(url_parts) == 2: pkg_name = url_parts[1].strip("/") j.logger.debug( f"user didn't pass a URL containing branch {giturl}, try to guess (master, main, development) in order" ) if j.tools.http.get(f"{giturl}/tree/master").status_code == 200: url_parts.extend(["tree", "master"]) elif j.tools.http.get(f"{giturl}/tree/main").status_code == 200: url_parts.extend(["tree", "main"]) elif j.tools.http.get(f"{giturl}/tree/development").status_code == 200: url_parts.extend(["tree", "development"]) else: raise j.exceptions.Value(f"couldn't guess the branch for {giturl}") else: pkg_name = url_parts[-1].strip("/") if len(url_parts) < 4: raise j.exceptions.Value(f"invalid git URL {giturl}") org, repo, _, branch = url_parts[:4] repo_dir = f"{org}_{repo}_{pkg_name}_{branch}" repo_path = j.sals.fs.join_paths(DOWNLOADED_PACKAGES_PATH, repo_dir) repo_url = f"{url.scheme}://{url.hostname}/{org}/{repo}" # delete repo dir if exists j.sals.fs.rmtree(repo_path) j.tools.git.clone_repo(url=repo_url, dest=repo_path, branch_or_tag=branch) toml_paths = list( j.sals.fs.walk(repo_path, "*", filter_fun=lambda x: str(x).endswith(f"{pkg_name}/package.toml")) ) if not toml_paths: raise j.exceptions.Value(f"couldn't find {pkg_name}/package.toml in {repo_path}") path_for_package_toml = toml_paths[0] package_path = j.sals.fs.parent(path_for_package_toml) path = package_path admins = kwargs.pop("admins", []) package = Package( path=path, default_domain=self.threebot.domain, default_email=self.threebot.email, giturl=giturl, kwargs=kwargs, admins=admins, ) # TODO: adding under the same name if same path and same giturl should be fine, no? # if package.name in self.packages: # raise j.exceptions.Value(f"Package with name {package.name} already exists") # execute package install method package.install(**kwargs) # install package if threebot is started if self.threebot.started: self.install(package) self.threebot.nginx.reload() self.packages[package.name] = { "name": package.name, "path": package.path, "giturl": package.giturl, "kwargs": package.kwargs, "admins": package.admins, "ui_name": package.ui_name, } self.save() # Return updated package info return {package.name: self.packages[package.name]} def delete(self, package_name): if package_name in DEFAULT_PACKAGES: raise j.exceptions.Value("cannot delete default packages") package = self.get(package_name) if not package: raise j.exceptions.NotFound(f"{package_name} package not found") # remove bottle servers rack_servers = list(self.threebot.rack._servers) for bottle_server in rack_servers: if bottle_server.startswith(f"{package_name}_"): self.threebot.rack.remove(bottle_server) # stop background services if package.services_dir: for service in package.services: self.threebot.services.stop_service(service["name"]) if self.threebot.started: # unregister gedis actors gedis_actors = list(self.threebot.gedis._loaded_actors.keys()) for actor in gedis_actors: if actor.startswith(f"{package_name}_"): self.threebot.gedis._system_actor.unregister_actor(actor) # unload chats try: if package.chats_dir: self.threebot.chatbot.unload(package.chats_dir) except Exception as e: j.logger.warning(f"Couldn't unload the chats of package {package_name}, this is the exception {str(e)}") # reload nginx self.threebot.nginx.reload() # execute package uninstall method package.uninstall() self.packages.pop(package_name) self.save() def install(self, package): """install and apply package configrations Args: package ([package object]): get package object using [self.get(package_name)] Returns: [dict]: [package info] """ sys.path.append(package.path + "/../") # TODO to be changed package.preinstall() for static_dir in package.static_dirs: path = package.resolve_staticdir_location(static_dir) if not j.sals.fs.exists(path): raise j.exceptions.NotFound(f"Cannot find static dir {path}") # add bottle servers # we first merge all apps of a package into a single app # then mount this app on threebot main app # this will work with multiple non-standalone apps package_app = j.servers.appserver.make_main_app() for bottle_server in package.bottle_servers: path = j.sals.fs.join_paths(package.path, bottle_server["file_path"]) if not j.sals.fs.exists(path): raise j.exceptions.NotFound(f"Cannot find bottle server path {path}") standalone = bottle_server.get("standalone", False) if standalone: bottle_wsgi_server = package.get_bottle_server(path, bottle_server["host"], bottle_server["port"]) self.threebot.rack.add(f"{package.name}_{bottle_server['name']}", bottle_wsgi_server) else: bottle_app = package.get_package_bottle_app(path) package_app.merge(bottle_app) if package_app.routes: j.logger.info(f"registering {package.name} package app") self.threebot.mainapp.mount(f"/{package.name}", package_app) # register gedis actors if package.actors_dir: for actor in package.actors: self.threebot.gedis._system_actor.register_actor(actor["name"], actor["path"], force_reload=True) # add chatflows actors if package.chats_dir: self.threebot.chatbot.load(package.chats_dir) # start background services if package.services_dir: for service in package.services: self.threebot.services.add_service(service["name"], service["path"]) j.logger.info(f"starting rack") # start servers self.threebot.rack.start() j.logger.info(f"applying nginx config") # apply nginx configuration package.nginx_config.apply() j.logger.info(f"starting package") # execute package start method package.start() j.logger.info(f"reloading gedis") self.threebot.gedis_http.client.reload() j.logger.info(f"reloading nginx") self.threebot.nginx.reload() def reload(self, package_name): if self.threebot.started: package = self.get(package_name) if not package: raise j.exceptions.NotFound(f"{package_name} package not found") if package.services_dir: for service in package.services: self.threebot.services.stop_service(service["name"]) self.install(package) self.threebot.nginx.reload() self.save() else: raise j.exceptions.Runtime("Can't reload package. Threebot server is not started") # Return updated package info return {package.name: self.packages[package.name]} def _install_all(self): """Install and apply all the packages configurations This method shall not be called directly from the shell, it must be called only from the code on the running Gedis server """ all_packages = self.list_all() for package in all_packages: if package not in DEFAULT_PACKAGES: j.logger.info(f"Configuring package {package}") pkg = self.get(package) if not pkg: j.logger.error(f"can't get package {package}") else: if pkg.path and pkg.is_valid(): self.install(pkg) else: j.logger.error(f"package {package} was installed before but {pkg.path} doesn't exist anymore.") def scan_packages_paths_in_dir(self, path): """Scans all packages in a path in any level and returns list of package paths Args: path (str): root path that has packages on some levels Returns: List[str]: list of all packages available under the path """ filterfun = lambda x: str(x).endswith("package.toml") pkgtoml_paths = j.sals.fs.walk(path, filter_fun=filterfun) pkgs_paths = list(map(lambda x: x.replace("/package.toml", ""), pkgtoml_paths)) return pkgs_paths def scan_packages_in_dir(self, path): """Gets a dict from packages names to packages paths existing under a path that may have jumpscale packages at any level. Args: path (str): root path that has packages on some levels Returns: Dict[package_name, package_path]: dict of all packages available under the path """ pkgname_to_path = {} for p in self.scan_packages_paths_in_dir(path): basename = j.sals.fs.basename(p).strip() if basename: pkgname_to_path[basename] = p return pkgname_to_pathAncestors
- Base
 - types.SimpleNamespace
 
Instance variables
var packages- 
getter method this property
will call
_get_value, which would if the value is already defined and will get the default value if notReturns
any- the field value
 
Expand source code
def getter(self): """ getter method this property will call `_get_value`, which would if the value is already defined and will get the default value if not Returns: any: the field value """ return self._get_value(name, field) var threebot- 
Expand source code
@property def threebot(self): if self._threebot is None: self._threebot = j.servers.threebot.get() return self._threebot 
Methods
def add(self, path: str = None, giturl: str = None, **kwargs)- 
Expand source code
def add(self, path: str = None, giturl: str = None, **kwargs): # first check if public repo # TODO: Check if package already exists if not any([path, giturl]) or all([path, giturl]): raise j.exceptions.Value("either path or giturl is required") pkg_name = "" if giturl: url = urlparse(giturl) url_parts = url.path.lstrip("/").split("/") if len(url_parts) == 2: pkg_name = url_parts[1].strip("/") j.logger.debug( f"user didn't pass a URL containing branch {giturl}, try to guess (master, main, development) in order" ) if j.tools.http.get(f"{giturl}/tree/master").status_code == 200: url_parts.extend(["tree", "master"]) elif j.tools.http.get(f"{giturl}/tree/main").status_code == 200: url_parts.extend(["tree", "main"]) elif j.tools.http.get(f"{giturl}/tree/development").status_code == 200: url_parts.extend(["tree", "development"]) else: raise j.exceptions.Value(f"couldn't guess the branch for {giturl}") else: pkg_name = url_parts[-1].strip("/") if len(url_parts) < 4: raise j.exceptions.Value(f"invalid git URL {giturl}") org, repo, _, branch = url_parts[:4] repo_dir = f"{org}_{repo}_{pkg_name}_{branch}" repo_path = j.sals.fs.join_paths(DOWNLOADED_PACKAGES_PATH, repo_dir) repo_url = f"{url.scheme}://{url.hostname}/{org}/{repo}" # delete repo dir if exists j.sals.fs.rmtree(repo_path) j.tools.git.clone_repo(url=repo_url, dest=repo_path, branch_or_tag=branch) toml_paths = list( j.sals.fs.walk(repo_path, "*", filter_fun=lambda x: str(x).endswith(f"{pkg_name}/package.toml")) ) if not toml_paths: raise j.exceptions.Value(f"couldn't find {pkg_name}/package.toml in {repo_path}") path_for_package_toml = toml_paths[0] package_path = j.sals.fs.parent(path_for_package_toml) path = package_path admins = kwargs.pop("admins", []) package = Package( path=path, default_domain=self.threebot.domain, default_email=self.threebot.email, giturl=giturl, kwargs=kwargs, admins=admins, ) # TODO: adding under the same name if same path and same giturl should be fine, no? # if package.name in self.packages: # raise j.exceptions.Value(f"Package with name {package.name} already exists") # execute package install method package.install(**kwargs) # install package if threebot is started if self.threebot.started: self.install(package) self.threebot.nginx.reload() self.packages[package.name] = { "name": package.name, "path": package.path, "giturl": package.giturl, "kwargs": package.kwargs, "admins": package.admins, "ui_name": package.ui_name, } self.save() # Return updated package info return {package.name: self.packages[package.name]} def delete(self, package_name)- 
Expand source code
def delete(self, package_name): if package_name in DEFAULT_PACKAGES: raise j.exceptions.Value("cannot delete default packages") package = self.get(package_name) if not package: raise j.exceptions.NotFound(f"{package_name} package not found") # remove bottle servers rack_servers = list(self.threebot.rack._servers) for bottle_server in rack_servers: if bottle_server.startswith(f"{package_name}_"): self.threebot.rack.remove(bottle_server) # stop background services if package.services_dir: for service in package.services: self.threebot.services.stop_service(service["name"]) if self.threebot.started: # unregister gedis actors gedis_actors = list(self.threebot.gedis._loaded_actors.keys()) for actor in gedis_actors: if actor.startswith(f"{package_name}_"): self.threebot.gedis._system_actor.unregister_actor(actor) # unload chats try: if package.chats_dir: self.threebot.chatbot.unload(package.chats_dir) except Exception as e: j.logger.warning(f"Couldn't unload the chats of package {package_name}, this is the exception {str(e)}") # reload nginx self.threebot.nginx.reload() # execute package uninstall method package.uninstall() self.packages.pop(package_name) self.save() def get(self, package_name)- 
Expand source code
def get(self, package_name): if package_name in self.packages: package_path = self.packages[package_name]["path"] package_giturl = self.packages[package_name]["giturl"] package_kwargs = self.packages[package_name].get("kwargs", {}) package_admins = self.packages[package_name].get("admins", []) return Package( path=package_path, default_domain=self.threebot.domain, default_email=self.threebot.email, default_acme_server_type=self.threebot.acme_server_type, default_acme_server_url=self.threebot.acme_server_url, giturl=package_giturl, kwargs=package_kwargs, admins=package_admins, ) def get_packages(self)- 
Expand source code
def get_packages(self): all_packages = [] # Add installed packages including outer packages for pkg in self.packages: package = self.get(pkg) if package and package.is_valid(): if j.sals.fs.exists(package.path): chatflows = True if package.chats_dir else False all_packages.append( { "name": pkg, "path": package.path, "giturl": package.giturl, "system_package": pkg in DEFAULT_PACKAGES.keys(), "installed": True, "frontend": package.config.get("frontend", False), "chatflows": chatflows, "ui_name": package.ui_name, } ) else: j.logger.error(f"path {package.path} for {pkg} doesn't exist anymore") else: j.logger.error("pkg {pkg} is in self.packages but it's None") # Add uninstalled sdk packages under j.packages for path in set(pkgnamespace.__path__): for pkg in os.listdir(path): pkg_path = j.sals.fs.join_paths(path, pkg) pkgtoml_path = j.sals.fs.join_paths(pkg_path, "package.toml") ui_name = pkg excluded = False with open(pkgtoml_path) as f: conf = j.data.serializers.toml.loads(f.read()) ui_name = conf.get("ui_name", pkg) excluded = conf.get("excluded", False) if pkg not in self.packages and j.sals.fs.exists(pkgtoml_path) and not excluded: all_packages.append( { "name": pkg, "path": j.sals.fs.dirname(getattr(j.packages, pkg).__file__), "giturl": "", "system_package": pkg in DEFAULT_PACKAGES.keys(), "installed": False, "ui_name": ui_name, } ) return all_packages def install(self, package)- 
install and apply package configrations
Args
package:[package object]- get package object using [self.get(package_name)]
 
Returns
[dict]- [package info]
 
Expand source code
def install(self, package): """install and apply package configrations Args: package ([package object]): get package object using [self.get(package_name)] Returns: [dict]: [package info] """ sys.path.append(package.path + "/../") # TODO to be changed package.preinstall() for static_dir in package.static_dirs: path = package.resolve_staticdir_location(static_dir) if not j.sals.fs.exists(path): raise j.exceptions.NotFound(f"Cannot find static dir {path}") # add bottle servers # we first merge all apps of a package into a single app # then mount this app on threebot main app # this will work with multiple non-standalone apps package_app = j.servers.appserver.make_main_app() for bottle_server in package.bottle_servers: path = j.sals.fs.join_paths(package.path, bottle_server["file_path"]) if not j.sals.fs.exists(path): raise j.exceptions.NotFound(f"Cannot find bottle server path {path}") standalone = bottle_server.get("standalone", False) if standalone: bottle_wsgi_server = package.get_bottle_server(path, bottle_server["host"], bottle_server["port"]) self.threebot.rack.add(f"{package.name}_{bottle_server['name']}", bottle_wsgi_server) else: bottle_app = package.get_package_bottle_app(path) package_app.merge(bottle_app) if package_app.routes: j.logger.info(f"registering {package.name} package app") self.threebot.mainapp.mount(f"/{package.name}", package_app) # register gedis actors if package.actors_dir: for actor in package.actors: self.threebot.gedis._system_actor.register_actor(actor["name"], actor["path"], force_reload=True) # add chatflows actors if package.chats_dir: self.threebot.chatbot.load(package.chats_dir) # start background services if package.services_dir: for service in package.services: self.threebot.services.add_service(service["name"], service["path"]) j.logger.info(f"starting rack") # start servers self.threebot.rack.start() j.logger.info(f"applying nginx config") # apply nginx configuration package.nginx_config.apply() j.logger.info(f"starting package") # execute package start method package.start() j.logger.info(f"reloading gedis") self.threebot.gedis_http.client.reload() j.logger.info(f"reloading nginx") self.threebot.nginx.reload() def list_all(self)- 
Expand source code
def list_all(self): return list(self.packages.keys()) def reload(self, package_name)- 
Expand source code
def reload(self, package_name): if self.threebot.started: package = self.get(package_name) if not package: raise j.exceptions.NotFound(f"{package_name} package not found") if package.services_dir: for service in package.services: self.threebot.services.stop_service(service["name"]) self.install(package) self.threebot.nginx.reload() self.save() else: raise j.exceptions.Runtime("Can't reload package. Threebot server is not started") # Return updated package info return {package.name: self.packages[package.name]} def scan_packages_in_dir(self, path)- 
Gets a dict from packages names to packages paths existing under a path that may have jumpscale packages at any level.
Args
path:str- root path that has packages on some levels
 
Returns
Dict[package_name, package_path]- dict of all packages available under the path
 
Expand source code
def scan_packages_in_dir(self, path): """Gets a dict from packages names to packages paths existing under a path that may have jumpscale packages at any level. Args: path (str): root path that has packages on some levels Returns: Dict[package_name, package_path]: dict of all packages available under the path """ pkgname_to_path = {} for p in self.scan_packages_paths_in_dir(path): basename = j.sals.fs.basename(p).strip() if basename: pkgname_to_path[basename] = p return pkgname_to_path def scan_packages_paths_in_dir(self, path)- 
Scans all packages in a path in any level and returns list of package paths
Args
path:str- root path that has packages on some levels
 
Returns
List[str]- list of all packages available under the path
 
Expand source code
def scan_packages_paths_in_dir(self, path): """Scans all packages in a path in any level and returns list of package paths Args: path (str): root path that has packages on some levels Returns: List[str]: list of all packages available under the path """ filterfun = lambda x: str(x).endswith("package.toml") pkgtoml_paths = j.sals.fs.walk(path, filter_fun=filterfun) pkgs_paths = list(map(lambda x: x.replace("/package.toml", ""), pkgtoml_paths)) return pkgs_paths 
Inherited members
 class ThreebotServer (*args, **kwargs)- 
A simple attribute-based namespace.
SimpleNamespace(**kwargs)
base class implementation for any class with fields which supports getting/setting raw data for any instance fields.
any instance can have an optional name and a parent.
class Person(Base): name = fields.String() age = fields.Float() p = Person(name="ahmed", age="19") print(p.name, p.age)Args
parent_:Base, optional- parent instance. Defaults to None.
 instance_name_:str, optional- instance name. Defaults to None.
 **values- any given field values to initiate the instance with
 
Expand source code
class ThreebotServer(Base): _package_manager = fields.Factory(PackageManager) domain = fields.String() email = fields.String() acme_server_type = fields.Enum(AcmeServer) acme_server_url = fields.URL() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._rack = None self._gedis = None self._db = None self._gedis_http = None self._services = None self._packages = None self._started = False self._nginx = None self._redis = None self.rack.add(GEDIS, self.gedis) self.rack.add(GEDIS_HTTP, self.gedis_http.gevent_server) self.rack.add(SERVICE_MANAGER, self.services) def is_running(self): nginx_running = self.nginx.is_running() redis_running = self.redis.cmd.is_running() or j.sals.nettools.wait_connection_test( "127.0.0.1", 6379, timeout=1 ) gedis_running = j.sals.nettools.wait_connection_test("127.0.0.1", 16000, timeout=1) return nginx_running and redis_running and gedis_running @property def started(self): return self._started @property def nginx(self): if self._nginx is None: self._nginx = j.tools.nginx.get("default") return self._nginx @property def redis(self): if self._redis is None: self._redis = j.tools.redis.get("default") return self._redis @property def db(self): if self._db is None: self._db = j.core.db return self._db @property def rack(self): if self._rack is None: self._rack = j.servers.rack return self._rack @property def gedis(self): if self._gedis is None: self._gedis = j.servers.gedis.get("threebot") return self._gedis @property def gedis_http(self): if self._gedis_http is None: self._gedis_http = j.servers.gedis_http.get("threebot") return self._gedis_http @property def services(self): if self._services is None: self._services = j.tools.servicemanager.get("threebot") return self._services @property def chatbot(self): return self.gedis._loaded_actors.get("chatflows_chatbot") @property def packages(self): if self._packages is None: self._packages = self._package_manager.get(self.instance_name) return self._packages def check_dependencies(self): install_msg = "Visit https://github.com/threefoldtech/js-sdk/blob/development/docs/wiki/quick_start.md for installation guide" if not self.nginx.installed: raise j.exceptions.NotFound(f"nginx is not installed.\n{install_msg}") ret = shutil.which("certbot") if not ret: raise j.exceptions.NotFound(f"certbot is not installed.\n{install_msg}") rc, out, err = j.sals.process.execute("certbot plugins") if "* nginx" not in out: raise j.exceptions.NotFound(f"python-certbot-nginx is not installed.\n{install_msg}") if not self.redis.installed: raise j.exceptions.NotFound(f"redis is not installed.\n{install_msg}") ret = shutil.which("tmux") if not ret: raise j.exceptions.NotFound(f"tmux is not installed.\n{install_msg}") ret = shutil.which("git") if not ret: raise j.exceptions.NotFound(f"git is not installed.\n{install_msg}") def start(self, wait: bool = False, cert: bool = True): # start default servers in the rack # handle signals for signal_type in (signal.SIGTERM, signal.SIGINT, signal.SIGKILL): gevent.signal_handler(signal_type, self.stop) # mark app as started if self.is_running(): return self.check_dependencies() self.redis.start() self.nginx.start() j.sals.nginx.get(self.nginx.server_name).cert = cert self.mainapp = j.servers.appserver.make_main_app() self.rack.start() j.logger.register(f"threebot_{self.instance_name}") # add default packages for package_name in DEFAULT_PACKAGES: j.logger.info(f"Configuring package {package_name}") try: package = self.packages.get(package_name) self.packages.install(package) except Exception as e: self.stop() raise j.core.exceptions.Runtime( f"Error happened during getting or installing {package_name} package, the detailed error is {str(e)}" ) from e # install all package j.logger.info("Adding packages") self.packages._install_all() j.logger.info("jsappserver") self.jsappserver = WSGIServer(("localhost", 31000), apply_main_middlewares(self.mainapp)) j.logger.info("rack add") self.rack.add(f"appserver", self.jsappserver) j.logger.info("Reloading nginx") self.nginx.reload() # mark server as started self._started = True j.logger.info(f"routes: {self.mainapp.routes}") j.logger.info(f"Threebot is running at http://localhost:{PORTS.HTTP} and https://localhost:{PORTS.HTTPS}") self.rack.start(wait=wait) # to keep the server running def stop(self): server_packages = self.packages.list_all() for package_name in server_packages: package = self.packages.get(package_name) package.stop() self.nginx.stop() # mark app as stopped, do this before stopping redis j.logger.unregister() self.rack.stop() self.redis.stop() self._started = FalseAncestors
- Base
 - types.SimpleNamespace
 
Instance variables
var acme_server_type- 
getter method this property
will call
_get_value, which would if the value is already defined and will get the default value if notReturns
any- the field value
 
Expand source code
def getter(self): """ getter method this property will call `_get_value`, which would if the value is already defined and will get the default value if not Returns: any: the field value """ return self._get_value(name, field) var acme_server_url- 
getter method this property
will call
_get_value, which would if the value is already defined and will get the default value if notReturns
any- the field value
 
Expand source code
def getter(self): """ getter method this property will call `_get_value`, which would if the value is already defined and will get the default value if not Returns: any: the field value """ return self._get_value(name, field) var chatbot- 
Expand source code
@property def chatbot(self): return self.gedis._loaded_actors.get("chatflows_chatbot") var db- 
Expand source code
@property def db(self): if self._db is None: self._db = j.core.db return self._db var domain- 
getter method this property
will call
_get_value, which would if the value is already defined and will get the default value if notReturns
any- the field value
 
Expand source code
def getter(self): """ getter method this property will call `_get_value`, which would if the value is already defined and will get the default value if not Returns: any: the field value """ return self._get_value(name, field) var email- 
getter method this property
will call
_get_value, which would if the value is already defined and will get the default value if notReturns
any- the field value
 
Expand source code
def getter(self): """ getter method this property will call `_get_value`, which would if the value is already defined and will get the default value if not Returns: any: the field value """ return self._get_value(name, field) var gedis- 
Expand source code
@property def gedis(self): if self._gedis is None: self._gedis = j.servers.gedis.get("threebot") return self._gedis var gedis_http- 
Expand source code
@property def gedis_http(self): if self._gedis_http is None: self._gedis_http = j.servers.gedis_http.get("threebot") return self._gedis_http var nginx- 
Expand source code
@property def nginx(self): if self._nginx is None: self._nginx = j.tools.nginx.get("default") return self._nginx var packages- 
Expand source code
@property def packages(self): if self._packages is None: self._packages = self._package_manager.get(self.instance_name) return self._packages var rack- 
Expand source code
@property def rack(self): if self._rack is None: self._rack = j.servers.rack return self._rack var redis- 
Expand source code
@property def redis(self): if self._redis is None: self._redis = j.tools.redis.get("default") return self._redis var services- 
Expand source code
@property def services(self): if self._services is None: self._services = j.tools.servicemanager.get("threebot") return self._services var started- 
Expand source code
@property def started(self): return self._started 
Methods
def check_dependencies(self)- 
Expand source code
def check_dependencies(self): install_msg = "Visit https://github.com/threefoldtech/js-sdk/blob/development/docs/wiki/quick_start.md for installation guide" if not self.nginx.installed: raise j.exceptions.NotFound(f"nginx is not installed.\n{install_msg}") ret = shutil.which("certbot") if not ret: raise j.exceptions.NotFound(f"certbot is not installed.\n{install_msg}") rc, out, err = j.sals.process.execute("certbot plugins") if "* nginx" not in out: raise j.exceptions.NotFound(f"python-certbot-nginx is not installed.\n{install_msg}") if not self.redis.installed: raise j.exceptions.NotFound(f"redis is not installed.\n{install_msg}") ret = shutil.which("tmux") if not ret: raise j.exceptions.NotFound(f"tmux is not installed.\n{install_msg}") ret = shutil.which("git") if not ret: raise j.exceptions.NotFound(f"git is not installed.\n{install_msg}") def is_running(self)- 
Expand source code
def is_running(self): nginx_running = self.nginx.is_running() redis_running = self.redis.cmd.is_running() or j.sals.nettools.wait_connection_test( "127.0.0.1", 6379, timeout=1 ) gedis_running = j.sals.nettools.wait_connection_test("127.0.0.1", 16000, timeout=1) return nginx_running and redis_running and gedis_running def start(self, wait: bool = False, cert: bool = True)- 
Expand source code
def start(self, wait: bool = False, cert: bool = True): # start default servers in the rack # handle signals for signal_type in (signal.SIGTERM, signal.SIGINT, signal.SIGKILL): gevent.signal_handler(signal_type, self.stop) # mark app as started if self.is_running(): return self.check_dependencies() self.redis.start() self.nginx.start() j.sals.nginx.get(self.nginx.server_name).cert = cert self.mainapp = j.servers.appserver.make_main_app() self.rack.start() j.logger.register(f"threebot_{self.instance_name}") # add default packages for package_name in DEFAULT_PACKAGES: j.logger.info(f"Configuring package {package_name}") try: package = self.packages.get(package_name) self.packages.install(package) except Exception as e: self.stop() raise j.core.exceptions.Runtime( f"Error happened during getting or installing {package_name} package, the detailed error is {str(e)}" ) from e # install all package j.logger.info("Adding packages") self.packages._install_all() j.logger.info("jsappserver") self.jsappserver = WSGIServer(("localhost", 31000), apply_main_middlewares(self.mainapp)) j.logger.info("rack add") self.rack.add(f"appserver", self.jsappserver) j.logger.info("Reloading nginx") self.nginx.reload() # mark server as started self._started = True j.logger.info(f"routes: {self.mainapp.routes}") j.logger.info(f"Threebot is running at http://localhost:{PORTS.HTTP} and https://localhost:{PORTS.HTTPS}") self.rack.start(wait=wait) # to keep the server running def stop(self)- 
Expand source code
def stop(self): server_packages = self.packages.list_all() for package_name in server_packages: package = self.packages.get(package_name) package.stop() self.nginx.stop() # mark app as stopped, do this before stopping redis j.logger.unregister() self.rack.stop() self.redis.stop() self._started = False 
Inherited members