// Copyright Epic Games, Inc. All Rights Reserved. #include "config.h" #include "diag/logging.h" #include #include #include #include #pragma warning(push) #pragma warning(disable : 4267) // warning C4267: '=': conversion from 'size_t' to 'US', possible loss of data #include #pragma warning(pop) #include #include #include #include #if ZEN_PLATFORM_WINDOWS // Used for getting My Documents for default data directory # include # pragma comment(lib, "shell32.lib") std::filesystem::path PickDefaultStateDirectory() { // Pick sensible default WCHAR myDocumentsDir[MAX_PATH]; HRESULT hRes = SHGetFolderPathW(NULL, CSIDL_PERSONAL /* My Documents */, NULL, SHGFP_TYPE_CURRENT, /* out */ myDocumentsDir); if (SUCCEEDED(hRes)) { wcscat_s(myDocumentsDir, L"\\zen"); return myDocumentsDir; } return L""; } #else std::filesystem::path PickDefaultStateDirectory() { return std::filesystem::path("~/.zen"); } #endif UpstreamCachePolicy ParseUpstreamCachePolicy(std::string_view Options) { if (Options == "readonly") { return UpstreamCachePolicy::Read; } else if (Options == "writeonly") { return UpstreamCachePolicy::Write; } else if (Options == "disabled") { return UpstreamCachePolicy::Disabled; } else { return UpstreamCachePolicy::ReadWrite; } } void ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptions); void ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions) { #if ZEN_WITH_HTTPSYS const char* DefaultHttp = "httpsys"; #else const char* DefaultHttp = "asio"; #endif cxxopts::Options options("zenserver", "Zen Server"); options.add_options()("dedicated", "Enable dedicated server mode", cxxopts::value(ServerOptions.IsDedicated)->default_value("false")); options.add_options()("d, debug", "Enable debugging", cxxopts::value(ServerOptions.IsDebug)->default_value("false")); options.add_options()("help", "Show command line help"); options.add_options()("t, test", "Enable test mode", cxxopts::value(ServerOptions.IsTest)->default_value("false")); options.add_options()("log-id", "Specify id for adding context to log output", cxxopts::value(ServerOptions.LogId)); options.add_options()("data-dir", "Specify persistence root", cxxopts::value(ServerOptions.DataDir)); options.add_options()("content-dir", "Frontend content directory", cxxopts::value(ServerOptions.ContentDir)); options.add_options()("abslog", "Path to log file", cxxopts::value(ServerOptions.AbsLogFile)); options.add_options()("config", "Path to Lua config file", cxxopts::value(ServerOptions.ConfigFile)); options .add_option("lifetime", "", "owner-pid", "Specify owning process id", cxxopts::value(ServerOptions.OwnerPid), ""); options.add_option("lifetime", "", "child-id", "Specify id which can be used to signal parent", cxxopts::value(ServerOptions.ChildId), ""); #if ZEN_PLATFORM_WINDOWS options.add_option("lifetime", "", "install", "Install zenserver as a Windows service", cxxopts::value(ServerOptions.InstallService), ""); options.add_option("lifetime", "", "uninstall", "Uninstall zenserver as a Windows service", cxxopts::value(ServerOptions.UninstallService), ""); #endif options.add_option("network", "", "http", "Select HTTP server implementation (asio|httpsys|null)", cxxopts::value(ServerOptions.HttpServerClass)->default_value(DefaultHttp), ""); options.add_option("network", "p", "port", "Select HTTP port", cxxopts::value(ServerOptions.BasePort)->default_value("1337"), ""); #if ZEN_ENABLE_MESH options.add_option("network", "m", "mesh", "Enable mesh network", cxxopts::value(ServerOptions.MeshEnabled)->default_value("false"), ""); #endif options.add_option("diagnostics", "", "crash", "Simulate a crash", cxxopts::value(ServerOptions.ShouldCrash)->default_value("false"), ""); std::string UpstreamCachePolicyOptions; options.add_option("cache", "", "upstream-cache-policy", "", cxxopts::value(UpstreamCachePolicyOptions)->default_value(""), "Upstream cache policy (readwrite|readonly|writeonly|disabled)"); options.add_option("cache", "", "upstream-jupiter-url", "URL to a Jupiter instance", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.Url)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-oauth-url", "URL to the OAuth provier", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthProvider)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-oauth-clientid", "The OAuth client ID", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientId)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-oauth-clientsecret", "The OAuth client secret", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientSecret)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-namespace", "The Common Blob Store API namespace", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.Namespace)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-namespace-ddc", "The lecacy DDC namespace", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.DdcNamespace)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-prod", "Enable Jupiter upstream caching using production settings", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.UseProductionSettings)->default_value("false"), ""); options.add_option("cache", "", "upstream-jupiter-dev", "Enable Jupiter upstream caching using development settings", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.UseDevelopmentSettings)->default_value("false"), ""); options.add_option("cache", "", "upstream-jupiter-use-legacy-ddc", "Whether to store derived data using the legacy endpoint", cxxopts::value(ServerOptions.UpstreamCacheConfig.JupiterConfig.UseLegacyDdc)->default_value("false"), ""); options.add_option("cache", "", "upstream-zen-url", "URL to remote Zen server. Use a comma separated list to choose the one with the best latency.", cxxopts::value>(ServerOptions.UpstreamCacheConfig.ZenConfig.Urls)->default_value(""), ""); options.add_option("cache", "", "upstream-zen-dns", "DNS that resolves to one or more Zen server instance(s)", cxxopts::value>(ServerOptions.UpstreamCacheConfig.ZenConfig.Dns)->default_value(""), ""); options.add_option("cache", "", "upstream-thread-count", "Number of threads used for upstream procsssing", cxxopts::value(ServerOptions.UpstreamCacheConfig.UpstreamThreadCount)->default_value("4"), ""); options.add_option("cache", "", "upstream-stats", "Collect performance metrics for upstream endpoints", cxxopts::value(ServerOptions.UpstreamCacheConfig.StatsEnabled)->default_value("false"), ""); options.add_option("cache", "", "upstream-connect-timeout-ms", "Connect timeout in millisecond(s). Default 5000 ms.", cxxopts::value(ServerOptions.UpstreamCacheConfig.ConnectTimeoutMilliseconds)->default_value("5000"), ""); options.add_option("cache", "", "upstream-timeout-ms", "Timeout in millisecond(s). Default 0 ms", cxxopts::value(ServerOptions.UpstreamCacheConfig.TimeoutMilliseconds)->default_value("0"), ""); options.add_option("gc", "", "gc-enabled", "Whether garbage collection is enabled or not.", cxxopts::value(ServerOptions.GcConfig.Enabled)->default_value("true"), ""); options.add_option("gc", "", "gc-interval-seconds", "Garbage collection interval. Default is 1h.", cxxopts::value(ServerOptions.GcConfig.IntervalSeconds)->default_value("3600"), ""); try { auto result = options.parse(argc, argv); if (result.count("help")) { zen::logging::ConsoleLog().info("{}", options.help()); zen::logging::ConsoleLog().info("Press any key to exit!"); _getch(); exit(0); } ServerOptions.UpstreamCacheConfig.CachePolicy = ParseUpstreamCachePolicy(UpstreamCachePolicyOptions); if (!ServerOptions.ConfigFile.empty()) { ParseConfigFile(ServerOptions.ConfigFile, ServerOptions); } else { ParseConfigFile(ServerOptions.DataDir / "zen_cfg.lua", ServerOptions); } } catch (cxxopts::OptionParseException& e) { zen::logging::ConsoleLog().error("Error parsing zenserver arguments: {}\n\n{}", e.what(), options.help()); throw; } if (ServerOptions.DataDir.empty()) { ServerOptions.DataDir = PickDefaultStateDirectory(); } } void ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptions) { using namespace fmt::literals; zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path.native().c_str()); if (LuaScript) { sol::state lua; lua.open_libraries(sol::lib::base); lua.set_function("getenv", [&](const std::string env) -> sol::object { std::wstring EnvVarValue; size_t RequiredSize = 0; std::wstring EnvWide = zen::Utf8ToWide(env); _wgetenv_s(&RequiredSize, nullptr, 0, EnvWide.c_str()); if (RequiredSize == 0) return sol::make_object(lua, sol::lua_nil); EnvVarValue.resize(RequiredSize); _wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str()); return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str())); }); try { sol::load_result config = lua.load(std::string_view((const char*)LuaScript.Data(), LuaScript.Size()), "zen_cfg"); if (!config.valid()) { sol::error err = config; std::string ErrorString = sol::to_string(config.status()); throw std::runtime_error("{} error: {}"_format(ErrorString, err.what())); } config(); } catch (std::exception& e) { throw std::runtime_error("failed to load config script ('{}'): {}"_format(Path, e.what()).c_str()); } if (sol::optional ServerConfig = lua["server"]) { if (ServerOptions.DataDir.empty()) { if (sol::optional Opt = ServerConfig.value()["datadir"]) { ServerOptions.DataDir = Opt.value(); } } if (ServerOptions.ContentDir.empty()) { if (sol::optional Opt = ServerConfig.value()["contentdir"]) { ServerOptions.ContentDir = Opt.value(); } } if (ServerOptions.AbsLogFile.empty()) { if (sol::optional Opt = ServerConfig.value()["abslog"]) { ServerOptions.AbsLogFile = Opt.value(); } } ServerOptions.IsDebug = ServerConfig->get_or("debug", ServerOptions.IsDebug); } if (sol::optional NetworkConfig = lua["network"]) { if (sol::optional Opt = NetworkConfig.value()["httpserverclass"]) { ServerOptions.HttpServerClass = Opt.value(); } ServerOptions.BasePort = NetworkConfig->get_or("port", ServerOptions.BasePort); #if ZEN_ENABLE_MESH ServerOptions.MeshEnabled = NetworkConfig->get_or("meshenabled", ServerOptions.MeshEnabled); #endif } auto UpdateStringValueFromConfig = [](const sol::table& Table, std::string_view Key, std::string& OutValue) { // Update the specified config value unless it has been set, i.e. from command line if (auto MaybeValue = Table.get>(Key); MaybeValue.has_value() && OutValue.empty()) { OutValue = MaybeValue.value(); } }; if (sol::optional StructuredCacheConfig = lua["cache"]) { ServerOptions.StructuredCacheEnabled = StructuredCacheConfig->get_or("enable", ServerOptions.StructuredCacheEnabled); if (auto UpstreamConfig = StructuredCacheConfig->get>("upstream")) { std::string Policy = UpstreamConfig->get_or("policy", std::string()); ServerOptions.UpstreamCacheConfig.CachePolicy = ParseUpstreamCachePolicy(Policy); ServerOptions.UpstreamCacheConfig.UpstreamThreadCount = UpstreamConfig->get_or("upstreamthreadcount", ServerOptions.UpstreamCacheConfig.UpstreamThreadCount); if (auto JupiterConfig = UpstreamConfig->get>("jupiter")) { UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("url"), ServerOptions.UpstreamCacheConfig.JupiterConfig.Url); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("oauthprovider"), ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthProvider); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("oauthclientid"), ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientId); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("oauthclientsecret"), ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientSecret); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("namespace"), ServerOptions.UpstreamCacheConfig.JupiterConfig.Namespace); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("ddcnamespace"), ServerOptions.UpstreamCacheConfig.JupiterConfig.DdcNamespace); ServerOptions.UpstreamCacheConfig.JupiterConfig.UseDevelopmentSettings = JupiterConfig->get_or("usedevelopmentsettings", ServerOptions.UpstreamCacheConfig.JupiterConfig.UseDevelopmentSettings); ServerOptions.UpstreamCacheConfig.JupiterConfig.UseLegacyDdc = JupiterConfig->get_or("uselegacyddc", ServerOptions.UpstreamCacheConfig.JupiterConfig.UseLegacyDdc); }; if (auto ZenConfig = UpstreamConfig->get>("zen")) { if (auto Url = ZenConfig.value().get>("url")) { ServerOptions.UpstreamCacheConfig.ZenConfig.Urls.push_back(Url.value()); } else if (auto Urls = ZenConfig.value().get>("url")) { for (const auto& Kv : Urls.value()) { ServerOptions.UpstreamCacheConfig.ZenConfig.Urls.push_back(Kv.second.as()); } } if (auto Dns = ZenConfig.value().get>("dns")) { ServerOptions.UpstreamCacheConfig.ZenConfig.Dns.push_back(Dns.value()); } else if (auto DnsArray = ZenConfig.value().get>("dns")) { for (const auto& Kv : DnsArray.value()) { ServerOptions.UpstreamCacheConfig.ZenConfig.Dns.push_back(Kv.second.as()); } } } } } } }